diff --git a/src/applications/differential/xaction/DifferentialRevisionAbandonTransaction.php b/src/applications/differential/xaction/DifferentialRevisionAbandonTransaction.php index 627e1c9d37..2f221bd8c6 100644 --- a/src/applications/differential/xaction/DifferentialRevisionAbandonTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionAbandonTransaction.php @@ -1,83 +1,87 @@ isAbandoned(); } public function applyInternalEffects($object, $value) { $object->setStatus(ArcanistDifferentialRevisionStatus::ABANDONED); } protected function validateAction($object, PhabricatorUser $viewer) { if ($object->isClosed()) { throw new Exception( pht( 'You can not abandon this revision because it has already been '. 'closed. Only open revisions can be abandoned.')); } $config_key = 'differential.always-allow-abandon'; if (!PhabricatorEnv::getEnvConfig($config_key)) { if (!$this->isViewerRevisionAuthor($object, $viewer)) { throw new Exception( pht( 'You can not abandon this revision because you are not the '. 'author. You can only abandon revisions you own. You can change '. 'this behavior by adjusting the "%s" setting in Config.', $config_key)); } } } public function getTitle() { return pht( '%s abandoned this revision.', $this->renderAuthor()); } public function getTitleForFeed() { return pht( '%s abandoned %s.', $this->renderAuthor(), $this->renderObject()); } } diff --git a/src/applications/differential/xaction/DifferentialRevisionAcceptTransaction.php b/src/applications/differential/xaction/DifferentialRevisionAcceptTransaction.php index dcb19c4037..df4e3b4d33 100644 --- a/src/applications/differential/xaction/DifferentialRevisionAcceptTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionAcceptTransaction.php @@ -1,99 +1,103 @@ getActor(); return $this->isViewerAcceptingReviewer($object, $actor); } public function applyExternalEffects($object, $value) { $status = DifferentialReviewerStatus::STATUS_ACCEPTED; $actor = $this->getActor(); $this->applyReviewerEffect($object, $actor, $value, $status); } protected function validateAction($object, PhabricatorUser $viewer) { if ($object->isClosed()) { throw new Exception( pht( 'You can not accept this revision because it has already been '. 'closed. Only open revisions can be accepted.')); } $config_key = 'differential.allow-self-accept'; if (!PhabricatorEnv::getEnvConfig($config_key)) { if ($this->isViewerRevisionAuthor($object, $viewer)) { throw new Exception( pht( 'You can not accept this revision because you are the revision '. 'author. You can only accept revisions you do not own. You can '. 'change this behavior by adjusting the "%s" setting in Config.', $config_key)); } } if ($this->isViewerAcceptingReviewer($object, $viewer)) { throw new Exception( pht( 'You can not accept this revision because you have already '. 'accepted it.')); } } public function getTitle() { return pht( '%s accepted this revision.', $this->renderAuthor()); } public function getTitleForFeed() { return pht( '%s accepted %s.', $this->renderAuthor(), $this->renderObject()); } } diff --git a/src/applications/differential/xaction/DifferentialRevisionActionTransaction.php b/src/applications/differential/xaction/DifferentialRevisionActionTransaction.php index dd80706b44..5507d1309d 100644 --- a/src/applications/differential/xaction/DifferentialRevisionActionTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionActionTransaction.php @@ -1,128 +1,132 @@ getPhobjectClassConstant('ACTIONKEY', 32); } public function isActionAvailable($object, PhabricatorUser $viewer) { try { $this->validateAction($object, $viewer); return true; } catch (Exception $ex) { return false; } } abstract protected function validateAction($object, PhabricatorUser $viewer); abstract protected function getRevisionActionLabel(); public function getCommandKeyword() { return null; } public function getCommandAliases() { return array(); } public function getCommandSummary() { return null; } protected function getRevisionActionOrder() { return 1000; } + public function getActionStrength() { + return 3; + } + public function getRevisionActionOrderVector() { return id(new PhutilSortVector()) ->addInt($this->getRevisionActionOrder()); } protected function getRevisionActionGroupKey() { return DifferentialRevisionEditEngine::ACTIONGROUP_REVISION; } protected function getRevisionActionDescription() { return null; } public static function loadAllActions() { return id(new PhutilClassMapQuery()) ->setAncestorClass(__CLASS__) ->setUniqueMethod('getRevisionActionKey') ->execute(); } protected function isViewerRevisionAuthor( DifferentialRevision $revision, PhabricatorUser $viewer) { if (!$viewer->getPHID()) { return false; } return ($viewer->getPHID() === $revision->getAuthorPHID()); } public function newEditField( DifferentialRevision $revision, PhabricatorUser $viewer) { $field = id(new PhabricatorApplyEditField()) ->setKey($this->getRevisionActionKey()) ->setTransactionType($this->getTransactionTypeConstant()) ->setValue(true); if ($this->isActionAvailable($revision, $viewer)) { $label = $this->getRevisionActionLabel(); if ($label !== null) { $field->setCommentActionLabel($label); $description = $this->getRevisionActionDescription(); $field->setActionDescription($description); $group_key = $this->getRevisionActionGroupKey(); $field->setCommentActionGroupKey($group_key); // Currently, every revision action conflicts with every other // revision action: for example, you can not simultaneously Accept and // Reject a revision. // Under some configurations, some combinations of actions are sort of // technically permissible. For example, you could reasonably Reject // and Abandon a revision if "anyone can abandon anything" is enabled. // It's not clear that these combinations are actually useful, so just // keep things simple for now. $field->setActionConflictKey('revision.action'); } } return $field; } public function validateTransactions($object, array $xactions) { $errors = array(); $actor = $this->getActor(); $action_exception = null; try { $this->validateAction($object, $actor); } catch (Exception $ex) { $action_exception = $ex; } foreach ($xactions as $xaction) { if ($action_exception) { $errors[] = $this->newInvalidError( $action_exception->getMessage(), $xaction); } } return $errors; } } diff --git a/src/applications/differential/xaction/DifferentialRevisionCloseTransaction.php b/src/applications/differential/xaction/DifferentialRevisionCloseTransaction.php index c2ba6c1bb8..30bfd5044f 100644 --- a/src/applications/differential/xaction/DifferentialRevisionCloseTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionCloseTransaction.php @@ -1,89 +1,93 @@ isClosed(); } public function applyInternalEffects($object, $value) { $status_closed = ArcanistDifferentialRevisionStatus::CLOSED; $status_accepted = ArcanistDifferentialRevisionStatus::ACCEPTED; $old_status = $object->getStatus(); $object->setStatus($status_closed); $was_accepted = ($old_status == $status_accepted); $object->setProperty( DifferentialRevision::PROPERTY_CLOSED_FROM_ACCEPTED, $was_accepted); } protected function validateAction($object, PhabricatorUser $viewer) { if ($object->isClosed()) { throw new Exception( pht( 'You can not close this revision because it has already been '. 'closed. Only open revisions can be closed.')); } if (!$object->isAccepted()) { throw new Exception( pht( 'You can not close this revision because it has not been accepted. '. 'Revisions must be accepted before they can be closed.')); } $config_key = 'differential.always-allow-close'; if (!PhabricatorEnv::getEnvConfig($config_key)) { if (!$this->isViewerRevisionAuthor($object, $viewer)) { throw new Exception( pht( 'You can not close this revision because you are not the '. 'author. You can only close revisions you own. You can change '. 'this behavior by adjusting the "%s" setting in Config.', $config_key)); } } } public function getTitle() { return pht( '%s closed this revision.', $this->renderAuthor()); } public function getTitleForFeed() { return pht( '%s closed %s.', $this->renderAuthor(), $this->renderObject()); } } diff --git a/src/applications/differential/xaction/DifferentialRevisionCommandeerTransaction.php b/src/applications/differential/xaction/DifferentialRevisionCommandeerTransaction.php index 57ecf5a039..4e4cd9600f 100644 --- a/src/applications/differential/xaction/DifferentialRevisionCommandeerTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionCommandeerTransaction.php @@ -1,85 +1,89 @@ getAuthorPHID(); } public function generateNewValue($object, $value) { $actor = $this->getActor(); return $actor->getPHID(); } public function applyInternalEffects($object, $value) { $object->setAuthorPHID($value); } protected function validateAction($object, PhabricatorUser $viewer) { if ($object->isClosed()) { throw new Exception( pht( 'You can not commandeer this revision because it has already '. 'been closed. You can only commandeer open revisions.')); } if ($this->isViewerRevisionAuthor($object, $viewer)) { throw new Exception( pht( 'You can not commandeer this revision because you are already '. 'the author.')); } } public function getTitle() { return pht( '%s commandeered this revision.', $this->renderAuthor()); } public function getTitleForFeed() { return pht( '%s commandeered %s.', $this->renderAuthor(), $this->renderObject()); } } diff --git a/src/applications/differential/xaction/DifferentialRevisionPlanChangesTransaction.php b/src/applications/differential/xaction/DifferentialRevisionPlanChangesTransaction.php index 35c40273f6..e7cdaa2455 100644 --- a/src/applications/differential/xaction/DifferentialRevisionPlanChangesTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionPlanChangesTransaction.php @@ -1,93 +1,97 @@ getStatus() == $status_planned); } public function applyInternalEffects($object, $value) { $status_planned = ArcanistDifferentialRevisionStatus::CHANGES_PLANNED; $object->setStatus($status_planned); } protected function validateAction($object, PhabricatorUser $viewer) { $status_planned = ArcanistDifferentialRevisionStatus::CHANGES_PLANNED; if ($object->getStatus() == $status_planned) { throw new Exception( pht( 'You can not request review of this revision because this '. 'revision is already under review and the action would have '. 'no effect.')); } if ($object->isClosed()) { throw new Exception( pht( 'You can not plan changes to this this revision because it has '. 'already been closed.')); } if (!$this->isViewerRevisionAuthor($object, $viewer)) { throw new Exception( pht( 'You can not plan changes to this revision because you do not '. 'own it. Only the author of a revision can plan changes to it.')); } } public function getTitle() { return pht( '%s planned changes to this revision.', $this->renderAuthor()); } public function getTitleForFeed() { return pht( '%s planned changes to %s.', $this->renderAuthor(), $this->renderObject()); } } diff --git a/src/applications/differential/xaction/DifferentialRevisionReclaimTransaction.php b/src/applications/differential/xaction/DifferentialRevisionReclaimTransaction.php index 96a1f4415c..1f2f6a8d6c 100644 --- a/src/applications/differential/xaction/DifferentialRevisionReclaimTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionReclaimTransaction.php @@ -1,78 +1,82 @@ isAbandoned(); } public function applyInternalEffects($object, $value) { $object->setStatus(ArcanistDifferentialRevisionStatus::NEEDS_REVIEW); } protected function validateAction($object, PhabricatorUser $viewer) { if (!$object->isAbandoned()) { throw new Exception( pht( 'You can not reclaim this revision because it has not been '. 'abandoned. Only abandoned revisions can be reclaimed.')); } if (!$this->isViewerRevisionAuthor($object, $viewer)) { throw new Exception( pht( 'You can not reclaim this revision because you are not the '. 'revision author. You can only reclaim revisions you own.')); } } public function getTitle() { return pht( '%s reclaimed this revision.', $this->renderAuthor()); } public function getTitleForFeed() { return pht( '%s reclaimed %s.', $this->renderAuthor(), $this->renderObject()); } } diff --git a/src/applications/differential/xaction/DifferentialRevisionRejectTransaction.php b/src/applications/differential/xaction/DifferentialRevisionRejectTransaction.php index 808412ff78..96812de06b 100644 --- a/src/applications/differential/xaction/DifferentialRevisionRejectTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionRejectTransaction.php @@ -1,92 +1,96 @@ getActor(); return $this->isViewerRejectingReviewer($object, $actor); } public function applyExternalEffects($object, $value) { $status = DifferentialReviewerStatus::STATUS_REJECTED; $actor = $this->getActor(); $this->applyReviewerEffect($object, $actor, $value, $status); } protected function validateAction($object, PhabricatorUser $viewer) { if ($object->isClosed()) { throw new Exception( pht( 'You can not request changes to this revision because it has '. 'already been closed. You can only request changes to open '. 'revisions.')); } if ($this->isViewerRevisionAuthor($object, $viewer)) { throw new Exception( pht( 'You can not request changes to this revision because you are the '. 'revision author. You can only request changes to revisions you do '. 'not own.')); } if ($this->isViewerRejectingReviewer($object, $viewer)) { throw new Exception( pht( 'You can not request changes to this revision because you have '. 'already requested changes.')); } } public function getTitle() { return pht( '%s requested changes to this revision.', $this->renderAuthor()); } public function getTitleForFeed() { return pht( '%s requested changes to %s.', $this->renderAuthor(), $this->renderObject()); } } diff --git a/src/applications/differential/xaction/DifferentialRevisionReopenTransaction.php b/src/applications/differential/xaction/DifferentialRevisionReopenTransaction.php index 5024ab99fa..84015b9286 100644 --- a/src/applications/differential/xaction/DifferentialRevisionReopenTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionReopenTransaction.php @@ -1,72 +1,76 @@ isClosed(); } public function applyInternalEffects($object, $value) { $object->setStatus(ArcanistDifferentialRevisionStatus::NEEDS_REVIEW); } protected function validateAction($object, PhabricatorUser $viewer) { // Note that we're testing for "Closed", exactly, not just any closed // status. $status_closed = ArcanistDifferentialRevisionStatus::CLOSED; if ($object->getStatus() != $status_closed) { throw new Exception( pht( 'You can not reopen this revision because it is not closed. '. 'Only closed revisions can be reopened.')); } $config_key = 'differential.allow-reopen'; if (!PhabricatorEnv::getEnvConfig($config_key)) { throw new Exception( pht( 'You can not reopen this revision because configuration prevents '. 'any revision from being reopened. You can change this behavior '. 'by adjusting the "%s" setting in Config.', $config_key)); } } public function getTitle() { return pht( '%s reopened this revision.', $this->renderAuthor()); } public function getTitleForFeed() { return pht( '%s reopened %s.', $this->renderAuthor(), $this->renderObject()); } } diff --git a/src/applications/differential/xaction/DifferentialRevisionRequestReviewTransaction.php b/src/applications/differential/xaction/DifferentialRevisionRequestReviewTransaction.php index cd1c14c66e..32fcb3271e 100644 --- a/src/applications/differential/xaction/DifferentialRevisionRequestReviewTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionRequestReviewTransaction.php @@ -1,74 +1,78 @@ getStatus() == $status_review); } public function applyInternalEffects($object, $value) { $status_review = ArcanistDifferentialRevisionStatus::NEEDS_REVIEW; $object->setStatus($status_review); } protected function validateAction($object, PhabricatorUser $viewer) { $status_review = ArcanistDifferentialRevisionStatus::NEEDS_REVIEW; if ($object->getStatus() == $status_review) { throw new Exception( pht( 'You can not request review of this revision because this '. 'revision is already under review and the action would have '. 'no effect.')); } if ($object->isClosed()) { throw new Exception( pht( 'You can not request review of this revision because it has '. 'already been closed. You can only request review of open '. 'revisions.')); } if (!$this->isViewerRevisionAuthor($object, $viewer)) { throw new Exception( pht( 'You can not request review of this revision because you are not '. 'the author of the revision.')); } } public function getTitle() { return pht( '%s requested review of this revision.', $this->renderAuthor()); } public function getTitleForFeed() { return pht( '%s requested review of %s.', $this->renderAuthor(), $this->renderObject()); } } diff --git a/src/applications/differential/xaction/DifferentialRevisionResignTransaction.php b/src/applications/differential/xaction/DifferentialRevisionResignTransaction.php index 0d3045247d..5b75d7753f 100644 --- a/src/applications/differential/xaction/DifferentialRevisionResignTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionResignTransaction.php @@ -1,82 +1,86 @@ getActor(); return !$this->isViewerAnyReviewer($object, $actor); } public function applyExternalEffects($object, $value) { $status = DifferentialReviewerStatus::STATUS_RESIGNED; $actor = $this->getActor(); $this->applyReviewerEffect($object, $actor, $value, $status); } protected function validateAction($object, PhabricatorUser $viewer) { if ($object->isClosed()) { throw new Exception( pht( 'You can not resign from this revision because it has already '. 'been closed. You can only resign from open revisions.')); } if (!$this->isViewerAnyReviewer($object, $viewer)) { throw new Exception( pht( 'You can not resign from this revision because you are not a '. 'reviewer. You can only resign from revisions where you are a '. 'reviewer.')); } } public function getTitle() { return pht( '%s resigned from this revision.', $this->renderAuthor()); } public function getTitleForFeed() { return pht( '%s resigned from %s.', $this->renderAuthor(), $this->renderObject()); } } diff --git a/src/applications/diffusion/xaction/DiffusionCommitAcceptTransaction.php b/src/applications/diffusion/xaction/DiffusionCommitAcceptTransaction.php index e04310a53e..76c2de609e 100644 --- a/src/applications/diffusion/xaction/DiffusionCommitAcceptTransaction.php +++ b/src/applications/diffusion/xaction/DiffusionCommitAcceptTransaction.php @@ -1,74 +1,78 @@ getActor(); return $this->isViewerAcceptingAuditor($object, $actor); } public function applyExternalEffects($object, $value) { $status = PhabricatorAuditStatusConstants::ACCEPTED; $actor = $this->getActor(); $this->applyAuditorEffect($object, $actor, $value, $status); } protected function validateAction($object, PhabricatorUser $viewer) { $config_key = 'audit.can-author-close-audit'; if (!PhabricatorEnv::getEnvConfig($config_key)) { if ($this->isViewerCommitAuthor($object, $viewer)) { throw new Exception( pht( 'You can not accept this commit because you are the commit '. 'author. You can only accept commits you did not author. You can '. 'change this behavior by adjusting the "%s" setting in Config.', $config_key)); } } if ($this->isViewerAcceptingAuditor($object, $viewer)) { throw new Exception( pht( 'You can not accept this commit because you have already '. 'accepted it.')); } } public function getTitle() { return pht( '%s accepted this commit.', $this->renderAuthor()); } public function getTitleForFeed() { return pht( '%s accepted %s.', $this->renderAuthor(), $this->renderObject()); } } diff --git a/src/applications/diffusion/xaction/DiffusionCommitConcernTransaction.php b/src/applications/diffusion/xaction/DiffusionCommitConcernTransaction.php index 01d4db01ad..6bbc27818f 100644 --- a/src/applications/diffusion/xaction/DiffusionCommitConcernTransaction.php +++ b/src/applications/diffusion/xaction/DiffusionCommitConcernTransaction.php @@ -1,70 +1,74 @@ getActor(); return $this->isViewerRejectingAuditor($object, $actor); } public function applyExternalEffects($object, $value) { $status = PhabricatorAuditStatusConstants::CONCERNED; $actor = $this->getActor(); $this->applyAuditorEffect($object, $actor, $value, $status); } protected function validateAction($object, PhabricatorUser $viewer) { if ($this->isViewerCommitAuthor($object, $viewer)) { throw new Exception( pht( 'You can not raise a concern with this commit because you are '. 'the commit author. You can only raise concerns with commits '. 'you did not author.')); } if ($this->isViewerRejectingAuditor($object, $viewer)) { throw new Exception( pht( 'You can not raise a concern with this commit because you have '. 'already raised a concern with it.')); } } public function getTitle() { return pht( '%s raised a concern with this commit.', $this->renderAuthor()); } public function getTitleForFeed() { return pht( '%s raised a concern with %s.', $this->renderAuthor(), $this->renderObject()); } } diff --git a/src/applications/diffusion/xaction/DiffusionCommitResignTransaction.php b/src/applications/diffusion/xaction/DiffusionCommitResignTransaction.php index 4a68252414..103d0fabfe 100644 --- a/src/applications/diffusion/xaction/DiffusionCommitResignTransaction.php +++ b/src/applications/diffusion/xaction/DiffusionCommitResignTransaction.php @@ -1,62 +1,66 @@ getActor(); return !$this->isViewerAnyActiveAuditor($object, $actor); } public function applyExternalEffects($object, $value) { $status = PhabricatorAuditStatusConstants::RESIGNED; $actor = $this->getActor(); $this->applyAuditorEffect($object, $actor, $value, $status); } protected function validateAction($object, PhabricatorUser $viewer) { if (!$this->isViewerAnyActiveAuditor($object, $viewer)) { throw new Exception( pht( 'You can not resign from this commit because you are not an '. 'active auditor.')); } } public function getTitle() { return pht( '%s resigned from this commit.', $this->renderAuthor()); } public function getTitleForFeed() { return pht( '%s resigned from %s.', $this->renderAuthor(), $this->renderObject()); } } diff --git a/src/applications/transactions/storage/PhabricatorModularTransaction.php b/src/applications/transactions/storage/PhabricatorModularTransaction.php index 035a2585fd..396478c98b 100644 --- a/src/applications/transactions/storage/PhabricatorModularTransaction.php +++ b/src/applications/transactions/storage/PhabricatorModularTransaction.php @@ -1,168 +1,186 @@ getTransactionImplementation(); } final protected function getTransactionImplementation() { if (!$this->implementation) { $this->implementation = $this->newTransactionImplementation(); } return $this->implementation; } public function newModularTransactionTypes() { $base_class = $this->getBaseTransactionClass(); $types = id(new PhutilClassMapQuery()) ->setAncestorClass($base_class) ->setUniqueMethod('getTransactionTypeConstant') ->execute(); // Add core transaction types. $types += id(new PhutilClassMapQuery()) ->setAncestorClass('PhabricatorCoreTransactionType') ->setUniqueMethod('getTransactionTypeConstant') ->execute(); return $types; } private function newTransactionImplementation() { $types = $this->newModularTransactionTypes(); $key = $this->getTransactionType(); if (empty($types[$key])) { $type = $this->newFallbackModularTransactionType(); } else { $type = clone $types[$key]; } $type->setStorage($this); return $type; } protected function newFallbackModularTransactionType() { return new PhabricatorCoreVoidTransaction(); } final public function generateOldValue($object) { return $this->getTransactionImplementation()->generateOldValue($object); } final public function generateNewValue($object) { return $this->getTransactionImplementation() ->generateNewValue($object, $this->getNewValue()); } final public function willApplyTransactions($object, array $xactions) { return $this->getTransactionImplementation() ->willApplyTransactions($object, $xactions); } final public function applyInternalEffects($object) { return $this->getTransactionImplementation() ->applyInternalEffects($object); } final public function applyExternalEffects($object) { return $this->getTransactionImplementation() ->applyExternalEffects($object); } /* final */ public function shouldHide() { if ($this->getTransactionImplementation()->shouldHide()) { return true; } return parent::shouldHide(); } /* final */ public function getIcon() { $icon = $this->getTransactionImplementation()->getIcon(); if ($icon !== null) { return $icon; } return parent::getIcon(); } /* final */ public function getTitle() { $title = $this->getTransactionImplementation()->getTitle(); if ($title !== null) { return $title; } return parent::getTitle(); } + /* final */ public function getActionName() { + $action = $this->getTransactionImplementation()->getActionName(); + if ($action !== null) { + return $action; + } + + return parent::getActionName(); + } + + /* final */ public function getActionStrength() { + $strength = $this->getTransactionImplementation()->getActionStrength(); + if ($strength !== null) { + return $strength; + } + + return parent::getActionStrength(); + } + public function getTitleForMail() { $old_target = $this->getRenderingTarget(); $new_target = self::TARGET_TEXT; $this->setRenderingTarget($new_target); $title = $this->getTitle(); $this->setRenderingTarget($old_target); return $title; } /* final */ public function getTitleForFeed() { $title = $this->getTransactionImplementation()->getTitleForFeed(); if ($title !== null) { return $title; } return parent::getTitleForFeed(); } /* final */ public function getColor() { $color = $this->getTransactionImplementation()->getColor(); if ($color !== null) { return $color; } return parent::getColor(); } public function attachViewer(PhabricatorUser $viewer) { $this->getTransactionImplementation()->setViewer($viewer); return parent::attachViewer($viewer); } final public function hasChangeDetails() { if ($this->getTransactionImplementation()->hasChangeDetailView()) { return true; } return parent::hasChangeDetails(); } final public function renderChangeDetails(PhabricatorUser $viewer) { $impl = $this->getTransactionImplementation(); $impl->setViewer($viewer); $view = $impl->newChangeDetailView(); if ($view !== null) { return $view; } return parent::renderChangeDetails($viewer); } final protected function newRemarkupChanges() { return $this->getTransactionImplementation()->newRemarkupChanges(); } } diff --git a/src/applications/transactions/storage/PhabricatorModularTransactionType.php b/src/applications/transactions/storage/PhabricatorModularTransactionType.php index 2606264778..f57d91b08f 100644 --- a/src/applications/transactions/storage/PhabricatorModularTransactionType.php +++ b/src/applications/transactions/storage/PhabricatorModularTransactionType.php @@ -1,304 +1,312 @@ getPhobjectClassConstant('TRANSACTIONTYPE'); } public function generateOldValue($object) { throw new PhutilMethodNotImplementedException(); } public function generateNewValue($object, $value) { return $value; } public function validateTransactions($object, array $xactions) { return array(); } public function willApplyTransactions($object, array $xactions) { return; } public function applyInternalEffects($object, $value) { return; } public function applyExternalEffects($object, $value) { return; } public function getTransactionHasEffect($object, $old, $new) { return ($old !== $new); } public function extractFilePHIDs($object, $value) { return array(); } public function shouldHide() { return false; } public function getIcon() { return null; } public function getTitle() { return null; } public function getTitleForFeed() { return null; } + public function getActionName() { + return null; + } + + public function getActionStrength() { + return null; + } + public function getColor() { return null; } public function hasChangeDetailView() { return false; } public function newChangeDetailView() { return null; } public function getMailDiffSectionHeader() { return pht('EDIT DETAILS'); } public function newRemarkupChanges() { return array(); } final public function setStorage( PhabricatorApplicationTransaction $xaction) { $this->storage = $xaction; return $this; } private function getStorage() { return $this->storage; } final public function setViewer(PhabricatorUser $viewer) { $this->viewer = $viewer; return $this; } final protected function getViewer() { return $this->viewer; } final public function getActor() { return $this->getEditor()->getActor(); } final public function getActingAsPHID() { return $this->getEditor()->getActingAsPHID(); } final public function setEditor( PhabricatorApplicationTransactionEditor $editor) { $this->editor = $editor; return $this; } final protected function getEditor() { if (!$this->editor) { throw new PhutilInvalidStateException('setEditor'); } return $this->editor; } final protected function getAuthorPHID() { return $this->getStorage()->getAuthorPHID(); } final protected function getObjectPHID() { return $this->getStorage()->getObjectPHID(); } final protected function getObject() { return $this->getStorage()->getObject(); } final protected function getOldValue() { return $this->getStorage()->getOldValue(); } final protected function getNewValue() { return $this->getStorage()->getNewValue(); } final protected function renderAuthor() { $author_phid = $this->getAuthorPHID(); return $this->getStorage()->renderHandleLink($author_phid); } final protected function renderObject() { $object_phid = $this->getObjectPHID(); return $this->getStorage()->renderHandleLink($object_phid); } final protected function renderHandle($phid) { $viewer = $this->getViewer(); $display = $viewer->renderHandle($phid); if ($this->isTextMode()) { $display->setAsText(true); } return $display; } final protected function renderOldHandle() { return $this->renderHandle($this->getOldValue()); } final protected function renderNewHandle() { return $this->renderHandle($this->getNewValue()); } final protected function renderHandleList(array $phids) { $viewer = $this->getViewer(); $display = $viewer->renderHandleList($phids) ->setAsInline(true); if ($this->isTextMode()) { $display->setAsText(true); } return $display; } final protected function renderValue($value) { if ($this->isTextMode()) { return sprintf('"%s"', $value); } return phutil_tag( 'span', array( 'class' => 'phui-timeline-value', ), $value); } final protected function renderOldValue() { return $this->renderValue($this->getOldValue()); } final protected function renderNewValue() { return $this->renderValue($this->getNewValue()); } final protected function renderDate($epoch) { $viewer = $this->getViewer(); // We accept either epoch timestamps or dictionaries describing a // PhutilCalendarDateTime. if (is_array($epoch)) { $datetime = PhutilCalendarAbsoluteDateTime::newFromDictionary($epoch) ->setViewerTimezone($viewer->getTimezoneIdentifier()); $all_day = $datetime->getIsAllDay(); $epoch = $datetime->getEpoch(); } else { $all_day = false; } if ($all_day) { $display = phabricator_date($epoch, $viewer); } else { $display = phabricator_datetime($epoch, $viewer); // When rendering to text, we explicitly render the offset from UTC to // provide context to the date: the mail may be generating with the // server's settings, or the user may later refer back to it after // changing timezones. if ($this->isRenderingTargetExternal()) { $offset = $viewer->getTimeZoneOffsetInHours(); if ($offset >= 0) { $display = pht('%s (UTC+%d)', $display, $offset); } else { $display = pht('%s (UTC-%d)', $display, abs($offset)); } } } return $this->renderValue($display); } final protected function renderOldDate() { return $this->renderDate($this->getOldValue()); } final protected function renderNewDate() { return $this->renderDate($this->getNewValue()); } final protected function newError($title, $message, $xaction = null) { return new PhabricatorApplicationTransactionValidationError( $this->getTransactionTypeConstant(), $title, $message, $xaction); } final protected function newRequiredError($message, $xaction = null) { return $this->newError(pht('Required'), $message, $xaction) ->setIsMissingFieldError(true); } final protected function newInvalidError($message, $xaction = null) { return $this->newError(pht('Invalid'), $message, $xaction); } final protected function isNewObject() { return $this->getEditor()->getIsNewObject(); } final protected function isEmptyTextTransaction($value, array $xactions) { foreach ($xactions as $xaction) { $value = $xaction->getNewValue(); } return !strlen($value); } /** * When rendering to external targets (Email/Asana/etc), we need to include * more information that users can't obtain later. */ final protected function isRenderingTargetExternal() { // Right now, this is our best proxy for this: return $this->isTextMode(); // "TARGET_TEXT" means "EMail" and "TARGET_HTML" means "Web". } final protected function isTextMode() { $target = $this->getStorage()->getRenderingTarget(); return ($target == PhabricatorApplicationTransaction::TARGET_TEXT); } final protected function newRemarkupChange() { return id(new PhabricatorTransactionRemarkupChange()) ->setTransaction($this->getStorage()); } final protected function isCreateTransaction() { return $this->getStorage()->getIsCreateTransaction(); } }