Remove product literal strings in "pht()", part 18
[phabricator.git] / src / applications / differential / xaction / DifferentialRevisionReviewTransaction.php
blob8aec75d2fb3fda246e6cf058192ad47e55559d06
1 <?php
3 abstract class DifferentialRevisionReviewTransaction
4 extends DifferentialRevisionActionTransaction {
6 protected function getRevisionActionGroupKey() {
7 return DifferentialRevisionEditEngine::ACTIONGROUP_REVIEW;
10 public function generateNewValue($object, $value) {
11 if (!is_array($value)) {
12 return true;
15 // If the list of options is the same as the default list, just treat this
16 // as a "take the default action" transaction.
17 $viewer = $this->getActor();
18 list($options, $default) = $this->getActionOptions($viewer, $object);
20 // Remove reviewers which aren't actionable. In the case of "Accept", we
21 // may allow the transaction to proceed with some reviewers who have
22 // already accepted, to avoid race conditions where two reviewers fill
23 // out the form at the same time and accept on behalf of the same package.
24 // It's okay for these reviewers to survive validation, but they should
25 // not survive beyond this point.
26 $value = array_fuse($value);
27 $value = array_intersect($value, array_keys($options));
28 $value = array_values($value);
30 sort($default);
31 sort($value);
33 if ($default === $value) {
34 return true;
37 return $value;
40 protected function isViewerAnyReviewer(
41 DifferentialRevision $revision,
42 PhabricatorUser $viewer) {
43 return ($this->getViewerReviewerStatus($revision, $viewer) !== null);
46 protected function isViewerAnyAuthority(
47 DifferentialRevision $revision,
48 PhabricatorUser $viewer) {
50 $reviewers = $revision->getReviewers();
51 foreach ($revision->getReviewers() as $reviewer) {
52 if ($reviewer->hasAuthority($viewer)) {
53 return true;
57 return false;
60 protected function isViewerFullyAccepted(
61 DifferentialRevision $revision,
62 PhabricatorUser $viewer) {
63 return $this->isViewerReviewerStatusFully(
64 $revision,
65 $viewer,
66 DifferentialReviewerStatus::STATUS_ACCEPTED);
69 protected function isViewerFullyRejected(
70 DifferentialRevision $revision,
71 PhabricatorUser $viewer) {
72 return $this->isViewerReviewerStatusFully(
73 $revision,
74 $viewer,
75 DifferentialReviewerStatus::STATUS_REJECTED);
78 protected function getViewerReviewerStatus(
79 DifferentialRevision $revision,
80 PhabricatorUser $viewer) {
82 if (!$viewer->getPHID()) {
83 return null;
86 foreach ($revision->getReviewers() as $reviewer) {
87 if ($reviewer->getReviewerPHID() != $viewer->getPHID()) {
88 continue;
91 return $reviewer->getReviewerStatus();
94 return null;
97 private function isViewerReviewerStatusFully(
98 DifferentialRevision $revision,
99 PhabricatorUser $viewer,
100 $require_status) {
102 // If the user themselves is not a reviewer, the reviews they have
103 // authority over can not all be in any set of states since their own
104 // personal review has no state.
105 $status = $this->getViewerReviewerStatus($revision, $viewer);
106 if ($status === null) {
107 return false;
110 $active_phid = $this->getActiveDiffPHID($revision);
112 $status_accepted = DifferentialReviewerStatus::STATUS_ACCEPTED;
113 $status_rejected = DifferentialReviewerStatus::STATUS_REJECTED;
115 $is_accepted = ($require_status == $status_accepted);
116 $is_rejected = ($require_status == $status_rejected);
118 // Otherwise, check that all reviews they have authority over are in
119 // the desired set of states.
120 foreach ($revision->getReviewers() as $reviewer) {
121 if (!$reviewer->hasAuthority($viewer)) {
122 $can_force = false;
124 if ($is_accepted) {
125 if ($revision->canReviewerForceAccept($viewer, $reviewer)) {
126 $can_force = true;
130 if (!$can_force) {
131 continue;
135 $status = $reviewer->getReviewerStatus();
136 if ($status != $require_status) {
137 return false;
140 // Here, we're primarily testing if we can remove a void on the review.
141 if ($is_accepted) {
142 if (!$reviewer->isAccepted($active_phid)) {
143 return false;
147 if ($is_rejected) {
148 if (!$reviewer->isRejected($active_phid)) {
149 return false;
153 // This is a broader check to see if we can update the diff where the
154 // last action occurred.
155 if ($reviewer->getLastActionDiffPHID() != $active_phid) {
156 return false;
160 return true;
163 protected function applyReviewerEffect(
164 DifferentialRevision $revision,
165 PhabricatorUser $viewer,
166 $value,
167 $status,
168 array $reviewer_options = array()) {
170 PhutilTypeSpec::checkMap(
171 $reviewer_options,
172 array(
173 'sticky' => 'optional bool',
176 $map = array();
178 // When you accept or reject, you may accept or reject on behalf of all
179 // reviewers you have authority for. When you resign, you only affect
180 // yourself.
181 $with_authority = ($status != DifferentialReviewerStatus::STATUS_RESIGNED);
182 $with_force = ($status == DifferentialReviewerStatus::STATUS_ACCEPTED);
184 if ($with_authority) {
185 foreach ($revision->getReviewers() as $reviewer) {
186 if (!$reviewer->hasAuthority($viewer)) {
187 if (!$with_force) {
188 continue;
191 if (!$revision->canReviewerForceAccept($viewer, $reviewer)) {
192 continue;
196 $map[$reviewer->getReviewerPHID()] = $status;
200 // In all cases, you affect yourself.
201 $map[$viewer->getPHID()] = $status;
203 // If we're applying an "accept the defaults" transaction, and this
204 // transaction type uses checkboxes, replace the value with the list of
205 // defaults.
206 if (!is_array($value)) {
207 list($options, $default) = $this->getActionOptions($viewer, $revision);
208 if ($options) {
209 $value = $default;
213 // If we have a specific list of reviewers to act on, usually because the
214 // user has submitted a specific list of reviewers to act as by
215 // unchecking some checkboxes under "Accept", only affect those reviewers.
216 if (is_array($value)) {
217 $map = array_select_keys($map, $value);
220 // Now, do the new write.
222 if ($map) {
223 $diff = $this->getEditor()->getActiveDiff($revision);
224 if ($diff) {
225 $diff_phid = $diff->getPHID();
226 } else {
227 $diff_phid = null;
230 $table = new DifferentialReviewer();
231 $src_phid = $revision->getPHID();
233 $reviewers = $table->loadAllWhere(
234 'revisionPHID = %s AND reviewerPHID IN (%Ls)',
235 $src_phid,
236 array_keys($map));
237 $reviewers = mpull($reviewers, null, 'getReviewerPHID');
239 foreach (array_keys($map) as $dst_phid) {
240 $reviewer = idx($reviewers, $dst_phid);
241 if (!$reviewer) {
242 $reviewer = id(new DifferentialReviewer())
243 ->setRevisionPHID($src_phid)
244 ->setReviewerPHID($dst_phid);
247 $old_status = $reviewer->getReviewerStatus();
248 $reviewer->setReviewerStatus($status);
250 if ($diff_phid) {
251 $reviewer->setLastActionDiffPHID($diff_phid);
254 if ($old_status !== $status) {
255 $reviewer->setLastActorPHID($this->getActingAsPHID());
258 // Clear any outstanding void on this reviewer. A void may be placed
259 // by the author using "Request Review" when a reviewer has already
260 // accepted.
261 $reviewer->setVoidedPHID(null);
263 $reviewer->setOption('sticky', idx($reviewer_options, 'sticky'));
265 try {
266 $reviewer->save();
267 } catch (AphrontDuplicateKeyQueryException $ex) {
268 // At least for now, just ignore it if we lost a race.