Generate file attachment transactions for explicit Remarkup attachments on common...
[phabricator.git] / src / applications / differential / xaction / DifferentialRevisionActionTransaction.php
blobb1f51e77e7615cd0ef9025353b95780900f9a59e
1 <?php
3 abstract class DifferentialRevisionActionTransaction
4 extends DifferentialRevisionTransactionType {
6 final public function getRevisionActionKey() {
7 return $this->getPhobjectClassConstant('ACTIONKEY', 32);
10 public function isActionAvailable($object, PhabricatorUser $viewer) {
11 try {
12 $this->validateAction($object, $viewer);
13 return true;
14 } catch (Exception $ex) {
15 return false;
19 abstract protected function validateAction($object, PhabricatorUser $viewer);
20 abstract protected function getRevisionActionLabel(
21 DifferentialRevision $revision,
22 PhabricatorUser $viewer);
24 protected function validateOptionValue($object, $actor, array $value) {
25 return null;
28 public function getCommandKeyword() {
29 return null;
32 public function getCommandAliases() {
33 return array();
36 public function getCommandSummary() {
37 return null;
40 protected function getRevisionActionOrder() {
41 return 1000;
44 public function getActionStrength() {
45 return 300;
48 public function getRevisionActionOrderVector() {
49 return id(new PhutilSortVector())
50 ->addInt($this->getRevisionActionOrder());
53 protected function getRevisionActionGroupKey() {
54 return DifferentialRevisionEditEngine::ACTIONGROUP_REVISION;
57 protected function getRevisionActionDescription(
58 DifferentialRevision $revision,
59 PhabricatorUser $viewer) {
60 return null;
63 protected function getRevisionActionSubmitButtonText(
64 DifferentialRevision $revision,
65 PhabricatorUser $viewer) {
66 return null;
69 protected function getRevisionActionMetadata(
70 DifferentialRevision $revision,
71 PhabricatorUser $viewer) {
72 return array();
75 public static function loadAllActions() {
76 return id(new PhutilClassMapQuery())
77 ->setAncestorClass(__CLASS__)
78 ->setUniqueMethod('getRevisionActionKey')
79 ->execute();
82 protected function isViewerRevisionAuthor(
83 DifferentialRevision $revision,
84 PhabricatorUser $viewer) {
86 if (!$viewer->getPHID()) {
87 return false;
90 return ($viewer->getPHID() === $revision->getAuthorPHID());
93 protected function getActionOptions(
94 PhabricatorUser $viewer,
95 DifferentialRevision $revision) {
96 return array(
97 array(),
98 array(),
102 public function newEditField(
103 DifferentialRevision $revision,
104 PhabricatorUser $viewer) {
106 // Actions in the "review" group, like "Accept Revision", do not require
107 // that the actor be able to edit the revision.
108 $group_review = DifferentialRevisionEditEngine::ACTIONGROUP_REVIEW;
109 $is_review = ($this->getRevisionActionGroupKey() == $group_review);
111 $field = id(new PhabricatorApplyEditField())
112 ->setKey($this->getRevisionActionKey())
113 ->setTransactionType($this->getTransactionTypeConstant())
114 ->setCanApplyWithoutEditCapability($is_review)
115 ->setValue(true);
117 if ($this->isActionAvailable($revision, $viewer)) {
118 $label = $this->getRevisionActionLabel($revision, $viewer);
119 if ($label !== null) {
120 $field->setCommentActionLabel($label);
122 $description = $this->getRevisionActionDescription($revision, $viewer);
123 $field->setActionDescription($description);
125 $group_key = $this->getRevisionActionGroupKey();
126 $field->setCommentActionGroupKey($group_key);
128 $button_text = $this->getRevisionActionSubmitButtonText(
129 $revision,
130 $viewer);
131 $field->setActionSubmitButtonText($button_text);
133 // Currently, every revision action conflicts with every other
134 // revision action: for example, you can not simultaneously Accept and
135 // Reject a revision.
137 // Under some configurations, some combinations of actions are sort of
138 // technically permissible. For example, you could reasonably Reject
139 // and Abandon a revision if "anyone can abandon anything" is enabled.
141 // It's not clear that these combinations are actually useful, so just
142 // keep things simple for now.
143 $field->setActionConflictKey('revision.action');
145 list($options, $value) = $this->getActionOptions($viewer, $revision);
147 // Show the options if the user can select on behalf of two or more
148 // reviewers, or can force-accept on behalf of one or more reviewers,
149 // or can accept on behalf of a reviewer other than themselves (see
150 // T12533).
151 $can_multi = (count($options) > 1);
152 $can_force = (count($value) < count($options));
153 $not_self = (head_key($options) != $viewer->getPHID());
155 if ($can_multi || $can_force || $not_self) {
156 $field->setOptions($options);
157 $field->setValue($value);
160 $metadata = $this->getRevisionActionMetadata($revision, $viewer);
161 foreach ($metadata as $metadata_key => $metadata_value) {
162 $field->setMetadataValue($metadata_key, $metadata_value);
167 return $field;
170 public function validateTransactions($object, array $xactions) {
171 $errors = array();
172 $actor = $this->getActor();
174 $action_exception = null;
175 foreach ($xactions as $xaction) {
176 // If this is a draft demotion action, let it skip all the normal
177 // validation. This is a little hacky and should perhaps move down
178 // into the actual action implementations, but currently we can not
179 // apply this rule in validateAction() because it doesn't operate on
180 // the actual transaction.
181 if ($xaction->getMetadataValue('draft.demote')) {
182 continue;
185 try {
186 $this->validateAction($object, $actor);
187 } catch (Exception $ex) {
188 $action_exception = $ex;
191 break;
194 foreach ($xactions as $xaction) {
195 if ($action_exception) {
196 $errors[] = $this->newInvalidError(
197 $action_exception->getMessage(),
198 $xaction);
199 continue;
202 $new = $xaction->getNewValue();
203 if (!is_array($new)) {
204 continue;
207 try {
208 $this->validateOptionValue($object, $actor, $new);
209 } catch (Exception $ex) {
210 $errors[] = $this->newInvalidError(
211 $ex->getMessage(),
212 $xaction);
216 return $errors;