3 final class PhabricatorObjectListQuery
extends Phobject
{
7 private $allowedTypes = array();
8 private $allowPartialResults;
9 private $suffixes = array();
11 public function setAllowPartialResults($allow_partial_results) {
12 $this->allowPartialResults
= $allow_partial_results;
16 public function getAllowPartialResults() {
17 return $this->allowPartialResults
;
20 public function setSuffixes(array $suffixes) {
21 $this->suffixes
= $suffixes;
25 public function getSuffixes() {
26 return $this->suffixes
;
29 public function setAllowedTypes(array $allowed_types) {
30 $this->allowedTypes
= $allowed_types;
34 public function getAllowedTypes() {
35 return $this->allowedTypes
;
38 public function setViewer(PhabricatorUser
$viewer) {
39 $this->viewer
= $viewer;
43 public function getViewer() {
47 public function setObjectList($object_list) {
48 $this->objectList
= $object_list;
52 public function getObjectList() {
53 return $this->objectList
;
56 public function execute() {
57 $names = $this->getObjectList();
59 // First, normalize any internal whitespace so we don't get weird results
60 // if linebreaks hit in weird spots.
61 $names = preg_replace('/\s+/', ' ', $names);
63 // Split the list on commas.
64 $names = explode(',', $names);
66 // Trim and remove empty tokens.
67 foreach ($names as $key => $name) {
79 $names = array_unique($names);
82 foreach ($names as $name) {
83 $parts = explode(' ', $name);
85 // If this looks like a monogram, ignore anything after the first token.
86 // This allows us to parse "O123 Package Name" as though it was "O123",
87 // which we can look up.
88 if (preg_match('/^[A-Z]\d+\z/', $parts[0])) {
89 $name_map[$parts[0]] = $name;
91 // For anything else, split it on spaces and use each token as a
92 // value. This means "alincoln htaft", separated with a space instead
93 // of with a comma, is two different users.
94 foreach ($parts as $part) {
95 $name_map[$part] = $part;
100 // If we're parsing with suffixes, strip them off any tokens and keep
101 // track of them for later.
102 $suffixes = $this->getSuffixes();
104 $suffixes = array_fuse($suffixes);
105 $suffix_map = array();
106 $stripped_map = array();
107 foreach ($name_map as $key => $name) {
108 $found_suffixes = array();
110 $has_any_suffix = false;
111 foreach ($suffixes as $suffix) {
112 if (!$this->hasSuffix($name, $suffix)) {
116 $key = $this->stripSuffix($key, $suffix);
117 $name = $this->stripSuffix($name, $suffix);
119 $found_suffixes[] = $suffix;
120 $has_any_suffix = true;
123 } while ($has_any_suffix);
125 $stripped_map[$key] = $name;
126 $suffix_map[$key] = array_fuse($found_suffixes);
128 $name_map = $stripped_map;
131 $objects = $this->loadObjects(array_keys($name_map));
134 foreach ($objects as $name => $object) {
135 $types[phid_get_type($object->getPHID())][] = $name;
139 if ($this->getAllowedTypes()) {
140 $allowed = array_fuse($this->getAllowedTypes());
141 foreach ($types as $type => $names_of_type) {
142 if (empty($allowed[$type])) {
143 $invalid[] = $names_of_type;
147 $invalid = array_mergev($invalid);
150 foreach ($name_map as $key => $name) {
151 if (empty($objects[$key])) {
152 $missing[$key] = $name;
156 $result = array_unique(mpull($objects, 'getPHID'));
158 // For values which are plain PHIDs of allowed types, let them through
159 // unchecked. This can happen occur if subscribers or reviewers which the
160 // revision author does not have permission to see are added by Herald
161 // rules. Any actual edits will be checked later: users are not allowed
162 // to add new reviewers they can't see, but they can touch a field which
164 foreach ($missing as $key => $value) {
165 if (isset($allowed[phid_get_type($value)])) {
166 unset($missing[$key]);
167 $result[$key] = $value;
171 // NOTE: We could couple this less tightly with Differential, but it is
172 // currently the only thing that uses it, and we'd have to add a lot of
173 // extra API to loosen this. It's not clear that this will be useful
174 // elsewhere any time soon, so let's cross that bridge when we come to it.
176 if (!$this->getAllowPartialResults()) {
177 if ($invalid && $missing) {
178 throw new DifferentialFieldParseException(
180 'The objects you have listed include objects of the wrong '.
181 'type (%s) and objects which do not exist (%s).',
182 implode(', ', $invalid),
183 implode(', ', $missing)));
184 } else if ($invalid) {
185 throw new DifferentialFieldParseException(
187 'The objects you have listed include objects of the wrong '.
189 implode(', ', $invalid)));
190 } else if ($missing) {
191 throw new DifferentialFieldParseException(
193 'The objects you have listed include objects which do not '.
195 implode(', ', $missing)));
200 foreach ($result as $key => $phid) {
201 $result[$key] = array(
203 'suffixes' => idx($suffix_map, $key, array()),
208 return array_values($result);
211 private function loadObjects($names) {
212 // First, try to load visible objects using monograms. This covers most
213 // object types, but does not cover users or user email addresses.
214 $query = id(new PhabricatorObjectQuery())
215 ->setViewer($this->getViewer())
219 $objects = $query->getNamedResults();
222 foreach ($names as $key => $name) {
223 if (isset($objects[$name])) {
224 $results[$name] = $objects[$name];
230 // We still have some symbols we haven't been able to resolve, so try to
231 // load users. Try by username first...
232 $users = id(new PhabricatorPeopleQuery())
233 ->setViewer($this->getViewer())
234 ->withUsernames($names)
238 foreach ($users as $user) {
239 $user_map[phutil_utf8_strtolower($user->getUsername())] = $user;
242 foreach ($names as $key => $name) {
243 $normal_name = phutil_utf8_strtolower($name);
244 if (isset($user_map[$normal_name])) {
245 $results[$name] = $user_map[$normal_name];
254 private function hasSuffix($key, $suffix) {
255 return (substr($key, -strlen($suffix)) === $suffix);
258 private function stripSuffix($key, $suffix) {
259 if ($this->hasSuffix($key, $suffix)) {
260 return substr($key, 0, -strlen($suffix));