@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
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}