Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/applications/differential/xaction/DifferentialRevisionReviewersTransaction.php
12256 views
1
<?php
2
3
final class DifferentialRevisionReviewersTransaction
4
extends DifferentialRevisionTransactionType {
5
6
const TRANSACTIONTYPE = 'differential.revision.reviewers';
7
const EDITKEY = 'reviewers';
8
9
public function generateOldValue($object) {
10
$reviewers = $object->getReviewers();
11
$reviewers = mpull($reviewers, 'getReviewerStatus', 'getReviewerPHID');
12
return $reviewers;
13
}
14
15
public function generateNewValue($object, $value) {
16
$actor = $this->getActor();
17
18
$datasource = id(new DifferentialBlockingReviewerDatasource())
19
->setViewer($actor);
20
21
$reviewers = $this->generateOldValue($object);
22
$old_reviewers = $reviewers;
23
24
// First, remove any reviewers we're getting rid of.
25
$rem = idx($value, '-', array());
26
$rem = $datasource->evaluateTokens($rem);
27
foreach ($rem as $spec) {
28
if (!is_array($spec)) {
29
$phid = $spec;
30
} else {
31
$phid = $spec['phid'];
32
}
33
34
unset($reviewers[$phid]);
35
}
36
37
$add = idx($value, '+', array());
38
$add = $datasource->evaluateTokens($add);
39
$add_map = array();
40
foreach ($add as $spec) {
41
if (!is_array($spec)) {
42
$phid = $spec;
43
$status = DifferentialReviewerStatus::STATUS_ADDED;
44
} else {
45
$phid = $spec['phid'];
46
$status = $spec['type'];
47
}
48
49
$add_map[$phid] = $status;
50
}
51
52
$set = idx($value, '=', null);
53
if ($set !== null) {
54
$set = $datasource->evaluateTokens($set);
55
foreach ($set as $spec) {
56
if (!is_array($spec)) {
57
$phid = $spec;
58
$status = DifferentialReviewerStatus::STATUS_ADDED;
59
} else {
60
$phid = $spec['phid'];
61
$status = $spec['type'];
62
}
63
64
$add_map[$phid] = $status;
65
}
66
67
// We treat setting reviewers as though they were being added to an
68
// empty list, so we can share more code between pathways.
69
$reviewers = array();
70
}
71
72
$status_blocking = DifferentialReviewerStatus::STATUS_BLOCKING;
73
foreach ($add_map as $phid => $new_status) {
74
$old_status = idx($old_reviewers, $phid);
75
76
// If we have an old status and this didn't make the reviewer blocking
77
// or nonblocking, just retain the old status. This makes sure we don't
78
// throw away rejects, accepts, etc.
79
if ($old_status) {
80
$was_blocking = ($old_status == $status_blocking);
81
$now_blocking = ($new_status == $status_blocking);
82
83
$is_block = ($now_blocking && !$was_blocking);
84
$is_unblock = (!$now_blocking && $was_blocking);
85
86
if (!$is_block && !$is_unblock) {
87
$reviewers[$phid] = $old_status;
88
continue;
89
}
90
}
91
92
$reviewers[$phid] = $new_status;
93
}
94
95
return $reviewers;
96
}
97
98
public function getTransactionHasEffect($object, $old, $new) {
99
// At least for now, we ignore transactions which ONLY reorder reviewers
100
// without making any actual changes.
101
ksort($old);
102
ksort($new);
103
return ($old !== $new);
104
}
105
106
public function applyExternalEffects($object, $value) {
107
$src_phid = $object->getPHID();
108
109
$old = $this->generateOldValue($object);
110
$new = $value;
111
$rem = array_diff_key($old, $new);
112
113
$table = new DifferentialReviewer();
114
$table_name = $table->getTableName();
115
$conn = $table->establishConnection('w');
116
117
if ($rem) {
118
queryfx(
119
$conn,
120
'DELETE FROM %T WHERE revisionPHID = %s AND reviewerPHID IN (%Ls)',
121
$table_name,
122
$src_phid,
123
array_keys($rem));
124
}
125
126
if ($new) {
127
$reviewers = $table->loadAllWhere(
128
'revisionPHID = %s AND reviewerPHID IN (%Ls)',
129
$src_phid,
130
array_keys($new));
131
$reviewers = mpull($reviewers, null, 'getReviewerPHID');
132
133
foreach ($new as $dst_phid => $status) {
134
$old_status = idx($old, $dst_phid);
135
if ($old_status === $status) {
136
continue;
137
}
138
139
$reviewer = idx($reviewers, $dst_phid);
140
if (!$reviewer) {
141
$reviewer = id(new DifferentialReviewer())
142
->setRevisionPHID($src_phid)
143
->setReviewerPHID($dst_phid);
144
}
145
146
$reviewer->setReviewerStatus($status);
147
148
try {
149
$reviewer->save();
150
} catch (AphrontDuplicateKeyQueryException $ex) {
151
// At least for now, just ignore it if we lost a race.
152
}
153
}
154
}
155
}
156
157
public function getTitle() {
158
return $this->renderReviewerEditTitle(false);
159
}
160
161
public function getTitleForFeed() {
162
return $this->renderReviewerEditTitle(true);
163
}
164
165
private function renderReviewerEditTitle($is_feed) {
166
$old = $this->getOldValue();
167
$new = $this->getNewValue();
168
169
$rem = array_diff_key($old, $new);
170
$add = array_diff_key($new, $old);
171
$rem_phids = array_keys($rem);
172
$add_phids = array_keys($add);
173
$total_count = count($rem) + count($add);
174
175
$parts = array();
176
177
if ($rem && $add) {
178
if ($is_feed) {
179
$parts[] = pht(
180
'%s edited %s reviewer(s) for %s, added %s: %s; removed %s: %s.',
181
$this->renderAuthor(),
182
new PhutilNumber($total_count),
183
$this->renderObject(),
184
phutil_count($add_phids),
185
$this->renderHandleList($add_phids),
186
phutil_count($rem_phids),
187
$this->renderHandleList($rem_phids));
188
} else {
189
$parts[] = pht(
190
'%s edited %s reviewer(s), added %s: %s; removed %s: %s.',
191
$this->renderAuthor(),
192
new PhutilNumber($total_count),
193
phutil_count($add_phids),
194
$this->renderHandleList($add_phids),
195
phutil_count($rem_phids),
196
$this->renderHandleList($rem_phids));
197
}
198
} else if ($add) {
199
if ($is_feed) {
200
$parts[] = pht(
201
'%s added %s reviewer(s) for %s: %s.',
202
$this->renderAuthor(),
203
phutil_count($add_phids),
204
$this->renderObject(),
205
$this->renderHandleList($add_phids));
206
} else {
207
$parts[] = pht(
208
'%s added %s reviewer(s): %s.',
209
$this->renderAuthor(),
210
phutil_count($add_phids),
211
$this->renderHandleList($add_phids));
212
}
213
} else if ($rem) {
214
if ($is_feed) {
215
$parts[] = pht(
216
'%s removed %s reviewer(s) for %s: %s.',
217
$this->renderAuthor(),
218
phutil_count($rem_phids),
219
$this->renderObject(),
220
$this->renderHandleList($rem_phids));
221
} else {
222
$parts[] = pht(
223
'%s removed %s reviewer(s): %s.',
224
$this->renderAuthor(),
225
phutil_count($rem_phids),
226
$this->renderHandleList($rem_phids));
227
}
228
}
229
230
$status_blocking = DifferentialReviewerStatus::STATUS_BLOCKING;
231
$blocks = array();
232
$unblocks = array();
233
foreach ($new as $phid => $new_status) {
234
$old_status = idx($old, $phid);
235
if (!$old_status) {
236
continue;
237
}
238
239
$was_blocking = ($old_status == $status_blocking);
240
$now_blocking = ($new_status == $status_blocking);
241
242
$is_block = ($now_blocking && !$was_blocking);
243
$is_unblock = (!$now_blocking && $was_blocking);
244
245
if ($is_block) {
246
$blocks[] = $phid;
247
}
248
if ($is_unblock) {
249
$unblocks[] = $phid;
250
}
251
}
252
253
$total_count = count($blocks) + count($unblocks);
254
255
if ($blocks && $unblocks) {
256
if ($is_feed) {
257
$parts[] = pht(
258
'%s changed %s blocking reviewer(s) for %s, added %s: %s; removed '.
259
'%s: %s.',
260
$this->renderAuthor(),
261
new PhutilNumber($total_count),
262
$this->renderObject(),
263
phutil_count($blocks),
264
$this->renderHandleList($blocks),
265
phutil_count($unblocks),
266
$this->renderHandleList($unblocks));
267
} else {
268
$parts[] = pht(
269
'%s changed %s blocking reviewer(s), added %s: %s; removed %s: %s.',
270
$this->renderAuthor(),
271
new PhutilNumber($total_count),
272
phutil_count($blocks),
273
$this->renderHandleList($blocks),
274
phutil_count($unblocks),
275
$this->renderHandleList($unblocks));
276
}
277
} else if ($blocks) {
278
if ($is_feed) {
279
$parts[] = pht(
280
'%s added %s blocking reviewer(s) for %s: %s.',
281
$this->renderAuthor(),
282
phutil_count($blocks),
283
$this->renderObject(),
284
$this->renderHandleList($blocks));
285
} else {
286
$parts[] = pht(
287
'%s added %s blocking reviewer(s): %s.',
288
$this->renderAuthor(),
289
phutil_count($blocks),
290
$this->renderHandleList($blocks));
291
}
292
} else if ($unblocks) {
293
if ($is_feed) {
294
$parts[] = pht(
295
'%s removed %s blocking reviewer(s) for %s: %s.',
296
$this->renderAuthor(),
297
phutil_count($unblocks),
298
$this->renderObject(),
299
$this->renderHandleList($unblocks));
300
} else {
301
$parts[] = pht(
302
'%s removed %s blocking reviewer(s): %s.',
303
$this->renderAuthor(),
304
phutil_count($unblocks),
305
$this->renderHandleList($unblocks));
306
}
307
}
308
309
if ($this->isTextMode()) {
310
return implode(' ', $parts);
311
} else {
312
return phutil_implode_html(' ', $parts);
313
}
314
}
315
316
public function validateTransactions($object, array $xactions) {
317
$actor = $this->getActor();
318
$errors = array();
319
320
if (!$xactions) {
321
// If we aren't applying new reviewer transactions, just bail. We need
322
// reviewers to be attached to the revision continue validation, and
323
// they won't always be (for example, when mentioning a revision).
324
return $errors;
325
}
326
327
$author_phid = $object->getAuthorPHID();
328
$config_self_accept_key = 'differential.allow-self-accept';
329
$allow_self_accept = PhabricatorEnv::getEnvConfig($config_self_accept_key);
330
331
$old = $this->generateOldValue($object);
332
foreach ($xactions as $xaction) {
333
$new = $this->generateNewValue($object, $xaction->getNewValue());
334
335
$add = array_diff_key($new, $old);
336
if (!$add) {
337
continue;
338
}
339
340
$objects = id(new PhabricatorObjectQuery())
341
->setViewer($actor)
342
->withPHIDs(array_keys($add))
343
->execute();
344
$objects = mpull($objects, null, 'getPHID');
345
346
foreach ($add as $phid => $status) {
347
if (!isset($objects[$phid])) {
348
$errors[] = $this->newInvalidError(
349
pht(
350
'Reviewer "%s" is not a valid object.',
351
$phid),
352
$xaction);
353
continue;
354
}
355
356
switch (phid_get_type($phid)) {
357
case PhabricatorPeopleUserPHIDType::TYPECONST:
358
case PhabricatorOwnersPackagePHIDType::TYPECONST:
359
case PhabricatorProjectProjectPHIDType::TYPECONST:
360
break;
361
default:
362
$errors[] = $this->newInvalidError(
363
pht(
364
'Reviewer "%s" must be a user, a package, or a project.',
365
$phid),
366
$xaction);
367
continue 2;
368
}
369
370
// NOTE: This weird behavior around commandeering is a bit unorthodox,
371
// but this restriction is an unusual one.
372
373
$is_self = ($phid === $author_phid);
374
if ($is_self && !$allow_self_accept) {
375
if (!$xaction->getIsCommandeerSideEffect()) {
376
$errors[] = $this->newInvalidError(
377
pht('The author of a revision can not be a reviewer.'),
378
$xaction);
379
continue;
380
}
381
}
382
}
383
}
384
385
return $errors;
386
}
387
388
389
public function getTransactionTypeForConduit($xaction) {
390
return 'reviewers';
391
}
392
393
public function getFieldValuesForConduit($xaction, $data) {
394
$old_value = $xaction->getOldValue();
395
$new_value = $xaction->getNewValue();
396
397
$status_blocking = DifferentialReviewerStatus::STATUS_BLOCKING;
398
399
$add_phids = array_diff_key($new_value, $old_value);
400
foreach ($add_phids as $add_phid => $value) {
401
$add_phids[$add_phid] = array(
402
'operation' => 'add',
403
'phid' => $add_phid,
404
'oldStatus' => null,
405
'newStatus' => $value,
406
'isBlocking' => ($value === $status_blocking),
407
);
408
}
409
410
$rem_phids = array_diff_key($old_value, $new_value);
411
foreach ($rem_phids as $rem_phid => $value) {
412
$rem_phids[$rem_phid] = array(
413
'operation' => 'remove',
414
'phid' => $rem_phid,
415
'oldStatus' => $value,
416
'newStatus' => null,
417
'isBlocking' => false,
418
);
419
}
420
421
$mod_phids = array_intersect_key($old_value, $new_value);
422
foreach ($mod_phids as $mod_phid => $ignored) {
423
$old = $old_value[$mod_phid];
424
$new = $new_value[$mod_phid];
425
426
if ($old === $new) {
427
unset($mod_phids[$mod_phid]);
428
continue;
429
}
430
431
$mod_phids[$mod_phid] = array(
432
'operation' => 'update',
433
'phid' => $mod_phid,
434
'oldStatus' => $old,
435
'newStatus' => $new,
436
'isBlocking' => ($new === $status_blocking),
437
);
438
}
439
440
$all_ops = $add_phids + $rem_phids + $mod_phids;
441
$all_ops = array_select_keys($all_ops, $new_value) + $all_ops;
442
$all_ops = array_values($all_ops);
443
444
return array(
445
'operations' => $all_ops,
446
);
447
}
448
}
449
450