diff --git a/src/applications/metamta/query/PhabricatorMetaMTAMemberQuery.php b/src/applications/metamta/query/PhabricatorMetaMTAMemberQuery.php index 8657981836..f7fb0ba27a 100644 --- a/src/applications/metamta/query/PhabricatorMetaMTAMemberQuery.php +++ b/src/applications/metamta/query/PhabricatorMetaMTAMemberQuery.php @@ -1,155 +1,165 @@ viewer = $viewer; return $this; } public function getViewer() { return $this->viewer; } public function withPHIDs(array $phids) { $this->phids = $phids; return $this; } public function execute() { $viewer = $this->getViewer(); $phids = array_fuse($this->phids); $actors = array(); $type_map = array(); foreach ($phids as $phid) { $type_map[phid_get_type($phid)][] = $phid; } // TODO: Generalize this somewhere else. // If we have packages, break them down into their constituent user and // project owners first. Then we'll resolve those and build the packages // back up from the pieces. $package_type = PhabricatorOwnersPackagePHIDType::TYPECONST; $package_phids = idx($type_map, $package_type, array()); unset($type_map[$package_type]); $package_map = array(); if ($package_phids) { $packages = id(new PhabricatorOwnersPackageQuery()) ->setViewer($viewer) ->withPHIDs($package_phids) ->execute(); foreach ($packages as $package) { $package_owners = array(); foreach ($package->getOwners() as $owner) { $owner_phid = $owner->getUserPHID(); $owner_type = phid_get_type($owner_phid); $type_map[$owner_type][] = $owner_phid; $package_owners[] = $owner_phid; } $package_map[$package->getPHID()] = $package_owners; } + + // See T13648. We may have packages that no longer exist or can't be + // loaded (for example, because they have been destroyed). Give them + // empty entries in the map so we return a mapping for all input PHIDs. + + foreach ($package_phids as $package_phid) { + if (!isset($package_map[$package_phid])) { + $package_map[$package_phid] = array(); + } + } } $results = array(); foreach ($type_map as $type => $phids) { switch ($type) { case PhabricatorProjectProjectPHIDType::TYPECONST: // NOTE: We're loading the projects here in order to respect policies. $projects = id(new PhabricatorProjectQuery()) ->setViewer($viewer) ->withPHIDs($phids) ->needMembers(true) ->needWatchers(true) ->execute(); $edge_type = PhabricatorProjectSilencedEdgeType::EDGECONST; $edge_query = id(new PhabricatorEdgeQuery()) ->withSourcePHIDs($phids) ->withEdgeTypes( array( $edge_type, )); $edge_query->execute(); $projects = mpull($projects, null, 'getPHID'); foreach ($phids as $phid) { $project = idx($projects, $phid); if (!$project) { $results[$phid] = array(); continue; } // Recipients are members who haven't silenced the project, plus // watchers. $members = $project->getMemberPHIDs(); $members = array_fuse($members); $watchers = $project->getWatcherPHIDs(); $watchers = array_fuse($watchers); $silenced = $edge_query->getDestinationPHIDs( array($phid), array($edge_type)); $silenced = array_fuse($silenced); $result_map = array_diff_key($members, $silenced); $result_map = $result_map + $watchers; $results[$phid] = array_values($result_map); } break; default: // For other types, just map the PHID to itself without modification. // This allows callers to do less work. foreach ($phids as $phid) { $results[$phid] = array($phid); } break; } } // For any packages, stitch them back together from the resolved users // and projects. if ($package_map) { foreach ($package_map as $package_phid => $owner_phids) { $resolved = array(); foreach ($owner_phids as $owner_phid) { $resolved_phids = idx($results, $owner_phid, array()); foreach ($resolved_phids as $resolved_phid) { $resolved[] = $resolved_phid; } } $results[$package_phid] = $resolved; } } return $results; } /** * Execute the query, merging results into a single list of unique member * PHIDs. */ public function executeExpansion() { return array_unique(array_mergev($this->execute())); } }