Page MenuHomePhorge

No OneTemporary

diff --git a/resources/sql/autopatches/20161003.cal.02.parameters.sql b/resources/sql/autopatches/20161003.cal.02.parameters.sql
new file mode 100644
index 0000000000..9fd3783a5b
--- /dev/null
+++ b/resources/sql/autopatches/20161003.cal.02.parameters.sql
@@ -0,0 +1,5 @@
+ALTER TABLE {$NAMESPACE}_calendar.calendar_event
+ ADD parameters LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT};
+
+UPDATE {$NAMESPACE}_calendar.calendar_event
+ SET parameters = '{}' WHERE parameters = '';
diff --git a/src/applications/calendar/storage/PhabricatorCalendarEvent.php b/src/applications/calendar/storage/PhabricatorCalendarEvent.php
index 66af8dfe5f..ab563dfde1 100644
--- a/src/applications/calendar/storage/PhabricatorCalendarEvent.php
+++ b/src/applications/calendar/storage/PhabricatorCalendarEvent.php
@@ -1,1025 +1,1084 @@
<?php
final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
implements
PhabricatorPolicyInterface,
PhabricatorProjectInterface,
PhabricatorMarkupInterface,
PhabricatorApplicationTransactionInterface,
PhabricatorSubscribableInterface,
PhabricatorTokenReceiverInterface,
PhabricatorDestructibleInterface,
PhabricatorMentionableInterface,
PhabricatorFlaggableInterface,
PhabricatorSpacesInterface,
PhabricatorFulltextInterface,
PhabricatorConduitResultInterface {
protected $name;
protected $hostPHID;
protected $dateFrom;
protected $dateTo;
protected $allDayDateFrom;
protected $allDayDateTo;
protected $description;
protected $isCancelled;
protected $isAllDay;
protected $icon;
protected $mailKey;
protected $isStub;
protected $isRecurring = 0;
protected $recurrenceFrequency = array();
protected $recurrenceEndDate;
private $isGhostEvent = false;
protected $instanceOfEventPHID;
protected $sequenceIndex;
protected $viewPolicy;
protected $editPolicy;
protected $spacePHID;
protected $utcInitialEpoch;
protected $utcUntilEpoch;
protected $utcInstanceEpoch;
+ protected $parameters = array();
private $parentEvent = self::ATTACHABLE;
private $invitees = self::ATTACHABLE;
private $viewerDateFrom;
private $viewerDateTo;
private $viewerTimezone;
// Frequency Constants
const FREQUENCY_DAILY = 'daily';
const FREQUENCY_WEEKLY = 'weekly';
const FREQUENCY_MONTHLY = 'monthly';
const FREQUENCY_YEARLY = 'yearly';
public static function initializeNewCalendarEvent(PhabricatorUser $actor) {
$app = id(new PhabricatorApplicationQuery())
->setViewer($actor)
->withClasses(array('PhabricatorCalendarApplication'))
->executeOne();
$view_default = PhabricatorCalendarEventDefaultViewCapability::CAPABILITY;
$edit_default = PhabricatorCalendarEventDefaultEditCapability::CAPABILITY;
$view_policy = $app->getPolicy($view_default);
$edit_policy = $app->getPolicy($edit_default);
$now = PhabricatorTime::getNow();
$start = new DateTime('@'.$now);
$start->setTimeZone($actor->getTimeZone());
$start->setTime($start->format('H'), 0, 0);
$start->modify('+1 hour');
$end = id(clone $start)->modify('+1 hour');
$epoch_min = $start->format('U');
$epoch_max = $end->format('U');
$now_date = new DateTime('@'.$now);
$now_min = id(clone $now_date)->setTime(0, 0)->format('U');
$now_max = id(clone $now_date)->setTime(23, 59)->format('U');
$default_icon = 'fa-calendar';
+ $datetime_start = PhutilCalendarAbsoluteDateTime::newFromEpoch(
+ $now,
+ $actor->getTimezoneIdentifier());
+ $datetime_end = $datetime_start->newRelativeDateTime('PT1H');
+
return id(new PhabricatorCalendarEvent())
->setHostPHID($actor->getPHID())
->setIsCancelled(0)
->setIsAllDay(0)
->setIsStub(0)
->setIsRecurring(0)
->setRecurrenceFrequency(
array(
'rule' => self::FREQUENCY_WEEKLY,
))
->setIcon($default_icon)
->setViewPolicy($view_policy)
->setEditPolicy($edit_policy)
->setSpacePHID($actor->getDefaultSpacePHID())
->attachInvitees(array())
->setDateFrom($epoch_min)
->setDateTo($epoch_max)
->setAllDayDateFrom($now_min)
->setAllDayDateTo($now_max)
+ ->setStartDateTime($datetime_start)
+ ->setEndDateTime($datetime_end)
->applyViewerTimezone($actor);
}
private function newChild(PhabricatorUser $actor, $sequence) {
if (!$this->isParentEvent()) {
throw new Exception(
pht(
'Unable to generate a new child event for an event which is not '.
'a recurring parent event!'));
}
$child = id(new self())
->setIsCancelled(0)
->setIsStub(0)
->setInstanceOfEventPHID($this->getPHID())
->setSequenceIndex($sequence)
->setIsRecurring(true)
->setRecurrenceFrequency($this->getRecurrenceFrequency())
->attachParentEvent($this);
return $child->copyFromParent($actor);
}
protected function readField($field) {
static $inherit = array(
'hostPHID' => true,
'isAllDay' => true,
'icon' => true,
'spacePHID' => true,
'viewPolicy' => true,
'editPolicy' => true,
'name' => true,
'description' => true,
);
// Read these fields from the parent event instead of this event. For
// example, we want any changes to the parent event's name to apply to
// the child.
if (isset($inherit[$field])) {
if ($this->getIsStub()) {
// TODO: This should be unconditional, but the execution order of
// CalendarEventQuery and applyViewerTimezone() are currently odd.
if ($this->parentEvent !== self::ATTACHABLE) {
return $this->getParentEvent()->readField($field);
}
}
}
return parent::readField($field);
}
public function copyFromParent(PhabricatorUser $actor) {
if (!$this->isChildEvent()) {
throw new Exception(
pht(
'Unable to copy from parent event: this is not a child event.'));
}
$parent = $this->getParentEvent();
$this
->setHostPHID($parent->getHostPHID())
->setIsAllDay($parent->getIsAllDay())
->setIcon($parent->getIcon())
->setSpacePHID($parent->getSpacePHID())
->setViewPolicy($parent->getViewPolicy())
->setEditPolicy($parent->getEditPolicy())
->setName($parent->getName())
->setDescription($parent->getDescription());
$sequence = $this->getSequenceIndex();
$duration = $this->getDuration();
$epochs = $parent->getSequenceIndexEpochs($actor, $sequence, $duration);
$this
->setDateFrom($epochs['dateFrom'])
->setDateTo($epochs['dateTo'])
->setAllDayDateFrom($epochs['allDayDateFrom'])
->setAllDayDateTo($epochs['allDayDateTo']);
return $this;
}
public function isValidSequenceIndex(PhabricatorUser $viewer, $sequence) {
try {
$this->getSequenceIndexEpochs($viewer, $sequence, $this->getDuration());
return true;
} catch (Exception $ex) {
return false;
}
}
private function getSequenceIndexEpochs(
PhabricatorUser $viewer,
$sequence,
$duration) {
$frequency = $this->getFrequencyUnit();
$modify_key = '+'.$sequence.' '.$frequency;
$date = $this->getDateFrom();
$date_time = PhabricatorTime::getDateTimeFromEpoch($date, $viewer);
$date_time->modify($modify_key);
$date = $date_time->format('U');
$end_date = $this->getRecurrenceEndDate();
if ($end_date && $date > $end_date) {
throw new Exception(
pht(
'Sequence "%s" is invalid for this event: it would occur after '.
'the event stops repeating.',
$sequence));
}
$utc = new DateTimeZone('UTC');
$allday_from = $this->getAllDayDateFrom();
$allday_date = new DateTime('@'.$allday_from, $utc);
$allday_date->setTimeZone($utc);
$allday_date->modify($modify_key);
$allday_min = $allday_date->format('U');
$allday_duration = ($this->getAllDayDateTo() - $allday_from);
return array(
'dateFrom' => $date,
'dateTo' => $date + $duration,
'allDayDateFrom' => $allday_min,
'allDayDateTo' => $allday_min + $allday_duration,
);
}
public function newStub(PhabricatorUser $actor, $sequence) {
$stub = $this->newChild($actor, $sequence);
$stub->setIsStub(1);
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
$stub->save();
unset($unguarded);
$stub->applyViewerTimezone($actor);
return $stub;
}
public function newGhost(PhabricatorUser $actor, $sequence) {
$ghost = $this->newChild($actor, $sequence);
$ghost
->setIsGhostEvent(true)
->makeEphemeral();
$ghost->applyViewerTimezone($actor);
return $ghost;
}
public function getViewerDateFrom() {
if ($this->viewerDateFrom === null) {
throw new PhutilInvalidStateException('applyViewerTimezone');
}
return $this->viewerDateFrom;
}
public function getViewerDateTo() {
if ($this->viewerDateTo === null) {
throw new PhutilInvalidStateException('applyViewerTimezone');
}
return $this->viewerDateTo;
}
public function applyViewerTimezone(PhabricatorUser $viewer) {
if (!$this->getIsAllDay()) {
$this->viewerDateFrom = $this->getDateFrom();
$this->viewerDateTo = $this->getDateTo();
} else {
$zone = $viewer->getTimeZone();
$this->viewerDateFrom = $this->getDateEpochForTimezone(
$this->getAllDayDateFrom(),
new DateTimeZone('UTC'),
'Y-m-d',
null,
$zone);
$this->viewerDateTo = $this->getDateEpochForTimezone(
$this->getAllDayDateTo(),
new DateTimeZone('UTC'),
'Y-m-d 23:59:00',
null,
$zone);
}
$this->viewerTimezone = $viewer->getTimezoneIdentifier();
return $this;
}
public function getDuration() {
return $this->getDateTo() - $this->getDateFrom();
}
public function getDateEpochForTimezone(
$epoch,
$src_zone,
$format,
$adjust,
$dst_zone) {
$src = new DateTime('@'.$epoch);
$src->setTimeZone($src_zone);
if (strlen($adjust)) {
$adjust = ' '.$adjust;
}
$dst = new DateTime($src->format($format).$adjust, $dst_zone);
return $dst->format('U');
}
public function updateUTCEpochs() {
// The "intitial" epoch is the start time of the event, in UTC.
$start_date = $this->newStartDateTime()
->setViewerTimezone('UTC');
$start_epoch = $start_date->getEpoch();
$this->setUTCInitialEpoch($start_epoch);
// The "until" epoch is the last UTC epoch on which any instance of this
// event occurs. For infinitely recurring events, it is `null`.
if (!$this->getIsRecurring()) {
$end_date = $this->newEndDateTime()
->setViewerTimezone('UTC');
$until_epoch = $end_date->getEpoch();
} else {
$until_epoch = null;
$until_date = $this->newUntilDateTime()
->setViewerTimezone('UTC');
if ($until_date) {
$duration = $this->newDuration();
$until_epoch = id(new PhutilCalendarRelativeDateTime())
->setOrigin($until_date)
->setDuration($duration)
->getEpoch();
}
}
$this->setUTCUntilEpoch($until_epoch);
// The "instance" epoch is a property of instances of recurring events.
// It's the original UTC epoch on which the instance started. Usually that
// is the same as the start date, but they may be different if the instance
// has been edited.
// The ICS format uses this value (original start time) to identify event
// instances, and must do so because it allows additional arbitrary
// instances to be added (with "RDATE").
$instance_epoch = null;
$instance_date = $this->newInstanceDateTime();
if ($instance_date) {
$instance_epoch = $instance_date
->setViewerTimezone('UTC')
->getEpoch();
}
$this->setUTCInstanceEpoch($instance_epoch);
return $this;
}
public function save() {
if (!$this->mailKey) {
$this->mailKey = Filesystem::readRandomCharacters(20);
}
$this->updateUTCEpochs();
return parent::save();
}
/**
* Get the event start epoch for evaluating invitee availability.
*
* When assessing availability, we pretend events start earlier than they
* really do. This allows us to mark users away for the entire duration of a
* series of back-to-back meetings, even if they don't strictly overlap.
*
* @return int Event start date for availability caches.
*/
public function getDateFromForCache() {
return ($this->getViewerDateFrom() - phutil_units('15 minutes in seconds'));
}
protected function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_COLUMN_SCHEMA => array(
'name' => 'text',
'dateFrom' => 'epoch',
'dateTo' => 'epoch',
'allDayDateFrom' => 'epoch',
'allDayDateTo' => 'epoch',
'description' => 'text',
'isCancelled' => 'bool',
'isAllDay' => 'bool',
'icon' => 'text32',
'mailKey' => 'bytes20',
'isRecurring' => 'bool',
'recurrenceEndDate' => 'epoch?',
'instanceOfEventPHID' => 'phid?',
'sequenceIndex' => 'uint32?',
'isStub' => 'bool',
'utcInitialEpoch' => 'epoch',
'utcUntilEpoch' => 'epoch?',
'utcInstanceEpoch' => 'epoch?',
),
self::CONFIG_KEY_SCHEMA => array(
'key_date' => array(
'columns' => array('dateFrom', 'dateTo'),
),
'key_instance' => array(
'columns' => array('instanceOfEventPHID', 'sequenceIndex'),
'unique' => true,
),
'key_epoch' => array(
'columns' => array('utcInitialEpoch', 'utcUntilEpoch'),
),
'key_rdate' => array(
'columns' => array('instanceOfEventPHID', 'utcInstanceEpoch'),
'unique' => true,
),
),
self::CONFIG_SERIALIZATION => array(
'recurrenceFrequency' => self::SERIALIZATION_JSON,
+ 'parameters' => self::SERIALIZATION_JSON,
),
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
PhabricatorCalendarEventPHIDType::TYPECONST);
}
public function getMonogram() {
return 'E'.$this->getID();
}
public function getInvitees() {
return $this->assertAttached($this->invitees);
}
public function attachInvitees(array $invitees) {
$this->invitees = $invitees;
return $this;
}
public function getInviteePHIDsForEdit() {
$invitees = array();
foreach ($this->getInvitees() as $invitee) {
if ($invitee->isUninvited()) {
continue;
}
$invitees[] = $invitee->getInviteePHID();
}
return $invitees;
}
public function getUserInviteStatus($phid) {
$invitees = $this->getInvitees();
$invitees = mpull($invitees, null, 'getInviteePHID');
$invited = idx($invitees, $phid);
if (!$invited) {
return PhabricatorCalendarEventInvitee::STATUS_UNINVITED;
}
$invited = $invited->getStatus();
return $invited;
}
public function getIsUserAttending($phid) {
$attending_status = PhabricatorCalendarEventInvitee::STATUS_ATTENDING;
$old_status = $this->getUserInviteStatus($phid);
$is_attending = ($old_status == $attending_status);
return $is_attending;
}
public function getIsUserInvited($phid) {
$uninvited_status = PhabricatorCalendarEventInvitee::STATUS_UNINVITED;
$declined_status = PhabricatorCalendarEventInvitee::STATUS_DECLINED;
$status = $this->getUserInviteStatus($phid);
if ($status == $uninvited_status || $status == $declined_status) {
return false;
}
return true;
}
public function getIsGhostEvent() {
return $this->isGhostEvent;
}
public function setIsGhostEvent($is_ghost_event) {
$this->isGhostEvent = $is_ghost_event;
return $this;
}
public function getFrequencyRule() {
return idx($this->recurrenceFrequency, 'rule');
}
public function getFrequencyUnit() {
$frequency = $this->getFrequencyRule();
switch ($frequency) {
case 'daily':
return 'day';
case 'weekly':
return 'week';
case 'monthly':
return 'month';
case 'yearly':
return 'year';
default:
return 'day';
}
}
public function getURI() {
if ($this->getIsGhostEvent()) {
$base = $this->getParentEvent()->getURI();
$sequence = $this->getSequenceIndex();
return "{$base}/{$sequence}/";
}
return '/'.$this->getMonogram();
}
public function getParentEvent() {
return $this->assertAttached($this->parentEvent);
}
public function attachParentEvent($event) {
$this->parentEvent = $event;
return $this;
}
public function isParentEvent() {
return ($this->getIsRecurring() && !$this->getInstanceOfEventPHID());
}
public function isChildEvent() {
return ($this->instanceOfEventPHID !== null);
}
public function isCancelledEvent() {
if ($this->getIsCancelled()) {
return true;
}
if ($this->isChildEvent()) {
if ($this->getParentEvent()->getIsCancelled()) {
return true;
}
}
return false;
}
public function renderEventDate(
PhabricatorUser $viewer,
$show_end) {
if ($show_end) {
$min_date = PhabricatorTime::getDateTimeFromEpoch(
$this->getViewerDateFrom(),
$viewer);
$max_date = PhabricatorTime::getDateTimeFromEpoch(
$this->getViewerDateTo(),
$viewer);
$min_day = $min_date->format('Y m d');
$max_day = $max_date->format('Y m d');
$show_end_date = ($min_day != $max_day);
} else {
$show_end_date = false;
}
$min_epoch = $this->getViewerDateFrom();
$max_epoch = $this->getViewerDateTo();
if ($this->getIsAllDay()) {
if ($show_end_date) {
return pht(
'%s - %s, All Day',
phabricator_date($min_epoch, $viewer),
phabricator_date($max_epoch, $viewer));
} else {
return pht(
'%s, All Day',
phabricator_date($min_epoch, $viewer));
}
} else if ($show_end_date) {
return pht(
'%s - %s',
phabricator_datetime($min_epoch, $viewer),
phabricator_datetime($max_epoch, $viewer));
} else if ($show_end) {
return pht(
'%s - %s',
phabricator_datetime($min_epoch, $viewer),
phabricator_time($max_epoch, $viewer));
} else {
return pht(
'%s',
phabricator_datetime($min_epoch, $viewer));
}
}
public function getDisplayIcon(PhabricatorUser $viewer) {
if ($this->isCancelledEvent()) {
return 'fa-times';
}
if ($viewer->isLoggedIn()) {
$status = $this->getUserInviteStatus($viewer->getPHID());
switch ($status) {
case PhabricatorCalendarEventInvitee::STATUS_ATTENDING:
return 'fa-check-circle';
case PhabricatorCalendarEventInvitee::STATUS_INVITED:
return 'fa-user-plus';
case PhabricatorCalendarEventInvitee::STATUS_DECLINED:
return 'fa-times';
}
}
return $this->getIcon();
}
public function getDisplayIconColor(PhabricatorUser $viewer) {
if ($this->isCancelledEvent()) {
return 'red';
}
if ($viewer->isLoggedIn()) {
$status = $this->getUserInviteStatus($viewer->getPHID());
switch ($status) {
case PhabricatorCalendarEventInvitee::STATUS_ATTENDING:
return 'green';
case PhabricatorCalendarEventInvitee::STATUS_INVITED:
return 'green';
case PhabricatorCalendarEventInvitee::STATUS_DECLINED:
return 'grey';
}
}
return 'bluegrey';
}
public function getDisplayIconLabel(PhabricatorUser $viewer) {
if ($this->isCancelledEvent()) {
return pht('Cancelled');
}
if ($viewer->isLoggedIn()) {
$status = $this->getUserInviteStatus($viewer->getPHID());
switch ($status) {
case PhabricatorCalendarEventInvitee::STATUS_ATTENDING:
return pht('Attending');
case PhabricatorCalendarEventInvitee::STATUS_INVITED:
return pht('Invited');
case PhabricatorCalendarEventInvitee::STATUS_DECLINED:
return pht('Declined');
}
}
return null;
}
public function getICSFilename() {
return $this->getMonogram().'.ics';
}
public function newIntermediateEventNode(PhabricatorUser $viewer) {
$base_uri = new PhutilURI(PhabricatorEnv::getProductionURI('/'));
$domain = $base_uri->getDomain();
$uid = $this->getPHID().'@'.$domain;
$created = $this->getDateCreated();
$created = PhutilCalendarAbsoluteDateTime::newFromEpoch($created);
$modified = $this->getDateModified();
$modified = PhutilCalendarAbsoluteDateTime::newFromEpoch($modified);
$date_start = $this->newStartDateTime();
$date_end = $this->newEndDateTime();
if ($this->getIsAllDay()) {
$date_start->setIsAllDay(true);
$date_end->setIsAllDay(true);
}
$host_phid = $this->getHostPHID();
$invitees = $this->getInvitees();
foreach ($invitees as $key => $invitee) {
if ($invitee->isUninvited()) {
unset($invitees[$key]);
}
}
$phids = array();
$phids[] = $host_phid;
foreach ($invitees as $invitee) {
$phids[] = $invitee->getInviteePHID();
}
$handles = $viewer->loadHandles($phids);
$host_handle = $handles[$host_phid];
$host_name = $host_handle->getFullName();
$host_uri = $host_handle->getURI();
$host_uri = PhabricatorEnv::getURI($host_uri);
$organizer = id(new PhutilCalendarUserNode())
->setName($host_name)
->setURI($host_uri);
$attendees = array();
foreach ($invitees as $invitee) {
$invitee_phid = $invitee->getInviteePHID();
$invitee_handle = $handles[$invitee_phid];
$invitee_name = $invitee_handle->getFullName();
$invitee_uri = $invitee_handle->getURI();
$invitee_uri = PhabricatorEnv::getURI($invitee_uri);
switch ($invitee->getStatus()) {
case PhabricatorCalendarEventInvitee::STATUS_ATTENDING:
$status = PhutilCalendarUserNode::STATUS_ACCEPTED;
break;
case PhabricatorCalendarEventInvitee::STATUS_DECLINED:
$status = PhutilCalendarUserNode::STATUS_DECLINED;
break;
case PhabricatorCalendarEventInvitee::STATUS_INVITED:
default:
$status = PhutilCalendarUserNode::STATUS_INVITED;
break;
}
$attendees[] = id(new PhutilCalendarUserNode())
->setName($invitee_name)
->setURI($invitee_uri)
->setStatus($status);
}
$node = id(new PhutilCalendarEventNode())
->setUID($uid)
->setName($this->getName())
->setDescription($this->getDescription())
->setCreatedDateTime($created)
->setModifiedDateTime($modified)
->setStartDateTime($date_start)
->setEndDateTime($date_end)
->setOrganizer($organizer)
->setAttendees($attendees);
return $node;
}
public function newStartDateTime() {
+ $datetime = $this->getParameter('startDateTime');
+ if ($datetime) {
+ return $this->newDateTimeFromDictionary($datetime);
+ }
+
$epoch = $this->getDateFrom();
return $this->newDateTimeFromEpoch($epoch);
}
public function newEndDateTime() {
+ $datetime = $this->getParameter('endDateTime');
+ if ($datetime) {
+ return $this->newDateTimeFromDictionary($datetime);
+ }
+
$epoch = $this->getDateTo();
return $this->newDateTimeFromEpoch($epoch);
}
public function newUntilDateTime() {
+ $datetime = $this->getParameter('untilDateTime');
+ if ($datetime) {
+ return $this->newDateTimeFromDictionary($datetime);
+ }
+
$epoch = $this->getRecurrenceEndDate();
if (!$epoch) {
return null;
}
return $this->newDateTimeFromEpoch($epoch);
}
public function newDuration() {
return id(new PhutilCalendarDuration())
->setSeconds($this->getDuration());
}
public function newInstanceDateTime() {
if (!$this->getIsRecurring()) {
return null;
}
$epochs = $this->getParent()->getSequenceIndexEpochs(
new PhabricatorUser(),
$this->getSequenceIndex(),
$this->getDuration());
$epoch = $epochs['dateFrom'];
return $this->newDateTimeFromEpoch($epoch);
}
private function newDateTimeFromEpoch($epoch) {
$datetime = PhutilCalendarAbsoluteDateTime::newFromEpoch($epoch);
+ if ($this->getIsAllDay()) {
+ $datetime->setIsAllDay(true);
+ }
+
+ return $this->newDateTimeFromDateTime($datetime);
+ }
+
+ private function newDateTimeFromDictionary(array $dict) {
+ $datetime = PhutilCalendarAbsoluteDateTime::newFromDictionary($dict);
+ return $this->newDateTimeFromDateTime($datetime);
+ }
+
+ private function newDateTimeFromDateTime(PhutilCalendarDateTime $datetime) {
$viewer_timezone = $this->viewerTimezone;
if ($viewer_timezone) {
$datetime->setViewerTimezone($viewer_timezone);
}
- if ($this->getIsAllDay()) {
- $datetime->setIsAllDay(true);
- }
-
return $datetime;
}
+ public function getParameter($key, $default = null) {
+ return idx($this->parameters, $key, $default);
+ }
+
+ public function setParameter($key, $value) {
+ $this->parameters[$key] = $value;
+ return $this;
+ }
+
+ public function setStartDateTime(PhutilCalendarDateTime $datetime) {
+ return $this->setParameter(
+ 'startDateTime',
+ $datetime->newAbsoluteDateTime()->toDictionary());
+ }
+
+ public function setEndDateTime(PhutilCalendarDateTime $datetime) {
+ return $this->setParameter(
+ 'endDateTime',
+ $datetime->newAbsoluteDateTime()->toDictionary());
+ }
+
+ public function setUntilDateTime(PhutilCalendarDateTime $datetime) {
+ return $this->setParameter(
+ 'untilDateTime',
+ $datetime->newAbsoluteDateTime()->toDictionary());
+ }
/* -( Markup Interface )--------------------------------------------------- */
/**
* @task markup
*/
public function getMarkupFieldKey($field) {
$hash = PhabricatorHash::digest($this->getMarkupText($field));
$id = $this->getID();
return "calendar:T{$id}:{$field}:{$hash}";
}
/**
* @task markup
*/
public function getMarkupText($field) {
return $this->getDescription();
}
/**
* @task markup
*/
public function newMarkupEngine($field) {
return PhabricatorMarkupEngine::newCalendarMarkupEngine();
}
/**
* @task markup
*/
public function didMarkupText(
$field,
$output,
PhutilMarkupEngine $engine) {
return $output;
}
/**
* @task markup
*/
public function shouldUseMarkupCache($field) {
return (bool)$this->getID();
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
);
}
public function getPolicy($capability) {
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
return $this->getViewPolicy();
case PhabricatorPolicyCapability::CAN_EDIT:
return $this->getEditPolicy();
}
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
// The host of an event can always view and edit it.
$user_phid = $this->getHostPHID();
if ($user_phid) {
$viewer_phid = $viewer->getPHID();
if ($viewer_phid == $user_phid) {
return true;
}
}
if ($capability == PhabricatorPolicyCapability::CAN_VIEW) {
$status = $this->getUserInviteStatus($viewer->getPHID());
if ($status == PhabricatorCalendarEventInvitee::STATUS_INVITED ||
$status == PhabricatorCalendarEventInvitee::STATUS_ATTENDING ||
$status == PhabricatorCalendarEventInvitee::STATUS_DECLINED) {
return true;
}
}
return false;
}
public function describeAutomaticCapability($capability) {
return pht(
'The host of an event can always view and edit it. Users who are '.
'invited to an event can always view it.');
}
/* -( PhabricatorApplicationTransactionInterface )------------------------- */
public function getApplicationTransactionEditor() {
return new PhabricatorCalendarEventEditor();
}
public function getApplicationTransactionObject() {
return $this;
}
public function getApplicationTransactionTemplate() {
return new PhabricatorCalendarEventTransaction();
}
public function willRenderTimeline(
PhabricatorApplicationTransactionView $timeline,
AphrontRequest $request) {
return $timeline;
}
/* -( PhabricatorSubscribableInterface )----------------------------------- */
public function isAutomaticallySubscribed($phid) {
return ($phid == $this->getHostPHID());
}
/* -( PhabricatorTokenReceiverInterface )---------------------------------- */
public function getUsersToNotifyOfTokenGiven() {
return array($this->getHostPHID());
}
/* -( PhabricatorDestructibleInterface )----------------------------------- */
public function destroyObjectPermanently(
PhabricatorDestructionEngine $engine) {
$this->openTransaction();
$this->delete();
$this->saveTransaction();
}
/* -( PhabricatorSpacesInterface )----------------------------------------- */
public function getSpacePHID() {
return $this->spacePHID;
}
/* -( PhabricatorFulltextInterface )--------------------------------------- */
public function newFulltextEngine() {
return new PhabricatorCalendarEventFulltextEngine();
}
/* -( PhabricatorConduitResultInterface )---------------------------------- */
public function getFieldSpecificationsForConduit() {
return array(
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('name')
->setType('string')
->setDescription(pht('The name of the event.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('description')
->setType('string')
->setDescription(pht('The event description.')),
);
}
public function getFieldValuesForConduit() {
return array(
'name' => $this->getName(),
'description' => $this->getDescription(),
);
}
public function getConduitSearchAttachments() {
return array();
}
}
diff --git a/src/applications/calendar/xaction/PhabricatorCalendarEventAllDayTransaction.php b/src/applications/calendar/xaction/PhabricatorCalendarEventAllDayTransaction.php
index 1bcad16adf..eac229c395 100644
--- a/src/applications/calendar/xaction/PhabricatorCalendarEventAllDayTransaction.php
+++ b/src/applications/calendar/xaction/PhabricatorCalendarEventAllDayTransaction.php
@@ -1,46 +1,65 @@
<?php
final class PhabricatorCalendarEventAllDayTransaction
extends PhabricatorCalendarEventTransactionType {
const TRANSACTIONTYPE = 'calendar.allday';
public function generateOldValue($object) {
return (int)$object->getIsAllDay();
}
public function generateNewValue($object, $value) {
return (int)$value;
}
public function applyInternalEffects($object, $value) {
$object->setIsAllDay($value);
+
+ // Adjust the flags on any other dates the event has.
+ $keys = array(
+ 'startDateTime',
+ 'endDateTime',
+ 'untilDateTime',
+ );
+
+ foreach ($keys as $key) {
+ $dict = $object->getParameter($key);
+ if (!$dict) {
+ continue;
+ }
+
+ $datetime = PhutilCalendarAbsoluteDateTime::newFromDictionary($dict);
+ $datetime->setIsAllDay($value);
+
+ $object->setParameter($key, $datetime->toDictionary());
+ }
}
public function getTitle() {
if ($this->getNewValue()) {
return pht(
'%s changed this as an all day event.',
$this->renderAuthor());
} else {
return pht(
'%s converted this from an all day event.',
$this->renderAuthor());
}
}
public function getTitleForFeed() {
if ($this->getNewValue()) {
return pht(
'%s changed %s to an all day event.',
$this->renderAuthor(),
$this->renderObject());
} else {
return pht(
'%s converted %s from an all day event.',
$this->renderAuthor(),
$this->renderObject());
}
}
}
diff --git a/src/applications/calendar/xaction/PhabricatorCalendarEventEndDateTransaction.php b/src/applications/calendar/xaction/PhabricatorCalendarEventEndDateTransaction.php
index fc7f9859ba..91d9b396ff 100644
--- a/src/applications/calendar/xaction/PhabricatorCalendarEventEndDateTransaction.php
+++ b/src/applications/calendar/xaction/PhabricatorCalendarEventEndDateTransaction.php
@@ -1,47 +1,53 @@
<?php
final class PhabricatorCalendarEventEndDateTransaction
extends PhabricatorCalendarEventDateTransaction {
const TRANSACTIONTYPE = 'calendar.enddate';
public function generateOldValue($object) {
return $object->getDateTo();
}
public function applyInternalEffects($object, $value) {
$actor = $this->getActor();
+ // TODO: DEPRECATED.
$object->setDateTo($value);
-
$object->setAllDayDateTo(
$object->getDateEpochForTimezone(
$value,
$actor->getTimeZone(),
'Y-m-d 23:59:00',
null,
new DateTimeZone('UTC')));
+
+ $datetime = PhutilCalendarAbsoluteDateTime::newFromEpoch(
+ $value,
+ $actor->getTimezoneIdentifier());
+ $datetime->setIsAllDay($object->getIsAllDay());
+ $object->setEndDateTime($datetime);
}
public function getTitle() {
return pht(
'%s changed the end date for this event from %s to %s.',
$this->renderAuthor(),
$this->renderOldDate(),
$this->renderNewDate());
}
public function getTitleForFeed() {
return pht(
'%s changed the end date for %s from %s to %s.',
$this->renderAuthor(),
$this->renderObject(),
$this->renderOldDate(),
$this->renderNewDate());
}
protected function getInvalidDateMessage() {
return pht('End date is invalid.');
}
}
diff --git a/src/applications/calendar/xaction/PhabricatorCalendarEventStartDateTransaction.php b/src/applications/calendar/xaction/PhabricatorCalendarEventStartDateTransaction.php
index 9823ec336f..90e3b9c9c5 100644
--- a/src/applications/calendar/xaction/PhabricatorCalendarEventStartDateTransaction.php
+++ b/src/applications/calendar/xaction/PhabricatorCalendarEventStartDateTransaction.php
@@ -1,47 +1,53 @@
<?php
final class PhabricatorCalendarEventStartDateTransaction
extends PhabricatorCalendarEventDateTransaction {
const TRANSACTIONTYPE = 'calendar.startdate';
public function generateOldValue($object) {
return $object->getDateFrom();
}
public function applyInternalEffects($object, $value) {
$actor = $this->getActor();
+ // TODO: DEPRECATED.
$object->setDateFrom($value);
-
$object->setAllDayDateFrom(
$object->getDateEpochForTimezone(
$value,
$actor->getTimeZone(),
'Y-m-d',
null,
new DateTimeZone('UTC')));
+
+ $datetime = PhutilCalendarAbsoluteDateTime::newFromEpoch(
+ $value,
+ $actor->getTimezoneIdentifier());
+ $datetime->setIsAllDay($object->getIsAllDay());
+ $object->setStartDateTime($datetime);
}
public function getTitle() {
return pht(
'%s changed the start date for this event from %s to %s.',
$this->renderAuthor(),
$this->renderOldDate(),
$this->renderNewDate());
}
public function getTitleForFeed() {
return pht(
'%s changed the start date for %s from %s to %s.',
$this->renderAuthor(),
$this->renderObject(),
$this->renderOldDate(),
$this->renderNewDate());
}
protected function getInvalidDateMessage() {
return pht('Start date is invalid.');
}
}
diff --git a/src/applications/calendar/xaction/PhabricatorCalendarEventUntilDateTransaction.php b/src/applications/calendar/xaction/PhabricatorCalendarEventUntilDateTransaction.php
index 454d391293..4cffda8ff6 100644
--- a/src/applications/calendar/xaction/PhabricatorCalendarEventUntilDateTransaction.php
+++ b/src/applications/calendar/xaction/PhabricatorCalendarEventUntilDateTransaction.php
@@ -1,35 +1,44 @@
<?php
final class PhabricatorCalendarEventUntilDateTransaction
extends PhabricatorCalendarEventDateTransaction {
const TRANSACTIONTYPE = 'calendar.recurrenceenddate';
public function generateOldValue($object) {
return $object->getRecurrenceEndDate();
}
public function applyInternalEffects($object, $value) {
+ $actor = $this->getActor();
+
+ // TODO: DEPRECATED.
$object->setRecurrenceEndDate($value);
+
+ $datetime = PhutilCalendarAbsoluteDateTime::newFromEpoch(
+ $value,
+ $actor->getTimezoneIdentifier());
+ $datetime->setIsAllDay($object->getIsAllDay());
+ $object->setUntilDateTime($datetime);
}
public function getTitle() {
return pht(
'%s changed this event to repeat until %s.',
$this->renderAuthor(),
$this->renderNewDate());
}
public function getTitleForFeed() {
return pht(
'%s changed %s to repeat until %s.',
$this->renderAuthor(),
$this->renderObject(),
$this->renderNewDate());
}
protected function getInvalidDateMessage() {
return pht('Repeat until date is invalid.');
}
}

File Metadata

Mime Type
text/x-diff
Expires
Sun, Jan 19, 16:59 (2 w, 5 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1126723
Default Alt Text
(37 KB)

Event Timeline