mirror of
https://we.phorge.it/source/phorge.git
synced 2025-01-10 14:51:06 +01:00
3085e52843
Summary: Ref T11326. Try to make this a little more useful: - Don't show entire attendee list (not useful?) - Show host (useful?) - Show your own status prominently (attending vs declined vs invited). - Show cancelled events prominently. Test Plan: {F1723550} Reviewers: chad Reviewed By: chad Maniphest Tasks: T11326 Differential Revision: https://secure.phabricator.com/D16299
552 lines
16 KiB
PHP
552 lines
16 KiB
PHP
<?php
|
|
|
|
final class PhabricatorCalendarEventSearchEngine
|
|
extends PhabricatorApplicationSearchEngine {
|
|
|
|
private $calendarYear;
|
|
private $calendarMonth;
|
|
private $calendarDay;
|
|
|
|
public function getResultTypeDescription() {
|
|
return pht('Calendar Events');
|
|
}
|
|
|
|
public function getApplicationClassName() {
|
|
return 'PhabricatorCalendarApplication';
|
|
}
|
|
|
|
public function newQuery() {
|
|
return new PhabricatorCalendarEventQuery();
|
|
}
|
|
|
|
protected function shouldShowOrderField() {
|
|
return false;
|
|
}
|
|
|
|
protected function buildCustomSearchFields() {
|
|
return array(
|
|
id(new PhabricatorSearchDatasourceField())
|
|
->setLabel(pht('Created By'))
|
|
->setKey('creatorPHIDs')
|
|
->setDatasource(new PhabricatorPeopleUserFunctionDatasource()),
|
|
id(new PhabricatorSearchDatasourceField())
|
|
->setLabel(pht('Invited'))
|
|
->setKey('invitedPHIDs')
|
|
->setDatasource(new PhabricatorPeopleUserFunctionDatasource()),
|
|
id(new PhabricatorSearchDateControlField())
|
|
->setLabel(pht('Occurs After'))
|
|
->setKey('rangeStart'),
|
|
id(new PhabricatorSearchDateControlField())
|
|
->setLabel(pht('Occurs Before'))
|
|
->setKey('rangeEnd')
|
|
->setAliases(array('rangeEnd')),
|
|
id(new PhabricatorSearchCheckboxesField())
|
|
->setKey('upcoming')
|
|
->setOptions(array(
|
|
'upcoming' => pht('Show only upcoming events.'),
|
|
)),
|
|
id(new PhabricatorSearchSelectField())
|
|
->setLabel(pht('Cancelled Events'))
|
|
->setKey('isCancelled')
|
|
->setOptions($this->getCancelledOptions())
|
|
->setDefault('active'),
|
|
id(new PhabricatorSearchSelectField())
|
|
->setLabel(pht('Display Options'))
|
|
->setKey('display')
|
|
->setOptions($this->getViewOptions())
|
|
->setDefault('month'),
|
|
);
|
|
}
|
|
|
|
private function getCancelledOptions() {
|
|
return array(
|
|
'active' => pht('Active Events Only'),
|
|
'cancelled' => pht('Cancelled Events Only'),
|
|
'both' => pht('Both Cancelled and Active Events'),
|
|
);
|
|
}
|
|
|
|
private function getViewOptions() {
|
|
return array(
|
|
'month' => pht('Month View'),
|
|
'day' => pht('Day View'),
|
|
'list' => pht('List View'),
|
|
);
|
|
}
|
|
|
|
protected function buildQueryFromParameters(array $map) {
|
|
$query = $this->newQuery();
|
|
$viewer = $this->requireViewer();
|
|
|
|
if ($map['creatorPHIDs']) {
|
|
$query->withCreatorPHIDs($map['creatorPHIDs']);
|
|
}
|
|
|
|
if ($map['invitedPHIDs']) {
|
|
$query->withInvitedPHIDs($map['invitedPHIDs']);
|
|
}
|
|
|
|
$range_start = $map['rangeStart'];
|
|
$range_end = $map['rangeEnd'];
|
|
$display = $map['display'];
|
|
|
|
if ($map['upcoming'] && $map['upcoming'][0] == 'upcoming') {
|
|
$upcoming = true;
|
|
} else {
|
|
$upcoming = false;
|
|
}
|
|
|
|
list($range_start, $range_end) = $this->getQueryDateRange(
|
|
$range_start,
|
|
$range_end,
|
|
$display,
|
|
$upcoming);
|
|
|
|
$query->withDateRange($range_start, $range_end);
|
|
|
|
switch ($map['isCancelled']) {
|
|
case 'active':
|
|
$query->withIsCancelled(false);
|
|
break;
|
|
case 'cancelled':
|
|
$query->withIsCancelled(true);
|
|
break;
|
|
}
|
|
|
|
// Generate ghosts (and ignore stub events) if we aren't querying for
|
|
// specific events.
|
|
if (!$map['ids'] && !$map['phids']) {
|
|
$query
|
|
->withIsStub(false)
|
|
->setGenerateGhosts(true);
|
|
}
|
|
|
|
return $query;
|
|
}
|
|
|
|
private function getQueryDateRange(
|
|
$start_date_wild,
|
|
$end_date_wild,
|
|
$display,
|
|
$upcoming) {
|
|
|
|
$start_date_value = $this->getSafeDate($start_date_wild);
|
|
$end_date_value = $this->getSafeDate($end_date_wild);
|
|
|
|
$viewer = $this->requireViewer();
|
|
$timezone = new DateTimeZone($viewer->getTimezoneIdentifier());
|
|
$min_range = null;
|
|
$max_range = null;
|
|
|
|
$min_range = $start_date_value->getEpoch();
|
|
$max_range = $end_date_value->getEpoch();
|
|
|
|
if ($display == 'month' || $display == 'day') {
|
|
list($start_year, $start_month, $start_day) =
|
|
$this->getDisplayYearAndMonthAndDay($min_range, $max_range, $display);
|
|
|
|
$start_day = new DateTime(
|
|
"{$start_year}-{$start_month}-{$start_day}",
|
|
$timezone);
|
|
$next = clone $start_day;
|
|
|
|
if ($display == 'month') {
|
|
$next->modify('+1 month');
|
|
} else if ($display == 'day') {
|
|
$next->modify('+7 day');
|
|
}
|
|
|
|
$display_start = $start_day->format('U');
|
|
$display_end = $next->format('U');
|
|
|
|
$start_of_week = $viewer->getUserSetting(
|
|
PhabricatorWeekStartDaySetting::SETTINGKEY);
|
|
|
|
$end_of_week = ($start_of_week + 6) % 7;
|
|
|
|
$first_of_month = $start_day->format('w');
|
|
$last_of_month = id(clone $next)->modify('-1 day')->format('w');
|
|
|
|
if (!$min_range || ($min_range < $display_start)) {
|
|
$min_range = $display_start;
|
|
|
|
if ($display == 'month' &&
|
|
$first_of_month !== $start_of_week) {
|
|
$interim_day_num = ($first_of_month + 7 - $start_of_week) % 7;
|
|
$min_range = id(clone $start_day)
|
|
->modify('-'.$interim_day_num.' days')
|
|
->format('U');
|
|
}
|
|
}
|
|
if (!$max_range || ($max_range > $display_end)) {
|
|
$max_range = $display_end;
|
|
|
|
if ($display == 'month' &&
|
|
$last_of_month !== $end_of_week) {
|
|
$interim_day_num = ($end_of_week + 7 - $last_of_month) % 7;
|
|
$max_range = id(clone $next)
|
|
->modify('+'.$interim_day_num.' days')
|
|
->format('U');
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($upcoming) {
|
|
$now = PhabricatorTime::getNow();
|
|
if ($min_range) {
|
|
$min_range = max($now, $min_range);
|
|
} else {
|
|
$min_range = $now;
|
|
}
|
|
}
|
|
|
|
return array($min_range, $max_range);
|
|
}
|
|
|
|
protected function getURI($path) {
|
|
return '/calendar/'.$path;
|
|
}
|
|
|
|
protected function getBuiltinQueryNames() {
|
|
$names = array(
|
|
'month' => pht('Month View'),
|
|
'day' => pht('Day View'),
|
|
'upcoming' => pht('Upcoming Events'),
|
|
'all' => pht('All Events'),
|
|
);
|
|
|
|
return $names;
|
|
}
|
|
|
|
public function setCalendarYearAndMonthAndDay($year, $month, $day = null) {
|
|
$this->calendarYear = $year;
|
|
$this->calendarMonth = $month;
|
|
$this->calendarDay = $day;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function buildSavedQueryFromBuiltin($query_key) {
|
|
$query = $this->newSavedQuery();
|
|
$query->setQueryKey($query_key);
|
|
|
|
switch ($query_key) {
|
|
case 'month':
|
|
return $query->setParameter('display', 'month');
|
|
case 'day':
|
|
return $query->setParameter('display', 'day');
|
|
case 'upcoming':
|
|
return $query
|
|
->setParameter('display', 'list')
|
|
->setParameter('upcoming', array(
|
|
0 => 'upcoming',
|
|
));
|
|
case 'all':
|
|
return $query;
|
|
}
|
|
|
|
return parent::buildSavedQueryFromBuiltin($query_key);
|
|
}
|
|
|
|
protected function renderResultList(
|
|
array $events,
|
|
PhabricatorSavedQuery $query,
|
|
array $handles) {
|
|
|
|
if ($this->isMonthView($query)) {
|
|
return $this->buildCalendarView($events, $query, $handles);
|
|
} else if ($this->isDayView($query)) {
|
|
return $this->buildCalendarDayView($events, $query, $handles);
|
|
}
|
|
|
|
assert_instances_of($events, 'PhabricatorCalendarEvent');
|
|
$viewer = $this->requireViewer();
|
|
$list = new PHUIObjectItemListView();
|
|
|
|
foreach ($events as $event) {
|
|
if ($event->getIsGhostEvent()) {
|
|
$monogram = $event->getParentEvent()->getMonogram();
|
|
$index = $event->getSequenceIndex();
|
|
$monogram = "{$monogram}/{$index}";
|
|
} else {
|
|
$monogram = $event->getMonogram();
|
|
}
|
|
|
|
$item = id(new PHUIObjectItemView())
|
|
->setUser($viewer)
|
|
->setObject($event)
|
|
->setObjectName($monogram)
|
|
->setHeader($event->getName())
|
|
->setHref($event->getURI());
|
|
|
|
$item->addAttribute($event->renderEventDate($viewer, false));
|
|
|
|
if ($event->isCancelledEvent()) {
|
|
$status_icon = 'fa-times red';
|
|
$status_label = pht('Cancelled');
|
|
$item->setDisabled(true);
|
|
} else if ($viewer->isLoggedIn()) {
|
|
$status = $event->getUserInviteStatus($viewer->getPHID());
|
|
switch ($status) {
|
|
case PhabricatorCalendarEventInvitee::STATUS_ATTENDING:
|
|
$status_icon = 'fa-check-circle green';
|
|
$status_label = pht('Attending');
|
|
break;
|
|
case PhabricatorCalendarEventInvitee::STATUS_INVITED:
|
|
$status_icon = 'fa-user-plus green';
|
|
$status_label = pht('Invited');
|
|
break;
|
|
case PhabricatorCalendarEventInvitee::STATUS_DECLINED:
|
|
$status_icon = 'fa-times grey';
|
|
$status_label = pht('Declined');
|
|
break;
|
|
default:
|
|
$status_icon = $event->getIcon().' grey';
|
|
$status_label = null;
|
|
break;
|
|
}
|
|
}
|
|
|
|
$item->setStatusIcon($status_icon, $status_label);
|
|
|
|
$host = pht(
|
|
'Hosted by %s',
|
|
$viewer->renderHandle($event->getHostPHID()));
|
|
$item->addByline($host);
|
|
|
|
$list->addItem($item);
|
|
}
|
|
|
|
$result = new PhabricatorApplicationSearchResultView();
|
|
$result->setObjectList($list);
|
|
$result->setNoDataString(pht('No events found.'));
|
|
|
|
return $result;
|
|
}
|
|
|
|
private function buildCalendarView(
|
|
array $statuses,
|
|
PhabricatorSavedQuery $query,
|
|
array $handles) {
|
|
|
|
$viewer = $this->requireViewer();
|
|
$now = time();
|
|
|
|
list($start_year, $start_month) =
|
|
$this->getDisplayYearAndMonthAndDay(
|
|
$this->getQueryDateFrom($query)->getEpoch(),
|
|
$this->getQueryDateTo($query)->getEpoch(),
|
|
$query->getParameter('display'));
|
|
|
|
$now_year = phabricator_format_local_time($now, $viewer, 'Y');
|
|
$now_month = phabricator_format_local_time($now, $viewer, 'm');
|
|
$now_day = phabricator_format_local_time($now, $viewer, 'j');
|
|
|
|
if ($start_month == $now_month && $start_year == $now_year) {
|
|
$month_view = new PHUICalendarMonthView(
|
|
$this->getQueryDateFrom($query),
|
|
$this->getQueryDateTo($query),
|
|
$start_month,
|
|
$start_year,
|
|
$now_day);
|
|
} else {
|
|
$month_view = new PHUICalendarMonthView(
|
|
$this->getQueryDateFrom($query),
|
|
$this->getQueryDateTo($query),
|
|
$start_month,
|
|
$start_year);
|
|
}
|
|
|
|
$month_view->setUser($viewer);
|
|
|
|
$phids = mpull($statuses, 'getHostPHID');
|
|
$handles = $viewer->loadHandles($phids);
|
|
|
|
foreach ($statuses as $status) {
|
|
$viewer_is_invited = $status->getIsUserInvited($viewer->getPHID());
|
|
|
|
$event = new AphrontCalendarEventView();
|
|
$event->setEpochRange(
|
|
$status->getViewerDateFrom(),
|
|
$status->getViewerDateTo());
|
|
$event->setIsAllDay($status->getIsAllDay());
|
|
$event->setIcon($status->getIcon());
|
|
|
|
$name_text = $handles[$status->getHostPHID()]->getName();
|
|
$status_text = $status->getName();
|
|
$event->setHostPHID($status->getHostPHID());
|
|
$event->setDescription(pht('%s (%s)', $name_text, $status_text));
|
|
$event->setName($status_text);
|
|
$event->setURI($status->getURI());
|
|
$event->setViewerIsInvited($viewer_is_invited);
|
|
$month_view->addEvent($event);
|
|
}
|
|
|
|
$month_view->setBrowseURI(
|
|
$this->getURI('query/'.$query->getQueryKey().'/'));
|
|
|
|
// TODO redesign-2015 : Move buttons out of PHUICalendarView?
|
|
$result = new PhabricatorApplicationSearchResultView();
|
|
$result->setContent($month_view);
|
|
|
|
return $result;
|
|
}
|
|
|
|
private function buildCalendarDayView(
|
|
array $statuses,
|
|
PhabricatorSavedQuery $query,
|
|
array $handles) {
|
|
|
|
$viewer = $this->requireViewer();
|
|
|
|
list($start_year, $start_month, $start_day) =
|
|
$this->getDisplayYearAndMonthAndDay(
|
|
$this->getQueryDateFrom($query)->getEpoch(),
|
|
$this->getQueryDateTo($query)->getEpoch(),
|
|
$query->getParameter('display'));
|
|
|
|
$day_view = id(new PHUICalendarDayView(
|
|
$this->getQueryDateFrom($query)->getEpoch(),
|
|
$this->getQueryDateTo($query)->getEpoch(),
|
|
$start_year,
|
|
$start_month,
|
|
$start_day))
|
|
->setQuery($query->getQueryKey());
|
|
|
|
$day_view->setUser($viewer);
|
|
|
|
$phids = mpull($statuses, 'getHostPHID');
|
|
|
|
foreach ($statuses as $status) {
|
|
if ($status->getIsCancelled()) {
|
|
continue;
|
|
}
|
|
|
|
$viewer_is_invited = $status->getIsUserInvited($viewer->getPHID());
|
|
|
|
$can_edit = PhabricatorPolicyFilter::hasCapability(
|
|
$viewer,
|
|
$status,
|
|
PhabricatorPolicyCapability::CAN_EDIT);
|
|
|
|
$event = new AphrontCalendarEventView();
|
|
$event->setCanEdit($can_edit);
|
|
$event->setEventID($status->getID());
|
|
$event->setEpochRange(
|
|
$status->getViewerDateFrom(),
|
|
$status->getViewerDateTo());
|
|
$event->setIsAllDay($status->getIsAllDay());
|
|
$event->setIcon($status->getIcon());
|
|
$event->setViewerIsInvited($viewer_is_invited);
|
|
|
|
$event->setName($status->getName());
|
|
$event->setURI($status->getURI());
|
|
$day_view->addEvent($event);
|
|
}
|
|
|
|
$day_view->setBrowseURI(
|
|
$this->getURI('query/'.$query->getQueryKey().'/'));
|
|
|
|
$result = new PhabricatorApplicationSearchResultView();
|
|
$result->setContent($day_view);
|
|
|
|
return $result;
|
|
}
|
|
|
|
private function getDisplayYearAndMonthAndDay(
|
|
$range_start,
|
|
$range_end,
|
|
$display) {
|
|
|
|
$viewer = $this->requireViewer();
|
|
$epoch = null;
|
|
|
|
if ($this->calendarYear && $this->calendarMonth) {
|
|
$start_year = $this->calendarYear;
|
|
$start_month = $this->calendarMonth;
|
|
$start_day = $this->calendarDay ? $this->calendarDay : 1;
|
|
} else {
|
|
if ($range_start) {
|
|
$epoch = $range_start;
|
|
} else if ($range_end) {
|
|
$epoch = $range_end;
|
|
} else {
|
|
$epoch = time();
|
|
}
|
|
if ($display == 'month') {
|
|
$day = 1;
|
|
} else {
|
|
$day = phabricator_format_local_time($epoch, $viewer, 'd');
|
|
}
|
|
$start_year = phabricator_format_local_time($epoch, $viewer, 'Y');
|
|
$start_month = phabricator_format_local_time($epoch, $viewer, 'm');
|
|
$start_day = $day;
|
|
}
|
|
return array($start_year, $start_month, $start_day);
|
|
}
|
|
|
|
public function getPageSize(PhabricatorSavedQuery $saved) {
|
|
if ($this->isMonthView($saved) || $this->isDayView($saved)) {
|
|
return $saved->getParameter('limit', 1000);
|
|
} else {
|
|
return $saved->getParameter('limit', 100);
|
|
}
|
|
}
|
|
|
|
private function getQueryDateFrom(PhabricatorSavedQuery $saved) {
|
|
return $this->getQueryDate($saved, 'rangeStart');
|
|
}
|
|
|
|
private function getQueryDateTo(PhabricatorSavedQuery $saved) {
|
|
return $this->getQueryDate($saved, 'rangeEnd');
|
|
}
|
|
|
|
private function getQueryDate(PhabricatorSavedQuery $saved, $key) {
|
|
$viewer = $this->requireViewer();
|
|
|
|
$wild = $saved->getParameter($key);
|
|
return $this->getSafeDate($wild);
|
|
}
|
|
|
|
private function getSafeDate($value) {
|
|
$viewer = $this->requireViewer();
|
|
if ($value) {
|
|
// ideally this would be consistent and always pass in the same type
|
|
if ($value instanceof AphrontFormDateControlValue) {
|
|
return $value;
|
|
} else {
|
|
$value = AphrontFormDateControlValue::newFromWild($viewer, $value);
|
|
}
|
|
} else {
|
|
$value = AphrontFormDateControlValue::newFromEpoch(
|
|
$viewer,
|
|
PhabricatorTime::getTodayMidnightDateTime($viewer)->format('U'));
|
|
$value->setEnabled(false);
|
|
}
|
|
|
|
$value->setOptional(true);
|
|
|
|
return $value;
|
|
}
|
|
|
|
private function isMonthView(PhabricatorSavedQuery $query) {
|
|
if ($this->isDayView($query)) {
|
|
return false;
|
|
}
|
|
if ($query->getParameter('display') == 'month') {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
private function isDayView(PhabricatorSavedQuery $query) {
|
|
if ($query->getParameter('display') == 'day') {
|
|
return true;
|
|
}
|
|
if ($this->calendarDay) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
}
|