[ Index ] |
PHP Cross Reference of Phabricator |
[Summary view] [Print] [Text view]
1 <?php 2 3 /** 4 * @concrete-extensible 5 */ 6 class PhabricatorApplicationTransactionView extends AphrontView { 7 8 private $transactions; 9 private $engine; 10 private $showEditActions = true; 11 private $isPreview; 12 private $objectPHID; 13 private $shouldTerminate = false; 14 private $quoteTargetID; 15 private $quoteRef; 16 17 public function setQuoteRef($quote_ref) { 18 $this->quoteRef = $quote_ref; 19 return $this; 20 } 21 22 public function getQuoteRef() { 23 return $this->quoteRef; 24 } 25 26 public function setQuoteTargetID($quote_target_id) { 27 $this->quoteTargetID = $quote_target_id; 28 return $this; 29 } 30 31 public function getQuoteTargetID() { 32 return $this->quoteTargetID; 33 } 34 35 public function setObjectPHID($object_phid) { 36 $this->objectPHID = $object_phid; 37 return $this; 38 } 39 40 public function getObjectPHID() { 41 return $this->objectPHID; 42 } 43 44 public function setIsPreview($is_preview) { 45 $this->isPreview = $is_preview; 46 return $this; 47 } 48 49 public function setShowEditActions($show_edit_actions) { 50 $this->showEditActions = $show_edit_actions; 51 return $this; 52 } 53 54 public function getShowEditActions() { 55 return $this->showEditActions; 56 } 57 58 public function setMarkupEngine(PhabricatorMarkupEngine $engine) { 59 $this->engine = $engine; 60 return $this; 61 } 62 63 public function setTransactions(array $transactions) { 64 assert_instances_of($transactions, 'PhabricatorApplicationTransaction'); 65 $this->transactions = $transactions; 66 return $this; 67 } 68 69 public function setShouldTerminate($term) { 70 $this->shouldTerminate = $term; 71 return $this; 72 } 73 74 public function buildEvents($with_hiding = false) { 75 $user = $this->getUser(); 76 77 $xactions = $this->transactions; 78 79 $xactions = $this->filterHiddenTransactions($xactions); 80 $xactions = $this->groupRelatedTransactions($xactions); 81 $groups = $this->groupDisplayTransactions($xactions); 82 83 // If the viewer has interacted with this object, we hide things from 84 // before their most recent interaction by default. This tends to make 85 // very long threads much more manageable, because you don't have to 86 // scroll through a lot of history and can focus on just new stuff. 87 88 $show_group = null; 89 90 if ($with_hiding) { 91 // Find the most recent comment by the viewer. 92 $group_keys = array_keys($groups); 93 $group_keys = array_reverse($group_keys); 94 95 // If we would only hide a small number of transactions, don't hide 96 // anything. Just don't examine the last few keys. Also, we always 97 // want to show the most recent pieces of activity, so don't examine 98 // the first few keys either. 99 $group_keys = array_slice($group_keys, 2, -2); 100 101 $type_comment = PhabricatorTransactions::TYPE_COMMENT; 102 foreach ($group_keys as $group_key) { 103 $group = $groups[$group_key]; 104 foreach ($group as $xaction) { 105 if ($xaction->getAuthorPHID() == $user->getPHID() && 106 $xaction->getTransactionType() == $type_comment) { 107 // This is the most recent group where the user commented. 108 $show_group = $group_key; 109 break 2; 110 } 111 } 112 } 113 } 114 115 $events = array(); 116 $hide_by_default = ($show_group !== null); 117 118 foreach ($groups as $group_key => $group) { 119 if ($hide_by_default && ($show_group === $group_key)) { 120 $hide_by_default = false; 121 } 122 123 $group_event = null; 124 foreach ($group as $xaction) { 125 $event = $this->renderEvent($xaction, $group); 126 $event->setHideByDefault($hide_by_default); 127 if (!$group_event) { 128 $group_event = $event; 129 } else { 130 $group_event->addEventToGroup($event); 131 } 132 } 133 $events[] = $group_event; 134 135 } 136 137 return $events; 138 } 139 140 public function render() { 141 if (!$this->getObjectPHID()) { 142 throw new Exception('Call setObjectPHID() before render()!'); 143 } 144 145 $view = new PHUITimelineView(); 146 $view->setShouldTerminate($this->shouldTerminate); 147 $events = $this->buildEvents($with_hiding = true); 148 foreach ($events as $event) { 149 $view->addEvent($event); 150 } 151 152 if ($this->getShowEditActions()) { 153 Javelin::initBehavior('phabricator-transaction-list'); 154 } 155 156 return $view->render(); 157 } 158 159 protected function getOrBuildEngine() { 160 if (!$this->engine) { 161 $field = PhabricatorApplicationTransactionComment::MARKUP_FIELD_COMMENT; 162 163 $engine = id(new PhabricatorMarkupEngine()) 164 ->setViewer($this->getUser()); 165 foreach ($this->transactions as $xaction) { 166 if (!$xaction->hasComment()) { 167 continue; 168 } 169 $engine->addObject($xaction->getComment(), $field); 170 } 171 $engine->process(); 172 173 $this->engine = $engine; 174 } 175 176 return $this->engine; 177 } 178 179 private function buildChangeDetailsLink( 180 PhabricatorApplicationTransaction $xaction) { 181 182 return javelin_tag( 183 'a', 184 array( 185 'href' => '/transactions/detail/'.$xaction->getPHID().'/', 186 'sigil' => 'workflow', 187 ), 188 pht('(Show Details)')); 189 } 190 191 private function buildExtraInformationLink( 192 PhabricatorApplicationTransaction $xaction) { 193 194 $link = $xaction->renderExtraInformationLink(); 195 if (!$link) { 196 return null; 197 } 198 199 return phutil_tag( 200 'span', 201 array( 202 'class' => 'phui-timeline-extra-information', 203 ), 204 array(" \xC2\xB7 ", $link)); 205 } 206 207 protected function shouldGroupTransactions( 208 PhabricatorApplicationTransaction $u, 209 PhabricatorApplicationTransaction $v) { 210 return false; 211 } 212 213 protected function renderTransactionContent( 214 PhabricatorApplicationTransaction $xaction) { 215 216 $field = PhabricatorApplicationTransactionComment::MARKUP_FIELD_COMMENT; 217 $engine = $this->getOrBuildEngine(); 218 $comment = $xaction->getComment(); 219 220 if ($comment) { 221 if ($comment->getIsRemoved()) { 222 return javelin_tag( 223 'span', 224 array( 225 'class' => 'comment-deleted', 226 'sigil' => 'transaction-comment', 227 'meta' => array('phid' => $comment->getTransactionPHID()), 228 ), 229 pht( 230 'This comment was removed by %s.', 231 $xaction->getHandle($comment->getAuthorPHID())->renderLink())); 232 } else if ($comment->getIsDeleted()) { 233 return javelin_tag( 234 'span', 235 array( 236 'class' => 'comment-deleted', 237 'sigil' => 'transaction-comment', 238 'meta' => array('phid' => $comment->getTransactionPHID()), 239 ), 240 pht('This comment has been deleted.')); 241 } else if ($xaction->hasComment()) { 242 return javelin_tag( 243 'span', 244 array( 245 'class' => 'transaction-comment', 246 'sigil' => 'transaction-comment', 247 'meta' => array('phid' => $comment->getTransactionPHID()), 248 ), 249 $engine->getOutput($comment, $field)); 250 } else { 251 // This is an empty, non-deleted comment. Usually this happens when 252 // rendering previews. 253 return null; 254 } 255 } 256 257 return null; 258 } 259 260 private function filterHiddenTransactions(array $xactions) { 261 foreach ($xactions as $key => $xaction) { 262 if ($xaction->shouldHide()) { 263 unset($xactions[$key]); 264 } 265 } 266 return $xactions; 267 } 268 269 private function groupRelatedTransactions(array $xactions) { 270 $last = null; 271 $last_key = null; 272 $groups = array(); 273 foreach ($xactions as $key => $xaction) { 274 if ($last && $this->shouldGroupTransactions($last, $xaction)) { 275 $groups[$last_key][] = $xaction; 276 unset($xactions[$key]); 277 } else { 278 $last = $xaction; 279 $last_key = $key; 280 } 281 } 282 283 foreach ($xactions as $key => $xaction) { 284 $xaction->attachTransactionGroup(idx($groups, $key, array())); 285 } 286 287 return $xactions; 288 } 289 290 private function groupDisplayTransactions(array $xactions) { 291 $groups = array(); 292 $group = array(); 293 foreach ($xactions as $xaction) { 294 if ($xaction->shouldDisplayGroupWith($group)) { 295 $group[] = $xaction; 296 } else { 297 if ($group) { 298 $groups[] = $group; 299 } 300 $group = array($xaction); 301 } 302 } 303 304 if ($group) { 305 $groups[] = $group; 306 } 307 308 foreach ($groups as $key => $group) { 309 $group = msort($group, 'getActionStrength'); 310 $group = array_reverse($group); 311 $groups[$key] = $group; 312 } 313 314 return $groups; 315 } 316 317 private function renderEvent( 318 PhabricatorApplicationTransaction $xaction, 319 array $group) { 320 $viewer = $this->getUser(); 321 322 $event = id(new PHUITimelineEventView()) 323 ->setUser($viewer) 324 ->setTransactionPHID($xaction->getPHID()) 325 ->setUserHandle($xaction->getHandle($xaction->getAuthorPHID())) 326 ->setIcon($xaction->getIcon()) 327 ->setColor($xaction->getColor()); 328 329 list($token, $token_removed) = $xaction->getToken(); 330 if ($token) { 331 $event->setToken($token, $token_removed); 332 } 333 334 if (!$this->shouldSuppressTitle($xaction, $group)) { 335 $title = $xaction->getTitle(); 336 if ($xaction->hasChangeDetails()) { 337 if (!$this->isPreview) { 338 $details = $this->buildChangeDetailsLink($xaction); 339 $title = array( 340 $title, 341 ' ', 342 $details, 343 ); 344 } 345 } 346 347 if (!$this->isPreview) { 348 $more = $this->buildExtraInformationLink($xaction); 349 if ($more) { 350 $title = array($title, ' ', $more); 351 } 352 } 353 354 $event->setTitle($title); 355 } 356 357 if ($this->isPreview) { 358 $event->setIsPreview(true); 359 } else { 360 $event 361 ->setDateCreated($xaction->getDateCreated()) 362 ->setContentSource($xaction->getContentSource()) 363 ->setAnchor($xaction->getID()); 364 } 365 366 $transaction_type = $xaction->getTransactionType(); 367 $comment_type = PhabricatorTransactions::TYPE_COMMENT; 368 $is_normal_comment = ($transaction_type == $comment_type); 369 370 if ($this->getShowEditActions() && 371 !$this->isPreview && 372 $is_normal_comment) { 373 374 $has_deleted_comment = 375 $xaction->getComment() && 376 $xaction->getComment()->getIsDeleted(); 377 378 $has_removed_comment = 379 $xaction->getComment() && 380 $xaction->getComment()->getIsRemoved(); 381 382 if ($xaction->getCommentVersion() > 1 && !$has_removed_comment) { 383 $event->setIsEdited(true); 384 } 385 386 // If we have a place for quoted text to go and this is a quotable 387 // comment, pass the quote target ID to the event view. 388 if ($this->getQuoteTargetID()) { 389 if ($xaction->hasComment()) { 390 if (!$has_removed_comment && !$has_deleted_comment) { 391 $event->setQuoteTargetID($this->getQuoteTargetID()); 392 $event->setQuoteRef($this->getQuoteRef()); 393 } 394 } 395 } 396 397 $can_edit = PhabricatorPolicyCapability::CAN_EDIT; 398 399 if ($xaction->hasComment() || $has_deleted_comment) { 400 $has_edit_capability = PhabricatorPolicyFilter::hasCapability( 401 $viewer, 402 $xaction, 403 $can_edit); 404 if ($has_edit_capability && !$has_removed_comment) { 405 $event->setIsEditable(true); 406 } 407 if ($has_edit_capability || $viewer->getIsAdmin()) { 408 if (!$has_removed_comment) { 409 $event->setIsRemovable(true); 410 } 411 } 412 } 413 } 414 415 $comment = $this->renderTransactionContent($xaction); 416 if ($comment) { 417 $event->appendChild($comment); 418 } 419 420 return $event; 421 } 422 423 private function shouldSuppressTitle( 424 PhabricatorApplicationTransaction $xaction, 425 array $group) { 426 427 // This is a little hard-coded, but we don't have any other reasonable 428 // cases for now. Suppress "commented on" if there are other actions in 429 // the display group. 430 431 if (count($group) > 1) { 432 $type_comment = PhabricatorTransactions::TYPE_COMMENT; 433 if ($xaction->getTransactionType() == $type_comment) { 434 return true; 435 } 436 } 437 438 return false; 439 } 440 441 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Sun Nov 30 09:20:46 2014 | Cross-referenced by PHPXref 0.7.1 |