diff --git a/src/applications/maniphest/constants/ManiphestTaskStatus.php b/src/applications/maniphest/constants/ManiphestTaskStatus.php index 8e2b045e68..276af1ec00 100644 --- a/src/applications/maniphest/constants/ManiphestTaskStatus.php +++ b/src/applications/maniphest/constants/ManiphestTaskStatus.php @@ -1,187 +1,254 @@ $open, - self::STATUS_CLOSED_RESOLVED => $resolved, - self::STATUS_CLOSED_WONTFIX => $wontfix, - self::STATUS_CLOSED_INVALID => $invalid, - self::STATUS_CLOSED_DUPLICATE => $duplicate, - self::STATUS_CLOSED_SPITE => $spite, + const SPECIAL_DEFAULT = 'default'; + const SPECIAL_CLOSED = 'closed'; + const SPECIAL_DUPLICATE = 'duplicate'; + + private static function getStatusConfig() { + return array( + self::STATUS_OPEN => array( + 'name' => pht('Open'), + 'special' => self::SPECIAL_DEFAULT, + ), + self::STATUS_CLOSED_RESOLVED => array( + 'name' => pht('Resolved'), + 'name.full' => pht('Closed, Resolved'), + 'closed' => true, + 'special' => self::SPECIAL_CLOSED, + 'prefixes' => array( + 'closed', + 'closes', + 'close', + 'fix', + 'fixes', + 'fixed', + 'resolve', + 'resolves', + 'resolved', + ), + 'suffixes' => array( + 'as resolved', + 'as fixed', + ), + ), + self::STATUS_CLOSED_WONTFIX => array( + 'name' => pht('Wontfix'), + 'name.full' => pht('Closed, Wontfix'), + 'closed' => true, + 'prefixes' => array( + 'wontfix', + 'wontfixes', + 'wontfixed', + ), + 'suffixes' => array( + 'as wontfix', + ), + ), + self::STATUS_CLOSED_INVALID => array( + 'name' => pht('Invalid'), + 'name.full' => pht('Closed, Invalid'), + 'closed' => true, + 'prefixes' => array( + 'invalidate', + 'invalidates', + 'invalidated', + ), + 'suffixes' => array( + 'as invalid', + ), + ), + self::STATUS_CLOSED_DUPLICATE => array( + 'name' => pht('Duplicate'), + 'name.full' => pht('Closed, Duplicate'), + 'transaction.icon' => 'delete', + 'special' => self::SPECIAL_DUPLICATE, + 'closed' => true, + ), + self::STATUS_CLOSED_SPITE => array( + 'name' => pht('Spite'), + 'name.full' => pht('Closed, Spite'), + 'name.action' => pht('Spited'), + 'transaction.icon' => 'dislike', + 'silly' => true, + 'closed' => true, + 'prefixes' => array( + 'spite', + 'spites', + 'spited', + ), + 'suffixes' => array( + 'out of spite', + 'as spite', + ), + ), ); + } + + private static function getEnabledStatusMap() { + $spec = self::getStatusConfig(); $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business'); - if (!$is_serious) { - $statuses[self::STATUS_CLOSED_SPITE] = pht('Spite'); + foreach ($spec as $const => $status) { + if ($is_serious && !empty($status['silly'])) { + unset($spec[$const]); + continue; + } } - return $statuses; + return $spec; + } + + public static function getTaskStatusMap() { + return ipull(self::getEnabledStatusMap(), 'name'); } public static function getTaskStatusName($status) { - return idx(self::getTaskStatusMap(), $status, pht('Unknown Status')); + return self::getStatusAttribute($status, 'name', pht('Unknown Status')); } public static function getTaskStatusFullName($status) { - $open = pht('Open'); - $resolved = pht('Closed, Resolved'); - $wontfix = pht('Closed, Wontfix'); - $invalid = pht('Closed, Invalid'); - $duplicate = pht('Closed, Duplicate'); - $spite = pht('Closed, Spite'); - - $map = array( - self::STATUS_OPEN => $open, - self::STATUS_CLOSED_RESOLVED => $resolved, - self::STATUS_CLOSED_WONTFIX => $wontfix, - self::STATUS_CLOSED_INVALID => $invalid, - self::STATUS_CLOSED_DUPLICATE => $duplicate, - self::STATUS_CLOSED_SPITE => $spite, - ); - return idx($map, $status, '???'); + $name = self::getStatusAttribute($status, 'name.full'); + if ($name !== null) { + return $name; + } + + return self::getStatusAttribute($status, 'name', pht('Unknown Status')); } public static function renderFullDescription($status) { if (self::isOpenStatus($status)) { $color = 'status'; $icon = 'oh-open'; } else { $color = 'status-dark'; $icon = 'oh-closed-dark'; } $img = id(new PHUIIconView()) ->setSpriteSheet(PHUIIconView::SPRITE_STATUS) ->setSpriteIcon($icon); $tag = phutil_tag( 'span', array( 'class' => 'phui-header-'.$color.' plr', ), array( $img, self::getTaskStatusFullName($status), )); return $tag; } + private static function getSpecialStatus($special) { + foreach (self::getEnabledStatusMap() as $const => $status) { + if (idx($status, 'special') == $special) { + return $const; + } + } + return null; + } + public static function getDefaultStatus() { - return self::STATUS_OPEN; + return self::getSpecialStatus(self::SPECIAL_DEFAULT); } public static function getDefaultClosedStatus() { - return self::STATUS_CLOSED_RESOLVED; + return self::getSpecialStatus(self::SPECIAL_CLOSED); } public static function getDuplicateStatus() { - return self::STATUS_CLOSED_DUPLICATE; + return self::getSpecialStatus(self::SPECIAL_DUPLICATE); } public static function getOpenStatusConstants() { - return array( - self::STATUS_OPEN, - ); + $result = array(); + foreach (self::getEnabledStatusMap() as $const => $status) { + if (empty($status['closed'])) { + $result[] = $const; + } + } + return $result; } public static function getClosedStatusConstants() { $all = array_keys(self::getTaskStatusMap()); $open = self::getOpenStatusConstants(); return array_diff($all, $open); } public static function isOpenStatus($status) { foreach (self::getOpenStatusConstants() as $constant) { if ($status == $constant) { return true; } } return false; } public static function isClosedStatus($status) { return !self::isOpenStatus($status); } public static function getStatusActionName($status) { - switch ($status) { - case self::STATUS_CLOSED_SPITE: - return pht('Spited'); - } - return null; + return self::getStatusAttribute($status, 'name.action'); } public static function getStatusColor($status) { - if (self::isOpenStatus($status)) { - return 'green'; - } - return 'black'; + return self::getStatusAttribute($status, 'transaction.color'); } public static function getStatusIcon($status) { - switch ($status) { - case ManiphestTaskStatus::STATUS_CLOSED_SPITE: - return 'dislike'; - case ManiphestTaskStatus::STATUS_CLOSED_DUPLICATE: - return 'delete'; - } + return self::getStatusAttribute($status, 'transaction.icon'); } - public static function getStatusPrefixMap() { - return array( - 'resolve' => self::STATUS_CLOSED_RESOLVED, - 'resolves' => self::STATUS_CLOSED_RESOLVED, - 'resolved' => self::STATUS_CLOSED_RESOLVED, - 'fix' => self::STATUS_CLOSED_RESOLVED, - 'fixes' => self::STATUS_CLOSED_RESOLVED, - 'fixed' => self::STATUS_CLOSED_RESOLVED, - 'wontfix' => self::STATUS_CLOSED_WONTFIX, - 'wontfixes' => self::STATUS_CLOSED_WONTFIX, - 'wontfixed' => self::STATUS_CLOSED_WONTFIX, - 'spite' => self::STATUS_CLOSED_SPITE, - 'spites' => self::STATUS_CLOSED_SPITE, - 'spited' => self::STATUS_CLOSED_SPITE, - 'invalidate' => self::STATUS_CLOSED_INVALID, - 'invaldiates' => self::STATUS_CLOSED_INVALID, - 'invalidated' => self::STATUS_CLOSED_INVALID, - 'close' => self::STATUS_CLOSED_RESOLVED, - 'closes' => self::STATUS_CLOSED_RESOLVED, - 'closed' => self::STATUS_CLOSED_RESOLVED, + $map = array(); + foreach (self::getEnabledStatusMap() as $const => $status) { + foreach (idx($status, 'prefixes', array()) as $prefix) { + $map[$prefix] = $const; + } + } + + $map += array( 'ref' => null, 'refs' => null, 'references' => null, 'cf.' => null, ); + + return $map; } public static function getStatusSuffixMap() { - return array( - 'as resolved' => self::STATUS_CLOSED_RESOLVED, - 'as fixed' => self::STATUS_CLOSED_RESOLVED, - 'as wontfix' => self::STATUS_CLOSED_WONTFIX, - 'as spite' => self::STATUS_CLOSED_SPITE, - 'out of spite' => self::STATUS_CLOSED_SPITE, - 'as invalid' => self::STATUS_CLOSED_INVALID, - ); + $map = array(); + foreach (self::getEnabledStatusMap() as $const => $status) { + foreach (idx($status, 'suffixes', array()) as $prefix) { + $map[$prefix] = $const; + } + } + return $map; } + private static function getStatusAttribute($status, $key, $default = null) { + $config = self::getStatusConfig(); + + $spec = idx($config, $status); + if ($spec) { + return idx($spec, $key, $default); + } + + return $default; + } } diff --git a/src/applications/maniphest/storage/ManiphestTransaction.php b/src/applications/maniphest/storage/ManiphestTransaction.php index f6617530e9..cedcb89cde 100644 --- a/src/applications/maniphest/storage/ManiphestTransaction.php +++ b/src/applications/maniphest/storage/ManiphestTransaction.php @@ -1,747 +1,752 @@ getTransactionType()) { case self::TYPE_PROJECT_COLUMN: case self::TYPE_EDGE: return false; } return parent::shouldGenerateOldValue(); } public function getRequiredHandlePHIDs() { $phids = parent::getRequiredHandlePHIDs(); $new = $this->getNewValue(); $old = $this->getOldValue(); switch ($this->getTransactionType()) { case self::TYPE_OWNER: if ($new) { $phids[] = $new; } if ($old) { $phids[] = $old; } break; case self::TYPE_CCS: case self::TYPE_PROJECTS: $phids = array_mergev( array( $phids, nonempty($old, array()), nonempty($new, array()), )); break; case self::TYPE_PROJECT_COLUMN: $phids[] = $new['projectPHID']; $phids[] = head($new['columnPHIDs']); break; case self::TYPE_EDGE: $phids = array_mergev( array( $phids, array_keys(nonempty($old, array())), array_keys(nonempty($new, array())), )); break; case self::TYPE_ATTACH: $old = nonempty($old, array()); $new = nonempty($new, array()); $phids = array_mergev( array( $phids, array_keys(idx($new, 'FILE', array())), array_keys(idx($old, 'FILE', array())), )); break; } return $phids; } public function shouldHide() { switch ($this->getTransactionType()) { case self::TYPE_TITLE: case self::TYPE_DESCRIPTION: case self::TYPE_PRIORITY: if ($this->getOldValue() === null) { return true; } else { return false; } break; case self::TYPE_SUBPRIORITY: return true; } return parent::shouldHide(); } public function getActionStrength() { switch ($this->getTransactionType()) { case self::TYPE_STATUS: return 1.3; case self::TYPE_OWNER: return 1.2; case self::TYPE_PRIORITY: return 1.1; } return parent::getActionStrength(); } public function getColor() { $old = $this->getOldValue(); $new = $this->getNewValue(); switch ($this->getTransactionType()) { case self::TYPE_OWNER: if ($this->getAuthorPHID() == $new) { return 'green'; } else if (!$new) { return 'black'; } else if (!$old) { return 'green'; } else { return 'green'; } case self::TYPE_STATUS: $color = ManiphestTaskStatus::getStatusColor($new); if ($color !== null) { return $color; } - break; + + if (ManiphestTaskStatus::isOpenStatus($new)) { + return 'green'; + } else { + return 'black'; + } case self::TYPE_PRIORITY: if ($old == ManiphestTaskPriority::getDefaultPriority()) { return 'green'; } else if ($old > $new) { return 'grey'; } else { return 'yellow'; } } return parent::getColor(); } public function getActionName() { $old = $this->getOldValue(); $new = $this->getNewValue(); switch ($this->getTransactionType()) { case self::TYPE_TITLE: return pht('Retitled'); case self::TYPE_STATUS: if ($old === null) { return pht('Created'); } $action = ManiphestTaskStatus::getStatusActionName($new); if ($action) { return $action; } $old_closed = ManiphestTaskStatus::isClosedStatus($old); $new_closed = ManiphestTaskStatus::isClosedStatus($new); if ($new_closed && !$old_closed) { return pht('Closed'); } else if (!$new_closed && $old_closed) { return pht('Reopened'); } else { return pht('Changed Status'); } case self::TYPE_DESCRIPTION: return pht('Edited'); case self::TYPE_OWNER: if ($this->getAuthorPHID() == $new) { return pht('Claimed'); } else if (!$new) { return pht('Up For Grabs'); } else if (!$old) { return pht('Assigned'); } else { return pht('Reassigned'); } case self::TYPE_CCS: return pht('Changed CC'); case self::TYPE_PROJECTS: return pht('Changed Projects'); case self::TYPE_PROJECT_COLUMN: return pht('Changed Project Column'); case self::TYPE_PRIORITY: if ($old == ManiphestTaskPriority::getDefaultPriority()) { return pht('Triaged'); } else if ($old > $new) { return pht('Lowered Priority'); } else { return pht('Raised Priority'); } case self::TYPE_EDGE: case self::TYPE_ATTACH: return pht('Attached'); } return parent::getActionName(); } public function getIcon() { $old = $this->getOldValue(); $new = $this->getNewValue(); switch ($this->getTransactionType()) { case self::TYPE_OWNER: return 'user'; case self::TYPE_CCS: return 'meta-mta'; case self::TYPE_TITLE: return 'edit'; case self::TYPE_STATUS: if ($old === null) { return 'create'; } $action = ManiphestTaskStatus::getStatusIcon($new); if ($action !== null) { return $action; } if (ManiphestTaskStatus::isClosedStatus($new)) { return 'check'; } else { return 'edit'; } case self::TYPE_DESCRIPTION: return 'edit'; case self::TYPE_PROJECTS: return 'project'; case self::TYPE_PROJECT_COLUMN: return 'workboard'; case self::TYPE_PRIORITY: if ($old == ManiphestTaskPriority::getDefaultPriority()) { return 'normal-priority'; return pht('Triaged'); } else if ($old > $new) { return 'lower-priority'; } else { return 'raise-priority'; } case self::TYPE_EDGE: case self::TYPE_ATTACH: return 'attach'; } return parent::getIcon(); } public function getTitle() { $author_phid = $this->getAuthorPHID(); $old = $this->getOldValue(); $new = $this->getNewValue(); switch ($this->getTransactionType()) { case self::TYPE_TITLE: return pht( '%s changed the title from "%s" to "%s".', $this->renderHandleLink($author_phid), $old, $new); case self::TYPE_DESCRIPTION: return pht( '%s edited the task description.', $this->renderHandleLink($author_phid)); case self::TYPE_STATUS: if ($old === null) { return pht( '%s created this task.', $this->renderHandleLink($author_phid)); } $old_closed = ManiphestTaskStatus::isClosedStatus($old); $new_closed = ManiphestTaskStatus::isClosedStatus($new); $old_name = ManiphestTaskStatus::getTaskStatusName($old); $new_name = ManiphestTaskStatus::getTaskStatusName($new); if ($new_closed && !$old_closed) { if ($new == ManiphestTaskStatus::getDuplicateStatus()) { return pht( '%s closed this task as a duplicate.', $this->renderHandleLink($author_phid)); } else { return pht( '%s closed this task as "%s".', $this->renderHandleLink($author_phid), $new_name); } } else if (!$new_closed && $old_closed) { return pht( '%s reopened this task as "%s".', $this->renderHandleLink($author_phid), $new_name); } else { return pht( '%s changed the task status from "%s" to "%s".', $this->renderHandleLink($author_phid), $old_name, $new_name); } case self::TYPE_OWNER: if ($author_phid == $new) { return pht( '%s claimed this task.', $this->renderHandleLink($author_phid)); } else if (!$new) { return pht( '%s placed this task up for grabs.', $this->renderHandleLink($author_phid)); } else if (!$old) { return pht( '%s assigned this task to %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($new)); } else { return pht( '%s reassigned this task from %s to %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($old), $this->renderHandleLink($new)); } case self::TYPE_PROJECTS: $added = array_diff($new, $old); $removed = array_diff($old, $new); if ($added && !$removed) { return pht( '%s added %d project(s): %s', $this->renderHandleLink($author_phid), count($added), $this->renderHandleList($added)); } else if ($removed && !$added) { return pht( '%s removed %d project(s): %s', $this->renderHandleLink($author_phid), count($removed), $this->renderHandleList($removed)); } else if ($removed && $added) { return pht( '%s changed project(s), added %d: %s; removed %d: %s', $this->renderHandleLink($author_phid), count($added), $this->renderHandleList($added), count($removed), $this->renderHandleList($removed)); } else { // This is hit when rendering previews. return pht( '%s changed projects...', $this->renderHandleLink($author_phid)); } case self::TYPE_PRIORITY: $old_name = ManiphestTaskPriority::getTaskPriorityName($old); $new_name = ManiphestTaskPriority::getTaskPriorityName($new); if ($old == ManiphestTaskPriority::getDefaultPriority()) { return pht( '%s triaged this task as "%s" priority.', $this->renderHandleLink($author_phid), $new_name); } else if ($old > $new) { return pht( '%s lowered the priority of this task from "%s" to "%s".', $this->renderHandleLink($author_phid), $old_name, $new_name); } else { return pht( '%s raised the priority of this task from "%s" to "%s".', $this->renderHandleLink($author_phid), $old_name, $new_name); } case self::TYPE_CCS: // TODO: Remove this when we switch to subscribers. Just reuse the // code in the parent. $clone = clone $this; $clone->setTransactionType(PhabricatorTransactions::TYPE_SUBSCRIBERS); return $clone->getTitle(); case self::TYPE_EDGE: // TODO: Remove this when we switch to real edges. Just reuse the // code in the parent; $clone = clone $this; $clone->setTransactionType(PhabricatorTransactions::TYPE_EDGE); return $clone->getTitle(); case self::TYPE_ATTACH: $old = nonempty($old, array()); $new = nonempty($new, array()); $new = array_keys(idx($new, 'FILE', array())); $old = array_keys(idx($old, 'FILE', array())); $added = array_diff($new, $old); $removed = array_diff($old, $new); if ($added && !$removed) { return pht( '%s attached %d file(s): %s', $this->renderHandleLink($author_phid), count($added), $this->renderHandleList($added)); } else if ($removed && !$added) { return pht( '%s detached %d file(s): %s', $this->renderHandleLink($author_phid), count($removed), $this->renderHandleList($removed)); } else { return pht( '%s changed file(s), attached %d: %s; detached %d: %s', $this->renderHandleLink($author_phid), count($added), $this->renderHandleList($added), count($removed), $this->renderHandleList($removed)); } case self::TYPE_PROJECT_COLUMN: $project_phid = $new['projectPHID']; $column_phid = head($new['columnPHIDs']); return pht( '%s moved this task to %s on the %s workboard.', $this->renderHandleLink($author_phid), $this->renderHandleLink($column_phid), $this->renderHandleLink($project_phid)); break; } return parent::getTitle(); } public function getTitleForFeed(PhabricatorFeedStory $story) { $author_phid = $this->getAuthorPHID(); $object_phid = $this->getObjectPHID(); $old = $this->getOldValue(); $new = $this->getNewValue(); switch ($this->getTransactionType()) { case self::TYPE_TITLE: return pht( '%s renamed %s from "%s" to "%s".', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid), $old, $new); case self::TYPE_DESCRIPTION: return pht( '%s edited the description of %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); case self::TYPE_STATUS: if ($old === null) { return pht( '%s created %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); } $old_closed = ManiphestTaskStatus::isClosedStatus($old); $new_closed = ManiphestTaskStatus::isClosedStatus($new); $old_name = ManiphestTaskStatus::getTaskStatusName($old); $new_name = ManiphestTaskStatus::getTaskStatusName($new); if ($new_closed && !$old_closed) { if ($new == ManiphestTaskStatus::getDuplicateStatus()) { return pht( '%s closed %s as a duplicate.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); } else { return pht( '%s closed %s as "%s".', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid), $new_name); } } else if (!$new_closed && $old_closed) { return pht( '%s reopened %s as "%s".', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid), $new_name); } else { return pht( '%s changed the status of %s from "%s" to "%s".', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid), $old_name, $new_name); } case self::TYPE_OWNER: if ($author_phid == $new) { return pht( '%s claimed %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); } else if (!$new) { return pht( '%s placed %s up for grabs.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); } else if (!$old) { return pht( '%s assigned %s to %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid), $this->renderHandleLink($new)); } else { return pht( '%s reassigned %s from %s to %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid), $this->renderHandleLink($old), $this->renderHandleLink($new)); } case self::TYPE_PROJECTS: $added = array_diff($new, $old); $removed = array_diff($old, $new); if ($added && !$removed) { return pht( '%s added %d project(s) to %s: %s', $this->renderHandleLink($author_phid), count($added), $this->renderHandleLink($object_phid), $this->renderHandleList($added)); } else if ($removed && !$added) { return pht( '%s removed %d project(s) from %s: %s', $this->renderHandleLink($author_phid), count($removed), $this->renderHandleLink($object_phid), $this->renderHandleList($removed)); } else if ($removed && $added) { return pht( '%s changed project(s) of %s, added %d: %s; removed %d: %s', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid), count($added), $this->renderHandleList($added), count($removed), $this->renderHandleList($removed)); } case self::TYPE_PRIORITY: $old_name = ManiphestTaskPriority::getTaskPriorityName($old); $new_name = ManiphestTaskPriority::getTaskPriorityName($new); if ($old == ManiphestTaskPriority::getDefaultPriority()) { return pht( '%s triaged %s as "%s" priority.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid), $new_name); } else if ($old > $new) { return pht( '%s lowered the priority of %s from "%s" to "%s".', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid), $old_name, $new_name); } else { return pht( '%s raised the priority of %s from "%s" to "%s".', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid), $old_name, $new_name); } case self::TYPE_CCS: // TODO: Remove this when we switch to subscribers. Just reuse the // code in the parent. $clone = clone $this; $clone->setTransactionType(PhabricatorTransactions::TYPE_SUBSCRIBERS); return $clone->getTitleForFeed($story); case self::TYPE_EDGE: // TODO: Remove this when we switch to real edges. Just reuse the // code in the parent; $clone = clone $this; $clone->setTransactionType(PhabricatorTransactions::TYPE_EDGE); return $clone->getTitleForFeed($story); case self::TYPE_ATTACH: $old = nonempty($old, array()); $new = nonempty($new, array()); $new = array_keys(idx($new, 'FILE', array())); $old = array_keys(idx($old, 'FILE', array())); $added = array_diff($new, $old); $removed = array_diff($old, $new); if ($added && !$removed) { return pht( '%s attached %d file(s) of %s: %s', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid), count($added), $this->renderHandleList($added)); } else if ($removed && !$added) { return pht( '%s detached %d file(s) of %s: %s', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid), count($removed), $this->renderHandleList($removed)); } else { return pht( '%s changed file(s) for %s, attached %d: %s; detached %d: %s', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid), count($added), $this->renderHandleList($added), count($removed), $this->renderHandleList($removed)); } case self::TYPE_PROJECT_COLUMN: $project_phid = $new['projectPHID']; $column_phid = head($new['columnPHIDs']); return pht( '%s moved %s to %s on the %s workboard.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid), $this->renderHandleLink($column_phid), $this->renderHandleLink($project_phid)); break; } return parent::getTitleForFeed($story); } public function hasChangeDetails() { switch ($this->getTransactionType()) { case self::TYPE_DESCRIPTION: return true; } return parent::hasChangeDetails(); } public function renderChangeDetails(PhabricatorUser $viewer) { return $this->renderTextCorpusChangeDetails( $viewer, $this->getOldValue(), $this->getNewValue()); } public function getMailTags() { $tags = array(); switch ($this->getTransactionType()) { case self::TYPE_STATUS: $tags[] = MetaMTANotificationType::TYPE_MANIPHEST_STATUS; break; case self::TYPE_OWNER: $tags[] = MetaMTANotificationType::TYPE_MANIPHEST_OWNER; break; case self::TYPE_CCS: $tags[] = MetaMTANotificationType::TYPE_MANIPHEST_CC; break; case self::TYPE_PROJECTS: $tags[] = MetaMTANotificationType::TYPE_MANIPHEST_PROJECTS; break; case self::TYPE_PRIORITY: $tags[] = MetaMTANotificationType::TYPE_MANIPHEST_PRIORITY; break; case PhabricatorTransactions::TYPE_COMMENT: $tags[] = MetaMTANotificationType::TYPE_MANIPHEST_COMMENT; break; default: $tags[] = MetaMTANotificationType::TYPE_MANIPHEST_OTHER; break; } return $tags; } public function getNoEffectDescription() { switch ($this->getTransactionType()) { case self::TYPE_STATUS: return pht('The task already has the selected status.'); case self::TYPE_OWNER: return pht('The task already has the selected owner.'); case self::TYPE_PROJECTS: return pht('The task is already associated with those projects.'); case self::TYPE_PRIORITY: return pht('The task already has the selected priority.'); } return parent::getNoEffectDescription(); } }