[ Index ]

PHP Cross Reference of Phabricator

title

Body

[close]

/src/applications/transactions/view/ -> PhabricatorApplicationTransactionView.php (source)

   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  }


Generated: Sun Nov 30 09:20:46 2014 Cross-referenced by PHPXref 0.7.1