Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F2894612
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Advanced/Developer...
View Handle
View Hovercard
Size
108 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/src/applications/differential/doorkeeper/DifferentialDoorkeeperRevisionFeedStoryPublisher.php b/src/applications/differential/doorkeeper/DifferentialDoorkeeperRevisionFeedStoryPublisher.php
index 5bfd5ee836..ae662ab245 100644
--- a/src/applications/differential/doorkeeper/DifferentialDoorkeeperRevisionFeedStoryPublisher.php
+++ b/src/applications/differential/doorkeeper/DifferentialDoorkeeperRevisionFeedStoryPublisher.php
@@ -1,106 +1,94 @@
<?php
final class DifferentialDoorkeeperRevisionFeedStoryPublisher
extends DoorkeeperFeedStoryPublisher {
public function canPublishStory(PhabricatorFeedStory $story, $object) {
return ($object instanceof DifferentialRevision);
}
public function isStoryAboutObjectCreation($object) {
$story = $this->getFeedStory();
$action = $story->getStoryData()->getValue('action');
return ($action == DifferentialAction::ACTION_CREATE);
}
public function isStoryAboutObjectClosure($object) {
$story = $this->getFeedStory();
$action = $story->getStoryData()->getValue('action');
return ($action == DifferentialAction::ACTION_CLOSE) ||
($action == DifferentialAction::ACTION_ABANDON);
}
public function willPublishStory($object) {
return id(new DifferentialRevisionQuery())
->setViewer($this->getViewer())
->withIDs(array($object->getID()))
->needRelationships(true)
->executeOne();
}
public function getOwnerPHID($object) {
return $object->getAuthorPHID();
}
public function getActiveUserPHIDs($object) {
$status = $object->getStatus();
if ($status == ArcanistDifferentialRevisionStatus::NEEDS_REVIEW) {
return $object->getReviewers();
} else {
return array();
}
}
public function getPassiveUserPHIDs($object) {
$status = $object->getStatus();
if ($status == ArcanistDifferentialRevisionStatus::NEEDS_REVIEW) {
return array();
} else {
return $object->getReviewers();
}
}
public function getCCUserPHIDs($object) {
return $object->getCCPHIDs();
}
public function getObjectTitle($object) {
$prefix = $this->getTitlePrefix($object);
$lines = new PhutilNumber($object->getLineCount());
$lines = pht('[Request, %d lines]', $lines);
$id = $object->getID();
$title = $object->getTitle();
return ltrim("{$prefix} {$lines} D{$id}: {$title}");
}
public function getObjectURI($object) {
return PhabricatorEnv::getProductionURI('/D'.$object->getID());
}
public function getObjectDescription($object) {
return $object->getSummary();
}
public function isObjectClosed($object) {
return $object->isClosed();
}
public function getResponsibilityTitle($object) {
$prefix = $this->getTitlePrefix($object);
return pht('%s Review Request', $prefix);
}
- public function getStoryText($object) {
- $implied_context = $this->getRenderWithImpliedContext();
-
- $story = $this->getFeedStory();
- if ($story instanceof PhabricatorFeedStoryDifferential) {
- $text = $story->renderForAsanaBridge($implied_context);
- } else {
- $text = $story->renderText();
- }
- return $text;
- }
-
private function getTitlePrefix(DifferentialRevision $revision) {
$prefix_key = 'metamta.differential.subject-prefix';
return PhabricatorEnv::getEnvConfig($prefix_key);
}
}
diff --git a/src/applications/differential/storage/DifferentialTransaction.php b/src/applications/differential/storage/DifferentialTransaction.php
index 55c589b6e3..4cc8c4e803 100644
--- a/src/applications/differential/storage/DifferentialTransaction.php
+++ b/src/applications/differential/storage/DifferentialTransaction.php
@@ -1,600 +1,654 @@
<?php
final class DifferentialTransaction extends PhabricatorApplicationTransaction {
private $isCommandeerSideEffect;
public function setIsCommandeerSideEffect($is_side_effect) {
$this->isCommandeerSideEffect = $is_side_effect;
return $this;
}
public function getIsCommandeerSideEffect() {
return $this->isCommandeerSideEffect;
}
const TYPE_INLINE = 'differential:inline';
const TYPE_UPDATE = 'differential:update';
const TYPE_ACTION = 'differential:action';
const TYPE_STATUS = 'differential:status';
public function getApplicationName() {
return 'differential';
}
public function getApplicationTransactionType() {
return DifferentialRevisionPHIDType::TYPECONST;
}
public function getApplicationTransactionCommentObject() {
return new DifferentialTransactionComment();
}
public function shouldHide() {
$old = $this->getOldValue();
$new = $this->getNewValue();
switch ($this->getTransactionType()) {
case self::TYPE_UPDATE:
// Older versions of this transaction have an ID for the new value,
// and/or do not record the old value. Only hide the transaction if
// the new value is a PHID, indicating that this is a newer style
// transaction.
if ($old === null) {
if (phid_get_type($new) == DifferentialDiffPHIDType::TYPECONST) {
return true;
}
}
break;
case PhabricatorTransactions::TYPE_EDGE:
$add = array_diff_key($new, $old);
$rem = array_diff_key($old, $new);
// Hide metadata-only edge transactions. These correspond to users
// accepting or rejecting revisions, but the change is always explicit
// because of the TYPE_ACTION transaction. Rendering these transactions
// just creates clutter.
if (!$add && !$rem) {
return true;
}
break;
}
return parent::shouldHide();
}
public function shouldHideForMail(array $xactions) {
switch ($this->getTransactionType()) {
case self::TYPE_INLINE:
// Hide inlines when rendering mail transactions if any other
// transaction type exists.
foreach ($xactions as $xaction) {
if ($xaction->getTransactionType() != self::TYPE_INLINE) {
return true;
}
}
// If only inline transactions exist, we just render the first one.
return ($this !== head($xactions));
}
return parent::shouldHideForMail($xactions);
}
public function getBodyForMail() {
switch ($this->getTransactionType()) {
case self::TYPE_INLINE:
// Don't render inlines into the mail body; they render into a special
// section immediately after the body instead.
return null;
}
return parent::getBodyForMail();
}
public function getRequiredHandlePHIDs() {
$phids = parent::getRequiredHandlePHIDs();
$old = $this->getOldValue();
$new = $this->getNewValue();
switch ($this->getTransactionType()) {
case self::TYPE_ACTION:
if ($new == DifferentialAction::ACTION_CLOSE &&
$this->getMetadataValue('isCommitClose')) {
$phids[] = $this->getMetadataValue('commitPHID');
if ($this->getMetadataValue('committerPHID')) {
$phids[] = $this->getMetadataValue('committerPHID');
}
if ($this->getMetadataValue('authorPHID')) {
$phids[] = $this->getMetadataValue('authorPHID');
}
}
break;
case self::TYPE_UPDATE:
if ($new) {
$phids[] = $new;
}
break;
}
return $phids;
}
public function getActionStrength() {
switch ($this->getTransactionType()) {
case self::TYPE_ACTION:
return 3;
case self::TYPE_UPDATE:
return 2;
case self::TYPE_INLINE:
return 0.25;
}
return parent::getActionStrength();
}
public function getActionName() {
switch ($this->getTransactionType()) {
case self::TYPE_INLINE:
return pht('Commented On');
case self::TYPE_UPDATE:
$old = $this->getOldValue();
if ($old === null) {
return pht('Request');
} else {
return pht('Updated');
}
case self::TYPE_ACTION:
$map = array(
DifferentialAction::ACTION_ACCEPT => pht('Accepted'),
DifferentialAction::ACTION_REJECT => pht('Requested Changes To'),
DifferentialAction::ACTION_RETHINK => pht('Planned Changes To'),
DifferentialAction::ACTION_ABANDON => pht('Abandoned'),
DifferentialAction::ACTION_CLOSE => pht('Closed'),
DifferentialAction::ACTION_REQUEST => pht('Requested A Review Of'),
DifferentialAction::ACTION_RESIGN => pht('Resigned From'),
DifferentialAction::ACTION_ADDREVIEWERS => pht('Added Reviewers'),
DifferentialAction::ACTION_CLAIM => pht('Commandeered'),
DifferentialAction::ACTION_REOPEN => pht('Reopened'),
);
$name = idx($map, $this->getNewValue());
if ($name !== null) {
return $name;
}
break;
}
return parent::getActionName();
}
public function getMailTags() {
$tags = array();
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_SUBSCRIBERS;
$tags[] = MetaMTANotificationType::TYPE_DIFFERENTIAL_CC;
break;
case self::TYPE_ACTION:
switch ($this->getNewValue()) {
case DifferentialAction::ACTION_CLOSE:
$tags[] = MetaMTANotificationType::TYPE_DIFFERENTIAL_CLOSED;
break;
}
break;
case self::TYPE_UPDATE:
$old = $this->getOldValue();
if ($old === null) {
$tags[] = MetaMTANotificationType::TYPE_DIFFERENTIAL_REVIEW_REQUEST;
} else {
$tags[] = MetaMTANotificationType::TYPE_DIFFERENTIAL_UPDATED;
}
break;
case PhabricatorTransactions::TYPE_EDGE:
switch ($this->getMetadataValue('edge:type')) {
case PhabricatorEdgeConfig::TYPE_DREV_HAS_REVIEWER:
$tags[] = MetaMTANotificationType::TYPE_DIFFERENTIAL_REVIEWERS;
break;
}
break;
case PhabricatorTransactions::TYPE_COMMENT:
case self::TYPE_INLINE:
$tags[] = MetaMTANotificationType::TYPE_DIFFERENTIAL_COMMENT;
break;
}
if (!$tags) {
$tags[] = MetaMTANotificationType::TYPE_DIFFERENTIAL_OTHER;
}
return $tags;
}
public function getTitle() {
$author_phid = $this->getAuthorPHID();
$author_handle = $this->renderHandleLink($author_phid);
$old = $this->getOldValue();
$new = $this->getNewValue();
switch ($this->getTransactionType()) {
case self::TYPE_INLINE:
return pht(
'%s added inline comments.',
$author_handle);
case self::TYPE_UPDATE:
if ($this->getMetadataValue('isCommitUpdate')) {
return pht(
'This revision was automatically updated to reflect the '.
'committed changes.');
} else if ($new) {
// TODO: Migrate to PHIDs and use handles here?
if (phid_get_type($new) == DifferentialDiffPHIDType::TYPECONST) {
return pht(
'%s updated this revision to %s.',
$author_handle,
$this->renderHandleLink($new));
} else {
return pht(
'%s updated this revision.',
$author_handle);
}
} else {
return pht(
'%s updated this revision.',
$author_handle);
}
case self::TYPE_ACTION:
switch ($new) {
case DifferentialAction::ACTION_CLOSE:
if (!$this->getMetadataValue('isCommitClose')) {
return DifferentialAction::getBasicStoryText(
$new,
$author_handle);
}
$commit_name = $this->renderHandleLink(
$this->getMetadataValue('commitPHID'));
$committer_phid = $this->getMetadataValue('committerPHID');
$author_phid = $this->getMetadataValue('authorPHID');
if ($this->getHandleIfExists($committer_phid)) {
$committer_name = $this->renderHandleLink($committer_phid);
} else {
$committer_name = $this->getMetadataValue('committerName');
}
if ($this->getHandleIfExists($author_phid)) {
$author_name = $this->renderHandleLink($author_phid);
} else {
$author_name = $this->getMetadataValue('authorName');
}
if ($committer_name && ($committer_name != $author_name)) {
return pht(
'Closed by commit %s (authored by %s, committed by %s).',
$commit_name,
$author_name,
$committer_name);
} else {
return pht(
'Closed by commit %s (authored by %s).',
$commit_name,
$author_name);
}
break;
default:
return DifferentialAction::getBasicStoryText($new, $author_handle);
}
break;
case self::TYPE_STATUS:
switch ($this->getNewValue()) {
case ArcanistDifferentialRevisionStatus::ACCEPTED:
return pht(
'This revision is now accepted and ready to land.');
case ArcanistDifferentialRevisionStatus::NEEDS_REVISION:
return pht(
'This revision now requires changes to proceed.');
case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW:
return pht(
'This revision now requires review to proceed.');
}
}
return parent::getTitle();
}
public function getTitleForFeed(PhabricatorFeedStory $story) {
$author_phid = $this->getAuthorPHID();
$object_phid = $this->getObjectPHID();
$old = $this->getOldValue();
$new = $this->getNewValue();
$author_link = $this->renderHandleLink($author_phid);
$object_link = $this->renderHandleLink($object_phid);
switch ($this->getTransactionType()) {
case self::TYPE_INLINE:
return pht(
'%s added inline comments to %s.',
$author_link,
$object_link);
case self::TYPE_UPDATE:
return pht(
'%s updated the diff for %s.',
$author_link,
$object_link);
case self::TYPE_ACTION:
switch ($new) {
case DifferentialAction::ACTION_ACCEPT:
return pht(
'%s accepted %s.',
$author_link,
$object_link);
case DifferentialAction::ACTION_REJECT:
return pht(
'%s requested changes to %s.',
$author_link,
$object_link);
case DifferentialAction::ACTION_RETHINK:
return pht(
'%s planned changes to %s.',
$author_link,
$object_link);
case DifferentialAction::ACTION_ABANDON:
return pht(
'%s abandoned %s.',
$author_link,
$object_link);
case DifferentialAction::ACTION_CLOSE:
if (!$this->getMetadataValue('isCommitClose')) {
return pht(
'%s closed %s.',
$author_link,
$object_link);
} else {
$commit_name = $this->renderHandleLink(
$this->getMetadataValue('commitPHID'));
$committer_phid = $this->getMetadataValue('committerPHID');
$author_phid = $this->getMetadataValue('authorPHID');
if ($this->getHandleIfExists($committer_phid)) {
$committer_name = $this->renderHandleLink($committer_phid);
} else {
$committer_name = $this->getMetadataValue('committerName');
}
if ($this->getHandleIfExists($author_phid)) {
$author_name = $this->renderHandleLink($author_phid);
} else {
$author_name = $this->getMetadataValue('authorName');
}
if ($committer_name && ($committer_name != $author_name)) {
return pht(
'%s closed %s by commit %s (authored by %s).',
$author_link,
$object_link,
$commit_name,
$author_name);
} else {
return pht(
'%s closed %s by commit %s.',
$author_link,
$object_link,
$commit_name);
}
}
break;
case DifferentialAction::ACTION_REQUEST:
return pht(
'%s requested review of %s.',
$author_link,
$object_link);
case DifferentialAction::ACTION_RECLAIM:
return pht(
'%s reclaimed %s.',
$author_link,
$object_link);
case DifferentialAction::ACTION_RESIGN:
return pht(
'%s resigned from %s.',
$author_link,
$object_link);
case DifferentialAction::ACTION_CLAIM:
return pht(
'%s commandeered %s.',
$author_link,
$object_link);
case DifferentialAction::ACTION_REOPEN:
return pht(
'%s reopened %s.',
$author_link,
$object_link);
}
break;
case self::TYPE_STATUS:
switch ($this->getNewValue()) {
case ArcanistDifferentialRevisionStatus::ACCEPTED:
return pht(
'%s is now accepted and ready to land.',
$object_link);
case ArcanistDifferentialRevisionStatus::NEEDS_REVISION:
return pht(
'%s now requires changes to proceed.',
$object_link);
case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW:
return pht(
'%s now requires review to proceed.',
$object_link);
}
}
return parent::getTitleForFeed($story);
}
public function getIcon() {
switch ($this->getTransactionType()) {
case self::TYPE_INLINE:
return 'fa-comment';
case self::TYPE_UPDATE:
return 'fa-refresh';
case self::TYPE_STATUS:
switch ($this->getNewValue()) {
case ArcanistDifferentialRevisionStatus::ACCEPTED:
return 'fa-check';
case ArcanistDifferentialRevisionStatus::NEEDS_REVISION:
return 'fa-times';
case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW:
return 'fa-undo';
}
break;
case self::TYPE_ACTION:
switch ($this->getNewValue()) {
case DifferentialAction::ACTION_CLOSE:
return 'fa-check';
case DifferentialAction::ACTION_ACCEPT:
return 'fa-check-circle-o';
case DifferentialAction::ACTION_REJECT:
return 'fa-times-circle-o';
case DifferentialAction::ACTION_ABANDON:
return 'fa-plane';
case DifferentialAction::ACTION_RETHINK:
return 'fa-headphones';
case DifferentialAction::ACTION_REQUEST:
return 'fa-refresh';
case DifferentialAction::ACTION_RECLAIM:
case DifferentialAction::ACTION_REOPEN:
return 'fa-bullhorn';
case DifferentialAction::ACTION_RESIGN:
return 'fa-flag';
case DifferentialAction::ACTION_CLAIM:
return 'fa-flag';
}
case PhabricatorTransactions::TYPE_EDGE:
switch ($this->getMetadataValue('edge:type')) {
case PhabricatorEdgeConfig::TYPE_DREV_HAS_REVIEWER:
return 'fa-user';
}
}
return parent::getIcon();
}
public function shouldDisplayGroupWith(array $group) {
// Never group status changes with other types of actions, they're indirect
// and don't make sense when combined with direct actions.
$type_status = self::TYPE_STATUS;
if ($this->getTransactionType() == $type_status) {
return false;
}
foreach ($group as $xaction) {
if ($xaction->getTransactionType() == $type_status) {
return false;
}
}
return parent::shouldDisplayGroupWith($group);
}
public function getColor() {
switch ($this->getTransactionType()) {
case self::TYPE_UPDATE:
return PhabricatorTransactions::COLOR_SKY;
case self::TYPE_STATUS:
switch ($this->getNewValue()) {
case ArcanistDifferentialRevisionStatus::ACCEPTED:
return PhabricatorTransactions::COLOR_GREEN;
case ArcanistDifferentialRevisionStatus::NEEDS_REVISION:
return PhabricatorTransactions::COLOR_RED;
case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW:
return PhabricatorTransactions::COLOR_ORANGE;
}
break;
case self::TYPE_ACTION:
switch ($this->getNewValue()) {
case DifferentialAction::ACTION_CLOSE:
return PhabricatorTransactions::COLOR_BLUE;
case DifferentialAction::ACTION_ACCEPT:
return PhabricatorTransactions::COLOR_GREEN;
case DifferentialAction::ACTION_REJECT:
return PhabricatorTransactions::COLOR_RED;
case DifferentialAction::ACTION_ABANDON:
return PhabricatorTransactions::COLOR_BLACK;
case DifferentialAction::ACTION_RETHINK:
return PhabricatorTransactions::COLOR_RED;
case DifferentialAction::ACTION_REQUEST:
return PhabricatorTransactions::COLOR_SKY;
case DifferentialAction::ACTION_RECLAIM:
return PhabricatorTransactions::COLOR_SKY;
case DifferentialAction::ACTION_REOPEN:
return PhabricatorTransactions::COLOR_SKY;
case DifferentialAction::ACTION_RESIGN:
return PhabricatorTransactions::COLOR_ORANGE;
case DifferentialAction::ACTION_CLAIM:
return PhabricatorTransactions::COLOR_YELLOW;
}
}
return parent::getColor();
}
public function getNoEffectDescription() {
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_EDGE:
switch ($this->getMetadataValue('edge:type')) {
case PhabricatorEdgeConfig::TYPE_DREV_HAS_REVIEWER:
return pht(
'The reviewers you are trying to add are already reviewing '.
'this revision.');
}
break;
case DifferentialTransaction::TYPE_ACTION:
switch ($this->getNewValue()) {
case DifferentialAction::ACTION_CLOSE:
return pht('This revision is already closed.');
case DifferentialAction::ACTION_ABANDON:
return pht('This revision has already been abandoned.');
case DifferentialAction::ACTION_RECLAIM:
return pht(
'You can not reclaim this revision because his revision is '.
'not abandoned.');
case DifferentialAction::ACTION_REOPEN:
return pht(
'You can not reopen this revision because this revision is '.
'not closed.');
case DifferentialAction::ACTION_RETHINK:
return pht('This revision already requires changes.');
case DifferentialAction::ACTION_REQUEST:
return pht('Review is already requested for this revision.');
case DifferentialAction::ACTION_RESIGN:
return pht(
'You can not resign from this revision because you are not '.
'a reviewer.');
case DifferentialAction::ACTION_CLAIM:
return pht(
'You can not commandeer this revision because you already own '.
'it.');
case DifferentialAction::ACTION_ACCEPT:
return pht(
'You have already accepted this revision.');
case DifferentialAction::ACTION_REJECT:
return pht(
'You have already requested changes to this revision.');
}
break;
}
return parent::getNoEffectDescription();
}
+ public function renderAsTextForDoorkeeper(
+ DoorkeeperFeedStoryPublisher $publisher,
+ PhabricatorFeedStory $story,
+ array $xactions) {
+
+ $body = parent::renderAsTextForDoorkeeper($publisher, $story, $xactions);
+
+ $inlines = array();
+ foreach ($xactions as $xaction) {
+ if ($xaction->getTransactionType() == self::TYPE_INLINE) {
+ $inlines[] = $xaction;
+ }
+ }
+
+ // TODO: This is a bit gross, but far less bad than it used to be. It
+ // could be further cleaned up at some point.
+
+ if ($inlines) {
+ $engine = PhabricatorMarkupEngine::newMarkupEngine(array())
+ ->setConfig('viewer', new PhabricatorUser())
+ ->setMode(PhutilRemarkupEngine::MODE_TEXT);
+
+ $body .= "\n\n";
+ $body .= pht('Inline Comments');
+ $body .= "\n";
+
+ $changeset_ids = array();
+ foreach ($inlines as $inline) {
+ $changeset_ids[] = $inline->getComment()->getChangesetID();
+ }
+
+ $changesets = id(new DifferentialChangeset())->loadAllWhere(
+ 'id IN (%Ld)',
+ $changeset_ids);
+
+ foreach ($inlines as $inline) {
+ $comment = $inline->getComment();
+ $changeset = idx($changesets, $comment->getChangesetID());
+ if (!$changeset) {
+ continue;
+ }
+
+ $filename = $changeset->getDisplayFilename();
+ $linenumber = $comment->getLineNumber();
+ $inline_text = $engine->markupText($comment->getContent());
+ $inline_text = rtrim($inline_text);
+
+ $body .= "{$filename}:{$linenumber} {$inline_text}\n";
+ }
+ }
+
+ return $body;
+ }
+
}
diff --git a/src/applications/diffusion/doorkeeper/DiffusionDoorkeeperCommitFeedStoryPublisher.php b/src/applications/diffusion/doorkeeper/DiffusionDoorkeeperCommitFeedStoryPublisher.php
index a8bab3a3f1..088f4b752e 100644
--- a/src/applications/diffusion/doorkeeper/DiffusionDoorkeeperCommitFeedStoryPublisher.php
+++ b/src/applications/diffusion/doorkeeper/DiffusionDoorkeeperCommitFeedStoryPublisher.php
@@ -1,200 +1,188 @@
<?php
final class DiffusionDoorkeeperCommitFeedStoryPublisher
extends DoorkeeperFeedStoryPublisher {
private $auditRequests;
private $activePHIDs;
private $passivePHIDs;
private function getAuditRequests() {
return $this->auditRequests;
}
public function canPublishStory(PhabricatorFeedStory $story, $object) {
return
($story instanceof PhabricatorApplicationTransactionFeedStory) &&
($object instanceof PhabricatorRepositoryCommit);
}
public function isStoryAboutObjectCreation($object) {
// TODO: Although creation stories exist, they currently don't have a
// primary object PHID set, so they'll never make it here because they
// won't pass `canPublishStory()`.
return false;
}
public function isStoryAboutObjectClosure($object) {
// TODO: This isn't quite accurate, but pretty close: check if this story
// is a close (which clearly is about object closure) or is an "Accept" and
// the commit is fully audited (which is almost certainly a closure).
// After ApplicationTransactions, we could annotate feed stories more
// explicitly.
$fully_audited = PhabricatorAuditCommitStatusConstants::FULLY_AUDITED;
$story = $this->getFeedStory();
$xaction = $story->getPrimaryTransaction();
switch ($xaction->getTransactionType()) {
case PhabricatorAuditActionConstants::ACTION:
switch ($xaction->getNewValue()) {
case PhabricatorAuditActionConstants::CLOSE:
return true;
case PhabricatorAuditActionConstants::ACCEPT:
if ($object->getAuditStatus() == $fully_audited) {
return true;
}
break;
}
}
return false;
}
public function willPublishStory($commit) {
$requests = id(new DiffusionCommitQuery())
->setViewer($this->getViewer())
->withPHIDs(array($commit->getPHID()))
->needAuditRequests(true)
->executeOne()
->getAudits();
// TODO: This is messy and should be generalized, but we don't have a good
// query for it yet. Since we run in the daemons, just do the easiest thing
// we can for the moment. Figure out who all of the "active" (need to
// audit) and "passive" (no action necessary) users are.
$auditor_phids = mpull($requests, 'getAuditorPHID');
$objects = id(new PhabricatorObjectQuery())
->setViewer($this->getViewer())
->withPHIDs($auditor_phids)
->execute();
$active = array();
$passive = array();
foreach ($requests as $request) {
$status = $request->getAuditStatus();
$object = idx($objects, $request->getAuditorPHID());
if (!$object) {
continue;
}
$request_phids = array();
if ($object instanceof PhabricatorUser) {
$request_phids = array($object->getPHID());
} else if ($object instanceof PhabricatorOwnersPackage) {
$request_phids = PhabricatorOwnersOwner::loadAffiliatedUserPHIDs(
array($object->getID()));
} else if ($object instanceof PhabricatorProject) {
$project = id(new PhabricatorProjectQuery())
->setViewer($this->getViewer())
->withIDs(array($object->getID()))
->needMembers(true)
->executeOne();
$request_phids = $project->getMemberPHIDs();
} else {
// Dunno what this is.
$request_phids = array();
}
switch ($status) {
case PhabricatorAuditStatusConstants::AUDIT_REQUIRED:
case PhabricatorAuditStatusConstants::AUDIT_REQUESTED:
case PhabricatorAuditStatusConstants::CONCERNED:
$active += array_fuse($request_phids);
break;
default:
$passive += array_fuse($request_phids);
break;
}
}
// Remove "Active" users from the "Passive" list.
$passive = array_diff_key($passive, $active);
$this->activePHIDs = $active;
$this->passivePHIDs = $passive;
$this->auditRequests = $requests;
return $commit;
}
public function getOwnerPHID($object) {
return $object->getAuthorPHID();
}
public function getActiveUserPHIDs($object) {
return $this->activePHIDs;
}
public function getPassiveUserPHIDs($object) {
return $this->passivePHIDs;
}
public function getCCUserPHIDs($object) {
return PhabricatorSubscribersQuery::loadSubscribersForPHID(
$object->getPHID());
}
public function getObjectTitle($object) {
$prefix = $this->getTitlePrefix($object);
$repository = $object->getRepository();
$name = $repository->formatCommitName($object->getCommitIdentifier());
$title = $object->getSummary();
return ltrim("{$prefix} {$name}: {$title}");
}
public function getObjectURI($object) {
$repository = $object->getRepository();
$name = $repository->formatCommitName($object->getCommitIdentifier());
return PhabricatorEnv::getProductionURI('/'.$name);
}
public function getObjectDescription($object) {
$data = $object->loadCommitData();
if ($data) {
return $data->getCommitMessage();
}
return null;
}
public function isObjectClosed($object) {
switch ($object->getAuditStatus()) {
case PhabricatorAuditCommitStatusConstants::NEEDS_AUDIT:
case PhabricatorAuditCommitStatusConstants::CONCERN_RAISED:
case PhabricatorAuditCommitStatusConstants::PARTIALLY_AUDITED:
return false;
default:
return true;
}
}
public function getResponsibilityTitle($object) {
$prefix = $this->getTitlePrefix($object);
return pht('%s Audit', $prefix);
}
- public function getStoryText($object) {
- $implied_context = $this->getRenderWithImpliedContext();
-
- $story = $this->getFeedStory();
- if ($story instanceof PhabricatorFeedStoryAudit) {
- $text = $story->renderForAsanaBridge($implied_context);
- } else {
- $text = $story->renderText();
- }
- return $text;
- }
-
private function getTitlePrefix(PhabricatorRepositoryCommit $commit) {
$prefix_key = 'metamta.diffusion.subject-prefix';
return PhabricatorEnv::getEnvConfig($prefix_key);
}
}
diff --git a/src/applications/doorkeeper/engine/DoorkeeperFeedStoryPublisher.php b/src/applications/doorkeeper/engine/DoorkeeperFeedStoryPublisher.php
index be7aebd892..ef98fdab49 100644
--- a/src/applications/doorkeeper/engine/DoorkeeperFeedStoryPublisher.php
+++ b/src/applications/doorkeeper/engine/DoorkeeperFeedStoryPublisher.php
@@ -1,97 +1,101 @@
<?php
/**
* @task config Configuration
*/
abstract class DoorkeeperFeedStoryPublisher {
private $feedStory;
private $viewer;
private $renderWithImpliedContext;
/* -( Configuration )------------------------------------------------------ */
/**
* Render story text using contextual language to identify the object the
* story is about, instead of the full object name. For example, without
* contextual language a story might render like this:
*
* alincoln created D123: Chop Wood for Log Cabin v2.0
*
* With contextual language, it will render like this instead:
*
* alincoln created this revision.
*
* If the interface where the text will be displayed is specific to an
* individual object (like Asana tasks that represent one review or commit
* are), it's generally more natural to use language that assumes context.
* If the target context may show information about several objects (like
* JIRA issues which can have several linked revisions), it's generally
* more useful not to assume context.
*
* @param bool True to assume object context when rendering.
* @return this
* @task config
*/
public function setRenderWithImpliedContext($render_with_implied_context) {
$this->renderWithImpliedContext = $render_with_implied_context;
return $this;
}
/**
* Determine if rendering should assume object context. For discussion, see
* @{method:setRenderWithImpliedContext}.
*
* @return bool True if rendering should assume object context is implied.
* @task config
*/
public function getRenderWithImpliedContext() {
return $this->renderWithImpliedContext;
}
public function setFeedStory(PhabricatorFeedStory $feed_story) {
$this->feedStory = $feed_story;
return $this;
}
public function getFeedStory() {
return $this->feedStory;
}
public function setViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer;
return $this;
}
public function getViewer() {
return $this->viewer;
}
abstract public function canPublishStory(
PhabricatorFeedStory $story,
$object);
/**
* Hook for publishers to mutate the story object, particularly by loading
* and attaching additional data.
*/
public function willPublishStory($object) {
return $object;
}
+
+ public function getStoryText($object) {
+ return $this->getFeedStory()->renderAsTextForDoorkeeper($this);
+ }
+
abstract public function isStoryAboutObjectCreation($object);
abstract public function isStoryAboutObjectClosure($object);
abstract public function getOwnerPHID($object);
abstract public function getActiveUserPHIDs($object);
abstract public function getPassiveUserPHIDs($object);
abstract public function getCCUserPHIDs($object);
abstract public function getObjectTitle($object);
abstract public function getObjectURI($object);
abstract public function getObjectDescription($object);
abstract public function isObjectClosed($object);
abstract public function getResponsibilityTitle($object);
- abstract public function getStoryText($object);
}
diff --git a/src/applications/feed/constants/PhabricatorFeedStoryTypeConstants.php b/src/applications/feed/constants/PhabricatorFeedStoryTypeConstants.php
index ba1b24892e..ce67ad1c7b 100644
--- a/src/applications/feed/constants/PhabricatorFeedStoryTypeConstants.php
+++ b/src/applications/feed/constants/PhabricatorFeedStoryTypeConstants.php
@@ -1,10 +1,9 @@
<?php
final class PhabricatorFeedStoryTypeConstants
extends PhabricatorFeedConstants {
const STORY_PHRICTION = 'PhabricatorFeedStoryPhriction';
- const STORY_AUDIT = 'PhabricatorFeedStoryAudit';
const STORY_COMMIT = 'PhabricatorFeedStoryCommit';
}
diff --git a/src/applications/feed/story/PhabricatorFeedStory.php b/src/applications/feed/story/PhabricatorFeedStory.php
index c38c05faa3..26bcbca024 100644
--- a/src/applications/feed/story/PhabricatorFeedStory.php
+++ b/src/applications/feed/story/PhabricatorFeedStory.php
@@ -1,502 +1,513 @@
<?php
/**
* Manages rendering and aggregation of a story. A story is an event (like a
* user adding a comment) which may be represented in different forms on
* different channels (like feed, notifications and realtime alerts).
*
* @task load Loading Stories
* @task policy Policy Implementation
*/
abstract class PhabricatorFeedStory
implements
PhabricatorPolicyInterface,
PhabricatorMarkupInterface {
private $data;
private $hasViewed;
private $framed;
private $hovercard = false;
private $renderingTarget = PhabricatorApplicationTransaction::TARGET_HTML;
private $handles = array();
private $objects = array();
private $projectPHIDs = array();
private $markupFieldOutput = array();
/* -( Loading Stories )---------------------------------------------------- */
/**
* Given @{class:PhabricatorFeedStoryData} rows, load them into objects and
* construct appropriate @{class:PhabricatorFeedStory} wrappers for each
* data row.
*
* @param list<dict> List of @{class:PhabricatorFeedStoryData} rows from the
* database.
* @return list<PhabricatorFeedStory> List of @{class:PhabricatorFeedStory}
* objects.
* @task load
*/
public static function loadAllFromRows(array $rows, PhabricatorUser $viewer) {
$stories = array();
$data = id(new PhabricatorFeedStoryData())->loadAllFromArray($rows);
foreach ($data as $story_data) {
$class = $story_data->getStoryType();
try {
$ok =
class_exists($class) &&
is_subclass_of($class, 'PhabricatorFeedStory');
} catch (PhutilMissingSymbolException $ex) {
$ok = false;
}
// If the story type isn't a valid class or isn't a subclass of
// PhabricatorFeedStory, decline to load it.
if (!$ok) {
continue;
}
$key = $story_data->getChronologicalKey();
$stories[$key] = newv($class, array($story_data));
}
$object_phids = array();
$key_phids = array();
foreach ($stories as $key => $story) {
$phids = array();
foreach ($story->getRequiredObjectPHIDs() as $phid) {
$phids[$phid] = true;
}
if ($story->getPrimaryObjectPHID()) {
$phids[$story->getPrimaryObjectPHID()] = true;
}
$key_phids[$key] = $phids;
$object_phids += $phids;
}
$objects = id(new PhabricatorObjectQuery())
->setViewer($viewer)
->withPHIDs(array_keys($object_phids))
->execute();
foreach ($key_phids as $key => $phids) {
if (!$phids) {
continue;
}
$story_objects = array_select_keys($objects, array_keys($phids));
if (count($story_objects) != count($phids)) {
// An object this story requires either does not exist or is not visible
// to the user. Decline to render the story.
unset($stories[$key]);
unset($key_phids[$key]);
continue;
}
$stories[$key]->setObjects($story_objects);
}
// If stories are about PhabricatorProjectInterface objects, load the
// projects the objects are a part of so we can render project tags
// on the stories.
$project_phids = array();
foreach ($objects as $object) {
if ($object instanceof PhabricatorProjectInterface) {
$project_phids[$object->getPHID()] = array();
}
}
if ($project_phids) {
$edge_query = id(new PhabricatorEdgeQuery())
->withSourcePHIDs(array_keys($project_phids))
->withEdgeTypes(
array(
PhabricatorProjectObjectHasProjectEdgeType::EDGECONST,
));
$edge_query->execute();
foreach ($project_phids as $phid => $ignored) {
$project_phids[$phid] = $edge_query->getDestinationPHIDs(array($phid));
}
}
$handle_phids = array();
foreach ($stories as $key => $story) {
foreach ($story->getRequiredHandlePHIDs() as $phid) {
$key_phids[$key][$phid] = true;
}
if ($story->getAuthorPHID()) {
$key_phids[$key][$story->getAuthorPHID()] = true;
}
$object_phid = $story->getPrimaryObjectPHID();
$object_project_phids = idx($project_phids, $object_phid, array());
$story->setProjectPHIDs($object_project_phids);
foreach ($object_project_phids as $dst) {
$key_phids[$key][$dst] = true;
}
$handle_phids += $key_phids[$key];
}
$handles = id(new PhabricatorHandleQuery())
->setViewer($viewer)
->withPHIDs(array_keys($handle_phids))
->execute();
foreach ($key_phids as $key => $phids) {
if (!$phids) {
continue;
}
$story_handles = array_select_keys($handles, array_keys($phids));
$stories[$key]->setHandles($story_handles);
}
// Load and process story markup blocks.
$engine = new PhabricatorMarkupEngine();
$engine->setViewer($viewer);
foreach ($stories as $story) {
foreach ($story->getFieldStoryMarkupFields() as $field) {
$engine->addObject($story, $field);
}
}
$engine->process();
foreach ($stories as $story) {
foreach ($story->getFieldStoryMarkupFields() as $field) {
$story->setMarkupFieldOutput(
$field,
$engine->getOutput($story, $field));
}
}
return $stories;
}
public function setMarkupFieldOutput($field, $output) {
$this->markupFieldOutput[$field] = $output;
return $this;
}
public function getMarkupFieldOutput($field) {
if (!array_key_exists($field, $this->markupFieldOutput)) {
throw new Exception(
pht(
'Trying to retrieve markup field key "%s", but this feed story '.
'did not request it be rendered.',
$field));
}
return $this->markupFieldOutput[$field];
}
public function setHovercard($hover) {
$this->hovercard = $hover;
return $this;
}
public function setRenderingTarget($target) {
$this->validateRenderingTarget($target);
$this->renderingTarget = $target;
return $this;
}
public function getRenderingTarget() {
return $this->renderingTarget;
}
private function validateRenderingTarget($target) {
switch ($target) {
case PhabricatorApplicationTransaction::TARGET_HTML:
case PhabricatorApplicationTransaction::TARGET_TEXT:
break;
default:
throw new Exception('Unknown rendering target: '.$target);
break;
}
}
public function setObjects(array $objects) {
$this->objects = $objects;
return $this;
}
public function getObject($phid) {
$object = idx($this->objects, $phid);
if (!$object) {
throw new Exception(
"Story is asking for an object it did not request ('{$phid}')!");
}
return $object;
}
public function getPrimaryObject() {
$phid = $this->getPrimaryObjectPHID();
if (!$phid) {
throw new Exception('Story has no primary object!');
}
return $this->getObject($phid);
}
public function getPrimaryObjectPHID() {
return null;
}
final public function __construct(PhabricatorFeedStoryData $data) {
$this->data = $data;
}
abstract public function renderView();
+ public function renderAsTextForDoorkeeper(
+ DoorkeeperFeedStoryPublisher $publisher) {
+
+ // TODO: This (and text rendering) should be properly abstract and
+ // universal. However, this is far less bad than it used to be, and we
+ // need to clean up more old feed code to really make this reasonable.
+
+ return pht(
+ '(Unable to render story of class %s for Doorkeeper.)',
+ get_class($this));
+ }
public function getRequiredHandlePHIDs() {
return array();
}
public function getRequiredObjectPHIDs() {
return array();
}
public function setHasViewed($has_viewed) {
$this->hasViewed = $has_viewed;
return $this;
}
public function getHasViewed() {
return $this->hasViewed;
}
final public function setFramed($framed) {
$this->framed = $framed;
return $this;
}
final public function setHandles(array $handles) {
assert_instances_of($handles, 'PhabricatorObjectHandle');
$this->handles = $handles;
return $this;
}
final protected function getObjects() {
return $this->objects;
}
final protected function getHandles() {
return $this->handles;
}
final protected function getHandle($phid) {
if (isset($this->handles[$phid])) {
if ($this->handles[$phid] instanceof PhabricatorObjectHandle) {
return $this->handles[$phid];
}
}
$handle = new PhabricatorObjectHandle();
$handle->setPHID($phid);
$handle->setName("Unloaded Object '{$phid}'");
return $handle;
}
final public function getStoryData() {
return $this->data;
}
final public function getEpoch() {
return $this->getStoryData()->getEpoch();
}
final public function getChronologicalKey() {
return $this->getStoryData()->getChronologicalKey();
}
final public function getValue($key, $default = null) {
return $this->getStoryData()->getValue($key, $default);
}
final public function getAuthorPHID() {
return $this->getStoryData()->getAuthorPHID();
}
final protected function renderHandleList(array $phids) {
$items = array();
foreach ($phids as $phid) {
$items[] = $this->linkTo($phid);
}
$list = null;
switch ($this->getRenderingTarget()) {
case PhabricatorApplicationTransaction::TARGET_TEXT:
$list = implode(', ', $items);
break;
case PhabricatorApplicationTransaction::TARGET_HTML:
$list = phutil_implode_html(', ', $items);
break;
}
return $list;
}
final protected function linkTo($phid) {
$handle = $this->getHandle($phid);
switch ($this->getRenderingTarget()) {
case PhabricatorApplicationTransaction::TARGET_TEXT:
return $handle->getLinkName();
}
// NOTE: We render our own link here to customize the styling and add
// the '_top' target for framed feeds.
$class = null;
if ($handle->getType() == PhabricatorPeopleUserPHIDType::TYPECONST) {
$class = 'phui-link-person';
}
return javelin_tag(
'a',
array(
'href' => $handle->getURI(),
'target' => $this->framed ? '_top' : null,
'sigil' => $this->hovercard ? 'hovercard' : null,
'meta' => $this->hovercard ? array('hoverPHID' => $phid) : null,
'class' => $class,
),
$handle->getLinkName());
}
final protected function renderString($str) {
switch ($this->getRenderingTarget()) {
case PhabricatorApplicationTransaction::TARGET_TEXT:
return $str;
case PhabricatorApplicationTransaction::TARGET_HTML:
return phutil_tag('strong', array(), $str);
}
}
final protected function renderSummary($text, $len = 128) {
if ($len) {
$text = id(new PhutilUTF8StringTruncator())
->setMaximumGlyphs($len)
->truncateString($text);
}
switch ($this->getRenderingTarget()) {
case PhabricatorApplicationTransaction::TARGET_HTML:
$text = phutil_escape_html_newlines($text);
break;
}
return $text;
}
public function getNotificationAggregations() {
return array();
}
protected function newStoryView() {
$view = id(new PHUIFeedStoryView())
->setChronologicalKey($this->getChronologicalKey())
->setEpoch($this->getEpoch())
->setViewed($this->getHasViewed());
$project_phids = $this->getProjectPHIDs();
if ($project_phids) {
$view->setTags($this->renderHandleList($project_phids));
}
return $view;
}
public function setProjectPHIDs(array $phids) {
$this->projectPHIDs = $phids;
return $this;
}
public function getProjectPHIDs() {
return $this->projectPHIDs;
}
public function getFieldStoryMarkupFields() {
return array();
}
/* -( PhabricatorPolicyInterface Implementation )-------------------------- */
public function getPHID() {
return null;
}
/**
* @task policy
*/
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
);
}
/**
* @task policy
*/
public function getPolicy($capability) {
// If this story's primary object is a policy-aware object, use its policy
// to control story visiblity.
$primary_phid = $this->getPrimaryObjectPHID();
if (isset($this->objects[$primary_phid])) {
$object = $this->objects[$primary_phid];
if ($object instanceof PhabricatorPolicyInterface) {
return $object->getPolicy($capability);
}
}
// TODO: Remove this once all objects are policy-aware. For now, keep
// respecting the `feed.public` setting.
return PhabricatorEnv::getEnvConfig('feed.public')
? PhabricatorPolicies::POLICY_PUBLIC
: PhabricatorPolicies::POLICY_USER;
}
/**
* @task policy
*/
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return false;
}
public function describeAutomaticCapability($capability) {
return null;
}
/* -( PhabricatorMarkupInterface Implementation )--------------------------- */
public function getMarkupFieldKey($field) {
return 'feed:'.$this->getChronologicalKey().':'.$field;
}
public function newMarkupEngine($field) {
return PhabricatorMarkupEngine::newMarkupEngine(array());
}
public function getMarkupText($field) {
throw new PhutilMethodNotImplementedException();
}
public function didMarkupText(
$field,
$output,
PhutilMarkupEngine $engine) {
return $output;
}
public function shouldUseMarkupCache($field) {
return true;
}
}
diff --git a/src/applications/feed/story/PhabricatorFeedStoryAudit.php b/src/applications/feed/story/PhabricatorFeedStoryAudit.php
index bade63e766..60ac7b3058 100644
--- a/src/applications/feed/story/PhabricatorFeedStoryAudit.php
+++ b/src/applications/feed/story/PhabricatorFeedStoryAudit.php
@@ -1,84 +1,50 @@
<?php
final class PhabricatorFeedStoryAudit extends PhabricatorFeedStory {
public function getPrimaryObjectPHID() {
return $this->getStoryData()->getValue('commitPHID');
}
public function renderView() {
$author_phid = $this->getAuthorPHID();
$commit_phid = $this->getPrimaryObjectPHID();
$view = $this->newStoryView();
$view->setAppIcon('audit-dark');
$action = $this->getValue('action');
$verb = PhabricatorAuditActionConstants::getActionPastTenseVerb($action);
$view->setTitle(hsprintf(
'%s %s commit %s.',
$this->linkTo($author_phid),
$verb,
$this->linkTo($commit_phid)));
$comments = $this->getValue('content');
$view->setImage($this->getHandle($author_phid)->getImageURI());
if ($comments) {
$content = $this->renderSummary($this->getValue('content'));
$view->appendChild($content);
}
return $view;
}
public function renderText() {
$author_name = $this->getHandle($this->getAuthorPHID())->getLinkName();
$commit_path = $this->getHandle($this->getPrimaryObjectPHID())->getURI();
$commit_uri = PhabricatorEnv::getURI($commit_path);
$action = $this->getValue('action');
$verb = PhabricatorAuditActionConstants::getActionPastTenseVerb($action);
$text = "{$author_name} {$verb} commit {$commit_uri}";
return $text;
}
-
- // TODO: At some point, make feed rendering not terrible and remove this
- // hacky mess.
- public function renderForAsanaBridge($implied_context = false) {
- $data = $this->getStoryData();
- $comment = $data->getValue('content');
-
- $author_name = $this->getHandle($this->getAuthorPHID())->getName();
- $action = $this->getValue('action');
- $verb = PhabricatorAuditActionConstants::getActionPastTenseVerb($action);
-
- $commit_phid = $this->getPrimaryObjectPHID();
- $commit_name = $this->getHandle($commit_phid)->getFullName();
-
- if ($implied_context) {
- $title = "{$author_name} {$verb} this commit.";
- } else {
- $title = "{$author_name} {$verb} commit {$commit_name}.";
- }
-
- if (strlen($comment)) {
- $engine = PhabricatorMarkupEngine::newMarkupEngine(array())
- ->setConfig('viewer', new PhabricatorUser())
- ->setMode(PhutilRemarkupEngine::MODE_TEXT);
-
- $comment = $engine->markupText($comment);
-
- $title .= "\n\n";
- $title .= $comment;
- }
-
- return $title;
- }
-
}
diff --git a/src/applications/feed/story/PhabricatorFeedStoryDifferential.php b/src/applications/feed/story/PhabricatorFeedStoryDifferential.php
index 20b6013608..094e19796b 100644
--- a/src/applications/feed/story/PhabricatorFeedStoryDifferential.php
+++ b/src/applications/feed/story/PhabricatorFeedStoryDifferential.php
@@ -1,393 +1,238 @@
<?php
final class PhabricatorFeedStoryDifferential extends PhabricatorFeedStory {
public function getPrimaryObjectPHID() {
return $this->getValue('revision_phid');
}
public function renderView() {
$data = $this->getStoryData();
$view = $this->newStoryView();
$view->setAppIcon('differential-dark');
$line = $this->getLineForData($data);
$view->setTitle($line);
$href = $this->getHandle($data->getValue('revision_phid'))->getURI();
$view->setHref($href);
$action = $data->getValue('action');
switch ($action) {
case DifferentialAction::ACTION_CREATE:
case DifferentialAction::ACTION_CLOSE:
case DifferentialAction::ACTION_COMMENT:
$full_size = true;
break;
default:
$full_size = false;
break;
}
$view->setImage($this->getHandle($data->getAuthorPHID())->getImageURI());
if ($full_size) {
$content = $this->renderSummary($data->getValue('feedback_content'));
$view->appendChild($content);
}
return $view;
}
private function getLineForData($data) {
$actor_phid = $data->getAuthorPHID();
$revision_phid = $data->getValue('revision_phid');
$action = $data->getValue('action');
$actor_link = $this->linkTo($actor_phid);
$revision_link = $this->linkTo($revision_phid);
switch ($action) {
case DifferentialAction::ACTION_COMMENT:
$one_line = pht('%s commented on revision %s',
$actor_link, $revision_link);
break;
case DifferentialAction::ACTION_ACCEPT:
$one_line = pht('%s accepted revision %s',
$actor_link, $revision_link);
break;
case DifferentialAction::ACTION_REJECT:
$one_line = pht('%s requested changes to revision %s',
$actor_link, $revision_link);
break;
case DifferentialAction::ACTION_RETHINK:
$one_line = pht('%s planned changes to revision %s',
$actor_link, $revision_link);
break;
case DifferentialAction::ACTION_ABANDON:
$one_line = pht('%s abandoned revision %s',
$actor_link, $revision_link);
break;
case DifferentialAction::ACTION_CLOSE:
$one_line = pht('%s closed revision %s',
$actor_link, $revision_link);
break;
case DifferentialAction::ACTION_REQUEST:
$one_line = pht('%s requested a review of revision %s',
$actor_link, $revision_link);
break;
case DifferentialAction::ACTION_RECLAIM:
$one_line = pht('%s reclaimed revision %s',
$actor_link, $revision_link);
break;
case DifferentialAction::ACTION_UPDATE:
$one_line = pht('%s updated revision %s',
$actor_link, $revision_link);
break;
case DifferentialAction::ACTION_RESIGN:
$one_line = pht('%s resigned from revision %s',
$actor_link, $revision_link);
break;
case DifferentialAction::ACTION_SUMMARIZE:
$one_line = pht('%s summarized revision %s',
$actor_link, $revision_link);
break;
case DifferentialAction::ACTION_TESTPLAN:
$one_line = pht('%s explained the test plan for revision %s',
$actor_link, $revision_link);
break;
case DifferentialAction::ACTION_CREATE:
$one_line = pht('%s created revision %s',
$actor_link, $revision_link);
break;
case DifferentialAction::ACTION_ADDREVIEWERS:
$one_line = pht('%s added reviewers to revision %s',
$actor_link, $revision_link);
break;
case DifferentialAction::ACTION_ADDCCS:
$one_line = pht('%s added CCs to revision %s',
$actor_link, $revision_link);
break;
case DifferentialAction::ACTION_CLAIM:
$one_line = pht('%s commandeered revision %s',
$actor_link, $revision_link);
break;
case DifferentialAction::ACTION_REOPEN:
$one_line = pht('%s reopened revision %s',
$actor_link, $revision_link);
break;
case DifferentialTransaction::TYPE_INLINE:
$one_line = pht('%s added inline comments to %s',
$actor_link, $revision_link);
break;
default:
$one_line = pht('%s edited %s',
$actor_link, $revision_link);
break;
}
return $one_line;
}
public function renderText() {
$author_name = $this->getHandle($this->getAuthorPHID())->getLinkName();
$revision_handle = $this->getHandle($this->getPrimaryObjectPHID());
$revision_title = $revision_handle->getLinkName();
$revision_uri = PhabricatorEnv::getURI($revision_handle->getURI());
$action = $this->getValue('action');
switch ($action) {
case DifferentialAction::ACTION_COMMENT:
$one_line = pht('%s commented on revision %s %s',
$author_name, $revision_title, $revision_uri);
break;
case DifferentialAction::ACTION_ACCEPT:
$one_line = pht('%s accepted revision %s %s',
$author_name, $revision_title, $revision_uri);
break;
case DifferentialAction::ACTION_REJECT:
$one_line = pht('%s requested changes to revision %s %s',
$author_name, $revision_title, $revision_uri);
break;
case DifferentialAction::ACTION_RETHINK:
$one_line = pht('%s planned changes to revision %s %s',
$author_name, $revision_title, $revision_uri);
break;
case DifferentialAction::ACTION_ABANDON:
$one_line = pht('%s abandoned revision %s %s',
$author_name, $revision_title, $revision_uri);
break;
case DifferentialAction::ACTION_CLOSE:
$one_line = pht('%s closed revision %s %s',
$author_name, $revision_title, $revision_uri);
break;
case DifferentialAction::ACTION_REQUEST:
$one_line = pht('%s requested a review of revision %s %s',
$author_name, $revision_title, $revision_uri);
break;
case DifferentialAction::ACTION_RECLAIM:
$one_line = pht('%s reclaimed revision %s %s',
$author_name, $revision_title, $revision_uri);
break;
case DifferentialAction::ACTION_UPDATE:
$one_line = pht('%s updated revision %s %s',
$author_name, $revision_title, $revision_uri);
break;
case DifferentialAction::ACTION_RESIGN:
$one_line = pht('%s resigned from revision %s %s',
$author_name, $revision_title, $revision_uri);
break;
case DifferentialAction::ACTION_SUMMARIZE:
$one_line = pht('%s summarized revision %s %s',
$author_name, $revision_title, $revision_uri);
break;
case DifferentialAction::ACTION_TESTPLAN:
$one_line = pht('%s explained the test plan for revision %s %s',
$author_name, $revision_title, $revision_uri);
break;
case DifferentialAction::ACTION_CREATE:
$one_line = pht('%s created revision %s %s',
$author_name, $revision_title, $revision_uri);
break;
case DifferentialAction::ACTION_ADDREVIEWERS:
$one_line = pht('%s added reviewers to revision %s %s',
$author_name, $revision_title, $revision_uri);
break;
case DifferentialAction::ACTION_ADDCCS:
$one_line = pht('%s added CCs to revision %s %s',
$author_name, $revision_title, $revision_uri);
break;
case DifferentialAction::ACTION_CLAIM:
$one_line = pht('%s commandeered revision %s %s',
$author_name, $revision_title, $revision_uri);
break;
case DifferentialAction::ACTION_REOPEN:
$one_line = pht('%s reopened revision %s %s',
$author_name, $revision_title, $revision_uri);
break;
case DifferentialTransaction::TYPE_INLINE:
$one_line = pht('%s added inline comments to %s %s',
$author_name, $revision_title, $revision_uri);
break;
default:
$one_line = pht('%s edited %s %s',
$author_name, $revision_title, $revision_uri);
break;
}
return $one_line;
}
public function getNotificationAggregations() {
$class = get_class($this);
$phid = $this->getStoryData()->getValue('revision_phid');
$read = (int)$this->getHasViewed();
// Don't aggregate updates separated by more than 2 hours.
$block = (int)($this->getEpoch() / (60 * 60 * 2));
return array(
"{$class}:{$phid}:{$read}:{$block}"
=> 'PhabricatorFeedStoryDifferentialAggregate',
);
}
- // TODO: At some point, make feed rendering not terrible and remove this
- // hacky mess.
- public function renderForAsanaBridge($implied_context = false) {
- $data = $this->getStoryData();
- $comment = $data->getValue('feedback_content');
-
- $author_name = $this->getHandle($this->getAuthorPHID())->getName();
- $action = $this->getValue('action');
-
- $engine = PhabricatorMarkupEngine::newMarkupEngine(array())
- ->setConfig('viewer', new PhabricatorUser())
- ->setMode(PhutilRemarkupEngine::MODE_TEXT);
-
- $revision_phid = $this->getPrimaryObjectPHID();
- $revision_name = $this->getHandle($revision_phid)->getFullName();
-
- if ($implied_context) {
- $title = DifferentialAction::getBasicStoryText(
- $action, $author_name);
- } else {
- switch ($action) {
- case DifferentialAction::ACTION_COMMENT:
- $title = pht('%s commented on revision %s',
- $author_name, $revision_name);
- break;
- case DifferentialAction::ACTION_ACCEPT:
- $title = pht('%s accepted revision %s',
- $author_name, $revision_name);
- break;
- case DifferentialAction::ACTION_REJECT:
- $title = pht('%s requested changes to revision %s',
- $author_name, $revision_name);
- break;
- case DifferentialAction::ACTION_RETHINK:
- $title = pht('%s planned changes to revision %s',
- $author_name, $revision_name);
- break;
- case DifferentialAction::ACTION_ABANDON:
- $title = pht('%s abandoned revision %s',
- $author_name, $revision_name);
- break;
- case DifferentialAction::ACTION_CLOSE:
- $title = pht('%s closed revision %s',
- $author_name, $revision_name);
- break;
- case DifferentialAction::ACTION_REQUEST:
- $title = pht('%s requested a review of revision %s',
- $author_name, $revision_name);
- break;
- case DifferentialAction::ACTION_RECLAIM:
- $title = pht('%s reclaimed revision %s',
- $author_name, $revision_name);
- break;
- case DifferentialAction::ACTION_UPDATE:
- $title = pht('%s updated revision %s',
- $author_name, $revision_name);
- break;
- case DifferentialAction::ACTION_RESIGN:
- $title = pht('%s resigned from revision %s',
- $author_name, $revision_name);
- break;
- case DifferentialAction::ACTION_SUMMARIZE:
- $title = pht('%s summarized revision %s',
- $author_name, $revision_name);
- break;
- case DifferentialAction::ACTION_TESTPLAN:
- $title = pht('%s explained the test plan for revision %s',
- $author_name, $revision_name);
- break;
- case DifferentialAction::ACTION_CREATE:
- $title = pht('%s created revision %s',
- $author_name, $revision_name);
- break;
- case DifferentialAction::ACTION_ADDREVIEWERS:
- $title = pht('%s added reviewers to revision %s',
- $author_name, $revision_name);
- break;
- case DifferentialAction::ACTION_ADDCCS:
- $title = pht('%s added CCs to revision %s',
- $author_name, $revision_name);
- break;
- case DifferentialAction::ACTION_CLAIM:
- $title = pht('%s commandeered revision %s',
- $author_name, $revision_name);
- break;
- case DifferentialAction::ACTION_REOPEN:
- $title = pht('%s reopened revision %s',
- $author_name, $revision_name);
- break;
- case DifferentialTransaction::TYPE_INLINE:
- $title = pht('%s added inline comments to %s',
- $author_name, $revision_name);
- break;
- default:
- $title = pht('%s edited revision %s',
- $author_name, $revision_name);
- break;
- }
- }
-
- if (strlen($comment)) {
- $comment = $engine->markupText($comment);
-
- $title .= "\n\n";
- $title .= $comment;
- }
-
- // Roughly render inlines into the comment.
- $xaction_phids = $data->getValue('temporaryTransactionPHIDs');
- if ($xaction_phids) {
- $inlines = id(new DifferentialTransactionQuery())
- ->setViewer(PhabricatorUser::getOmnipotentUser())
- ->withPHIDs($xaction_phids)
- ->needComments(true)
- ->withTransactionTypes(
- array(
- DifferentialTransaction::TYPE_INLINE,
- ))
- ->execute();
- if ($inlines) {
- $title .= "\n\n";
- $title .= pht('Inline Comments');
- $title .= "\n";
-
- $changeset_ids = array();
- foreach ($inlines as $inline) {
- $changeset_ids[] = $inline->getComment()->getChangesetID();
- }
-
- $changesets = id(new DifferentialChangeset())->loadAllWhere(
- 'id IN (%Ld)',
- $changeset_ids);
-
- foreach ($inlines as $inline) {
- $comment = $inline->getComment();
- $changeset = idx($changesets, $comment->getChangesetID());
- if (!$changeset) {
- continue;
- }
-
- $filename = $changeset->getDisplayFilename();
- $linenumber = $comment->getLineNumber();
- $inline_text = $engine->markupText($comment->getContent());
- $inline_text = rtrim($inline_text);
-
- $title .= "{$filename}:{$linenumber} {$inline_text}\n";
- }
- }
- }
-
-
- return $title;
- }
-
-
}
diff --git a/src/applications/transactions/feed/PhabricatorApplicationTransactionFeedStory.php b/src/applications/transactions/feed/PhabricatorApplicationTransactionFeedStory.php
index 0e488b359a..923ac77e24 100644
--- a/src/applications/transactions/feed/PhabricatorApplicationTransactionFeedStory.php
+++ b/src/applications/transactions/feed/PhabricatorApplicationTransactionFeedStory.php
@@ -1,107 +1,120 @@
<?php
/**
* @concrete-extensible
*/
class PhabricatorApplicationTransactionFeedStory
extends PhabricatorFeedStory {
public function getPrimaryObjectPHID() {
return $this->getValue('objectPHID');
}
public function getRequiredObjectPHIDs() {
return $this->getValue('transactionPHIDs');
}
public function getRequiredHandlePHIDs() {
$phids = array();
$phids[] = $this->getValue('objectPHID');
foreach ($this->getValue('transactionPHIDs') as $xaction_phid) {
$xaction = $this->getObject($xaction_phid);
foreach ($xaction->getRequiredHandlePHIDs() as $handle_phid) {
$phids[] = $handle_phid;
}
}
return $phids;
}
protected function getPrimaryTransactionPHID() {
return head($this->getValue('transactionPHIDs'));
}
public function getPrimaryTransaction() {
return $this->getObject($this->getPrimaryTransactionPHID());
}
public function getFieldStoryMarkupFields() {
$xaction_phids = $this->getValue('transactionPHIDs');
$fields = array();
foreach ($xaction_phids as $xaction_phid) {
$xaction = $this->getObject($xaction_phid);
foreach ($xaction->getMarkupFieldsForFeed($this) as $field) {
$fields[] = $field;
}
}
return $fields;
}
public function getMarkupText($field) {
$xaction_phids = $this->getValue('transactionPHIDs');
foreach ($xaction_phids as $xaction_phid) {
$xaction = $this->getObject($xaction_phid);
foreach ($xaction->getMarkupFieldsForFeed($this) as $xaction_field) {
if ($xaction_field == $field) {
return $xaction->getMarkupTextForFeed($this, $field);
}
}
}
return null;
}
public function renderView() {
$view = $this->newStoryView();
$handle = $this->getHandle($this->getPrimaryObjectPHID());
$view->setHref($handle->getURI());
$view->setAppIconFromPHID($handle->getPHID());
$xaction_phids = $this->getValue('transactionPHIDs');
$xaction = $this->getPrimaryTransaction();
$xaction->setHandles($this->getHandles());
$view->setTitle($xaction->getTitleForFeed($this));
foreach ($xaction_phids as $xaction_phid) {
$secondary_xaction = $this->getObject($xaction_phid);
$secondary_xaction->setHandles($this->getHandles());
$body = $secondary_xaction->getBodyForFeed($this);
if (nonempty($body)) {
$view->appendChild($body);
}
}
$view->setImage(
$this->getHandle($xaction->getAuthorPHID())->getImageURI());
return $view;
}
public function renderText() {
$xaction = $this->getPrimaryTransaction();
$old_target = $xaction->getRenderingTarget();
$new_target = PhabricatorApplicationTransaction::TARGET_TEXT;
$xaction->setRenderingTarget($new_target);
$xaction->setHandles($this->getHandles());
$text = $xaction->getTitleForFeed($this);
$xaction->setRenderingTarget($old_target);
return $text;
}
+ public function renderAsTextForDoorkeeper(
+ DoorkeeperFeedStoryPublisher $publisher) {
+
+ $xactions = array();
+ $xaction_phids = $this->getValue('transactionPHIDs');
+ foreach ($xaction_phids as $xaction_phid) {
+ $xactions[] = $this->getObject($xaction_phid);
+ }
+
+ $primary = $this->getPrimaryTransaction();
+ return $primary->renderAsTextForDoorkeeper($publisher, $this, $xactions);
+ }
+
}
diff --git a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php
index da9561ac71..570c67af7f 100644
--- a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php
+++ b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php
@@ -1,1139 +1,1177 @@
<?php
abstract class PhabricatorApplicationTransaction
extends PhabricatorLiskDAO
implements
PhabricatorPolicyInterface,
PhabricatorDestructibleInterface {
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;
private $object = self::ATTACHABLE;
private $oldValueHasBeenSet = false;
private $ignoreOnNoEffect;
/**
* Flag this transaction as a pure side-effect which should be ignored when
* applying transactions if it has no effect, even if transaction application
* would normally fail. This both provides users with better error messages
* and allows transactions to perform optional side effects.
*/
public function setIgnoreOnNoEffect($ignore) {
$this->ignoreOnNoEffect = $ignore;
return $this;
}
public function getIgnoreOnNoEffect() {
return $this->ignoreOnNoEffect;
}
public function shouldGenerateOldValue() {
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_BUILDABLE:
case PhabricatorTransactions::TYPE_TOKEN:
case PhabricatorTransactions::TYPE_CUSTOMFIELD:
return false;
}
return true;
}
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 PhutilMethodNotImplementedException();
}
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 = PhabricatorApplicationTransactionTransactionPHIDType::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,
),
self::CONFIG_COLUMN_SCHEMA => array(
'commentPHID' => 'phid?',
'commentVersion' => 'uint32',
'contentSource' => 'text',
'transactionType' => 'text32',
),
self::CONFIG_KEY_SCHEMA => array(
'key_object' => array(
'columns' => array('objectPHID'),
),
),
) + 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;
}
public function attachObject($object) {
$this->object = $object;
return $this;
}
public function getObject() {
return $this->assertAttached($this->object);
}
public function getRemarkupBlocks() {
$blocks = array();
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_CUSTOMFIELD:
$field = $this->getTransactionCustomField();
if ($field) {
$custom_blocks = $field->getApplicationTransactionRemarkupBlocks(
$this);
foreach ($custom_blocks as $custom_block) {
$blocks[] = $custom_block;
}
}
break;
}
if ($this->getComment()) {
$blocks[] = $this->getComment()->getContent();
}
return $blocks;
}
public function setOldValue($value) {
$this->oldValueHasBeenSet = true;
$this->writeField('oldValue', $value);
return $this;
}
public function hasOldValue() {
return $this->oldValueHasBeenSet;
}
/* -( 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_CUSTOMFIELD:
$field = $this->getTransactionCustomField();
if ($field) {
$phids[] = $field->getApplicationTransactionRequiredHandlePHIDs(
$this);
}
break;
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;
case PhabricatorTransactions::TYPE_TOKEN:
break;
case PhabricatorTransactions::TYPE_BUILDABLE:
$phid = $this->getMetadataValue('harbormaster:buildablePHID');
if ($phid) {
$phids[] = array($phid);
}
break;
}
if ($this->getComment()) {
$phids[] = array($this->getComment()->getAuthorPHID());
}
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(
pht(
'Transaction ("%s", of type "%s") requires a handle ("%s") that it '.
'did not load.',
$this->getPHID(),
$this->getTransactionType(),
$phid));
}
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);
}
}
private function renderSubscriberList(array $phids, $change_type) {
if ($this->getRenderingTarget() == self::TARGET_TEXT) {
return $this->renderHandleList($phids);
} else {
$handles = array_select_keys($this->getHandles(), $phids);
return id(new SubscriptionListStringBuilder())
->setHandles($handles)
->setObjectPHID($this->getPHID())
->buildTransactionString($change_type);
}
}
protected function renderPolicyName($phid, $state = 'old') {
$policy = PhabricatorPolicy::newFromPolicyAndHandle(
$phid,
$this->getHandleIfExists($phid));
if ($this->renderingTarget == self::TARGET_HTML) {
switch ($policy->getType()) {
case PhabricatorPolicyType::TYPE_CUSTOM:
$policy->setHref('/transactions/'.$state.'/'.$this->getPHID().'/');
$policy->setWorkflow(true);
break;
default:
break;
}
$output = $policy->renderDescription();
} else {
$output = hsprintf('%s', $policy->getFullName());
}
return $output;
}
public function getIcon() {
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_COMMENT:
$comment = $this->getComment();
if ($comment && $comment->getIsRemoved()) {
return 'fa-eraser';
}
return 'fa-comment';
case PhabricatorTransactions::TYPE_SUBSCRIBERS:
return 'fa-envelope';
case PhabricatorTransactions::TYPE_VIEW_POLICY:
case PhabricatorTransactions::TYPE_EDIT_POLICY:
case PhabricatorTransactions::TYPE_JOIN_POLICY:
return 'fa-lock';
case PhabricatorTransactions::TYPE_EDGE:
return 'fa-link';
case PhabricatorTransactions::TYPE_BUILDABLE:
return 'fa-wrench';
case PhabricatorTransactions::TYPE_TOKEN:
return 'fa-trophy';
}
return 'fa-pencil';
}
public function getToken() {
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_TOKEN:
$old = $this->getOldValue();
$new = $this->getNewValue();
if ($new) {
$icon = substr($new, 10);
} else {
$icon = substr($old, 10);
}
return array($icon, !$this->getNewValue());
}
return array(null, null);
}
public function getColor() {
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_COMMENT;
$comment = $this->getComment();
if ($comment && $comment->getIsRemoved()) {
return 'black';
}
break;
case PhabricatorTransactions::TYPE_BUILDABLE:
switch ($this->getNewValue()) {
case HarbormasterBuildable::STATUS_PASSED:
return 'green';
case HarbormasterBuildable::STATUS_FAILED:
return 'red';
}
break;
}
return null;
}
protected function getTransactionCustomField() {
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_CUSTOMFIELD:
$key = $this->getMetadataValue('customfield:key');
if (!$key) {
return null;
}
$field = PhabricatorCustomField::getObjectField(
$this->getObject(),
PhabricatorCustomField::ROLE_APPLICATIONTRANSACTIONS,
$key);
if (!$field) {
return null;
}
$field->setViewer($this->getViewer());
return $field;
}
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;
case PhabricatorTransactions::TYPE_CUSTOMFIELD:
$field = $this->getTransactionCustomField();
if ($field) {
return $field->shouldHideInApplicationTransactions($this);
}
case PhabricatorTransactions::TYPE_EDGE:
$edge_type = $this->getMetadataValue('edge:type');
switch ($edge_type) {
case PhabricatorObjectMentionsObject::EDGECONST:
return true;
break;
case PhabricatorObjectMentionedByObject::EDGECONST:
$new = ipull($this->getNewValue(), 'dst');
$old = ipull($this->getOldValue(), 'dst');
$add = array_diff($new, $old);
$add_value = reset($add);
$add_handle = $this->getHandle($add_value);
if ($add_handle->getPolicyFiltered()) {
return true;
}
return false;
break;
default:
break;
}
break;
}
return false;
}
public function shouldHideForMail(array $xactions) {
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_TOKEN:
return true;
case PhabricatorTransactions::TYPE_BUILDABLE:
switch ($this->getNewValue()) {
case HarbormasterBuildable::STATUS_FAILED:
// For now, only ever send mail when builds fail. We might let
// you customize this later, but in most cases this is probably
// completely uninteresting.
return false;
}
return true;
case PhabricatorTransactions::TYPE_EDGE:
$edge_type = $this->getMetadataValue('edge:type');
switch ($edge_type) {
case PhabricatorObjectMentionsObject::EDGECONST:
case PhabricatorObjectMentionedByObject::EDGECONST:
return true;
break;
default:
break;
}
break;
}
return $this->shouldHide();
}
public function shouldHideForFeed() {
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_TOKEN:
return true;
case PhabricatorTransactions::TYPE_BUILDABLE:
switch ($this->getNewValue()) {
case HarbormasterBuildable::STATUS_FAILED:
// For now, don't notify on build passes either. These are pretty
// high volume and annoying, with very little present value. We
// might want to turn them back on in the specific case of
// build successes on the current document?
return false;
}
return true;
case PhabricatorTransactions::TYPE_EDGE:
$edge_type = $this->getMetadataValue('edge:type');
switch ($edge_type) {
case PhabricatorObjectMentionsObject::EDGECONST:
case PhabricatorObjectMentionedByObject::EDGECONST:
return true;
break;
default:
break;
}
break;
}
return $this->shouldHide();
}
public function getTitleForMail() {
return id(clone $this)->setRenderingTarget('text')->getTitle();
}
public function getBodyForMail() {
$comment = $this->getComment();
if ($comment && strlen($comment->getContent())) {
return $comment->getContent();
}
return null;
}
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, 'old'),
$this->renderPolicyName($new, '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, 'old'),
$this->renderPolicyName($new, '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, 'old'),
$this->renderPolicyName($new, '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->renderSubscriberList($add, 'add'),
count($rem),
$this->renderSubscriberList($rem, 'rem'));
} else if ($add) {
return pht(
'%s added %d subscriber(s): %s.',
$this->renderHandleLink($author_phid),
count($add),
$this->renderSubscriberList($add, 'add'));
} else if ($rem) {
return pht(
'%s removed %d subscriber(s): %s.',
$this->renderHandleLink($author_phid),
count($rem),
$this->renderSubscriberList($rem, '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);
$type_obj = PhabricatorEdgeType::getByConstant($type);
if ($add && $rem) {
return $type_obj->getTransactionEditString(
$this->renderHandleLink($author_phid),
new PhutilNumber(count($add) + count($rem)),
new PhutilNumber(count($add)),
$this->renderHandleList($add),
new PhutilNumber(count($rem)),
$this->renderHandleList($rem));
} else if ($add) {
return $type_obj->getTransactionAddString(
$this->renderHandleLink($author_phid),
new PhutilNumber(count($add)),
$this->renderHandleList($add));
} else if ($rem) {
return $type_obj->getTransactionRemoveString(
$this->renderHandleLink($author_phid),
new PhutilNumber(count($rem)),
$this->renderHandleList($rem));
} else {
return pht(
'%s edited edge metadata.',
$this->renderHandleLink($author_phid));
}
case PhabricatorTransactions::TYPE_CUSTOMFIELD:
$field = $this->getTransactionCustomField();
if ($field) {
return $field->getApplicationTransactionTitle($this);
} else {
return pht(
'%s edited a custom field.',
$this->renderHandleLink($author_phid));
}
case PhabricatorTransactions::TYPE_TOKEN:
if ($old && $new) {
return pht(
'%s updated a token.',
$this->renderHandleLink($author_phid));
} else if ($old) {
return pht(
'%s rescinded a token.',
$this->renderHandleLink($author_phid));
} else {
return pht(
'%s awarded a token.',
$this->renderHandleLink($author_phid));
}
case PhabricatorTransactions::TYPE_BUILDABLE:
switch ($this->getNewValue()) {
case HarbormasterBuildable::STATUS_BUILDING:
return pht(
'%s started building %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink(
$this->getMetadataValue('harbormaster:buildablePHID')));
case HarbormasterBuildable::STATUS_PASSED:
return pht(
'%s completed building %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink(
$this->getMetadataValue('harbormaster:buildablePHID')));
case HarbormasterBuildable::STATUS_FAILED:
return pht(
'%s failed to build %s!',
$this->renderHandleLink($author_phid),
$this->renderHandleLink(
$this->getMetadataValue('harbormaster:buildablePHID')));
default:
return null;
}
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:
$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);
$type_obj = PhabricatorEdgeType::getByConstant($type);
if ($add && $rem) {
return $type_obj->getFeedEditString(
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid),
new PhutilNumber(count($add) + count($rem)),
new PhutilNumber(count($add)),
$this->renderHandleList($add),
new PhutilNumber(count($rem)),
$this->renderHandleList($rem));
} else if ($add) {
return $type_obj->getFeedAddString(
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid),
new PhutilNumber(count($add)),
$this->renderHandleList($add));
} else if ($rem) {
return $type_obj->getFeedRemoveString(
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid),
new PhutilNumber(count($rem)),
$this->renderHandleList($rem));
} else {
return pht(
'%s edited edge metadata for %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
}
case PhabricatorTransactions::TYPE_CUSTOMFIELD:
$field = $this->getTransactionCustomField();
if ($field) {
return $field->getApplicationTransactionTitleForFeed($this, $story);
} else {
return pht(
'%s edited a custom field on %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
}
case PhabricatorTransactions::TYPE_BUILDABLE:
switch ($this->getNewValue()) {
case HarbormasterBuildable::STATUS_BUILDING:
return pht(
'%s started building %s for %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink(
$this->getMetadataValue('harbormaster:buildablePHID')),
$this->renderHandleLink($object_phid));
case HarbormasterBuildable::STATUS_PASSED:
return pht(
'%s completed building %s for %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink(
$this->getMetadataValue('harbormaster:buildablePHID')),
$this->renderHandleLink($object_phid));
case HarbormasterBuildable::STATUS_FAILED:
return pht(
'%s failed to build %s for %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink(
$this->getMetadataValue('harbormaster:buildablePHID')),
$this->renderHandleLink($object_phid));
default:
return null;
}
}
return $this->getTitle();
}
public function getMarkupFieldsForFeed(PhabricatorFeedStory $story) {
$fields = array();
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_COMMENT:
$text = $this->getComment()->getContent();
if (strlen($text)) {
$fields[] = 'comment/'.$this->getID();
}
break;
}
return $fields;
}
public function getMarkupTextForFeed(PhabricatorFeedStory $story, $field) {
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_COMMENT:
$text = $this->getComment()->getContent();
return PhabricatorMarkupEngine::summarize($text);
}
return null;
}
public function getBodyForFeed(PhabricatorFeedStory $story) {
$old = $this->getOldValue();
$new = $this->getNewValue();
$body = null;
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_COMMENT:
$text = $this->getComment()->getContent();
if (strlen($text)) {
$body = $story->getMarkupFieldOutput('comment/'.$this->getID());
}
break;
}
return $body;
}
public function getActionStrength() {
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_COMMENT:
return 0.5;
case PhabricatorTransactions::TYPE_SUBSCRIBERS:
$old = $this->getOldValue();
$new = $this->getNewValue();
$add = array_diff($old, $new);
$rem = array_diff($new, $old);
// If this action is the actor subscribing or unsubscribing themselves,
// it is less interesting. In particular, if someone makes a comment and
// also implicitly subscribes themselves, we should treat the
// transaction group as "comment", not "subscribe". In this specific
// case (one affected user, and that affected user it the actor),
// decrease the action strength.
if ((count($add) + count($rem)) != 1) {
// Not exactly one CC change.
break;
}
$affected_phid = head(array_merge($add, $rem));
if ($affected_phid != $this->getAuthorPHID()) {
// Affected user is someone else.
break;
}
// Make this weaker than TYPE_COMMENT.
return 0.25;
}
return 1.0;
}
public function isCommentTransaction() {
if ($this->hasComment()) {
return true;
}
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_COMMENT:
return true;
}
return false;
}
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');
case PhabricatorTransactions::TYPE_BUILDABLE:
switch ($this->getNewValue()) {
case HarbormasterBuildable::STATUS_PASSED:
return pht('Build Passed');
case HarbormasterBuildable::STATUS_FAILED:
return pht('Build Failed');
default:
return pht('Build Status');
}
default:
return pht('Updated');
}
}
public function getMailTags() {
return array();
}
public function hasChangeDetails() {
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_CUSTOMFIELD:
$field = $this->getTransactionCustomField();
if ($field) {
return $field->getApplicationTransactionHasChangeDetails($this);
}
break;
}
return false;
}
public function renderChangeDetails(PhabricatorUser $viewer) {
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_CUSTOMFIELD:
$field = $this->getTransactionCustomField();
if ($field) {
return $field->getApplicationTransactionChangeDetails($this, $viewer);
}
break;
}
return $this->renderTextCorpusChangeDetails(
$viewer,
$this->getOldValue(),
$this->getNewValue());
}
public function renderTextCorpusChangeDetails(
PhabricatorUser $viewer,
$old,
$new) {
require_celerity_resource('differential-changeset-view-css');
$view = id(new PhabricatorApplicationTransactionTextDiffDetailView())
->setUser($viewer)
->setOldText($old)
->setNewText($new);
return $view->render();
}
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) {
$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->isCommentTransaction()) {
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;
}
public function renderExtraInformationLink() {
$herald_xscript_id = $this->getMetadataValue('herald:transcriptID');
if ($herald_xscript_id) {
return phutil_tag(
'a',
array(
'href' => '/herald/transcript/'.$herald_xscript_id.'/',
),
pht('View Herald Transcript'));
}
return null;
}
+ public function renderAsTextForDoorkeeper(
+ DoorkeeperFeedStoryPublisher $publisher,
+ PhabricatorFeedStory $story,
+ array $xactions) {
+
+ $text = array();
+ $body = array();
+
+ foreach ($xactions as $xaction) {
+ $xaction_body = $xaction->getBodyForMail();
+ if ($xaction_body !== null) {
+ $body[] = $xaction_body;
+ }
+
+ if ($xaction->shouldHideForMail($xactions)) {
+ continue;
+ }
+
+ $old_target = $xaction->getRenderingTarget();
+ $new_target = PhabricatorApplicationTransaction::TARGET_TEXT;
+ $xaction->setRenderingTarget($new_target);
+
+ if ($publisher->getRenderWithImpliedContext()) {
+ $text[] = $xaction->getTitle();
+ } else {
+ $text[] = $xaction->getTitleForFeed($story);
+ }
+
+ $xaction->setRenderingTarget($old_target);
+ }
+
+ $text = implode("\n", $text);
+ $body = implode("\n\n", $body);
+
+ return rtrim($text."\n\n".$body);
+ }
+
+
/* -( 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;
}
/* -( PhabricatorDestructibleInterface )----------------------------------- */
public function destroyObjectPermanently(
PhabricatorDestructionEngine $engine) {
$this->openTransaction();
$comment_template = null;
try {
$comment_template = $this->getApplicationTransactionCommentObject();
} catch (Exception $ex) {
// Continue; no comments for these transactions.
}
if ($comment_template) {
$comments = $comment_template->loadAllWhere(
'transactionPHID = %s',
$this->getPHID());
foreach ($comments as $comment) {
$engine->destroyObject($comment);
}
}
$this->delete();
$this->saveTransaction();
}
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sun, Jan 19, 20:08 (1 w, 5 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1128315
Default Alt Text
(108 KB)
Attached To
Mode
rP Phorge
Attached
Detach File
Event Timeline
Log In to Comment