Path: blob/master/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php
12256 views
<?php12final class PhabricatorCalendarEventSearchEngine3extends PhabricatorApplicationSearchEngine {45private $calendarYear;6private $calendarMonth;7private $calendarDay;89public function getResultTypeDescription() {10return pht('Calendar Events');11}1213public function getApplicationClassName() {14return 'PhabricatorCalendarApplication';15}1617public function newQuery() {18$viewer = $this->requireViewer();1920return id(new PhabricatorCalendarEventQuery())21->needRSVPs(array($viewer->getPHID()));22}2324protected function shouldShowOrderField() {25return false;26}2728protected function buildCustomSearchFields() {29return array(30id(new PhabricatorSearchDatasourceField())31->setLabel(pht('Hosts'))32->setKey('hostPHIDs')33->setAliases(array('host', 'hostPHID', 'hosts'))34->setDatasource(new PhabricatorPeopleUserFunctionDatasource()),35id(new PhabricatorSearchDatasourceField())36->setLabel(pht('Invited'))37->setKey('invitedPHIDs')38->setDatasource(new PhabricatorCalendarInviteeDatasource()),39id(new PhabricatorSearchDateControlField())40->setLabel(pht('Occurs After'))41->setKey('rangeStart'),42id(new PhabricatorSearchDateControlField())43->setLabel(pht('Occurs Before'))44->setKey('rangeEnd')45->setAliases(array('rangeEnd')),46id(new PhabricatorSearchCheckboxesField())47->setKey('upcoming')48->setOptions(array(49'upcoming' => pht('Show only upcoming events.'),50)),51id(new PhabricatorSearchSelectField())52->setLabel(pht('Cancelled Events'))53->setKey('isCancelled')54->setOptions($this->getCancelledOptions())55->setDefault('active'),56id(new PhabricatorPHIDsSearchField())57->setLabel(pht('Import Sources'))58->setKey('importSourcePHIDs')59->setAliases(array('importSourcePHID')),60id(new PhabricatorSearchSelectField())61->setLabel(pht('Display Options'))62->setKey('display')63->setOptions($this->getViewOptions())64->setDefault('month'),65);66}6768private function getCancelledOptions() {69return array(70'active' => pht('Active Events Only'),71'cancelled' => pht('Cancelled Events Only'),72'both' => pht('Both Cancelled and Active Events'),73);74}7576private function getViewOptions() {77return array(78'month' => pht('Month View'),79'day' => pht('Day View'),80'list' => pht('List View'),81);82}8384public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) {85$query = parent::buildQueryFromSavedQuery($saved);8687// If this is an export query for generating an ".ics" file, don't88// build ghost events.89if ($saved->getParameter('export')) {90$query->setGenerateGhosts(false);91}9293return $query;94}9596protected function buildQueryFromParameters(array $map) {97$query = $this->newQuery();98$viewer = $this->requireViewer();99100if ($map['hostPHIDs']) {101$query->withHostPHIDs($map['hostPHIDs']);102}103104if ($map['invitedPHIDs']) {105$query->withInvitedPHIDs($map['invitedPHIDs']);106}107108$range_start = $map['rangeStart'];109$range_end = $map['rangeEnd'];110$display = $map['display'];111112if ($map['upcoming'] && $map['upcoming'][0] == 'upcoming') {113$upcoming = true;114} else {115$upcoming = false;116}117118list($range_start, $range_end) = $this->getQueryDateRange(119$range_start,120$range_end,121$display,122$upcoming);123124$query->withDateRange($range_start, $range_end);125126switch ($map['isCancelled']) {127case 'active':128$query->withIsCancelled(false);129break;130case 'cancelled':131$query->withIsCancelled(true);132break;133}134135if ($map['importSourcePHIDs']) {136$query->withImportSourcePHIDs($map['importSourcePHIDs']);137}138139if (!$map['ids'] && !$map['phids']) {140$query141->withIsStub(false)142->setGenerateGhosts(true);143}144145return $query;146}147148private function getQueryDateRange(149$start_date_wild,150$end_date_wild,151$display,152$upcoming) {153154$start_date_value = $this->getSafeDate($start_date_wild);155$end_date_value = $this->getSafeDate($end_date_wild);156157$viewer = $this->requireViewer();158$timezone = new DateTimeZone($viewer->getTimezoneIdentifier());159$min_range = null;160$max_range = null;161162$min_range = $start_date_value->getEpoch();163$max_range = $end_date_value->getEpoch();164165if ($display == 'month' || $display == 'day') {166list($start_year, $start_month, $start_day) =167$this->getDisplayYearAndMonthAndDay($min_range, $max_range, $display);168169$start_day = new DateTime(170"{$start_year}-{$start_month}-{$start_day}",171$timezone);172$next = clone $start_day;173174if ($display == 'month') {175$next->modify('+1 month');176} else if ($display == 'day') {177$next->modify('+7 day');178}179180$display_start = $start_day->format('U');181$display_end = $next->format('U');182183$start_of_week = $viewer->getUserSetting(184PhabricatorWeekStartDaySetting::SETTINGKEY);185186$end_of_week = ($start_of_week + 6) % 7;187188$first_of_month = $start_day->format('w');189$last_of_month = id(clone $next)->modify('-1 day')->format('w');190191if (!$min_range || ($min_range < $display_start)) {192$min_range = $display_start;193194if ($display == 'month' &&195$first_of_month !== $start_of_week) {196$interim_day_num = ($first_of_month + 7 - $start_of_week) % 7;197$min_range = id(clone $start_day)198->modify('-'.$interim_day_num.' days')199->format('U');200}201}202if (!$max_range || ($max_range > $display_end)) {203$max_range = $display_end;204205if ($display == 'month' &&206$last_of_month !== $end_of_week) {207$interim_day_num = ($end_of_week + 7 - $last_of_month) % 7;208$max_range = id(clone $next)209->modify('+'.$interim_day_num.' days')210->format('U');211}212}213}214215if ($upcoming) {216$now = PhabricatorTime::getNow();217if ($min_range) {218$min_range = max($now, $min_range);219} else {220$min_range = $now;221}222}223224return array($min_range, $max_range);225}226227protected function getURI($path) {228return '/calendar/'.$path;229}230231protected function getBuiltinQueryNames() {232$names = array(233'month' => pht('Month View'),234'day' => pht('Day View'),235'upcoming' => pht('Upcoming Events'),236'all' => pht('All Events'),237);238239return $names;240}241242public function setCalendarYearAndMonthAndDay($year, $month, $day = null) {243$this->calendarYear = $year;244$this->calendarMonth = $month;245$this->calendarDay = $day;246247return $this;248}249250public function buildSavedQueryFromBuiltin($query_key) {251$query = $this->newSavedQuery();252$query->setQueryKey($query_key);253254switch ($query_key) {255case 'month':256return $query->setParameter('display', 'month');257case 'day':258return $query->setParameter('display', 'day');259case 'upcoming':260return $query261->setParameter('display', 'list')262->setParameter('upcoming', array(2630 => 'upcoming',264));265case 'all':266return $query;267}268269return parent::buildSavedQueryFromBuiltin($query_key);270}271272protected function renderResultList(273array $events,274PhabricatorSavedQuery $query,275array $handles) {276277if ($this->isMonthView($query)) {278$result = $this->buildCalendarMonthView($events, $query);279} else if ($this->isDayView($query)) {280$result = $this->buildCalendarDayView($events, $query);281} else {282$result = $this->buildCalendarListView($events, $query);283}284285return $result;286}287288private function buildCalendarListView(289array $events,290PhabricatorSavedQuery $query) {291292assert_instances_of($events, 'PhabricatorCalendarEvent');293$viewer = $this->requireViewer();294$list = new PHUIObjectItemListView();295296foreach ($events as $event) {297if ($event->getIsGhostEvent()) {298$monogram = $event->getParentEvent()->getMonogram();299$index = $event->getSequenceIndex();300$monogram = "{$monogram}/{$index}";301} else {302$monogram = $event->getMonogram();303}304305$item = id(new PHUIObjectItemView())306->setUser($viewer)307->setObject($event)308->setObjectName($monogram)309->setHeader($event->getName())310->setHref($event->getURI());311312$item->addAttribute($event->renderEventDate($viewer, false));313314if ($event->getIsCancelled()) {315$item->setDisabled(true);316}317318$status_icon = $event->getDisplayIcon($viewer);319$status_color = $event->getDisplayIconColor($viewer);320$status_label = $event->getDisplayIconLabel($viewer);321322$item->setStatusIcon("{$status_icon} {$status_color}", $status_label);323324$host = pht(325'Hosted by %s',326$viewer->renderHandle($event->getHostPHID()));327$item->addByline($host);328329$list->addItem($item);330}331332return $this->newResultView()333->setObjectList($list)334->setNoDataString(pht('No events found.'));335}336337private function buildCalendarMonthView(338array $events,339PhabricatorSavedQuery $query) {340assert_instances_of($events, 'PhabricatorCalendarEvent');341342$viewer = $this->requireViewer();343$now = PhabricatorTime::getNow();344345list($start_year, $start_month) =346$this->getDisplayYearAndMonthAndDay(347$this->getQueryDateFrom($query)->getEpoch(),348$this->getQueryDateTo($query)->getEpoch(),349$query->getParameter('display'));350351$now_year = phabricator_format_local_time($now, $viewer, 'Y');352$now_month = phabricator_format_local_time($now, $viewer, 'm');353$now_day = phabricator_format_local_time($now, $viewer, 'j');354355if ($start_month == $now_month && $start_year == $now_year) {356$month_view = new PHUICalendarMonthView(357$this->getQueryDateFrom($query),358$this->getQueryDateTo($query),359$start_month,360$start_year,361$now_day);362} else {363$month_view = new PHUICalendarMonthView(364$this->getQueryDateFrom($query),365$this->getQueryDateTo($query),366$start_month,367$start_year);368}369370$month_view->setUser($viewer);371372$viewer_phid = $viewer->getPHID();373foreach ($events as $event) {374$epoch_min = $event->getStartDateTimeEpoch();375$epoch_max = $event->getEndDateTimeEpoch();376377$is_invited = $event->isRSVPInvited($viewer_phid);378$is_attending = $event->getIsUserAttending($viewer_phid);379380$event_view = id(new AphrontCalendarEventView())381->setHostPHID($event->getHostPHID())382->setEpochRange($epoch_min, $epoch_max)383->setIsCancelled($event->getIsCancelled())384->setName($event->getName())385->setURI($event->getURI())386->setIsAllDay($event->getIsAllDay())387->setIcon($event->getDisplayIcon($viewer))388->setViewerIsInvited($is_invited || $is_attending)389->setDatetimeSummary($event->renderEventDate($viewer, true))390->setIconColor($event->getDisplayIconColor($viewer));391392$month_view->addEvent($event_view);393}394395$month_view->setBrowseURI(396$this->getURI('query/'.$query->getQueryKey().'/'));397398$from = $this->getQueryDateFrom($query)->getDateTime();399400$crumbs = array();401$crumbs[] = id(new PHUICrumbView())402->setName($from->format('F Y'));403404$header = id(new PHUIHeaderView())405->setProfileHeader(true)406->setHeader($from->format('F Y'));407408return $this->newResultView($month_view)409->setCrumbs($crumbs)410->setHeader($header);411}412413private function buildCalendarDayView(414array $events,415PhabricatorSavedQuery $query) {416417$viewer = $this->requireViewer();418419list($start_year, $start_month, $start_day) =420$this->getDisplayYearAndMonthAndDay(421$this->getQueryDateFrom($query)->getEpoch(),422$this->getQueryDateTo($query)->getEpoch(),423$query->getParameter('display'));424425$day_view = id(new PHUICalendarDayView(426$this->getQueryDateFrom($query),427$this->getQueryDateTo($query),428$start_year,429$start_month,430$start_day))431->setQuery($query->getQueryKey());432433$day_view->setUser($viewer);434435$phids = mpull($events, 'getHostPHID');436437foreach ($events as $event) {438$can_edit = PhabricatorPolicyFilter::hasCapability(439$viewer,440$event,441PhabricatorPolicyCapability::CAN_EDIT);442443$epoch_min = $event->getStartDateTimeEpoch();444$epoch_max = $event->getEndDateTimeEpoch();445446$status_icon = $event->getDisplayIcon($viewer);447$status_color = $event->getDisplayIconColor($viewer);448449$event_view = id(new AphrontCalendarEventView())450->setCanEdit($can_edit)451->setEventID($event->getID())452->setEpochRange($epoch_min, $epoch_max)453->setIsAllDay($event->getIsAllDay())454->setIcon($status_icon)455->setIconColor($status_color)456->setName($event->getName())457->setURI($event->getURI())458->setDatetimeSummary($event->renderEventDate($viewer, true))459->setIsCancelled($event->getIsCancelled());460461$day_view->addEvent($event_view);462}463464$browse_uri = $this->getURI('query/'.$query->getQueryKey().'/');465$day_view->setBrowseURI($browse_uri);466467$from = $this->getQueryDateFrom($query)->getDateTime();468$month_uri = $browse_uri.$from->format('Y/m/');469470$crumbs = array(471id(new PHUICrumbView())472->setName($from->format('F Y'))473->setHref($month_uri),474id(new PHUICrumbView())475->setName($from->format('D jS')),476);477478$header = id(new PHUIHeaderView())479->setProfileHeader(true)480->setHeader($from->format('D, F jS'));481482return $this->newResultView($day_view)483->setCrumbs($crumbs)484->setHeader($header);485}486487private function getDisplayYearAndMonthAndDay(488$range_start,489$range_end,490$display) {491492$viewer = $this->requireViewer();493$epoch = null;494495if ($this->calendarYear && $this->calendarMonth) {496$start_year = $this->calendarYear;497$start_month = $this->calendarMonth;498$start_day = $this->calendarDay ? $this->calendarDay : 1;499} else {500if ($range_start) {501$epoch = $range_start;502} else if ($range_end) {503$epoch = $range_end;504} else {505$epoch = time();506}507if ($display == 'month') {508$day = 1;509} else {510$day = phabricator_format_local_time($epoch, $viewer, 'd');511}512$start_year = phabricator_format_local_time($epoch, $viewer, 'Y');513$start_month = phabricator_format_local_time($epoch, $viewer, 'm');514$start_day = $day;515}516return array($start_year, $start_month, $start_day);517}518519public function getPageSize(PhabricatorSavedQuery $saved) {520if ($this->isMonthView($saved) || $this->isDayView($saved)) {521return $saved->getParameter('limit', 1000);522} else {523return $saved->getParameter('limit', 100);524}525}526527private function getQueryDateFrom(PhabricatorSavedQuery $saved) {528if ($this->calendarYear && $this->calendarMonth) {529$viewer = $this->requireViewer();530531$start_year = $this->calendarYear;532$start_month = $this->calendarMonth;533$start_day = $this->calendarDay ? $this->calendarDay : 1;534535return AphrontFormDateControlValue::newFromDictionary(536$viewer,537array(538'd' => "{$start_year}-{$start_month}-{$start_day}",539));540}541542return $this->getQueryDate($saved, 'rangeStart');543}544545private function getQueryDateTo(PhabricatorSavedQuery $saved) {546return $this->getQueryDate($saved, 'rangeEnd');547}548549private function getQueryDate(PhabricatorSavedQuery $saved, $key) {550$viewer = $this->requireViewer();551552$wild = $saved->getParameter($key);553return $this->getSafeDate($wild);554}555556private function getSafeDate($value) {557$viewer = $this->requireViewer();558if ($value) {559// ideally this would be consistent and always pass in the same type560if ($value instanceof AphrontFormDateControlValue) {561return $value;562} else {563$value = AphrontFormDateControlValue::newFromWild($viewer, $value);564}565} else {566$value = AphrontFormDateControlValue::newFromEpoch(567$viewer,568PhabricatorTime::getTodayMidnightDateTime($viewer)->format('U'));569$value->setEnabled(false);570}571572$value->setOptional(true);573574return $value;575}576577private function isMonthView(PhabricatorSavedQuery $query) {578if ($this->isDayView($query)) {579return false;580}581if ($query->getParameter('display') == 'month') {582return true;583}584}585586private function isDayView(PhabricatorSavedQuery $query) {587if ($query->getParameter('display') == 'day') {588return true;589}590if ($this->calendarDay) {591return true;592}593594return false;595}596597public function newUseResultsActions(PhabricatorSavedQuery $saved) {598$viewer = $this->requireViewer();599$can_export = $viewer->isLoggedIn();600601return array(602id(new PhabricatorActionView())603->setIcon('fa-download')604->setName(pht('Export Query as .ics'))605->setDisabled(!$can_export)606->setHref('/calendar/export/edit/?queryKey='.$saved->getQueryKey()),607);608}609610611private function newResultView($content = null) {612// If we aren't rendering a dashboard panel, activate global drag-and-drop613// so you can import ".ics" files by dropping them directly onto the614// calendar.615if (!$this->isPanelContext()) {616$drop_upload = id(new PhabricatorGlobalUploadTargetView())617->setViewer($this->requireViewer())618->setHintText("\xE2\x87\xAA ".pht('Drop .ics Files to Import'))619->setSubmitURI('/calendar/import/drop/')620->setViewPolicy(PhabricatorPolicies::POLICY_NOONE);621622$content = array(623$drop_upload,624$content,625);626}627628return id(new PhabricatorApplicationSearchResultView())629->setContent($content);630}631632}633634635