Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/applications/maniphest/constants/ManiphestTaskStatus.php
12256 views
1
<?php
2
3
/**
4
* @task validate Configuration Validation
5
*/
6
final class ManiphestTaskStatus extends ManiphestConstants {
7
8
const STATUS_OPEN = 'open';
9
const STATUS_CLOSED_RESOLVED = 'resolved';
10
const STATUS_CLOSED_WONTFIX = 'wontfix';
11
const STATUS_CLOSED_INVALID = 'invalid';
12
const STATUS_CLOSED_DUPLICATE = 'duplicate';
13
const STATUS_CLOSED_SPITE = 'spite';
14
15
const SPECIAL_DEFAULT = 'default';
16
const SPECIAL_CLOSED = 'closed';
17
const SPECIAL_DUPLICATE = 'duplicate';
18
19
const LOCKED_COMMENTS = 'comments';
20
const LOCKED_EDITS = 'edits';
21
22
private static function getStatusConfig() {
23
return PhabricatorEnv::getEnvConfig('maniphest.statuses');
24
}
25
26
private static function getEnabledStatusMap() {
27
$spec = self::getStatusConfig();
28
29
$is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
30
foreach ($spec as $const => $status) {
31
if ($is_serious && !empty($status['silly'])) {
32
unset($spec[$const]);
33
continue;
34
}
35
}
36
37
return $spec;
38
}
39
40
public static function getTaskStatusMap() {
41
return ipull(self::getEnabledStatusMap(), 'name');
42
}
43
44
45
/**
46
* Get the statuses and their command keywords.
47
*
48
* @return map Statuses to lists of command keywords.
49
*/
50
public static function getTaskStatusKeywordsMap() {
51
$map = self::getEnabledStatusMap();
52
foreach ($map as $key => $spec) {
53
$words = idx($spec, 'keywords', array());
54
if (!is_array($words)) {
55
$words = array($words);
56
}
57
58
// For statuses, we include the status name because it's usually
59
// at least somewhat meaningful.
60
$words[] = $key;
61
62
foreach ($words as $word_key => $word) {
63
$words[$word_key] = phutil_utf8_strtolower($word);
64
}
65
66
$words = array_unique($words);
67
68
$map[$key] = $words;
69
}
70
71
return $map;
72
}
73
74
75
public static function getTaskStatusName($status) {
76
return self::getStatusAttribute($status, 'name', pht('Unknown Status'));
77
}
78
79
public static function getTaskStatusFullName($status) {
80
$name = self::getStatusAttribute($status, 'name.full');
81
if ($name !== null) {
82
return $name;
83
}
84
85
return self::getStatusAttribute($status, 'name', pht('Unknown Status'));
86
}
87
88
public static function renderFullDescription($status, $priority) {
89
if (self::isOpenStatus($status)) {
90
$name = pht('%s, %s', self::getTaskStatusFullName($status), $priority);
91
$color = 'grey';
92
$icon = 'fa-square-o';
93
} else {
94
$name = self::getTaskStatusFullName($status);
95
$color = 'indigo';
96
$icon = 'fa-check-square-o';
97
}
98
99
$tag = id(new PHUITagView())
100
->setName($name)
101
->setIcon($icon)
102
->setType(PHUITagView::TYPE_SHADE)
103
->setColor($color);
104
105
return $tag;
106
}
107
108
private static function getSpecialStatus($special) {
109
foreach (self::getStatusConfig() as $const => $status) {
110
if (idx($status, 'special') == $special) {
111
return $const;
112
}
113
}
114
return null;
115
}
116
117
public static function getDefaultStatus() {
118
return self::getSpecialStatus(self::SPECIAL_DEFAULT);
119
}
120
121
public static function getDefaultClosedStatus() {
122
return self::getSpecialStatus(self::SPECIAL_CLOSED);
123
}
124
125
public static function getDuplicateStatus() {
126
return self::getSpecialStatus(self::SPECIAL_DUPLICATE);
127
}
128
129
public static function getOpenStatusConstants() {
130
$result = array();
131
foreach (self::getEnabledStatusMap() as $const => $status) {
132
if (empty($status['closed'])) {
133
$result[] = $const;
134
}
135
}
136
return $result;
137
}
138
139
public static function getClosedStatusConstants() {
140
$all = array_keys(self::getTaskStatusMap());
141
$open = self::getOpenStatusConstants();
142
return array_diff($all, $open);
143
}
144
145
public static function isOpenStatus($status) {
146
foreach (self::getOpenStatusConstants() as $constant) {
147
if ($status == $constant) {
148
return true;
149
}
150
}
151
return false;
152
}
153
154
public static function isClaimStatus($status) {
155
return self::getStatusAttribute($status, 'claim', true);
156
}
157
158
public static function isClosedStatus($status) {
159
return !self::isOpenStatus($status);
160
}
161
162
public static function areCommentsLockedInStatus($status) {
163
return (bool)self::getStatusAttribute($status, 'locked', false);
164
}
165
166
public static function areEditsLockedInStatus($status) {
167
$locked = self::getStatusAttribute($status, 'locked');
168
return ($locked === self::LOCKED_EDITS);
169
}
170
171
public static function isMFAStatus($status) {
172
return self::getStatusAttribute($status, 'mfa', false);
173
}
174
175
public static function getStatusActionName($status) {
176
return self::getStatusAttribute($status, 'name.action');
177
}
178
179
public static function getStatusColor($status) {
180
return self::getStatusAttribute($status, 'transaction.color');
181
}
182
183
public static function isDisabledStatus($status) {
184
return self::getStatusAttribute($status, 'disabled');
185
}
186
187
public static function getStatusIcon($status) {
188
$icon = self::getStatusAttribute($status, 'transaction.icon');
189
if ($icon) {
190
return $icon;
191
}
192
193
if (self::isOpenStatus($status)) {
194
return 'fa-exclamation-circle';
195
} else {
196
return 'fa-check-square-o';
197
}
198
}
199
200
public static function getStatusPrefixMap() {
201
$map = array();
202
foreach (self::getEnabledStatusMap() as $const => $status) {
203
foreach (idx($status, 'prefixes', array()) as $prefix) {
204
$map[$prefix] = $const;
205
}
206
}
207
208
$map += array(
209
'ref' => null,
210
'refs' => null,
211
'references' => null,
212
'cf.' => null,
213
);
214
215
return $map;
216
}
217
218
public static function getStatusSuffixMap() {
219
$map = array();
220
foreach (self::getEnabledStatusMap() as $const => $status) {
221
foreach (idx($status, 'suffixes', array()) as $prefix) {
222
$map[$prefix] = $const;
223
}
224
}
225
return $map;
226
}
227
228
private static function getStatusAttribute($status, $key, $default = null) {
229
$config = self::getStatusConfig();
230
231
$spec = idx($config, $status);
232
if ($spec) {
233
return idx($spec, $key, $default);
234
}
235
236
return $default;
237
}
238
239
240
/* -( Configuration Validation )------------------------------------------- */
241
242
243
/**
244
* @task validate
245
*/
246
public static function isValidStatusConstant($constant) {
247
if (!strlen($constant) || strlen($constant) > 64) {
248
return false;
249
}
250
251
// Alphanumeric, but not exclusively numeric
252
if (!preg_match('/^(?![0-9]*$)[a-zA-Z0-9]+$/', $constant)) {
253
return false;
254
}
255
return true;
256
}
257
258
/**
259
* @task validate
260
*/
261
public static function validateConfiguration(array $config) {
262
foreach ($config as $key => $value) {
263
if (!self::isValidStatusConstant($key)) {
264
throw new Exception(
265
pht(
266
'Key "%s" is not a valid status constant. Status constants '.
267
'must be 1-64 alphanumeric characters and cannot be exclusively '.
268
'digits. For example, "%s" or "%s" are reasonable choices.',
269
$key,
270
'open',
271
'closed'));
272
}
273
if (!is_array($value)) {
274
throw new Exception(
275
pht(
276
'Value for key "%s" should be a dictionary.',
277
$key));
278
}
279
280
PhutilTypeSpec::checkMap(
281
$value,
282
array(
283
'name' => 'string',
284
'name.full' => 'optional string',
285
'name.action' => 'optional string',
286
'closed' => 'optional bool',
287
'special' => 'optional string',
288
'transaction.icon' => 'optional string',
289
'transaction.color' => 'optional string',
290
'silly' => 'optional bool',
291
'prefixes' => 'optional list<string>',
292
'suffixes' => 'optional list<string>',
293
'keywords' => 'optional list<string>',
294
'disabled' => 'optional bool',
295
'claim' => 'optional bool',
296
'locked' => 'optional bool|string',
297
'mfa' => 'optional bool',
298
));
299
}
300
301
// Supported values are "comments" or "edits". For backward compatibility,
302
// "true" is an alias of "comments".
303
304
foreach ($config as $key => $value) {
305
$locked = idx($value, 'locked', false);
306
if ($locked === true || $locked === false) {
307
continue;
308
}
309
310
if ($locked === self::LOCKED_EDITS ||
311
$locked === self::LOCKED_COMMENTS) {
312
continue;
313
}
314
315
throw new Exception(
316
pht(
317
'Task status ("%s") has unrecognized value for "locked" '.
318
'configuration ("%s"). Supported values are: "%s", "%s".',
319
$key,
320
$locked,
321
self::LOCKED_COMMENTS,
322
self::LOCKED_EDITS));
323
}
324
325
$special_map = array();
326
foreach ($config as $key => $value) {
327
$special = idx($value, 'special');
328
if (!$special) {
329
continue;
330
}
331
332
if (isset($special_map[$special])) {
333
throw new Exception(
334
pht(
335
'Configuration has two statuses both marked with the special '.
336
'attribute "%s" ("%s" and "%s"). There should be only one.',
337
$special,
338
$special_map[$special],
339
$key));
340
}
341
342
switch ($special) {
343
case self::SPECIAL_DEFAULT:
344
if (!empty($value['closed'])) {
345
throw new Exception(
346
pht(
347
'Status "%s" is marked as default, but it is a closed '.
348
'status. The default status should be an open status.',
349
$key));
350
}
351
break;
352
case self::SPECIAL_CLOSED:
353
if (empty($value['closed'])) {
354
throw new Exception(
355
pht(
356
'Status "%s" is marked as the default status for closing '.
357
'tasks, but is not a closed status. It should be a closed '.
358
'status.',
359
$key));
360
}
361
break;
362
case self::SPECIAL_DUPLICATE:
363
if (empty($value['closed'])) {
364
throw new Exception(
365
pht(
366
'Status "%s" is marked as the status for closing tasks as '.
367
'duplicates, but it is not a closed status. It should '.
368
'be a closed status.',
369
$key));
370
}
371
break;
372
}
373
374
$special_map[$special] = $key;
375
}
376
377
// NOTE: We're not explicitly validating that we have at least one open
378
// and one closed status, because the DEFAULT and CLOSED specials imply
379
// that to be true. If those change in the future, that might become a
380
// reasonable thing to validate.
381
382
$required = array(
383
self::SPECIAL_DEFAULT,
384
self::SPECIAL_CLOSED,
385
self::SPECIAL_DUPLICATE,
386
);
387
388
foreach ($required as $required_special) {
389
if (!isset($special_map[$required_special])) {
390
throw new Exception(
391
pht(
392
'Configuration defines no task status with special attribute '.
393
'"%s", but you must specify a status which fills this special '.
394
'role.',
395
$required_special));
396
}
397
}
398
}
399
400
}
401
402