@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.)
hq.recaptime.dev/wiki/Phorge
phorge
phabricator
1<?php
2
3final class ManiphestTaskSearchEngine
4 extends PhabricatorApplicationSearchEngine {
5
6 private $showBatchControls;
7 private $baseURI;
8 private $isBoardView;
9
10 public function setIsBoardView($is_board_view) {
11 $this->isBoardView = $is_board_view;
12 return $this;
13 }
14
15 public function getIsBoardView() {
16 return $this->isBoardView;
17 }
18
19 public function setBaseURI($base_uri) {
20 $this->baseURI = $base_uri;
21 return $this;
22 }
23
24 public function getBaseURI() {
25 return $this->baseURI;
26 }
27
28 public function setShowBatchControls($show_batch_controls) {
29 $this->showBatchControls = $show_batch_controls;
30 return $this;
31 }
32
33 public function getResultTypeDescription() {
34 return pht('Maniphest Tasks');
35 }
36
37 public function getApplicationClassName() {
38 return PhabricatorManiphestApplication::class;
39 }
40
41 public function newQuery() {
42 return id(new ManiphestTaskQuery())
43 ->needProjectPHIDs(true);
44 }
45
46 protected function buildCustomSearchFields() {
47 // Hide the "Subtypes" constraint from the web UI if the install only
48 // defines one task subtype, since it isn't of any use in this case.
49 $subtype_map = id(new ManiphestTask())->newEditEngineSubtypeMap();
50 $hide_subtypes = ($subtype_map->getCount() == 1);
51
52 return array(
53 id(new PhabricatorOwnersSearchField())
54 ->setLabel(pht('Assigned To'))
55 ->setKey('assignedPHIDs')
56 ->setConduitKey('assigned')
57 ->setAliases(array('assigned'))
58 ->setDescription(
59 pht('Search for tasks owned by a user from a list.')),
60 id(new PhabricatorUsersSearchField())
61 ->setLabel(pht('Authors'))
62 ->setKey('authorPHIDs')
63 ->setAliases(array('author', 'authors'))
64 ->setDescription(
65 pht('Search for tasks with given authors.')),
66 id(new PhabricatorSearchDatasourceField())
67 ->setLabel(pht('Statuses'))
68 ->setKey('statuses')
69 ->setAliases(array('status'))
70 ->setDescription(
71 pht('Search for tasks with given statuses.'))
72 ->setDatasource(new ManiphestTaskStatusFunctionDatasource()),
73 id(new PhabricatorSearchDatasourceField())
74 ->setLabel(pht('Priorities'))
75 ->setKey('priorities')
76 ->setAliases(array('priority'))
77 ->setDescription(
78 pht('Search for tasks with given priorities.'))
79 ->setConduitParameterType(new ConduitIntListParameterType())
80 ->setDatasource(new ManiphestTaskPriorityDatasource()),
81 id(new PhabricatorSearchDatasourceField())
82 ->setLabel(pht('Subtypes'))
83 ->setKey('subtypes')
84 ->setAliases(array('subtype'))
85 ->setDescription(
86 pht('Search for tasks with given subtypes.'))
87 ->setDatasource(new ManiphestTaskSubtypeDatasource())
88 ->setIsHidden($hide_subtypes),
89 id(new PhabricatorPHIDsSearchField())
90 ->setLabel(pht('Columns'))
91 ->setKey('columnPHIDs')
92 ->setAliases(array('column', 'columnPHID', 'columns'))
93 ->setDescription(
94 pht('Search for tasks in specific workboard columns.')),
95 id(new PhabricatorSearchThreeStateField())
96 ->setLabel(pht('Open Parents'))
97 ->setKey('hasParents')
98 ->setAliases(array('blocking'))
99 ->setDescription(
100 pht('Search for tasks which block open parent tasks.'))
101 ->setOptions(
102 pht('(Show All)'),
103 pht('Show Only Tasks With Open Parents'),
104 pht('Show Only Tasks Without Open Parents')),
105 id(new PhabricatorSearchThreeStateField())
106 ->setLabel(pht('Open Subtasks'))
107 ->setKey('hasSubtasks')
108 ->setAliases(array('blocked'))
109 ->setDescription(
110 pht('Search for tasks blocked by open subtasks.'))
111 ->setOptions(
112 pht('(Show All)'),
113 pht('Show Only Tasks With Open Subtasks'),
114 pht('Show Only Tasks Without Open Subtasks')),
115 id(new PhabricatorIDsSearchField())
116 ->setLabel(pht('Parent IDs'))
117 ->setKey('parentIDs')
118 ->setAliases(array('parentID'))
119 ->setDescription(
120 pht('Search for tasks which have specific parent tasks.')),
121 id(new PhabricatorIDsSearchField())
122 ->setLabel(pht('Subtask IDs'))
123 ->setKey('subtaskIDs')
124 ->setAliases(array('subtaskID'))
125 ->setDescription(
126 pht('Search for tasks which have specific subtasks.')),
127 id(new PhabricatorSearchSelectField())
128 ->setLabel(pht('Group By'))
129 ->setKey('group')
130 ->setDescription(pht('Group results by a certain parameter.'))
131 ->setOptions($this->getGroupOptions()),
132 id(new PhabricatorSearchDateField())
133 ->setLabel(pht('Created After'))
134 ->setKey('createdStart')
135 ->setDescription(
136 pht('Search for tasks created after a certain date.')),
137 id(new PhabricatorSearchDateField())
138 ->setLabel(pht('Created Before'))
139 ->setKey('createdEnd')
140 ->setDescription(
141 pht('Search for tasks created before a certain date.')),
142 id(new PhabricatorSearchDateField())
143 ->setLabel(pht('Updated After'))
144 ->setKey('modifiedStart')
145 ->setDescription(
146 pht('Search for tasks updated after a certain date.')),
147 id(new PhabricatorSearchDateField())
148 ->setLabel(pht('Updated Before'))
149 ->setKey('modifiedEnd')
150 ->setDescription(
151 pht('Search for tasks updated before a certain date.')),
152 id(new PhabricatorSearchDateField())
153 ->setLabel(pht('Closed After'))
154 ->setKey('closedStart')
155 ->setDescription(
156 pht('Search for tasks closed after a certain date.')),
157 id(new PhabricatorSearchDateField())
158 ->setLabel(pht('Closed Before'))
159 ->setKey('closedEnd')
160 ->setDescription(
161 pht('Search for tasks closed before a certain date.')),
162 id(new PhabricatorUsersSearchField())
163 ->setLabel(pht('Closed By'))
164 ->setKey('closerPHIDs')
165 ->setAliases(array('closer', 'closerPHID', 'closers'))
166 ->setDescription(pht('Search for tasks closed by certain users.')),
167 id(new PhabricatorSearchIntField())
168 ->setLabel(pht('Page Size'))
169 ->setKey('limit'),
170 );
171 }
172
173 protected function getDefaultFieldOrder() {
174 return array(
175 'assignedPHIDs',
176 'projectPHIDs',
177 'authorPHIDs',
178 'subscriberPHIDs',
179 'statuses',
180 'priorities',
181 'subtypes',
182 'hasParents',
183 'hasSubtasks',
184 'parentIDs',
185 'subtaskIDs',
186 'group',
187 'order',
188 'ids',
189 '...',
190 'createdStart',
191 'createdEnd',
192 'modifiedStart',
193 'modifiedEnd',
194 'closedStart',
195 'closedEnd',
196 'closerPHIDs',
197 'limit',
198 );
199 }
200
201 protected function getHiddenFields() {
202 $keys = array();
203
204 if ($this->getIsBoardView()) {
205 $keys[] = 'group';
206 $keys[] = 'order';
207 $keys[] = 'limit';
208 }
209
210 return $keys;
211 }
212
213 protected function buildQueryFromParameters(array $map) {
214 $query = $this->newQuery();
215
216 if ($map['assignedPHIDs']) {
217 $query->withOwners($map['assignedPHIDs']);
218 }
219
220 if ($map['authorPHIDs']) {
221 $query->withAuthors($map['authorPHIDs']);
222 }
223
224 if ($map['statuses']) {
225 $query->withStatuses($map['statuses']);
226 }
227
228 if ($map['priorities']) {
229 $query->withPriorities($map['priorities']);
230 }
231
232 if ($map['subtypes']) {
233 $query->withSubtypes($map['subtypes']);
234 }
235
236 if ($map['createdStart']) {
237 $query->withDateCreatedAfter($map['createdStart']);
238 }
239
240 if ($map['createdEnd']) {
241 $query->withDateCreatedBefore($map['createdEnd']);
242 }
243
244 if ($map['modifiedStart']) {
245 $query->withDateModifiedAfter($map['modifiedStart']);
246 }
247
248 if ($map['modifiedEnd']) {
249 $query->withDateModifiedBefore($map['modifiedEnd']);
250 }
251
252 if ($map['closedStart'] || $map['closedEnd']) {
253 $query->withClosedEpochBetween($map['closedStart'], $map['closedEnd']);
254 }
255
256 if ($map['closerPHIDs']) {
257 $query->withCloserPHIDs($map['closerPHIDs']);
258 }
259
260 if ($map['hasParents'] !== null) {
261 $query->withOpenParents($map['hasParents']);
262 }
263
264 if ($map['hasSubtasks'] !== null) {
265 $query->withOpenSubtasks($map['hasSubtasks']);
266 }
267
268 if ($map['parentIDs']) {
269 $query->withParentTaskIDs($map['parentIDs']);
270 }
271
272 if ($map['subtaskIDs']) {
273 $query->withSubtaskIDs($map['subtaskIDs']);
274 }
275
276 if ($map['columnPHIDs']) {
277 $query->withColumnPHIDs($map['columnPHIDs']);
278 }
279
280 $group = idx($map, 'group');
281 $group = idx($this->getGroupValues(), $group);
282 if ($group) {
283 $query->setGroupBy($group);
284 }
285
286 $ids = idx($map, 'ids');
287 if ($ids) {
288 foreach ($ids as $key => $id) {
289 $id = trim($id, ' Tt');
290 if (!$id || !is_numeric($id)) {
291 unset($ids[$key]);
292 } else {
293 $ids[$key] = $id;
294 }
295 }
296
297 if ($ids) {
298 $query->withIDs($ids);
299 }
300 }
301
302 return $query;
303 }
304
305 protected function getURI($path) {
306 if ($this->baseURI) {
307 return $this->baseURI.$path;
308 }
309 return '/maniphest/'.$path;
310 }
311
312 protected function getBuiltinQueryNames() {
313 $names = array();
314
315 if ($this->requireViewer()->isLoggedIn()) {
316 $names['assigned'] = pht('Assigned');
317 $names['authored'] = pht('Authored');
318 $names['subscribed'] = pht('Subscribed');
319 }
320
321 $names['open'] = pht('Open Tasks');
322 $names['all'] = pht('All Tasks');
323
324 return $names;
325 }
326
327 public function buildSavedQueryFromBuiltin($query_key) {
328
329 $query = $this->newSavedQuery();
330 $query->setQueryKey($query_key);
331
332 $viewer_phid = $this->requireViewer()->getPHID();
333
334 switch ($query_key) {
335 case 'all':
336 return $query;
337 case 'assigned':
338 return $query
339 ->setParameter('assignedPHIDs', array($viewer_phid))
340 ->setParameter(
341 'statuses',
342 ManiphestTaskStatus::getOpenStatusConstants());
343 case 'subscribed':
344 return $query
345 ->setParameter('subscriberPHIDs', array($viewer_phid))
346 ->setParameter(
347 'statuses',
348 ManiphestTaskStatus::getOpenStatusConstants());
349 case 'open':
350 return $query
351 ->setParameter(
352 'statuses',
353 ManiphestTaskStatus::getOpenStatusConstants());
354 case 'authored':
355 return $query
356 ->setParameter('authorPHIDs', array($viewer_phid))
357 ->setParameter('order', 'created')
358 ->setParameter('group', 'none');
359 }
360
361 return parent::buildSavedQueryFromBuiltin($query_key);
362 }
363
364 private function getGroupOptions() {
365 return array(
366 'priority' => pht('Priority'),
367 'assigned' => pht('Assigned'),
368 'status' => pht('Status'),
369 'project' => pht('Project'),
370 'none' => pht('None'),
371 );
372 }
373
374 private function getGroupValues() {
375 return array(
376 'priority' => ManiphestTaskQuery::GROUP_PRIORITY,
377 'assigned' => ManiphestTaskQuery::GROUP_OWNER,
378 'status' => ManiphestTaskQuery::GROUP_STATUS,
379 'project' => ManiphestTaskQuery::GROUP_PROJECT,
380 'none' => ManiphestTaskQuery::GROUP_NONE,
381 );
382 }
383
384 protected function renderResultList(
385 array $tasks,
386 PhabricatorSavedQuery $saved,
387 array $handles) {
388
389 $viewer = $this->requireViewer();
390
391 if ($this->isPanelContext()) {
392 $can_bulk_edit = false;
393 } else {
394 $can_bulk_edit = PhabricatorPolicyFilter::hasCapability(
395 $viewer,
396 $this->getApplication(),
397 ManiphestBulkEditCapability::CAPABILITY);
398 }
399
400 $custom_field_lists = $this->loadCustomFields(
401 $tasks,
402 PhabricatorCustomField::ROLE_LIST);
403
404 $list = id(new ManiphestTaskResultListView())
405 ->setViewer($viewer)
406 ->setTasks($tasks)
407 ->setHandles($handles)
408 ->setSavedQuery($saved)
409 ->setCanBatchEdit($can_bulk_edit)
410 ->setCustomFieldLists($custom_field_lists)
411 ->setShowBatchControls($this->showBatchControls);
412
413 $result = new PhabricatorApplicationSearchResultView();
414 $result->setContent($list);
415
416 return $result;
417 }
418
419 protected function getRequiredHandlePHIDsForResultList(
420 array $objects,
421 PhabricatorSavedQuery $query) {
422
423 $phids = array();
424 foreach ($objects as $task) {
425 $assigned_phid = $task->getOwnerPHID();
426 if ($assigned_phid) {
427 $phids[] = $assigned_phid;
428 }
429 foreach ($task->getProjectPHIDs() as $project_phid) {
430 $phids[] = $project_phid;
431 }
432 }
433
434 return $phids;
435 }
436
437 protected function willUseSavedQuery(PhabricatorSavedQuery $saved) {
438
439 // The 'withUnassigned' parameter may be present in old saved queries from
440 // before parameterized typeaheads, and is retained for compatibility. We
441 // could remove it by migrating old saved queries.
442 $assigned_phids = $saved->getParameter('assignedPHIDs', array());
443 if ($saved->getParameter('withUnassigned')) {
444 $assigned_phids[] = PhabricatorPeopleNoOwnerDatasource::FUNCTION_TOKEN;
445 }
446 $saved->setParameter('assignedPHIDs', $assigned_phids);
447
448 // The 'projects' and other parameters may be present in old saved queries
449 // from before parameterized typeaheads.
450 $project_phids = $saved->getParameter('projectPHIDs', array());
451
452 $old = $saved->getParameter('projects', array());
453 foreach ($old as $phid) {
454 $project_phids[] = $phid;
455 }
456
457 $all = $saved->getParameter('allProjectPHIDs', array());
458 foreach ($all as $phid) {
459 $project_phids[] = $phid;
460 }
461
462 $any = $saved->getParameter('anyProjectPHIDs', array());
463 foreach ($any as $phid) {
464 $project_phids[] = 'any('.$phid.')';
465 }
466
467 $not = $saved->getParameter('excludeProjectPHIDs', array());
468 foreach ($not as $phid) {
469 $project_phids[] = 'not('.$phid.')';
470 }
471
472 $users = $saved->getParameter('userProjectPHIDs', array());
473 foreach ($users as $phid) {
474 $project_phids[] = 'projects('.$phid.')';
475 }
476
477 $no = $saved->getParameter('withNoProject');
478 if ($no) {
479 $project_phids[] = 'null()';
480 }
481
482 $saved->setParameter('projectPHIDs', $project_phids);
483 }
484
485 protected function getNewUserBody() {
486 $viewer = $this->requireViewer();
487
488 $create_button = id(new ManiphestEditEngine())
489 ->setViewer($viewer)
490 ->newNUXBUtton(pht('Create a Task'));
491
492 $icon = $this->getApplication()->getIcon();
493 $app_name = $this->getApplication()->getName();
494 $view = id(new PHUIBigInfoView())
495 ->setIcon($icon)
496 ->setTitle(pht('Welcome to %s', $app_name))
497 ->setDescription(
498 pht('Use Maniphest to track bugs, features, todos, or anything else '.
499 'you need to get done. Tasks assigned to you will appear here.'))
500 ->addAction($create_button);
501
502 return $view;
503 }
504
505
506 protected function newExportFields() {
507 $fields = array(
508 id(new PhabricatorStringExportField())
509 ->setKey('monogram')
510 ->setLabel(pht('Monogram')),
511 id(new PhabricatorPHIDExportField())
512 ->setKey('authorPHID')
513 ->setLabel(pht('Author PHID')),
514 id(new PhabricatorStringExportField())
515 ->setKey('author')
516 ->setLabel(pht('Author')),
517 id(new PhabricatorPHIDExportField())
518 ->setKey('ownerPHID')
519 ->setLabel(pht('Owner PHID')),
520 id(new PhabricatorStringExportField())
521 ->setKey('owner')
522 ->setLabel(pht('Owner')),
523 id(new PhabricatorStringExportField())
524 ->setKey('status')
525 ->setLabel(pht('Status')),
526 id(new PhabricatorStringExportField())
527 ->setKey('statusName')
528 ->setLabel(pht('Status Name')),
529 id(new PhabricatorEpochExportField())
530 ->setKey('dateClosed')
531 ->setLabel(pht('Date Closed')),
532 id(new PhabricatorPHIDExportField())
533 ->setKey('closerPHID')
534 ->setLabel(pht('Closer PHID')),
535 id(new PhabricatorStringExportField())
536 ->setKey('closer')
537 ->setLabel(pht('Closer')),
538 id(new PhabricatorStringExportField())
539 ->setKey('priority')
540 ->setLabel(pht('Priority')),
541 id(new PhabricatorStringExportField())
542 ->setKey('priorityName')
543 ->setLabel(pht('Priority Name')),
544 id(new PhabricatorStringExportField())
545 ->setKey('subtype')
546 ->setLabel('Subtype'),
547 id(new PhabricatorURIExportField())
548 ->setKey('uri')
549 ->setLabel(pht('URI')),
550 id(new PhabricatorStringExportField())
551 ->setKey('title')
552 ->setLabel(pht('Title')),
553 id(new PhabricatorStringExportField())
554 ->setKey('description')
555 ->setLabel(pht('Description')),
556 );
557
558 if (ManiphestTaskPoints::getIsEnabled()) {
559 $fields[] = id(new PhabricatorDoubleExportField())
560 ->setKey('points')
561 ->setLabel('Points');
562 }
563
564 return $fields;
565 }
566
567 protected function newExportData(array $tasks) {
568 $viewer = $this->requireViewer();
569
570 $phids = array();
571 foreach ($tasks as $task) {
572 $phids[] = $task->getAuthorPHID();
573 $phids[] = $task->getOwnerPHID();
574 $phids[] = $task->getCloserPHID();
575 }
576 $handles = $viewer->loadHandles($phids);
577
578 $export = array();
579 foreach ($tasks as $task) {
580
581 $author_phid = $task->getAuthorPHID();
582 if ($author_phid) {
583 $author_name = $handles[$author_phid]->getName();
584 } else {
585 $author_name = null;
586 }
587
588 $owner_phid = $task->getOwnerPHID();
589 if ($owner_phid) {
590 $owner_name = $handles[$owner_phid]->getName();
591 } else {
592 $owner_name = null;
593 }
594
595 $closer_phid = $task->getCloserPHID();
596 if ($closer_phid) {
597 $closer_name = $handles[$closer_phid]->getName();
598 } else {
599 $closer_name = null;
600 }
601
602 $status_value = $task->getStatus();
603 $status_name = ManiphestTaskStatus::getTaskStatusName($status_value);
604
605 $priority_value = $task->getPriority();
606 $priority_name = ManiphestTaskPriority::getTaskPriorityName(
607 $priority_value);
608
609 $export[] = array(
610 'monogram' => $task->getMonogram(),
611 'authorPHID' => $author_phid,
612 'author' => $author_name,
613 'ownerPHID' => $owner_phid,
614 'owner' => $owner_name,
615 'status' => $status_value,
616 'statusName' => $status_name,
617 'priority' => $priority_value,
618 'priorityName' => $priority_name,
619 'points' => $task->getPoints(),
620 'subtype' => $task->getSubtype(),
621 'title' => $task->getTitle(),
622 'uri' => PhabricatorEnv::getProductionURI($task->getURI()),
623 'description' => $task->getDescription(),
624 'dateClosed' => $task->getClosedEpoch(),
625 'closerPHID' => $closer_phid,
626 'closer' => $closer_name,
627 );
628 }
629
630 return $export;
631 }
632}