diff --git a/resources/sql/autopatches/20150427.calendar.1.xaction.sql b/resources/sql/autopatches/20150427.calendar.1.xaction.sql new file mode 100644 --- /dev/null +++ b/resources/sql/autopatches/20150427.calendar.1.xaction.sql @@ -0,0 +1,19 @@ +CREATE TABLE {$NAMESPACE}_calendar.calendar_eventtransaction ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + phid VARBINARY(64) NOT NULL, + authorPHID VARBINARY(64) NOT NULL, + objectPHID VARBINARY(64) NOT NULL, + viewPolicy VARBINARY(64) NOT NULL, + editPolicy VARBINARY(64) NOT NULL, + commentPHID VARBINARY(64) DEFAULT NULL, + commentVersion INT UNSIGNED NOT NULL, + transactionType VARCHAR(32) COLLATE {$COLLATE_TEXT} NOT NULL, + oldValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, + newValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, + contentSource LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, + metadata LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + UNIQUE KEY `key_phid` (`phid`), + KEY `key_object` (`objectPHID`) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20150427.calendar.2.xaction.sql b/resources/sql/autopatches/20150427.calendar.2.xaction.sql new file mode 100644 --- /dev/null +++ b/resources/sql/autopatches/20150427.calendar.2.xaction.sql @@ -0,0 +1,16 @@ +CREATE TABLE {$NAMESPACE}_calendar.calendar_eventtransaction_comment ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + phid VARBINARY(64) NOT NULL, + transactionPHID VARBINARY(64) DEFAULT NULL, + authorPHID VARBINARY(64) NOT NULL, + viewPolicy VARBINARY(64) NOT NULL, + editPolicy VARBINARY(64) NOT NULL, + commentVersion INT UNSIGNED NOT NULL, + content LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, + contentSource LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, + isDeleted TINYINT(1) NOT NULL, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + UNIQUE KEY `key_phid` (`phid`), + UNIQUE KEY `key_version` (`transactionPHID`,`commentVersion`) +) ENGINE=InnoDB COLLATE {$COLLATE_TEXT} diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1484,11 +1484,15 @@ 'PhabricatorCalendarEvent' => 'applications/calendar/storage/PhabricatorCalendarEvent.php', 'PhabricatorCalendarEventDeleteController' => 'applications/calendar/controller/PhabricatorCalendarEventDeleteController.php', 'PhabricatorCalendarEventEditController' => 'applications/calendar/controller/PhabricatorCalendarEventEditController.php', + 'PhabricatorCalendarEventEditor' => 'applications/calendar/editor/PhabricatorCalendarEventEditor.php', 'PhabricatorCalendarEventInvalidEpochException' => 'applications/calendar/exception/PhabricatorCalendarEventInvalidEpochException.php', 'PhabricatorCalendarEventListController' => 'applications/calendar/controller/PhabricatorCalendarEventListController.php', 'PhabricatorCalendarEventPHIDType' => 'applications/calendar/phid/PhabricatorCalendarEventPHIDType.php', 'PhabricatorCalendarEventQuery' => 'applications/calendar/query/PhabricatorCalendarEventQuery.php', 'PhabricatorCalendarEventSearchEngine' => 'applications/calendar/query/PhabricatorCalendarEventSearchEngine.php', + 'PhabricatorCalendarEventTransaction' => 'applications/calendar/storage/PhabricatorCalendarEventTransaction.php', + 'PhabricatorCalendarEventTransactionComment' => 'applications/calendar/storage/PhabricatorCalendarEventTransactionComment.php', + 'PhabricatorCalendarEventTransactionQuery' => 'applications/calendar/query/PhabricatorCalendarEventTransactionQuery.php', 'PhabricatorCalendarEventViewController' => 'applications/calendar/controller/PhabricatorCalendarEventViewController.php', 'PhabricatorCalendarHoliday' => 'applications/calendar/storage/PhabricatorCalendarHoliday.php', 'PhabricatorCalendarHolidayTestCase' => 'applications/calendar/storage/__tests__/PhabricatorCalendarHolidayTestCase.php', @@ -4802,14 +4806,19 @@ 'PhabricatorCalendarDAO', 'PhabricatorPolicyInterface', 'PhabricatorMarkupInterface', + 'PhabricatorApplicationTransactionInterface', ), 'PhabricatorCalendarEventDeleteController' => 'PhabricatorCalendarController', 'PhabricatorCalendarEventEditController' => 'PhabricatorCalendarController', + 'PhabricatorCalendarEventEditor' => 'PhabricatorApplicationTransactionEditor', 'PhabricatorCalendarEventInvalidEpochException' => 'Exception', 'PhabricatorCalendarEventListController' => 'PhabricatorCalendarController', 'PhabricatorCalendarEventPHIDType' => 'PhabricatorPHIDType', 'PhabricatorCalendarEventQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorCalendarEventSearchEngine' => 'PhabricatorApplicationSearchEngine', + 'PhabricatorCalendarEventTransaction' => 'PhabricatorApplicationTransaction', + 'PhabricatorCalendarEventTransactionComment' => 'PhabricatorApplicationTransactionComment', + 'PhabricatorCalendarEventTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhabricatorCalendarEventViewController' => 'PhabricatorCalendarController', 'PhabricatorCalendarHoliday' => 'PhabricatorCalendarDAO', 'PhabricatorCalendarHolidayTestCase' => 'PhabricatorTestCase', diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php b/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php --- a/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php @@ -30,13 +30,13 @@ ->setInitialTime(AphrontFormDateControl::TIME_END_OF_DAY); if ($this->isCreate()) { - $status = new PhabricatorCalendarEvent(); - $end_value = $end_time->readValueFromRequest($request); - $start_value = $start_time->readValueFromRequest($request); + $status = PhabricatorCalendarEvent::initializeNewCalendarEvent($user); + $end_value = $end_time->readValueFromRequest($request); + $start_value = $start_time->readValueFromRequest($request); $submit_label = pht('Create'); - $filter = 'status/create/'; - $page_title = pht('Create Event'); - $redirect = 'created'; + $filter = 'status/create/'; + $page_title = pht('Create Event'); + $redirect = 'created'; } else { $status = id(new PhabricatorCalendarEventQuery()) ->setViewer($user) @@ -61,6 +61,7 @@ $errors = array(); if ($request->isFormPost()) { + $xactions = array(); $type = $request->getInt('status'); $start_value = $start_time->readValueFromRequest($request); $end_value = $end_time->readValueFromRequest($request); @@ -73,39 +74,33 @@ $errors[] = pht('Invalid end time; reset to default.'); } if (!$errors) { - try { - $status - ->setUserPHID($user->getPHID()) - ->setStatus($type) - ->setDateFrom($start_value) - ->setDateTo($end_value) - ->setDescription($description) - ->save(); - } catch (PhabricatorCalendarEventInvalidEpochException $e) { - $errors[] = pht('Start must be before end.'); - } - } - - if (!$errors) { - $uri = new PhutilURI($this->getApplicationURI()); - $uri->setQueryParams( - array( - 'month' => phabricator_format_local_time($status->getDateFrom(), - $user, - 'm'), - 'year' => phabricator_format_local_time($status->getDateFrom(), - $user, - 'Y'), - $redirect => true, - )); - if ($request->isAjax()) { - $response = id(new AphrontAjaxResponse()) - ->setContent(array('redirect_uri' => $uri)); - } else { - $response = id(new AphrontRedirectResponse()) - ->setURI($uri); - } - return $response; + $xactions[] = id(new PhabricatorCalendarEventTransaction()) + ->setTransactionType( + PhabricatorCalendarEventTransaction::TYPE_START_DATE) + ->setNewValue($start_value); + + $xactions[] = id(new PhabricatorCalendarEventTransaction()) + ->setTransactionType( + PhabricatorCalendarEventTransaction::TYPE_END_DATE) + ->setNewValue($end_value); + + $xactions[] = id(new PhabricatorCalendarEventTransaction()) + ->setTransactionType( + PhabricatorCalendarEventTransaction::TYPE_STATUS) + ->setNewValue($type); + + $xactions[] = id(new PhabricatorCalendarEventTransaction()) + ->setTransactionType( + PhabricatorCalendarEventTransaction::TYPE_DESCRIPTION) + ->setNewValue($description); + + $editor = id(new PhabricatorCalendarEventEditor()) + ->setActor($user) + ->setContentSourceFromRequest($request) + ->setContinueOnNoEffect(true); + + $xactions = $editor->applyTransactions($status, $xactions); + return id(new AphrontRedirectResponse())->setURI('/E'.$status->getID()); } } diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php b/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php --- a/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php @@ -29,6 +29,10 @@ $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($title, '/E'.$event->getID()); + $timeline = $this->buildTransactionTimeline( + $event, + new PhabricatorCalendarEventTransactionQuery()); + $header = $this->buildHeaderView($event); $actions = $this->buildActionView($event); $properties = $this->buildPropertyView($event); @@ -42,6 +46,7 @@ array( $crumbs, $box, + $timeline, ), array( 'title' => $title, diff --git a/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php b/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php new file mode 100644 --- /dev/null +++ b/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php @@ -0,0 +1,108 @@ +getTransactionType()) { + case PhabricatorCalendarEventTransaction::TYPE_START_DATE: + return $object->getDateFrom(); + case PhabricatorCalendarEventTransaction::TYPE_END_DATE: + return $object->getDateTo(); + case PhabricatorCalendarEventTransaction::TYPE_STATUS: + $status = $object->getStatus(); + if ($status === null) { + return null; + } + return (int)$status; + case PhabricatorCalendarEventTransaction::TYPE_DESCRIPTION: + return $object->getDescription(); + } + + return parent::getCustomTransactionOldValue($object, $xaction); + } + + protected function getCustomTransactionNewValue( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + + switch ($xaction->getTransactionType()) { + case PhabricatorCalendarEventTransaction::TYPE_START_DATE: + case PhabricatorCalendarEventTransaction::TYPE_END_DATE: + case PhabricatorCalendarEventTransaction::TYPE_DESCRIPTION: + return $xaction->getNewValue(); + case PhabricatorCalendarEventTransaction::TYPE_STATUS: + return (int)$xaction->getNewValue(); + } + + return parent::getCustomTransactionNewValue($object, $xaction); + } + + protected function applyCustomInternalTransaction( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + + switch ($xaction->getTransactionType()) { + case PhabricatorCalendarEventTransaction::TYPE_START_DATE: + $object->setDateFrom($xaction->getNewValue()); + return; + case PhabricatorCalendarEventTransaction::TYPE_END_DATE: + $object->setDateTo($xaction->getNewValue()); + return; + case PhabricatorCalendarEventTransaction::TYPE_STATUS: + $object->setStatus($xaction->getNewValue()); + return; + case PhabricatorCalendarEventTransaction::TYPE_DESCRIPTION: + $object->setDescription($xaction->getNewValue()); + return; + case PhabricatorTransactions::TYPE_VIEW_POLICY: + case PhabricatorTransactions::TYPE_EDIT_POLICY: + case PhabricatorTransactions::TYPE_EDGE: + return; + } + + return parent::applyCustomInternalTransaction($object, $xaction); + } + + protected function applyCustomExternalTransaction( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + + switch ($xaction->getTransactionType()) { + case PhabricatorCalendarEventTransaction::TYPE_START_DATE: + case PhabricatorCalendarEventTransaction::TYPE_END_DATE: + case PhabricatorCalendarEventTransaction::TYPE_STATUS: + case PhabricatorCalendarEventTransaction::TYPE_DESCRIPTION: + case PhabricatorTransactions::TYPE_VIEW_POLICY: + case PhabricatorTransactions::TYPE_EDIT_POLICY: + case PhabricatorTransactions::TYPE_EDGE: + return; + } + + return parent::applyCustomExternalTransaction($object, $xaction); + } +} diff --git a/src/applications/calendar/query/PhabricatorCalendarEventTransactionQuery.php b/src/applications/calendar/query/PhabricatorCalendarEventTransactionQuery.php new file mode 100644 --- /dev/null +++ b/src/applications/calendar/query/PhabricatorCalendarEventTransactionQuery.php @@ -0,0 +1,10 @@ +setViewer($actor) + ->withClasses(array('PhabricatorCalendarApplication')) + ->executeOne(); + + return id(new PhabricatorCalendarEvent()) + ->setUserPHID($actor->getPHID()); + } + private static $statusTexts = array( self::STATUS_AWAY => 'away', self::STATUS_SPORADIC => 'sporadic', @@ -86,6 +97,17 @@ return mpull($statuses, null, 'getUserPHID'); } + public static function getNameForStatus($value) { + switch ($value) { + case self::STATUS_AWAY: + return pht('Away'); + case self::STATUS_SPORADIC: + return pht('Sporadic'); + default: + return pht('Unknown'); + } + } + /** * Validates data and throws exceptions for non-sensical status * windows @@ -173,4 +195,26 @@ return null; } +/* -( 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; + } + } diff --git a/src/applications/calendar/storage/PhabricatorCalendarEventTransaction.php b/src/applications/calendar/storage/PhabricatorCalendarEventTransaction.php new file mode 100644 --- /dev/null +++ b/src/applications/calendar/storage/PhabricatorCalendarEventTransaction.php @@ -0,0 +1,210 @@ +getTransactionType()) { + case self::TYPE_START_DATE: + case self::TYPE_END_DATE: + case self::TYPE_STATUS: + case self::TYPE_DESCRIPTION: + $phids[] = $this->getObjectPHID(); + break; + } + + return $phids; + } + + public function shouldHide() { + $old = $this->getOldValue(); + switch ($this->getTransactionType()) { + case self::TYPE_START_DATE: + case self::TYPE_END_DATE: + case self::TYPE_STATUS: + case self::TYPE_DESCRIPTION: + return ($old === null); + } + return parent::shouldHide(); + } + + public function getIcon() { + switch ($this->getTransactionType()) { + case self::TYPE_START_DATE: + case self::TYPE_END_DATE: + case self::TYPE_STATUS: + case self::TYPE_DESCRIPTION: + return 'fa-pencil'; + break; + } + return parent::getIcon(); + } + + public function getTitle() { + $author_phid = $this->getAuthorPHID(); + $object_phid = $this->getObjectPHID(); + + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + $type = $this->getTransactionType(); + switch ($type) { + case self::TYPE_START_DATE: + if ($old) { + return pht( + '%s edited the start date of this event.', + $this->renderHandleLink($author_phid)); + } + break; + case self::TYPE_END_DATE: + if ($old) { + return pht( + '%s edited the end date of this event.', + $this->renderHandleLink($author_phid)); + } + break; + case self::TYPE_STATUS: + $old_name = PhabricatorCalendarEvent::getNameForStatus($old); + $new_name = PhabricatorCalendarEvent::getNameForStatus($new); + return pht( + '%s updated the event status from %s to %s.', + $this->renderHandleLink($author_phid), + $old_name, + $new_name); + break; + case self::TYPE_DESCRIPTION: + return pht( + "%s updated the event's description.", + $this->renderHandleLink($author_phid)); + break; + } + + return parent::getTitle(); + } + + public function getTitleForFeed() { + $author_phid = $this->getAuthorPHID(); + $object_phid = $this->getObjectPHID(); + + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + $type = $this->getTransactionType(); + switch ($type) { + case self::TYPE_START_DATE: + if ($old) { + return pht( + '%s edited the start date of this event from %s to %s.', + $this->renderHandleLink($author_phid), + $old, + $new); + } + break; + case self::TYPE_END_DATE: + if ($old) { + return pht( + '%s edited the end date of this event from %s to %s.', + $this->renderHandleLink($author_phid), + $old, + $new); + } + break; + case self::TYPE_STATUS: + return pht( + '%s updated the event status from %s to %s.', + $this->renderHandleLink($author_phid), + $old, + $new); + break; + case self::TYPE_DESCRIPTION: + return pht( + "%s updated the event's description.", + $this->renderHandleLink($author_phid)); + break; + } + + return parent::getTitleForFeed(); + } + + public function getColor() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + switch ($this->getTransactionType()) { + case self::TYPE_START_DATE: + case self::TYPE_END_DATE: + case self::TYPE_STATUS: + case self::TYPE_DESCRIPTION: + return PhabricatorTransactions::COLOR_GREEN; + } + + return parent::getColor(); + } + + + public function hasChangeDetails() { + switch ($this->getTransactionType()) { + case self::TYPE_DESCRIPTION: + return ($this->getOldValue() !== null); + } + + return parent::hasChangeDetails(); + } + + public function renderChangeDetails(PhabricatorUser $viewer) { + switch ($this->getTransactionType()) { + case self::TYPE_DESCRIPTION: + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + return $this->renderTextCorpusChangeDetails( + $viewer, + $old, + $new); + } + + return parent::renderChangeDetails($viewer); + } + + public function getMailTags() { + $tags = array(); + switch ($this->getTransactionType()) { + case self::TYPE_START_DATE: + $tags[] = self::MAILTAG_CONTENT; + break; + case self::TYPE_END_DATE: + $tags[] = self::MAILTAG_CONTENT; + break; + case self::TYPE_STATUS: + $tags[] = self::MAILTAG_OTHER; + break; + case self::TYPE_DESCRIPTION: + $tags[] = self::MAILTAG_CONTENT; + break; + } + return $tags; + } + +} diff --git a/src/applications/calendar/storage/PhabricatorCalendarEventTransactionComment.php b/src/applications/calendar/storage/PhabricatorCalendarEventTransactionComment.php new file mode 100644 --- /dev/null +++ b/src/applications/calendar/storage/PhabricatorCalendarEventTransactionComment.php @@ -0,0 +1,10 @@ +