@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 541 lines 13 kB view raw
1<?php 2 3final class PHUIHeaderView extends AphrontTagView { 4 5 const PROPERTY_STATUS = 1; 6 7 private $header; 8 private $tags = array(); 9 private $image; 10 private $imageURL = null; 11 private $imageEditURL = null; 12 private $subheader; 13 private $headerIcon; 14 private $noBackground; 15 private $bleedHeader; 16 private $profileHeader; 17 private $tall; 18 private $properties = array(); 19 private $actionLinks = array(); 20 private $buttonBar = null; 21 private $policyObject; 22 private $epoch; 23 private $actionItems = array(); 24 private $href; 25 private $actionList; 26 private $actionListID; 27 private $collapsible; 28 29 public function setHeader($header) { 30 $this->header = $header; 31 return $this; 32 } 33 34 public function setNoBackground($nada) { 35 $this->noBackground = $nada; 36 return $this; 37 } 38 39 public function setTall($tall) { 40 $this->tall = $tall; 41 return $this; 42 } 43 44 public function addTag(PHUITagView $tag) { 45 $this->tags[] = $tag; 46 return $this; 47 } 48 49 public function setImage($uri) { 50 $this->image = $uri; 51 return $this; 52 } 53 54 public function setImageURL($url) { 55 $this->imageURL = $url; 56 return $this; 57 } 58 59 public function setImageEditURL($url) { 60 $this->imageEditURL = $url; 61 return $this; 62 } 63 64 public function setSubheader($subheader) { 65 $this->subheader = $subheader; 66 return $this; 67 } 68 69 public function setBleedHeader($bleed) { 70 $this->bleedHeader = $bleed; 71 return $this; 72 } 73 74 public function setProfileHeader($bighead) { 75 $this->profileHeader = $bighead; 76 return $this; 77 } 78 79 public function setHeaderIcon($icon) { 80 $this->headerIcon = $icon; 81 return $this; 82 } 83 84 public function setActionList(PhabricatorActionListView $list) { 85 $this->actionList = $list; 86 return $this; 87 } 88 89 public function setActionListID($action_list_id) { 90 $this->actionListID = $action_list_id; 91 return $this; 92 } 93 94 /** 95 * Render PHUIHeaderView as a <summary> instead of a <div> HTML tag. 96 * To be used for collapse/expand in combination with PHUIBoxView. 97 * 98 * @param bool $collapsible True to wrap in <summary> instead of <div> HTML 99 * tag. 100 */ 101 public function setCollapsible($collapsible) { 102 $this->collapsible = $collapsible; 103 return $this; 104 } 105 106 public function setPolicyObject(PhabricatorPolicyInterface $object) { 107 $this->policyObject = $object; 108 return $this; 109 } 110 111 public function addProperty($property, $value) { 112 $this->properties[$property] = $value; 113 return $this; 114 } 115 116 public function addActionLink(PHUIButtonView $button) { 117 $this->actionLinks[] = $button; 118 return $this; 119 } 120 121 public function addActionItem($action) { 122 $this->actionItems[] = $action; 123 return $this; 124 } 125 126 public function setButtonBar(PHUIButtonBarView $bb) { 127 $this->buttonBar = $bb; 128 return $this; 129 } 130 131 public function setStatus($icon, $color, $name) { 132 133 // TODO: Normalize "closed/archived" to constants. 134 if ($color == 'dark') { 135 $color = PHUITagView::COLOR_INDIGO; 136 } 137 138 $tag = id(new PHUITagView()) 139 ->setName($name) 140 ->setIcon($icon) 141 ->setColor($color) 142 ->setType(PHUITagView::TYPE_SHADE); 143 144 return $this->addProperty(self::PROPERTY_STATUS, $tag); 145 } 146 147 public function setEpoch($epoch) { 148 $age = time() - $epoch; 149 $age = floor($age / (60 * 60 * 24)); 150 if ($age < 1) { 151 $when = pht('Today'); 152 } else if ($age == 1) { 153 $when = pht('Yesterday'); 154 } else { 155 $when = pht('%s Day(s) Ago', new PhutilNumber($age)); 156 } 157 158 $this->setStatus('fa-clock-o bluegrey', null, pht('Updated %s', $when)); 159 return $this; 160 } 161 162 public function setHref($href) { 163 $this->href = $href; 164 return $this; 165 } 166 167 public function getHref() { 168 return $this->href; 169 } 170 171 /** 172 * Get the HTML tag 173 * @return string 174 */ 175 protected function getTagName() { 176 if ($this->collapsible) { 177 return 'summary'; 178 } 179 return 'div'; 180 } 181 182 protected function getTagAttributes() { 183 require_celerity_resource('phui-header-view-css'); 184 185 $classes = array(); 186 $classes[] = 'phui-header-shell'; 187 188 if ($this->noBackground) { 189 $classes[] = 'phui-header-no-background'; 190 } 191 192 if ($this->bleedHeader) { 193 $classes[] = 'phui-bleed-header'; 194 } 195 196 if ($this->profileHeader) { 197 $classes[] = 'phui-profile-header'; 198 } 199 200 if ($this->properties || $this->policyObject || 201 $this->subheader || $this->tall) { 202 $classes[] = 'phui-header-tall'; 203 } 204 205 return array( 206 'class' => $classes, 207 ); 208 } 209 210 protected function getTagContent() { 211 212 if ($this->actionList || $this->actionListID) { 213 $action_button = id(new PHUIButtonView()) 214 ->setTag('a') 215 ->setText(pht('Actions')) 216 ->setHref('#') 217 ->setIcon('fa-bars') 218 ->addClass('phui-mobile-menu'); 219 220 if ($this->actionList) { 221 $action_button->setDropdownMenu($this->actionList); 222 } else if ($this->actionListID) { 223 $action_button->setDropdownMenuID($this->actionListID); 224 } 225 226 $this->addActionLink($action_button); 227 } 228 229 $image = null; 230 if ($this->image) { 231 $image_href = null; 232 if ($this->imageURL) { 233 $image_href = $this->imageURL; 234 } else if ($this->imageEditURL) { 235 $image_href = $this->imageEditURL; 236 } 237 238 $image = phutil_tag( 239 'span', 240 array( 241 'class' => 'phui-header-image', 242 'style' => 'background-image: url('.$this->image.')', 243 )); 244 245 if ($image_href) { 246 $edit_view = null; 247 if ($this->imageEditURL) { 248 $edit_view = phutil_tag( 249 'span', 250 array( 251 'class' => 'phui-header-image-edit', 252 ), 253 pht('Edit')); 254 } 255 256 $image = phutil_tag( 257 'a', 258 array( 259 'href' => $image_href, 260 'class' => 'phui-header-image-href', 261 ), 262 array( 263 $image, 264 $edit_view, 265 )); 266 } 267 } 268 269 $viewer = $this->getUser(); 270 271 $left = array(); 272 $right = array(); 273 274 $space_header = null; 275 if ($viewer) { 276 $space_header = id(new PHUISpacesNamespaceContextView()) 277 ->setViewer($viewer) 278 ->setObject($this->policyObject); 279 } 280 281 if ($this->actionLinks) { 282 $actions = array(); 283 foreach ($this->actionLinks as $button) { 284 if (!$button->getColor()) { 285 $button->setColor(PHUIButtonView::GREY); 286 } 287 $button->addClass(PHUI::MARGIN_SMALL_LEFT); 288 $button->addClass('phui-header-action-link'); 289 $actions[] = $button; 290 } 291 $right[] = phutil_tag( 292 'div', 293 array( 294 'class' => 'phui-header-action-links', 295 ), 296 $actions); 297 } 298 299 if ($this->buttonBar) { 300 $right[] = phutil_tag( 301 'div', 302 array( 303 'class' => 'phui-header-action-links', 304 ), 305 $this->buttonBar); 306 } 307 308 if ($this->actionItems) { 309 $action_list = array(); 310 foreach ($this->actionItems as $item) { 311 $action_list[] = phutil_tag( 312 'li', 313 array( 314 'class' => 'phui-header-action-item', 315 ), 316 $item); 317 } 318 $right[] = phutil_tag( 319 'ul', 320 array( 321 'class' => 'phui-header-action-list', 322 ), 323 $action_list); 324 } 325 326 $icon = null; 327 if ($this->headerIcon) { 328 if ($this->headerIcon instanceof PHUIIconView) { 329 $icon = id(clone $this->headerIcon) 330 ->addClass('phui-header-icon'); 331 } else { 332 $icon = id(new PHUIIconView()) 333 ->setIcon($this->headerIcon) 334 ->addClass('phui-header-icon'); 335 } 336 } 337 338 $header_content = $this->header; 339 340 $href = $this->getHref(); 341 if ($href !== null) { 342 $header_content = phutil_tag( 343 'a', 344 array( 345 'href' => $href, 346 ), 347 $header_content); 348 } 349 350 $left[] = phutil_tag( 351 'span', 352 array( 353 'class' => 'phui-header-header', 354 ), 355 array( 356 $space_header, 357 $icon, 358 $header_content, 359 )); 360 361 if ($this->subheader) { 362 $left[] = phutil_tag( 363 'div', 364 array( 365 'class' => 'phui-header-subheader', 366 ), 367 array( 368 $this->subheader, 369 )); 370 } 371 372 if ($this->properties || $this->policyObject || $this->tags) { 373 $property_list = array(); 374 foreach ($this->properties as $type => $property) { 375 switch ($type) { 376 case self::PROPERTY_STATUS: 377 $property_list[] = $property; 378 break; 379 default: 380 throw new Exception(pht('Incorrect Property Passed')); 381 } 382 } 383 384 if ($this->policyObject) { 385 $property_list[] = $this->renderPolicyProperty($this->policyObject); 386 } 387 388 if ($this->tags) { 389 $property_list[] = $this->tags; 390 } 391 392 $left[] = phutil_tag( 393 'div', 394 array( 395 'class' => 'phui-header-subheader', 396 ), 397 $property_list); 398 } 399 400 // We here at @phabricator 401 $header_image = null; 402 if ($image) { 403 $header_image = phutil_tag( 404 'div', 405 array( 406 'class' => 'phui-header-col1', 407 ), 408 $image); 409 } 410 411 // All really love 412 $header_left = phutil_tag( 413 'div', 414 array( 415 'class' => 'phui-header-col2', 416 ), 417 $left); 418 419 // Tables and Pokemon. 420 $header_right = phutil_tag( 421 'div', 422 array( 423 'class' => 'phui-header-col3', 424 ), 425 $right); 426 427 $header_row = phutil_tag( 428 'div', 429 array( 430 'class' => 'phui-header-row', 431 ), 432 array( 433 $header_image, 434 $header_left, 435 $header_right, 436 )); 437 438 return phutil_tag( 439 'h1', 440 array( 441 'class' => 'phui-header-view', 442 ), 443 $header_row); 444 } 445 446 private function renderPolicyProperty(PhabricatorPolicyInterface $object) { 447 $viewer = $this->getUser(); 448 449 $policies = PhabricatorPolicyQuery::loadPolicies($viewer, $object); 450 451 $view_capability = PhabricatorPolicyCapability::CAN_VIEW; 452 $policy = idx($policies, $view_capability); 453 if (!$policy) { 454 return null; 455 } 456 457 // If an object is in a Space with a strictly stronger (more restrictive) 458 // policy, we show the more restrictive policy. This better aligns the 459 // UI hint with the actual behavior. 460 461 // NOTE: We'll do this even if the viewer has access to only one space, and 462 // show them information about the existence of spaces if they click 463 // through. 464 $use_space_policy = false; 465 if ($object instanceof PhabricatorSpacesInterface) { 466 $space_phid = PhabricatorSpacesNamespaceQuery::getObjectSpacePHID( 467 $object); 468 469 $spaces = PhabricatorSpacesNamespaceQuery::getViewerSpaces($viewer); 470 $space = idx($spaces, $space_phid); 471 if ($space) { 472 $space_policies = PhabricatorPolicyQuery::loadPolicies( 473 $viewer, 474 $space); 475 $space_policy = idx($space_policies, $view_capability); 476 if ($space_policy) { 477 if ($space_policy->isStrongerThan($policy)) { 478 $policy = $space_policy; 479 $use_space_policy = true; 480 } 481 } 482 } 483 } 484 485 $container_classes = array(); 486 $container_classes[] = 'policy-header-callout'; 487 $phid = $object->getPHID(); 488 489 $policy_name = array($policy->getShortName()); 490 $policy_icon = $policy->getIcon().' bluegrey'; 491 492 if ($object instanceof PhabricatorPolicyCodexInterface) { 493 $codex = PhabricatorPolicyCodex::newFromObject($object, $viewer); 494 495 $codex_name = $codex->getPolicyShortName($policy, $view_capability); 496 if ($codex_name !== null) { 497 $policy_name = $codex_name; 498 } 499 500 $codex_icon = $codex->getPolicyIcon($policy, $view_capability); 501 if ($codex_icon !== null) { 502 $policy_icon = $codex_icon; 503 } 504 505 $codex_classes = $codex->getPolicyTagClasses($policy, $view_capability); 506 foreach ($codex_classes as $codex_class) { 507 $container_classes[] = $codex_class; 508 } 509 } 510 511 if (!is_array($policy_name)) { 512 $policy_name = (array)$policy_name; 513 } 514 515 $arrow = id(new PHUIIconView()) 516 ->setIcon('fa-angle-right') 517 ->addClass('policy-tier-separator'); 518 519 $policy_name = phutil_implode_html($arrow, $policy_name); 520 521 $icon = id(new PHUIIconView()) 522 ->setIcon($policy_icon); 523 524 $link = javelin_tag( 525 'a', 526 array( 527 'class' => 'policy-link', 528 'href' => '/policy/explain/'.$phid.'/'.$view_capability.'/', 529 'sigil' => 'workflow', 530 ), 531 $policy_name); 532 533 return phutil_tag( 534 'span', 535 array( 536 'class' => implode(' ', $container_classes), 537 ), 538 array($icon, $link)); 539 } 540 541}