diff --git a/resources/sql/autopatches/20150428.calendar.1.name.sql b/resources/sql/autopatches/20150428.calendar.1.name.sql new file mode 100644 index 0000000000..7e5e8da6c9 --- /dev/null +++ b/resources/sql/autopatches/20150428.calendar.1.name.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_calendar.calendar_event + ADD name LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL; diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php b/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php index a966afb8d3..378b27d5ce 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php @@ -1,202 +1,199 @@ id = idx($data, 'id'); } public function isCreate() { return !$this->id; } public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); + $request = $this->getRequest(); + $user = $request->getUser(); + $error_name = true; + $validation_exception = null; + $start_time = id(new AphrontFormDateControl()) ->setUser($user) ->setName('start') ->setLabel(pht('Start')) ->setInitialTime(AphrontFormDateControl::TIME_START_OF_DAY); $end_time = id(new AphrontFormDateControl()) ->setUser($user) ->setName('end') ->setLabel(pht('End')) ->setInitialTime(AphrontFormDateControl::TIME_END_OF_DAY); if ($this->isCreate()) { - $status = PhabricatorCalendarEvent::initializeNewCalendarEvent($user); + $event = PhabricatorCalendarEvent::initializeNewCalendarEvent($user); $end_value = $end_time->readValueFromRequest($request); $start_value = $start_time->readValueFromRequest($request); $submit_label = pht('Create'); - $filter = 'status/create/'; + $filter = 'event/create/'; $page_title = pht('Create Event'); $redirect = 'created'; } else { - $status = id(new PhabricatorCalendarEventQuery()) + $event = id(new PhabricatorCalendarEventQuery()) ->setViewer($user) ->withIDs(array($this->id)) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) ->executeOne(); - if (!$status) { + if (!$event) { return new Aphront404Response(); } - $end_time->setValue($status->getDateTo()); - $start_time->setValue($status->getDateFrom()); + $end_time->setValue($event->getDateTo()); + $start_time->setValue($event->getDateFrom()); $submit_label = pht('Update'); - $filter = 'event/edit/'.$status->getID().'/'; + $filter = 'event/edit/'.$event->getID().'/'; $page_title = pht('Update Event'); $redirect = 'updated'; } $errors = array(); if ($request->isFormPost()) { - $xactions = array(); - $type = $request->getInt('status'); + $xactions = array(); + $name = $request->getStr('name'); + $type = $request->getInt('status'); $start_value = $start_time->readValueFromRequest($request); - $end_value = $end_time->readValueFromRequest($request); + $end_value = $end_time->readValueFromRequest($request); $description = $request->getStr('description'); if ($start_time->getError()) { $errors[] = pht('Invalid start time; reset to default.'); } if ($end_time->getError()) { $errors[] = pht('Invalid end time; reset to default.'); } if (!$errors) { + $xactions[] = id(new PhabricatorCalendarEventTransaction()) + ->setTransactionType( + PhabricatorCalendarEventTransaction::TYPE_NAME) + ->setNewValue($name); + $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()); + try { + $xactions = $editor->applyTransactions($event, $xactions); + $response = id(new AphrontRedirectResponse()); + return $response->setURI('/E'.$event->getID()); + } catch (PhabricatorApplicationTransactionValidationException $ex) { + $validation_exception = $ex; + $error_name = $ex + ->getShortMessage(PhabricatorCalendarEventTransaction::TYPE_NAME); + } } } $error_view = null; if ($errors) { $error_view = id(new PHUIInfoView()) ->setTitle(pht('Status can not be set!')) ->setErrors($errors); } + $name = id(new AphrontFormTextControl()) + ->setLabel(pht('Name')) + ->setName('name') + ->setValue($event->getName()) + ->setError($error_name); + $status_select = id(new AphrontFormSelectControl()) ->setLabel(pht('Status')) ->setName('status') - ->setValue($status->getStatus()) - ->setOptions($status->getStatusOptions()); + ->setValue($event->getStatus()) + ->setOptions($event->getStatusOptions()); $description = id(new AphrontFormTextAreaControl()) ->setLabel(pht('Description')) ->setName('description') - ->setValue($status->getDescription()); - - if ($request->isAjax()) { - $dialog = id(new AphrontDialogView()) - ->setUser($user) - ->setTitle($page_title) - ->setWidth(AphrontDialogView::WIDTH_FORM); - if ($this->isCreate()) { - $dialog->setSubmitURI($this->getApplicationURI('event/create/')); - } else { - $dialog->setSubmitURI( - $this->getApplicationURI('event/edit/'.$status->getID().'/')); - } - $form = new PHUIFormLayoutView(); - if ($error_view) { - $form->appendChild($error_view); - } - } else { - $form = id(new AphrontFormView()) - ->setUser($user); - } + ->setValue($event->getDescription()); - $form + $form = id(new AphrontFormView()) + ->setUser($user) + ->appendChild($name) ->appendChild($status_select) ->appendChild($start_time) ->appendChild($end_time) ->appendChild($description); - if ($request->isAjax()) { - $dialog->addSubmitButton($submit_label); - $submit = $dialog; - } else { - $submit = id(new AphrontFormSubmitControl()) - ->setValue($submit_label); - } + $submit = id(new AphrontFormSubmitControl()) + ->setValue($submit_label); if ($this->isCreate()) { $submit->addCancelButton($this->getApplicationURI()); } else { - $submit->addCancelButton('/E'.$status->getID()); + $submit->addCancelButton('/E'.$event->getID()); } - if ($request->isAjax()) { - $dialog->appendChild($form); - return id(new AphrontDialogResponse()) - ->setDialog($dialog); - } $form->appendChild($submit); - - $form_box = id(new PHUIObjectBoxView()) ->setHeaderText($page_title) ->setFormErrors($errors) ->setForm($form); - $nav = $this->buildSideNavView($status); + $nav = $this->buildSideNavView($event); $nav->selectFilter($filter); $crumbs = $this->buildApplicationCrumbs(); if (!$this->isCreate()) { - $crumbs->addTextCrumb('E'.$status->getId(), '/E'.$status->getId()); + $crumbs->addTextCrumb('E'.$event->getId(), '/E'.$event->getId()); } $crumbs->addTextCrumb($page_title); + $object_box = id(new PHUIObjectBoxView()) + ->setHeaderText($page_title) + ->setValidationException($validation_exception) + ->appendChild($form); + $nav->appendChild( array( $crumbs, - $form_box, + $object_box, )); return $this->buildApplicationPage( $nav, array( 'title' => $page_title, )); } } diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php b/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php index adb2542db7..0cf3d7401b 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php @@ -1,120 +1,125 @@ id = $data['id']; } public function processRequest() { $request = $this->getRequest(); $viewer = $request->getUser(); $event = id(new PhabricatorCalendarEventQuery()) ->setViewer($viewer) ->withIDs(array($this->id)) ->executeOne(); if (!$event) { return new Aphront404Response(); } $title = 'E'.$event->getID(); + $page_title = $title.' '.$event->getName(); $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); $properties->setActionList($actions); $box = id(new PHUIObjectBoxView()) ->setHeader($header) ->addPropertyList($properties); return $this->buildApplicationPage( array( $crumbs, $box, $timeline, ), array( - 'title' => $title, + 'title' => $page_title, )); } private function buildHeaderView(PhabricatorCalendarEvent $event) { $viewer = $this->getRequest()->getUser(); return id(new PHUIHeaderView()) ->setUser($viewer) - ->setHeader($event->getTerseSummary($viewer)) + ->setHeader($event->getName()) ->setPolicyObject($event); } private function buildActionView(PhabricatorCalendarEvent $event) { $viewer = $this->getRequest()->getUser(); $id = $event->getID(); $actions = id(new PhabricatorActionListView()) ->setObjectURI($this->getApplicationURI('event/'.$id.'/')) ->setUser($viewer); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $event, PhabricatorPolicyCapability::CAN_EDIT); $actions->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Event')) ->setIcon('fa-pencil') ->setHref($this->getApplicationURI("event/edit/{$id}/")) ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); $actions->addAction( id(new PhabricatorActionView()) ->setName(pht('Cancel Event')) ->setIcon('fa-times') ->setHref($this->getApplicationURI("event/delete/{$id}/")) ->setDisabled(!$can_edit) ->setWorkflow(true)); return $actions; } private function buildPropertyView(PhabricatorCalendarEvent $event) { $viewer = $this->getRequest()->getUser(); $properties = id(new PHUIPropertyListView()) ->setUser($viewer) ->setObject($event); + // $properties->addProperty( + // pht('Name'), + // $event->getName()); + $properties->addProperty( pht('Starts'), phabricator_datetime($event->getDateFrom(), $viewer)); $properties->addProperty( pht('Ends'), phabricator_datetime($event->getDateTo(), $viewer)); $properties->addSectionHeader( pht('Description'), PHUIPropertyListView::ICON_SUMMARY); $properties->addTextContent($event->getDescription()); return $properties; } } diff --git a/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php b/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php index e58a8df15d..b865bb85ac 100644 --- a/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php +++ b/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php @@ -1,108 +1,145 @@ getTransactionType()) { + case PhabricatorCalendarEventTransaction::TYPE_NAME: + return $object->getName(); 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_NAME: 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_NAME: + $object->setName($xaction->getNewValue()); + return; 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_NAME: 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); } + + protected function validateTransaction( + PhabricatorLiskDAO $object, + $type, + array $xactions) { + + $errors = parent::validateTransaction($object, $type, $xactions); + + switch ($type) { + case PhabricatorCalendarEventTransaction::TYPE_NAME: + $missing = $this->validateIsEmptyTextField( + $object->getName(), + $xactions); + + if ($missing) { + $error = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Required'), + pht('Event name is required.'), + nonempty(last($xactions), null)); + + $error->setIsMissingFieldError(true); + $errors[] = $error; + } + break; + } + + return $errors; + } } diff --git a/src/applications/calendar/storage/PhabricatorCalendarEvent.php b/src/applications/calendar/storage/PhabricatorCalendarEvent.php index 6e277fedac..8d6ab918d2 100644 --- a/src/applications/calendar/storage/PhabricatorCalendarEvent.php +++ b/src/applications/calendar/storage/PhabricatorCalendarEvent.php @@ -1,220 +1,222 @@ 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', ); public function getTextStatus() { return self::$statusTexts[$this->status]; } public function getStatusOptions() { return array( self::STATUS_AWAY => pht('Away'), self::STATUS_SPORADIC => pht('Sporadic'), ); } public function getHumanStatus() { $options = $this->getStatusOptions(); return $options[$this->status]; } protected function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, self::CONFIG_COLUMN_SCHEMA => array( + 'name' => 'text', 'dateFrom' => 'epoch', 'dateTo' => 'epoch', 'status' => 'uint32', 'description' => 'text', ), self::CONFIG_KEY_SCHEMA => array( 'userPHID_dateFrom' => array( 'columns' => array('userPHID', 'dateTo'), ), ), ) + parent::getConfiguration(); } public function generatePHID() { return PhabricatorPHID::generateNewPHID( PhabricatorCalendarEventPHIDType::TYPECONST); } public function getMonogram() { return 'E'.$this->getID(); } public function getTerseSummary(PhabricatorUser $viewer) { $until = phabricator_date($this->dateTo, $viewer); if ($this->status == PhabricatorCalendarEvent::STATUS_SPORADIC) { return pht('Sporadic until %s', $until); } else { return pht('Away until %s', $until); } } public function setTextStatus($status) { $statuses = array_flip(self::$statusTexts); return $this->setStatus($statuses[$status]); } public function loadCurrentStatuses($user_phids) { if (!$user_phids) { return array(); } $statuses = $this->loadAllWhere( 'userPHID IN (%Ls) AND UNIX_TIMESTAMP() BETWEEN dateFrom AND dateTo', $user_phids); 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 */ public function save() { if ($this->getDateTo() <= $this->getDateFrom()) { throw new PhabricatorCalendarEventInvalidEpochException(); } return parent::save(); } /* -( 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 PhabricatorPolicies::getMostOpenPolicy(); case PhabricatorPolicyCapability::CAN_EDIT: return $this->getUserPHID(); } } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { return false; } public function describeAutomaticCapability($capability) { 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 index 4f5892a24b..c17bf0905f 100644 --- a/src/applications/calendar/storage/PhabricatorCalendarEventTransaction.php +++ b/src/applications/calendar/storage/PhabricatorCalendarEventTransaction.php @@ -1,210 +1,237 @@ getTransactionType()) { + case self::TYPE_NAME: 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_NAME: 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_NAME: 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_NAME: + if ($old) { + return pht( + '%s changed the name of this event from %s to %s.', + $this->renderHandleLink($author_phid), + $old, + $new); + } + break; 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_NAME: + if ($old) { + return pht( + '%s changed the name of %s from %s to %s.', + $this->renderHandleLink($author_phid), + $this->renderHandleLink($object_phid), + $old, + $new); + } + break; 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_NAME: 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_NAME: + $tags[] = self::MAILTAG_CONTENT; + break; 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; } }