@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

at recaptime-dev/main 400 lines 11 kB view raw
1<?php 2 3/** 4 * @task validate Configuration Validation 5 */ 6final 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}