diff --git a/src/applications/differential/command/DifferentialActionEmailCommand.php b/src/applications/differential/command/DifferentialActionEmailCommand.php index a761339141..cbf94487e9 100644 --- a/src/applications/differential/command/DifferentialActionEmailCommand.php +++ b/src/applications/differential/command/DifferentialActionEmailCommand.php @@ -1,140 +1,103 @@ command; } private function setCommand($command) { $this->command = $command; return $this; } private function setAction($action) { $this->action = $action; return $this; } private function getAction() { return $this->action; } private function setCommandAliases(array $aliases) { $this->aliases = $aliases; return $this; } public function getCommandAliases() { return $this->aliases; } public function setCommandSummary($command_summary) { $this->commandSummary = $command_summary; return $this; } public function getCommandSummary() { return $this->commandSummary; } public function setCommandDescription($command_description) { $this->commandDescription = $command_description; return $this; } public function getCommandDescription() { return $this->commandDescription; } public function getCommandObjects() { - $actions = array( - DifferentialAction::ACTION_REJECT => 'request', - DifferentialAction::ACTION_ABANDON => 'abandon', - DifferentialAction::ACTION_RECLAIM => 'reclaim', - DifferentialAction::ACTION_RESIGN => 'resign', - DifferentialAction::ACTION_RETHINK => 'planchanges', - DifferentialAction::ACTION_CLAIM => 'commandeer', - ); - - if (PhabricatorEnv::getEnvConfig('differential.enable-email-accept')) { - $actions[DifferentialAction::ACTION_ACCEPT] = 'accept'; - } - - $aliases = array( - DifferentialAction::ACTION_REJECT => array('reject'), - DifferentialAction::ACTION_CLAIM => array('claim'), - DifferentialAction::ACTION_RETHINK => array('rethink'), - ); - - $summaries = array( - DifferentialAction::ACTION_REJECT => - pht('Request changes to a revision.'), - DifferentialAction::ACTION_ABANDON => - pht('Abandon a revision.'), - DifferentialAction::ACTION_RECLAIM => - pht('Reclaim a revision.'), - DifferentialAction::ACTION_RESIGN => - pht('Resign from a revision.'), - DifferentialAction::ACTION_RETHINK => - pht('Plan changes to a revision.'), - DifferentialAction::ACTION_CLAIM => - pht('Commandeer a revision.'), - DifferentialAction::ACTION_ACCEPT => - pht('Accept a revision.'), - ); - - $descriptions = array( - - ); + $actions = DifferentialRevisionActionTransaction::loadAllActions(); + $actions = msort($actions, 'getRevisionActionOrderVector'); $objects = array(); - foreach ($actions as $action => $keyword) { - $object = id(new DifferentialActionEmailCommand()) - ->setCommand($keyword) - ->setAction($action) - ->setCommandSummary($summaries[$action]); - - if (isset($aliases[$action])) { - $object->setCommandAliases($aliases[$action]); + foreach ($actions as $action) { + $keyword = $action->getCommandKeyword(); + if ($keyword === null) { + continue; } - if (isset($descriptions[$action])) { - $object->setCommandDescription($descriptions[$action]); - } + $aliases = $action->getCommandAliases(); + $summary = $action->getCommandSummary(); + + $object = id(new self()) + ->setCommand($keyword) + ->setCommandAliases($aliases) + ->setAction($action->getTransactionTypeConstant()) + ->setCommandSummary($summary); $objects[] = $object; } - return $objects; } public function isCommandSupportedForObject( PhabricatorApplicationTransactionInterface $object) { return ($object instanceof DifferentialRevision); } public function buildTransactions( PhabricatorUser $viewer, PhabricatorApplicationTransactionInterface $object, PhabricatorMetaMTAReceivedMail $mail, $command, array $argv) { $xactions = array(); $xactions[] = $object->getApplicationTransactionTemplate() - ->setTransactionType(DifferentialTransaction::TYPE_ACTION) - ->setNewValue($this->getAction()); + ->setTransactionType($this->getAction()) + ->setNewValue(true); return $xactions; } } diff --git a/src/applications/differential/xaction/DifferentialRevisionAbandonTransaction.php b/src/applications/differential/xaction/DifferentialRevisionAbandonTransaction.php index e4fc7f1a5f..627e1c9d37 100644 --- a/src/applications/differential/xaction/DifferentialRevisionAbandonTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionAbandonTransaction.php @@ -1,71 +1,83 @@ 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 c99c663c8d..dcb19c4037 100644 --- a/src/applications/differential/xaction/DifferentialRevisionAcceptTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionAcceptTransaction.php @@ -1,81 +1,99 @@ 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 14d7a1369a..dd80706b44 100644 --- a/src/applications/differential/xaction/DifferentialRevisionActionTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionActionTransaction.php @@ -1,116 +1,128 @@ 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 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/DifferentialRevisionCommandeerTransaction.php b/src/applications/differential/xaction/DifferentialRevisionCommandeerTransaction.php index fac7ec8b0b..57ecf5a039 100644 --- a/src/applications/differential/xaction/DifferentialRevisionCommandeerTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionCommandeerTransaction.php @@ -1,71 +1,85 @@ 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 d33992d36e..35c40273f6 100644 --- a/src/applications/differential/xaction/DifferentialRevisionPlanChangesTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionPlanChangesTransaction.php @@ -1,79 +1,93 @@ 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 ce8367f7c3..96a1f4415c 100644 --- a/src/applications/differential/xaction/DifferentialRevisionReclaimTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionReclaimTransaction.php @@ -1,66 +1,78 @@ 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 17ecca0e20..808412ff78 100644 --- a/src/applications/differential/xaction/DifferentialRevisionRejectTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionRejectTransaction.php @@ -1,78 +1,92 @@ 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/DifferentialRevisionResignTransaction.php b/src/applications/differential/xaction/DifferentialRevisionResignTransaction.php index f994c2f45e..0d3045247d 100644 --- a/src/applications/differential/xaction/DifferentialRevisionResignTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionResignTransaction.php @@ -1,70 +1,82 @@ 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()); } }