Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php
12256 views
1
<?php
2
3
final class PhabricatorCalendarEventSearchEngine
4
extends PhabricatorApplicationSearchEngine {
5
6
private $calendarYear;
7
private $calendarMonth;
8
private $calendarDay;
9
10
public function getResultTypeDescription() {
11
return pht('Calendar Events');
12
}
13
14
public function getApplicationClassName() {
15
return 'PhabricatorCalendarApplication';
16
}
17
18
public function newQuery() {
19
$viewer = $this->requireViewer();
20
21
return id(new PhabricatorCalendarEventQuery())
22
->needRSVPs(array($viewer->getPHID()));
23
}
24
25
protected function shouldShowOrderField() {
26
return false;
27
}
28
29
protected function buildCustomSearchFields() {
30
return array(
31
id(new PhabricatorSearchDatasourceField())
32
->setLabel(pht('Hosts'))
33
->setKey('hostPHIDs')
34
->setAliases(array('host', 'hostPHID', 'hosts'))
35
->setDatasource(new PhabricatorPeopleUserFunctionDatasource()),
36
id(new PhabricatorSearchDatasourceField())
37
->setLabel(pht('Invited'))
38
->setKey('invitedPHIDs')
39
->setDatasource(new PhabricatorCalendarInviteeDatasource()),
40
id(new PhabricatorSearchDateControlField())
41
->setLabel(pht('Occurs After'))
42
->setKey('rangeStart'),
43
id(new PhabricatorSearchDateControlField())
44
->setLabel(pht('Occurs Before'))
45
->setKey('rangeEnd')
46
->setAliases(array('rangeEnd')),
47
id(new PhabricatorSearchCheckboxesField())
48
->setKey('upcoming')
49
->setOptions(array(
50
'upcoming' => pht('Show only upcoming events.'),
51
)),
52
id(new PhabricatorSearchSelectField())
53
->setLabel(pht('Cancelled Events'))
54
->setKey('isCancelled')
55
->setOptions($this->getCancelledOptions())
56
->setDefault('active'),
57
id(new PhabricatorPHIDsSearchField())
58
->setLabel(pht('Import Sources'))
59
->setKey('importSourcePHIDs')
60
->setAliases(array('importSourcePHID')),
61
id(new PhabricatorSearchSelectField())
62
->setLabel(pht('Display Options'))
63
->setKey('display')
64
->setOptions($this->getViewOptions())
65
->setDefault('month'),
66
);
67
}
68
69
private function getCancelledOptions() {
70
return array(
71
'active' => pht('Active Events Only'),
72
'cancelled' => pht('Cancelled Events Only'),
73
'both' => pht('Both Cancelled and Active Events'),
74
);
75
}
76
77
private function getViewOptions() {
78
return array(
79
'month' => pht('Month View'),
80
'day' => pht('Day View'),
81
'list' => pht('List View'),
82
);
83
}
84
85
public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) {
86
$query = parent::buildQueryFromSavedQuery($saved);
87
88
// If this is an export query for generating an ".ics" file, don't
89
// build ghost events.
90
if ($saved->getParameter('export')) {
91
$query->setGenerateGhosts(false);
92
}
93
94
return $query;
95
}
96
97
protected function buildQueryFromParameters(array $map) {
98
$query = $this->newQuery();
99
$viewer = $this->requireViewer();
100
101
if ($map['hostPHIDs']) {
102
$query->withHostPHIDs($map['hostPHIDs']);
103
}
104
105
if ($map['invitedPHIDs']) {
106
$query->withInvitedPHIDs($map['invitedPHIDs']);
107
}
108
109
$range_start = $map['rangeStart'];
110
$range_end = $map['rangeEnd'];
111
$display = $map['display'];
112
113
if ($map['upcoming'] && $map['upcoming'][0] == 'upcoming') {
114
$upcoming = true;
115
} else {
116
$upcoming = false;
117
}
118
119
list($range_start, $range_end) = $this->getQueryDateRange(
120
$range_start,
121
$range_end,
122
$display,
123
$upcoming);
124
125
$query->withDateRange($range_start, $range_end);
126
127
switch ($map['isCancelled']) {
128
case 'active':
129
$query->withIsCancelled(false);
130
break;
131
case 'cancelled':
132
$query->withIsCancelled(true);
133
break;
134
}
135
136
if ($map['importSourcePHIDs']) {
137
$query->withImportSourcePHIDs($map['importSourcePHIDs']);
138
}
139
140
if (!$map['ids'] && !$map['phids']) {
141
$query
142
->withIsStub(false)
143
->setGenerateGhosts(true);
144
}
145
146
return $query;
147
}
148
149
private function getQueryDateRange(
150
$start_date_wild,
151
$end_date_wild,
152
$display,
153
$upcoming) {
154
155
$start_date_value = $this->getSafeDate($start_date_wild);
156
$end_date_value = $this->getSafeDate($end_date_wild);
157
158
$viewer = $this->requireViewer();
159
$timezone = new DateTimeZone($viewer->getTimezoneIdentifier());
160
$min_range = null;
161
$max_range = null;
162
163
$min_range = $start_date_value->getEpoch();
164
$max_range = $end_date_value->getEpoch();
165
166
if ($display == 'month' || $display == 'day') {
167
list($start_year, $start_month, $start_day) =
168
$this->getDisplayYearAndMonthAndDay($min_range, $max_range, $display);
169
170
$start_day = new DateTime(
171
"{$start_year}-{$start_month}-{$start_day}",
172
$timezone);
173
$next = clone $start_day;
174
175
if ($display == 'month') {
176
$next->modify('+1 month');
177
} else if ($display == 'day') {
178
$next->modify('+7 day');
179
}
180
181
$display_start = $start_day->format('U');
182
$display_end = $next->format('U');
183
184
$start_of_week = $viewer->getUserSetting(
185
PhabricatorWeekStartDaySetting::SETTINGKEY);
186
187
$end_of_week = ($start_of_week + 6) % 7;
188
189
$first_of_month = $start_day->format('w');
190
$last_of_month = id(clone $next)->modify('-1 day')->format('w');
191
192
if (!$min_range || ($min_range < $display_start)) {
193
$min_range = $display_start;
194
195
if ($display == 'month' &&
196
$first_of_month !== $start_of_week) {
197
$interim_day_num = ($first_of_month + 7 - $start_of_week) % 7;
198
$min_range = id(clone $start_day)
199
->modify('-'.$interim_day_num.' days')
200
->format('U');
201
}
202
}
203
if (!$max_range || ($max_range > $display_end)) {
204
$max_range = $display_end;
205
206
if ($display == 'month' &&
207
$last_of_month !== $end_of_week) {
208
$interim_day_num = ($end_of_week + 7 - $last_of_month) % 7;
209
$max_range = id(clone $next)
210
->modify('+'.$interim_day_num.' days')
211
->format('U');
212
}
213
}
214
}
215
216
if ($upcoming) {
217
$now = PhabricatorTime::getNow();
218
if ($min_range) {
219
$min_range = max($now, $min_range);
220
} else {
221
$min_range = $now;
222
}
223
}
224
225
return array($min_range, $max_range);
226
}
227
228
protected function getURI($path) {
229
return '/calendar/'.$path;
230
}
231
232
protected function getBuiltinQueryNames() {
233
$names = array(
234
'month' => pht('Month View'),
235
'day' => pht('Day View'),
236
'upcoming' => pht('Upcoming Events'),
237
'all' => pht('All Events'),
238
);
239
240
return $names;
241
}
242
243
public function setCalendarYearAndMonthAndDay($year, $month, $day = null) {
244
$this->calendarYear = $year;
245
$this->calendarMonth = $month;
246
$this->calendarDay = $day;
247
248
return $this;
249
}
250
251
public function buildSavedQueryFromBuiltin($query_key) {
252
$query = $this->newSavedQuery();
253
$query->setQueryKey($query_key);
254
255
switch ($query_key) {
256
case 'month':
257
return $query->setParameter('display', 'month');
258
case 'day':
259
return $query->setParameter('display', 'day');
260
case 'upcoming':
261
return $query
262
->setParameter('display', 'list')
263
->setParameter('upcoming', array(
264
0 => 'upcoming',
265
));
266
case 'all':
267
return $query;
268
}
269
270
return parent::buildSavedQueryFromBuiltin($query_key);
271
}
272
273
protected function renderResultList(
274
array $events,
275
PhabricatorSavedQuery $query,
276
array $handles) {
277
278
if ($this->isMonthView($query)) {
279
$result = $this->buildCalendarMonthView($events, $query);
280
} else if ($this->isDayView($query)) {
281
$result = $this->buildCalendarDayView($events, $query);
282
} else {
283
$result = $this->buildCalendarListView($events, $query);
284
}
285
286
return $result;
287
}
288
289
private function buildCalendarListView(
290
array $events,
291
PhabricatorSavedQuery $query) {
292
293
assert_instances_of($events, 'PhabricatorCalendarEvent');
294
$viewer = $this->requireViewer();
295
$list = new PHUIObjectItemListView();
296
297
foreach ($events as $event) {
298
if ($event->getIsGhostEvent()) {
299
$monogram = $event->getParentEvent()->getMonogram();
300
$index = $event->getSequenceIndex();
301
$monogram = "{$monogram}/{$index}";
302
} else {
303
$monogram = $event->getMonogram();
304
}
305
306
$item = id(new PHUIObjectItemView())
307
->setUser($viewer)
308
->setObject($event)
309
->setObjectName($monogram)
310
->setHeader($event->getName())
311
->setHref($event->getURI());
312
313
$item->addAttribute($event->renderEventDate($viewer, false));
314
315
if ($event->getIsCancelled()) {
316
$item->setDisabled(true);
317
}
318
319
$status_icon = $event->getDisplayIcon($viewer);
320
$status_color = $event->getDisplayIconColor($viewer);
321
$status_label = $event->getDisplayIconLabel($viewer);
322
323
$item->setStatusIcon("{$status_icon} {$status_color}", $status_label);
324
325
$host = pht(
326
'Hosted by %s',
327
$viewer->renderHandle($event->getHostPHID()));
328
$item->addByline($host);
329
330
$list->addItem($item);
331
}
332
333
return $this->newResultView()
334
->setObjectList($list)
335
->setNoDataString(pht('No events found.'));
336
}
337
338
private function buildCalendarMonthView(
339
array $events,
340
PhabricatorSavedQuery $query) {
341
assert_instances_of($events, 'PhabricatorCalendarEvent');
342
343
$viewer = $this->requireViewer();
344
$now = PhabricatorTime::getNow();
345
346
list($start_year, $start_month) =
347
$this->getDisplayYearAndMonthAndDay(
348
$this->getQueryDateFrom($query)->getEpoch(),
349
$this->getQueryDateTo($query)->getEpoch(),
350
$query->getParameter('display'));
351
352
$now_year = phabricator_format_local_time($now, $viewer, 'Y');
353
$now_month = phabricator_format_local_time($now, $viewer, 'm');
354
$now_day = phabricator_format_local_time($now, $viewer, 'j');
355
356
if ($start_month == $now_month && $start_year == $now_year) {
357
$month_view = new PHUICalendarMonthView(
358
$this->getQueryDateFrom($query),
359
$this->getQueryDateTo($query),
360
$start_month,
361
$start_year,
362
$now_day);
363
} else {
364
$month_view = new PHUICalendarMonthView(
365
$this->getQueryDateFrom($query),
366
$this->getQueryDateTo($query),
367
$start_month,
368
$start_year);
369
}
370
371
$month_view->setUser($viewer);
372
373
$viewer_phid = $viewer->getPHID();
374
foreach ($events as $event) {
375
$epoch_min = $event->getStartDateTimeEpoch();
376
$epoch_max = $event->getEndDateTimeEpoch();
377
378
$is_invited = $event->isRSVPInvited($viewer_phid);
379
$is_attending = $event->getIsUserAttending($viewer_phid);
380
381
$event_view = id(new AphrontCalendarEventView())
382
->setHostPHID($event->getHostPHID())
383
->setEpochRange($epoch_min, $epoch_max)
384
->setIsCancelled($event->getIsCancelled())
385
->setName($event->getName())
386
->setURI($event->getURI())
387
->setIsAllDay($event->getIsAllDay())
388
->setIcon($event->getDisplayIcon($viewer))
389
->setViewerIsInvited($is_invited || $is_attending)
390
->setDatetimeSummary($event->renderEventDate($viewer, true))
391
->setIconColor($event->getDisplayIconColor($viewer));
392
393
$month_view->addEvent($event_view);
394
}
395
396
$month_view->setBrowseURI(
397
$this->getURI('query/'.$query->getQueryKey().'/'));
398
399
$from = $this->getQueryDateFrom($query)->getDateTime();
400
401
$crumbs = array();
402
$crumbs[] = id(new PHUICrumbView())
403
->setName($from->format('F Y'));
404
405
$header = id(new PHUIHeaderView())
406
->setProfileHeader(true)
407
->setHeader($from->format('F Y'));
408
409
return $this->newResultView($month_view)
410
->setCrumbs($crumbs)
411
->setHeader($header);
412
}
413
414
private function buildCalendarDayView(
415
array $events,
416
PhabricatorSavedQuery $query) {
417
418
$viewer = $this->requireViewer();
419
420
list($start_year, $start_month, $start_day) =
421
$this->getDisplayYearAndMonthAndDay(
422
$this->getQueryDateFrom($query)->getEpoch(),
423
$this->getQueryDateTo($query)->getEpoch(),
424
$query->getParameter('display'));
425
426
$day_view = id(new PHUICalendarDayView(
427
$this->getQueryDateFrom($query),
428
$this->getQueryDateTo($query),
429
$start_year,
430
$start_month,
431
$start_day))
432
->setQuery($query->getQueryKey());
433
434
$day_view->setUser($viewer);
435
436
$phids = mpull($events, 'getHostPHID');
437
438
foreach ($events as $event) {
439
$can_edit = PhabricatorPolicyFilter::hasCapability(
440
$viewer,
441
$event,
442
PhabricatorPolicyCapability::CAN_EDIT);
443
444
$epoch_min = $event->getStartDateTimeEpoch();
445
$epoch_max = $event->getEndDateTimeEpoch();
446
447
$status_icon = $event->getDisplayIcon($viewer);
448
$status_color = $event->getDisplayIconColor($viewer);
449
450
$event_view = id(new AphrontCalendarEventView())
451
->setCanEdit($can_edit)
452
->setEventID($event->getID())
453
->setEpochRange($epoch_min, $epoch_max)
454
->setIsAllDay($event->getIsAllDay())
455
->setIcon($status_icon)
456
->setIconColor($status_color)
457
->setName($event->getName())
458
->setURI($event->getURI())
459
->setDatetimeSummary($event->renderEventDate($viewer, true))
460
->setIsCancelled($event->getIsCancelled());
461
462
$day_view->addEvent($event_view);
463
}
464
465
$browse_uri = $this->getURI('query/'.$query->getQueryKey().'/');
466
$day_view->setBrowseURI($browse_uri);
467
468
$from = $this->getQueryDateFrom($query)->getDateTime();
469
$month_uri = $browse_uri.$from->format('Y/m/');
470
471
$crumbs = array(
472
id(new PHUICrumbView())
473
->setName($from->format('F Y'))
474
->setHref($month_uri),
475
id(new PHUICrumbView())
476
->setName($from->format('D jS')),
477
);
478
479
$header = id(new PHUIHeaderView())
480
->setProfileHeader(true)
481
->setHeader($from->format('D, F jS'));
482
483
return $this->newResultView($day_view)
484
->setCrumbs($crumbs)
485
->setHeader($header);
486
}
487
488
private function getDisplayYearAndMonthAndDay(
489
$range_start,
490
$range_end,
491
$display) {
492
493
$viewer = $this->requireViewer();
494
$epoch = null;
495
496
if ($this->calendarYear && $this->calendarMonth) {
497
$start_year = $this->calendarYear;
498
$start_month = $this->calendarMonth;
499
$start_day = $this->calendarDay ? $this->calendarDay : 1;
500
} else {
501
if ($range_start) {
502
$epoch = $range_start;
503
} else if ($range_end) {
504
$epoch = $range_end;
505
} else {
506
$epoch = time();
507
}
508
if ($display == 'month') {
509
$day = 1;
510
} else {
511
$day = phabricator_format_local_time($epoch, $viewer, 'd');
512
}
513
$start_year = phabricator_format_local_time($epoch, $viewer, 'Y');
514
$start_month = phabricator_format_local_time($epoch, $viewer, 'm');
515
$start_day = $day;
516
}
517
return array($start_year, $start_month, $start_day);
518
}
519
520
public function getPageSize(PhabricatorSavedQuery $saved) {
521
if ($this->isMonthView($saved) || $this->isDayView($saved)) {
522
return $saved->getParameter('limit', 1000);
523
} else {
524
return $saved->getParameter('limit', 100);
525
}
526
}
527
528
private function getQueryDateFrom(PhabricatorSavedQuery $saved) {
529
if ($this->calendarYear && $this->calendarMonth) {
530
$viewer = $this->requireViewer();
531
532
$start_year = $this->calendarYear;
533
$start_month = $this->calendarMonth;
534
$start_day = $this->calendarDay ? $this->calendarDay : 1;
535
536
return AphrontFormDateControlValue::newFromDictionary(
537
$viewer,
538
array(
539
'd' => "{$start_year}-{$start_month}-{$start_day}",
540
));
541
}
542
543
return $this->getQueryDate($saved, 'rangeStart');
544
}
545
546
private function getQueryDateTo(PhabricatorSavedQuery $saved) {
547
return $this->getQueryDate($saved, 'rangeEnd');
548
}
549
550
private function getQueryDate(PhabricatorSavedQuery $saved, $key) {
551
$viewer = $this->requireViewer();
552
553
$wild = $saved->getParameter($key);
554
return $this->getSafeDate($wild);
555
}
556
557
private function getSafeDate($value) {
558
$viewer = $this->requireViewer();
559
if ($value) {
560
// ideally this would be consistent and always pass in the same type
561
if ($value instanceof AphrontFormDateControlValue) {
562
return $value;
563
} else {
564
$value = AphrontFormDateControlValue::newFromWild($viewer, $value);
565
}
566
} else {
567
$value = AphrontFormDateControlValue::newFromEpoch(
568
$viewer,
569
PhabricatorTime::getTodayMidnightDateTime($viewer)->format('U'));
570
$value->setEnabled(false);
571
}
572
573
$value->setOptional(true);
574
575
return $value;
576
}
577
578
private function isMonthView(PhabricatorSavedQuery $query) {
579
if ($this->isDayView($query)) {
580
return false;
581
}
582
if ($query->getParameter('display') == 'month') {
583
return true;
584
}
585
}
586
587
private function isDayView(PhabricatorSavedQuery $query) {
588
if ($query->getParameter('display') == 'day') {
589
return true;
590
}
591
if ($this->calendarDay) {
592
return true;
593
}
594
595
return false;
596
}
597
598
public function newUseResultsActions(PhabricatorSavedQuery $saved) {
599
$viewer = $this->requireViewer();
600
$can_export = $viewer->isLoggedIn();
601
602
return array(
603
id(new PhabricatorActionView())
604
->setIcon('fa-download')
605
->setName(pht('Export Query as .ics'))
606
->setDisabled(!$can_export)
607
->setHref('/calendar/export/edit/?queryKey='.$saved->getQueryKey()),
608
);
609
}
610
611
612
private function newResultView($content = null) {
613
// If we aren't rendering a dashboard panel, activate global drag-and-drop
614
// so you can import ".ics" files by dropping them directly onto the
615
// calendar.
616
if (!$this->isPanelContext()) {
617
$drop_upload = id(new PhabricatorGlobalUploadTargetView())
618
->setViewer($this->requireViewer())
619
->setHintText("\xE2\x87\xAA ".pht('Drop .ics Files to Import'))
620
->setSubmitURI('/calendar/import/drop/')
621
->setViewPolicy(PhabricatorPolicies::POLICY_NOONE);
622
623
$content = array(
624
$drop_upload,
625
$content,
626
);
627
}
628
629
return id(new PhabricatorApplicationSearchResultView())
630
->setContent($content);
631
}
632
633
}
634
635