Page MenuHomestyx hydra

No OneTemporary

diff --git a/src/applications/calendar/editor/PhabricatorCalendarEventEditEngine.php b/src/applications/calendar/editor/PhabricatorCalendarEventEditEngine.php
index 441c3c5de5..4457f60fd8 100644
--- a/src/applications/calendar/editor/PhabricatorCalendarEventEditEngine.php
+++ b/src/applications/calendar/editor/PhabricatorCalendarEventEditEngine.php
@@ -1,276 +1,276 @@
<?php
final class PhabricatorCalendarEventEditEngine
extends PhabricatorEditEngine {
const ENGINECONST = 'calendar.event';
public function getEngineName() {
return pht('Calendar Events');
}
public function getSummaryHeader() {
return pht('Configure Calendar Event Forms');
}
public function getSummaryText() {
return pht('Configure how users create and edit events.');
}
public function getEngineApplicationClass() {
return 'PhabricatorCalendarApplication';
}
protected function newEditableObject() {
return PhabricatorCalendarEvent::initializeNewCalendarEvent(
$this->getViewer());
}
protected function newObjectQuery() {
return new PhabricatorCalendarEventQuery();
}
protected function getObjectCreateTitleText($object) {
return pht('Create New Event');
}
protected function getObjectEditTitleText($object) {
return pht('Edit Event: %s', $object->getName());
}
protected function getObjectEditShortText($object) {
return $object->getMonogram();
}
protected function getObjectCreateShortText() {
return pht('Create Event');
}
protected function getObjectName() {
return pht('Event');
}
protected function getObjectViewURI($object) {
return $object->getURI();
}
protected function getEditorURI() {
return $this->getApplication()->getApplicationURI('event/edit/');
}
protected function buildCustomEditFields($object) {
$viewer = $this->getViewer();
if ($this->getIsCreate()) {
$invitee_phids = array($viewer->getPHID());
} else {
$invitee_phids = $object->getInviteePHIDsForEdit();
}
$frequency_map = PhabricatorCalendarEvent::getFrequencyMap();
$frequency_options = ipull($frequency_map, 'label');
$rrule = $object->newRecurrenceRule();
if ($rrule) {
$frequency = $rrule->getFrequency();
} else {
$frequency = null;
}
$fields = array(
id(new PhabricatorTextEditField())
->setKey('name')
->setLabel(pht('Name'))
->setDescription(pht('Name of the event.'))
->setIsRequired(true)
->setTransactionType(
PhabricatorCalendarEventNameTransaction::TRANSACTIONTYPE)
->setConduitDescription(pht('Rename the event.'))
->setConduitTypeDescription(pht('New event name.'))
->setValue($object->getName()),
id(new PhabricatorBoolEditField())
->setKey('isAllDay')
->setOptions(pht('Normal Event'), pht('All Day Event'))
->setAsCheckbox(true)
->setTransactionType(
PhabricatorCalendarEventAllDayTransaction::TRANSACTIONTYPE)
->setDescription(pht('Marks this as an all day event.'))
->setConduitDescription(pht('Make the event an all day event.'))
->setConduitTypeDescription(pht('Mark the event as an all day event.'))
->setValue($object->getIsAllDay()),
id(new PhabricatorEpochEditField())
->setKey('start')
->setLabel(pht('Start'))
->setIsLockable(false)
->setIsDefaultable(false)
->setTransactionType(
PhabricatorCalendarEventStartDateTransaction::TRANSACTIONTYPE)
->setDescription(pht('Start time of the event.'))
->setConduitDescription(pht('Change the start time of the event.'))
->setConduitTypeDescription(pht('New event start time.'))
->setValue($object->getStartDateTimeEpoch()),
id(new PhabricatorEpochEditField())
->setKey('end')
->setLabel(pht('End'))
->setIsLockable(false)
->setIsDefaultable(false)
->setTransactionType(
PhabricatorCalendarEventEndDateTransaction::TRANSACTIONTYPE)
->setDescription(pht('End time of the event.'))
->setConduitDescription(pht('Change the end time of the event.'))
->setConduitTypeDescription(pht('New event end time.'))
- ->setValue($object->getEndDateTimeEpoch()),
+ ->setValue($object->newEndDateTimeForEdit()->getEpoch()),
id(new PhabricatorBoolEditField())
->setKey('cancelled')
->setOptions(pht('Active'), pht('Cancelled'))
->setLabel(pht('Cancelled'))
->setDescription(pht('Cancel the event.'))
->setTransactionType(
PhabricatorCalendarEventCancelTransaction::TRANSACTIONTYPE)
->setIsConduitOnly(true)
->setConduitDescription(pht('Cancel or restore the event.'))
->setConduitTypeDescription(pht('True to cancel the event.'))
->setValue($object->getIsCancelled()),
id(new PhabricatorUsersEditField())
->setKey('hostPHID')
->setAliases(array('host'))
->setLabel(pht('Host'))
->setDescription(pht('Host of the event.'))
->setTransactionType(
PhabricatorCalendarEventHostTransaction::TRANSACTIONTYPE)
->setIsConduitOnly($this->getIsCreate())
->setConduitDescription(pht('Change the host of the event.'))
->setConduitTypeDescription(pht('New event host.'))
->setSingleValue($object->getHostPHID()),
id(new PhabricatorDatasourceEditField())
->setKey('inviteePHIDs')
->setAliases(array('invite', 'invitee', 'invitees', 'inviteePHID'))
->setLabel(pht('Invitees'))
->setDatasource(new PhabricatorMetaMTAMailableDatasource())
->setTransactionType(
PhabricatorCalendarEventInviteTransaction::TRANSACTIONTYPE)
->setDescription(pht('Users invited to the event.'))
->setConduitDescription(pht('Change invited users.'))
->setConduitTypeDescription(pht('New event invitees.'))
->setValue($invitee_phids)
->setCommentActionLabel(pht('Change Invitees')),
id(new PhabricatorRemarkupEditField())
->setKey('description')
->setLabel(pht('Description'))
->setDescription(pht('Description of the event.'))
->setTransactionType(
PhabricatorCalendarEventDescriptionTransaction::TRANSACTIONTYPE)
->setConduitDescription(pht('Update the event description.'))
->setConduitTypeDescription(pht('New event description.'))
->setValue($object->getDescription()),
id(new PhabricatorIconSetEditField())
->setKey('icon')
->setLabel(pht('Icon'))
->setIconSet(new PhabricatorCalendarIconSet())
->setTransactionType(
PhabricatorCalendarEventIconTransaction::TRANSACTIONTYPE)
->setDescription(pht('Event icon.'))
->setConduitDescription(pht('Change the event icon.'))
->setConduitTypeDescription(pht('New event icon.'))
->setValue($object->getIcon()),
id(new PhabricatorBoolEditField())
->setIsHidden($object->getIsRecurring())
->setKey('isRecurring')
->setLabel(pht('Recurring'))
->setOptions(pht('One-Time Event'), pht('Recurring Event'))
->setTransactionType(
PhabricatorCalendarEventRecurringTransaction::TRANSACTIONTYPE)
->setDescription(pht('One time or recurring event.'))
->setConduitDescription(pht('Make the event recurring.'))
->setConduitTypeDescription(pht('Mark the event as a recurring event.'))
->setValue($object->getIsRecurring()),
id(new PhabricatorSelectEditField())
->setKey('frequency')
->setLabel(pht('Frequency'))
->setOptions($frequency_options)
->setTransactionType(
PhabricatorCalendarEventFrequencyTransaction::TRANSACTIONTYPE)
->setDescription(pht('Recurring event frequency.'))
->setConduitDescription(pht('Change the event frequency.'))
->setConduitTypeDescription(pht('New event frequency.'))
->setValue($frequency),
id(new PhabricatorEpochEditField())
->setIsLockable(false)
->setIsDefaultable(false)
->setAllowNull(true)
->setHideTime($object->getIsAllDay())
->setKey('until')
->setLabel(pht('Repeat Until'))
->setTransactionType(
PhabricatorCalendarEventUntilDateTransaction::TRANSACTIONTYPE)
->setDescription(pht('Last instance of the event.'))
->setConduitDescription(pht('Change when the event repeats until.'))
->setConduitTypeDescription(pht('New final event time.'))
->setValue($object->getUntilDateTimeEpoch()),
);
return $fields;
}
protected function willBuildEditForm($object, array $fields) {
$all_day_field = idx($fields, 'isAllDay');
$start_field = idx($fields, 'start');
$end_field = idx($fields, 'end');
if ($all_day_field) {
$is_all_day = $all_day_field->getValueForTransaction();
$control_ids = array();
if ($start_field) {
$control_ids[] = $start_field->getControlID();
}
if ($end_field) {
$control_ids[] = $end_field->getControlID();
}
Javelin::initBehavior(
'event-all-day',
array(
'allDayID' => $all_day_field->getControlID(),
'controlIDs' => $control_ids,
));
} else {
$is_all_day = $object->getIsAllDay();
}
if ($is_all_day) {
if ($start_field) {
$start_field->setHideTime(true);
}
if ($end_field) {
$end_field->setHideTime(true);
}
}
return $fields;
}
protected function newPages($object) {
// Controls for event recurrence behavior go on a separate page which we
// put in a dialog. This simplifies event creation in the common case.
return array(
id(new PhabricatorEditPage())
->setKey('core')
->setLabel(pht('Core'))
->setIsDefault(true),
id(new PhabricatorEditPage())
->setKey('recurring')
->setLabel(pht('Recurrence'))
->setFieldKeys(
array(
'isRecurring',
'frequency',
'until',
)),
);
}
}
diff --git a/src/applications/calendar/storage/PhabricatorCalendarEvent.php b/src/applications/calendar/storage/PhabricatorCalendarEvent.php
index 1410c0e106..ef7a808a61 100644
--- a/src/applications/calendar/storage/PhabricatorCalendarEvent.php
+++ b/src/applications/calendar/storage/PhabricatorCalendarEvent.php
@@ -1,1307 +1,1326 @@
<?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;
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);
}
public static function getFrequencyMap() {
return array(
PhutilCalendarRecurrenceRule::FREQUENCY_DAILY => array(
'label' => pht('Daily'),
),
PhutilCalendarRecurrenceRule::FREQUENCY_WEEKLY => array(
'label' => pht('Weekly'),
),
PhutilCalendarRecurrenceRule::FREQUENCY_MONTHLY => array(
'label' => pht('Monthly'),
),
PhutilCalendarRecurrenceRule::FREQUENCY_YEARLY => array(
'label' => pht('Yearly'),
),
);
}
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();
// NOTE: Gmail shows "Who: Unknown Organizer*" if the organizer URI does
// not look like an email address. Use a synthetic address so it shows
// the host name instead.
$install_uri = PhabricatorEnv::getProductionURI('/');
$install_uri = new PhutilURI($install_uri);
// This should possibly use "metamta.reply-handler-domain" instead, but
// we do not currently accept mail for users anyway, and that option may
// not be configured.
$mail_domain = $install_uri->getDomain();
$host_uri = "mailto:{$host_phid}@{$mail_domain}";
$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() {
+ public function newEndDateTimeForEdit() {
$datetime = $this->getParameter('endDateTime');
if ($datetime) {
return $this->newDateTimeFromDictionary($datetime);
}
$epoch = $this->getDateTo();
return $this->newDateTimeFromEpoch($epoch);
}
+ public function newEndDateTime() {
+ $datetime = $this->newEndDateTimeForEdit();
+
+ // If this is an all day event, we move the end date time forward to the
+ // first second of the following day. This is consistent with what users
+ // expect: an all day event from "Nov 1" to "Nov 1" lasts the entire day.
+ if ($this->getIsAllDay()) {
+ $datetime = $datetime
+ ->newAbsoluteDateTime()
+ ->setHour(0)
+ ->setMinute(0)
+ ->setSecond(0)
+ ->newRelativeDateTime('P1D')
+ ->newAbsoluteDateTime();
+ }
+
+ return $datetime;
+ }
+
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,
);
}
}
diff --git a/src/applications/calendar/xaction/PhabricatorCalendarEventEndDateTransaction.php b/src/applications/calendar/xaction/PhabricatorCalendarEventEndDateTransaction.php
index a0d127c32f..8d66176ea8 100644
--- a/src/applications/calendar/xaction/PhabricatorCalendarEventEndDateTransaction.php
+++ b/src/applications/calendar/xaction/PhabricatorCalendarEventEndDateTransaction.php
@@ -1,44 +1,44 @@
<?php
final class PhabricatorCalendarEventEndDateTransaction
extends PhabricatorCalendarEventDateTransaction {
const TRANSACTIONTYPE = 'calendar.enddate';
public function generateOldValue($object) {
// TODO: Upgrade this.
- return $object->getEndDateTimeEpoch();
+ return $object->newEndDateTimeForEdit()->getEpoch();
}
public function applyInternalEffects($object, $value) {
$actor = $this->getActor();
$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.');
}
}

File Metadata

Mime Type
text/x-diff
Expires
Mon, Dec 1, 9:23 PM (1 d, 3 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
431189
Default Alt Text
(48 KB)

Event Timeline