@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 PHUITimelineView extends AphrontView {
4
5 private $events = array();
6 private $id;
7 private $shouldTerminate = false;
8 private $shouldAddSpacers = true;
9 private $pager;
10 private $viewData = array();
11 private $quoteTargetID;
12 private $quoteRef;
13
14 public function setID($id) {
15 $this->id = $id;
16 return $this;
17 }
18
19 public function setShouldTerminate($term) {
20 $this->shouldTerminate = $term;
21 return $this;
22 }
23
24 public function setShouldAddSpacers($bool) {
25 $this->shouldAddSpacers = $bool;
26 return $this;
27 }
28
29 public function setPager(AphrontCursorPagerView $pager) {
30 $this->pager = $pager;
31 return $this;
32 }
33
34 public function getPager() {
35 return $this->pager;
36 }
37
38 public function addEvent(PHUITimelineEventView $event) {
39 $this->events[] = $event;
40 return $this;
41 }
42
43 public function setViewData(array $data) {
44 $this->viewData = $data;
45 return $this;
46 }
47
48 public function getViewData() {
49 return $this->viewData;
50 }
51
52 public function setQuoteTargetID($quote_target_id) {
53 $this->quoteTargetID = $quote_target_id;
54 return $this;
55 }
56
57 public function getQuoteTargetID() {
58 return $this->quoteTargetID;
59 }
60
61 public function setQuoteRef($quote_ref) {
62 $this->quoteRef = $quote_ref;
63 return $this;
64 }
65
66 public function getQuoteRef() {
67 return $this->quoteRef;
68 }
69
70 public function render() {
71 if ($this->getPager()) {
72 if ($this->id === null) {
73 $this->id = celerity_generate_unique_node_id();
74 }
75 Javelin::initBehavior(
76 'phabricator-show-older-transactions',
77 array(
78 'timelineID' => $this->id,
79 'viewData' => $this->getViewData(),
80 ));
81 }
82 $events = $this->buildEvents();
83
84 return phutil_tag(
85 'div',
86 array(
87 'class' => 'phui-timeline-view',
88 'id' => $this->id,
89 ),
90 array(
91 phutil_tag(
92 'h2',
93 array(
94 'class' => 'aural-only',
95 ),
96 pht('Event Timeline')),
97 $events,
98 ));
99 }
100
101 public function buildEvents() {
102 require_celerity_resource('phui-timeline-view-css');
103
104 $spacer = self::renderSpacer();
105
106 // Track why we're hiding older results.
107 $hide_reason = null;
108
109 $hide = array();
110 $show = array();
111
112 // Bucket timeline events into events we'll hide by default (because they
113 // predate your most recent interaction with the object) and events we'll
114 // show by default.
115 foreach ($this->events as $event) {
116 if ($event->getHideByDefault()) {
117 $hide[] = $event;
118 } else {
119 $show[] = $event;
120 }
121 }
122
123 // If you've never interacted with the object, all the events will be shown
124 // by default. We may still need to paginate if there are a large number
125 // of events.
126 $more = (bool)$hide;
127
128 if ($more) {
129 $hide_reason = 'comment';
130 }
131
132 if ($this->getPager()) {
133 if ($this->getPager()->getHasMoreResults()) {
134 if (!$more) {
135 $hide_reason = 'limit';
136 }
137 $more = true;
138 }
139 }
140
141 $events = array();
142 if ($more && $this->getPager()) {
143 switch ($hide_reason) {
144 case 'comment':
145 $hide_help = pht(
146 'Changes from before your most recent comment are hidden.');
147 break;
148 case 'limit':
149 default:
150 $hide_help = pht(
151 'There are a very large number of changes, so older changes are '.
152 'hidden.');
153 break;
154 }
155
156 $uri = $this->getPager()->getNextPageURI();
157
158 $target_id = $this->getQuoteTargetID();
159 if ($target_id === null) {
160 $uri->removeQueryParam('quoteTargetID');
161 } else {
162 $uri->replaceQueryParam('quoteTargetID', $target_id);
163 }
164
165 $quote_ref = $this->getQuoteRef();
166 if ($quote_ref === null) {
167 $uri->removeQueryParam('quoteRef');
168 } else {
169 $uri->replaceQueryParam('quoteRef', $quote_ref);
170 }
171
172 $events[] = javelin_tag(
173 'div',
174 array(
175 'sigil' => 'show-older-block',
176 'class' => 'phui-timeline-older-transactions-are-hidden',
177 ),
178 array(
179 $hide_help,
180 ' ',
181 javelin_tag(
182 'a',
183 array(
184 'href' => (string)$uri,
185 'mustcapture' => true,
186 'sigil' => 'show-older-link',
187 ),
188 pht('Show Older Changes')),
189 ));
190
191 if ($show) {
192 $events[] = $spacer;
193 }
194 }
195
196 if ($show) {
197 $this->prepareBadgeData($show);
198 $events[] = phutil_implode_html($spacer, $show);
199 }
200
201 if ($events) {
202 if ($this->shouldAddSpacers) {
203 $events = array($spacer, $events, $spacer);
204 }
205 } else {
206 $events = array($spacer);
207 }
208
209 if ($this->shouldTerminate) {
210 $events[] = self::renderEnder();
211 }
212
213 return $events;
214 }
215
216 public static function renderSpacer() {
217 return phutil_tag(
218 'div',
219 array(
220 'class' => 'phui-timeline-event-view '.
221 'phui-timeline-spacer',
222 ),
223 '');
224 }
225
226 public static function renderEnder() {
227 return phutil_tag(
228 'div',
229 array(
230 'class' => 'phui-timeline-event-view '.
231 'the-worlds-end',
232 ),
233 '');
234 }
235
236 /**
237 * @param array<PHUITimelineEventView> $events
238 */
239 private function prepareBadgeData(array $events) {
240 assert_instances_of($events, PHUITimelineEventView::class);
241
242 $viewer = $this->getUser();
243 $can_use_badges = PhabricatorApplication::isClassInstalledForViewer(
244 PhabricatorBadgesApplication::class,
245 $viewer);
246 if (!$can_use_badges) {
247 return;
248 }
249
250 $user_phid_type = PhabricatorPeopleUserPHIDType::TYPECONST;
251
252 $user_phids = array();
253 foreach ($events as $key => $event) {
254 $author_phid = $event->getAuthorPHID();
255 if (!$author_phid) {
256 unset($events[$key]);
257 continue;
258 }
259
260 if (phid_get_type($author_phid) != $user_phid_type) {
261 // This is likely an application actor, like "Herald" or "Harbormaster".
262 // They can't have badges.
263 unset($events[$key]);
264 continue;
265 }
266
267 $user_phids[$author_phid] = $author_phid;
268 }
269
270 if (!$user_phids) {
271 return;
272 }
273
274 $users = id(new PhabricatorPeopleQuery())
275 ->setViewer($viewer)
276 ->withPHIDs($user_phids)
277 ->needBadgeAwards(true)
278 ->execute();
279 $users = mpull($users, null, 'getPHID');
280
281 foreach ($events as $event) {
282 $user_phid = $event->getAuthorPHID();
283 if (!array_key_exists($user_phid, $users)) {
284 continue;
285 }
286 $badges = $users[$user_phid]->getRecentBadgeAwards();
287 foreach ($badges as $badge) {
288 $badge_view = id(new PHUIBadgeMiniView())
289 ->setIcon($badge['icon'])
290 ->setQuality($badge['quality'])
291 ->setHeader($badge['name'])
292 ->setTipDirection('E')
293 ->setHref('/badges/view/'.$badge['id'].'/');
294 $event->addBadge($badge_view);
295 }
296 }
297 }
298
299}