diff --git a/src/applications/differential/xaction/DifferentialRevisionAbandonTransaction.php b/src/applications/differential/xaction/DifferentialRevisionAbandonTransaction.php index c2baef034f..c793663b89 100644 --- a/src/applications/differential/xaction/DifferentialRevisionAbandonTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionAbandonTransaction.php @@ -1,97 +1,100 @@ isAbandoned(); } public function applyInternalEffects($object, $value) { $status_abandoned = DifferentialRevisionStatus::ABANDONED; $object->setModernRevisionStatus($status_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()); } public function getTransactionTypeForConduit($xaction) { return 'abandon'; } public function getFieldValuesForConduit($object, $data) { return array(); } } diff --git a/src/applications/differential/xaction/DifferentialRevisionAcceptTransaction.php b/src/applications/differential/xaction/DifferentialRevisionAcceptTransaction.php index 3d2df14899..12596d8693 100644 --- a/src/applications/differential/xaction/DifferentialRevisionAcceptTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionAcceptTransaction.php @@ -1,245 +1,248 @@ getReviewers(); $options = array(); $value = array(); // Put the viewer's user reviewer first, if it exists, so that "Accept as // yourself" is always at the top. $head = array(); $tail = array(); foreach ($reviewers as $key => $reviewer) { if ($reviewer->isUser()) { $head[$key] = $reviewer; } else { $tail[$key] = $reviewer; } } $reviewers = $head + $tail; $diff_phid = $this->getActiveDiffPHID($revision); $reviewer_phids = array(); // If the viewer isn't a reviewer, add them to the list of options first. // This happens when you navigate to some revision you aren't involved in: // you can accept and become a reviewer. $viewer_phid = $viewer->getPHID(); if ($viewer_phid) { if (!isset($reviewers[$viewer_phid])) { $reviewer_phids[$viewer_phid] = $viewer_phid; } } $default_unchecked = array(); foreach ($reviewers as $reviewer) { $reviewer_phid = $reviewer->getReviewerPHID(); if (!$reviewer->hasAuthority($viewer)) { // If the viewer doesn't have authority to act on behalf of a reviewer, // we check if they can accept by force. if ($revision->canReviewerForceAccept($viewer, $reviewer)) { $default_unchecked[$reviewer_phid] = true; } else { continue; } } if (!$include_accepted) { if ($reviewer->isAccepted($diff_phid)) { // If a reviewer is already in a full "accepted" state, don't // include that reviewer as an option unless we're listing all // reviewers, including reviewers who have already accepted. continue; } } $reviewer_phids[$reviewer_phid] = $reviewer_phid; } $handles = $viewer->loadHandles($reviewer_phids); $head = array(); $tail = array(); foreach ($reviewer_phids as $reviewer_phid) { $is_force = isset($default_unchecked[$reviewer_phid]); if ($is_force) { $tail[] = $reviewer_phid; $options[$reviewer_phid] = pht( 'Force accept as %s', $viewer->renderHandle($reviewer_phid)); } else { $head[] = $reviewer_phid; $value[] = $reviewer_phid; $options[$reviewer_phid] = pht( 'Accept as %s', $viewer->renderHandle($reviewer_phid)); } } // Reorder reviewers so "force accept" reviewers come at the end. $options = array_select_keys($options, $head) + array_select_keys($options, $tail); return array($options, $value); } public function generateOldValue($object) { $actor = $this->getActor(); return $this->isViewerFullyAccepted($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.')); } if ($object->isDraft() || !$object->getShouldBroadcast()) { throw new Exception( pht('You can not accept a draft revision.')); } $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->isViewerFullyAccepted($object, $viewer)) { throw new Exception( pht( 'You can not accept this revision because you have already '. 'accepted it.')); } } protected function validateOptionValue($object, $actor, array $value) { if (!$value) { throw new Exception( pht( 'When accepting a revision, you must accept on behalf of at '. 'least one reviewer.')); } // NOTE: We're including reviewers who have already been accepted in this // check. Legitimate users may race one another to accept on behalf of // packages. If we get a form submission which includes a reviewer which // someone has already accepted, that's fine. See T12757. list($options) = $this->getActionOptions($actor, $object, true); foreach ($value as $phid) { if (!isset($options[$phid])) { throw new Exception( pht( 'Reviewer "%s" is not a valid reviewer which you have authority '. 'to accept on behalf of.', $phid)); } } } public function getTitle() { $new = $this->getNewValue(); if (is_array($new) && $new) { return pht( '%s accepted this revision as %s reviewer(s): %s.', $this->renderAuthor(), phutil_count($new), $this->renderHandleList($new)); } else { return pht( '%s accepted this revision.', $this->renderAuthor()); } } public function getTitleForFeed() { return pht( '%s accepted %s.', $this->renderAuthor(), $this->renderObject()); } public function getTransactionTypeForConduit($xaction) { return 'accept'; } public function getFieldValuesForConduit($object, $data) { return array(); } } diff --git a/src/applications/differential/xaction/DifferentialRevisionActionTransaction.php b/src/applications/differential/xaction/DifferentialRevisionActionTransaction.php index 338fde99b2..241678487a 100644 --- a/src/applications/differential/xaction/DifferentialRevisionActionTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionActionTransaction.php @@ -1,202 +1,208 @@ 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(); + abstract protected function getRevisionActionLabel( + DifferentialRevision $revision, + PhabricatorUser $viewer); protected function validateOptionValue($object, $actor, array $value) { return null; } public function getCommandKeyword() { return null; } public function getCommandAliases() { return array(); } public function getCommandSummary() { return null; } protected function getRevisionActionOrder() { return 1000; } public function getActionStrength() { return 300; } public function getRevisionActionOrderVector() { return id(new PhutilSortVector()) ->addInt($this->getRevisionActionOrder()); } protected function getRevisionActionGroupKey() { return DifferentialRevisionEditEngine::ACTIONGROUP_REVISION; } protected function getRevisionActionDescription( - DifferentialRevision $revision) { + DifferentialRevision $revision, + PhabricatorUser $viewer) { return null; } protected function getRevisionActionSubmitButtonText( - DifferentialRevision $revision) { + DifferentialRevision $revision, + PhabricatorUser $viewer) { 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()); } protected function getActionOptions( PhabricatorUser $viewer, DifferentialRevision $revision) { return array( array(), array(), ); } public function newEditField( DifferentialRevision $revision, PhabricatorUser $viewer) { // Actions in the "review" group, like "Accept Revision", do not require // that the actor be able to edit the revision. $group_review = DifferentialRevisionEditEngine::ACTIONGROUP_REVIEW; $is_review = ($this->getRevisionActionGroupKey() == $group_review); $field = id(new PhabricatorApplyEditField()) ->setKey($this->getRevisionActionKey()) ->setTransactionType($this->getTransactionTypeConstant()) ->setCanApplyWithoutEditCapability($is_review) ->setValue(true); if ($this->isActionAvailable($revision, $viewer)) { - $label = $this->getRevisionActionLabel(); + $label = $this->getRevisionActionLabel($revision, $viewer); if ($label !== null) { $field->setCommentActionLabel($label); - $description = $this->getRevisionActionDescription($revision); + $description = $this->getRevisionActionDescription($revision, $viewer); $field->setActionDescription($description); $group_key = $this->getRevisionActionGroupKey(); $field->setCommentActionGroupKey($group_key); - $button_text = $this->getRevisionActionSubmitButtonText($revision); + $button_text = $this->getRevisionActionSubmitButtonText( + $revision, + $viewer); $field->setActionSubmitButtonText($button_text); // 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'); list($options, $value) = $this->getActionOptions($viewer, $revision); // Show the options if the user can select on behalf of two or more // reviewers, or can force-accept on behalf of one or more reviewers, // or can accept on behalf of a reviewer other than themselves (see // T12533). $can_multi = (count($options) > 1); $can_force = (count($value) < count($options)); $not_self = (head_key($options) != $viewer->getPHID()); if ($can_multi || $can_force || $not_self) { $field->setOptions($options); $field->setValue($value); } } } return $field; } public function validateTransactions($object, array $xactions) { $errors = array(); $actor = $this->getActor(); $action_exception = null; foreach ($xactions as $xaction) { // If this is a draft demotion action, let it skip all the normal // validation. This is a little hacky and should perhaps move down // into the actual action implementations, but currently we can not // apply this rule in validateAction() because it doesn't operate on // the actual transaction. if ($xaction->getMetadataValue('draft.demote')) { continue; } try { $this->validateAction($object, $actor); } catch (Exception $ex) { $action_exception = $ex; } break; } foreach ($xactions as $xaction) { if ($action_exception) { $errors[] = $this->newInvalidError( $action_exception->getMessage(), $xaction); continue; } $new = $xaction->getNewValue(); if (!is_array($new)) { continue; } try { $this->validateOptionValue($object, $actor, $new); } catch (Exception $ex) { $errors[] = $this->newInvalidError( $ex->getMessage(), $xaction); } } return $errors; } } diff --git a/src/applications/differential/xaction/DifferentialRevisionCloseTransaction.php b/src/applications/differential/xaction/DifferentialRevisionCloseTransaction.php index 1a6017a02f..b1ad04ba77 100644 --- a/src/applications/differential/xaction/DifferentialRevisionCloseTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionCloseTransaction.php @@ -1,163 +1,166 @@ isClosed(); } public function applyInternalEffects($object, $value) { $was_accepted = $object->isAccepted(); $status_published = DifferentialRevisionStatus::PUBLISHED; $object->setModernRevisionStatus($status_published); $object->setProperty( DifferentialRevision::PROPERTY_CLOSED_FROM_ACCEPTED, $was_accepted); // See T13300. When a revision is closed, we promote it out of "Draft" // immediately. This usually happens when a user creates a draft revision // and then lands the associated commit before the revision leaves draft. $object->setShouldBroadcast(true); } protected function validateAction($object, PhabricatorUser $viewer) { if ($this->hasEditor()) { if ($this->getEditor()->getIsCloseByCommit()) { // If we're closing a revision because we discovered a commit, we don't // care what state it was in. return; } } 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() { $commit_phid = $this->getMetadataValue('commitPHID'); if ($commit_phid) { $commit = id(new DiffusionCommitQuery()) ->setViewer($this->getViewer()) ->withPHIDs(array($commit_phid)) ->needIdentities(true) ->executeOne(); } else { $commit = null; } if (!$commit) { return pht( '%s closed this revision.', $this->renderAuthor()); } $author_phid = null; if ($commit->hasAuthorIdentity()) { $identity = $commit->getAuthorIdentity(); $author_phid = $identity->getIdentityDisplayPHID(); } $committer_phid = null; if ($commit->hasCommitterIdentity()) { $identity = $commit->getCommitterIdentity(); $committer_phid = $identity->getIdentityDisplayPHID(); } if (!$author_phid) { return pht( 'Closed by commit %s.', $this->renderHandle($commit_phid)); } else if (!$committer_phid || ($committer_phid === $author_phid)) { return pht( 'Closed by commit %s (authored by %s).', $this->renderHandle($commit_phid), $this->renderHandle($author_phid)); } else { return pht( 'Closed by commit %s (authored by %s, committed by %s).', $this->renderHandle($commit_phid), $this->renderHandle($author_phid), $this->renderHandle($committer_phid)); } } public function getTitleForFeed() { return pht( '%s closed %s.', $this->renderAuthor(), $this->renderObject()); } public function getTransactionTypeForConduit($xaction) { return 'close'; } public function getFieldValuesForConduit($object, $data) { $commit_phid = $object->getMetadataValue('commitPHID'); if ($commit_phid) { $commit_phids = array($commit_phid); } else { $commit_phids = array(); } return array( 'commitPHIDs' => $commit_phids, ); } } diff --git a/src/applications/differential/xaction/DifferentialRevisionCommandeerTransaction.php b/src/applications/differential/xaction/DifferentialRevisionCommandeerTransaction.php index a296597bc7..88f6a7258b 100644 --- a/src/applications/differential/xaction/DifferentialRevisionCommandeerTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionCommandeerTransaction.php @@ -1,108 +1,111 @@ 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 a revision has already landed, we generally want to discourage // reopening and reusing it since this tends to create a big mess (users // should create a new revision instead). Thus, we stop you from // commandeering closed revisions. // See PHI985. If the revision was abandoned, there's no peril in allowing // the commandeer since the change (likely) never actually landed. So // it's okay to commandeer abandoned revisions. if ($object->isClosed() && !$object->isAbandoned()) { throw new Exception( pht( 'You can not commandeer this revision because it has already '. 'been closed. You can only commandeer open or abandoned '. '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()); } public function getTransactionTypeForConduit($xaction) { return 'commandeer'; } public function getFieldValuesForConduit($object, $data) { return array(); } } diff --git a/src/applications/differential/xaction/DifferentialRevisionPlanChangesTransaction.php b/src/applications/differential/xaction/DifferentialRevisionPlanChangesTransaction.php index f4fb0a3eb1..144152526d 100644 --- a/src/applications/differential/xaction/DifferentialRevisionPlanChangesTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionPlanChangesTransaction.php @@ -1,130 +1,133 @@ isChangePlanned(); } public function applyInternalEffects($object, $value) { $status_planned = DifferentialRevisionStatus::CHANGES_PLANNED; $object->setModernRevisionStatus($status_planned); } protected function validateAction($object, PhabricatorUser $viewer) { if ($object->isDraft()) { // See PHI346. Until the "Draft" state fully unprototypes, allow drafts // to be moved to "changes planned" via the API. This preserves the // behavior of "arc diff --plan-changes". We still prevent this // transition from the web UI. // TODO: Remove this once drafts leave prototype. $editor = $this->getEditor(); $type_web = PhabricatorWebContentSource::SOURCECONST; if ($editor->getContentSource()->getSource() == $type_web) { throw new Exception( pht('You can not plan changes to a draft revision.')); } } if ($object->isChangePlanned()) { 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() { if ($this->isDraftDemotion()) { return pht( '%s returned this revision to the author for changes because remote '. 'builds failed.', $this->renderAuthor()); } else { return pht( '%s planned changes to this revision.', $this->renderAuthor()); } } public function getTitleForFeed() { return pht( '%s planned changes to %s.', $this->renderAuthor(), $this->renderObject()); } private function isDraftDemotion() { return (bool)$this->getMetadataValue('draft.demote'); } public function getTransactionTypeForConduit($xaction) { return 'plan-changes'; } public function getFieldValuesForConduit($object, $data) { return array(); } } diff --git a/src/applications/differential/xaction/DifferentialRevisionReclaimTransaction.php b/src/applications/differential/xaction/DifferentialRevisionReclaimTransaction.php index e023dda0a1..6f9d7b79bf 100644 --- a/src/applications/differential/xaction/DifferentialRevisionReclaimTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionReclaimTransaction.php @@ -1,96 +1,99 @@ isAbandoned(); } public function applyInternalEffects($object, $value) { if ($object->getShouldBroadcast()) { $new_status = DifferentialRevisionStatus::NEEDS_REVIEW; } else { $new_status = DifferentialRevisionStatus::DRAFT; } $object->setModernRevisionStatus($new_status); } 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()); } public function getTransactionTypeForConduit($xaction) { return 'reclaim'; } public function getFieldValuesForConduit($object, $data) { return array(); } } diff --git a/src/applications/differential/xaction/DifferentialRevisionRejectTransaction.php b/src/applications/differential/xaction/DifferentialRevisionRejectTransaction.php index 0001ce09ca..cd3e815d26 100644 --- a/src/applications/differential/xaction/DifferentialRevisionRejectTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionRejectTransaction.php @@ -1,110 +1,113 @@ getActor(); return $this->isViewerFullyRejected($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 ($object->isDraft() || !$object->getShouldBroadcast()) { throw new Exception( pht('You can not request changes to a draft revision.')); } if ($this->isViewerFullyRejected($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()); } public function getTransactionTypeForConduit($xaction) { return 'request-changes'; } public function getFieldValuesForConduit($object, $data) { return array(); } } diff --git a/src/applications/differential/xaction/DifferentialRevisionReopenTransaction.php b/src/applications/differential/xaction/DifferentialRevisionReopenTransaction.php index a2d25287bf..2e7a18fb22 100644 --- a/src/applications/differential/xaction/DifferentialRevisionReopenTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionReopenTransaction.php @@ -1,83 +1,86 @@ isClosed(); } public function applyInternalEffects($object, $value) { $status_review = DifferentialRevisionStatus::NEEDS_REVIEW; $object->setModernRevisionStatus($status_review); } protected function validateAction($object, PhabricatorUser $viewer) { if (!$object->isPublished()) { 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()); } public function getTransactionTypeForConduit($xaction) { return 'reopen'; } public function getFieldValuesForConduit($object, $data) { return array(); } } diff --git a/src/applications/differential/xaction/DifferentialRevisionRequestReviewTransaction.php b/src/applications/differential/xaction/DifferentialRevisionRequestReviewTransaction.php index 169e41dec5..026b57b55c 100644 --- a/src/applications/differential/xaction/DifferentialRevisionRequestReviewTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionRequestReviewTransaction.php @@ -1,108 +1,112 @@ isDraft()) { return pht('This revision will be submitted to reviewers for feedback.'); } else { return pht('This revision will be returned to reviewers for feedback.'); } } protected function getRevisionActionSubmitButtonText( - DifferentialRevision $revision) { + DifferentialRevision $revision, + PhabricatorUser $viewer) { // See PHI975. When the action stack will promote the revision out of // draft, change the button text from "Submit Quietly". if ($revision->isDraft()) { return pht('Publish Revision'); } return null; } public function getColor() { return 'sky'; } protected function getRevisionActionOrder() { return 200; } public function getActionName() { return pht('Requested Review'); } public function generateOldValue($object) { return $object->isNeedsReview(); } public function applyInternalEffects($object, $value) { $status_review = DifferentialRevisionStatus::NEEDS_REVIEW; $object ->setModernRevisionStatus($status_review) ->setShouldBroadcast(true); } protected function validateAction($object, PhabricatorUser $viewer) { if ($object->isNeedsReview()) { 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.')); } // When revisions automatically promote out of "Draft" after builds finish, // the viewer may be acting as the Harbormaster application. if (!$viewer->isOmnipotent()) { 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()); } public function getTransactionTypeForConduit($xaction) { return 'request-review'; } public function getFieldValuesForConduit($object, $data) { return array(); } } diff --git a/src/applications/differential/xaction/DifferentialRevisionResignTransaction.php b/src/applications/differential/xaction/DifferentialRevisionResignTransaction.php index a2b4fd4337..faab748cdd 100644 --- a/src/applications/differential/xaction/DifferentialRevisionResignTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionResignTransaction.php @@ -1,104 +1,107 @@ getActor(); $resigned = DifferentialReviewerStatus::STATUS_RESIGNED; return ($this->getViewerReviewerStatus($object, $actor) == $resigned); } 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.')); } $resigned = DifferentialReviewerStatus::STATUS_RESIGNED; if ($this->getViewerReviewerStatus($object, $viewer) == $resigned) { throw new Exception( pht( 'You can not resign from this revision because you have already '. 'resigned.')); } if (!$this->isViewerAnyAuthority($object, $viewer)) { throw new Exception( pht( 'You can not resign from this revision because you are not a '. 'reviewer, and do not have authority over any 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()); } public function getTransactionTypeForConduit($xaction) { return 'resign'; } public function getFieldValuesForConduit($object, $data) { return array(); } }