diff --git a/src/applications/calendar/controller/PhabricatorCalendarBrowseController.php b/src/applications/calendar/controller/PhabricatorCalendarBrowseController.php index b384aeb58c..f10b0589ac 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarBrowseController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarBrowseController.php @@ -1,97 +1,102 @@ getRequest(); $user = $request->getUser(); $year_d = phabricator_format_local_time($now, $user, 'Y'); $year = $request->getInt('year', $year_d); $month_d = phabricator_format_local_time($now, $user, 'm'); $month = $request->getInt('month', $month_d); $day = phabricator_format_local_time($now, $user, 'j'); $holidays = id(new PhabricatorCalendarHoliday())->loadAllWhere( 'day BETWEEN %s AND %s', "{$year}-{$month}-01", "{$year}-{$month}-31"); $statuses = id(new PhabricatorCalendarEventQuery()) ->setViewer($user) ->withDateRange( strtotime("{$year}-{$month}-01"), strtotime("{$year}-{$month}-01 next month")) ->execute(); if ($month == $month_d && $year == $year_d) { $month_view = new PHUICalendarMonthView($month, $year, $day); } else { $month_view = new PHUICalendarMonthView($month, $year); } $month_view->setBrowseURI($request->getRequestURI()); $month_view->setUser($user); $month_view->setHolidays($holidays); $phids = mpull($statuses, 'getUserPHID'); $handles = $this->loadViewerHandles($phids); foreach ($statuses as $status) { $event = new AphrontCalendarEventView(); $event->setEpochRange($status->getDateFrom(), $status->getDateTo()); $name_text = $handles[$status->getUserPHID()]->getName(); $status_text = $status->getHumanStatus(); $event->setUserPHID($status->getUserPHID()); $event->setName("{$name_text} ({$status_text})"); $details = ''; if ($status->getDescription()) { $details = "\n\n".rtrim($status->getDescription()); } $event->setDescription( $status->getTerseSummary($user).$details); $event->setEventID($status->getID()); $month_view->addEvent($event); } + $date = new DateTime("{$year}-{$month}-01"); + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb($date->format('F Y')); + $nav = $this->buildSideNavView(); $nav->selectFilter('/'); $nav->appendChild( array( + $crumbs, $this->getNoticeView(), $month_view, )); return $this->buildApplicationPage( $nav, array( 'title' => pht('Calendar'), 'device' => true, )); } private function getNoticeView() { $request = $this->getRequest(); $view = null; if ($request->getExists('created')) { $view = id(new AphrontErrorView()) ->setSeverity(AphrontErrorView::SEVERITY_NOTICE) ->setTitle(pht('Successfully created your status.')); } else if ($request->getExists('updated')) { $view = id(new AphrontErrorView()) ->setSeverity(AphrontErrorView::SEVERITY_NOTICE) ->setTitle(pht('Successfully updated your status.')); } else if ($request->getExists('deleted')) { $view = id(new AphrontErrorView()) ->setSeverity(AphrontErrorView::SEVERITY_NOTICE) ->setTitle(pht('Successfully deleted your status.')); } return $view; } } diff --git a/src/applications/calendar/controller/PhabricatorCalendarController.php b/src/applications/calendar/controller/PhabricatorCalendarController.php index 80d01e4a43..c9328cab2f 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarController.php @@ -1,22 +1,38 @@ setBaseURI(new PhutilURI($this->getApplicationURI())); $nav->addLabel(pht('Calendar')); $nav->addFilter('/', pht('View All')); - $nav->addFilter('event/create/', pht('New Status')); + $nav->addFilter('event/create/', pht('Create Event')); if ($status && $status->getID()) { - $nav->addFilter('event/edit/'.$status->getID().'/', pht('Edit Status')); + $nav->addFilter('event/edit/'.$status->getID().'/', pht('Edit Event')); } - $nav->addFilter('event/', pht('Upcoming Statuses')); + $nav->addFilter('event/', pht('Upcoming Events')); return $nav; } + public function buildApplicationMenu() { + return $this->buildSideNavView()->getMenu(); + } + + public function buildApplicationCrumbs() { + $crumbs = parent::buildApplicationCrumbs(); + + $crumbs->addAction( + id(new PHUIListItemView()) + ->setName(pht('Create Event')) + ->setHref($this->getApplicationURI().'event/create') + ->setIcon('create')); + + return $crumbs; + } + } diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php b/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php index 4aa00de082..53ef1fc567 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php @@ -1,190 +1,197 @@ id = idx($data, 'id'); } public function isCreate() { return !$this->id; } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $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 = new PhabricatorCalendarEvent(); $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'; } else { $status = id(new PhabricatorCalendarEventQuery()) ->setViewer($user) ->withIDs(array($this->id)) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) ->executeOne(); $end_time->setValue($status->getDateTo()); $start_time->setValue($status->getDateFrom()); $submit_label = pht('Update'); $filter = 'event/edit/'.$status->getID().'/'; $page_title = pht('Update Event'); $redirect = 'updated'; } $errors = array(); if ($request->isFormPost()) { $type = $request->getInt('status'); $start_value = $start_time->readValueFromRequest($request); $end_value = $end_time->readValueFromRequest($request); $description = $request->getStr('description'); 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.'); } catch (PhabricatorCalendarEventOverlapException $e) { $errors[] = pht('There is already a status within the specified '. 'timeframe. Edit or delete this existing status.'); } 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; } } $error_view = null; if ($errors) { $error_view = id(new AphrontErrorView()) ->setTitle(pht('Status can not be set!')) ->setErrors($errors); } $status_select = id(new AphrontFormSelectControl()) ->setLabel(pht('Status')) ->setName('status') ->setValue($status->getStatus()) ->setOptions($status->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); } $form ->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); } if ($this->isCreate()) { $submit->addCancelButton($this->getApplicationURI()); } else { $submit->addCancelButton( $this->getApplicationURI('event/view/'.$status->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->selectFilter($filter); + $crumbs = $this + ->buildApplicationCrumbs() + ->addTextCrumb($page_title); + $nav->appendChild( array( + $crumbs, $form_box, )); return $this->buildApplicationPage( $nav, array( 'title' => $page_title, 'device' => true )); } } diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventListController.php b/src/applications/calendar/controller/PhabricatorCalendarEventListController.php index 0727aec39c..c33d0bcdb9 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventListController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventListController.php @@ -1,93 +1,81 @@ queryKey = idx($data, 'queryKey'); } public function processRequest() { $request = $this->getRequest(); $controller = id(new PhabricatorApplicationSearchController($request)) ->setQueryKey($this->queryKey) ->setSearchEngine(new PhabricatorCalendarEventSearchEngine()) ->setNavigation($this->buildSideNav()); return $this->delegateToController($controller); } public function buildSideNav() { $user = $this->getRequest()->getUser(); $nav = new AphrontSideNavFilterView(); $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); id(new PhabricatorCalendarEventSearchEngine()) ->setViewer($user) ->addNavigationItems($nav->getMenu()); $nav->selectFilter(null); return $nav; } - public function buildApplicationCrumbs() { - $crumbs = parent::buildApplicationCrumbs(); - - $crumbs->addAction( - id(new PHUIListItemView()) - ->setIcon('create') - ->setName(pht('Create Event')) - ->setHref($this->getApplicationURI().'create/')); - - return $crumbs; - } - public function renderResultsList( array $events, PhabricatorSavedQuery $query) { assert_instances_of($events, 'PhabricatorCalendarEvent'); $viewer = $this->getRequest()->getUser(); $list = new PHUIObjectItemListView(); foreach ($events as $event) { if ($event->getUserPHID() == $viewer->getPHID()) { $href = $this->getApplicationURI('/event/edit/'.$event->getID().'/'); } else { $from = $event->getDateFrom(); $month = phabricator_format_local_time($from, $viewer, 'm'); $year = phabricator_format_local_time($from, $viewer, 'Y'); $uri = new PhutilURI($this->getApplicationURI()); $uri->setQueryParams( array( 'month' => $month, 'year' => $year, )); $href = (string) $uri; } $from = phabricator_datetime($event->getDateFrom(), $viewer); $to = phabricator_datetime($event->getDateTo(), $viewer); $color = ($event->getStatus() == PhabricatorCalendarEvent::STATUS_AWAY) ? 'red' : 'yellow'; $item = id(new PHUIObjectItemView()) ->setHeader($event->getTerseSummary($viewer)) ->setHref($href) ->setBarColor($color) ->addAttribute(pht('From %s to %s', $from, $to)) ->addAttribute( phutil_utf8_shorten($event->getDescription(), 64)); $list->addItem($item); } return $list; } } diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php b/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php index 1684f3cb4d..a03995f2de 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php @@ -1,112 +1,112 @@ 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 = pht('Event %d', $event->getID()); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($title); $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, ), array( 'title' => $title, 'device' => true, )); } private function buildHeaderView(PhabricatorCalendarEvent $event) { $viewer = $this->getRequest()->getUser(); return id(new PHUIHeaderView()) ->setUser($viewer) ->setHeader($event->getTerseSummary($viewer)) ->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('edit') ->setHref($this->getApplicationURI("event/edit/{$id}/")) ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); $actions->addAction( id(new PhabricatorActionView()) ->setName(pht('Cancel Event')) ->setIcon('delete') ->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('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/storage/PhabricatorCalendarEvent.php b/src/applications/calendar/storage/PhabricatorCalendarEvent.php index db9af33530..7eeecdedc3 100644 --- a/src/applications/calendar/storage/PhabricatorCalendarEvent.php +++ b/src/applications/calendar/storage/PhabricatorCalendarEvent.php @@ -1,136 +1,136 @@ '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]; } public function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, ) + parent::getConfiguration(); } public function generatePHID() { return PhabricatorPHID::generateNewPHID( PhabricatorCalendarPHIDTypeEvent::TYPECONST); } public function getTerseSummary(PhabricatorUser $viewer) { $until = phabricator_date($this->dateTo, $viewer); if ($this->status == PhabricatorCalendarEvent::STATUS_SPORADIC) { - return 'Sporadic until '.$until; + return pht('Sporadic until %s', $until); } else { - return 'Away until '.$until; + 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'); } /** * Validates data and throws exceptions for non-sensical status * windows and attempts to create an overlapping status. */ public function save() { if ($this->getDateTo() <= $this->getDateFrom()) { throw new PhabricatorCalendarEventInvalidEpochException(); } $this->openTransaction(); $this->beginWriteLocking(); if ($this->shouldInsertWhenSaved()) { $overlap = $this->loadAllWhere( 'userPHID = %s AND dateFrom < %d AND dateTo > %d', $this->getUserPHID(), $this->getDateTo(), $this->getDateFrom()); if ($overlap) { $this->endWriteLocking(); $this->killTransaction(); throw new PhabricatorCalendarEventOverlapException(); } } parent::save(); $this->endWriteLocking(); return $this->saveTransaction(); } /* -( 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; } }