[ Index ] |
PHP Cross Reference of Phabricator |
[Summary view] [Print] [Text view]
1 <?php 2 3 /** 4 * Query tasks by specific criteria. This class uses the higher-performance 5 * but less-general Maniphest indexes to satisfy queries. 6 */ 7 final class ManiphestTaskQuery extends PhabricatorCursorPagedPolicyAwareQuery { 8 9 private $taskIDs = array(); 10 private $taskPHIDs = array(); 11 private $authorPHIDs = array(); 12 private $ownerPHIDs = array(); 13 private $includeUnowned = null; 14 private $projectPHIDs = array(); 15 private $xprojectPHIDs = array(); 16 private $subscriberPHIDs = array(); 17 private $anyProjectPHIDs = array(); 18 private $anyUserProjectPHIDs = array(); 19 private $includeNoProject = null; 20 private $dateCreatedAfter; 21 private $dateCreatedBefore; 22 private $dateModifiedAfter; 23 private $dateModifiedBefore; 24 25 private $fullTextSearch = ''; 26 27 private $status = 'status-any'; 28 const STATUS_ANY = 'status-any'; 29 const STATUS_OPEN = 'status-open'; 30 const STATUS_CLOSED = 'status-closed'; 31 const STATUS_RESOLVED = 'status-resolved'; 32 const STATUS_WONTFIX = 'status-wontfix'; 33 const STATUS_INVALID = 'status-invalid'; 34 const STATUS_SPITE = 'status-spite'; 35 const STATUS_DUPLICATE = 'status-duplicate'; 36 37 private $statuses; 38 private $priorities; 39 40 private $groupBy = 'group-none'; 41 const GROUP_NONE = 'group-none'; 42 const GROUP_PRIORITY = 'group-priority'; 43 const GROUP_OWNER = 'group-owner'; 44 const GROUP_STATUS = 'group-status'; 45 const GROUP_PROJECT = 'group-project'; 46 47 private $orderBy = 'order-modified'; 48 const ORDER_PRIORITY = 'order-priority'; 49 const ORDER_CREATED = 'order-created'; 50 const ORDER_MODIFIED = 'order-modified'; 51 const ORDER_TITLE = 'order-title'; 52 53 const DEFAULT_PAGE_SIZE = 1000; 54 55 public function withAuthors(array $authors) { 56 $this->authorPHIDs = $authors; 57 return $this; 58 } 59 60 public function withIDs(array $ids) { 61 $this->taskIDs = $ids; 62 return $this; 63 } 64 65 public function withPHIDs(array $phids) { 66 $this->taskPHIDs = $phids; 67 return $this; 68 } 69 70 public function withOwners(array $owners) { 71 $this->includeUnowned = false; 72 foreach ($owners as $k => $phid) { 73 if ($phid == ManiphestTaskOwner::OWNER_UP_FOR_GRABS || $phid === null) { 74 $this->includeUnowned = true; 75 unset($owners[$k]); 76 break; 77 } 78 } 79 $this->ownerPHIDs = $owners; 80 return $this; 81 } 82 83 public function withAllProjects(array $projects) { 84 $this->includeNoProject = false; 85 foreach ($projects as $k => $phid) { 86 if ($phid == ManiphestTaskOwner::PROJECT_NO_PROJECT) { 87 $this->includeNoProject = true; 88 unset($projects[$k]); 89 } 90 } 91 $this->projectPHIDs = $projects; 92 return $this; 93 } 94 95 /** 96 * Add an additional "all projects" constraint to existing filters. 97 * 98 * This is used by boards to supplement queries. 99 * 100 * @param list<phid> List of project PHIDs to add to any existing constraint. 101 * @return this 102 */ 103 public function addWithAllProjects(array $projects) { 104 if ($this->projectPHIDs === null) { 105 $this->projectPHIDs = array(); 106 } 107 108 return $this->withAllProjects(array_merge($this->projectPHIDs, $projects)); 109 } 110 111 public function withoutProjects(array $projects) { 112 $this->xprojectPHIDs = $projects; 113 return $this; 114 } 115 116 public function withStatus($status) { 117 $this->status = $status; 118 return $this; 119 } 120 121 public function withStatuses(array $statuses) { 122 $this->statuses = $statuses; 123 return $this; 124 } 125 126 public function withPriorities(array $priorities) { 127 $this->priorities = $priorities; 128 return $this; 129 } 130 131 public function withSubscribers(array $subscribers) { 132 $this->subscriberPHIDs = $subscribers; 133 return $this; 134 } 135 136 public function withFullTextSearch($fulltext_search) { 137 $this->fullTextSearch = $fulltext_search; 138 return $this; 139 } 140 141 public function setGroupBy($group) { 142 $this->groupBy = $group; 143 return $this; 144 } 145 146 public function setOrderBy($order) { 147 $this->orderBy = $order; 148 return $this; 149 } 150 151 public function withAnyProjects(array $projects) { 152 $this->anyProjectPHIDs = $projects; 153 return $this; 154 } 155 156 public function withAnyUserProjects(array $users) { 157 $this->anyUserProjectPHIDs = $users; 158 return $this; 159 } 160 161 public function withDateCreatedBefore($date_created_before) { 162 $this->dateCreatedBefore = $date_created_before; 163 return $this; 164 } 165 166 public function withDateCreatedAfter($date_created_after) { 167 $this->dateCreatedAfter = $date_created_after; 168 return $this; 169 } 170 171 public function withDateModifiedBefore($date_modified_before) { 172 $this->dateModifiedBefore = $date_modified_before; 173 return $this; 174 } 175 176 public function withDateModifiedAfter($date_modified_after) { 177 $this->dateModifiedAfter = $date_modified_after; 178 return $this; 179 } 180 181 public function loadPage() { 182 // TODO: (T603) It is possible for a user to find the PHID of a project 183 // they can't see, then query for tasks in that project and deduce the 184 // identity of unknown/invisible projects. Before we allow the user to 185 // execute a project-based PHID query, we should verify that they 186 // can see the project. 187 188 $task_dao = new ManiphestTask(); 189 $conn = $task_dao->establishConnection('r'); 190 191 $where = array(); 192 $where[] = $this->buildTaskIDsWhereClause($conn); 193 $where[] = $this->buildTaskPHIDsWhereClause($conn); 194 $where[] = $this->buildStatusWhereClause($conn); 195 $where[] = $this->buildStatusesWhereClause($conn); 196 $where[] = $this->buildPrioritiesWhereClause($conn); 197 $where[] = $this->buildAuthorWhereClause($conn); 198 $where[] = $this->buildOwnerWhereClause($conn); 199 $where[] = $this->buildSubscriberWhereClause($conn); 200 $where[] = $this->buildProjectWhereClause($conn); 201 $where[] = $this->buildAnyProjectWhereClause($conn); 202 $where[] = $this->buildAnyUserProjectWhereClause($conn); 203 $where[] = $this->buildXProjectWhereClause($conn); 204 $where[] = $this->buildFullTextWhereClause($conn); 205 206 if ($this->dateCreatedAfter) { 207 $where[] = qsprintf( 208 $conn, 209 'task.dateCreated >= %d', 210 $this->dateCreatedAfter); 211 } 212 213 if ($this->dateCreatedBefore) { 214 $where[] = qsprintf( 215 $conn, 216 'task.dateCreated <= %d', 217 $this->dateCreatedBefore); 218 } 219 220 if ($this->dateModifiedAfter) { 221 $where[] = qsprintf( 222 $conn, 223 'task.dateModified >= %d', 224 $this->dateModifiedAfter); 225 } 226 227 if ($this->dateModifiedBefore) { 228 $where[] = qsprintf( 229 $conn, 230 'task.dateModified <= %d', 231 $this->dateModifiedBefore); 232 } 233 234 $where[] = $this->buildPagingClause($conn); 235 236 $where = $this->formatWhereClause($where); 237 238 $having = ''; 239 $count = ''; 240 241 if (count($this->projectPHIDs) > 1) { 242 // We want to treat the query as an intersection query, not a union 243 // query. We sum the project count and require it be the same as the 244 // number of projects we're searching for. 245 246 $count = ', COUNT(project.dst) projectCount'; 247 $having = qsprintf( 248 $conn, 249 'HAVING projectCount = %d', 250 count($this->projectPHIDs)); 251 } 252 253 $order = $this->buildCustomOrderClause($conn); 254 255 // TODO: Clean up this nonstandardness. 256 if (!$this->getLimit()) { 257 $this->setLimit(self::DEFAULT_PAGE_SIZE); 258 } 259 260 $group_column = ''; 261 switch ($this->groupBy) { 262 case self::GROUP_PROJECT: 263 $group_column = qsprintf( 264 $conn, 265 ', projectGroupName.indexedObjectPHID projectGroupPHID'); 266 break; 267 } 268 269 $rows = queryfx_all( 270 $conn, 271 'SELECT task.* %Q %Q FROM %T task %Q %Q %Q %Q %Q %Q', 272 $count, 273 $group_column, 274 $task_dao->getTableName(), 275 $this->buildJoinsClause($conn), 276 $where, 277 $this->buildGroupClause($conn), 278 $having, 279 $order, 280 $this->buildLimitClause($conn)); 281 282 switch ($this->groupBy) { 283 case self::GROUP_PROJECT: 284 $data = ipull($rows, null, 'id'); 285 break; 286 default: 287 $data = $rows; 288 break; 289 } 290 291 $tasks = $task_dao->loadAllFromArray($data); 292 293 switch ($this->groupBy) { 294 case self::GROUP_PROJECT: 295 $results = array(); 296 foreach ($rows as $row) { 297 $task = clone $tasks[$row['id']]; 298 $task->attachGroupByProjectPHID($row['projectGroupPHID']); 299 $results[] = $task; 300 } 301 $tasks = $results; 302 break; 303 } 304 305 return $tasks; 306 } 307 308 protected function willFilterPage(array $tasks) { 309 if ($this->groupBy == self::GROUP_PROJECT) { 310 // We should only return project groups which the user can actually see. 311 $project_phids = mpull($tasks, 'getGroupByProjectPHID'); 312 $projects = id(new PhabricatorProjectQuery()) 313 ->setViewer($this->getViewer()) 314 ->withPHIDs($project_phids) 315 ->execute(); 316 $projects = mpull($projects, null, 'getPHID'); 317 318 foreach ($tasks as $key => $task) { 319 if (!$task->getGroupByProjectPHID()) { 320 // This task is either not in any projects, or only in projects 321 // which we're ignoring because they're being queried for explicitly. 322 continue; 323 } 324 325 if (empty($projects[$task->getGroupByProjectPHID()])) { 326 unset($tasks[$key]); 327 } 328 } 329 } 330 331 return $tasks; 332 } 333 334 protected function didFilterPage(array $tasks) { 335 // TODO: Eventually, we should make this optional and introduce a 336 // needProjectPHIDs() method, but for now there's a lot of code which 337 // assumes the data is always populated. 338 339 $edge_query = id(new PhabricatorEdgeQuery()) 340 ->withSourcePHIDs(mpull($tasks, 'getPHID')) 341 ->withEdgeTypes( 342 array( 343 PhabricatorProjectObjectHasProjectEdgeType::EDGECONST, 344 )); 345 $edge_query->execute(); 346 347 foreach ($tasks as $task) { 348 $phids = $edge_query->getDestinationPHIDs(array($task->getPHID())); 349 $task->attachProjectPHIDs($phids); 350 } 351 352 return $tasks; 353 } 354 355 private function buildTaskIDsWhereClause(AphrontDatabaseConnection $conn) { 356 if (!$this->taskIDs) { 357 return null; 358 } 359 360 return qsprintf( 361 $conn, 362 'id in (%Ld)', 363 $this->taskIDs); 364 } 365 366 private function buildTaskPHIDsWhereClause(AphrontDatabaseConnection $conn) { 367 if (!$this->taskPHIDs) { 368 return null; 369 } 370 371 return qsprintf( 372 $conn, 373 'phid in (%Ls)', 374 $this->taskPHIDs); 375 } 376 377 private function buildStatusWhereClause(AphrontDatabaseConnection $conn) { 378 static $map = array( 379 self::STATUS_RESOLVED => ManiphestTaskStatus::STATUS_CLOSED_RESOLVED, 380 self::STATUS_WONTFIX => ManiphestTaskStatus::STATUS_CLOSED_WONTFIX, 381 self::STATUS_INVALID => ManiphestTaskStatus::STATUS_CLOSED_INVALID, 382 self::STATUS_SPITE => ManiphestTaskStatus::STATUS_CLOSED_SPITE, 383 self::STATUS_DUPLICATE => ManiphestTaskStatus::STATUS_CLOSED_DUPLICATE, 384 ); 385 386 switch ($this->status) { 387 case self::STATUS_ANY: 388 return null; 389 case self::STATUS_OPEN: 390 return qsprintf( 391 $conn, 392 'status IN (%Ls)', 393 ManiphestTaskStatus::getOpenStatusConstants()); 394 case self::STATUS_CLOSED: 395 return qsprintf( 396 $conn, 397 'status IN (%Ls)', 398 ManiphestTaskStatus::getClosedStatusConstants()); 399 default: 400 $constant = idx($map, $this->status); 401 if (!$constant) { 402 throw new Exception("Unknown status query '{$this->status}'!"); 403 } 404 return qsprintf( 405 $conn, 406 'status = %s', 407 $constant); 408 } 409 } 410 411 private function buildStatusesWhereClause(AphrontDatabaseConnection $conn) { 412 if ($this->statuses) { 413 return qsprintf( 414 $conn, 415 'status IN (%Ls)', 416 $this->statuses); 417 } 418 return null; 419 } 420 421 private function buildPrioritiesWhereClause(AphrontDatabaseConnection $conn) { 422 if ($this->priorities) { 423 return qsprintf( 424 $conn, 425 'priority IN (%Ld)', 426 $this->priorities); 427 } 428 429 return null; 430 } 431 432 private function buildAuthorWhereClause(AphrontDatabaseConnection $conn) { 433 if (!$this->authorPHIDs) { 434 return null; 435 } 436 437 return qsprintf( 438 $conn, 439 'authorPHID in (%Ls)', 440 $this->authorPHIDs); 441 } 442 443 private function buildOwnerWhereClause(AphrontDatabaseConnection $conn) { 444 if (!$this->ownerPHIDs) { 445 if ($this->includeUnowned === null) { 446 return null; 447 } else if ($this->includeUnowned) { 448 return qsprintf( 449 $conn, 450 'ownerPHID IS NULL'); 451 } else { 452 return qsprintf( 453 $conn, 454 'ownerPHID IS NOT NULL'); 455 } 456 } 457 458 if ($this->includeUnowned) { 459 return qsprintf( 460 $conn, 461 'ownerPHID IN (%Ls) OR ownerPHID IS NULL', 462 $this->ownerPHIDs); 463 } else { 464 return qsprintf( 465 $conn, 466 'ownerPHID IN (%Ls)', 467 $this->ownerPHIDs); 468 } 469 } 470 471 private function buildFullTextWhereClause(AphrontDatabaseConnection $conn) { 472 if (!strlen($this->fullTextSearch)) { 473 return null; 474 } 475 476 // In doing a fulltext search, we first find all the PHIDs that match the 477 // fulltext search, and then use that to limit the rest of the search 478 $fulltext_query = id(new PhabricatorSavedQuery()) 479 ->setEngineClassName('PhabricatorSearchApplicationSearchEngine') 480 ->setParameter('query', $this->fullTextSearch); 481 482 // NOTE: Setting this to something larger than 2^53 will raise errors in 483 // ElasticSearch, and billions of results won't fit in memory anyway. 484 $fulltext_query->setParameter('limit', 100000); 485 $fulltext_query->setParameter('type', ManiphestTaskPHIDType::TYPECONST); 486 487 $engine = PhabricatorSearchEngineSelector::newSelector()->newEngine(); 488 $fulltext_results = $engine->executeSearch($fulltext_query); 489 490 if (empty($fulltext_results)) { 491 $fulltext_results = array(null); 492 } 493 494 return qsprintf( 495 $conn, 496 'phid IN (%Ls)', 497 $fulltext_results); 498 } 499 500 private function buildSubscriberWhereClause(AphrontDatabaseConnection $conn) { 501 if (!$this->subscriberPHIDs) { 502 return null; 503 } 504 505 return qsprintf( 506 $conn, 507 'subscriber.subscriberPHID IN (%Ls)', 508 $this->subscriberPHIDs); 509 } 510 511 private function buildProjectWhereClause(AphrontDatabaseConnection $conn) { 512 if (!$this->projectPHIDs && !$this->includeNoProject) { 513 return null; 514 } 515 516 $parts = array(); 517 if ($this->projectPHIDs) { 518 $parts[] = qsprintf( 519 $conn, 520 'project.dst in (%Ls)', 521 $this->projectPHIDs); 522 } 523 if ($this->includeNoProject) { 524 $parts[] = qsprintf( 525 $conn, 526 'project.dst IS NULL'); 527 } 528 529 return '('.implode(') OR (', $parts).')'; 530 } 531 532 private function buildAnyProjectWhereClause(AphrontDatabaseConnection $conn) { 533 if (!$this->anyProjectPHIDs) { 534 return null; 535 } 536 537 return qsprintf( 538 $conn, 539 'anyproject.dst IN (%Ls)', 540 $this->anyProjectPHIDs); 541 } 542 543 private function buildAnyUserProjectWhereClause( 544 AphrontDatabaseConnection $conn) { 545 if (!$this->anyUserProjectPHIDs) { 546 return null; 547 } 548 549 $projects = id(new PhabricatorProjectQuery()) 550 ->setViewer($this->getViewer()) 551 ->withMemberPHIDs($this->anyUserProjectPHIDs) 552 ->execute(); 553 $any_user_project_phids = mpull($projects, 'getPHID'); 554 if (!$any_user_project_phids) { 555 throw new PhabricatorEmptyQueryException(); 556 } 557 558 return qsprintf( 559 $conn, 560 'anyproject.dst IN (%Ls)', 561 $any_user_project_phids); 562 } 563 564 private function buildXProjectWhereClause(AphrontDatabaseConnection $conn) { 565 if (!$this->xprojectPHIDs) { 566 return null; 567 } 568 569 return qsprintf( 570 $conn, 571 'xproject.dst IS NULL'); 572 } 573 574 private function buildCustomOrderClause(AphrontDatabaseConnection $conn) { 575 $reverse = ($this->getBeforeID() xor $this->getReversePaging()); 576 577 $order = array(); 578 579 switch ($this->groupBy) { 580 case self::GROUP_NONE: 581 break; 582 case self::GROUP_PRIORITY: 583 $order[] = 'priority'; 584 break; 585 case self::GROUP_OWNER: 586 $order[] = 'ownerOrdering'; 587 break; 588 case self::GROUP_STATUS: 589 $order[] = 'status'; 590 break; 591 case self::GROUP_PROJECT: 592 $order[] = '<group.project>'; 593 break; 594 default: 595 throw new Exception("Unknown group query '{$this->groupBy}'!"); 596 } 597 598 $app_order = $this->buildApplicationSearchOrders($conn, $reverse); 599 600 if (!$app_order) { 601 switch ($this->orderBy) { 602 case self::ORDER_PRIORITY: 603 $order[] = 'priority'; 604 $order[] = 'subpriority'; 605 $order[] = 'dateModified'; 606 break; 607 case self::ORDER_CREATED: 608 $order[] = 'id'; 609 break; 610 case self::ORDER_MODIFIED: 611 $order[] = 'dateModified'; 612 break; 613 case self::ORDER_TITLE: 614 $order[] = 'title'; 615 break; 616 default: 617 throw new Exception("Unknown order query '{$this->orderBy}'!"); 618 } 619 } 620 621 $order = array_unique($order); 622 623 if (empty($order) && empty($app_order)) { 624 return null; 625 } 626 627 foreach ($order as $k => $column) { 628 switch ($column) { 629 case 'subpriority': 630 case 'ownerOrdering': 631 case 'title': 632 if ($reverse) { 633 $order[$k] = "task.{$column} DESC"; 634 } else { 635 $order[$k] = "task.{$column} ASC"; 636 } 637 break; 638 case '<group.project>': 639 // Put "No Project" at the end of the list. 640 if ($reverse) { 641 $order[$k] = 642 'projectGroupName.indexedObjectName IS NULL DESC, '. 643 'projectGroupName.indexedObjectName DESC'; 644 } else { 645 $order[$k] = 646 'projectGroupName.indexedObjectName IS NULL ASC, '. 647 'projectGroupName.indexedObjectName ASC'; 648 } 649 break; 650 default: 651 if ($reverse) { 652 $order[$k] = "task.{$column} ASC"; 653 } else { 654 $order[$k] = "task.{$column} DESC"; 655 } 656 break; 657 } 658 } 659 660 if ($app_order) { 661 foreach ($app_order as $order_by) { 662 $order[] = $order_by; 663 } 664 665 if ($reverse) { 666 $order[] = 'task.id ASC'; 667 } else { 668 $order[] = 'task.id DESC'; 669 } 670 } 671 672 return 'ORDER BY '.implode(', ', $order); 673 } 674 675 private function buildJoinsClause(AphrontDatabaseConnection $conn_r) { 676 $edge_table = PhabricatorEdgeConfig::TABLE_NAME_EDGE; 677 678 $joins = array(); 679 680 if ($this->projectPHIDs || $this->includeNoProject) { 681 $joins[] = qsprintf( 682 $conn_r, 683 '%Q JOIN %T project ON project.src = task.phid 684 AND project.type = %d', 685 ($this->includeNoProject ? 'LEFT' : ''), 686 $edge_table, 687 PhabricatorProjectObjectHasProjectEdgeType::EDGECONST); 688 } 689 690 if ($this->anyProjectPHIDs || $this->anyUserProjectPHIDs) { 691 $joins[] = qsprintf( 692 $conn_r, 693 'JOIN %T anyproject ON anyproject.src = task.phid 694 AND anyproject.type = %d', 695 $edge_table, 696 PhabricatorProjectObjectHasProjectEdgeType::EDGECONST); 697 } 698 699 if ($this->xprojectPHIDs) { 700 $joins[] = qsprintf( 701 $conn_r, 702 'LEFT JOIN %T xproject ON xproject.src = task.phid 703 AND xproject.type = %d 704 AND xproject.dst IN (%Ls)', 705 $edge_table, 706 PhabricatorProjectObjectHasProjectEdgeType::EDGECONST, 707 $this->xprojectPHIDs); 708 } 709 710 if ($this->subscriberPHIDs) { 711 $subscriber_dao = new ManiphestTaskSubscriber(); 712 $joins[] = qsprintf( 713 $conn_r, 714 'JOIN %T subscriber ON subscriber.taskPHID = task.phid', 715 $subscriber_dao->getTableName()); 716 } 717 718 switch ($this->groupBy) { 719 case self::GROUP_PROJECT: 720 $ignore_group_phids = $this->getIgnoreGroupedProjectPHIDs(); 721 if ($ignore_group_phids) { 722 $joins[] = qsprintf( 723 $conn_r, 724 'LEFT JOIN %T projectGroup ON task.phid = projectGroup.src 725 AND projectGroup.type = %d 726 AND projectGroup.dst NOT IN (%Ls)', 727 $edge_table, 728 PhabricatorProjectObjectHasProjectEdgeType::EDGECONST, 729 $ignore_group_phids); 730 } else { 731 $joins[] = qsprintf( 732 $conn_r, 733 'LEFT JOIN %T projectGroup ON task.phid = projectGroup.src 734 AND projectGroup.type = %d', 735 $edge_table, 736 PhabricatorProjectObjectHasProjectEdgeType::EDGECONST); 737 } 738 $joins[] = qsprintf( 739 $conn_r, 740 'LEFT JOIN %T projectGroupName 741 ON projectGroup.dst = projectGroupName.indexedObjectPHID', 742 id(new ManiphestNameIndex())->getTableName()); 743 break; 744 } 745 746 $joins[] = $this->buildApplicationSearchJoinClause($conn_r); 747 748 return implode(' ', $joins); 749 } 750 751 private function buildGroupClause(AphrontDatabaseConnection $conn_r) { 752 $joined_multiple_rows = (count($this->projectPHIDs) > 1) || 753 (count($this->anyProjectPHIDs) > 1) || 754 ($this->getApplicationSearchMayJoinMultipleRows()); 755 756 $joined_project_name = ($this->groupBy == self::GROUP_PROJECT); 757 758 // If we're joining multiple rows, we need to group the results by the 759 // task IDs. 760 if ($joined_multiple_rows) { 761 if ($joined_project_name) { 762 return 'GROUP BY task.phid, projectGroup.dst'; 763 } else { 764 return 'GROUP BY task.phid'; 765 } 766 } else { 767 return ''; 768 } 769 } 770 771 /** 772 * Return project PHIDs which we should ignore when grouping tasks by 773 * project. For example, if a user issues a query like: 774 * 775 * Tasks in all projects: Frontend, Bugs 776 * 777 * ...then we don't show "Frontend" or "Bugs" groups in the result set, since 778 * they're meaningless as all results are in both groups. 779 * 780 * Similarly, for queries like: 781 * 782 * Tasks in any projects: Public Relations 783 * 784 * ...we ignore the single project, as every result is in that project. (In 785 * the case that there are several "any" projects, we do not ignore them.) 786 * 787 * @return list<phid> Project PHIDs which should be ignored in query 788 * construction. 789 */ 790 private function getIgnoreGroupedProjectPHIDs() { 791 $phids = array(); 792 793 if ($this->projectPHIDs) { 794 $phids[] = $this->projectPHIDs; 795 } 796 797 if (count($this->anyProjectPHIDs) == 1) { 798 $phids[] = $this->anyProjectPHIDs; 799 } 800 801 // Maybe we should also exclude the "excludeProjectPHIDs"? It won't 802 // impact the results, but we might end up with a better query plan. 803 // Investigate this on real data? This is likely very rare. 804 805 return array_mergev($phids); 806 } 807 808 private function loadCursorObject($id) { 809 $results = id(new ManiphestTaskQuery()) 810 ->setViewer($this->getPagingViewer()) 811 ->withIDs(array((int)$id)) 812 ->execute(); 813 return head($results); 814 } 815 816 protected function getPagingValue($result) { 817 $id = $result->getID(); 818 819 switch ($this->groupBy) { 820 case self::GROUP_NONE: 821 return $id; 822 case self::GROUP_PRIORITY: 823 return $id.'.'.$result->getPriority(); 824 case self::GROUP_OWNER: 825 return rtrim($id.'.'.$result->getOwnerPHID(), '.'); 826 case self::GROUP_STATUS: 827 return $id.'.'.$result->getStatus(); 828 case self::GROUP_PROJECT: 829 return rtrim($id.'.'.$result->getGroupByProjectPHID(), '.'); 830 default: 831 throw new Exception("Unknown group query '{$this->groupBy}'!"); 832 } 833 } 834 835 protected function buildPagingClause(AphrontDatabaseConnection $conn_r) { 836 $default = parent::buildPagingClause($conn_r); 837 838 $before_id = $this->getBeforeID(); 839 $after_id = $this->getAfterID(); 840 841 if (!$before_id && !$after_id) { 842 return $default; 843 } 844 845 $cursor_id = nonempty($before_id, $after_id); 846 $cursor_parts = explode('.', $cursor_id, 2); 847 $task_id = $cursor_parts[0]; 848 $group_id = idx($cursor_parts, 1); 849 850 $cursor = $this->loadCursorObject($task_id); 851 if (!$cursor) { 852 return null; 853 } 854 855 $columns = array(); 856 857 switch ($this->groupBy) { 858 case self::GROUP_NONE: 859 break; 860 case self::GROUP_PRIORITY: 861 $columns[] = array( 862 'name' => 'task.priority', 863 'value' => (int)$group_id, 864 'type' => 'int', 865 ); 866 break; 867 case self::GROUP_OWNER: 868 $columns[] = array( 869 'name' => '(task.ownerOrdering IS NULL)', 870 'value' => (int)(strlen($group_id) ? 0 : 1), 871 'type' => 'int', 872 ); 873 if ($group_id) { 874 $paging_users = id(new PhabricatorPeopleQuery()) 875 ->setViewer($this->getViewer()) 876 ->withPHIDs(array($group_id)) 877 ->execute(); 878 if (!$paging_users) { 879 return null; 880 } 881 $columns[] = array( 882 'name' => 'task.ownerOrdering', 883 'value' => head($paging_users)->getUsername(), 884 'type' => 'string', 885 'reverse' => true, 886 ); 887 } 888 break; 889 case self::GROUP_STATUS: 890 $columns[] = array( 891 'name' => 'task.status', 892 'value' => $group_id, 893 'type' => 'string', 894 ); 895 break; 896 case self::GROUP_PROJECT: 897 $columns[] = array( 898 'name' => '(projectGroupName.indexedObjectName IS NULL)', 899 'value' => (int)(strlen($group_id) ? 0 : 1), 900 'type' => 'int', 901 ); 902 if ($group_id) { 903 $paging_projects = id(new PhabricatorProjectQuery()) 904 ->setViewer($this->getViewer()) 905 ->withPHIDs(array($group_id)) 906 ->execute(); 907 if (!$paging_projects) { 908 return null; 909 } 910 $columns[] = array( 911 'name' => 'projectGroupName.indexedObjectName', 912 'value' => head($paging_projects)->getName(), 913 'type' => 'string', 914 'reverse' => true, 915 ); 916 } 917 break; 918 default: 919 throw new Exception("Unknown group query '{$this->groupBy}'!"); 920 } 921 922 $app_columns = $this->buildApplicationSearchPagination($conn_r, $cursor); 923 if ($app_columns) { 924 $columns = array_merge($columns, $app_columns); 925 $columns[] = array( 926 'name' => 'task.id', 927 'value' => (int)$cursor->getID(), 928 'type' => 'int', 929 ); 930 } else { 931 switch ($this->orderBy) { 932 case self::ORDER_PRIORITY: 933 if ($this->groupBy != self::GROUP_PRIORITY) { 934 $columns[] = array( 935 'name' => 'task.priority', 936 'value' => (int)$cursor->getPriority(), 937 'type' => 'int', 938 ); 939 } 940 $columns[] = array( 941 'name' => 'task.subpriority', 942 'value' => (int)$cursor->getSubpriority(), 943 'type' => 'int', 944 'reverse' => true, 945 ); 946 $columns[] = array( 947 'name' => 'task.dateModified', 948 'value' => (int)$cursor->getDateModified(), 949 'type' => 'int', 950 ); 951 break; 952 case self::ORDER_CREATED: 953 $columns[] = array( 954 'name' => 'task.id', 955 'value' => (int)$cursor->getID(), 956 'type' => 'int', 957 ); 958 break; 959 case self::ORDER_MODIFIED: 960 $columns[] = array( 961 'name' => 'task.dateModified', 962 'value' => (int)$cursor->getDateModified(), 963 'type' => 'int', 964 ); 965 break; 966 case self::ORDER_TITLE: 967 $columns[] = array( 968 'name' => 'task.title', 969 'value' => $cursor->getTitle(), 970 'type' => 'string', 971 ); 972 $columns[] = array( 973 'name' => 'task.id', 974 'value' => $cursor->getID(), 975 'type' => 'int', 976 ); 977 break; 978 default: 979 throw new Exception("Unknown order query '{$this->orderBy}'!"); 980 } 981 } 982 983 return $this->buildPagingClauseFromMultipleColumns( 984 $conn_r, 985 $columns, 986 array( 987 'reversed' => (bool)($before_id xor $this->getReversePaging()), 988 )); 989 } 990 991 protected function getApplicationSearchObjectPHIDColumn() { 992 return 'task.phid'; 993 } 994 995 public function getQueryApplicationClass() { 996 return 'PhabricatorManiphestApplication'; 997 } 998 999 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Sun Nov 30 09:20:46 2014 | Cross-referenced by PHPXref 0.7.1 |