diff --git a/src/applications/config/storage/PhabricatorConfigTransaction.php b/src/applications/config/storage/PhabricatorConfigTransaction.php index c02c65859b..a0428a73d7 100644 --- a/src/applications/config/storage/PhabricatorConfigTransaction.php +++ b/src/applications/config/storage/PhabricatorConfigTransaction.php @@ -1,127 +1,128 @@ getAuthorPHID(); $old = $this->getOldValue(); $new = $this->getNewValue(); switch ($this->getTransactionType()) { case self::TYPE_EDIT: // TODO: After T2213 show the actual values too; for now, we don't // have the tools to do it without making a bit of a mess of it. $old_del = idx($old, 'deleted'); $new_del = idx($new, 'deleted'); if ($old_del && !$new_del) { return pht( '%s created this configuration entry.', $this->renderHandleLink($author_phid)); } else if (!$old_del && $new_del) { return pht( '%s deleted this configuration entry.', $this->renderHandleLink($author_phid)); } else if ($old_del && $new_del) { // This is a bug. return pht( '%s deleted this configuration entry (again?).', $this->renderHandleLink($author_phid)); } else { return pht( '%s edited this configuration entry.', $this->renderHandleLink($author_phid)); } break; } return parent::getTitle(); } public function getIcon() { switch ($this->getTransactionType()) { case self::TYPE_EDIT: return 'edit'; } return parent::getIcon(); } public function hasChangeDetails() { switch ($this->getTransactionType()) { case self::TYPE_EDIT: return true; } return parent::hasChangeDetails(); } - public function renderChangeDetails() { + public function renderChangeDetails(PhabricatorUser $viewer) { $old = $this->getOldValue(); $new = $this->getNewValue(); if ($old['deleted']) { $old_text = ''; } else { // NOTE: Here and below, we're adding a synthetic "\n" to prevent the // differ from complaining about missing trailing newlines. $old_text = PhabricatorConfigJSON::prettyPrintJSON($old['value'])."\n"; } if ($new['deleted']) { $new_text = ''; } else { $new_text = PhabricatorConfigJSON::prettyPrintJSON($new['value'])."\n"; } $view = id(new PhabricatorApplicationTransactionTextDiffDetailView()) + ->setUser($viewer) ->setOldText($old_text) ->setNewText($new_text); return $view->render(); } public function getColor() { $old = $this->getOldValue(); $new = $this->getNewValue(); switch ($this->getTransactionType()) { case self::TYPE_EDIT: $old_del = idx($old, 'deleted'); $new_del = idx($new, 'deleted'); if ($old_del && !$new_del) { return PhabricatorTransactions::COLOR_GREEN; } else if (!$old_del && $new_del) { return PhabricatorTransactions::COLOR_RED; } else { return PhabricatorTransactions::COLOR_BLUE; } break; } } } diff --git a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php index 7779f3313c..782e6d8b8d 100644 --- a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php +++ b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php @@ -1,355 +1,355 @@ getApplicationTransactionType(); return PhabricatorPHID::generateNewPHID($type, $subtype); } public function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, self::CONFIG_SERIALIZATION => array( 'oldValue' => self::SERIALIZATION_JSON, 'newValue' => self::SERIALIZATION_JSON, ), ) + parent::getConfiguration(); } public function setContentSource(PhabricatorContentSource $content_source) { $this->contentSource = $content_source->serialize(); return $this; } public function getContentSource() { return PhabricatorContentSource::newFromSerialized($this->contentSource); } public function hasComment() { return $this->getComment() && strlen($this->getComment()->getContent()); } public function getComment() { if ($this->commentNotLoaded) { throw new Exception("Comment for this transaction was not loaded."); } return $this->comment; } public function attachComment( PhabricatorApplicationTransactionComment $comment) { $this->comment = $comment; $this->commentNotLoaded = false; return $this; } public function setCommentNotLoaded($not_loaded) { $this->commentNotLoaded = $not_loaded; return $this; } /* -( Rendering )---------------------------------------------------------- */ public function setRenderingTarget($rendering_target) { $this->renderingTarget = $rendering_target; return $this; } public function getRenderingTarget() { return $this->renderingTarget; } public function getRequiredHandlePHIDs() { $phids = array(); $old = $this->getOldValue(); $new = $this->getNewValue(); $phids[] = array($this->getAuthorPHID()); switch ($this->getTransactionType()) { case PhabricatorTransactions::TYPE_SUBSCRIBERS: $phids[] = $old; $phids[] = $new; break; } return array_mergev($phids); } public function setHandles(array $handles) { $this->handles = $handles; return $this; } public function getHandle($phid) { if (empty($this->handles[$phid])) { throw new Exception( "Transaction requires a handle ('{$phid}') it did not load."); } return $this->handles[$phid]; } public function getHandles() { if ($this->handles === null) { throw new Exception( 'Transaction requires handles and it did not load them.' ); } return $this->handles; } protected function renderHandleLink($phid) { if ($this->renderingTarget == self::TARGET_HTML) { return $this->getHandle($phid)->renderLink(); } else { return hsprintf('%s', $this->getHandle($phid)->getName()); } } protected function renderHandleList(array $phids) { $links = array(); foreach ($phids as $phid) { $links[] = $this->renderHandleLink($phid); } return phutil_implode_html(', ', $links); } public function getIcon() { switch ($this->getTransactionType()) { case PhabricatorTransactions::TYPE_COMMENT: return 'comment'; case PhabricatorTransactions::TYPE_SUBSCRIBERS: return 'message'; case PhabricatorTransactions::TYPE_VIEW_POLICY: case PhabricatorTransactions::TYPE_EDIT_POLICY: return 'lock'; } return null; } public function getColor() { return null; } public function shouldHide() { switch ($this->getTransactionType()) { case PhabricatorTransactions::TYPE_VIEW_POLICY: case PhabricatorTransactions::TYPE_EDIT_POLICY: if ($this->getOldValue() === null) { return true; } else { return false; } break; } return false; } public function getNoEffectDescription() { switch ($this->getTransactionType()) { case PhabricatorTransactions::TYPE_COMMENT: return pht('You can not post an empty comment.'); case PhabricatorTransactions::TYPE_VIEW_POLICY: return pht( 'This %s already has that view policy.', $this->getApplicationObjectTypeName()); case PhabricatorTransactions::TYPE_EDIT_POLICY: return pht( 'This %s already has that edit policy.', $this->getApplicationObjectTypeName()); case PhabricatorTransactions::TYPE_SUBSCRIBERS: return pht( 'All users are already subscribed to this %s.', $this->getApplicationObjectTypeName()); } return pht('Transaction has no effect.'); } public function getTitle() { $author_phid = $this->getAuthorPHID(); $old = $this->getOldValue(); $new = $this->getNewValue(); switch ($this->getTransactionType()) { case PhabricatorTransactions::TYPE_COMMENT: return pht( '%s added a comment.', $this->renderHandleLink($author_phid)); case PhabricatorTransactions::TYPE_VIEW_POLICY: // TODO: Render human-readable. return pht( '%s changed the visibility of this %s from "%s" to "%s".', $this->renderHandleLink($author_phid), $this->getApplicationObjectTypeName(), $old, $new); case PhabricatorTransactions::TYPE_EDIT_POLICY: // TODO: Render human-readable. return pht( '%s changed the edit policy of this %s from "%s" to "%s".', $this->renderHandleLink($author_phid), $this->getApplicationObjectTypeName(), $old, $new); case PhabricatorTransactions::TYPE_SUBSCRIBERS: $add = array_diff($new, $old); $rem = array_diff($old, $new); if ($add && $rem) { return pht( '%s edited subscriber(s), added %d: %s; removed %d: %s.', $this->renderHandleLink($author_phid), count($add), $this->renderHandleList($add), count($rem), $this->renderHandleList($rem)); } else if ($add) { return pht( '%s added %d subscriber(s): %s.', $this->renderHandleLink($author_phid), count($add), $this->renderHandleList($add)); } else { return pht( '%s removed %d subscriber(s): %s.', $this->renderHandleLink($author_phid), count($rem), $this->renderHandleList($rem)); } break; default: return pht( '%s edited this %s.', $this->renderHandleLink($author_phid), $this->getApplicationObjectTypeName()); } } public function getTitleForFeed() { $author_phid = $this->getAuthorPHID(); $object_phid = $this->getObjectPHID(); $old = $this->getOldValue(); $new = $this->getNewValue(); switch ($this->getTransactionType()) { case PhabricatorTransactions::TYPE_COMMENT: return pht( '%s added a comment to %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); case PhabricatorTransactions::TYPE_VIEW_POLICY: return pht( '%s changed the visibility for %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); case PhabricatorTransactions::TYPE_EDIT_POLICY: return pht( '%s changed the edit policy for %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); case PhabricatorTransactions::TYPE_SUBSCRIBERS: return pht( '%s updated subscribers of %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); } return $this->getTitle(); } public function getActionStrength() { switch ($this->getTransactionType()) { case PhabricatorTransactions::TYPE_COMMENT: return 0.5; } return 1.0; } public function getActionName() { switch ($this->getTransactionType()) { case PhabricatorTransactions::TYPE_COMMENT: return pht('Commented On'); case PhabricatorTransactions::TYPE_VIEW_POLICY: case PhabricatorTransactions::TYPE_EDIT_POLICY: return pht('Changed Policy'); case PhabricatorTransactions::TYPE_SUBSCRIBERS: return pht('Changed Subscribers'); default: return pht('Updated'); } } public function getMailTags() { return array(); } public function hasChangeDetails() { return false; } - public function renderChangeDetails() { + public function renderChangeDetails(PhabricatorUser $viewer) { return null; } /* -( PhabricatorPolicyInterface Implementation )-------------------------- */ public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, ); } public function getPolicy($capability) { switch ($capability) { case PhabricatorPolicyCapability::CAN_VIEW: return $this->getViewPolicy(); case PhabricatorPolicyCapability::CAN_EDIT: return $this->getEditPolicy(); } } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { return ($viewer->getPHID() == $this->getAuthorPHID()); } } diff --git a/src/applications/transactions/view/PhabricatorApplicationTransactionView.php b/src/applications/transactions/view/PhabricatorApplicationTransactionView.php index c77941a458..8f9c5f6eb8 100644 --- a/src/applications/transactions/view/PhabricatorApplicationTransactionView.php +++ b/src/applications/transactions/view/PhabricatorApplicationTransactionView.php @@ -1,220 +1,220 @@ isPreview = $is_preview; return $this; } public function setShowEditActions($show_edit_actions) { $this->showEditActions = $show_edit_actions; return $this; } public function getShowEditActions() { return $this->showEditActions; } public function setAnchorOffset($anchor_offset) { $this->anchorOffset = $anchor_offset; return $this; } public function setMarkupEngine(PhabricatorMarkupEngine $engine) { $this->engine = $engine; return $this; } public function setTransactions(array $transactions) { assert_instances_of($transactions, 'PhabricatorApplicationTransaction'); $this->transactions = $transactions; return $this; } public function buildEvents() { $field = PhabricatorApplicationTransactionComment::MARKUP_FIELD_COMMENT; $engine = $this->getOrBuildEngine(); $user = $this->getUser(); $anchor = $this->anchorOffset; $events = array(); foreach ($this->transactions as $xaction) { if ($xaction->shouldHide()) { continue; } $event = id(new PhabricatorTimelineEventView()) ->setUser($user) ->setTransactionPHID($xaction->getPHID()) ->setUserHandle($xaction->getHandle($xaction->getAuthorPHID())) ->setIcon($xaction->getIcon()) ->setColor($xaction->getColor()); $title = $xaction->getTitle(); if ($xaction->hasChangeDetails()) { $title = array( $title, ' ', $this->buildChangeDetails($xaction), ); } $event->setTitle($title); if ($this->isPreview) { $event->setIsPreview(true); } else { $event ->setDateCreated($xaction->getDateCreated()) ->setContentSource($xaction->getContentSource()) ->setAnchor($anchor); $anchor++; } $has_deleted_comment = $xaction->getComment() && $xaction->getComment()->getIsDeleted(); if ($this->getShowEditActions() && !$this->isPreview) { if ($xaction->getCommentVersion() > 1) { $event->setIsEdited(true); } $can_edit = PhabricatorPolicyCapability::CAN_EDIT; if ($xaction->hasComment() || $has_deleted_comment) { $has_edit_capability = PhabricatorPolicyFilter::hasCapability( $user, $xaction, $can_edit); if ($has_edit_capability) { $event->setIsEditable(true); } } } if ($xaction->hasComment()) { $event->appendChild( $engine->getOutput($xaction->getComment(), $field)); } else if ($has_deleted_comment) { $event->appendChild(phutil_tag('em', array(), pht( 'This comment has been deleted.'))); } $events[] = $event; } return $events; } public function render() { $view = new PhabricatorTimelineView(); $events = $this->buildEvents(); foreach ($events as $event) { $view->addEvent($event); } if ($this->getShowEditActions()) { $list_id = celerity_generate_unique_node_id(); $view->setID($list_id); Javelin::initBehavior( 'phabricator-transaction-list', array( 'listID' => $list_id, 'nextAnchor' => $this->anchorOffset + count($events), )); } return $view->render(); } private function getOrBuildEngine() { if ($this->engine) { return $this->engine; } $field = PhabricatorApplicationTransactionComment::MARKUP_FIELD_COMMENT; $engine = id(new PhabricatorMarkupEngine()) ->setViewer($this->getUser()); foreach ($this->transactions as $xaction) { if (!$xaction->hasComment()) { continue; } $engine->addObject($xaction->getComment(), $field); } $engine->process(); return $engine; } private function buildChangeDetails( PhabricatorApplicationTransaction $xaction) { Javelin::initBehavior('phabricator-reveal-content'); $show_id = celerity_generate_unique_node_id(); $hide_id = celerity_generate_unique_node_id(); $content_id = celerity_generate_unique_node_id(); $show_more = javelin_tag( 'a', array( 'href' => '#', 'sigil' => 'reveal-content', 'mustcapture' => true, 'id' => $show_id, 'meta' => array( 'hideIDs' => array($show_id), 'showIDs' => array($hide_id, $content_id), ), ), pht('(Show Details)')); $hide_more = javelin_tag( 'a', array( 'href' => '#', 'sigil' => 'reveal-content', 'mustcapture' => true, 'id' => $hide_id, 'style' => 'display: none', 'meta' => array( 'hideIDs' => array($hide_id, $content_id), 'showIDs' => array($show_id), ), ), pht('(Hide Details)')); $content = phutil_tag( 'div', array( 'id' => $content_id, 'style' => 'display: none', 'class' => 'phabricator-timeline-change-details', ), - $xaction->renderChangeDetails()); + $xaction->renderChangeDetails($this->getUser())); return array( $show_more, $hide_more, $content, ); } }