Path: blob/master/src/applications/maniphest/constants/ManiphestTaskStatus.php
12256 views
<?php12/**3* @task validate Configuration Validation4*/5final class ManiphestTaskStatus extends ManiphestConstants {67const STATUS_OPEN = 'open';8const STATUS_CLOSED_RESOLVED = 'resolved';9const STATUS_CLOSED_WONTFIX = 'wontfix';10const STATUS_CLOSED_INVALID = 'invalid';11const STATUS_CLOSED_DUPLICATE = 'duplicate';12const STATUS_CLOSED_SPITE = 'spite';1314const SPECIAL_DEFAULT = 'default';15const SPECIAL_CLOSED = 'closed';16const SPECIAL_DUPLICATE = 'duplicate';1718const LOCKED_COMMENTS = 'comments';19const LOCKED_EDITS = 'edits';2021private static function getStatusConfig() {22return PhabricatorEnv::getEnvConfig('maniphest.statuses');23}2425private static function getEnabledStatusMap() {26$spec = self::getStatusConfig();2728$is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');29foreach ($spec as $const => $status) {30if ($is_serious && !empty($status['silly'])) {31unset($spec[$const]);32continue;33}34}3536return $spec;37}3839public static function getTaskStatusMap() {40return ipull(self::getEnabledStatusMap(), 'name');41}424344/**45* Get the statuses and their command keywords.46*47* @return map Statuses to lists of command keywords.48*/49public static function getTaskStatusKeywordsMap() {50$map = self::getEnabledStatusMap();51foreach ($map as $key => $spec) {52$words = idx($spec, 'keywords', array());53if (!is_array($words)) {54$words = array($words);55}5657// For statuses, we include the status name because it's usually58// at least somewhat meaningful.59$words[] = $key;6061foreach ($words as $word_key => $word) {62$words[$word_key] = phutil_utf8_strtolower($word);63}6465$words = array_unique($words);6667$map[$key] = $words;68}6970return $map;71}727374public static function getTaskStatusName($status) {75return self::getStatusAttribute($status, 'name', pht('Unknown Status'));76}7778public static function getTaskStatusFullName($status) {79$name = self::getStatusAttribute($status, 'name.full');80if ($name !== null) {81return $name;82}8384return self::getStatusAttribute($status, 'name', pht('Unknown Status'));85}8687public static function renderFullDescription($status, $priority) {88if (self::isOpenStatus($status)) {89$name = pht('%s, %s', self::getTaskStatusFullName($status), $priority);90$color = 'grey';91$icon = 'fa-square-o';92} else {93$name = self::getTaskStatusFullName($status);94$color = 'indigo';95$icon = 'fa-check-square-o';96}9798$tag = id(new PHUITagView())99->setName($name)100->setIcon($icon)101->setType(PHUITagView::TYPE_SHADE)102->setColor($color);103104return $tag;105}106107private static function getSpecialStatus($special) {108foreach (self::getStatusConfig() as $const => $status) {109if (idx($status, 'special') == $special) {110return $const;111}112}113return null;114}115116public static function getDefaultStatus() {117return self::getSpecialStatus(self::SPECIAL_DEFAULT);118}119120public static function getDefaultClosedStatus() {121return self::getSpecialStatus(self::SPECIAL_CLOSED);122}123124public static function getDuplicateStatus() {125return self::getSpecialStatus(self::SPECIAL_DUPLICATE);126}127128public static function getOpenStatusConstants() {129$result = array();130foreach (self::getEnabledStatusMap() as $const => $status) {131if (empty($status['closed'])) {132$result[] = $const;133}134}135return $result;136}137138public static function getClosedStatusConstants() {139$all = array_keys(self::getTaskStatusMap());140$open = self::getOpenStatusConstants();141return array_diff($all, $open);142}143144public static function isOpenStatus($status) {145foreach (self::getOpenStatusConstants() as $constant) {146if ($status == $constant) {147return true;148}149}150return false;151}152153public static function isClaimStatus($status) {154return self::getStatusAttribute($status, 'claim', true);155}156157public static function isClosedStatus($status) {158return !self::isOpenStatus($status);159}160161public static function areCommentsLockedInStatus($status) {162return (bool)self::getStatusAttribute($status, 'locked', false);163}164165public static function areEditsLockedInStatus($status) {166$locked = self::getStatusAttribute($status, 'locked');167return ($locked === self::LOCKED_EDITS);168}169170public static function isMFAStatus($status) {171return self::getStatusAttribute($status, 'mfa', false);172}173174public static function getStatusActionName($status) {175return self::getStatusAttribute($status, 'name.action');176}177178public static function getStatusColor($status) {179return self::getStatusAttribute($status, 'transaction.color');180}181182public static function isDisabledStatus($status) {183return self::getStatusAttribute($status, 'disabled');184}185186public static function getStatusIcon($status) {187$icon = self::getStatusAttribute($status, 'transaction.icon');188if ($icon) {189return $icon;190}191192if (self::isOpenStatus($status)) {193return 'fa-exclamation-circle';194} else {195return 'fa-check-square-o';196}197}198199public static function getStatusPrefixMap() {200$map = array();201foreach (self::getEnabledStatusMap() as $const => $status) {202foreach (idx($status, 'prefixes', array()) as $prefix) {203$map[$prefix] = $const;204}205}206207$map += array(208'ref' => null,209'refs' => null,210'references' => null,211'cf.' => null,212);213214return $map;215}216217public static function getStatusSuffixMap() {218$map = array();219foreach (self::getEnabledStatusMap() as $const => $status) {220foreach (idx($status, 'suffixes', array()) as $prefix) {221$map[$prefix] = $const;222}223}224return $map;225}226227private static function getStatusAttribute($status, $key, $default = null) {228$config = self::getStatusConfig();229230$spec = idx($config, $status);231if ($spec) {232return idx($spec, $key, $default);233}234235return $default;236}237238239/* -( Configuration Validation )------------------------------------------- */240241242/**243* @task validate244*/245public static function isValidStatusConstant($constant) {246if (!strlen($constant) || strlen($constant) > 64) {247return false;248}249250// Alphanumeric, but not exclusively numeric251if (!preg_match('/^(?![0-9]*$)[a-zA-Z0-9]+$/', $constant)) {252return false;253}254return true;255}256257/**258* @task validate259*/260public static function validateConfiguration(array $config) {261foreach ($config as $key => $value) {262if (!self::isValidStatusConstant($key)) {263throw new Exception(264pht(265'Key "%s" is not a valid status constant. Status constants '.266'must be 1-64 alphanumeric characters and cannot be exclusively '.267'digits. For example, "%s" or "%s" are reasonable choices.',268$key,269'open',270'closed'));271}272if (!is_array($value)) {273throw new Exception(274pht(275'Value for key "%s" should be a dictionary.',276$key));277}278279PhutilTypeSpec::checkMap(280$value,281array(282'name' => 'string',283'name.full' => 'optional string',284'name.action' => 'optional string',285'closed' => 'optional bool',286'special' => 'optional string',287'transaction.icon' => 'optional string',288'transaction.color' => 'optional string',289'silly' => 'optional bool',290'prefixes' => 'optional list<string>',291'suffixes' => 'optional list<string>',292'keywords' => 'optional list<string>',293'disabled' => 'optional bool',294'claim' => 'optional bool',295'locked' => 'optional bool|string',296'mfa' => 'optional bool',297));298}299300// Supported values are "comments" or "edits". For backward compatibility,301// "true" is an alias of "comments".302303foreach ($config as $key => $value) {304$locked = idx($value, 'locked', false);305if ($locked === true || $locked === false) {306continue;307}308309if ($locked === self::LOCKED_EDITS ||310$locked === self::LOCKED_COMMENTS) {311continue;312}313314throw new Exception(315pht(316'Task status ("%s") has unrecognized value for "locked" '.317'configuration ("%s"). Supported values are: "%s", "%s".',318$key,319$locked,320self::LOCKED_COMMENTS,321self::LOCKED_EDITS));322}323324$special_map = array();325foreach ($config as $key => $value) {326$special = idx($value, 'special');327if (!$special) {328continue;329}330331if (isset($special_map[$special])) {332throw new Exception(333pht(334'Configuration has two statuses both marked with the special '.335'attribute "%s" ("%s" and "%s"). There should be only one.',336$special,337$special_map[$special],338$key));339}340341switch ($special) {342case self::SPECIAL_DEFAULT:343if (!empty($value['closed'])) {344throw new Exception(345pht(346'Status "%s" is marked as default, but it is a closed '.347'status. The default status should be an open status.',348$key));349}350break;351case self::SPECIAL_CLOSED:352if (empty($value['closed'])) {353throw new Exception(354pht(355'Status "%s" is marked as the default status for closing '.356'tasks, but is not a closed status. It should be a closed '.357'status.',358$key));359}360break;361case self::SPECIAL_DUPLICATE:362if (empty($value['closed'])) {363throw new Exception(364pht(365'Status "%s" is marked as the status for closing tasks as '.366'duplicates, but it is not a closed status. It should '.367'be a closed status.',368$key));369}370break;371}372373$special_map[$special] = $key;374}375376// NOTE: We're not explicitly validating that we have at least one open377// and one closed status, because the DEFAULT and CLOSED specials imply378// that to be true. If those change in the future, that might become a379// reasonable thing to validate.380381$required = array(382self::SPECIAL_DEFAULT,383self::SPECIAL_CLOSED,384self::SPECIAL_DUPLICATE,385);386387foreach ($required as $required_special) {388if (!isset($special_map[$required_special])) {389throw new Exception(390pht(391'Configuration defines no task status with special attribute '.392'"%s", but you must specify a status which fills this special '.393'role.',394$required_special));395}396}397}398399}400401402