@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
at upstream/main 632 lines 19 kB view raw
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}