@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 PhabricatorObjectHandle
4 extends Phobject
5 implements PhabricatorPolicyInterface {
6
7 const AVAILABILITY_FULL = 'full';
8 const AVAILABILITY_NONE = 'none';
9 const AVAILABILITY_NOEMAIL = 'no-email';
10 const AVAILABILITY_PARTIAL = 'partial';
11 const AVAILABILITY_DISABLED = 'disabled';
12
13 const STATUS_OPEN = 'open';
14 const STATUS_CLOSED = 'closed';
15
16 private $uri;
17 private $phid;
18 private $type;
19 private $name;
20 private $fullName;
21 private $title;
22 private $imageURI;
23 private $icon;
24 private $tagColor;
25 private $timestamp;
26 private $status = self::STATUS_OPEN;
27 private $availability = self::AVAILABILITY_FULL;
28 private $complete;
29 private $objectName;
30 private $policyFiltered;
31 private $subtitle;
32 private $tokenIcon;
33 private $commandLineObjectName;
34 private $mailStampName;
35 private $capabilities = array();
36 private $userIsEnrolledInMultiFactor = false;
37
38 public function setIcon($icon) {
39 $this->icon = $icon;
40 return $this;
41 }
42
43 public function getIcon() {
44 if ($this->getPolicyFiltered()) {
45 return 'fa-lock';
46 }
47
48 if ($this->icon) {
49 return $this->icon;
50 }
51 return $this->getTypeIcon();
52 }
53
54 public function setSubtitle($subtitle) {
55 $this->subtitle = $subtitle;
56 return $this;
57 }
58
59 public function getSubtitle() {
60 return $this->subtitle;
61 }
62
63 public function setTagColor($color) {
64 $colors = PHUITagView::getShadeMapCached();
65 if (isset($colors[$color])) {
66 $this->tagColor = $color;
67 }
68
69 return $this;
70 }
71
72 public function getTagColor() {
73 if ($this->getPolicyFiltered()) {
74 return 'disabled';
75 }
76
77 if ($this->tagColor) {
78 return $this->tagColor;
79 }
80
81 return 'blue';
82 }
83
84 public function getIconColor() {
85 if ($this->tagColor) {
86 return $this->tagColor;
87 }
88 return null;
89 }
90
91 public function setTokenIcon($icon) {
92 $this->tokenIcon = $icon;
93 return $this;
94 }
95
96 public function getTokenIcon() {
97 if ($this->tokenIcon !== null) {
98 return $this->tokenIcon;
99 }
100
101 return $this->getIcon();
102 }
103
104 public function getTypeIcon() {
105 if ($this->getPHIDType()) {
106 return $this->getPHIDType()->getTypeIcon();
107 }
108 return null;
109 }
110
111 public function setPolicyFiltered($policy_filered) {
112 $this->policyFiltered = $policy_filered;
113 return $this;
114 }
115
116 public function getPolicyFiltered() {
117 return $this->policyFiltered;
118 }
119
120 public function setObjectName($object_name) {
121 $this->objectName = $object_name;
122 return $this;
123 }
124
125 public function getObjectName() {
126 if (!$this->objectName) {
127 return $this->getName();
128 }
129 return $this->objectName;
130 }
131
132 public function setMailStampName($mail_stamp_name) {
133 $this->mailStampName = $mail_stamp_name;
134 return $this;
135 }
136
137 public function getMailStampName() {
138 return $this->mailStampName;
139 }
140
141 public function setURI($uri) {
142 $this->uri = $uri;
143 return $this;
144 }
145
146 public function getURI() {
147 return $this->uri;
148 }
149
150 public function setPHID($phid) {
151 $this->phid = $phid;
152 return $this;
153 }
154
155 public function getPHID() {
156 return $this->phid;
157 }
158
159 public function setName($name) {
160 $this->name = $name;
161 return $this;
162 }
163
164 public function getName() {
165 if ($this->name === null) {
166 if ($this->getPolicyFiltered()) {
167 return pht('Restricted %s', $this->getTypeName());
168 } else if ($this->getPHID() && $this->getTypeName() ===
169 PhabricatorPHIDConstants::PHID_TYPE_UNKNOWN) {
170 // Values of custom Select field conditions in Herald rules do not have
171 // a PHID (and no PHID type) as they are arbitrary text when loadPage()
172 // in PhabricatorHandleQuery calls $type = phid_get_type($phid).
173 // Thus the code lower in this class cannot pull a name to render for
174 // these non-existing PHIDs either.
175 // In this case, render their PHID (the actual Select field key value).
176 // This is always more informative than 'Unknown Object (????)' though
177 // still imperfect as it displays the key instead of the user-friendly
178 // name value defined in maniphest.custom-field-definitions.
179 // https://we.phorge.it/T15860
180 return $this->getPHID();
181 } else {
182 return pht('Unknown Object (%s)', $this->getTypeName());
183 }
184 }
185 return $this->name;
186 }
187
188 public function setAvailability($availability) {
189 $this->availability = $availability;
190 return $this;
191 }
192
193 public function getAvailability() {
194 return $this->availability;
195 }
196
197 public function isDisabled() {
198 return ($this->getAvailability() == self::AVAILABILITY_DISABLED);
199 }
200
201 public function setStatus($status) {
202 $this->status = $status;
203 return $this;
204 }
205
206 public function getStatus() {
207 return $this->status;
208 }
209
210 public function isClosed() {
211 return ($this->status === self::STATUS_CLOSED);
212 }
213
214 public function setFullName($full_name) {
215 $this->fullName = $full_name;
216 return $this;
217 }
218
219 public function getFullName() {
220 if ($this->fullName !== null) {
221 return $this->fullName;
222 }
223 return $this->getName();
224 }
225
226 public function setCommandLineObjectName($command_line_object_name) {
227 $this->commandLineObjectName = $command_line_object_name;
228 return $this;
229 }
230
231 public function getCommandLineObjectName() {
232 if ($this->commandLineObjectName !== null) {
233 return $this->commandLineObjectName;
234 }
235
236 return $this->getObjectName();
237 }
238
239 public function setTitle($title) {
240 $this->title = $title;
241 return $this;
242 }
243
244 public function getTitle() {
245 return $this->title;
246 }
247
248 public function setType($type) {
249 $this->type = $type;
250 return $this;
251 }
252
253 public function getType() {
254 return $this->type;
255 }
256
257 public function setImageURI($uri) {
258 $this->imageURI = $uri;
259 return $this;
260 }
261
262 public function getImageURI() {
263 return $this->imageURI;
264 }
265
266 public function setTimestamp($timestamp) {
267 $this->timestamp = $timestamp;
268 return $this;
269 }
270
271 public function getTimestamp() {
272 return $this->timestamp;
273 }
274
275 public function getTypeName() {
276 if ($this->getPHIDType()) {
277 return $this->getPHIDType()->getTypeName();
278 }
279
280 return $this->getType();
281 }
282
283 /**
284 * Set whether or not the user object has multi-factor auth enabled.
285 *
286 * @param bool $has_mfa True when the user represented by the handle has
287 * multi-factor auth enabled. Defaults to false otherwise.
288 * @return $this
289 */
290 public function setUserIsEnrolledInMultiFactor(bool $has_mfa) {
291 $this->userIsEnrolledInMultiFactor = $has_mfa;
292 return $this;
293 }
294
295 /**
296 * Get whether or not the user object has multi-factor auth enabled.
297 *
298 * @return bool True when the user represented by the handle has
299 * multi-factor auth enabled. Defaults to false.
300 */
301 public function getUserIsEnrolledInMultiFactor(): bool {
302 return $this->userIsEnrolledInMultiFactor;
303 }
304
305 /**
306 * Set whether or not the underlying object is complete. See
307 * @{method:isComplete} for an explanation of what it means to be complete.
308 *
309 * @param bool $complete True if the handle represents a complete object.
310 * @return $this
311 */
312 public function setComplete($complete) {
313 $this->complete = $complete;
314 return $this;
315 }
316
317
318 /**
319 * Determine if the handle represents an object which was completely loaded
320 * (i.e., the underlying object exists) vs an object which could not be
321 * completely loaded (e.g., the type or data for the PHID could not be
322 * identified or located).
323 *
324 * Basically, @{class:PhabricatorHandleQuery} gives you back a handle for
325 * any PHID you give it, but it gives you a complete handle only for valid
326 * PHIDs.
327 *
328 * @return bool True if the handle represents a complete object.
329 */
330 public function isComplete() {
331 return $this->complete;
332 }
333
334 public function renderLink($name = null) {
335 return $this->renderLinkWithAttributes($name, array());
336 }
337
338 public function renderHovercardLink($name = null, $context_phid = null) {
339 Javelin::initBehavior('phui-hovercards');
340
341 $hovercard_spec = array(
342 'objectPHID' => $this->getPHID(),
343 );
344
345 if ($context_phid) {
346 $hovercard_spec['contextPHID'] = $context_phid;
347 }
348
349 $attributes = array(
350 'sigil' => 'hovercard',
351 'meta' => array(
352 'hovercardSpec' => $hovercard_spec,
353 ),
354 );
355
356 return $this->renderLinkWithAttributes($name, $attributes);
357 }
358
359 private function renderLinkWithAttributes($name, array $attributes) {
360 if ($name === null) {
361 $name = $this->getLinkName();
362 }
363 $classes = array();
364 $classes[] = 'phui-handle';
365 $title = $this->title;
366
367 if ($this->status != self::STATUS_OPEN) {
368 $classes[] = 'handle-status-'.$this->status;
369 }
370
371 $circle = null;
372 if ($this->availability != self::AVAILABILITY_FULL) {
373 $classes[] = 'handle-availability-'.$this->availability;
374 $circle = array(
375 phutil_tag(
376 'span',
377 array(
378 'class' => 'perfect-circle',
379 ),
380 "\xE2\x80\xA2"),
381 ' ',
382 );
383 }
384
385 if ($this->getType() == PhabricatorPeopleUserPHIDType::TYPECONST) {
386 $classes[] = 'phui-link-person';
387 }
388
389 $uri = $this->getURI();
390
391 $icon = null;
392 if ($this->getPolicyFiltered()) {
393 $icon = id(new PHUIIconView())
394 ->setIcon('fa-lock lightgreytext');
395 }
396
397 $attributes = $attributes + array(
398 'href' => $uri,
399 'class' => implode(' ', $classes),
400 'title' => $title,
401 );
402
403 return javelin_tag(
404 $uri ? 'a' : 'span',
405 $attributes,
406 array($circle, $icon, $name));
407 }
408
409 public function renderTag() {
410 return id(new PHUITagView())
411 ->setType(PHUITagView::TYPE_SHADE)
412 ->setColor($this->getTagColor())
413 ->setIcon($this->getIcon())
414 ->setHref($this->getURI())
415 ->setName($this->getLinkName());
416 }
417
418 public function getLinkName() {
419 switch ($this->getType()) {
420 case PhabricatorPeopleUserPHIDType::TYPECONST:
421 $name = $this->getName();
422 break;
423 default:
424 $name = $this->getFullName();
425 break;
426 }
427 return $name;
428 }
429
430 protected function getPHIDType() {
431 $types = PhabricatorPHIDType::getAllTypes();
432 return idx($types, $this->getType());
433 }
434
435 public function hasCapabilities() {
436 if (!$this->isComplete()) {
437 return false;
438 }
439
440 return ($this->getType() === PhabricatorPeopleUserPHIDType::TYPECONST);
441 }
442
443 public function attachCapability(
444 PhabricatorPolicyInterface $object,
445 $capability,
446 $has_capability) {
447
448 if (!$this->hasCapabilities()) {
449 throw new Exception(
450 pht(
451 'Attempting to attach capability ("%s") for object ("%s") to '.
452 'handle, but this handle (of type "%s") can not have '.
453 'capabilities.',
454 $capability,
455 get_class($object),
456 $this->getType()));
457 }
458
459 $object_key = $this->getObjectCapabilityKey($object);
460 $this->capabilities[$object_key][$capability] = $has_capability;
461
462 return $this;
463 }
464
465 public function hasViewCapability(PhabricatorPolicyInterface $object) {
466 return $this->hasCapability($object, PhabricatorPolicyCapability::CAN_VIEW);
467 }
468
469 private function hasCapability(
470 PhabricatorPolicyInterface $object,
471 $capability) {
472
473 $object_key = $this->getObjectCapabilityKey($object);
474
475 if (!isset($this->capabilities[$object_key][$capability])) {
476 throw new Exception(
477 pht(
478 'Attempting to test capability "%s" for handle of type "%s", but '.
479 'this capability has not been attached.',
480 $capability,
481 $this->getType()));
482 }
483
484 return $this->capabilities[$object_key][$capability];
485 }
486
487 private function getObjectCapabilityKey(PhabricatorPolicyInterface $object) {
488 $object_phid = $object->getPHID();
489
490 if (!$object_phid) {
491 throw new Exception(
492 pht(
493 'Object (of class "%s") has no PHID, so handles can not interact '.
494 'with capabilities for it.',
495 get_class($object)));
496 }
497
498 return $object_phid;
499 }
500
501
502/* -( PhabricatorPolicyInterface )----------------------------------------- */
503
504
505 public function getCapabilities() {
506 return array(
507 PhabricatorPolicyCapability::CAN_VIEW,
508 );
509 }
510
511 public function getPolicy($capability) {
512 return PhabricatorPolicies::POLICY_PUBLIC;
513 }
514
515 public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
516 // NOTE: Handles are always visible, they just don't get populated with
517 // data if the user can't see the underlying object.
518 return true;
519 }
520
521 public function describeAutomaticCapability($capability) {
522 return null;
523 }
524
525}