@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 266 lines 7.6 kB view raw
1<?php 2 3final class PhabricatorObjectListQuery extends Phobject { 4 5 private $viewer; 6 private $objectList; 7 private $allowedTypes = array(); 8 private $allowPartialResults; 9 private $suffixes = array(); 10 11 public function setAllowPartialResults($allow_partial_results) { 12 $this->allowPartialResults = $allow_partial_results; 13 return $this; 14 } 15 16 public function getAllowPartialResults() { 17 return $this->allowPartialResults; 18 } 19 20 public function setSuffixes(array $suffixes) { 21 $this->suffixes = $suffixes; 22 return $this; 23 } 24 25 public function getSuffixes() { 26 return $this->suffixes; 27 } 28 29 public function setAllowedTypes(array $allowed_types) { 30 $this->allowedTypes = $allowed_types; 31 return $this; 32 } 33 34 public function getAllowedTypes() { 35 return $this->allowedTypes; 36 } 37 38 public function setViewer(PhabricatorUser $viewer) { 39 $this->viewer = $viewer; 40 return $this; 41 } 42 43 public function getViewer() { 44 return $this->viewer; 45 } 46 47 public function setObjectList($object_list) { 48 $this->objectList = $object_list; 49 return $this; 50 } 51 52 public function getObjectList() { 53 return $this->objectList; 54 } 55 56 public function execute() { 57 $names = $this->getObjectList(); 58 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); 62 63 // Split the list on commas. 64 $names = explode(',', $names); 65 66 // Trim and remove empty tokens. 67 foreach ($names as $key => $name) { 68 $name = trim($name); 69 70 if (!strlen($name)) { 71 unset($names[$key]); 72 continue; 73 } 74 75 $names[$key] = $name; 76 } 77 78 // Remove duplicates. 79 $names = array_unique($names); 80 81 $name_map = array(); 82 foreach ($names as $name) { 83 $parts = explode(' ', $name); 84 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; 90 } else { 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; 96 } 97 } 98 } 99 100 // If we're parsing with suffixes, strip them off any tokens and keep 101 // track of them for later. 102 $suffixes = $this->getSuffixes(); 103 $suffix_map = array(); 104 if ($suffixes) { 105 $suffixes = array_fuse($suffixes); 106 $stripped_map = array(); 107 foreach ($name_map as $key => $name) { 108 $found_suffixes = array(); 109 do { 110 $has_any_suffix = false; 111 foreach ($suffixes as $suffix) { 112 if (!$this->hasSuffix($name, $suffix)) { 113 continue; 114 } 115 116 $key = $this->stripSuffix($key, $suffix); 117 $name = $this->stripSuffix($name, $suffix); 118 119 $found_suffixes[] = $suffix; 120 $has_any_suffix = true; 121 break; 122 } 123 } while ($has_any_suffix); 124 125 $stripped_map[$key] = $name; 126 $suffix_map[$key] = array_fuse($found_suffixes); 127 } 128 $name_map = $stripped_map; 129 } 130 131 $objects = $this->loadObjects(array_keys($name_map)); 132 133 $types = array(); 134 foreach ($objects as $name => $object) { 135 $types[phid_get_type($object->getPHID())][] = $name; 136 } 137 138 $invalid = array(); 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; 144 } 145 } 146 } 147 $invalid = array_mergev($invalid); 148 149 $missing = array(); 150 foreach ($name_map as $key => $name) { 151 if (empty($objects[$key])) { 152 $missing[$key] = $name; 153 } 154 } 155 156 $result = array_unique(mpull($objects, 'getPHID')); 157 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 163 // contains them. 164 foreach ($missing as $key => $value) { 165 if (isset($allowed[phid_get_type($value)])) { 166 unset($missing[$key]); 167 $result[$key] = $value; 168 } 169 } 170 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. 175 176 if (!$this->getAllowPartialResults()) { 177 if ($invalid && $missing) { 178 throw new DifferentialFieldParseException( 179 pht( 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( 186 pht( 187 'The objects you have listed include objects of the wrong '. 188 'type (%s).', 189 implode(', ', $invalid))); 190 } else if ($missing) { 191 throw new DifferentialFieldParseException( 192 pht( 193 'The objects you have listed include objects which do not '. 194 'exist (%s).', 195 implode(', ', $missing))); 196 } 197 } 198 199 if ($suffixes) { 200 foreach ($result as $key => $phid) { 201 $result[$key] = array( 202 'phid' => $phid, 203 'suffixes' => idx($suffix_map, $key, array()), 204 ); 205 } 206 } 207 208 return array_values($result); 209 } 210 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()) 216 ->withNames($names); 217 218 $query->execute(); 219 $objects = $query->getNamedResults(); 220 221 $results = array(); 222 foreach ($names as $key => $name) { 223 if (isset($objects[$name])) { 224 $results[$name] = $objects[$name]; 225 unset($names[$key]); 226 } 227 } 228 229 if ($names) { 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) 235 ->execute(); 236 237 $user_map = array(); 238 foreach ($users as $user) { 239 $user_map[phutil_utf8_strtolower($user->getUsername())] = $user; 240 } 241 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]; 246 unset($names[$key]); 247 } 248 } 249 } 250 251 return $results; 252 } 253 254 private function hasSuffix($key, $suffix) { 255 return (substr($key, -strlen($suffix)) === $suffix); 256 } 257 258 private function stripSuffix($key, $suffix) { 259 if ($this->hasSuffix($key, $suffix)) { 260 return substr($key, 0, -strlen($suffix)); 261 } 262 263 return $key; 264 } 265 266}