Path: blob/master/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php
12256 views
<?php12final class PhabricatorCalendarEventViewController3extends PhabricatorCalendarController {45public function shouldAllowPublic() {6return true;7}89public function handleRequest(AphrontRequest $request) {10$viewer = $request->getViewer();1112$event = $this->loadEvent();13if (!$event) {14return new Aphront404Response();15}1617// If we looked up or generated a stub event, redirect to that event's18// canonical URI.19$id = $request->getURIData('id');20if ($event->getID() != $id) {21$uri = $event->getURI();22return id(new AphrontRedirectResponse())->setURI($uri);23}2425$monogram = $event->getMonogram();26$page_title = $monogram.' '.$event->getName();27$crumbs = $this->buildApplicationCrumbs();2829$start = $event->newStartDateTime()30->newPHPDateTime();3132$crumbs->addTextCrumb(33$start->format('F Y'),34'/calendar/query/month/'.$start->format('Y/m/'));3536$crumbs->addTextCrumb(37$start->format('D jS'),38'/calendar/query/month/'.$start->format('Y/m/d/'));3940$crumbs->addTextCrumb($monogram);41$crumbs->setBorder(true);4243$timeline = $this->buildTransactionTimeline(44$event,45new PhabricatorCalendarEventTransactionQuery());4647$header = $this->buildHeaderView($event);48$subheader = $this->buildSubheaderView($event);49$curtain = $this->buildCurtain($event);50$details = $this->buildPropertySection($event);51$recurring = $this->buildRecurringSection($event);52$description = $this->buildDescriptionView($event);5354$comment_view = id(new PhabricatorCalendarEventEditEngine())55->setViewer($viewer)56->buildEditEngineCommentView($event);5758$timeline->setQuoteRef($monogram);59$comment_view->setTransactionTimeline($timeline);6061$details_header = id(new PHUIHeaderView())62->setHeader(pht('Details'));63$recurring_header = $this->buildRecurringHeader($event);6465// NOTE: This is a bit hacky: for imported events, we're just hiding the66// comment form without actually preventing comments. Users could still67// submit a request to add comments to these events. This isn't really a68// major problem since they can't do anything truly bad and there isn't an69// easy way to selectively disable this or some other similar behaviors70// today, but it would probably be nice to fully disable these71// "pseudo-edits" (like commenting and probably subscribing and awarding72// tokens) at some point.73if ($event->isImportedEvent()) {74$comment_view = null;75$timeline->setShouldTerminate(true);76}7778$view = id(new PHUITwoColumnView())79->setHeader($header)80->setSubheader($subheader)81->setMainColumn(82array(83$timeline,84$comment_view,85))86->setCurtain($curtain)87->addPropertySection(pht('Description'), $description)88->addPropertySection($recurring_header, $recurring)89->addPropertySection($details_header, $details);9091return $this->newPage()92->setTitle($page_title)93->setCrumbs($crumbs)94->setPageObjectPHIDs(array($event->getPHID()))95->appendChild($view);96}9798private function buildHeaderView(99PhabricatorCalendarEvent $event) {100$viewer = $this->getViewer();101$id = $event->getID();102103if ($event->getIsCancelled()) {104$icon = 'fa-ban';105$color = 'red';106$status = pht('Cancelled');107} else {108$icon = 'fa-check';109$color = 'bluegrey';110$status = pht('Active');111}112113$header = id(new PHUIHeaderView())114->setUser($viewer)115->setHeader($event->getName())116->setStatus($icon, $color, $status)117->setPolicyObject($event)118->setHeaderIcon($event->getIcon());119120if ($event->isImportedEvent()) {121$header->addTag(122id(new PHUITagView())123->setType(PHUITagView::TYPE_SHADE)124->setName(pht('Imported'))125->setIcon('fa-download')126->setHref($event->getImportSource()->getURI())127->setColor(PHUITagView::COLOR_ORANGE));128}129130foreach ($this->buildRSVPActions($event) as $action) {131$header->addActionLink($action);132}133134$options = PhabricatorCalendarEventInvitee::getAvailabilityMap();135136$is_attending = $event->getIsUserAttending($viewer->getPHID());137if ($is_attending) {138$invitee = $event->getInviteeForPHID($viewer->getPHID());139140$selected = $invitee->getDisplayAvailability($event);141if (!$selected) {142$selected = PhabricatorCalendarEventInvitee::AVAILABILITY_AVAILABLE;143}144145$selected_option = idx($options, $selected);146147$availability_select = id(new PHUIButtonView())148->setTag('a')149->setIcon('fa-circle '.$selected_option['color'])150->setText(pht('Availability: %s', $selected_option['name']));151152$dropdown = id(new PhabricatorActionListView())153->setUser($viewer);154155foreach ($options as $key => $option) {156$uri = "event/availability/{$id}/{$key}/";157$uri = $this->getApplicationURI($uri);158159$dropdown->addAction(160id(new PhabricatorActionView())161->setName($option['name'])162->setIcon('fa-circle '.$option['color'])163->setHref($uri)164->setWorkflow(true));165}166167$availability_select->setDropdownMenu($dropdown);168$header->addActionLink($availability_select);169}170171return $header;172}173174private function buildCurtain(PhabricatorCalendarEvent $event) {175$viewer = $this->getRequest()->getUser();176$id = $event->getID();177$is_attending = $event->getIsUserAttending($viewer->getPHID());178179$can_edit = PhabricatorPolicyFilter::hasCapability(180$viewer,181$event,182PhabricatorPolicyCapability::CAN_EDIT);183184$edit_uri = "event/edit/{$id}/";185$edit_uri = $this->getApplicationURI($edit_uri);186$is_recurring = $event->getIsRecurring();187$edit_label = pht('Edit Event');188189$curtain = $this->newCurtainView($event);190191if ($edit_label && $edit_uri) {192$curtain->addAction(193id(new PhabricatorActionView())194->setName($edit_label)195->setIcon('fa-pencil')196->setHref($edit_uri)197->setDisabled(!$can_edit)198->setWorkflow(!$can_edit || $is_recurring));199}200201$recurring_uri = "{$edit_uri}page/recurring/";202$can_recurring = $can_edit && !$event->isChildEvent();203204if ($event->getIsRecurring()) {205$recurring_label = pht('Edit Recurrence');206} else {207$recurring_label = pht('Make Recurring');208}209210$curtain->addAction(211id(new PhabricatorActionView())212->setName($recurring_label)213->setIcon('fa-repeat')214->setHref($recurring_uri)215->setDisabled(!$can_recurring)216->setWorkflow(true));217218$can_attend = !$event->isImportedEvent();219220if ($is_attending) {221$curtain->addAction(222id(new PhabricatorActionView())223->setName(pht('Decline Event'))224->setIcon('fa-user-times')225->setHref($this->getApplicationURI("event/join/{$id}/"))226->setDisabled(!$can_attend)227->setWorkflow(true));228} else {229$curtain->addAction(230id(new PhabricatorActionView())231->setName(pht('Join Event'))232->setIcon('fa-user-plus')233->setHref($this->getApplicationURI("event/join/{$id}/"))234->setDisabled(!$can_attend)235->setWorkflow(true));236}237238$cancel_uri = $this->getApplicationURI("event/cancel/{$id}/");239$cancel_disabled = !$can_edit;240241$cancel_label = pht('Cancel Event');242$reinstate_label = pht('Reinstate Event');243244if ($event->getIsCancelled()) {245$curtain->addAction(246id(new PhabricatorActionView())247->setName($reinstate_label)248->setIcon('fa-plus')249->setHref($cancel_uri)250->setDisabled($cancel_disabled)251->setWorkflow(true));252} else {253$curtain->addAction(254id(new PhabricatorActionView())255->setName($cancel_label)256->setIcon('fa-times')257->setHref($cancel_uri)258->setDisabled($cancel_disabled)259->setWorkflow(true));260}261262$ics_name = $event->getICSFilename();263$export_uri = $this->getApplicationURI("event/export/{$id}/{$ics_name}");264265$curtain->addAction(266id(new PhabricatorActionView())267->setName(pht('Export as .ics'))268->setIcon('fa-download')269->setHref($export_uri));270271return $curtain;272}273274private function buildPropertySection(275PhabricatorCalendarEvent $event) {276$viewer = $this->getViewer();277278$properties = id(new PHUIPropertyListView())279->setUser($viewer);280281$invitees = $event->getInvitees();282foreach ($invitees as $key => $invitee) {283if ($invitee->isUninvited()) {284unset($invitees[$key]);285}286}287288if ($invitees) {289$invitee_list = new PHUIStatusListView();290291$icon_invited = PHUIStatusItemView::ICON_OPEN;292$icon_attending = PHUIStatusItemView::ICON_ACCEPT;293$icon_declined = PHUIStatusItemView::ICON_REJECT;294295$status_invited = PhabricatorCalendarEventInvitee::STATUS_INVITED;296$status_attending = PhabricatorCalendarEventInvitee::STATUS_ATTENDING;297$status_declined = PhabricatorCalendarEventInvitee::STATUS_DECLINED;298299$icon_map = array(300$status_invited => $icon_invited,301$status_attending => $icon_attending,302$status_declined => $icon_declined,303);304305$icon_color_map = array(306$status_invited => null,307$status_attending => 'green',308$status_declined => 'red',309);310311$viewer_phid = $viewer->getPHID();312$is_rsvp_invited = $event->isRSVPInvited($viewer_phid);313$type_user = PhabricatorPeopleUserPHIDType::TYPECONST;314315$head = array();316$tail = array();317foreach ($invitees as $invitee) {318$item = new PHUIStatusItemView();319$invitee_phid = $invitee->getInviteePHID();320$status = $invitee->getStatus();321$target = $viewer->renderHandle($invitee_phid);322$is_user = (phid_get_type($invitee_phid) == $type_user);323324if (!$is_user) {325$icon = 'fa-users';326$icon_color = 'blue';327} else {328$icon = $icon_map[$status];329$icon_color = $icon_color_map[$status];330}331332// Highlight invited groups which you're a member of if you have333// not RSVP'd to an event yet.334if ($is_rsvp_invited) {335if ($invitee_phid != $viewer_phid) {336if ($event->hasRSVPAuthority($viewer_phid, $invitee_phid)) {337$item->setHighlighted(true);338}339}340}341342$item->setIcon($icon, $icon_color)343->setTarget($target);344345if ($is_user) {346$tail[] = $item;347} else {348$head[] = $item;349}350}351352foreach (array_merge($head, $tail) as $item) {353$invitee_list->addItem($item);354}355} else {356$invitee_list = phutil_tag(357'em',358array(),359pht('None'));360}361362if ($event->isImportedEvent()) {363$properties->addProperty(364pht('Imported By'),365pht(366'%s from %s',367$viewer->renderHandle($event->getImportAuthorPHID()),368$viewer->renderHandle($event->getImportSourcePHID())));369}370371$properties->addProperty(372pht('Invitees'),373$invitee_list);374375$properties->invokeWillRenderEvent();376377return $properties;378}379380private function buildRecurringHeader(PhabricatorCalendarEvent $event) {381$viewer = $this->getViewer();382383if (!$event->getIsRecurring()) {384return null;385}386387$header = id(new PHUIHeaderView())388->setHeader(pht('Recurring Event'));389390$sequence = $event->getSequenceIndex();391if ($event->isParentEvent()) {392$parent = $event;393} else {394$parent = $event->getParentEvent();395}396397if ($parent->isValidSequenceIndex($viewer, $sequence + 1)) {398$next_uri = $parent->getURI().'/'.($sequence + 1);399$has_next = true;400} else {401$next_uri = null;402$has_next = false;403}404405if ($sequence) {406if ($sequence > 1) {407$previous_uri = $parent->getURI().'/'.($sequence - 1);408} else {409$previous_uri = $parent->getURI();410}411$has_previous = true;412} else {413$has_previous = false;414$previous_uri = null;415}416417$prev_button = id(new PHUIButtonView())418->setTag('a')419->setIcon('fa-chevron-left')420->setHref($previous_uri)421->setDisabled(!$has_previous)422->setText(pht('Previous'));423424$next_button = id(new PHUIButtonView())425->setTag('a')426->setIcon('fa-chevron-right')427->setHref($next_uri)428->setDisabled(!$has_next)429->setText(pht('Next'));430431$header432->addActionLink($next_button)433->addActionLink($prev_button);434435return $header;436}437438private function buildRecurringSection(PhabricatorCalendarEvent $event) {439$viewer = $this->getViewer();440441if (!$event->getIsRecurring()) {442return null;443}444445$properties = id(new PHUIPropertyListView())446->setUser($viewer);447448$is_parent = $event->isParentEvent();449if ($is_parent) {450$parent_link = null;451} else {452$parent = $event->getParentEvent();453$parent_link = $viewer454->renderHandle($parent->getPHID())455->render();456}457458$rrule = $event->newRecurrenceRule();459460if ($rrule) {461$frequency = $rrule->getFrequency();462} else {463$frequency = null;464}465466switch ($frequency) {467case PhutilCalendarRecurrenceRule::FREQUENCY_DAILY:468if ($is_parent) {469$message = pht('This event repeats every day.');470} else {471$message = pht(472'This event is an instance of %s, and repeats every day.',473$parent_link);474}475break;476case PhutilCalendarRecurrenceRule::FREQUENCY_WEEKLY:477if ($is_parent) {478$message = pht('This event repeats every week.');479} else {480$message = pht(481'This event is an instance of %s, and repeats every week.',482$parent_link);483}484break;485case PhutilCalendarRecurrenceRule::FREQUENCY_MONTHLY:486if ($is_parent) {487$message = pht('This event repeats every month.');488} else {489$message = pht(490'This event is an instance of %s, and repeats every month.',491$parent_link);492}493break;494case PhutilCalendarRecurrenceRule::FREQUENCY_YEARLY:495if ($is_parent) {496$message = pht('This event repeats every year.');497} else {498$message = pht(499'This event is an instance of %s, and repeats every year.',500$parent_link);501}502break;503}504505$properties->addProperty(pht('Event Series'), $message);506507return $properties;508}509510private function buildDescriptionView(511PhabricatorCalendarEvent $event) {512$viewer = $this->getViewer();513514$properties = id(new PHUIPropertyListView())515->setUser($viewer);516517if (strlen($event->getDescription())) {518$description = new PHUIRemarkupView($viewer, $event->getDescription());519$properties->addTextContent($description);520return $properties;521}522523return null;524}525526private function loadEvent() {527$request = $this->getRequest();528$viewer = $this->getViewer();529530$id = $request->getURIData('id');531$sequence = $request->getURIData('sequence');532533// We're going to figure out which event you're trying to look at. Most of534// the time this is simple, but you may be looking at an instance of a535// recurring event which we haven't generated an object for.536537// If you are, we're going to generate a "stub" event so we have a real538// ID and PHID to work with, since the rest of the infrastructure relies539// on these identifiers existing.540541// Load the event identified by ID first.542$event = id(new PhabricatorCalendarEventQuery())543->setViewer($viewer)544->withIDs(array($id))545->needRSVPs(array($viewer->getPHID()))546->executeOne();547if (!$event) {548return null;549}550551// If we aren't looking at an instance of this event, this is a completely552// normal request and we can just return this event.553if (!$sequence) {554return $event;555}556557// When you view "E123/999", E123 is normally the parent event. However,558// you might visit a different instance first instead and then fiddle559// with the URI. If the event we're looking at is a child, we are going560// to act on the parent instead.561if ($event->isChildEvent()) {562$event = $event->getParentEvent();563}564565// Try to load the instance. If it already exists, we're all done and566// can just return it.567$instance = id(new PhabricatorCalendarEventQuery())568->setViewer($viewer)569->withInstanceSequencePairs(570array(571array($event->getPHID(), $sequence),572))573->executeOne();574if ($instance) {575return $instance;576}577578if (!$viewer->isLoggedIn()) {579throw new Exception(580pht(581'This event instance has not been created yet. Log in to create '.582'it.'));583}584585if (!$event->isValidSequenceIndex($viewer, $sequence)) {586return null;587}588589return $event->newStub($viewer, $sequence);590}591592private function buildSubheaderView(PhabricatorCalendarEvent $event) {593$viewer = $this->getViewer();594595$host_phid = $event->getHostPHID();596597$handles = $viewer->loadHandles(array($host_phid));598$handle = $handles[$host_phid];599600$host = $viewer->renderHandle($host_phid);601$host = phutil_tag('strong', array(), $host);602603$image_uri = $handles[$host_phid]->getImageURI();604$image_href = $handles[$host_phid]->getURI();605606$date = $event->renderEventDate($viewer, true);607608$content = pht('Hosted by %s on %s.', $host, $date);609610return id(new PHUIHeadThingView())611->setImage($image_uri)612->setImageHref($image_href)613->setContent($content);614}615616617private function buildRSVPActions(PhabricatorCalendarEvent $event) {618$viewer = $this->getViewer();619$id = $event->getID();620621$is_pending = $event->isRSVPInvited($viewer->getPHID());622if (!$is_pending) {623return array();624}625626$decline_button = id(new PHUIButtonView())627->setTag('a')628->setIcon('fa-times grey')629->setHref($this->getApplicationURI("/event/decline/{$id}/"))630->setWorkflow(true)631->setText(pht('Decline'));632633$accept_button = id(new PHUIButtonView())634->setTag('a')635->setIcon('fa-check green')636->setHref($this->getApplicationURI("/event/accept/{$id}/"))637->setWorkflow(true)638->setText(pht('Accept'));639640return array($decline_button, $accept_button);641}642643}644645646