diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php b/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php index 38d12b099b..967c7deb19 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php @@ -74,6 +74,7 @@ final class PhabricatorCalendarEventEditController $name = $event->getName(); $description = $event->getDescription(); $type = $event->getStatus(); + $is_all_day = $event->getIsAllDay(); $current_policies = id(new PhabricatorPolicyQuery()) ->setViewer($user) @@ -95,6 +96,7 @@ final class PhabricatorCalendarEventEditController $subscribers = $request->getArr('subscribers'); $edit_policy = $request->getStr('editPolicy'); $view_policy = $request->getStr('viewPolicy'); + $is_all_day = $request->getStr('isAllDay'); $invitees = $request->getArr('invitees'); $new_invitees = $this->getNewInviteeList($invitees, $event); @@ -111,6 +113,11 @@ final class PhabricatorCalendarEventEditController PhabricatorCalendarEventTransaction::TYPE_NAME) ->setNewValue($name); + $xactions[] = id(new PhabricatorCalendarEventTransaction()) + ->setTransactionType( + PhabricatorCalendarEventTransaction::TYPE_ALL_DAY) + ->setNewValue($is_all_day); + $xactions[] = id(new PhabricatorCalendarEventTransaction()) ->setTransactionType( PhabricatorCalendarEventTransaction::TYPE_START_DATE) @@ -184,6 +191,13 @@ final class PhabricatorCalendarEventEditController ->setValue($type) ->setOptions($event->getStatusOptions()); + $all_day_select = id(new AphrontFormCheckboxControl()) + ->addCheckbox( + 'isAllDay', + 1, + pht('All Day Event'), + $is_all_day); + $start_control = id(new AphrontFormDateControl()) ->setUser($user) ->setName('start') @@ -234,6 +248,7 @@ final class PhabricatorCalendarEventEditController ->setUser($user) ->appendChild($name) ->appendChild($status_select) + ->appendChild($all_day_select) ->appendChild($start_control) ->appendChild($end_control) ->appendControl($view_policies) diff --git a/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php b/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php index f00ba4349f..8c992ce16f 100644 --- a/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php +++ b/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php @@ -183,6 +183,16 @@ final class PhabricatorCalendarEventEditor return parent::applyCustomExternalTransaction($object, $xaction); } + protected function didApplyInternalEffects( + PhabricatorLiskDAO $object, + array $xactions) { + + $object->removeViewerTimezone($this->requireActor()); + + return $xactions; + } + + protected function validateAllTransactions( PhabricatorLiskDAO $object, array $xactions) { diff --git a/src/applications/calendar/query/PhabricatorCalendarEventQuery.php b/src/applications/calendar/query/PhabricatorCalendarEventQuery.php index 1fd294781f..c66e367793 100644 --- a/src/applications/calendar/query/PhabricatorCalendarEventQuery.php +++ b/src/applications/calendar/query/PhabricatorCalendarEventQuery.php @@ -56,7 +56,13 @@ final class PhabricatorCalendarEventQuery $this->buildOrderClause($conn_r), $this->buildLimitClause($conn_r)); - return $table->loadAllFromArray($data); + $events = $table->loadAllFromArray($data); + + foreach ($events as $event) { + $event->applyViewerTimezone($this->getViewer()); + } + + return $events; } protected function buildJoinClauseParts(AphrontDatabaseConnection $conn_r) { diff --git a/src/applications/calendar/storage/PhabricatorCalendarEvent.php b/src/applications/calendar/storage/PhabricatorCalendarEvent.php index a63c869808..5d4d3cdd5a 100644 --- a/src/applications/calendar/storage/PhabricatorCalendarEvent.php +++ b/src/applications/calendar/storage/PhabricatorCalendarEvent.php @@ -24,6 +24,7 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO protected $editPolicy; private $invitees = self::ATTACHABLE; + private $appliedViewer; const STATUS_AWAY = 1; const STATUS_SPORADIC = 2; @@ -37,15 +38,112 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO return id(new PhabricatorCalendarEvent()) ->setUserPHID($actor->getPHID()) ->setIsCancelled(0) + ->setIsAllDay(0) ->setViewPolicy($actor->getPHID()) ->setEditPolicy($actor->getPHID()) - ->attachInvitees(array()); + ->attachInvitees(array()) + ->applyViewerTimezone($actor); + } + + public function applyViewerTimezone(PhabricatorUser $viewer) { + if ($this->appliedViewer) { + throw new Exception(pht('Viewer timezone is already applied!')); + } + + $this->appliedViewer = $viewer; + + if (!$this->getIsAllDay()) { + return $this; + } + + $zone = $viewer->getTimeZone(); + + + $this->setDateFrom( + $this->getDateEpochForTimeZone( + $this->getDateFrom(), + new DateTimeZone('GMT+12'), + 'Y-m-d', + null, + $zone)); + + $this->setDateTo( + $this->getDateEpochForTimeZone( + $this->getDateTo(), + new DateTimeZone('GMT-12'), + 'Y-m-d 23:59:59', + '-1 day', + $zone)); + + return $this; + } + + + public function removeViewerTimezone(PhabricatorUser $viewer) { + if (!$this->appliedViewer) { + throw new Exception(pht('Viewer timezone is not applied!')); + } + + if ($viewer->getPHID() != $this->appliedViewer->getPHID()) { + throw new Exception(pht('Removed viewer must match applied viewer!')); + } + + $this->appliedViewer = null; + + if (!$this->getIsAllDay()) { + return $this; + } + + $zone = $viewer->getTimeZone(); + + $this->setDateFrom( + $this->getDateEpochForTimeZone( + $this->getDateFrom(), + $zone, + 'Y-m-d', + null, + new DateTimeZone('GMT+12'))); + + $this->setDateTo( + $this->getDateEpochForTimeZone( + $this->getDateTo(), + $zone, + 'Y-m-d', + '+1 day', + new DateTimeZone('GMT-12'))); + + return $this; + } + + private 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 save() { + if ($this->appliedViewer) { + throw new Exception( + pht( + 'Can not save event with viewer timezone still applied!')); + } + if (!$this->mailKey) { $this->mailKey = Filesystem::readRandomCharacters(20); } + return parent::save(); } diff --git a/src/applications/people/storage/PhabricatorUser.php b/src/applications/people/storage/PhabricatorUser.php index ea138042b2..a6c9f6f349 100644 --- a/src/applications/people/storage/PhabricatorUser.php +++ b/src/applications/people/storage/PhabricatorUser.php @@ -682,6 +682,10 @@ EOBODY; } } + public function getTimeZone() { + return new DateTimeZone($this->getTimezoneIdentifier()); + } + public function __toString() { return $this->getUsername(); } diff --git a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php index c28fb135ee..1e75bd0432 100644 --- a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php +++ b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php @@ -569,6 +569,11 @@ abstract class PhabricatorApplicationTransactionEditor return $xaction; } + protected function didApplyInternalEffects( + PhabricatorLiskDAO $object, + array $xactions) { + return $xactions; + } protected function applyFinalEffects( PhabricatorLiskDAO $object, @@ -731,6 +736,8 @@ abstract class PhabricatorApplicationTransactionEditor $this->applyInternalEffects($object, $xaction); } + $xactions = $this->didApplyInternalEffects($object, $xactions); + $object->save(); foreach ($xactions as $xaction) {