2014-02-06 19:10:18 +01:00
|
|
|
<?php
|
|
|
|
|
|
|
|
final class PhabricatorCalendarEventSearchEngine
|
|
|
|
extends PhabricatorApplicationSearchEngine {
|
|
|
|
|
2015-05-03 01:17:05 +02:00
|
|
|
private $calendarYear;
|
|
|
|
private $calendarMonth;
|
2015-05-04 01:57:18 +02:00
|
|
|
private $calendarDay;
|
2015-05-03 01:17:05 +02:00
|
|
|
|
2014-06-12 22:22:20 +02:00
|
|
|
public function getResultTypeDescription() {
|
|
|
|
return pht('Calendar Events');
|
|
|
|
}
|
|
|
|
|
2015-02-05 00:47:48 +01:00
|
|
|
public function getApplicationClassName() {
|
2014-07-23 02:03:09 +02:00
|
|
|
return 'PhabricatorCalendarApplication';
|
2014-05-08 18:18:02 +02:00
|
|
|
}
|
|
|
|
|
2015-06-22 22:27:37 +02:00
|
|
|
public function newQuery() {
|
Improve Calendar event behavior for group invites
Summary:
Ref T11816. Projects can be invited to an event, but the UI is currently fairly agnostic about them.
Instead, introduce the idea of "RSVPs", which are basically invites for you as an individual or for any group you're a part of. When we go to check if you're invited, we check for you individually first, then check for any groups you belong to if you haven't already accepted/declined.
On the calendar detail page:
- Show the quick "Join" / "Decline" buttons if any project you're a member of is invited.
- If you're invited, highlight any projects which you're a member of to make that more clear.
On other calendar views:
- If you're invited as part of a project, show the "multiple users" icon.
- If it's just you, continue showing the "add one user" icon.
Test Plan: Viewed month view, day view, detail view. Invited groups and individuals. Invited "Dog Project", accepted invite as user "Dog".
Reviewers: chad
Reviewed By: chad
Maniphest Tasks: T11816
Differential Revision: https://secure.phabricator.com/D16868
2016-11-15 20:01:05 +01:00
|
|
|
$viewer = $this->requireViewer();
|
|
|
|
|
|
|
|
return id(new PhabricatorCalendarEventQuery())
|
|
|
|
->needRSVPs(array($viewer->getPHID()));
|
2015-06-22 22:27:37 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
protected function shouldShowOrderField() {
|
|
|
|
return false;
|
|
|
|
}
|
2014-02-06 19:10:18 +01:00
|
|
|
|
2015-06-22 22:27:37 +02:00
|
|
|
protected function buildCustomSearchFields() {
|
|
|
|
return array(
|
|
|
|
id(new PhabricatorSearchDatasourceField())
|
2016-07-14 21:54:36 +02:00
|
|
|
->setLabel(pht('Hosts'))
|
|
|
|
->setKey('hostPHIDs')
|
|
|
|
->setAliases(array('host', 'hostPHID', 'hosts'))
|
2015-06-22 22:27:37 +02:00
|
|
|
->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'),
|
2016-10-13 17:51:27 +02:00
|
|
|
id(new PhabricatorPHIDsSearchField())
|
|
|
|
->setLabel(pht('Import Sources'))
|
|
|
|
->setKey('importSourcePHIDs')
|
|
|
|
->setAliases(array('importSourcePHID')),
|
2015-06-22 22:27:37 +02:00
|
|
|
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();
|
|
|
|
|
2016-07-14 21:54:36 +02:00
|
|
|
if ($map['hostPHIDs']) {
|
|
|
|
$query->withHostPHIDs($map['hostPHIDs']);
|
2015-06-22 22:27:37 +02:00
|
|
|
}
|
2014-02-06 19:10:18 +01:00
|
|
|
|
2015-06-22 22:27:37 +02:00
|
|
|
if ($map['invitedPHIDs']) {
|
|
|
|
$query->withInvitedPHIDs($map['invitedPHIDs']);
|
|
|
|
}
|
2014-02-06 19:10:18 +01:00
|
|
|
|
2015-06-22 22:27:37 +02:00
|
|
|
$range_start = $map['rangeStart'];
|
|
|
|
$range_end = $map['rangeEnd'];
|
|
|
|
$display = $map['display'];
|
2014-02-06 19:10:18 +01:00
|
|
|
|
2015-06-22 22:27:37 +02:00
|
|
|
if ($map['upcoming'] && $map['upcoming'][0] == 'upcoming') {
|
|
|
|
$upcoming = true;
|
|
|
|
} else {
|
|
|
|
$upcoming = false;
|
|
|
|
}
|
2014-02-06 19:10:18 +01:00
|
|
|
|
2015-06-22 22:27:37 +02:00
|
|
|
list($range_start, $range_end) = $this->getQueryDateRange(
|
|
|
|
$range_start,
|
|
|
|
$range_end,
|
|
|
|
$display,
|
|
|
|
$upcoming);
|
2014-02-06 19:10:18 +01:00
|
|
|
|
2015-06-22 22:27:37 +02:00
|
|
|
$query->withDateRange($range_start, $range_end);
|
Canceling calendar events should deactivate the event
Summary: Closes T7943, Canceling calendar event should deactivate the event instead of destroying data.
Test Plan: Create an event, cancel it, see changed status icon, query for active events, event should not appear, query for deactivated events, event should appear in results.
Reviewers: #blessed_reviewers, epriestley
Reviewed By: #blessed_reviewers, epriestley
Subscribers: Korvin, epriestley
Maniphest Tasks: T7943
Differential Revision: https://secure.phabricator.com/D12604
2015-04-29 17:39:39 +02:00
|
|
|
|
2015-06-22 22:27:37 +02:00
|
|
|
switch ($map['isCancelled']) {
|
|
|
|
case 'active':
|
|
|
|
$query->withIsCancelled(false);
|
|
|
|
break;
|
|
|
|
case 'cancelled':
|
|
|
|
$query->withIsCancelled(true);
|
|
|
|
break;
|
|
|
|
}
|
2015-05-03 00:27:33 +02:00
|
|
|
|
2016-10-13 17:51:27 +02:00
|
|
|
if ($map['importSourcePHIDs']) {
|
|
|
|
$query->withImportSourcePHIDs($map['importSourcePHIDs']);
|
|
|
|
}
|
|
|
|
|
Generate "stub" events earlier, so more infrastructure works with Calendar
Summary:
Ref T9275. When you create a recurring event which recurs forever, we want to avoid writing an infinite number of rows to the database.
Currently, we write a row to the database right before you edit the event. Until then, we refer to it as `E123/999` or whatever ("instance 999 of event 123").
This creates a big mess with trying to make recurring events work with EditEngine, Subscriptions, Projects, Flags, Tokens, etc -- all of this stuff assumes that whatever you're working with has a PHID.
I poked at letting this stuff work without a PHID a little bit, but that looked like a gigantic mess.
Instead, generate an event "stub" a little sooner (when you look at the event detail page). This is basically just an ID/PHID to refer to the instance.
Then, when you edit the stub, "materialize" it into a real event.
This still has some issues, but I think it's more promising than the other approach was.
Also:
- Removes dead user profile calendar controller.
- Replaces comments with EditEngine comments.
Test Plan:
- Commented on a recurring event.
- Awarded tokens to a recurring event.
Reviewers: chad
Reviewed By: chad
Maniphest Tasks: T9275
Differential Revision: https://secure.phabricator.com/D16248
2016-07-07 16:19:58 +02:00
|
|
|
// Generate ghosts (and ignore stub events) if we aren't querying for
|
2016-10-06 21:31:57 +02:00
|
|
|
// specific events or exporting.
|
|
|
|
if (!empty($map['export'])) {
|
|
|
|
// This is a specific mode enabled by event exports.
|
|
|
|
$query
|
|
|
|
->withIsStub(false);
|
|
|
|
} else if (!$map['ids'] && !$map['phids']) {
|
Generate "stub" events earlier, so more infrastructure works with Calendar
Summary:
Ref T9275. When you create a recurring event which recurs forever, we want to avoid writing an infinite number of rows to the database.
Currently, we write a row to the database right before you edit the event. Until then, we refer to it as `E123/999` or whatever ("instance 999 of event 123").
This creates a big mess with trying to make recurring events work with EditEngine, Subscriptions, Projects, Flags, Tokens, etc -- all of this stuff assumes that whatever you're working with has a PHID.
I poked at letting this stuff work without a PHID a little bit, but that looked like a gigantic mess.
Instead, generate an event "stub" a little sooner (when you look at the event detail page). This is basically just an ID/PHID to refer to the instance.
Then, when you edit the stub, "materialize" it into a real event.
This still has some issues, but I think it's more promising than the other approach was.
Also:
- Removes dead user profile calendar controller.
- Replaces comments with EditEngine comments.
Test Plan:
- Commented on a recurring event.
- Awarded tokens to a recurring event.
Reviewers: chad
Reviewed By: chad
Maniphest Tasks: T9275
Differential Revision: https://secure.phabricator.com/D16248
2016-07-07 16:19:58 +02:00
|
|
|
$query
|
|
|
|
->withIsStub(false)
|
|
|
|
->setGenerateGhosts(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $query;
|
2014-02-06 19:10:18 +01:00
|
|
|
}
|
|
|
|
|
2015-06-22 22:27:37 +02:00
|
|
|
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);
|
|
|
|
|
2015-05-03 01:17:05 +02:00
|
|
|
$viewer = $this->requireViewer();
|
2015-05-06 01:04:00 +02:00
|
|
|
$timezone = new DateTimeZone($viewer->getTimezoneIdentifier());
|
2015-06-22 22:27:37 +02:00
|
|
|
$min_range = null;
|
|
|
|
$max_range = null;
|
2014-02-06 19:10:18 +01:00
|
|
|
|
2015-06-22 22:27:37 +02:00
|
|
|
$min_range = $start_date_value->getEpoch();
|
|
|
|
$max_range = $end_date_value->getEpoch();
|
2015-06-16 22:38:11 +02:00
|
|
|
|
2015-06-22 22:27:37 +02:00
|
|
|
if ($display == 'month' || $display == 'day') {
|
2015-05-06 01:04:00 +02:00
|
|
|
list($start_year, $start_month, $start_day) =
|
2015-06-22 22:27:37 +02:00
|
|
|
$this->getDisplayYearAndMonthAndDay($min_range, $max_range, $display);
|
2015-05-06 01:04:00 +02:00
|
|
|
|
|
|
|
$start_day = new DateTime(
|
|
|
|
"{$start_year}-{$start_month}-{$start_day}",
|
|
|
|
$timezone);
|
|
|
|
$next = clone $start_day;
|
|
|
|
|
2015-06-22 22:27:37 +02:00
|
|
|
if ($display == 'month') {
|
2015-05-06 01:04:00 +02:00
|
|
|
$next->modify('+1 month');
|
2015-06-22 22:27:37 +02:00
|
|
|
} else if ($display == 'day') {
|
|
|
|
$next->modify('+7 day');
|
2015-05-06 01:04:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
$display_start = $start_day->format('U');
|
|
|
|
$display_end = $next->format('U');
|
|
|
|
|
Convert some loadPreferences() to getUserSetting()
Summary:
Ref T4103. This doesn't get everything, but takes care of most of the easy stuff.
The tricky-ish bit here is that I need to move timezones, pronouns and translations to proper settings. I expect to pursue that next.
Test Plan:
- Grepped for `loadPreferences` to identify callsites.
- Changed start-of-week setting, loaded Calendar, saw correct start.
- Visited welcome page, read "Adjust Settings" point.
- Loaded Conpherence -- I changed behavior here slightly (switching threads drops the title glyph) but it wasn't consistent to start with and this seems like a good thing to push to the next version of Conpherence.
- Enabled Filetree, toggled in Differential.
- Disabled Filetree, no longer visible in Differential.
- Changed "Unified Diffs" preference to "Small Screens" vs "Always".
- Toggled filetree in Diffusion.
- Edited a task, saw sensible projects in policy dropdown.
- Viewed user profile, uncollapsed/collapsed side nav, reloaded page, sticky'd.
- Toggled "monospaced textareas", used a comment box, got appropriate fonts.
- Toggled durable column.
- Disabled title glyphs.
- Changed monospaced font to 18px/36px impact.
Reviewers: chad
Reviewed By: chad
Maniphest Tasks: T4103
Differential Revision: https://secure.phabricator.com/D16004
2016-06-01 22:59:08 +02:00
|
|
|
$start_of_week = $viewer->getUserSetting(
|
|
|
|
PhabricatorWeekStartDaySetting::SETTINGKEY);
|
2015-05-17 05:13:25 +02:00
|
|
|
|
|
|
|
$end_of_week = ($start_of_week + 6) % 7;
|
2015-05-14 04:02:33 +02:00
|
|
|
|
|
|
|
$first_of_month = $start_day->format('w');
|
|
|
|
$last_of_month = id(clone $next)->modify('-1 day')->format('w');
|
|
|
|
|
2015-05-06 01:04:00 +02:00
|
|
|
if (!$min_range || ($min_range < $display_start)) {
|
|
|
|
$min_range = $display_start;
|
2015-05-14 04:02:33 +02:00
|
|
|
|
2015-06-22 22:27:37 +02:00
|
|
|
if ($display == 'month' &&
|
2015-05-17 05:13:25 +02:00
|
|
|
$first_of_month !== $start_of_week) {
|
|
|
|
$interim_day_num = ($first_of_month + 7 - $start_of_week) % 7;
|
2015-05-14 04:02:33 +02:00
|
|
|
$min_range = id(clone $start_day)
|
2015-05-17 05:13:25 +02:00
|
|
|
->modify('-'.$interim_day_num.' days')
|
2015-05-14 04:02:33 +02:00
|
|
|
->format('U');
|
|
|
|
}
|
2015-05-03 01:17:05 +02:00
|
|
|
}
|
2015-05-06 01:04:00 +02:00
|
|
|
if (!$max_range || ($max_range > $display_end)) {
|
|
|
|
$max_range = $display_end;
|
2015-05-14 04:02:33 +02:00
|
|
|
|
2015-06-22 22:27:37 +02:00
|
|
|
if ($display == 'month' &&
|
2015-05-17 05:13:25 +02:00
|
|
|
$last_of_month !== $end_of_week) {
|
|
|
|
$interim_day_num = ($end_of_week + 7 - $last_of_month) % 7;
|
2015-05-14 04:02:33 +02:00
|
|
|
$max_range = id(clone $next)
|
2015-05-17 05:13:25 +02:00
|
|
|
->modify('+'.$interim_day_num.' days')
|
2015-05-14 04:02:33 +02:00
|
|
|
->format('U');
|
|
|
|
}
|
2015-05-03 01:17:05 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-06-22 22:27:37 +02:00
|
|
|
if ($upcoming) {
|
2016-07-13 18:29:02 +02:00
|
|
|
$now = PhabricatorTime::getNow();
|
2014-02-06 19:10:18 +01:00
|
|
|
if ($min_range) {
|
2016-07-13 18:29:02 +02:00
|
|
|
$min_range = max($now, $min_range);
|
2014-02-06 19:10:18 +01:00
|
|
|
} else {
|
2016-07-13 18:29:02 +02:00
|
|
|
$min_range = $now;
|
2014-02-06 19:10:18 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-06-22 22:27:37 +02:00
|
|
|
return array($min_range, $max_range);
|
2014-02-06 19:10:18 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
protected function getURI($path) {
|
2015-05-03 00:27:33 +02:00
|
|
|
return '/calendar/'.$path;
|
2014-02-06 19:10:18 +01:00
|
|
|
}
|
|
|
|
|
2015-01-06 21:34:51 +01:00
|
|
|
protected function getBuiltinQueryNames() {
|
2014-02-06 19:10:18 +01:00
|
|
|
$names = array(
|
2015-05-03 00:27:33 +02:00
|
|
|
'month' => pht('Month View'),
|
2015-05-08 06:18:48 +02:00
|
|
|
'day' => pht('Day View'),
|
2014-02-06 19:10:18 +01:00
|
|
|
'upcoming' => pht('Upcoming Events'),
|
2015-05-03 00:27:33 +02:00
|
|
|
'all' => pht('All Events'),
|
2014-02-06 19:10:18 +01:00
|
|
|
);
|
|
|
|
|
|
|
|
return $names;
|
|
|
|
}
|
|
|
|
|
2015-05-05 21:51:32 +02:00
|
|
|
public function setCalendarYearAndMonthAndDay($year, $month, $day = null) {
|
2015-05-03 01:17:05 +02:00
|
|
|
$this->calendarYear = $year;
|
|
|
|
$this->calendarMonth = $month;
|
2015-05-05 21:51:32 +02:00
|
|
|
$this->calendarDay = $day;
|
2015-05-03 01:17:05 +02:00
|
|
|
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2014-02-06 19:10:18 +01:00
|
|
|
public function buildSavedQueryFromBuiltin($query_key) {
|
|
|
|
$query = $this->newSavedQuery();
|
|
|
|
$query->setQueryKey($query_key);
|
|
|
|
|
|
|
|
switch ($query_key) {
|
2015-05-03 00:27:33 +02:00
|
|
|
case 'month':
|
|
|
|
return $query->setParameter('display', 'month');
|
2015-05-08 06:18:48 +02:00
|
|
|
case 'day':
|
|
|
|
return $query->setParameter('display', 'day');
|
2014-02-06 19:10:18 +01:00
|
|
|
case 'upcoming':
|
2015-06-25 23:40:06 +02:00
|
|
|
return $query
|
|
|
|
->setParameter('display', 'list')
|
|
|
|
->setParameter('upcoming', array(
|
|
|
|
0 => 'upcoming',
|
|
|
|
));
|
2014-02-06 19:10:18 +01:00
|
|
|
case 'all':
|
|
|
|
return $query;
|
|
|
|
}
|
|
|
|
|
|
|
|
return parent::buildSavedQueryFromBuiltin($query_key);
|
|
|
|
}
|
|
|
|
|
2014-05-08 18:18:02 +02:00
|
|
|
protected function renderResultList(
|
|
|
|
array $events,
|
|
|
|
PhabricatorSavedQuery $query,
|
|
|
|
array $handles) {
|
2015-05-03 00:27:33 +02:00
|
|
|
|
2015-05-12 03:15:27 +02:00
|
|
|
if ($this->isMonthView($query)) {
|
2016-10-06 01:22:54 +02:00
|
|
|
$result = $this->buildCalendarMonthView($events, $query);
|
2015-05-12 03:15:27 +02:00
|
|
|
} else if ($this->isDayView($query)) {
|
2016-10-06 01:22:54 +02:00
|
|
|
$result = $this->buildCalendarDayView($events, $query);
|
|
|
|
} else {
|
|
|
|
$result = $this->buildCalendarListView($events, $query);
|
2015-05-03 00:27:33 +02:00
|
|
|
}
|
|
|
|
|
2016-10-06 01:22:54 +02:00
|
|
|
return $result;
|
|
|
|
}
|
|
|
|
|
|
|
|
private function buildCalendarListView(
|
|
|
|
array $events,
|
|
|
|
PhabricatorSavedQuery $query) {
|
|
|
|
|
2014-05-08 18:18:02 +02:00
|
|
|
assert_instances_of($events, 'PhabricatorCalendarEvent');
|
|
|
|
$viewer = $this->requireViewer();
|
|
|
|
$list = new PHUIObjectItemListView();
|
2015-06-22 22:27:37 +02:00
|
|
|
|
2014-05-08 18:18:02 +02:00
|
|
|
foreach ($events as $event) {
|
2015-08-03 16:01:42 +02:00
|
|
|
if ($event->getIsGhostEvent()) {
|
2016-07-14 13:44:13 +02:00
|
|
|
$monogram = $event->getParentEvent()->getMonogram();
|
|
|
|
$index = $event->getSequenceIndex();
|
|
|
|
$monogram = "{$monogram}/{$index}";
|
2015-08-03 16:01:42 +02:00
|
|
|
} else {
|
2016-07-14 13:44:13 +02:00
|
|
|
$monogram = $event->getMonogram();
|
2015-08-03 16:01:42 +02:00
|
|
|
}
|
|
|
|
|
2014-05-08 18:18:02 +02:00
|
|
|
$item = id(new PHUIObjectItemView())
|
Events should offer Spaces as the view policy options
Summary: Ref T8687, Events should offer Spaces as the view policy options
Test Plan: Create event, choose default Space, save, edit, choose different Space, save, new policy should be reflected on Event.
Reviewers: epriestley, #blessed_reviewers
Reviewed By: epriestley, #blessed_reviewers
Subscribers: epriestley, Korvin
Maniphest Tasks: T8687
Differential Revision: https://secure.phabricator.com/D13459
2015-06-27 19:26:24 +02:00
|
|
|
->setUser($viewer)
|
|
|
|
->setObject($event)
|
2016-07-14 13:44:13 +02:00
|
|
|
->setObjectName($monogram)
|
|
|
|
->setHeader($event->getName())
|
2016-07-14 14:26:44 +02:00
|
|
|
->setHref($event->getURI());
|
|
|
|
|
|
|
|
$item->addAttribute($event->renderEventDate($viewer, false));
|
|
|
|
|
2016-10-31 21:42:10 +01:00
|
|
|
if ($event->getIsCancelled()) {
|
2016-07-14 14:26:44 +02:00
|
|
|
$item->setDisabled(true);
|
|
|
|
}
|
2016-02-07 19:00:14 +01:00
|
|
|
|
2016-07-14 18:47:46 +02:00
|
|
|
$status_icon = $event->getDisplayIcon($viewer);
|
|
|
|
$status_color = $event->getDisplayIconColor($viewer);
|
|
|
|
$status_label = $event->getDisplayIconLabel($viewer);
|
|
|
|
|
|
|
|
$item->setStatusIcon("{$status_icon} {$status_color}", $status_label);
|
2016-02-07 19:00:14 +01:00
|
|
|
|
2016-07-14 14:26:44 +02:00
|
|
|
$host = pht(
|
|
|
|
'Hosted by %s',
|
|
|
|
$viewer->renderHandle($event->getHostPHID()));
|
|
|
|
$item->addByline($host);
|
2016-02-07 19:00:14 +01:00
|
|
|
|
2014-05-08 18:18:02 +02:00
|
|
|
$list->addItem($item);
|
|
|
|
}
|
|
|
|
|
2016-10-18 23:24:17 +02:00
|
|
|
return $this->newResultView()
|
|
|
|
->setObjectList($list)
|
|
|
|
->setNoDataString(pht('No events found.'));
|
2014-05-08 18:18:02 +02:00
|
|
|
}
|
|
|
|
|
2016-07-27 15:58:28 +02:00
|
|
|
private function buildCalendarMonthView(
|
2016-07-14 18:47:46 +02:00
|
|
|
array $events,
|
|
|
|
PhabricatorSavedQuery $query) {
|
|
|
|
assert_instances_of($events, 'PhabricatorCalendarEvent');
|
2015-06-22 22:27:37 +02:00
|
|
|
|
2015-05-03 00:27:33 +02:00
|
|
|
$viewer = $this->requireViewer();
|
2016-07-14 18:47:46 +02:00
|
|
|
$now = PhabricatorTime::getNow();
|
2015-05-03 00:27:33 +02:00
|
|
|
|
2015-05-05 21:51:32 +02:00
|
|
|
list($start_year, $start_month) =
|
2015-06-22 22:27:37 +02:00
|
|
|
$this->getDisplayYearAndMonthAndDay(
|
|
|
|
$this->getQueryDateFrom($query)->getEpoch(),
|
|
|
|
$this->getQueryDateTo($query)->getEpoch(),
|
|
|
|
$query->getParameter('display'));
|
2015-05-03 00:27:33 +02:00
|
|
|
|
2016-07-14 18:47:46 +02:00
|
|
|
$now_year = phabricator_format_local_time($now, $viewer, 'Y');
|
2015-05-03 00:27:33 +02:00
|
|
|
$now_month = phabricator_format_local_time($now, $viewer, 'm');
|
2016-07-14 18:47:46 +02:00
|
|
|
$now_day = phabricator_format_local_time($now, $viewer, 'j');
|
2015-05-03 00:27:33 +02:00
|
|
|
|
2015-05-03 01:17:05 +02:00
|
|
|
if ($start_month == $now_month && $start_year == $now_year) {
|
|
|
|
$month_view = new PHUICalendarMonthView(
|
2015-06-22 22:27:37 +02:00
|
|
|
$this->getQueryDateFrom($query),
|
|
|
|
$this->getQueryDateTo($query),
|
2015-05-03 01:17:05 +02:00
|
|
|
$start_month,
|
|
|
|
$start_year,
|
|
|
|
$now_day);
|
2015-05-03 00:27:33 +02:00
|
|
|
} else {
|
2015-05-03 01:17:05 +02:00
|
|
|
$month_view = new PHUICalendarMonthView(
|
2015-06-22 22:27:37 +02:00
|
|
|
$this->getQueryDateFrom($query),
|
|
|
|
$this->getQueryDateTo($query),
|
2015-05-03 01:17:05 +02:00
|
|
|
$start_month,
|
|
|
|
$start_year);
|
2015-05-03 00:27:33 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
$month_view->setUser($viewer);
|
|
|
|
|
2016-07-14 18:47:46 +02:00
|
|
|
foreach ($events as $event) {
|
2016-10-04 18:47:38 +02:00
|
|
|
$epoch_min = $event->getStartDateTimeEpoch();
|
|
|
|
$epoch_max = $event->getEndDateTimeEpoch();
|
2016-07-14 18:47:46 +02:00
|
|
|
|
|
|
|
$event_view = id(new AphrontCalendarEventView())
|
|
|
|
->setHostPHID($event->getHostPHID())
|
|
|
|
->setEpochRange($epoch_min, $epoch_max)
|
2016-10-31 21:42:10 +01:00
|
|
|
->setIsCancelled($event->getIsCancelled())
|
2016-07-14 18:47:46 +02:00
|
|
|
->setName($event->getName())
|
|
|
|
->setURI($event->getURI())
|
|
|
|
->setIsAllDay($event->getIsAllDay())
|
|
|
|
->setIcon($event->getDisplayIcon($viewer))
|
2016-11-13 21:25:10 +01:00
|
|
|
->setViewerIsInvited($event->getIsUserInvited($viewer->getPHID()))
|
2016-07-14 18:47:46 +02:00
|
|
|
->setIconColor($event->getDisplayIconColor($viewer));
|
|
|
|
|
|
|
|
$month_view->addEvent($event_view);
|
2015-05-03 00:27:33 +02:00
|
|
|
}
|
|
|
|
|
2015-05-03 01:17:05 +02:00
|
|
|
$month_view->setBrowseURI(
|
|
|
|
$this->getURI('query/'.$query->getQueryKey().'/'));
|
|
|
|
|
2016-07-27 18:07:29 +02:00
|
|
|
$from = $this->getQueryDateFrom($query)->getDateTime();
|
|
|
|
|
|
|
|
$crumbs = array();
|
|
|
|
$crumbs[] = id(new PHUICrumbView())
|
|
|
|
->setName($from->format('F Y'));
|
|
|
|
|
|
|
|
$header = id(new PHUIHeaderView())
|
2016-08-01 21:06:35 +02:00
|
|
|
->setProfileHeader(true)
|
2016-07-27 18:07:29 +02:00
|
|
|
->setHeader($from->format('F Y'));
|
|
|
|
|
2016-10-18 23:24:17 +02:00
|
|
|
return $this->newResultView($month_view)
|
2016-07-27 18:07:29 +02:00
|
|
|
->setCrumbs($crumbs)
|
2016-10-18 23:24:17 +02:00
|
|
|
->setHeader($header);
|
2015-05-03 01:17:05 +02:00
|
|
|
}
|
|
|
|
|
2015-05-04 01:57:18 +02:00
|
|
|
private function buildCalendarDayView(
|
2016-07-15 23:02:24 +02:00
|
|
|
array $events,
|
|
|
|
PhabricatorSavedQuery $query) {
|
2015-06-22 22:27:37 +02:00
|
|
|
|
2015-05-04 01:57:18 +02:00
|
|
|
$viewer = $this->requireViewer();
|
2015-06-22 22:27:37 +02:00
|
|
|
|
2015-05-05 21:51:32 +02:00
|
|
|
list($start_year, $start_month, $start_day) =
|
2015-06-22 22:27:37 +02:00
|
|
|
$this->getDisplayYearAndMonthAndDay(
|
|
|
|
$this->getQueryDateFrom($query)->getEpoch(),
|
|
|
|
$this->getQueryDateTo($query)->getEpoch(),
|
|
|
|
$query->getParameter('display'));
|
2015-05-04 01:57:18 +02:00
|
|
|
|
2015-05-24 20:22:33 +02:00
|
|
|
$day_view = id(new PHUICalendarDayView(
|
2016-07-27 18:07:29 +02:00
|
|
|
$this->getQueryDateFrom($query),
|
|
|
|
$this->getQueryDateTo($query),
|
2015-05-04 01:57:18 +02:00
|
|
|
$start_year,
|
2015-05-05 21:51:32 +02:00
|
|
|
$start_month,
|
2015-05-24 20:22:33 +02:00
|
|
|
$start_day))
|
|
|
|
->setQuery($query->getQueryKey());
|
2015-05-04 01:57:18 +02:00
|
|
|
|
|
|
|
$day_view->setUser($viewer);
|
|
|
|
|
2016-07-15 23:02:24 +02:00
|
|
|
$phids = mpull($events, 'getHostPHID');
|
2015-05-15 04:20:44 +02:00
|
|
|
|
2016-07-15 23:02:24 +02:00
|
|
|
foreach ($events as $event) {
|
2015-05-24 20:22:33 +02:00
|
|
|
$can_edit = PhabricatorPolicyFilter::hasCapability(
|
|
|
|
$viewer,
|
2016-07-15 23:02:24 +02:00
|
|
|
$event,
|
2015-05-24 20:22:33 +02:00
|
|
|
PhabricatorPolicyCapability::CAN_EDIT);
|
|
|
|
|
2016-10-04 18:47:38 +02:00
|
|
|
$epoch_min = $event->getStartDateTimeEpoch();
|
|
|
|
$epoch_max = $event->getEndDateTimeEpoch();
|
2016-07-15 23:02:24 +02:00
|
|
|
|
|
|
|
$status_icon = $event->getDisplayIcon($viewer);
|
|
|
|
$status_color = $event->getDisplayIconColor($viewer);
|
|
|
|
|
|
|
|
$event_view = id(new AphrontCalendarEventView())
|
|
|
|
->setCanEdit($can_edit)
|
|
|
|
->setEventID($event->getID())
|
|
|
|
->setEpochRange($epoch_min, $epoch_max)
|
|
|
|
->setIsAllDay($event->getIsAllDay())
|
|
|
|
->setIcon($status_icon)
|
|
|
|
->setIconColor($status_color)
|
|
|
|
->setName($event->getName())
|
|
|
|
->setURI($event->getURI())
|
2016-10-31 21:42:10 +01:00
|
|
|
->setIsCancelled($event->getIsCancelled());
|
2016-07-15 23:02:24 +02:00
|
|
|
|
|
|
|
$day_view->addEvent($event_view);
|
2015-05-04 01:57:18 +02:00
|
|
|
}
|
|
|
|
|
2016-07-27 18:07:29 +02:00
|
|
|
$browse_uri = $this->getURI('query/'.$query->getQueryKey().'/');
|
|
|
|
$day_view->setBrowseURI($browse_uri);
|
|
|
|
|
|
|
|
$from = $this->getQueryDateFrom($query)->getDateTime();
|
|
|
|
$month_uri = $browse_uri.$from->format('Y/m/');
|
|
|
|
|
|
|
|
$crumbs = array(
|
|
|
|
id(new PHUICrumbView())
|
|
|
|
->setName($from->format('F Y'))
|
|
|
|
->setHref($month_uri),
|
|
|
|
id(new PHUICrumbView())
|
|
|
|
->setName($from->format('D jS')),
|
|
|
|
);
|
|
|
|
|
|
|
|
$header = id(new PHUIHeaderView())
|
2016-08-01 21:06:35 +02:00
|
|
|
->setProfileHeader(true)
|
2016-07-27 18:07:29 +02:00
|
|
|
->setHeader($from->format('D, F jS'));
|
2015-05-05 21:51:32 +02:00
|
|
|
|
2016-10-18 23:24:17 +02:00
|
|
|
return $this->newResultView($day_view)
|
2016-07-27 18:07:29 +02:00
|
|
|
->setCrumbs($crumbs)
|
2016-10-18 23:24:17 +02:00
|
|
|
->setHeader($header);
|
2015-05-04 01:57:18 +02:00
|
|
|
}
|
|
|
|
|
2015-05-05 21:51:32 +02:00
|
|
|
private function getDisplayYearAndMonthAndDay(
|
2015-06-22 22:27:37 +02:00
|
|
|
$range_start,
|
|
|
|
$range_end,
|
|
|
|
$display) {
|
|
|
|
|
2015-05-03 01:17:05 +02:00
|
|
|
$viewer = $this->requireViewer();
|
2015-06-22 22:27:37 +02:00
|
|
|
$epoch = null;
|
|
|
|
|
2015-05-03 01:17:05 +02:00
|
|
|
if ($this->calendarYear && $this->calendarMonth) {
|
|
|
|
$start_year = $this->calendarYear;
|
|
|
|
$start_month = $this->calendarMonth;
|
2015-05-05 21:51:32 +02:00
|
|
|
$start_day = $this->calendarDay ? $this->calendarDay : 1;
|
2015-05-04 01:57:18 +02:00
|
|
|
} else {
|
2015-06-22 22:27:37 +02:00
|
|
|
if ($range_start) {
|
|
|
|
$epoch = $range_start;
|
|
|
|
} else if ($range_end) {
|
|
|
|
$epoch = $range_end;
|
|
|
|
} else {
|
|
|
|
$epoch = time();
|
2015-05-04 01:57:18 +02:00
|
|
|
}
|
2015-06-22 22:27:37 +02:00
|
|
|
if ($display == 'month') {
|
2015-05-12 03:15:27 +02:00
|
|
|
$day = 1;
|
|
|
|
} else {
|
|
|
|
$day = phabricator_format_local_time($epoch, $viewer, 'd');
|
|
|
|
}
|
2015-05-04 01:57:18 +02:00
|
|
|
$start_year = phabricator_format_local_time($epoch, $viewer, 'Y');
|
|
|
|
$start_month = phabricator_format_local_time($epoch, $viewer, 'm');
|
2015-05-12 03:15:27 +02:00
|
|
|
$start_day = $day;
|
2015-05-04 01:57:18 +02:00
|
|
|
}
|
|
|
|
return array($start_year, $start_month, $start_day);
|
|
|
|
}
|
|
|
|
|
2015-05-03 01:17:05 +02:00
|
|
|
public function getPageSize(PhabricatorSavedQuery $saved) {
|
2015-06-03 03:26:51 +02:00
|
|
|
if ($this->isMonthView($saved) || $this->isDayView($saved)) {
|
|
|
|
return $saved->getParameter('limit', 1000);
|
|
|
|
} else {
|
|
|
|
return $saved->getParameter('limit', 100);
|
|
|
|
}
|
2015-05-03 00:27:33 +02:00
|
|
|
}
|
|
|
|
|
2015-06-22 22:27:37 +02:00
|
|
|
private function getQueryDateFrom(PhabricatorSavedQuery $saved) {
|
2016-07-27 18:07:29 +02:00
|
|
|
if ($this->calendarYear && $this->calendarMonth) {
|
|
|
|
$viewer = $this->requireViewer();
|
|
|
|
|
|
|
|
$start_year = $this->calendarYear;
|
|
|
|
$start_month = $this->calendarMonth;
|
|
|
|
$start_day = $this->calendarDay ? $this->calendarDay : 1;
|
|
|
|
|
|
|
|
return AphrontFormDateControlValue::newFromDictionary(
|
|
|
|
$viewer,
|
|
|
|
array(
|
|
|
|
'd' => "{$start_year}-{$start_month}-{$start_day}",
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
2015-06-22 22:27:37 +02:00
|
|
|
return $this->getQueryDate($saved, 'rangeStart');
|
2015-05-04 19:08:49 +02:00
|
|
|
}
|
|
|
|
|
2015-06-22 22:27:37 +02:00
|
|
|
private function getQueryDateTo(PhabricatorSavedQuery $saved) {
|
|
|
|
return $this->getQueryDate($saved, 'rangeEnd');
|
2015-05-04 19:08:49 +02:00
|
|
|
}
|
|
|
|
|
2015-06-22 22:27:37 +02:00
|
|
|
private function getQueryDate(PhabricatorSavedQuery $saved, $key) {
|
2015-05-04 19:08:49 +02:00
|
|
|
$viewer = $this->requireViewer();
|
|
|
|
|
|
|
|
$wild = $saved->getParameter($key);
|
2015-06-22 22:27:37 +02:00
|
|
|
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);
|
|
|
|
}
|
2015-05-04 19:08:49 +02:00
|
|
|
} else {
|
|
|
|
$value = AphrontFormDateControlValue::newFromEpoch(
|
|
|
|
$viewer,
|
2015-05-07 03:41:48 +02:00
|
|
|
PhabricatorTime::getTodayMidnightDateTime($viewer)->format('U'));
|
2015-05-04 19:08:49 +02:00
|
|
|
$value->setEnabled(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
$value->setOptional(true);
|
|
|
|
|
|
|
|
return $value;
|
|
|
|
}
|
|
|
|
|
2015-05-12 03:15:27 +02:00
|
|
|
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;
|
|
|
|
}
|
2015-06-23 03:44:08 +02:00
|
|
|
|
2016-10-06 01:22:54 +02:00
|
|
|
public function newUseResultsActions(PhabricatorSavedQuery $saved) {
|
|
|
|
$viewer = $this->requireViewer();
|
|
|
|
$can_export = $viewer->isLoggedIn();
|
|
|
|
|
|
|
|
return array(
|
|
|
|
id(new PhabricatorActionView())
|
|
|
|
->setIcon('fa-download')
|
|
|
|
->setName(pht('Export Query as .ics'))
|
|
|
|
->setDisabled(!$can_export)
|
|
|
|
->setHref('/calendar/export/edit/?queryKey='.$saved->getQueryKey()),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2016-10-18 23:24:17 +02:00
|
|
|
|
|
|
|
private function newResultView($content = null) {
|
|
|
|
// If we aren't rendering a dashboard panel, activate global drag-and-drop
|
|
|
|
// so you can import ".ics" files by dropping them directly onto the
|
|
|
|
// calendar.
|
|
|
|
if (!$this->isPanelContext()) {
|
|
|
|
$drop_upload = id(new PhabricatorGlobalUploadTargetView())
|
|
|
|
->setViewer($this->requireViewer())
|
|
|
|
->setHintText("\xE2\x87\xAA ".pht('Drop .ics Files to Import'))
|
|
|
|
->setSubmitURI('/calendar/import/drop/')
|
|
|
|
->setViewPolicy(PhabricatorPolicies::POLICY_NOONE);
|
|
|
|
|
|
|
|
$content = array(
|
|
|
|
$drop_upload,
|
|
|
|
$content,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return id(new PhabricatorApplicationSearchResultView())
|
|
|
|
->setContent($content);
|
|
|
|
}
|
|
|
|
|
2014-02-06 19:10:18 +01:00
|
|
|
}
|