Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/applications/differential/xaction/DifferentialRevisionReviewTransaction.php
12256 views
1
<?php
2
3
abstract class DifferentialRevisionReviewTransaction
4
extends DifferentialRevisionActionTransaction {
5
6
protected function getRevisionActionGroupKey() {
7
return DifferentialRevisionEditEngine::ACTIONGROUP_REVIEW;
8
}
9
10
public function generateNewValue($object, $value) {
11
if (!is_array($value)) {
12
return true;
13
}
14
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);
19
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);
29
30
sort($default);
31
sort($value);
32
33
if ($default === $value) {
34
return true;
35
}
36
37
return $value;
38
}
39
40
protected function isViewerAnyReviewer(
41
DifferentialRevision $revision,
42
PhabricatorUser $viewer) {
43
return ($this->getViewerReviewerStatus($revision, $viewer) !== null);
44
}
45
46
protected function isViewerAnyAuthority(
47
DifferentialRevision $revision,
48
PhabricatorUser $viewer) {
49
50
$reviewers = $revision->getReviewers();
51
foreach ($revision->getReviewers() as $reviewer) {
52
if ($reviewer->hasAuthority($viewer)) {
53
return true;
54
}
55
}
56
57
return false;
58
}
59
60
protected function isViewerFullyAccepted(
61
DifferentialRevision $revision,
62
PhabricatorUser $viewer) {
63
return $this->isViewerReviewerStatusFully(
64
$revision,
65
$viewer,
66
DifferentialReviewerStatus::STATUS_ACCEPTED);
67
}
68
69
protected function isViewerFullyRejected(
70
DifferentialRevision $revision,
71
PhabricatorUser $viewer) {
72
return $this->isViewerReviewerStatusFully(
73
$revision,
74
$viewer,
75
DifferentialReviewerStatus::STATUS_REJECTED);
76
}
77
78
protected function getViewerReviewerStatus(
79
DifferentialRevision $revision,
80
PhabricatorUser $viewer) {
81
82
if (!$viewer->getPHID()) {
83
return null;
84
}
85
86
foreach ($revision->getReviewers() as $reviewer) {
87
if ($reviewer->getReviewerPHID() != $viewer->getPHID()) {
88
continue;
89
}
90
91
return $reviewer->getReviewerStatus();
92
}
93
94
return null;
95
}
96
97
private function isViewerReviewerStatusFully(
98
DifferentialRevision $revision,
99
PhabricatorUser $viewer,
100
$require_status) {
101
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;
108
}
109
110
$active_phid = $this->getActiveDiffPHID($revision);
111
112
$status_accepted = DifferentialReviewerStatus::STATUS_ACCEPTED;
113
$status_rejected = DifferentialReviewerStatus::STATUS_REJECTED;
114
115
$is_accepted = ($require_status == $status_accepted);
116
$is_rejected = ($require_status == $status_rejected);
117
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;
123
124
if ($is_accepted) {
125
if ($revision->canReviewerForceAccept($viewer, $reviewer)) {
126
$can_force = true;
127
}
128
}
129
130
if (!$can_force) {
131
continue;
132
}
133
}
134
135
$status = $reviewer->getReviewerStatus();
136
if ($status != $require_status) {
137
return false;
138
}
139
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;
144
}
145
}
146
147
if ($is_rejected) {
148
if (!$reviewer->isRejected($active_phid)) {
149
return false;
150
}
151
}
152
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;
157
}
158
}
159
160
return true;
161
}
162
163
protected function applyReviewerEffect(
164
DifferentialRevision $revision,
165
PhabricatorUser $viewer,
166
$value,
167
$status,
168
array $reviewer_options = array()) {
169
170
PhutilTypeSpec::checkMap(
171
$reviewer_options,
172
array(
173
'sticky' => 'optional bool',
174
));
175
176
$map = array();
177
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);
183
184
if ($with_authority) {
185
foreach ($revision->getReviewers() as $reviewer) {
186
if (!$reviewer->hasAuthority($viewer)) {
187
if (!$with_force) {
188
continue;
189
}
190
191
if (!$revision->canReviewerForceAccept($viewer, $reviewer)) {
192
continue;
193
}
194
}
195
196
$map[$reviewer->getReviewerPHID()] = $status;
197
}
198
}
199
200
// In all cases, you affect yourself.
201
$map[$viewer->getPHID()] = $status;
202
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;
210
}
211
}
212
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);
218
}
219
220
// Now, do the new write.
221
222
if ($map) {
223
$diff = $this->getEditor()->getActiveDiff($revision);
224
if ($diff) {
225
$diff_phid = $diff->getPHID();
226
} else {
227
$diff_phid = null;
228
}
229
230
$table = new DifferentialReviewer();
231
$src_phid = $revision->getPHID();
232
233
$reviewers = $table->loadAllWhere(
234
'revisionPHID = %s AND reviewerPHID IN (%Ls)',
235
$src_phid,
236
array_keys($map));
237
$reviewers = mpull($reviewers, null, 'getReviewerPHID');
238
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);
245
}
246
247
$old_status = $reviewer->getReviewerStatus();
248
$reviewer->setReviewerStatus($status);
249
250
if ($diff_phid) {
251
$reviewer->setLastActionDiffPHID($diff_phid);
252
}
253
254
if ($old_status !== $status) {
255
$reviewer->setLastActorPHID($this->getActingAsPHID());
256
}
257
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);
262
263
$reviewer->setOption('sticky', idx($reviewer_options, 'sticky'));
264
265
try {
266
$reviewer->save();
267
} catch (AphrontDuplicateKeyQueryException $ex) {
268
// At least for now, just ignore it if we lost a race.
269
}
270
}
271
}
272
273
}
274
275
}
276
277