Path: blob/master/src/applications/differential/query/DifferentialRevisionQuery.php
12262 views
<?php12/**3* @task config Query Configuration4* @task exec Query Execution5* @task internal Internals6*/7final class DifferentialRevisionQuery8extends PhabricatorCursorPagedPolicyAwareQuery {910private $authors = array();11private $draftAuthors = array();12private $ccs = array();13private $reviewers = array();14private $revIDs = array();15private $commitHashes = array();16private $phids = array();17private $responsibles = array();18private $branches = array();19private $repositoryPHIDs;20private $updatedEpochMin;21private $updatedEpochMax;22private $statuses;23private $isOpen;24private $createdEpochMin;25private $createdEpochMax;26private $noReviewers;27private $paths;2829const ORDER_MODIFIED = 'order-modified';30const ORDER_CREATED = 'order-created';3132private $needActiveDiffs = false;33private $needDiffIDs = false;34private $needCommitPHIDs = false;35private $needHashes = false;36private $needReviewers = false;37private $needReviewerAuthority;38private $needDrafts;39private $needFlags;404142/* -( Query Configuration )------------------------------------------------ */4344/**45* Find revisions affecting one or more items in a list of paths.46*47* @param list<string> List of file paths.48* @return this49* @task config50*/51public function withPaths(array $paths) {52$this->paths = $paths;53return $this;54}5556/**57* Filter results to revisions authored by one of the given PHIDs. Calling58* this function will clear anything set by previous calls to59* @{method:withAuthors}.60*61* @param array List of PHIDs of authors62* @return this63* @task config64*/65public function withAuthors(array $author_phids) {66$this->authors = $author_phids;67return $this;68}6970/**71* Filter results to revisions which CC one of the listed people. Calling this72* function will clear anything set by previous calls to @{method:withCCs}.73*74* @param array List of PHIDs of subscribers.75* @return this76* @task config77*/78public function withCCs(array $cc_phids) {79$this->ccs = $cc_phids;80return $this;81}8283/**84* Filter results to revisions that have one of the provided PHIDs as85* reviewers. Calling this function will clear anything set by previous calls86* to @{method:withReviewers}.87*88* @param array List of PHIDs of reviewers89* @return this90* @task config91*/92public function withReviewers(array $reviewer_phids) {93if ($reviewer_phids === array()) {94throw new Exception(95pht(96'Empty "withReviewers()" constraint is invalid. Provide one or '.97'more values, or remove the constraint.'));98}99100$with_none = false;101102foreach ($reviewer_phids as $key => $phid) {103switch ($phid) {104case DifferentialNoReviewersDatasource::FUNCTION_TOKEN:105$with_none = true;106unset($reviewer_phids[$key]);107break;108default:109break;110}111}112113$this->noReviewers = $with_none;114if ($reviewer_phids) {115$this->reviewers = array_values($reviewer_phids);116}117118return $this;119}120121/**122* Filter results to revisions that have one of the provided commit hashes.123* Calling this function will clear anything set by previous calls to124* @{method:withCommitHashes}.125*126* @param array List of pairs <Class127* ArcanistDifferentialRevisionHash::HASH_$type constant,128* hash>129* @return this130* @task config131*/132public function withCommitHashes(array $commit_hashes) {133$this->commitHashes = $commit_hashes;134return $this;135}136137public function withStatuses(array $statuses) {138$this->statuses = $statuses;139return $this;140}141142public function withIsOpen($is_open) {143$this->isOpen = $is_open;144return $this;145}146147148/**149* Filter results to revisions on given branches.150*151* @param list List of branch names.152* @return this153* @task config154*/155public function withBranches(array $branches) {156$this->branches = $branches;157return $this;158}159160161/**162* Filter results to only return revisions whose ids are in the given set.163*164* @param array List of revision ids165* @return this166* @task config167*/168public function withIDs(array $ids) {169$this->revIDs = $ids;170return $this;171}172173174/**175* Filter results to only return revisions whose PHIDs are in the given set.176*177* @param array List of revision PHIDs178* @return this179* @task config180*/181public function withPHIDs(array $phids) {182$this->phids = $phids;183return $this;184}185186187/**188* Given a set of users, filter results to return only revisions they are189* responsible for (i.e., they are either authors or reviewers).190*191* @param array List of user PHIDs.192* @return this193* @task config194*/195public function withResponsibleUsers(array $responsible_phids) {196$this->responsibles = $responsible_phids;197return $this;198}199200201public function withRepositoryPHIDs(array $repository_phids) {202$this->repositoryPHIDs = $repository_phids;203return $this;204}205206public function withUpdatedEpochBetween($min, $max) {207$this->updatedEpochMin = $min;208$this->updatedEpochMax = $max;209return $this;210}211212public function withCreatedEpochBetween($min, $max) {213$this->createdEpochMin = $min;214$this->createdEpochMax = $max;215return $this;216}217218219/**220* Set whether or not the query should load the active diff for each221* revision.222*223* @param bool True to load and attach diffs.224* @return this225* @task config226*/227public function needActiveDiffs($need_active_diffs) {228$this->needActiveDiffs = $need_active_diffs;229return $this;230}231232233/**234* Set whether or not the query should load the associated commit PHIDs for235* each revision.236*237* @param bool True to load and attach diffs.238* @return this239* @task config240*/241public function needCommitPHIDs($need_commit_phids) {242$this->needCommitPHIDs = $need_commit_phids;243return $this;244}245246247/**248* Set whether or not the query should load associated diff IDs for each249* revision.250*251* @param bool True to load and attach diff IDs.252* @return this253* @task config254*/255public function needDiffIDs($need_diff_ids) {256$this->needDiffIDs = $need_diff_ids;257return $this;258}259260261/**262* Set whether or not the query should load associated commit hashes for each263* revision.264*265* @param bool True to load and attach commit hashes.266* @return this267* @task config268*/269public function needHashes($need_hashes) {270$this->needHashes = $need_hashes;271return $this;272}273274275/**276* Set whether or not the query should load associated reviewers.277*278* @param bool True to load and attach reviewers.279* @return this280* @task config281*/282public function needReviewers($need_reviewers) {283$this->needReviewers = $need_reviewers;284return $this;285}286287288/**289* Request information about the viewer's authority to act on behalf of each290* reviewer. In particular, they have authority to act on behalf of projects291* they are a member of.292*293* @param bool True to load and attach authority.294* @return this295* @task config296*/297public function needReviewerAuthority($need_reviewer_authority) {298$this->needReviewerAuthority = $need_reviewer_authority;299return $this;300}301302public function needFlags($need_flags) {303$this->needFlags = $need_flags;304return $this;305}306307public function needDrafts($need_drafts) {308$this->needDrafts = $need_drafts;309return $this;310}311312313/* -( Query Execution )---------------------------------------------------- */314315316public function newResultObject() {317return new DifferentialRevision();318}319320321/**322* Execute the query as configured, returning matching323* @{class:DifferentialRevision} objects.324*325* @return list List of matching DifferentialRevision objects.326* @task exec327*/328protected function loadPage() {329$data = $this->loadData();330$data = $this->didLoadRawRows($data);331$table = $this->newResultObject();332return $table->loadAllFromArray($data);333}334335protected function willFilterPage(array $revisions) {336$viewer = $this->getViewer();337338$repository_phids = mpull($revisions, 'getRepositoryPHID');339$repository_phids = array_filter($repository_phids);340341$repositories = array();342if ($repository_phids) {343$repositories = id(new PhabricatorRepositoryQuery())344->setViewer($this->getViewer())345->withPHIDs($repository_phids)346->execute();347$repositories = mpull($repositories, null, 'getPHID');348}349350// If a revision is associated with a repository:351//352// - the viewer must be able to see the repository; or353// - the viewer must have an automatic view capability.354//355// In the latter case, we'll load the revision but not load the repository.356357$can_view = PhabricatorPolicyCapability::CAN_VIEW;358foreach ($revisions as $key => $revision) {359$repo_phid = $revision->getRepositoryPHID();360if (!$repo_phid) {361// The revision has no associated repository. Attach `null` and move on.362$revision->attachRepository(null);363continue;364}365366$repository = idx($repositories, $repo_phid);367if ($repository) {368// The revision has an associated repository, and the viewer can see369// it. Attach it and move on.370$revision->attachRepository($repository);371continue;372}373374if ($revision->hasAutomaticCapability($can_view, $viewer)) {375// The revision has an associated repository which the viewer can not376// see, but the viewer has an automatic capability on this revision.377// Load the revision without attaching a repository.378$revision->attachRepository(null);379continue;380}381382if ($this->getViewer()->isOmnipotent()) {383// The viewer is omnipotent. Allow the revision to load even without384// a repository.385$revision->attachRepository(null);386continue;387}388389// The revision has an associated repository, and the viewer can't see390// it, and the viewer has no special capabilities. Filter out this391// revision.392$this->didRejectResult($revision);393unset($revisions[$key]);394}395396if (!$revisions) {397return array();398}399400$table = new DifferentialRevision();401$conn_r = $table->establishConnection('r');402403if ($this->needCommitPHIDs) {404$this->loadCommitPHIDs($revisions);405}406407$need_active = $this->needActiveDiffs;408$need_ids = $need_active || $this->needDiffIDs;409410if ($need_ids) {411$this->loadDiffIDs($conn_r, $revisions);412}413414if ($need_active) {415$this->loadActiveDiffs($conn_r, $revisions);416}417418if ($this->needHashes) {419$this->loadHashes($conn_r, $revisions);420}421422if ($this->needReviewers || $this->needReviewerAuthority) {423$this->loadReviewers($conn_r, $revisions);424}425426return $revisions;427}428429protected function didFilterPage(array $revisions) {430$viewer = $this->getViewer();431432if ($this->needFlags) {433$flags = id(new PhabricatorFlagQuery())434->setViewer($viewer)435->withOwnerPHIDs(array($viewer->getPHID()))436->withObjectPHIDs(mpull($revisions, 'getPHID'))437->execute();438$flags = mpull($flags, null, 'getObjectPHID');439foreach ($revisions as $revision) {440$revision->attachFlag(441$viewer,442idx($flags, $revision->getPHID()));443}444}445446if ($this->needDrafts) {447PhabricatorDraftEngine::attachDrafts(448$viewer,449$revisions);450}451452return $revisions;453}454455private function loadData() {456$table = $this->newResultObject();457$conn = $table->establishConnection('r');458459$selects = array();460461// NOTE: If the query includes "responsiblePHIDs", we execute it as a462// UNION of revisions they own and revisions they're reviewing. This has463// much better performance than doing it with JOIN/WHERE.464if ($this->responsibles) {465$basic_authors = $this->authors;466$basic_reviewers = $this->reviewers;467468try {469// Build the query where the responsible users are authors.470$this->authors = array_merge($basic_authors, $this->responsibles);471472$this->reviewers = $basic_reviewers;473$selects[] = $this->buildSelectStatement($conn);474475// Build the query where the responsible users are reviewers, or476// projects they are members of are reviewers.477$this->authors = $basic_authors;478$this->reviewers = array_merge($basic_reviewers, $this->responsibles);479$selects[] = $this->buildSelectStatement($conn);480481// Put everything back like it was.482$this->authors = $basic_authors;483$this->reviewers = $basic_reviewers;484} catch (Exception $ex) {485$this->authors = $basic_authors;486$this->reviewers = $basic_reviewers;487throw $ex;488}489} else {490$selects[] = $this->buildSelectStatement($conn);491}492493if (count($selects) > 1) {494$unions = null;495foreach ($selects as $select) {496if (!$unions) {497$unions = $select;498continue;499}500501$unions = qsprintf(502$conn,503'%Q UNION DISTINCT %Q',504$unions,505$select);506}507508$query = qsprintf(509$conn,510'%Q %Q %Q',511$unions,512$this->buildOrderClause($conn, true),513$this->buildLimitClause($conn));514} else {515$query = head($selects);516}517518return queryfx_all($conn, '%Q', $query);519}520521private function buildSelectStatement(AphrontDatabaseConnection $conn_r) {522$table = new DifferentialRevision();523524$select = $this->buildSelectClause($conn_r);525526$from = qsprintf(527$conn_r,528'FROM %T r',529$table->getTableName());530531$joins = $this->buildJoinsClause($conn_r);532$where = $this->buildWhereClause($conn_r);533$group_by = $this->buildGroupClause($conn_r);534$having = $this->buildHavingClause($conn_r);535536$order_by = $this->buildOrderClause($conn_r);537538$limit = $this->buildLimitClause($conn_r);539540return qsprintf(541$conn_r,542'(%Q %Q %Q %Q %Q %Q %Q %Q)',543$select,544$from,545$joins,546$where,547$group_by,548$having,549$order_by,550$limit);551}552553554/* -( Internals )---------------------------------------------------------- */555556557/**558* @task internal559*/560private function buildJoinsClause(AphrontDatabaseConnection $conn) {561$joins = array();562563if ($this->paths) {564$path_table = new DifferentialAffectedPath();565$joins[] = qsprintf(566$conn,567'JOIN %R paths ON paths.revisionID = r.id',568$path_table);569}570571if ($this->commitHashes) {572$joins[] = qsprintf(573$conn,574'JOIN %T hash_rel ON hash_rel.revisionID = r.id',575ArcanistDifferentialRevisionHash::TABLE_NAME);576}577578if ($this->ccs) {579$joins[] = qsprintf(580$conn,581'JOIN %T e_ccs ON e_ccs.src = r.phid '.582'AND e_ccs.type = %s '.583'AND e_ccs.dst in (%Ls)',584PhabricatorEdgeConfig::TABLE_NAME_EDGE,585PhabricatorObjectHasSubscriberEdgeType::EDGECONST,586$this->ccs);587}588589if ($this->reviewers) {590$joins[] = qsprintf(591$conn,592'LEFT JOIN %T reviewer ON reviewer.revisionPHID = r.phid593AND reviewer.reviewerStatus != %s594AND reviewer.reviewerPHID in (%Ls)',595id(new DifferentialReviewer())->getTableName(),596DifferentialReviewerStatus::STATUS_RESIGNED,597$this->reviewers);598}599600if ($this->noReviewers) {601$joins[] = qsprintf(602$conn,603'LEFT JOIN %T no_reviewer ON no_reviewer.revisionPHID = r.phid604AND no_reviewer.reviewerStatus != %s',605id(new DifferentialReviewer())->getTableName(),606DifferentialReviewerStatus::STATUS_RESIGNED);607}608609if ($this->draftAuthors) {610$joins[] = qsprintf(611$conn,612'JOIN %T has_draft ON has_draft.srcPHID = r.phid613AND has_draft.type = %s614AND has_draft.dstPHID IN (%Ls)',615PhabricatorEdgeConfig::TABLE_NAME_EDGE,616PhabricatorObjectHasDraftEdgeType::EDGECONST,617$this->draftAuthors);618}619620$joins[] = $this->buildJoinClauseParts($conn);621622return $this->formatJoinClause($conn, $joins);623}624625626/**627* @task internal628*/629protected function buildWhereClause(AphrontDatabaseConnection $conn) {630$viewer = $this->getViewer();631$where = array();632633if ($this->paths !== null) {634$paths = $this->paths;635636$path_map = id(new DiffusionPathIDQuery($paths))637->loadPathIDs();638639if (!$path_map) {640// If none of the paths have entries in the PathID table, we can not641// possibly find any revisions affecting them.642throw new PhabricatorEmptyQueryException();643}644645$where[] = qsprintf(646$conn,647'paths.pathID IN (%Ld)',648array_fuse($path_map));649650// If we have repository PHIDs, additionally constrain this query to651// try to help MySQL execute it efficiently.652if ($this->repositoryPHIDs !== null) {653$repositories = id(new PhabricatorRepositoryQuery())654->setViewer($viewer)655->setParentQuery($this)656->withPHIDs($this->repositoryPHIDs)657->execute();658659if (!$repositories) {660throw new PhabricatorEmptyQueryException();661}662663$repository_ids = mpull($repositories, 'getID');664665$where[] = qsprintf(666$conn,667'paths.repositoryID IN (%Ld)',668$repository_ids);669}670}671672if ($this->authors) {673$where[] = qsprintf(674$conn,675'r.authorPHID IN (%Ls)',676$this->authors);677}678679if ($this->revIDs) {680$where[] = qsprintf(681$conn,682'r.id IN (%Ld)',683$this->revIDs);684}685686if ($this->repositoryPHIDs) {687$where[] = qsprintf(688$conn,689'r.repositoryPHID IN (%Ls)',690$this->repositoryPHIDs);691}692693if ($this->commitHashes) {694$hash_clauses = array();695foreach ($this->commitHashes as $info) {696list($type, $hash) = $info;697$hash_clauses[] = qsprintf(698$conn,699'(hash_rel.type = %s AND hash_rel.hash = %s)',700$type,701$hash);702}703$hash_clauses = qsprintf($conn, '%LO', $hash_clauses);704$where[] = $hash_clauses;705}706707if ($this->phids) {708$where[] = qsprintf(709$conn,710'r.phid IN (%Ls)',711$this->phids);712}713714if ($this->branches) {715$where[] = qsprintf(716$conn,717'r.branchName in (%Ls)',718$this->branches);719}720721if ($this->updatedEpochMin !== null) {722$where[] = qsprintf(723$conn,724'r.dateModified >= %d',725$this->updatedEpochMin);726}727728if ($this->updatedEpochMax !== null) {729$where[] = qsprintf(730$conn,731'r.dateModified <= %d',732$this->updatedEpochMax);733}734735if ($this->createdEpochMin !== null) {736$where[] = qsprintf(737$conn,738'r.dateCreated >= %d',739$this->createdEpochMin);740}741742if ($this->createdEpochMax !== null) {743$where[] = qsprintf(744$conn,745'r.dateCreated <= %d',746$this->createdEpochMax);747}748749if ($this->statuses !== null) {750$where[] = qsprintf(751$conn,752'r.status in (%Ls)',753$this->statuses);754}755756if ($this->isOpen !== null) {757if ($this->isOpen) {758$statuses = DifferentialLegacyQuery::getModernValues(759DifferentialLegacyQuery::STATUS_OPEN);760} else {761$statuses = DifferentialLegacyQuery::getModernValues(762DifferentialLegacyQuery::STATUS_CLOSED);763}764$where[] = qsprintf(765$conn,766'r.status in (%Ls)',767$statuses);768}769770$reviewer_subclauses = array();771772if ($this->noReviewers) {773$reviewer_subclauses[] = qsprintf(774$conn,775'no_reviewer.reviewerPHID IS NULL');776}777778if ($this->reviewers) {779$reviewer_subclauses[] = qsprintf(780$conn,781'reviewer.reviewerPHID IS NOT NULL');782}783784if ($reviewer_subclauses) {785$where[] = qsprintf($conn, '%LO', $reviewer_subclauses);786}787788$where[] = $this->buildWhereClauseParts($conn);789790return $this->formatWhereClause($conn, $where);791}792793794/**795* @task internal796*/797protected function shouldGroupQueryResultRows() {798799if ($this->paths) {800// (If we have exactly one repository and exactly one path, we don't801// technically need to group, but it's simpler to always group.)802return true;803}804805if (count($this->ccs) > 1) {806return true;807}808809if (count($this->reviewers) > 1) {810return true;811}812813if (count($this->commitHashes) > 1) {814return true;815}816817if ($this->noReviewers) {818return true;819}820821return parent::shouldGroupQueryResultRows();822}823824public function getBuiltinOrders() {825$orders = parent::getBuiltinOrders() + array(826'updated' => array(827'vector' => array('updated', 'id'),828'name' => pht('Date Updated (Latest First)'),829'aliases' => array(self::ORDER_MODIFIED),830),831'outdated' => array(832'vector' => array('-updated', '-id'),833'name' => pht('Date Updated (Oldest First)'),834),835);836837// Alias the "newest" builtin to the historical key for it.838$orders['newest']['aliases'][] = self::ORDER_CREATED;839840return $orders;841}842843protected function getDefaultOrderVector() {844return array('updated', 'id');845}846847public function getOrderableColumns() {848return array(849'updated' => array(850'table' => $this->getPrimaryTableAlias(),851'column' => 'dateModified',852'type' => 'int',853),854) + parent::getOrderableColumns();855}856857protected function newPagingMapFromPartialObject($object) {858return array(859'id' => (int)$object->getID(),860'updated' => (int)$object->getDateModified(),861);862}863864private function loadCommitPHIDs(array $revisions) {865assert_instances_of($revisions, 'DifferentialRevision');866867if (!$revisions) {868return;869}870871$revisions = mpull($revisions, null, 'getPHID');872873$edge_query = id(new PhabricatorEdgeQuery())874->withSourcePHIDs(array_keys($revisions))875->withEdgeTypes(876array(877DifferentialRevisionHasCommitEdgeType::EDGECONST,878));879$edge_query->execute();880881foreach ($revisions as $phid => $revision) {882$commit_phids = $edge_query->getDestinationPHIDs(array($phid));883$revision->attachCommitPHIDs($commit_phids);884}885}886887private function loadDiffIDs($conn_r, array $revisions) {888assert_instances_of($revisions, 'DifferentialRevision');889890$diff_table = new DifferentialDiff();891892$diff_ids = queryfx_all(893$conn_r,894'SELECT revisionID, id FROM %T WHERE revisionID IN (%Ld)895ORDER BY id DESC',896$diff_table->getTableName(),897mpull($revisions, 'getID'));898$diff_ids = igroup($diff_ids, 'revisionID');899900foreach ($revisions as $revision) {901$ids = idx($diff_ids, $revision->getID(), array());902$ids = ipull($ids, 'id');903$revision->attachDiffIDs($ids);904}905}906907private function loadActiveDiffs($conn_r, array $revisions) {908assert_instances_of($revisions, 'DifferentialRevision');909910$diff_table = new DifferentialDiff();911912$load_ids = array();913foreach ($revisions as $revision) {914$diffs = $revision->getDiffIDs();915if ($diffs) {916$load_ids[] = max($diffs);917}918}919920$active_diffs = array();921if ($load_ids) {922$active_diffs = $diff_table->loadAllWhere(923'id IN (%Ld)',924$load_ids);925}926927$active_diffs = mpull($active_diffs, null, 'getRevisionID');928foreach ($revisions as $revision) {929$revision->attachActiveDiff(idx($active_diffs, $revision->getID()));930}931}932933private function loadHashes(934AphrontDatabaseConnection $conn_r,935array $revisions) {936assert_instances_of($revisions, 'DifferentialRevision');937938$data = queryfx_all(939$conn_r,940'SELECT * FROM %T WHERE revisionID IN (%Ld)',941'differential_revisionhash',942mpull($revisions, 'getID'));943944$data = igroup($data, 'revisionID');945foreach ($revisions as $revision) {946$hashes = idx($data, $revision->getID(), array());947$list = array();948foreach ($hashes as $hash) {949$list[] = array($hash['type'], $hash['hash']);950}951$revision->attachHashes($list);952}953}954955private function loadReviewers(956AphrontDatabaseConnection $conn,957array $revisions) {958959assert_instances_of($revisions, 'DifferentialRevision');960961$reviewer_table = new DifferentialReviewer();962$reviewer_rows = queryfx_all(963$conn,964'SELECT * FROM %T WHERE revisionPHID IN (%Ls)965ORDER BY id ASC',966$reviewer_table->getTableName(),967mpull($revisions, 'getPHID'));968$reviewer_list = $reviewer_table->loadAllFromArray($reviewer_rows);969$reviewer_map = mgroup($reviewer_list, 'getRevisionPHID');970971foreach ($reviewer_map as $key => $reviewers) {972$reviewer_map[$key] = mpull($reviewers, null, 'getReviewerPHID');973}974975$viewer = $this->getViewer();976$viewer_phid = $viewer->getPHID();977978$allow_key = 'differential.allow-self-accept';979$allow_self = PhabricatorEnv::getEnvConfig($allow_key);980981// Figure out which of these reviewers the viewer has authority to act as.982if ($this->needReviewerAuthority && $viewer_phid) {983$authority = $this->loadReviewerAuthority(984$revisions,985$reviewer_map,986$allow_self);987}988989foreach ($revisions as $revision) {990$reviewers = idx($reviewer_map, $revision->getPHID(), array());991foreach ($reviewers as $reviewer_phid => $reviewer) {992if ($this->needReviewerAuthority) {993if (!$viewer_phid) {994// Logged-out users never have authority.995$has_authority = false;996} else if ((!$allow_self) &&997($revision->getAuthorPHID() == $viewer_phid)) {998// The author can never have authority unless we allow self-accept.999$has_authority = false;1000} else {1001// Otherwise, look up whether the viewer has authority.1002$has_authority = isset($authority[$reviewer_phid]);1003}10041005$reviewer->attachAuthority($viewer, $has_authority);1006}10071008$reviewers[$reviewer_phid] = $reviewer;1009}10101011$revision->attachReviewers($reviewers);1012}1013}10141015private function loadReviewerAuthority(1016array $revisions,1017array $reviewers,1018$allow_self) {10191020$revision_map = mpull($revisions, null, 'getPHID');1021$viewer_phid = $this->getViewer()->getPHID();10221023// Find all the project/package reviewers which the user may have authority1024// over.1025$project_phids = array();1026$package_phids = array();1027$project_type = PhabricatorProjectProjectPHIDType::TYPECONST;1028$package_type = PhabricatorOwnersPackagePHIDType::TYPECONST;10291030foreach ($reviewers as $revision_phid => $reviewer_list) {1031if (!$allow_self) {1032if ($revision_map[$revision_phid]->getAuthorPHID() == $viewer_phid) {1033// If self-review isn't permitted, the user will never have1034// authority over projects on revisions they authored because you1035// can't accept your own revisions, so we don't need to load any1036// data about these reviewers.1037continue;1038}1039}10401041foreach ($reviewer_list as $reviewer_phid => $reviewer) {1042$phid_type = phid_get_type($reviewer_phid);1043if ($phid_type == $project_type) {1044$project_phids[] = $reviewer_phid;1045}1046if ($phid_type == $package_type) {1047$package_phids[] = $reviewer_phid;1048}1049}1050}10511052// The viewer has authority over themselves.1053$user_authority = array_fuse(array($viewer_phid));10541055// And over any projects they are a member of.1056$project_authority = array();1057if ($project_phids) {1058$project_authority = id(new PhabricatorProjectQuery())1059->setViewer($this->getViewer())1060->withPHIDs($project_phids)1061->withMemberPHIDs(array($viewer_phid))1062->execute();1063$project_authority = mpull($project_authority, 'getPHID');1064$project_authority = array_fuse($project_authority);1065}10661067// And over any packages they own.1068$package_authority = array();1069if ($package_phids) {1070$package_authority = id(new PhabricatorOwnersPackageQuery())1071->setViewer($this->getViewer())1072->withPHIDs($package_phids)1073->withAuthorityPHIDs(array($viewer_phid))1074->execute();1075$package_authority = mpull($package_authority, 'getPHID');1076$package_authority = array_fuse($package_authority);1077}10781079return $user_authority + $project_authority + $package_authority;1080}10811082public function getQueryApplicationClass() {1083return 'PhabricatorDifferentialApplication';1084}10851086protected function getPrimaryTableAlias() {1087return 'r';1088}10891090}109110921093