Page MenuHomestyx hydra

No OneTemporary

diff --git a/resources/sql/autopatches/20160715.event.03.allday.php b/resources/sql/autopatches/20160715.event.03.allday.php
index 8bc3ffe568..4c2d73a368 100644
--- a/resources/sql/autopatches/20160715.event.03.allday.php
+++ b/resources/sql/autopatches/20160715.event.03.allday.php
@@ -1,52 +1,3 @@
<?php
-$table = new PhabricatorCalendarEvent();
-$conn = $table->establishConnection('w');
-
-// Previously, "All Day" events were stored with a start and end date set to
-// the earliest possible start and end seconds for the corresponding days. We
-// now store all day events with their "date" epochs as UTC, separate from
-// individual event times.
-$zone_min = new DateTimeZone('Pacific/Midway');
-$zone_max = new DateTimeZone('Pacific/Kiritimati');
-$zone_utc = new DateTimeZone('UTC');
-
-foreach (new LiskMigrationIterator($table) as $event) {
- // If this event has already migrated, skip it.
- if ($event->getAllDayDateFrom()) {
- continue;
- }
-
- $is_all_day = $event->getIsAllDay();
-
- $epoch_min = $event->getDateFrom();
- $epoch_max = $event->getDateTo();
-
- $date_min = new DateTime('@'.$epoch_min);
- $date_max = new DateTime('@'.$epoch_max);
-
- if ($is_all_day) {
- $date_min->setTimeZone($zone_min);
- $date_min->modify('+2 days');
- $date_max->setTimeZone($zone_max);
- $date_max->modify('-2 days');
- } else {
- $date_min->setTimeZone($zone_utc);
- $date_max->setTimeZone($zone_utc);
- }
-
- $string_min = $date_min->format('Y-m-d');
- $string_max = $date_max->format('Y-m-d 23:59:00');
-
- $allday_min = id(new DateTime($string_min, $zone_utc))->format('U');
- $allday_max = id(new DateTime($string_max, $zone_utc))->format('U');
-
- queryfx(
- $conn,
- 'UPDATE %T SET allDayDateFrom = %d, allDayDateTo = %d
- WHERE id = %d',
- $table->getTableName(),
- $allday_min,
- $allday_max,
- $event->getID());
-}
+// This migration was replaced by "20161004.cal.01.noepoch.php".
diff --git a/resources/sql/autopatches/20161004.cal.01.noepoch.php b/resources/sql/autopatches/20161004.cal.01.noepoch.php
new file mode 100644
index 0000000000..6013376062
--- /dev/null
+++ b/resources/sql/autopatches/20161004.cal.01.noepoch.php
@@ -0,0 +1,125 @@
+<?php
+
+$table = new PhabricatorCalendarEvent();
+$conn = $table->establishConnection('w');
+$table_name = 'calendar_event';
+
+// Long ago, "All Day" events were stored with a start and end date set to
+// the earliest possible start and end seconds for the corresponding days. We
+// then moved to store all day events with their "date" epochs as UTC, separate
+// from individual event times. Both systems were later replaced with use of
+// CalendarDateTime.
+$zone_min = new DateTimeZone('Pacific/Midway');
+$zone_max = new DateTimeZone('Pacific/Kiritimati');
+$zone_utc = new DateTimeZone('UTC');
+
+foreach (new LiskRawMigrationIterator($conn, $table_name) as $row) {
+ $parameters = phutil_json_decode($row['parameters']);
+ if (isset($parameters['startDateTime'])) {
+ // This event has already been migrated.
+ continue;
+ }
+
+ $is_all_day = $row['isAllDay'];
+
+ if (empty($row['allDayDateFrom'])) {
+ // No "allDayDateFrom" means this is an old event which was never migrated
+ // by the earlier "20160715.event.03.allday.php" migration. The dateFrom
+ // and dateTo will be minimum and maximum earthly seconds for the event. We
+ // convert them to UTC if they were in extreme timezones.
+ $epoch_min = $row['dateFrom'];
+ $epoch_max = $row['dateTo'];
+
+ if ($is_all_day) {
+ $date_min = new DateTime('@'.$epoch_min);
+ $date_max = new DateTime('@'.$epoch_max);
+
+ $date_min->setTimeZone($zone_min);
+ $date_min->modify('+2 days');
+ $date_max->setTimeZone($zone_max);
+ $date_max->modify('-2 days');
+
+ $string_min = $date_min->format('Y-m-d');
+ $string_max = $date_max->format('Y-m-d 23:59:00');
+
+ $utc_min = id(new DateTime($string_min, $zone_utc))->format('U');
+ $utc_max = id(new DateTime($string_max, $zone_utc))->format('U');
+ } else {
+ $utc_min = $epoch_min;
+ $utc_max = $epoch_max;
+ }
+ } else {
+ // This is an event which was migrated already. We can pick the correct
+ // epoch timestamps based on the "isAllDay" flag.
+ if ($is_all_day) {
+ $utc_min = $row['allDayDateFrom'];
+ $utc_max = $row['allDayDateTo'];
+ } else {
+ $utc_min = $row['dateFrom'];
+ $utc_max = $row['dateTo'];
+ }
+ }
+
+ $utc_until = $row['recurrenceEndDate'];
+
+ $start_datetime = PhutilCalendarAbsoluteDateTime::newFromEpoch($utc_min);
+ if ($is_all_day) {
+ $start_datetime->setIsAllDay(true);
+ }
+
+ $end_datetime = PhutilCalendarAbsoluteDateTime::newFromEpoch($utc_max);
+ if ($is_all_day) {
+ $end_datetime->setIsAllDay(true);
+ }
+
+ if ($utc_until) {
+ $until_datetime = PhutilCalendarAbsoluteDateTime::newFromEpoch($utc_until);
+ } else {
+ $until_datetime = null;
+ }
+
+ $parameters['startDateTime'] = $start_datetime->toDictionary();
+ $parameters['endDateTime'] = $end_datetime->toDictionary();
+ if ($until_datetime) {
+ $parameters['untilDateTime'] = $until_datetime->toDictionary();
+ }
+
+ queryfx(
+ $conn,
+ 'UPDATE %T SET parameters = %s WHERE id = %d',
+ $table_name,
+ phutil_json_encode($parameters),
+ $row['id']);
+}
+
+// Generate UTC epochs for all events. We can't readily do this one at a
+// time because instance UTC epochs rely on having the parent event.
+$viewer = PhabricatorUser::getOmnipotentUser();
+
+$all_events = id(new PhabricatorCalendarEventQuery())
+ ->setViewer($viewer)
+ ->execute();
+foreach ($all_events as $event) {
+ if ($event->getUTCInitialEpoch()) {
+ // Already migrated.
+ continue;
+ }
+
+ try {
+ $event->updateUTCEpochs();
+ } catch (Exception $ex) {
+ continue;
+ }
+
+ queryfx(
+ $conn,
+ 'UPDATE %T SET
+ utcInitialEpoch = %d,
+ utcUntilEpoch = %nd,
+ utcInstanceEpoch = %nd WHERE id = %d',
+ $table_name,
+ $event->getUTCInitialEpoch(),
+ $event->getUTCUntilEpoch(),
+ $event->getUTCInstanceEpoch(),
+ $event->getID());
+}
diff --git a/src/applications/calendar/query/PhabricatorCalendarEventQuery.php b/src/applications/calendar/query/PhabricatorCalendarEventQuery.php
index 6c10964b02..1ba40d51a6 100644
--- a/src/applications/calendar/query/PhabricatorCalendarEventQuery.php
+++ b/src/applications/calendar/query/PhabricatorCalendarEventQuery.php
@@ -1,528 +1,528 @@
<?php
final class PhabricatorCalendarEventQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $rangeBegin;
private $rangeEnd;
private $inviteePHIDs;
private $hostPHIDs;
private $isCancelled;
private $eventsWithNoParent;
private $instanceSequencePairs;
private $isStub;
private $generateGhosts = false;
public function newResultObject() {
return new PhabricatorCalendarEvent();
}
public function setGenerateGhosts($generate_ghosts) {
$this->generateGhosts = $generate_ghosts;
return $this;
}
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withDateRange($begin, $end) {
$this->rangeBegin = $begin;
$this->rangeEnd = $end;
return $this;
}
public function withInvitedPHIDs(array $phids) {
$this->inviteePHIDs = $phids;
return $this;
}
public function withHostPHIDs(array $phids) {
$this->hostPHIDs = $phids;
return $this;
}
public function withIsCancelled($is_cancelled) {
$this->isCancelled = $is_cancelled;
return $this;
}
public function withIsStub($is_stub) {
$this->isStub = $is_stub;
return $this;
}
public function withEventsWithNoParent($events_with_no_parent) {
$this->eventsWithNoParent = $events_with_no_parent;
return $this;
}
public function withInstanceSequencePairs(array $pairs) {
$this->instanceSequencePairs = $pairs;
return $this;
}
protected function getDefaultOrderVector() {
return array('start', 'id');
}
public function getBuiltinOrders() {
return array(
'start' => array(
'vector' => array('start', 'id'),
'name' => pht('Event Start'),
),
) + parent::getBuiltinOrders();
}
public function getOrderableColumns() {
return array(
'start' => array(
'table' => $this->getPrimaryTableAlias(),
'column' => 'dateFrom',
'reverse' => true,
'type' => 'int',
'unique' => false,
),
) + parent::getOrderableColumns();
}
protected function getPagingValueMap($cursor, array $keys) {
$event = $this->loadCursorObject($cursor);
return array(
'start' => $event->getStartDateTimeEpoch(),
'id' => $event->getID(),
);
}
protected function shouldLimitResults() {
// When generating ghosts, we can't rely on database ordering because
// MySQL can't predict the ghost start times. We'll just load all matching
// events, then generate results from there.
if ($this->generateGhosts) {
return false;
}
return true;
}
protected function loadPage() {
$events = $this->loadStandardPage($this->newResultObject());
$viewer = $this->getViewer();
foreach ($events as $event) {
$event->applyViewerTimezone($viewer);
}
if (!$this->generateGhosts) {
return $events;
}
$raw_limit = $this->getRawResultLimit();
if (!$raw_limit && !$this->rangeEnd) {
throw new Exception(
pht(
'Event queries which generate ghost events must include either a '.
'result limit or an end date, because they may otherwise generate '.
'an infinite number of results. This query has neither.'));
}
foreach ($events as $key => $event) {
$sequence_start = 0;
$sequence_end = null;
$end = null;
$instance_of = $event->getInstanceOfEventPHID();
if ($instance_of == null && $this->isCancelled !== null) {
if ($event->getIsCancelled() != $this->isCancelled) {
unset($events[$key]);
continue;
}
}
}
// Pull out all of the parents first. We may discard them as we begin
// generating ghost events, but we still want to process all of them.
$parents = array();
foreach ($events as $key => $event) {
if ($event->isParentEvent()) {
$parents[$key] = $event;
}
}
// Now that we've picked out all the parent events, we can immediately
// discard anything outside of the time window.
$events = $this->getEventsInRange($events);
$enforced_end = null;
foreach ($parents as $key => $event) {
$sequence_start = 0;
$sequence_end = null;
$start = null;
$duration = $event->getDuration();
$frequency = $event->getFrequencyUnit();
$modify_key = '+1 '.$frequency;
if (($this->rangeBegin !== null) &&
($this->rangeBegin > $event->getStartDateTimeEpoch())) {
$max_date = $this->rangeBegin - $duration;
$date = $event->getStartDateTimeEpoch();
$datetime = PhabricatorTime::getDateTimeFromEpoch($date, $viewer);
while ($date < $max_date) {
// TODO: optimize this to not loop through all off-screen events
$sequence_start++;
$datetime = PhabricatorTime::getDateTimeFromEpoch($date, $viewer);
$date = $datetime->modify($modify_key)->format('U');
}
$start = $this->rangeBegin;
} else {
$start = $event->getStartDateTimeEpoch() - $duration;
}
$date = $start;
$datetime = PhabricatorTime::getDateTimeFromEpoch($date, $viewer);
// Select the minimum end time we need to generate events until.
$end_times = array();
if ($this->rangeEnd) {
$end_times[] = $this->rangeEnd;
}
if ($event->getRecurrenceEndDate()) {
$end_times[] = $event->getRecurrenceEndDate();
}
if ($enforced_end) {
$end_times[] = $enforced_end;
}
if ($end_times) {
$end = min($end_times);
$sequence_end = $sequence_start;
while ($date < $end) {
$sequence_end++;
$datetime->modify($modify_key);
$date = $datetime->format('U');
if ($sequence_end > $raw_limit + $sequence_start) {
break;
}
}
} else {
$sequence_end = $raw_limit + $sequence_start;
}
$sequence_start = max(1, $sequence_start);
for ($index = $sequence_start; $index < $sequence_end; $index++) {
$events[] = $event->newGhost($viewer, $index);
}
// NOTE: We're slicing results every time because this makes it cheaper
// to generate future ghosts. If we already have 100 events that occur
// before July 1, we know we never need to generate ghosts after that
// because they couldn't possibly ever appear in the result set.
if ($raw_limit) {
if (count($events) > $raw_limit) {
$events = msort($events, 'getStartDateTimeEpoch');
$events = array_slice($events, 0, $raw_limit, true);
$enforced_end = last($events)->getStartDateTimeEpoch();
}
}
}
// Now that we're done generating ghost events, we're going to remove any
// ghosts that we have concrete events for (or which we can load the
// concrete events for). These concrete events are generated when users
// edit a ghost, and replace the ghost events.
// First, generate a map of all concrete <parentPHID, sequence> events we
// already loaded. We don't need to load these again.
$have_pairs = array();
foreach ($events as $event) {
if ($event->getIsGhostEvent()) {
continue;
}
$parent_phid = $event->getInstanceOfEventPHID();
$sequence = $event->getSequenceIndex();
$have_pairs[$parent_phid][$sequence] = true;
}
// Now, generate a map of all <parentPHID, sequence> events we generated
// ghosts for. We need to try to load these if we don't already have them.
$map = array();
$parent_pairs = array();
foreach ($events as $key => $event) {
if (!$event->getIsGhostEvent()) {
continue;
}
$parent_phid = $event->getInstanceOfEventPHID();
$sequence = $event->getSequenceIndex();
// We already loaded the concrete version of this event, so we can just
// throw out the ghost and move on.
if (isset($have_pairs[$parent_phid][$sequence])) {
unset($events[$key]);
continue;
}
// We didn't load the concrete version of this event, so we need to
// try to load it if it exists.
$parent_pairs[] = array($parent_phid, $sequence);
$map[$parent_phid][$sequence] = $key;
}
if ($parent_pairs) {
$instances = id(new self())
->setViewer($viewer)
->setParentQuery($this)
->withInstanceSequencePairs($parent_pairs)
->execute();
foreach ($instances as $instance) {
$parent_phid = $instance->getInstanceOfEventPHID();
$sequence = $instance->getSequenceIndex();
$indexes = idx($map, $parent_phid);
$key = idx($indexes, $sequence);
// Replace the ghost with the corresponding concrete event.
$events[$key] = $instance;
}
}
$events = msort($events, 'getStartDateTimeEpoch');
return $events;
}
protected function buildJoinClauseParts(AphrontDatabaseConnection $conn_r) {
$parts = parent::buildJoinClauseParts($conn_r);
if ($this->inviteePHIDs !== null) {
$parts[] = qsprintf(
$conn_r,
'JOIN %T invitee ON invitee.eventPHID = event.phid
AND invitee.status != %s',
id(new PhabricatorCalendarEventInvitee())->getTableName(),
PhabricatorCalendarEventInvitee::STATUS_UNINVITED);
}
return $parts;
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids) {
$where[] = qsprintf(
$conn,
'event.id IN (%Ld)',
$this->ids);
}
if ($this->phids) {
$where[] = qsprintf(
$conn,
'event.phid IN (%Ls)',
$this->phids);
}
// NOTE: The date ranges we query for are larger than the requested ranges
// because we need to catch all-day events. We'll refine this range later
// after adjusting the visible range of events we load.
if ($this->rangeBegin) {
$where[] = qsprintf(
$conn,
- 'event.dateTo >= %d OR event.isRecurring = 1',
+ '(event.utcUntilEpoch >= %d) OR (event.utcUntilEpoch IS NULL)',
$this->rangeBegin - phutil_units('16 hours in seconds'));
}
if ($this->rangeEnd) {
$where[] = qsprintf(
$conn,
- 'event.dateFrom <= %d',
+ 'event.utcInitialEpoch <= %d',
$this->rangeEnd + phutil_units('16 hours in seconds'));
}
if ($this->inviteePHIDs !== null) {
$where[] = qsprintf(
$conn,
'invitee.inviteePHID IN (%Ls)',
$this->inviteePHIDs);
}
if ($this->hostPHIDs) {
$where[] = qsprintf(
$conn,
'event.hostPHID IN (%Ls)',
$this->hostPHIDs);
}
if ($this->isCancelled !== null) {
$where[] = qsprintf(
$conn,
'event.isCancelled = %d',
(int)$this->isCancelled);
}
if ($this->eventsWithNoParent == true) {
$where[] = qsprintf(
$conn,
'event.instanceOfEventPHID IS NULL');
}
if ($this->instanceSequencePairs !== null) {
$sql = array();
foreach ($this->instanceSequencePairs as $pair) {
$sql[] = qsprintf(
$conn,
'(event.instanceOfEventPHID = %s AND event.sequenceIndex = %d)',
$pair[0],
$pair[1]);
}
$where[] = qsprintf(
$conn,
'%Q',
implode(' OR ', $sql));
}
if ($this->isStub !== null) {
$where[] = qsprintf(
$conn,
'event.isStub = %d',
(int)$this->isStub);
}
return $where;
}
protected function getPrimaryTableAlias() {
return 'event';
}
protected function shouldGroupQueryResultRows() {
if ($this->inviteePHIDs !== null) {
return true;
}
return parent::shouldGroupQueryResultRows();
}
protected function getApplicationSearchObjectPHIDColumn() {
return 'event.phid';
}
public function getQueryApplicationClass() {
return 'PhabricatorCalendarApplication';
}
protected function willFilterPage(array $events) {
$instance_of_event_phids = array();
$recurring_events = array();
$viewer = $this->getViewer();
$events = $this->getEventsInRange($events);
$phids = array();
foreach ($events as $event) {
$phids[] = $event->getPHID();
$instance_of = $event->getInstanceOfEventPHID();
if ($instance_of) {
$instance_of_event_phids[] = $instance_of;
}
}
if (count($instance_of_event_phids) > 0) {
$recurring_events = id(new PhabricatorCalendarEventQuery())
->setViewer($viewer)
->withPHIDs($instance_of_event_phids)
->withEventsWithNoParent(true)
->execute();
$recurring_events = mpull($recurring_events, null, 'getPHID');
}
if ($events) {
$invitees = id(new PhabricatorCalendarEventInviteeQuery())
->setViewer($viewer)
->withEventPHIDs($phids)
->execute();
$invitees = mgroup($invitees, 'getEventPHID');
} else {
$invitees = array();
}
foreach ($events as $key => $event) {
$event_invitees = idx($invitees, $event->getPHID(), array());
$event->attachInvitees($event_invitees);
$instance_of = $event->getInstanceOfEventPHID();
if (!$instance_of) {
continue;
}
$parent = idx($recurring_events, $instance_of);
// should never get here
if (!$parent) {
unset($events[$key]);
continue;
}
$event->attachParentEvent($parent);
if ($this->isCancelled !== null) {
if ($event->getIsCancelled() != $this->isCancelled) {
unset($events[$key]);
continue;
}
}
}
$events = msort($events, 'getStartDateTimeEpoch');
return $events;
}
private function getEventsInRange(array $events) {
$range_start = $this->rangeBegin;
$range_end = $this->rangeEnd;
foreach ($events as $key => $event) {
$event_start = $event->getStartDateTimeEpoch();
$event_end = $event->getEndDateTimeEpoch();
if ($range_start && $event_end < $range_start) {
unset($events[$key]);
}
if ($range_end && $event_start > $range_end) {
unset($events[$key]);
}
}
return $events;
}
}
diff --git a/src/applications/calendar/storage/PhabricatorCalendarEvent.php b/src/applications/calendar/storage/PhabricatorCalendarEvent.php
index 2ddc71115f..8d93e839f2 100644
--- a/src/applications/calendar/storage/PhabricatorCalendarEvent.php
+++ b/src/applications/calendar/storage/PhabricatorCalendarEvent.php
@@ -1,1062 +1,1033 @@
<?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 $viewerTimezone;
+ // TODO: DEPRECATED. Remove once we're sure the migrations worked.
+ protected $allDayDateFrom;
+ protected $allDayDateTo;
+ protected $dateFrom;
+ protected $dateTo;
+ protected $recurrenceEndDate;
+
// 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)
+ ->setDateFrom(0)
+ ->setDateTo(0)
+ ->setAllDayDateFrom(0)
+ ->setAllDayDateTo(0)
->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);
+ ->attachParentEvent($this)
+ ->setAllDayDateFrom(0)
+ ->setAllDayDateTo(0)
+ ->setDateFrom(0)
+ ->setDateTo(0);
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 = $parent->getDuration();
$epochs = $parent->getSequenceIndexEpochs($actor, $sequence, $duration);
+ $start_datetime = PhutilCalendarAbsoluteDateTime::newFromEpoch(
+ $epochs['dateFrom'],
+ $parent->newStartDateTime()->getTimezone());
+ $end_datetime = PhutilCalendarAbsoluteDateTime::newFromEpoch(
+ $epochs['dateTo'],
+ $parent->newEndDateTime()->getTimezone());
+
$this
- ->setDateFrom($epochs['dateFrom'])
- ->setDateTo($epochs['dateTo'])
- ->setAllDayDateFrom($epochs['allDayDateFrom'])
- ->setAllDayDateTo($epochs['allDayDateTo']);
+ ->setStartDateTime($start_datetime)
+ ->setEndDateTime($end_datetime);
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 = $this->newStartDateTime()
+ ->setViewerTimezone($viewer->getTimezoneIdentifier())
+ ->newPHPDateTime();
+
$date_time->modify($modify_key);
$date = $date_time->format('U');
- $end_date = $this->getRecurrenceEndDate();
+ $end_date = $this->getUntilDateTimeEpoch();
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 applyViewerTimezone(PhabricatorUser $viewer) {
$this->viewerTimezone = $viewer->getTimezoneIdentifier();
return $this;
}
public function getDuration() {
return ($this->getEndDateTimeEpoch() - $this->getStartDateTimeEpoch());
}
- 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');
+ $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);
}
$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',
- '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?',
+
+ // 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() {
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) {
$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';
}
}
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 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;
}
- $epochs = $this->getParent()->getSequenceIndexEpochs(
+ $epochs = $this->getParentEvent()->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);
}
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/PhabricatorCalendarEventEndDateTransaction.php b/src/applications/calendar/xaction/PhabricatorCalendarEventEndDateTransaction.php
index 2d5f10d290..a0d127c32f 100644
--- a/src/applications/calendar/xaction/PhabricatorCalendarEventEndDateTransaction.php
+++ b/src/applications/calendar/xaction/PhabricatorCalendarEventEndDateTransaction.php
@@ -1,54 +1,44 @@
<?php
final class PhabricatorCalendarEventEndDateTransaction
extends PhabricatorCalendarEventDateTransaction {
const TRANSACTIONTYPE = 'calendar.enddate';
public function generateOldValue($object) {
// TODO: Upgrade this.
return $object->getEndDateTimeEpoch();
}
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 48318edf7e..e08bbac780 100644
--- a/src/applications/calendar/xaction/PhabricatorCalendarEventStartDateTransaction.php
+++ b/src/applications/calendar/xaction/PhabricatorCalendarEventStartDateTransaction.php
@@ -1,54 +1,44 @@
<?php
final class PhabricatorCalendarEventStartDateTransaction
extends PhabricatorCalendarEventDateTransaction {
const TRANSACTIONTYPE = 'calendar.startdate';
public function generateOldValue($object) {
// TODO: Upgrade this.
return $object->getStartDateTimeEpoch();
}
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.');
}
}

File Metadata

Mime Type
text/x-diff
Expires
Wed, Dec 3, 4:13 PM (2 h, 24 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
433698
Default Alt Text
(55 KB)

Event Timeline