Page MenuHomestyx hydra

No OneTemporary

diff --git a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php
index e4fb7c52b9..fbb13222c9 100644
--- a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php
+++ b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php
@@ -1,570 +1,621 @@
<?php
abstract class PhabricatorApplicationTransaction
extends PhabricatorLiskDAO
implements PhabricatorPolicyInterface {
const TARGET_TEXT = 'text';
const TARGET_HTML = 'html';
protected $phid;
protected $objectPHID;
protected $authorPHID;
protected $viewPolicy;
protected $editPolicy;
protected $commentPHID;
protected $commentVersion = 0;
protected $transactionType;
protected $oldValue;
protected $newValue;
protected $metadata = array();
protected $contentSource;
private $comment;
private $commentNotLoaded;
private $handles;
private $renderingTarget = self::TARGET_HTML;
private $transactionGroup = array();
private $viewer = self::ATTACHABLE;
abstract public function getApplicationTransactionType();
private function getApplicationObjectTypeName() {
$types = PhabricatorPHIDType::getAllTypes();
$type = idx($types, $this->getApplicationTransactionType());
if ($type) {
return $type->getTypeName();
}
return pht('Object');
}
public function getApplicationTransactionCommentObject() {
throw new Exception("Not implemented!");
}
public function getApplicationTransactionViewObject() {
return new PhabricatorApplicationTransactionView();
}
public function getMetadataValue($key, $default = null) {
return idx($this->metadata, $key, $default);
}
public function setMetadataValue($key, $value) {
$this->metadata[$key] = $value;
return $this;
}
public function generatePHID() {
$type = PhabricatorApplicationTransactionPHIDTypeTransaction::TYPECONST;
$subtype = $this->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,
'metadata' => 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 attachViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer;
return $this;
}
public function getViewer() {
return $this->assertAttached($this->viewer);
}
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;
case PhabricatorTransactions::TYPE_EDGE:
$phids[] = ipull($old, 'dst');
$phids[] = ipull($new, 'dst');
break;
case PhabricatorTransactions::TYPE_EDIT_POLICY:
case PhabricatorTransactions::TYPE_VIEW_POLICY:
case PhabricatorTransactions::TYPE_JOIN_POLICY:
if (!PhabricatorPolicyQuery::isGlobalPolicy($old)) {
$phids[] = array($old);
}
if (!PhabricatorPolicyQuery::isGlobalPolicy($new)) {
$phids[] = array($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 getHandleIfExists($phid) {
return idx($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;
}
public function renderHandleLink($phid) {
if ($this->renderingTarget == self::TARGET_HTML) {
return $this->getHandle($phid)->renderLink();
} else {
return $this->getHandle($phid)->getLinkName();
}
}
public function renderHandleList(array $phids) {
$links = array();
foreach ($phids as $phid) {
$links[] = $this->renderHandleLink($phid);
}
if ($this->renderingTarget == self::TARGET_HTML) {
return phutil_implode_html(', ', $links);
} else {
return implode(', ', $links);
}
}
public function renderPolicyName($phid) {
$policy = PhabricatorPolicy::newFromPolicyAndHandle(
$phid,
$this->getHandleIfExists($phid));
if ($this->renderingTarget == self::TARGET_HTML) {
$output = $policy->renderDescription();
} else {
$output = hsprintf('%s', $policy->getFullName());
}
return $output;
}
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:
case PhabricatorTransactions::TYPE_JOIN_POLICY:
return 'lock';
case PhabricatorTransactions::TYPE_EDGE:
return 'link';
}
- return null;
+ return 'edit';
}
public function getColor() {
return null;
}
public function shouldHide() {
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_VIEW_POLICY:
case PhabricatorTransactions::TYPE_EDIT_POLICY:
case PhabricatorTransactions::TYPE_JOIN_POLICY:
if ($this->getOldValue() === null) {
return true;
} else {
return false;
}
break;
}
return false;
}
public function shouldHideForMail() {
return $this->shouldHide();
}
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_JOIN_POLICY:
return pht(
'This %s already has that join policy.',
$this->getApplicationObjectTypeName());
case PhabricatorTransactions::TYPE_SUBSCRIBERS:
return pht(
'All users are already subscribed to this %s.',
$this->getApplicationObjectTypeName());
case PhabricatorTransactions::TYPE_EDGE:
return pht('Edges already exist; transaction has no effect.');
}
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:
return pht(
'%s changed the visibility of this %s from "%s" to "%s".',
$this->renderHandleLink($author_phid),
$this->getApplicationObjectTypeName(),
$this->renderPolicyName($old),
$this->renderPolicyName($new));
case PhabricatorTransactions::TYPE_EDIT_POLICY:
return pht(
'%s changed the edit policy of this %s from "%s" to "%s".',
$this->renderHandleLink($author_phid),
$this->getApplicationObjectTypeName(),
$this->renderPolicyName($old),
$this->renderPolicyName($new));
case PhabricatorTransactions::TYPE_JOIN_POLICY:
return pht(
'%s changed the join policy of this %s from "%s" to "%s".',
$this->renderHandleLink($author_phid),
$this->getApplicationObjectTypeName(),
$this->renderPolicyName($old),
$this->renderPolicyName($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 if ($rem) {
return pht(
'%s removed %d subscriber(s): %s.',
$this->renderHandleLink($author_phid),
count($rem),
$this->renderHandleList($rem));
} else {
// This is used when rendering previews, before the user actually
// selects any CCs.
return pht(
'%s updated subscribers...',
$this->renderHandleLink($author_phid));
}
break;
case PhabricatorTransactions::TYPE_EDGE:
$new = ipull($new, 'dst');
$old = ipull($old, 'dst');
$add = array_diff($new, $old);
$rem = array_diff($old, $new);
$type = $this->getMetadata('edge:type');
$type = head($type);
if ($add && $rem) {
$string = PhabricatorEdgeConfig::getEditStringForEdgeType($type);
return pht(
$string,
$this->renderHandleLink($author_phid),
count($add),
$this->renderHandleList($add),
count($rem),
$this->renderHandleList($rem));
} else if ($add) {
$string = PhabricatorEdgeConfig::getAddStringForEdgeType($type);
return pht(
$string,
$this->renderHandleLink($author_phid),
count($add),
$this->renderHandleList($add));
} else {
$string = PhabricatorEdgeConfig::getRemoveStringForEdgeType($type);
return pht(
$string,
$this->renderHandleLink($author_phid),
count($rem),
$this->renderHandleList($rem));
}
case PhabricatorTransactions::TYPE_CUSTOMFIELD:
$key = $this->getMetadataValue('customfield:key');
$field = PhabricatorCustomField::getObjectField(
// TODO: This is a giant hack, but we currently don't have a way to
// get the contextual object and this pathway is only hit by
// Maniphest. We should provide a way to get the actual object here.
new ManiphestTask(),
PhabricatorCustomField::ROLE_APPLICATIONTRANSACTIONS,
$key);
if ($field) {
$field->setViewer($this->getViewer());
return $field->getApplicationTransactionTitle($this);
} else {
return pht(
'%s edited a custom field.',
$this->renderHandleLink($author_phid));
}
default:
return pht(
'%s edited this %s.',
$this->renderHandleLink($author_phid),
$this->getApplicationObjectTypeName());
}
}
public function getTitleForFeed(PhabricatorFeedStory $story) {
$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_JOIN_POLICY:
return pht(
'%s changed the join 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));
case PhabricatorTransactions::TYPE_EDGE:
$type = $this->getMetadata('edge:type');
$type = head($type);
$string = PhabricatorEdgeConfig::getFeedStringForEdgeType($type);
return pht(
$string,
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
case PhabricatorTransactions::TYPE_CUSTOMFIELD:
$key = $this->getMetadataValue('customfield:key');
$field = PhabricatorCustomField::getObjectField(
// TODO: This is a giant hack, but we currently don't have a way to
// get the contextual object and this pathway is only hit by
// Maniphest. We should provide a way to get the actual object here.
new ManiphestTask(),
PhabricatorCustomField::ROLE_APPLICATIONTRANSACTIONS,
$key);
if ($field) {
$field->setViewer($this->getViewer());
return $field->getApplicationTransactionTitleForFeed($this, $story);
} else {
return pht(
'%s edited a custom field on %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
}
}
return $this->getTitle();
}
public function getBodyForFeed(PhabricatorFeedStory $story) {
$old = $this->getOldValue();
$new = $this->getNewValue();
$body = null;
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_COMMENT:
$text = $this->getComment()->getContent();
$body = phutil_escape_html_newlines(
phutil_utf8_shorten($text, 128));
break;
}
return $body;
}
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:
case PhabricatorTransactions::TYPE_JOIN_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(PhabricatorUser $viewer) {
return null;
}
public function attachTransactionGroup(array $group) {
assert_instances_of($group, 'PhabricatorApplicationTransaction');
$this->transactionGroup = $group;
return $this;
}
public function getTransactionGroup() {
return $this->transactionGroup;
}
+ /**
+ * Should this transaction be visually grouped with an existing transaction
+ * group?
+ *
+ * @param list<PhabricatorApplicationTransaction> List of transactions.
+ * @return bool True to display in a group with the other transactions.
+ */
+ public function shouldDisplayGroupWith(array $group) {
+ $type_comment = PhabricatorTransactions::TYPE_COMMENT;
+
+ $this_source = null;
+ if ($this->getContentSource()) {
+ $this_source = $this->getContentSource()->getSource();
+ }
+
+ foreach ($group as $xaction) {
+ // Don't group transactions by different authors.
+ if ($xaction->getAuthorPHID() != $this->getAuthorPHID()) {
+ return false;
+ }
+
+ // Don't group transactions for different objects.
+ if ($xaction->getObjectPHID() != $this->getObjectPHID()) {
+ return false;
+ }
+
+ // Don't group anything into a group which already has a comment.
+ if ($xaction->getTransactionType() == $type_comment) {
+ return false;
+ }
+
+ // Don't group transactions from different content sources.
+ $other_source = null;
+ if ($xaction->getContentSource()) {
+ $other_source = $xaction->getContentSource()->getSource();
+ }
+
+ if ($other_source != $this_source) {
+ return false;
+ }
+
+ // Don't group transactions which happened more than 2 minutes apart.
+ $apart = abs($xaction->getDateCreated() - $this->getDateCreated());
+ if ($apart > (60 * 2)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
/* -( 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());
}
public function describeAutomaticCapability($capability) {
// TODO: (T603) Exact policies are unclear here.
return null;
}
}
diff --git a/src/applications/transactions/view/PhabricatorApplicationTransactionView.php b/src/applications/transactions/view/PhabricatorApplicationTransactionView.php
index e64b5b86ff..aa1d856c12 100644
--- a/src/applications/transactions/view/PhabricatorApplicationTransactionView.php
+++ b/src/applications/transactions/view/PhabricatorApplicationTransactionView.php
@@ -1,299 +1,377 @@
<?php
/**
* @concrete-extensible
*/
class PhabricatorApplicationTransactionView extends AphrontView {
private $transactions;
private $engine;
private $anchorOffset = 1;
private $showEditActions = true;
private $isPreview;
private $objectPHID;
private $isDetailView;
public function setIsDetailView($is_detail_view) {
$this->isDetailView = $is_detail_view;
return $this;
}
public function setObjectPHID($object_phid) {
$this->objectPHID = $object_phid;
return $this;
}
public function getObjectPHID() {
return $this->objectPHID;
}
public function setIsPreview($is_preview) {
$this->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() {
$user = $this->getUser();
$anchor = $this->anchorOffset;
- $events = array();
$xactions = $this->transactions;
- foreach ($xactions as $key => $xaction) {
- if ($xaction->shouldHide()) {
- unset($xactions[$key]);
- }
- }
-
- $last = null;
- $last_key = null;
- $groups = array();
- foreach ($xactions as $key => $xaction) {
- if ($last && $this->shouldGroupTransactions($last, $xaction)) {
- $groups[$last_key][] = $xaction;
- unset($xactions[$key]);
- } else {
- $last = $xaction;
- $last_key = $key;
- }
- }
-
- foreach ($xactions as $key => $xaction) {
- $xaction->attachTransactionGroup(idx($groups, $key, array()));
-
- $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()) {
- if ($this->isPreview || $this->isDetailView) {
- $details = $this->buildChangeDetails($xaction);
- } else {
- $details = $this->buildChangeDetailsLink($xaction);
- }
- $title = array(
- $title,
- ' ',
- $details,
- );
- }
- $event->setTitle($title);
- if ($this->isPreview) {
- $event->setIsPreview(true);
- } else {
- $event
- ->setDateCreated($xaction->getDateCreated())
- ->setContentSource($xaction->getContentSource())
- ->setAnchor($anchor);
+ $xactions = $this->filterHiddenTransactions($xactions);
+ $xactions = $this->groupRelatedTransactions($xactions);
+ $groups = $this->groupDisplayTransactions($xactions);
+ $events = array();
+ foreach ($groups as $group) {
+ $group_event = null;
+ foreach ($group as $xaction) {
+ $event = $this->renderEvent($xaction, $group, $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 (!$group_event) {
+ $group_event = $event;
+ } else {
+ $group_event->addEventToGroup($event);
}
}
-
- $content = $this->renderTransactionContent($xaction);
- if ($content) {
- $event->appendChild($content);
- }
-
- $events[] = $event;
+ $events[] = $group_event;
}
return $events;
}
public function render() {
if (!$this->getObjectPHID()) {
throw new Exception("Call setObjectPHID() before 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,
'objectPHID' => $this->getObjectPHID(),
'nextAnchor' => $this->anchorOffset + count($events),
));
}
return $view->render();
}
protected 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,
'style' => 'display: none',
'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,
'meta' => array(
'hideIDs' => array($hide_id, $content_id),
'showIDs' => array($show_id),
),
),
pht('(Hide Details)'));
$content = phutil_tag(
'div',
array(
'id' => $content_id,
'class' => 'phabricator-timeline-change-details',
),
$xaction->renderChangeDetails($this->getUser()));
return array(
$show_more,
$hide_more,
$content,
);
}
private function buildChangeDetailsLink(
PhabricatorApplicationTransaction $xaction) {
return javelin_tag(
'a',
array(
'href' => '/transactions/detail/'.$xaction->getPHID().'/',
'sigil' => 'transaction-detail',
'mustcapture' => true,
'meta' => array(
'anchor' => $this->anchorOffset,
),
),
pht('(Show Details)'));
}
protected function shouldGroupTransactions(
PhabricatorApplicationTransaction $u,
PhabricatorApplicationTransaction $v) {
return false;
}
protected function renderTransactionContent(
PhabricatorApplicationTransaction $xaction) {
$field = PhabricatorApplicationTransactionComment::MARKUP_FIELD_COMMENT;
$engine = $this->getOrBuildEngine();
$comment = $xaction->getComment();
if ($xaction->hasComment()) {
if ($comment->getIsDeleted()) {
return phutil_tag(
'em',
array(),
pht('This comment has been deleted.'));
} else {
return $engine->getOutput($comment, $field);
}
}
return null;
}
+ private function filterHiddenTransactions(array $xactions) {
+ foreach ($xactions as $key => $xaction) {
+ if ($xaction->shouldHide()) {
+ unset($xactions[$key]);
+ }
+ }
+ return $xactions;
+ }
+
+ private function groupRelatedTransactions(array $xactions) {
+ $last = null;
+ $last_key = null;
+ $groups = array();
+ foreach ($xactions as $key => $xaction) {
+ if ($last && $this->shouldGroupTransactions($last, $xaction)) {
+ $groups[$last_key][] = $xaction;
+ unset($xactions[$key]);
+ } else {
+ $last = $xaction;
+ $last_key = $key;
+ }
+ }
+
+ foreach ($xactions as $key => $xaction) {
+ $xaction->attachTransactionGroup(idx($groups, $key, array()));
+ }
+
+ return $xactions;
+ }
+
+ private function groupDisplayTransactions(array $xactions) {
+ $groups = array();
+ $group = array();
+ foreach ($xactions as $xaction) {
+ if ($xaction->shouldDisplayGroupWith($group)) {
+ $group[] = $xaction;
+ } else {
+ if ($group) {
+ $groups[] = $group;
+ }
+ $group = array($xaction);
+ }
+ }
+
+ if ($group) {
+ $groups[] = $group;
+ }
+
+ foreach ($groups as $key => $group) {
+ $group = msort($group, 'getActionStrength');
+ $group = array_reverse($group);
+ $groups[$key] = $group;
+ }
+
+ return $groups;
+ }
+
+ private function renderEvent(
+ PhabricatorApplicationTransaction $xaction,
+ array $group,
+ $anchor) {
+ $viewer = $this->getUser();
+
+ $event = id(new PhabricatorTimelineEventView())
+ ->setUser($viewer)
+ ->setTransactionPHID($xaction->getPHID())
+ ->setUserHandle($xaction->getHandle($xaction->getAuthorPHID()))
+ ->setIcon($xaction->getIcon())
+ ->setColor($xaction->getColor());
+
+ if (!$this->shouldSuppressTitle($xaction, $group)) {
+ $title = $xaction->getTitle();
+ if ($xaction->hasChangeDetails()) {
+ if ($this->isPreview || $this->isDetailView) {
+ $details = $this->buildChangeDetails($xaction);
+ } else {
+ $details = $this->buildChangeDetailsLink($xaction);
+ }
+ $title = array(
+ $title,
+ ' ',
+ $details,
+ );
+ }
+ $event->setTitle($title);
+ }
+
+ if ($this->isPreview) {
+ $event->setIsPreview(true);
+ } else {
+ $event
+ ->setDateCreated($xaction->getDateCreated())
+ ->setContentSource($xaction->getContentSource())
+ ->setAnchor($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(
+ $viewer,
+ $xaction,
+ $can_edit);
+ if ($has_edit_capability) {
+ $event->setIsEditable(true);
+ }
+ }
+ }
+
+ $content = $this->renderTransactionContent($xaction);
+ if ($content) {
+ $event->appendChild($content);
+ }
+
+ return $event;
+ }
+
+ private function shouldSuppressTitle(
+ PhabricatorApplicationTransaction $xaction,
+ array $group) {
+
+ // This is a little hard-coded, but we don't have any other reasonable
+ // cases for now. Suppress "commented on" if there are other actions in
+ // the display group.
+
+ if (count($group) > 1) {
+ $type_comment = PhabricatorTransactions::TYPE_COMMENT;
+ if ($xaction->getTransactionType() == $type_comment) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
}
diff --git a/src/view/layout/PhabricatorTimelineEventView.php b/src/view/layout/PhabricatorTimelineEventView.php
index f7999c7e91..54f226dbbb 100644
--- a/src/view/layout/PhabricatorTimelineEventView.php
+++ b/src/view/layout/PhabricatorTimelineEventView.php
@@ -1,374 +1,376 @@
<?php
final class PhabricatorTimelineEventView extends AphrontView {
private $userHandle;
private $title;
private $icon;
private $color;
private $classes = array();
private $contentSource;
private $dateCreated;
private $anchor;
private $isEditable;
private $isEdited;
private $transactionPHID;
private $isPreview;
private $eventGroup = array();
public function setTransactionPHID($transaction_phid) {
$this->transactionPHID = $transaction_phid;
return $this;
}
public function getTransactionPHID() {
return $this->transactionPHID;
}
public function setIsEdited($is_edited) {
$this->isEdited = $is_edited;
return $this;
}
public function getIsEdited() {
return $this->isEdited;
}
public function setIsPreview($is_preview) {
$this->isPreview = $is_preview;
return $this;
}
public function getIsPreview() {
return $this->isPreview;
}
public function setIsEditable($is_editable) {
$this->isEditable = $is_editable;
return $this;
}
public function getIsEditable() {
return $this->isEditable;
}
public function setDateCreated($date_created) {
$this->dateCreated = $date_created;
return $this;
}
public function getDateCreated() {
return $this->dateCreated;
}
public function setContentSource(PhabricatorContentSource $content_source) {
$this->contentSource = $content_source;
return $this;
}
public function getContentSource() {
return $this->contentSource;
}
public function setUserHandle(PhabricatorObjectHandle $handle) {
$this->userHandle = $handle;
return $this;
}
public function setAnchor($anchor) {
$this->anchor = $anchor;
return $this;
}
public function setTitle($title) {
$this->title = $title;
return $this;
}
public function addClass($class) {
$this->classes[] = $class;
return $this;
}
public function setIcon($icon) {
$this->icon = $icon;
return $this;
}
public function setColor($color) {
$this->color = $color;
return $this;
}
public function getEventGroup() {
return array_merge(array($this), $this->eventGroup);
}
public function addEventToGroup(PhabricatorTimelineEventView $event) {
$this->eventGroup[] = $event;
return $this;
}
protected function renderEventTitle($is_first_event, $force_icon) {
$title = $this->title;
if (($title === null) && !$this->hasChildren()) {
$title = '';
}
if ($is_first_event) {
$extra = array();
$is_first_extra = true;
foreach ($this->getEventGroup() as $event) {
- $extra[] = $this->renderExtra($is_first_extra);
+ $extra[] = $event->renderExtra($is_first_extra);
$is_first_extra = false;
}
+ $extra = array_reverse($extra);
+ $extra = array_mergev($extra);
$extra = phutil_tag(
'span',
array(
'class' => 'phabricator-timeline-extra',
),
- phutil_implode_html(" \xC2\xB7 ", array_mergev($extra)));
+ phutil_implode_html(" \xC2\xB7 ", $extra));
} else {
$extra = null;
}
if ($title !== null || $extra) {
$title_classes = array();
$title_classes[] = 'phabricator-timeline-title';
$icon = null;
if ($this->icon || $force_icon) {
$title_classes[] = 'phabricator-timeline-title-with-icon';
}
if ($this->icon) {
$fill_classes = array();
$fill_classes[] = 'phabricator-timeline-icon-fill';
if ($this->color) {
$fill_classes[] = 'phabricator-timeline-icon-fill-'.$this->color;
}
$icon = phutil_tag(
'span',
array(
'class' => implode(' ', $fill_classes),
),
phutil_tag(
'span',
array(
'class' => 'phabricator-timeline-icon sprite-icons '.
'icons-'.$this->icon.'-white',
),
''));
}
$title = phutil_tag(
'div',
array(
'class' => implode(' ', $title_classes),
),
array($icon, $title, $extra));
}
return $title;
}
public function render() {
$events = $this->getEventGroup();
// Move events with icons first.
$icon_keys = array();
foreach ($this->getEventGroup() as $key => $event) {
if ($event->icon) {
$icon_keys[] = $key;
}
}
$events = array_select_keys($events, $icon_keys) + $events;
$force_icon = (bool)$icon_keys;
$group_titles = array();
$group_children = array();
$is_first_event = true;
foreach ($events as $event) {
$group_titles[] = $event->renderEventTitle($is_first_event, $force_icon);
$is_first_event = false;
if ($event->hasChildren()) {
$group_children[] = $event->renderChildren();
}
}
$wedge = phutil_tag(
'div',
array(
'class' => 'phabricator-timeline-wedge phabricator-timeline-border',
),
'');
$image_uri = $this->userHandle->getImageURI();
$image = phutil_tag(
'div',
array(
'style' => 'background-image: url('.$image_uri.')',
'class' => 'phabricator-timeline-image',
),
'');
$content_classes = array();
$content_classes[] = 'phabricator-timeline-content';
$classes = array();
$classes[] = 'phabricator-timeline-event-view';
if ($group_children) {
$classes[] = 'phabricator-timeline-major-event';
$content = phutil_tag(
'div',
array(
'class' => 'phabricator-timeline-inner-content',
),
array(
$group_titles,
phutil_tag(
'div',
array(
'class' => 'phabricator-timeline-core-content',
),
$group_children),
));
} else {
$classes[] = 'phabricator-timeline-minor-event';
$content = $group_titles;
}
$content = phutil_tag(
'div',
array(
'class' => 'phabricator-timeline-group phabricator-timeline-border',
),
$content);
$content = phutil_tag(
'div',
array(
'class' => implode(' ', $content_classes),
),
array($image, $wedge, $content));
$outer_classes = $this->classes;
$outer_classes[] = 'phabricator-timeline-shell';
$color = null;
foreach ($this->getEventGroup() as $event) {
if ($event->color) {
$color = $event->color;
break;
}
}
if ($color) {
$outer_classes[] = 'phabricator-timeline-'.$color;
}
$sigil = null;
$meta = null;
if ($this->getTransactionPHID()) {
$sigil = 'transaction';
$meta = array(
'phid' => $this->getTransactionPHID(),
'anchor' => $this->anchor,
);
}
return javelin_tag(
'div',
array(
'class' => implode(' ', $outer_classes),
'id' => $this->anchor ? 'anchor-'.$this->anchor : null,
'sigil' => $sigil,
'meta' => $meta,
),
phutil_tag(
'div',
array(
'class' => implode(' ', $classes),
),
$content));
}
private function renderExtra($is_first_extra) {
$extra = array();
if ($this->getIsPreview()) {
$extra[] = pht('PREVIEW');
} else {
$xaction_phid = $this->getTransactionPHID();
if ($this->getIsEdited()) {
$extra[] = javelin_tag(
'a',
array(
'href' => '/transactions/history/'.$xaction_phid.'/',
'sigil' => 'workflow',
),
pht('Edited'));
}
if ($this->getIsEditable()) {
$extra[] = javelin_tag(
'a',
array(
'href' => '/transactions/edit/'.$xaction_phid.'/',
'sigil' => 'workflow transaction-edit',
),
pht('Edit'));
}
if ($is_first_extra) {
$source = $this->getContentSource();
if ($source) {
$extra[] = id(new PhabricatorContentSourceView())
->setContentSource($source)
->setUser($this->getUser())
->render();
}
$date_created = null;
foreach ($this->getEventGroup() as $event) {
if ($event->getDateCreated()) {
if ($date_created === null) {
$date_created = $event->getDateCreated();
} else {
$date_created = min($event->getDateCreated(), $date_created);
}
}
}
if ($date_created) {
$date = phabricator_datetime(
$this->getDateCreated(),
$this->getUser());
if ($this->anchor) {
Javelin::initBehavior('phabricator-watch-anchor');
$anchor = id(new PhabricatorAnchorView())
->setAnchorName($this->anchor)
->render();
$date = array(
$anchor,
phutil_tag(
'a',
array(
'href' => '#'.$this->anchor,
),
$date),
);
}
$extra[] = $date;
}
}
}
return $extra;
}
}

File Metadata

Mime Type
text/x-diff
Expires
Fri, Oct 31, 12:36 PM (1 d, 20 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
312186
Default Alt Text
(41 KB)

Event Timeline