@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 recaptime-dev/main 299 lines 7.1 kB view raw
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}