Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/view/phui/PHUITimelineEventView.php
12249 views
1
<?php
2
3
final class PHUITimelineEventView extends AphrontView {
4
5
const DELIMITER = " \xC2\xB7 ";
6
7
private $userHandle;
8
private $title;
9
private $icon;
10
private $color;
11
private $classes = array();
12
private $contentSource;
13
private $dateCreated;
14
private $anchor;
15
private $isEditable;
16
private $isEdited;
17
private $isRemovable;
18
private $transactionPHID;
19
private $isPreview;
20
private $eventGroup = array();
21
private $hideByDefault;
22
private $token;
23
private $tokenRemoved;
24
private $quoteTargetID;
25
private $isNormalComment;
26
private $quoteRef;
27
private $reallyMajorEvent;
28
private $hideCommentOptions = false;
29
private $authorPHID;
30
private $badges = array();
31
private $pinboardItems = array();
32
private $isSilent;
33
private $isMFA;
34
private $isLockOverride;
35
private $canInteract;
36
37
public function setAuthorPHID($author_phid) {
38
$this->authorPHID = $author_phid;
39
return $this;
40
}
41
42
public function getAuthorPHID() {
43
return $this->authorPHID;
44
}
45
46
public function setQuoteRef($quote_ref) {
47
$this->quoteRef = $quote_ref;
48
return $this;
49
}
50
51
public function getQuoteRef() {
52
return $this->quoteRef;
53
}
54
55
public function setQuoteTargetID($quote_target_id) {
56
$this->quoteTargetID = $quote_target_id;
57
return $this;
58
}
59
60
public function getQuoteTargetID() {
61
return $this->quoteTargetID;
62
}
63
64
public function setIsNormalComment($is_normal_comment) {
65
$this->isNormalComment = $is_normal_comment;
66
return $this;
67
}
68
69
public function getIsNormalComment() {
70
return $this->isNormalComment;
71
}
72
73
public function setHideByDefault($hide_by_default) {
74
$this->hideByDefault = $hide_by_default;
75
return $this;
76
}
77
78
public function getHideByDefault() {
79
return $this->hideByDefault;
80
}
81
82
public function setTransactionPHID($transaction_phid) {
83
$this->transactionPHID = $transaction_phid;
84
return $this;
85
}
86
87
public function getTransactionPHID() {
88
return $this->transactionPHID;
89
}
90
91
public function setIsEdited($is_edited) {
92
$this->isEdited = $is_edited;
93
return $this;
94
}
95
96
public function getIsEdited() {
97
return $this->isEdited;
98
}
99
100
public function setIsPreview($is_preview) {
101
$this->isPreview = $is_preview;
102
return $this;
103
}
104
105
public function getIsPreview() {
106
return $this->isPreview;
107
}
108
109
public function setIsEditable($is_editable) {
110
$this->isEditable = $is_editable;
111
return $this;
112
}
113
114
public function getIsEditable() {
115
return $this->isEditable;
116
}
117
118
public function setCanInteract($can_interact) {
119
$this->canInteract = $can_interact;
120
return $this;
121
}
122
123
public function getCanInteract() {
124
return $this->canInteract;
125
}
126
127
public function setIsRemovable($is_removable) {
128
$this->isRemovable = $is_removable;
129
return $this;
130
}
131
132
public function getIsRemovable() {
133
return $this->isRemovable;
134
}
135
136
public function setDateCreated($date_created) {
137
$this->dateCreated = $date_created;
138
return $this;
139
}
140
141
public function getDateCreated() {
142
return $this->dateCreated;
143
}
144
145
public function setContentSource(PhabricatorContentSource $content_source) {
146
$this->contentSource = $content_source;
147
return $this;
148
}
149
150
public function getContentSource() {
151
return $this->contentSource;
152
}
153
154
public function setUserHandle(PhabricatorObjectHandle $handle) {
155
$this->userHandle = $handle;
156
return $this;
157
}
158
159
public function setAnchor($anchor) {
160
$this->anchor = $anchor;
161
return $this;
162
}
163
164
public function getAnchor() {
165
return $this->anchor;
166
}
167
168
public function setTitle($title) {
169
$this->title = $title;
170
return $this;
171
}
172
173
public function addClass($class) {
174
$this->classes[] = $class;
175
return $this;
176
}
177
178
public function addBadge(PHUIBadgeMiniView $badge) {
179
$this->badges[] = $badge;
180
return $this;
181
}
182
183
public function setIcon($icon) {
184
$this->icon = $icon;
185
return $this;
186
}
187
188
public function setColor($color) {
189
$this->color = $color;
190
return $this;
191
}
192
193
public function setIsSilent($is_silent) {
194
$this->isSilent = $is_silent;
195
return $this;
196
}
197
198
public function getIsSilent() {
199
return $this->isSilent;
200
}
201
202
public function setIsMFA($is_mfa) {
203
$this->isMFA = $is_mfa;
204
return $this;
205
}
206
207
public function getIsMFA() {
208
return $this->isMFA;
209
}
210
211
public function setIsLockOverride($is_override) {
212
$this->isLockOverride = $is_override;
213
return $this;
214
}
215
216
public function getIsLockOverride() {
217
return $this->isLockOverride;
218
}
219
220
public function setReallyMajorEvent($me) {
221
$this->reallyMajorEvent = $me;
222
return $this;
223
}
224
225
public function setHideCommentOptions($hide_comment_options) {
226
$this->hideCommentOptions = $hide_comment_options;
227
return $this;
228
}
229
230
public function getHideCommentOptions() {
231
return $this->hideCommentOptions;
232
}
233
234
public function addPinboardItem(PHUIPinboardItemView $item) {
235
$this->pinboardItems[] = $item;
236
return $this;
237
}
238
239
public function setToken($token, $removed = false) {
240
$this->token = $token;
241
$this->tokenRemoved = $removed;
242
return $this;
243
}
244
245
public function getEventGroup() {
246
return array_merge(array($this), $this->eventGroup);
247
}
248
249
public function addEventToGroup(PHUITimelineEventView $event) {
250
$this->eventGroup[] = $event;
251
return $this;
252
}
253
254
protected function shouldRenderEventTitle() {
255
if ($this->title === null) {
256
return false;
257
}
258
259
return true;
260
}
261
262
protected function renderEventTitle($force_icon, $has_menu, $extra) {
263
$title = $this->title;
264
265
$title_classes = array();
266
$title_classes[] = 'phui-timeline-title';
267
268
$icon = null;
269
if ($this->icon || $force_icon) {
270
$title_classes[] = 'phui-timeline-title-with-icon';
271
}
272
273
if ($has_menu) {
274
$title_classes[] = 'phui-timeline-title-with-menu';
275
}
276
277
if ($this->icon) {
278
$fill_classes = array();
279
$fill_classes[] = 'phui-timeline-icon-fill';
280
if ($this->color) {
281
$fill_classes[] = 'fill-has-color';
282
$fill_classes[] = 'phui-timeline-icon-fill-'.$this->color;
283
}
284
285
$icon = id(new PHUIIconView())
286
->setIcon($this->icon)
287
->addClass('phui-timeline-icon');
288
289
$icon = phutil_tag(
290
'span',
291
array(
292
'class' => implode(' ', $fill_classes),
293
),
294
$icon);
295
}
296
297
$token = null;
298
if ($this->token) {
299
$token = id(new PHUIIconView())
300
->addClass('phui-timeline-token')
301
->setSpriteSheet(PHUIIconView::SPRITE_TOKENS)
302
->setSpriteIcon($this->token);
303
if ($this->tokenRemoved) {
304
$token->addClass('strikethrough');
305
}
306
}
307
308
$title = phutil_tag(
309
'div',
310
array(
311
'class' => implode(' ', $title_classes),
312
),
313
array($icon, $token, $title, $extra));
314
315
return $title;
316
}
317
318
public function render() {
319
320
$events = $this->getEventGroup();
321
322
// Move events with icons first.
323
$icon_keys = array();
324
foreach ($this->getEventGroup() as $key => $event) {
325
if ($event->icon) {
326
$icon_keys[] = $key;
327
}
328
}
329
$events = array_select_keys($events, $icon_keys) + $events;
330
$force_icon = (bool)$icon_keys;
331
332
$menu = null;
333
$items = array();
334
if (!$this->getIsPreview() && !$this->getHideCommentOptions()) {
335
foreach ($this->getEventGroup() as $event) {
336
$items[] = $event->getMenuItems($this->anchor);
337
}
338
$items = array_mergev($items);
339
}
340
341
if ($items) {
342
$icon = id(new PHUIIconView())
343
->setIcon('fa-caret-down');
344
$aural = javelin_tag(
345
'span',
346
array(
347
'aural' => true,
348
),
349
pht('Comment Actions'));
350
351
if ($items) {
352
$sigil = 'phui-dropdown-menu';
353
Javelin::initBehavior('phui-dropdown-menu');
354
} else {
355
$sigil = null;
356
}
357
358
$action_list = id(new PhabricatorActionListView())
359
->setUser($this->getUser());
360
foreach ($items as $item) {
361
$action_list->addAction($item);
362
}
363
364
$menu = javelin_tag(
365
$items ? 'a' : 'span',
366
array(
367
'href' => '#',
368
'class' => 'phui-timeline-menu',
369
'sigil' => $sigil,
370
'aria-haspopup' => 'true',
371
'aria-expanded' => 'false',
372
'meta' => $action_list->getDropdownMenuMetadata(),
373
),
374
array(
375
$aural,
376
$icon,
377
));
378
379
$has_menu = true;
380
} else {
381
$has_menu = false;
382
}
383
384
// Render "extra" information (timestamp, etc).
385
$extra = $this->renderExtra($events);
386
387
$show_badges = false;
388
389
$group_titles = array();
390
$group_items = array();
391
$group_children = array();
392
foreach ($events as $event) {
393
if ($event->shouldRenderEventTitle()) {
394
395
// Render the group anchor here, outside the title box. If we render
396
// it inside the title box it ends up completely hidden and Chrome 55
397
// refuses to jump to it. See T11997 for discussion.
398
399
if ($extra && $this->anchor) {
400
$group_titles[] = id(new PhabricatorAnchorView())
401
->setAnchorName($this->anchor)
402
->render();
403
}
404
405
$group_titles[] = $event->renderEventTitle(
406
$force_icon,
407
$has_menu,
408
$extra);
409
410
// Don't render this information more than once.
411
$extra = null;
412
}
413
414
if ($event->hasChildren()) {
415
$group_children[] = $event->renderChildren();
416
$show_badges = true;
417
}
418
}
419
420
$image_uri = $this->userHandle->getImageURI();
421
422
$wedge = phutil_tag(
423
'div',
424
array(
425
'class' => 'phui-timeline-wedge',
426
'style' => (nonempty($image_uri)) ? '' : 'display: none;',
427
),
428
'');
429
430
$image = null;
431
$badges = null;
432
if ($image_uri) {
433
$image = javelin_tag(
434
($this->userHandle->getURI()) ? 'a' : 'div',
435
array(
436
'style' => 'background-image: url('.$image_uri.')',
437
'class' => 'phui-timeline-image',
438
'href' => $this->userHandle->getURI(),
439
'aural' => false,
440
),
441
'');
442
if ($this->badges && $show_badges) {
443
$flex = new PHUIBadgeBoxView();
444
$flex->addItems($this->badges);
445
$flex->setCollapsed(true);
446
$badges = phutil_tag(
447
'div',
448
array(
449
'class' => 'phui-timeline-badges',
450
),
451
$flex);
452
}
453
}
454
455
$content_classes = array();
456
$content_classes[] = 'phui-timeline-content';
457
458
$classes = array();
459
$classes[] = 'phui-timeline-event-view';
460
if ($group_children) {
461
$classes[] = 'phui-timeline-major-event';
462
$content = phutil_tag(
463
'div',
464
array(
465
'class' => 'phui-timeline-inner-content',
466
),
467
array(
468
$group_titles,
469
$menu,
470
phutil_tag(
471
'div',
472
array(
473
'class' => 'phui-timeline-core-content',
474
),
475
$group_children),
476
));
477
} else {
478
$classes[] = 'phui-timeline-minor-event';
479
$content = $group_titles;
480
}
481
482
$content = phutil_tag(
483
'div',
484
array(
485
'class' => 'phui-timeline-group',
486
),
487
$content);
488
489
// Image Events
490
$pinboard = null;
491
if ($this->pinboardItems) {
492
$pinboard = new PHUIPinboardView();
493
foreach ($this->pinboardItems as $item) {
494
$pinboard->addItem($item);
495
}
496
}
497
498
$content = phutil_tag(
499
'div',
500
array(
501
'class' => implode(' ', $content_classes),
502
),
503
array($image, $badges, $wedge, $content, $pinboard));
504
505
$outer_classes = $this->classes;
506
$outer_classes[] = 'phui-timeline-shell';
507
$color = null;
508
foreach ($this->getEventGroup() as $event) {
509
if ($event->color) {
510
$color = $event->color;
511
break;
512
}
513
}
514
515
if ($color) {
516
$outer_classes[] = 'phui-timeline-'.$color;
517
}
518
519
$sigils = array();
520
$meta = null;
521
if ($this->getTransactionPHID()) {
522
$sigils[] = 'transaction';
523
$meta = array(
524
'phid' => $this->getTransactionPHID(),
525
'anchor' => $this->anchor,
526
);
527
}
528
529
$major_event = null;
530
if ($this->reallyMajorEvent) {
531
$major_event = phutil_tag(
532
'div',
533
array(
534
'class' => 'phui-timeline-event-view '.
535
'phui-timeline-spacer '.
536
'phui-timeline-spacer-bold',
537
));
538
}
539
540
$sigils[] = 'anchor-container';
541
542
return array(
543
javelin_tag(
544
'div',
545
array(
546
'class' => implode(' ', $outer_classes),
547
'sigil' => implode(' ', $sigils),
548
'meta' => $meta,
549
),
550
phutil_tag(
551
'div',
552
array(
553
'class' => implode(' ', $classes),
554
),
555
$content)),
556
$major_event,
557
);
558
}
559
560
private function renderExtra(array $events) {
561
$extra = array();
562
563
if ($this->getIsPreview()) {
564
$extra[] = pht('PREVIEW');
565
} else {
566
foreach ($events as $event) {
567
if ($event->getIsEdited()) {
568
$extra[] = pht('Edited');
569
break;
570
}
571
}
572
573
$source = $this->getContentSource();
574
$content_source = null;
575
if ($source) {
576
$content_source = id(new PhabricatorContentSourceView())
577
->setContentSource($source)
578
->setUser($this->getUser());
579
$content_source = pht('Via %s', $content_source->getSourceName());
580
}
581
582
$date_created = null;
583
foreach ($events as $event) {
584
if ($event->getDateCreated()) {
585
if ($date_created === null) {
586
$date_created = $event->getDateCreated();
587
} else {
588
$date_created = min($event->getDateCreated(), $date_created);
589
}
590
}
591
}
592
593
if ($date_created) {
594
$date = phabricator_dual_datetime(
595
$date_created,
596
$this->getUser());
597
if ($this->anchor) {
598
Javelin::initBehavior('phabricator-watch-anchor');
599
Javelin::initBehavior('phabricator-tooltips');
600
601
$date = array(
602
javelin_tag(
603
'a',
604
array(
605
'href' => '#'.$this->anchor,
606
'sigil' => 'has-tooltip',
607
'meta' => array(
608
'tip' => $content_source,
609
),
610
),
611
$date),
612
);
613
}
614
615
$extra[] = $date;
616
}
617
618
// If this edit was applied silently, give user a hint that they should
619
// not expect to have received any mail or notifications.
620
if ($this->getIsSilent()) {
621
$extra[] = id(new PHUIIconView())
622
->setIcon('fa-bell-slash', 'white')
623
->setEmblemColor('red')
624
->setTooltip(pht('Silent Edit'));
625
}
626
627
// If this edit was applied while the actor was in high-security mode,
628
// provide a hint that it was extra authentic.
629
if ($this->getIsMFA()) {
630
$extra[] = id(new PHUIIconView())
631
->setIcon('fa-vcard', 'white')
632
->setEmblemColor('pink')
633
->setTooltip(pht('MFA Authenticated'));
634
}
635
636
if ($this->getIsLockOverride()) {
637
$extra[] = id(new PHUIIconView())
638
->setIcon('fa-chain-broken', 'white')
639
->setEmblemColor('violet')
640
->setTooltip(pht('Lock Overridden'));
641
}
642
}
643
644
$extra = javelin_tag(
645
'span',
646
array(
647
'class' => 'phui-timeline-extra',
648
),
649
phutil_implode_html(
650
javelin_tag(
651
'span',
652
array(
653
'aural' => false,
654
),
655
self::DELIMITER),
656
$extra));
657
658
return $extra;
659
}
660
661
private function getMenuItems($anchor) {
662
$xaction_phid = $this->getTransactionPHID();
663
664
$can_interact = $this->getCanInteract();
665
$viewer = $this->getViewer();
666
$is_admin = $viewer->getIsAdmin();
667
668
$items = array();
669
670
if ($this->getIsEditable()) {
671
$items[] = id(new PhabricatorActionView())
672
->setIcon('fa-pencil')
673
->setHref('/transactions/edit/'.$xaction_phid.'/')
674
->setName(pht('Edit Comment'))
675
->addSigil('transaction-edit')
676
->setDisabled(!$can_interact)
677
->setMetadata(
678
array(
679
'anchor' => $anchor,
680
));
681
}
682
683
if ($this->getQuoteTargetID()) {
684
$ref = null;
685
if ($this->getQuoteRef()) {
686
$ref = $this->getQuoteRef();
687
if ($anchor) {
688
$ref = $ref.'#'.$anchor;
689
}
690
}
691
692
$items[] = id(new PhabricatorActionView())
693
->setIcon('fa-quote-left')
694
->setName(pht('Quote Comment'))
695
->setHref('#')
696
->addSigil('transaction-quote')
697
->setMetadata(
698
array(
699
'targetID' => $this->getQuoteTargetID(),
700
'uri' => '/transactions/quote/'.$xaction_phid.'/',
701
'ref' => $ref,
702
));
703
}
704
705
if ($this->getIsNormalComment()) {
706
$items[] = id(new PhabricatorActionView())
707
->setIcon('fa-code')
708
->setHref('/transactions/raw/'.$xaction_phid.'/')
709
->setName(pht('View Raw Remarkup'))
710
->addSigil('transaction-raw')
711
->setMetadata(
712
array(
713
'anchor' => $anchor,
714
));
715
716
$content_source = $this->getContentSource();
717
$source_email = PhabricatorEmailContentSource::SOURCECONST;
718
if ($content_source->getSource() == $source_email) {
719
$source_id = $content_source->getContentSourceParameter('id');
720
if ($source_id) {
721
$items[] = id(new PhabricatorActionView())
722
->setIcon('fa-envelope-o')
723
->setHref('/transactions/raw/'.$xaction_phid.'/?email')
724
->setName(pht('View Email Body'))
725
->addSigil('transaction-raw')
726
->setMetadata(
727
array(
728
'anchor' => $anchor,
729
));
730
}
731
}
732
}
733
734
if ($this->getIsEdited()) {
735
$items[] = id(new PhabricatorActionView())
736
->setIcon('fa-list')
737
->setHref('/transactions/history/'.$xaction_phid.'/')
738
->setName(pht('View Edit History'))
739
->setWorkflow(true);
740
}
741
742
if ($this->getIsRemovable()) {
743
$items[] = id(new PhabricatorActionView())
744
->setType(PhabricatorActionView::TYPE_DIVIDER);
745
746
$remove_item = id(new PhabricatorActionView())
747
->setIcon('fa-trash-o')
748
->setHref('/transactions/remove/'.$xaction_phid.'/')
749
->setName(pht('Remove Comment'))
750
->addSigil('transaction-remove')
751
->setMetadata(
752
array(
753
'anchor' => $anchor,
754
));
755
756
if (!$is_admin && !$can_interact) {
757
$remove_item->setDisabled(!$is_admin && !$can_interact);
758
} else {
759
$remove_item->setColor(PhabricatorActionView::RED);
760
}
761
762
$items[] = $remove_item;
763
}
764
765
return $items;
766
}
767
768
}
769
770