diff --git a/src/applications/calendar/application/PhabricatorCalendarApplication.php b/src/applications/calendar/application/PhabricatorCalendarApplication.php index 638317165..b0b5d817f 100644 --- a/src/applications/calendar/application/PhabricatorCalendarApplication.php +++ b/src/applications/calendar/application/PhabricatorCalendarApplication.php @@ -1,75 +1,75 @@ <?php final class PhabricatorCalendarApplication extends PhabricatorApplication { public function getName() { return pht('Calendar'); } public function getShortDescription() { return pht('Upcoming Events'); } public function getFlavorText() { return pht('Never miss an episode ever again.'); } public function getBaseURI() { return '/calendar/'; } public function getFontIcon() { return 'fa-calendar'; } public function getTitleGlyph() { // Unicode has a calendar character but it's in some distant code plane, // use "keyboard" since it looks vaguely similar. return "\xE2\x8C\xA8"; } public function isPrototype() { return true; } public function getRemarkupRules() { return array( new PhabricatorCalendarRemarkupRule(), ); } public function getRoutes() { return array( '/E(?P<id>[1-9]\d*)' => 'PhabricatorCalendarEventViewController', '/calendar/' => array( '' => 'PhabricatorCalendarViewController', 'all/' => 'PhabricatorCalendarBrowseController', 'event/' => array( '(?:query/(?P<queryKey>[^/]+)/)?' => 'PhabricatorCalendarEventListController', 'create/' => 'PhabricatorCalendarEventEditController', 'edit/(?P<id>[1-9]\d*)/' => 'PhabricatorCalendarEventEditController', 'cancel/(?P<id>[1-9]\d*)/' => 'PhabricatorCalendarEventCancelController', - 'join/(?P<id>[1-9]\d*)/' + '(?P<action>join|decline|accept)/(?P<id>[1-9]\d*)/' => 'PhabricatorCalendarEventJoinController', ), ), ); } public function getQuickCreateItems(PhabricatorUser $viewer) { $items = array(); $item = id(new PHUIListItemView()) ->setName(pht('Calendar Event')) ->setIcon('fa-calendar') ->setHref($this->getBaseURI().'event/create/'); $items[] = $item; return $items; } } diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php b/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php index 01875351b..cd72d03fd 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php @@ -1,279 +1,277 @@ <?php final class PhabricatorCalendarEventEditController extends PhabricatorCalendarController { private $id; public function willProcessRequest(array $data) { $this->id = idx($data, 'id'); } public function isCreate() { return !$this->id; } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $user_phid = $user->getPHID(); $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()) { $event = PhabricatorCalendarEvent::initializeNewCalendarEvent($user); $end_value = $end_time->readValueFromRequest($request); $start_value = $start_time->readValueFromRequest($request); $submit_label = pht('Create'); $filter = 'event/create/'; $page_title = pht('Create Event'); $redirect = 'created'; $subscribers = array(); $invitees = array($user_phid); } else { $event = id(new PhabricatorCalendarEventQuery()) ->setViewer($user) ->withIDs(array($this->id)) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) ->executeOne(); if (!$event) { return new Aphront404Response(); } $end_time->setValue($event->getDateTo()); $start_time->setValue($event->getDateFrom()); $submit_label = pht('Update'); $filter = 'event/edit/'.$event->getID().'/'; $page_title = pht('Update Event'); $redirect = 'updated'; $subscribers = PhabricatorSubscribersQuery::loadSubscribersForPHID( $event->getPHID()); + $invitees = array(); foreach ($event->getInvitees() as $invitee) { if ($invitee->isUninvited()) { continue; } else { $invitees[] = $invitee->getInviteePHID(); } } } $errors = array(); if ($request->isFormPost()) { $xactions = array(); $name = $request->getStr('name'); $type = $request->getInt('status'); $start_value = $start_time->readValueFromRequest($request); $end_value = $end_time->readValueFromRequest($request); $description = $request->getStr('description'); $subscribers = $request->getArr('subscribers'); + $invitees = $request->getArr('invitees'); $new_invitees = $this->getNewInviteeList($invitees, $event); - + $status_attending = PhabricatorCalendarEventInvitee::STATUS_ATTENDING; if ($this->isCreate()) { $status = idx($new_invitees, $user->getPHID()); - $status_attending = PhabricatorCalendarEventInvitee::STATUS_ATTENDING; if ($status) { $new_invitees[$user->getPHID()] = $status_attending; } } 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( PhabricatorTransactions::TYPE_SUBSCRIBERS) ->setNewValue(array('=' => array_fuse($subscribers))); $xactions[] = id(new PhabricatorCalendarEventTransaction()) ->setTransactionType( PhabricatorCalendarEventTransaction::TYPE_INVITE) ->setNewValue($new_invitees); $xactions[] = id(new PhabricatorCalendarEventTransaction()) ->setTransactionType( PhabricatorCalendarEventTransaction::TYPE_DESCRIPTION) ->setNewValue($description); $editor = id(new PhabricatorCalendarEventEditor()) ->setActor($user) ->setContentSourceFromRequest($request) ->setContinueOnNoEffect(true); 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($event->getStatus()) ->setOptions($event->getStatusOptions()); $description = id(new AphrontFormTextAreaControl()) ->setLabel(pht('Description')) ->setName('description') ->setValue($event->getDescription()); $subscribers = id(new AphrontFormTokenizerControl()) ->setLabel(pht('Subscribers')) ->setName('subscribers') ->setValue($subscribers) ->setUser($user) ->setDatasource(new PhabricatorMetaMTAMailableDatasource()); $invitees = id(new AphrontFormTokenizerControl()) ->setLabel(pht('Invitees')) ->setName('invitees') ->setValue($invitees) ->setUser($user) ->setDatasource(new PhabricatorMetaMTAMailableDatasource()); $form = id(new AphrontFormView()) ->setUser($user) ->appendChild($name) ->appendChild($status_select) ->appendChild($start_time) ->appendChild($end_time) ->appendControl($subscribers) ->appendControl($invitees) ->appendChild($description); $submit = id(new AphrontFormSubmitControl()) ->setValue($submit_label); if ($this->isCreate()) { $submit->addCancelButton($this->getApplicationURI()); } else { $submit->addCancelButton('/E'.$event->getID()); } $form->appendChild($submit); $form_box = id(new PHUIObjectBoxView()) ->setHeaderText($page_title) ->setFormErrors($errors) ->setForm($form); $nav = $this->buildSideNavView($event); $nav->selectFilter($filter); $crumbs = $this->buildApplicationCrumbs(); if (!$this->isCreate()) { $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, $object_box, )); return $this->buildApplicationPage( $nav, array( 'title' => $page_title, )); } public function getNewInviteeList(array $phids, $event) { $invitees = $event->getInvitees(); $invitees = mpull($invitees, null, 'getInviteePHID'); $invited_status = PhabricatorCalendarEventInvitee::STATUS_INVITED; $uninvited_status = PhabricatorCalendarEventInvitee::STATUS_UNINVITED; $phids = array_fuse($phids); $new = array(); foreach ($phids as $phid) { - $old_invitee = idx($invitees, $phid); - if ($old_invitee) { - $old_status = $old_invitee->getStatus(); - if ($old_status != $uninvited_status) { - continue; - } + $old_status = $event->getUserInviteStatus($phid); + if ($old_status != $uninvited_status) { + continue; } $new[$phid] = $invited_status; } foreach ($invitees as $invitee) { $deleted_invitee = !idx($phids, $invitee->getInviteePHID()); if ($deleted_invitee) { $new[$invitee->getInviteePHID()] = $uninvited_status; } } return $new; } } diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventJoinController.php b/src/applications/calendar/controller/PhabricatorCalendarEventJoinController.php index 59c955767..00a1a80bd 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventJoinController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventJoinController.php @@ -1,74 +1,81 @@ <?php final class PhabricatorCalendarEventJoinController extends PhabricatorCalendarController { private $id; + const ACTION_ACCEPT = 'accept'; + const ACTION_DECLINE = 'decline'; + const ACTION_JOIN = 'join'; + public function handleRequest(AphrontRequest $request) { $this->id = $request->getURIData('id'); + $action = $request->getURIData('action'); + $request = $this->getRequest(); $viewer = $request->getViewer(); $declined_status = PhabricatorCalendarEventInvitee::STATUS_DECLINED; $attending_status = PhabricatorCalendarEventInvitee::STATUS_ATTENDING; $event = id(new PhabricatorCalendarEventQuery()) ->setViewer($viewer) ->withIDs(array($this->id)) ->executeOne(); if (!$event) { return new Aphront404Response(); } $cancel_uri = '/E'.$event->getID(); $validation_exception = null; $is_attending = $event->getIsUserAttending($viewer->getPHID()); if ($request->isFormPost()) { $new_status = null; if ($is_attending) { $new_status = array($viewer->getPHID() => $declined_status); } else { $new_status = array($viewer->getPHID() => $attending_status); } $xaction = id(new PhabricatorCalendarEventTransaction()) ->setTransactionType( PhabricatorCalendarEventTransaction::TYPE_INVITE) ->setNewValue($new_status); $editor = id(new PhabricatorCalendarEventEditor()) ->setActor($viewer) ->setContentSourceFromRequest($request) ->setContinueOnNoEffect(true) ->setContinueOnMissingFields(true); try { $editor->applyTransactions($event, array($xaction)); return id(new AphrontRedirectResponse())->setURI($cancel_uri); } catch (PhabricatorApplicationTransactionValidationException $ex) { $validation_exception = $ex; } } - if (!$is_attending) { + if (($action == self::ACTION_JOIN && !$is_attending) + || $action == self::ACTION_ACCEPT) { $title = pht('Join Event'); $paragraph = pht('Would you like to join this event?'); $submit = pht('Join'); } else { $title = pht('Decline Event'); $paragraph = pht('Would you like to decline this event?'); $submit = pht('Decline'); } return $this->newDialog() ->setTitle($title) ->setValidationException($validation_exception) ->appendParagraph($paragraph) ->addCancelButton($cancel_uri) ->addSubmitButton($submit); } } diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php b/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php index d8de1fffb..0019ad071 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php @@ -1,175 +1,203 @@ <?php final class PhabricatorCalendarEventViewController extends PhabricatorCalendarController { private $id; public function shouldAllowPublic() { return true; } public function willProcessRequest(array $data) { $this->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' => $page_title, )); } private function buildHeaderView(PhabricatorCalendarEvent $event) { $viewer = $this->getRequest()->getUser(); + $id = $event->getID(); + $is_cancelled = $event->getIsCancelled(); $icon = $is_cancelled ? ('fa-times') : ('fa-calendar'); $color = $is_cancelled ? ('grey') : ('green'); $status = $is_cancelled ? ('Cancelled') : ('Active'); - return id(new PHUIHeaderView()) + $invite_status = $event->getUserInviteStatus($viewer->getPHID()); + $status_invited = PhabricatorCalendarEventInvitee::STATUS_INVITED; + $is_invite_pending = ($invite_status == $status_invited); + + $header = id(new PHUIHeaderView()) ->setUser($viewer) ->setHeader($event->getName()) ->setStatus($icon, $color, $status) ->setPolicyObject($event); + + if ($is_invite_pending) { + $decline_button = id(new PHUIButtonView()) + ->setTag('a') + ->setIcon(id(new PHUIIconView()) + ->setIconFont('fa-times grey')) + ->setHref($this->getApplicationURI("/event/decline/{$id}/")) + ->setWorkflow(true) + ->setText(pht('Decline')); + + $accept_button = id(new PHUIButtonView()) + ->setTag('a') + ->setIcon(id(new PHUIIconView()) + ->setIconFont('fa-check green')) + ->setHref($this->getApplicationURI("/event/accept/{$id}/")) + ->setWorkflow(true) + ->setText(pht('Accept')); + + $header->addActionLink($decline_button) + ->addActionLink($accept_button); + } + return $header; } private function buildActionView(PhabricatorCalendarEvent $event) { $viewer = $this->getRequest()->getUser(); $id = $event->getID(); $is_cancelled = $event->getIsCancelled(); $is_attending = $event->getIsUserAttending($viewer->getPHID()); $actions = id(new PhabricatorActionListView()) ->setObjectURI($this->getApplicationURI('event/'.$id.'/')) ->setUser($viewer) ->setObject($event); $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)); if ($is_attending) { $actions->addAction( id(new PhabricatorActionView()) ->setName(pht('Decline Event')) ->setIcon('fa-user-times') ->setHref($this->getApplicationURI("event/join/{$id}/")) ->setWorkflow(true)); } else { $actions->addAction( id(new PhabricatorActionView()) ->setName(pht('Join Event')) ->setIcon('fa-user-plus') ->setHref($this->getApplicationURI("event/join/{$id}/")) ->setWorkflow(true)); } if ($is_cancelled) { $actions->addAction( id(new PhabricatorActionView()) ->setName(pht('Reinstate Event')) ->setIcon('fa-plus') ->setHref($this->getApplicationURI("event/cancel/{$id}/")) ->setDisabled(!$can_edit) ->setWorkflow(true)); } else { $actions->addAction( id(new PhabricatorActionView()) ->setName(pht('Cancel Event')) ->setIcon('fa-times') ->setHref($this->getApplicationURI("event/cancel/{$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)); $invitees = $event->getInvitees(); $invitee_list = new PHUIStatusListView(); foreach ($invitees as $invitee) { if ($invitee->isUninvited()) { continue; } $item = new PHUIStatusItemView(); $invitee_phid = $invitee->getInviteePHID(); $target = $viewer->renderHandle($invitee_phid); - $item->setNote($invitee->getStatus()); - $item->setTarget($target); + $item->setNote($invitee->getStatus()) + ->setTarget($target); $invitee_list->addItem($item); } $properties->addProperty( pht('Invitees'), $invitee_list); $properties->invokeWillRenderEvent(); $properties->addSectionHeader( pht('Description'), PHUIPropertyListView::ICON_SUMMARY); $properties->addTextContent($event->getDescription()); return $properties; } }