Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F2891582
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Advanced/Developer...
View Handle
View Hovercard
Size
45 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php b/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php
index 655037c140..d22d34bee7 100644
--- a/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php
+++ b/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php
@@ -1,335 +1,332 @@
<?php
final class PhabricatorCalendarEventEditor
extends PhabricatorApplicationTransactionEditor {
public function getEditorApplicationClass() {
return 'PhabricatorCalendarApplication';
}
public function getEditorObjectsDescription() {
return pht('Calendar');
}
protected function shouldApplyInitialEffects(
PhabricatorLiskDAO $object,
array $xactions) {
return true;
}
protected function applyInitialEffects(
PhabricatorLiskDAO $object,
array $xactions) {
$actor = $this->requireActor();
if ($object->getIsStub()) {
$this->materializeStub($object);
}
}
private function materializeStub(PhabricatorCalendarEvent $event) {
if (!$event->getIsStub()) {
throw new Exception(
pht('Can not materialize an event stub: this event is not a stub.'));
}
$actor = $this->getActor();
+
+ $invitees = $event->getInvitees();
+
$event->copyFromParent($actor);
$event->setIsStub(0);
- $invitees = $event->getParentEvent()->getInvitees();
-
- $new_invitees = array();
- foreach ($invitees as $invitee) {
- $invitee = id(new PhabricatorCalendarEventInvitee())
- ->setEventPHID($event->getPHID())
- ->setInviteePHID($invitee->getInviteePHID())
- ->setInviterPHID($invitee->getInviterPHID())
- ->setStatus($invitee->getStatus())
- ->save();
-
- $new_invitees[] = $invitee;
- }
+ $event->openTransaction();
+ $event->save();
+ foreach ($invitees as $invitee) {
+ $invitee
+ ->setEventPHID($event->getPHID())
+ ->save();
+ }
+ $event->saveTransaction();
- $event->save();
- $event->attachInvitees($new_invitees);
+ $event->attachInvitees($invitees);
}
public function getTransactionTypes() {
$types = parent::getTransactionTypes();
$types[] = PhabricatorTransactions::TYPE_COMMENT;
$types[] = PhabricatorTransactions::TYPE_VIEW_POLICY;
$types[] = PhabricatorTransactions::TYPE_EDIT_POLICY;
return $types;
}
protected function adjustObjectForPolicyChecks(
PhabricatorLiskDAO $object,
array $xactions) {
$copy = parent::adjustObjectForPolicyChecks($object, $xactions);
foreach ($xactions as $xaction) {
switch ($xaction->getTransactionType()) {
case PhabricatorCalendarEventHostTransaction::TRANSACTIONTYPE:
$copy->setHostPHID($xaction->getNewValue());
break;
case PhabricatorCalendarEventInviteTransaction::TRANSACTIONTYPE:
PhabricatorPolicyRule::passTransactionHintToRule(
$copy,
new PhabricatorCalendarEventInviteesPolicyRule(),
array_fuse($xaction->getNewValue()));
break;
}
}
return $copy;
}
protected function applyFinalEffects(
PhabricatorLiskDAO $object,
array $xactions) {
// Clear the availability caches for users whose availability is affected
// by this edit.
$invalidate_all = false;
$invalidate_phids = array();
foreach ($xactions as $xaction) {
switch ($xaction->getTransactionType()) {
case PhabricatorCalendarEventUntilDateTransaction::TRANSACTIONTYPE:
case PhabricatorCalendarEventStartDateTransaction::TRANSACTIONTYPE:
case PhabricatorCalendarEventEndDateTransaction::TRANSACTIONTYPE:
case PhabricatorCalendarEventCancelTransaction::TRANSACTIONTYPE:
case PhabricatorCalendarEventAllDayTransaction::TRANSACTIONTYPE:
// For these kinds of changes, we need to invalidate the availabilty
// caches for all attendees.
$invalidate_all = true;
break;
case PhabricatorCalendarEventAcceptTransaction::TRANSACTIONTYPE:
case PhabricatorCalendarEventDeclineTransaction::TRANSACTIONTYPE:
$acting_phid = $this->getActingAsPHID();
$invalidate_phids[$acting_phid] = $acting_phid;
break;
case PhabricatorCalendarEventInviteTransaction::TRANSACTIONTYPE:
foreach ($xaction->getNewValue() as $phid => $ignored) {
$invalidate_phids[$phid] = $phid;
}
break;
}
}
$phids = mpull($object->getInvitees(), 'getInviteePHID');
$phids = array_fuse($phids);
if (!$invalidate_all) {
$phids = array_select_keys($phids, $invalidate_phids);
}
if ($phids) {
$object->applyViewerTimezone($this->getActor());
$user = new PhabricatorUser();
$conn_w = $user->establishConnection('w');
queryfx(
$conn_w,
'UPDATE %T SET availabilityCacheTTL = NULL
WHERE phid IN (%Ls) AND availabilityCacheTTL >= %d',
$user->getTableName(),
$phids,
$object->getStartDateTimeEpochForCache());
}
return $xactions;
}
protected function validateAllTransactions(
PhabricatorLiskDAO $object,
array $xactions) {
$start_date_xaction =
PhabricatorCalendarEventStartDateTransaction::TRANSACTIONTYPE;
$end_date_xaction =
PhabricatorCalendarEventEndDateTransaction::TRANSACTIONTYPE;
$is_recurrence_xaction =
PhabricatorCalendarEventRecurringTransaction::TRANSACTIONTYPE;
$recurrence_end_xaction =
PhabricatorCalendarEventUntilDateTransaction::TRANSACTIONTYPE;
$start_date = $object->getStartDateTimeEpoch();
$end_date = $object->getEndDateTimeEpoch();
$recurrence_end = $object->getUntilDateTimeEpoch();
$is_recurring = $object->getIsRecurring();
$errors = array();
foreach ($xactions as $xaction) {
if ($xaction->getTransactionType() == $start_date_xaction) {
$start_date = $xaction->getNewValue()->getEpoch();
} else if ($xaction->getTransactionType() == $end_date_xaction) {
$end_date = $xaction->getNewValue()->getEpoch();
} else if ($xaction->getTransactionType() == $recurrence_end_xaction) {
$recurrence_end = $xaction->getNewValue()->getEpoch();
} else if ($xaction->getTransactionType() == $is_recurrence_xaction) {
$is_recurring = $xaction->getNewValue();
}
}
if ($start_date > $end_date) {
$errors[] = new PhabricatorApplicationTransactionValidationError(
$end_date_xaction,
pht('Invalid'),
pht('End date must be after start date.'),
null);
}
if ($recurrence_end && !$is_recurring) {
$errors[] = new PhabricatorApplicationTransactionValidationError(
$recurrence_end_xaction,
pht('Invalid'),
pht('Event must be recurring to have a recurrence end date.').
null);
}
return $errors;
}
protected function shouldPublishFeedStory(
PhabricatorLiskDAO $object,
array $xactions) {
if ($object->isImportedEvent()) {
return false;
}
return true;
}
protected function supportsSearch() {
return true;
}
protected function shouldSendMail(
PhabricatorLiskDAO $object,
array $xactions) {
if ($object->isImportedEvent()) {
return false;
}
return true;
}
protected function getMailSubjectPrefix() {
return pht('[Calendar]');
}
protected function getMailTo(PhabricatorLiskDAO $object) {
$phids = array();
if ($object->getHostPHID()) {
$phids[] = $object->getHostPHID();
}
$phids[] = $this->getActingAsPHID();
$invitees = $object->getInvitees();
foreach ($invitees as $invitee) {
$status = $invitee->getStatus();
if ($status === PhabricatorCalendarEventInvitee::STATUS_ATTENDING
|| $status === PhabricatorCalendarEventInvitee::STATUS_INVITED) {
$phids[] = $invitee->getInviteePHID();
}
}
$phids = array_unique($phids);
return $phids;
}
public function getMailTagsMap() {
return array(
PhabricatorCalendarEventTransaction::MAILTAG_CONTENT =>
pht(
"An event's name, status, invite list, ".
"icon, and description changes."),
PhabricatorCalendarEventTransaction::MAILTAG_RESCHEDULE =>
pht(
"An event's start and end date ".
"and cancellation status changes."),
PhabricatorCalendarEventTransaction::MAILTAG_OTHER =>
pht('Other event activity not listed above occurs.'),
);
}
protected function buildReplyHandler(PhabricatorLiskDAO $object) {
return id(new PhabricatorCalendarReplyHandler())
->setMailReceiver($object);
}
protected function buildMailTemplate(PhabricatorLiskDAO $object) {
$id = $object->getID();
$name = $object->getName();
return id(new PhabricatorMetaMTAMail())
->setSubject("E{$id}: {$name}")
->addHeader('Thread-Topic', "E{$id}: ".$object->getName());
}
protected function buildMailBody(
PhabricatorLiskDAO $object,
array $xactions) {
$description = $object->getDescription();
$body = parent::buildMailBody($object, $xactions);
if (strlen($description)) {
$body->addRemarkupSection(
pht('EVENT DESCRIPTION'),
$description);
}
$body->addLinkSection(
pht('EVENT DETAIL'),
PhabricatorEnv::getProductionURI('/E'.$object->getID()));
$ics_attachment = $this->newICSAttachment($object);
$body->addAttachment($ics_attachment);
return $body;
}
protected function shouldApplyHeraldRules(
PhabricatorLiskDAO $object,
array $xactions) {
return true;
}
protected function buildHeraldAdapter(
PhabricatorLiskDAO $object,
array $xactions) {
return id(new PhabricatorCalendarEventHeraldAdapter())
->setObject($object);
}
private function newICSAttachment(
PhabricatorCalendarEvent $event) {
$actor = $this->getActor();
$ics_data = id(new PhabricatorCalendarICSWriter())
->setViewer($actor)
->setEvents(array($event))
->writeICSDocument();
$ics_attachment = new PhabricatorMetaMTAAttachment(
$ics_data,
$event->getICSFilename(),
'text/calendar');
return $ics_attachment;
}
}
diff --git a/src/applications/calendar/storage/PhabricatorCalendarEvent.php b/src/applications/calendar/storage/PhabricatorCalendarEvent.php
index ce9e28fa17..c5d5c898f9 100644
--- a/src/applications/calendar/storage/PhabricatorCalendarEvent.php
+++ b/src/applications/calendar/storage/PhabricatorCalendarEvent.php
@@ -1,1252 +1,1280 @@
<?php
final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
implements
PhabricatorPolicyInterface,
PhabricatorExtendedPolicyInterface,
PhabricatorProjectInterface,
PhabricatorMarkupInterface,
PhabricatorApplicationTransactionInterface,
PhabricatorSubscribableInterface,
PhabricatorTokenReceiverInterface,
PhabricatorDestructibleInterface,
PhabricatorMentionableInterface,
PhabricatorFlaggableInterface,
PhabricatorSpacesInterface,
PhabricatorFulltextInterface,
PhabricatorConduitResultInterface {
protected $name;
protected $hostPHID;
protected $description;
protected $isCancelled;
protected $isAllDay;
protected $icon;
protected $mailKey;
protected $isStub;
protected $isRecurring = 0;
- private $isGhostEvent = false;
protected $instanceOfEventPHID;
protected $sequenceIndex;
protected $viewPolicy;
protected $editPolicy;
protected $spacePHID;
protected $utcInitialEpoch;
protected $utcUntilEpoch;
protected $utcInstanceEpoch;
protected $parameters = array();
protected $importAuthorPHID;
protected $importSourcePHID;
protected $importUIDIndex;
protected $importUID;
private $parentEvent = self::ATTACHABLE;
private $invitees = self::ATTACHABLE;
private $importSource = self::ATTACHABLE;
private $viewerTimezone;
// TODO: DEPRECATED. Remove once we're sure the migrations worked.
protected $allDayDateFrom;
protected $allDayDateTo;
protected $dateFrom;
protected $dateTo;
protected $recurrenceEndDate;
protected $recurrenceFrequency = array();
+ private $isGhostEvent = false;
+ private $stubInvitees;
+
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();
$default_icon = 'fa-calendar';
$datetime_defaults = self::newDefaultEventDateTimes(
$actor,
$now);
list($datetime_start, $datetime_end) = $datetime_defaults;
return id(new PhabricatorCalendarEvent())
->setDescription('')
->setHostPHID($actor->getPHID())
->setIsCancelled(0)
->setIsAllDay(0)
->setIsStub(0)
->setIsRecurring(0)
->setIcon($default_icon)
->setViewPolicy($view_policy)
->setEditPolicy($edit_policy)
->setSpacePHID($actor->getDefaultSpacePHID())
->attachInvitees(array())
->setDateFrom(0)
->setDateTo(0)
->setAllDayDateFrom(0)
->setAllDayDateTo(0)
->setStartDateTime($datetime_start)
->setEndDateTime($datetime_end)
->attachImportSource(null)
->applyViewerTimezone($actor);
}
public static function newDefaultEventDateTimes(
PhabricatorUser $viewer,
$now) {
$datetime_start = PhutilCalendarAbsoluteDateTime::newFromEpoch(
$now,
$viewer->getTimezoneIdentifier());
// Advance the time by an hour, then round downwards to the nearest hour.
// For example, if it is currently 3:25 PM, we suggest a default start time
// of 4 PM.
$datetime_start = $datetime_start
->newRelativeDateTime('PT1H')
->newAbsoluteDateTime();
$datetime_start->setMinute(0);
$datetime_start->setSecond(0);
// Default the end time to an hour after the start time.
$datetime_end = $datetime_start
->newRelativeDateTime('PT1H')
->newAbsoluteDateTime();
return array($datetime_start, $datetime_end);
}
private function newChild(
PhabricatorUser $actor,
$sequence,
PhutilCalendarDateTime $start = null) {
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)
->attachParentEvent($this)
->setAllDayDateFrom(0)
->setAllDayDateTo(0)
->setDateFrom(0)
->setDateTo(0);
return $child->copyFromParent($actor, $start);
}
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,
PhutilCalendarDateTime $start = null) {
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());
if ($start) {
$start_datetime = $start;
} else {
$sequence = $this->getSequenceIndex();
$start_datetime = $parent->newSequenceIndexDateTime($sequence);
if (!$start_datetime) {
throw new Exception(
pht(
'Sequence "%s" is not valid for event!',
$sequence));
}
}
$duration = $parent->newDuration();
$end_datetime = $start_datetime->newRelativeDateTime($duration);
$this
->setStartDateTime($start_datetime)
->setEndDateTime($end_datetime);
if ($parent->isImportedEvent()) {
$full_uid = $parent->getImportUID().'/'.$start_datetime->getEpoch();
// NOTE: We don't attach the import source because this gets called
// from CalendarEventQuery while building ghosts, before we've loaded
// and attached sources. Possibly this sequence should be flipped.
$this
->setImportAuthorPHID($parent->getImportAuthorPHID())
->setImportSourcePHID($parent->getImportSourcePHID())
->setImportUID($full_uid);
}
return $this;
}
public function isValidSequenceIndex(PhabricatorUser $viewer, $sequence) {
return (bool)$this->newSequenceIndexDateTime($sequence);
}
public function newSequenceIndexDateTime($sequence) {
$set = $this->newRecurrenceSet();
if (!$set) {
return null;
}
$limit = $sequence + 1;
$count = $this->getRecurrenceCount();
if ($count && ($count < $limit)) {
return null;
}
$instances = $set->getEventsBetween(
null,
$this->newUntilDateTime(),
$limit);
return idx($instances, $sequence, null);
}
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,
PhutilCalendarDateTime $start = null) {
$ghost = $this->newChild($actor, $sequence, $start);
$ghost
->setIsGhostEvent(true)
->makeEphemeral();
$ghost->applyViewerTimezone($actor);
return $ghost;
}
public function applyViewerTimezone(PhabricatorUser $viewer) {
$this->viewerTimezone = $viewer->getTimezoneIdentifier();
return $this;
}
public function getDuration() {
return ($this->getEndDateTimeEpoch() - $this->getStartDateTimeEpoch());
}
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();
if ($until_date) {
$until_date->setViewerTimezone('UTC');
$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);
}
$import_uid = $this->getImportUID();
if ($import_uid !== null) {
$index = PhabricatorHash::digestForIndex($import_uid);
} else {
$index = null;
}
$this->setImportUIDIndex($index);
$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 getStartDateTimeEpochForCache() {
$epoch = $this->getStartDateTimeEpoch();
$window = phutil_units('15 minutes in seconds');
return ($epoch - $window);
}
protected function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_COLUMN_SCHEMA => array(
'name' => 'text',
'description' => 'text',
'isCancelled' => 'bool',
'isAllDay' => 'bool',
'icon' => 'text32',
'mailKey' => 'bytes20',
'isRecurring' => 'bool',
'instanceOfEventPHID' => 'phid?',
'sequenceIndex' => 'uint32?',
'isStub' => 'bool',
'utcInitialEpoch' => 'epoch',
'utcUntilEpoch' => 'epoch?',
'utcInstanceEpoch' => 'epoch?',
'importAuthorPHID' => 'phid?',
'importSourcePHID' => 'phid?',
'importUIDIndex' => 'bytes12?',
'importUID' => 'text?',
// TODO: DEPRECATED.
'allDayDateFrom' => 'epoch',
'allDayDateTo' => 'epoch',
'dateFrom' => 'epoch',
'dateTo' => 'epoch',
'recurrenceEndDate' => '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() {
+ if ($this->getIsGhostEvent() || $this->getIsStub()) {
+ if ($this->stubInvitees === null) {
+ $this->stubInvitees = $this->newStubInvitees();
+ }
+ return $this->stubInvitees;
+ }
+
return $this->assertAttached($this->invitees);
}
+ private function newStubInvitees() {
+ $parent = $this->getParentEvent();
+
+ $parent_invitees = $parent->getInvitees();
+ $stub_invitees = array();
+
+ foreach ($parent_invitees as $invitee) {
+ $stub_invitee = id(new PhabricatorCalendarEventInvitee())
+ ->setInviteePHID($invitee->getInviteePHID())
+ ->setInviterPHID($invitee->getInviterPHID())
+ ->setStatus(PhabricatorCalendarEventInvitee::STATUS_INVITED);
+
+ $stub_invitees[] = $stub_invitee;
+ }
+
+ return $stub_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 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) {
$start = $this->newStartDateTime();
$end = $this->newEndDateTime();
if ($show_end) {
$min_date = $start->newPHPDateTime();
$max_date = $end->newPHPDateTime();
$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 = $start->getEpoch();
$max_epoch = $end->getEpoch();
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';
}
}
if ($this->isImportedEvent()) {
return 'fa-download';
}
return $this->getIcon();
}
public function getDisplayIconColor(PhabricatorUser $viewer) {
if ($this->isCancelledEvent()) {
return 'red';
}
if ($this->isImportedEvent()) {
return 'orange';
}
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,
array $children) {
$base_uri = new PhutilURI(PhabricatorEnv::getProductionURI('/'));
$domain = $base_uri->getDomain();
// NOTE: For recurring events, all of the events in the series have the
// same UID (the UID of the parent). The child event instances are
// differentiated by the "RECURRENCE-ID" field.
if ($this->isChildEvent()) {
$parent = $this->getParentEvent();
$instance_datetime = PhutilCalendarAbsoluteDateTime::newFromEpoch(
$this->getUTCInstanceEpoch());
$recurrence_id = $instance_datetime->getISO8601();
$rrule = null;
} else {
$parent = $this;
$recurrence_id = null;
$rrule = $this->newRecurrenceRule();
}
$uid = $parent->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);
}
// TODO: Use $children to generate EXDATE/RDATE information.
$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);
if ($rrule) {
$node->setRecurrenceRule($rrule);
}
if ($recurrence_id) {
$node->setRecurrenceID($recurrence_id);
}
return $node;
}
public function newStartDateTime() {
$datetime = $this->getParameter('startDateTime');
if ($datetime) {
return $this->newDateTimeFromDictionary($datetime);
}
$epoch = $this->getDateFrom();
return $this->newDateTimeFromEpoch($epoch);
}
public function getStartDateTimeEpoch() {
return $this->newStartDateTime()->getEpoch();
}
public function newEndDateTime() {
$datetime = $this->getParameter('endDateTime');
if ($datetime) {
return $this->newDateTimeFromDictionary($datetime);
}
$epoch = $this->getDateTo();
return $this->newDateTimeFromEpoch($epoch);
}
public function getEndDateTimeEpoch() {
return $this->newEndDateTime()->getEpoch();
}
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 getUntilDateTimeEpoch() {
$datetime = $this->newUntilDateTime();
if (!$datetime) {
return null;
}
return $datetime->getEpoch();
}
public function newDuration() {
return id(new PhutilCalendarDuration())
->setSeconds($this->getDuration());
}
public function newInstanceDateTime() {
if (!$this->getIsRecurring()) {
return null;
}
$index = $this->getSequenceIndex();
if (!$index) {
return null;
}
return $this->newSequenceIndexDateTime($index);
}
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);
}
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());
}
public function setRecurrenceRule(PhutilCalendarRecurrenceRule $rrule) {
return $this->setParameter(
'recurrenceRule',
$rrule->toDictionary());
}
public function newRecurrenceRule() {
if ($this->isChildEvent()) {
return $this->getParentEvent()->newRecurrenceRule();
}
if (!$this->getIsRecurring()) {
return null;
}
$dict = $this->getParameter('recurrenceRule');
if (!$dict) {
return null;
}
$rrule = PhutilCalendarRecurrenceRule::newFromDictionary($dict);
$start = $this->newStartDateTime();
$rrule->setStartDateTime($start);
$until = $this->newUntilDateTime();
if ($until) {
$rrule->setUntil($until);
}
$count = $this->getRecurrenceCount();
if ($count) {
$rrule->setCount($count);
}
return $rrule;
}
public function getRecurrenceCount() {
$count = (int)$this->getParameter('recurrenceCount');
if (!$count) {
return null;
}
return $count;
}
public function newRecurrenceSet() {
if ($this->isChildEvent()) {
return $this->getParentEvent()->newRecurrenceSet();
}
$set = new PhutilCalendarRecurrenceSet();
$rrule = $this->newRecurrenceRule();
if (!$rrule) {
return null;
}
$set->addSource($rrule);
return $set;
}
public function isImportedEvent() {
return (bool)$this->getImportSourcePHID();
}
public function getImportSource() {
return $this->assertAttached($this->importSource);
}
public function attachImportSource(
PhabricatorCalendarImport $import = null) {
$this->importSource = $import;
return $this;
}
/* -( 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:
if ($this->getImportSource()) {
return PhabricatorPolicies::POLICY_NOONE;
} else {
return $this->getEditPolicy();
}
}
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
if ($this->getImportSource()) {
return false;
}
// 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) {
if ($this->getImportSource()) {
return pht(
'Events imported from external sources can not be edited in '.
'Phabricator.');
}
return pht(
'The host of an event can always view and edit it. Users who are '.
'invited to an event can always view it.');
}
/* -( PhabricatorExtendedPolicyInterface )--------------------------------- */
public function getExtendedPolicy($capability, PhabricatorUser $viewer) {
$extended = array();
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
$import_source = $this->getImportSource();
if ($import_source) {
$extended[] = array(
$import_source,
PhabricatorPolicyCapability::CAN_VIEW,
);
}
break;
}
return $extended;
}
/* -( 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.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('isAllDay')
->setType('bool')
->setDescription(pht('True if the event is an all day event.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('startDateTime')
->setType('datetime')
->setDescription(pht('Start date and time of the event.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('endDateTime')
->setType('datetime')
->setDescription(pht('End date and time of the event.')),
);
}
public function getFieldValuesForConduit() {
$start_datetime = $this->newStartDateTime();
$end_datetime = $this->newEndDateTime();
return array(
'name' => $this->getName(),
'description' => $this->getDescription(),
'isAllDay' => (bool)$this->getIsAllDay(),
'startDateTime' => $this->getConduitDateTime($start_datetime),
'endDateTime' => $this->getConduitDateTime($end_datetime),
);
}
public function getConduitSearchAttachments() {
return array();
}
private function getConduitDateTime($datetime) {
if (!$datetime) {
return null;
}
$epoch = $datetime->getEpoch();
// TODO: Possibly pass the actual viewer in from the Conduit stuff, or
// retain it when setting the viewer timezone?
$viewer = id(new PhabricatorUser())
->overrideTimezoneIdentifier($this->viewerTimezone);
return array(
'epoch' => (int)$epoch,
'display' => array(
'default' => phabricator_datetime($epoch, $viewer),
),
'iso8601' => $datetime->getISO8601(),
'timezone' => $this->viewerTimezone,
);
}
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sun, Jan 19, 15:25 (3 w, 23 h ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1125967
Default Alt Text
(45 KB)
Attached To
Mode
rP Phorge
Attached
Detach File
Event Timeline
Log In to Comment