@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
3/**
4 * @extends PhabricatorCursorPagedPolicyAwareQuery<PhabricatorPolicy>
5 */
6final class PhabricatorPolicyQuery
7 extends PhabricatorCursorPagedPolicyAwareQuery {
8
9 private $object;
10 private $phids;
11
12 const OBJECT_POLICY_PREFIX = 'obj.';
13
14 public function setObject(PhabricatorPolicyInterface $object) {
15 $this->object = $object;
16 return $this;
17 }
18
19 public function withPHIDs(array $phids) {
20 $this->phids = $phids;
21 return $this;
22 }
23
24 public static function loadPolicies(
25 PhabricatorUser $viewer,
26 PhabricatorPolicyInterface $object) {
27
28 $results = array();
29
30 $map = array();
31 foreach ($object->getCapabilities() as $capability) {
32 $map[$capability] = $object->getPolicy($capability);
33 }
34
35 $policies = id(new PhabricatorPolicyQuery())
36 ->setViewer($viewer)
37 ->withPHIDs($map)
38 ->execute();
39
40 foreach ($map as $capability => $phid) {
41 $results[$capability] = $policies[$phid];
42 }
43
44 return $results;
45 }
46
47 public static function renderPolicyDescriptions(
48 PhabricatorUser $viewer,
49 PhabricatorPolicyInterface $object) {
50
51 $policies = self::loadPolicies($viewer, $object);
52
53 foreach ($policies as $capability => $policy) {
54 $policies[$capability] = $policy->newRef($viewer)
55 ->newCapabilityLink($object, $capability);
56 }
57
58 return $policies;
59 }
60
61 protected function loadPage() {
62 if ($this->object && $this->phids) {
63 throw new Exception(
64 pht(
65 'You can not issue a policy query with both %s and %s.',
66 'setObject()',
67 'setPHIDs()'));
68 } else if ($this->object) {
69 $phids = $this->loadObjectPolicyPHIDs();
70 } else {
71 $phids = $this->phids;
72 }
73
74 $phids = array_fuse($phids);
75
76 $results = array();
77
78 // First, load global policies.
79 foreach (self::getGlobalPolicies() as $phid => $policy) {
80 if (isset($phids[$phid])) {
81 $results[$phid] = $policy;
82 unset($phids[$phid]);
83 }
84 }
85
86 // Now, load object policies.
87 foreach (self::getObjectPolicies($this->object) as $phid => $policy) {
88 if (isset($phids[$phid])) {
89 $results[$phid] = $policy;
90 unset($phids[$phid]);
91 }
92 }
93
94 // If we still need policies, we're going to have to fetch data. Bucket
95 // the remaining policies into rule-based policies and handle-based
96 // policies.
97 if ($phids) {
98 $rule_policies = array();
99 $handle_policies = array();
100 foreach ($phids as $phid) {
101 $phid_type = phid_get_type($phid);
102 if ($phid_type == PhabricatorPolicyPHIDTypePolicy::TYPECONST) {
103 $rule_policies[$phid] = $phid;
104 } else {
105 $handle_policies[$phid] = $phid;
106 }
107 }
108
109 if ($handle_policies) {
110 $handles = id(new PhabricatorHandleQuery())
111 ->setViewer($this->getViewer())
112 ->withPHIDs($handle_policies)
113 ->execute();
114 foreach ($handle_policies as $phid) {
115 $results[$phid] = PhabricatorPolicy::newFromPolicyAndHandle(
116 $phid,
117 $handles[$phid]);
118 }
119 }
120
121 if ($rule_policies) {
122 $rules = id(new PhabricatorPolicy())->loadAllWhere(
123 'phid IN (%Ls)',
124 $rule_policies);
125 $results += mpull($rules, null, 'getPHID');
126 }
127 }
128
129 $results = msort($results, 'getSortKey');
130
131 return $results;
132 }
133
134 public static function isGlobalPolicy($policy) {
135 $global_policies = self::getGlobalPolicies();
136
137 if (isset($global_policies[$policy])) {
138 return true;
139 }
140
141 return false;
142 }
143
144 public static function getGlobalPolicy($policy) {
145 if (!self::isGlobalPolicy($policy)) {
146 throw new Exception(pht("Policy '%s' is not a global policy!", $policy));
147 }
148 return idx(self::getGlobalPolicies(), $policy);
149 }
150
151 private static function getGlobalPolicies() {
152 static $constants = array(
153 PhabricatorPolicies::POLICY_PUBLIC,
154 PhabricatorPolicies::POLICY_USER,
155 PhabricatorPolicies::POLICY_ADMIN,
156 PhabricatorPolicies::POLICY_NOONE,
157 );
158
159 $results = array();
160 foreach ($constants as $constant) {
161 $results[$constant] = id(new PhabricatorPolicy())
162 ->setType(PhabricatorPolicyType::TYPE_GLOBAL)
163 ->setPHID($constant)
164 ->setName(self::getGlobalPolicyName($constant))
165 ->setShortName(self::getGlobalPolicyShortName($constant))
166 ->makeEphemeral();
167 }
168
169 return $results;
170 }
171
172 private static function getGlobalPolicyName($policy) {
173 switch ($policy) {
174 case PhabricatorPolicies::POLICY_PUBLIC:
175 return pht('Public (No Login Required)');
176 case PhabricatorPolicies::POLICY_USER:
177 return pht('All Users');
178 case PhabricatorPolicies::POLICY_ADMIN:
179 return pht('Administrators');
180 case PhabricatorPolicies::POLICY_NOONE:
181 return pht('No One');
182 default:
183 return pht('Unknown Policy');
184 }
185 }
186
187 private static function getGlobalPolicyShortName($policy) {
188 switch ($policy) {
189 case PhabricatorPolicies::POLICY_PUBLIC:
190 return pht('Public');
191 default:
192 return null;
193 }
194 }
195
196 private function loadObjectPolicyPHIDs() {
197 $phids = array();
198 $viewer = $this->getViewer();
199
200 if ($viewer->getPHID()) {
201 $pref_key = PhabricatorPolicyFavoritesSetting::SETTINGKEY;
202
203 $favorite_limit = 10;
204 $default_limit = 5;
205
206 // If possible, show the user's 10 most recently used projects.
207 $favorites = $viewer->getUserSetting($pref_key);
208 if (!is_array($favorites)) {
209 $favorites = array();
210 }
211 $favorite_phids = array_keys($favorites);
212 $favorite_phids = array_slice($favorite_phids, -$favorite_limit);
213
214 if ($favorite_phids) {
215 $projects = id(new PhabricatorProjectQuery())
216 ->setViewer($viewer)
217 ->withPHIDs($favorite_phids)
218 ->withIsMilestone(false)
219 ->setLimit($favorite_limit)
220 ->execute();
221 $projects = mpull($projects, null, 'getPHID');
222 } else {
223 $projects = array();
224 }
225
226 // If we didn't find enough favorites, add some default projects. These
227 // are just arbitrary projects that the viewer is a member of, but may
228 // be useful on smaller installs and for new users until they can use
229 // the control enough time to establish useful favorites.
230 if (count($projects) < $default_limit) {
231 $default_projects = id(new PhabricatorProjectQuery())
232 ->setViewer($viewer)
233 ->withMemberPHIDs(array($viewer->getPHID()))
234 ->withIsMilestone(false)
235 ->withStatuses(
236 array(
237 PhabricatorProjectStatus::STATUS_ACTIVE,
238 ))
239 ->setLimit($default_limit)
240 ->execute();
241 $default_projects = mpull($default_projects, null, 'getPHID');
242 $projects = $projects + $default_projects;
243 $projects = array_slice($projects, 0, $default_limit);
244 }
245
246 foreach ($projects as $project) {
247 $phids[] = $project->getPHID();
248 }
249
250 // Include the "current viewer" policy. This improves consistency, but
251 // is also useful for creating private instances of normally-shared object
252 // types, like repositories.
253 $phids[] = $viewer->getPHID();
254 }
255
256 $capabilities = $this->object->getCapabilities();
257 foreach ($capabilities as $capability) {
258 $policy = $this->object->getPolicy($capability);
259 if (!$policy) {
260 continue;
261 }
262 $phids[] = $policy;
263 }
264
265 // If this install doesn't have "Public" enabled, don't include it as an
266 // option unless the object already has a "Public" policy. In this case we
267 // retain the policy but enforce it as though it was "All Users".
268 $show_public = PhabricatorEnv::getEnvConfig('policy.allow-public');
269 foreach (self::getGlobalPolicies() as $phid => $policy) {
270 if ($phid == PhabricatorPolicies::POLICY_PUBLIC) {
271 if (!$show_public) {
272 continue;
273 }
274 }
275 $phids[] = $phid;
276 }
277
278 foreach (self::getObjectPolicies($this->object) as $phid => $policy) {
279 $phids[] = $phid;
280 }
281
282 return $phids;
283 }
284
285 protected function shouldDisablePolicyFiltering() {
286 // Policy filtering of policies is currently perilous and not required by
287 // the application.
288 return true;
289 }
290
291 public function getQueryApplicationClass() {
292 return PhabricatorPolicyApplication::class;
293 }
294
295 public static function isSpecialPolicy($identifier) {
296 if ($identifier === null) {
297 return true;
298 }
299
300 if (self::isObjectPolicy($identifier)) {
301 return true;
302 }
303
304 if (self::isGlobalPolicy($identifier)) {
305 return true;
306 }
307
308 return false;
309 }
310
311
312/* -( Object Policies )---------------------------------------------------- */
313
314
315 public static function isObjectPolicy($identifier) {
316 $prefix = self::OBJECT_POLICY_PREFIX;
317 return !strncmp($identifier, $prefix, strlen($prefix));
318 }
319
320 public static function getObjectPolicy($identifier) {
321 if (!self::isObjectPolicy($identifier)) {
322 return null;
323 }
324
325 $policies = self::getObjectPolicies(null);
326 return idx($policies, $identifier);
327 }
328
329 public static function getObjectPolicyRule($identifier) {
330 if (!self::isObjectPolicy($identifier)) {
331 return null;
332 }
333
334 $rules = self::getObjectPolicyRules(null);
335 return idx($rules, $identifier);
336 }
337
338 public static function getObjectPolicies($object) {
339 $rule_map = self::getObjectPolicyRules($object);
340
341 $results = array();
342 foreach ($rule_map as $key => $rule) {
343 $results[$key] = id(new PhabricatorPolicy())
344 ->setType(PhabricatorPolicyType::TYPE_OBJECT)
345 ->setPHID($key)
346 ->setIcon($rule->getObjectPolicyIcon())
347 ->setName($rule->getObjectPolicyName())
348 ->setShortName($rule->getObjectPolicyShortName())
349 ->makeEphemeral();
350 }
351
352 return $results;
353 }
354
355 public static function getObjectPolicyRules($object) {
356 $rules = id(new PhutilClassMapQuery())
357 ->setAncestorClass(PhabricatorPolicyRule::class)
358 ->execute();
359
360 $results = array();
361 foreach ($rules as $rule) {
362 $key = $rule->getObjectPolicyKey();
363 if (!$key) {
364 continue;
365 }
366
367 $full_key = $rule->getObjectPolicyFullKey();
368 if (isset($results[$full_key])) {
369 throw new Exception(
370 pht(
371 'Two policy rules (of classes "%s" and "%s") define the same '.
372 'object policy key ("%s"), but each object policy rule must use '.
373 'a unique key.',
374 get_class($rule),
375 get_class($results[$full_key]),
376 $key));
377 }
378
379 $results[$full_key] = $rule;
380 }
381
382 if ($object !== null) {
383 foreach ($results as $key => $rule) {
384 if (!$rule->canApplyToObject($object)) {
385 unset($results[$key]);
386 }
387 }
388 }
389
390 return $results;
391 }
392
393 public static function getDefaultPolicyForObject(
394 PhabricatorUser $viewer,
395 PhabricatorPolicyInterface $object,
396 $capability) {
397
398 $phid = $object->getPHID();
399 if (!$phid) {
400 return null;
401 }
402
403 $type = phid_get_type($phid);
404
405 $map = self::getDefaultObjectTypePolicyMap();
406
407 if (empty($map[$type][$capability])) {
408 return null;
409 }
410
411 $policy_phid = $map[$type][$capability];
412
413 return id(new PhabricatorPolicyQuery())
414 ->setViewer($viewer)
415 ->withPHIDs(array($policy_phid))
416 ->executeOne();
417 }
418
419 private static function getDefaultObjectTypePolicyMap() {
420 static $map;
421
422 if ($map === null) {
423 $map = array();
424
425 $apps = PhabricatorApplication::getAllApplications();
426 foreach ($apps as $app) {
427 $map += $app->getDefaultObjectTypePolicyMap();
428 }
429 }
430
431 return $map;
432 }
433
434
435}