@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 227 lines 6.0 kB view raw
1<?php 2 3final class PhabricatorObjectQuery 4 extends PhabricatorCursorPagedPolicyAwareQuery { 5 6 private $phids = array(); 7 private $names = array(); 8 private $types; 9 10 private $namedResults; 11 12 public function withPHIDs(array $phids) { 13 $this->phids = $phids; 14 return $this; 15 } 16 17 public function withNames(array $names) { 18 $this->names = $names; 19 return $this; 20 } 21 22 public function withTypes(array $types) { 23 $this->types = $types; 24 return $this; 25 } 26 27 protected function loadPage() { 28 if ($this->namedResults === null) { 29 $this->namedResults = array(); 30 } 31 32 $names = array_unique($this->names); 33 $phids = $this->phids; 34 35 // We allow objects to be named by their PHID in addition to their normal 36 // name so that, e.g., CLI tools which accept object names can also accept 37 // PHIDs and work as users expect. 38 $actually_phids = array(); 39 if ($names) { 40 foreach ($names as $key => $name) { 41 if (!phutil_nonempty_string($name) || !strncmp($name, 'PHID-', 5)) { 42 $actually_phids[] = $name; 43 $phids[] = $name; 44 unset($names[$key]); 45 } 46 } 47 } 48 49 if ($names) { 50 $types = PhabricatorPHIDType::getAllTypes(); 51 if ($this->types) { 52 $types = array_select_keys($types, $this->types); 53 } 54 $name_results = $this->loadObjectsByName($types, $names); 55 } else { 56 $name_results = array(); 57 } 58 59 if ($phids) { 60 $phids = array_unique($phids); 61 62 $phid_types = array(); 63 foreach ($phids as $phid) { 64 $phid_type = phid_get_type($phid); 65 $phid_types[$phid_type] = $phid_type; 66 } 67 68 $types = PhabricatorPHIDType::getTypes($phid_types); 69 if ($this->types) { 70 $types = array_select_keys($types, $this->types); 71 } 72 73 $phid_results = $this->loadObjectsByPHID($types, $phids); 74 } else { 75 $phid_results = array(); 76 } 77 78 foreach ($actually_phids as $phid) { 79 if (isset($phid_results[$phid])) { 80 $name_results[$phid] = $phid_results[$phid]; 81 } 82 } 83 84 $this->namedResults += $name_results; 85 86 return $phid_results + mpull($name_results, null, 'getPHID'); 87 } 88 89 public function getNamedResults() { 90 if ($this->namedResults === null) { 91 throw new PhutilInvalidStateException('execute'); 92 } 93 return $this->namedResults; 94 } 95 96 private function loadObjectsByName(array $types, array $names) { 97 $groups = array(); 98 foreach ($names as $name) { 99 foreach ($types as $type => $type_impl) { 100 if (!$type_impl->canLoadNamedObject($name)) { 101 continue; 102 } 103 $groups[$type][] = $name; 104 break; 105 } 106 } 107 108 $results = array(); 109 foreach ($groups as $type => $group) { 110 $results += $types[$type]->loadNamedObjects($this, $group); 111 } 112 113 return $results; 114 } 115 116 private function loadObjectsByPHID(array $types, array $phids) { 117 $results = array(); 118 119 $groups = array(); 120 foreach ($phids as $phid) { 121 $type = phid_get_type($phid); 122 $groups[$type][] = $phid; 123 } 124 125 $in_flight = $this->getPHIDsInFlight(); 126 foreach ($groups as $type => $group) { 127 // We check the workspace for each group, because some groups may trigger 128 // other groups to load (for example, transactions load their objects). 129 $workspace = $this->getObjectsFromWorkspace($group); 130 131 foreach ($group as $key => $phid) { 132 if (isset($workspace[$phid])) { 133 $results[$phid] = $workspace[$phid]; 134 unset($group[$key]); 135 } 136 } 137 138 if (!$group) { 139 continue; 140 } 141 142 // Don't try to load PHIDs which are already "in flight"; this prevents 143 // us from recursing indefinitely if policy checks or edges form a loop. 144 // We will decline to load the corresponding objects. 145 foreach ($group as $key => $phid) { 146 if (isset($in_flight[$phid])) { 147 unset($group[$key]); 148 } 149 } 150 151 if ($group && isset($types[$type])) { 152 $this->putPHIDsInFlight($group); 153 $objects = $types[$type]->loadObjects($this, $group); 154 155 $map = mpull($objects, null, 'getPHID'); 156 $this->putObjectsInWorkspace($map); 157 $results += $map; 158 } 159 } 160 161 return $results; 162 } 163 164 protected function didFilterResults(array $filtered) { 165 foreach ($this->namedResults as $name => $result) { 166 if (isset($filtered[$result->getPHID()])) { 167 unset($this->namedResults[$name]); 168 } 169 } 170 } 171 172 /** 173 * This query disables policy filtering if the only required capability is 174 * the view capability. 175 * 176 * The view capability is always checked in the subqueries, so we do not need 177 * to re-filter results. For any other set of required capabilities, we do. 178 */ 179 protected function shouldDisablePolicyFiltering() { 180 $view_capability = PhabricatorPolicyCapability::CAN_VIEW; 181 if ($this->getRequiredCapabilities() === array($view_capability)) { 182 return true; 183 } 184 return false; 185 } 186 187 public function getQueryApplicationClass() { 188 return null; 189 } 190 191 192 /** 193 * Select invalid or restricted PHIDs from a list. 194 * 195 * PHIDs are invalid if their objects do not exist or can not be seen by the 196 * viewer. This method is generally used to validate that PHIDs affected by 197 * a transaction are valid. 198 * 199 * @param PhabricatorUser $viewer Viewer. 200 * @param list<string> $phids List of ostensibly valid PHIDs. 201 * @return list<string> List of invalid or restricted PHIDs. 202 */ 203 public static function loadInvalidPHIDsForViewer( 204 PhabricatorUser $viewer, 205 array $phids) { 206 207 if (!$phids) { 208 return array(); 209 } 210 211 $objects = id(new PhabricatorObjectQuery()) 212 ->setViewer($viewer) 213 ->withPHIDs($phids) 214 ->execute(); 215 $objects = mpull($objects, null, 'getPHID'); 216 217 $invalid = array(); 218 foreach ($phids as $phid) { 219 if (empty($objects[$phid])) { 220 $invalid[] = $phid; 221 } 222 } 223 224 return $invalid; 225 } 226 227}