@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 525 lines 13 kB view raw
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}