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