protected function expandTransactions(PhabricatorLiskDAO $object, array $xactions)
 {
     $results = parent::expandTransactions($object, $xactions);
     $is_unassigned = $object->getOwnerPHID() === null;
     $any_assign = false;
     foreach ($xactions as $xaction) {
         if ($xaction->getTransactionType() == ManiphestTransaction::TYPE_OWNER) {
             $any_assign = true;
             break;
         }
     }
     $is_open = !$object->isClosed();
     $new_status = null;
     foreach ($xactions as $xaction) {
         switch ($xaction->getTransactionType()) {
             case ManiphestTransaction::TYPE_STATUS:
                 $new_status = $xaction->getNewValue();
                 break;
         }
     }
     if ($new_status === null) {
         $is_closing = false;
     } else {
         $is_closing = ManiphestTaskStatus::isClosedStatus($new_status);
     }
     // If the task is not assigned, not being assigned, currently open, and
     // being closed, try to assign the actor as the owner.
     if ($is_unassigned && !$any_assign && $is_open && $is_closing) {
         // Don't assign the actor if they aren't a real user.
         $actor = $this->getActor();
         $actor_phid = $actor->getPHID();
         if ($actor_phid) {
             $results[] = id(new ManiphestTransaction())->setTransactionType(ManiphestTransaction::TYPE_OWNER)->setNewValue($actor_phid);
         }
     }
     return $results;
 }
 protected function expandTransactions(PhabricatorLiskDAO $object, array $xactions)
 {
     $actor = $this->getActor();
     $actor_phid = $actor->getPHID();
     $results = parent::expandTransactions($object, $xactions);
     $is_unassigned = $object->getOwnerPHID() === null;
     $any_assign = false;
     foreach ($xactions as $xaction) {
         if ($xaction->getTransactionType() == ManiphestTransaction::TYPE_OWNER) {
             $any_assign = true;
             break;
         }
     }
     $is_open = !$object->isClosed();
     $new_status = null;
     foreach ($xactions as $xaction) {
         switch ($xaction->getTransactionType()) {
             case ManiphestTransaction::TYPE_STATUS:
                 $new_status = $xaction->getNewValue();
                 break;
         }
     }
     if ($new_status === null) {
         $is_closing = false;
     } else {
         $is_closing = ManiphestTaskStatus::isClosedStatus($new_status);
     }
     // If the task is not assigned, not being assigned, currently open, and
     // being closed, try to assign the actor as the owner.
     if ($is_unassigned && !$any_assign && $is_open && $is_closing) {
         $is_claim = ManiphestTaskStatus::isClaimStatus($new_status);
         // Don't assign the actor if they aren't a real user.
         // Don't claim the task if the status is configured to not claim.
         if ($actor_phid && $is_claim) {
             $results[] = id(new ManiphestTransaction())->setTransactionType(ManiphestTransaction::TYPE_OWNER)->setNewValue($actor_phid);
         }
     }
     // Automatically subscribe the author when they create a task.
     if ($this->getIsNewObject()) {
         if ($actor_phid) {
             $results[] = id(new ManiphestTransaction())->setTransactionType(PhabricatorTransactions::TYPE_SUBSCRIBERS)->setNewValue(array('+' => array($actor_phid => $actor_phid)));
         }
     }
     return $results;
 }
 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:
             if ($old === null) {
                 return pht('%s created %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid));
             }
             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:
             $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_UNBLOCK:
             $blocker_phid = key($new);
             $old_status = head($old);
             $new_status = head($new);
             $old_closed = ManiphestTaskStatus::isClosedStatus($old_status);
             $new_closed = ManiphestTaskStatus::isClosedStatus($new_status);
             $old_name = ManiphestTaskStatus::getTaskStatusName($old_status);
             $new_name = ManiphestTaskStatus::getTaskStatusName($new_status);
             if ($old_closed && !$new_closed) {
                 return pht('%s reopened %s, a task blocking %s, as "%s".', $this->renderHandleLink($author_phid), $this->renderHandleLink($blocker_phid), $this->renderHandleLink($object_phid), $new_name);
             } else {
                 if (!$old_closed && $new_closed) {
                     return pht('%s closed %s, a task blocking %s, as "%s".', $this->renderHandleLink($author_phid), $this->renderHandleLink($blocker_phid), $this->renderHandleLink($object_phid), $new_name);
                 } else {
                     return pht('%s changed the status of %s, a task blocking %s, ' . 'from "%s" to "%s".', $this->renderHandleLink($author_phid), $this->renderHandleLink($blocker_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));
         case self::TYPE_MERGED_INTO:
             return pht('%s merged task %s into %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid), $this->renderHandleLink($new));
         case self::TYPE_MERGED_FROM:
             return pht('%s merged %d task(s) %s into %s.', $this->renderHandleLink($author_phid), count($new), $this->renderHandleList($new), $this->renderHandleLink($object_phid));
     }
     return parent::getTitleForFeed($story);
 }
 protected function newNavigationMenuItems(PhabricatorProfilePanelConfiguration $config)
 {
     $viewer = $this->getViewer();
     $project = $config->getProfileObject();
     $limit = 250;
     $tasks = id(new ManiphestTaskQuery())->setViewer($viewer)->withEdgeLogicPHIDs(PhabricatorProjectObjectHasProjectEdgeType::EDGECONST, PhabricatorQueryConstraint::OPERATOR_AND, array($project->getPHID()))->setLimit($limit + 1)->execute();
     if (count($tasks) > $limit) {
         return $this->renderError(pht('Too many tasks to compute statistics for (more than %s).', new PhutilNumber($limit)));
     }
     if (!$tasks) {
         return $this->renderError(pht('This milestone has no tasks yet.'));
     }
     $statuses = array();
     $points_done = 0;
     $points_total = 0;
     $no_points = 0;
     foreach ($tasks as $task) {
         $points = $task->getPoints();
         if ($points === null) {
             $no_points++;
             continue;
         }
         if (!$points) {
             continue;
         }
         $status = $task->getStatus();
         if (empty($statuses[$status])) {
             $statuses[$status] = 0;
         }
         $statuses[$status] += $points;
         if (ManiphestTaskStatus::isClosedStatus($status)) {
             $points_done += $points;
         }
         $points_total += $points;
     }
     if ($no_points == count($tasks)) {
         return $this->renderError(pht('No tasks have assigned point values.'));
     }
     if (!$points_total) {
         return $this->renderError(pht('All tasks with assigned point values are worth zero points.'));
     }
     $label = pht('%s of %s %s', new PhutilNumber($points_done), new PhutilNumber($points_total), ManiphestTaskPoints::getPointsLabel());
     $bar = id(new PHUISegmentBarView())->setLabel($label);
     $map = ManiphestTaskStatus::getTaskStatusMap();
     $statuses = array_select_keys($statuses, array_keys($map));
     foreach ($statuses as $status => $points) {
         if (!$points) {
             continue;
         }
         if (!ManiphestTaskStatus::isClosedStatus($status)) {
             continue;
         }
         $color = ManiphestTaskStatus::getStatusColor($status);
         if (!$color) {
             $color = 'sky';
         }
         $tooltip = pht('%s %s', new PhutilNumber($points), ManiphestTaskStatus::getTaskStatusName($status));
         $bar->newSegment()->setWidth($points / $points_total)->setColor($color)->setTooltip($tooltip);
     }
     $bar = phutil_tag('div', array('class' => 'phui-profile-segment-bar'), $bar);
     $item = $this->newItem()->appendChild($bar);
     return array($item);
 }
Exemple #5
0
 public function isClosed()
 {
     return ManiphestTaskStatus::isClosedStatus($this->getStatus());
 }
 /**
  * Extract important events (the times when tasks were opened or closed)
  * from a list of transactions.
  *
  * @param list<ManiphestTransaction> List of transactions.
  * @param list<phid> List of project PHIDs to emit "scope" events for.
  * @return list<dict> Chronologically sorted events.
  */
 private function extractEvents($xactions, array $scope_phids)
 {
     assert_instances_of($xactions, 'ManiphestTransaction');
     $scope_phids = array_fuse($scope_phids);
     $events = array();
     foreach ($xactions as $xaction) {
         $old = $xaction->getOldValue();
         $new = $xaction->getNewValue();
         $event_type = null;
         switch ($xaction->getTransactionType()) {
             case ManiphestTransaction::TYPE_STATUS:
                 $old_is_closed = $old === null || ManiphestTaskStatus::isClosedStatus($old);
                 $new_is_closed = ManiphestTaskStatus::isClosedStatus($new);
                 if ($old_is_closed == $new_is_closed) {
                     // This was just a status change from one open status to another,
                     // or from one closed status to another, so it's not an event we
                     // care about.
                     break;
                 }
                 if ($old === null) {
                     // This would show as "reopened" even though it's when the task was
                     // created so we skip it. Instead we will use the title for created
                     // events
                     break;
                 }
                 if ($new_is_closed) {
                     $event_type = 'close';
                 } else {
                     $event_type = 'reopen';
                 }
                 break;
             case ManiphestTransaction::TYPE_TITLE:
                 if ($old === null) {
                     $event_type = 'create';
                 }
                 break;
                 // Project changes are "core:edge" transactions
             // Project changes are "core:edge" transactions
             case PhabricatorTransactions::TYPE_EDGE:
                 // We only care about ProjectEdgeType
                 if (idx($xaction->getMetadata(), 'edge:type') !== PhabricatorProjectObjectHasProjectEdgeType::EDGECONST) {
                     break;
                 }
                 $old = ipull($old, 'dst');
                 $new = ipull($new, 'dst');
                 $in_old_scope = array_intersect_key($scope_phids, $old);
                 $in_new_scope = array_intersect_key($scope_phids, $new);
                 if ($in_new_scope && !$in_old_scope) {
                     $event_type = 'task-add';
                 } else {
                     if ($in_old_scope && !$in_new_scope) {
                         // NOTE: We will miss some of these events, becuase we are only
                         // examining tasks that are currently in the project. If a task
                         // is removed from the project and not added again later, it will
                         // just vanish from the chart completely, not show up as a
                         // scope contraction. We can't do better until the Facts application
                         // is avialable without examining *every* task.
                         $event_type = 'task-remove';
                     }
                 }
                 break;
             case PhabricatorTransactions::TYPE_CUSTOMFIELD:
                 if ($xaction->getMetadataValue('customfield:key') == 'isdc:sprint:storypoints') {
                     // POINTS!
                     $event_type = 'points';
                 }
             default:
                 // This is something else (comment, subscription change, etc) that
                 // we don't care about for now.
                 break;
         }
         // If we found some kind of event that we care about, stick it in the
         // list of events.
         if ($event_type !== null) {
             $events[] = array('transactionPHID' => $xaction->getPHID(), 'epoch' => $xaction->getDateCreated(), 'key' => $xaction->getMetadataValue('customfield:key'), 'type' => $event_type, 'title' => $xaction->getTitle());
         }
     }
     // Sort all events chronologically.
     $events = isort($events, 'epoch');
     return $events;
 }
 public function getTitleForFeed()
 {
     $author_phid = $this->getAuthorPHID();
     $object_phid = $this->getObjectPHID();
     $old = $this->getOldValue();
     $new = $this->getNewValue();
     switch ($this->getTransactionType()) {
         case self::TYPE_TITLE:
             if ($old === null) {
                 return pht('%s created %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid));
             }
             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:
             $old_closed = ManiphestTaskStatus::isClosedStatus($old);
             $new_closed = ManiphestTaskStatus::isClosedStatus($new);
             $old_name = ManiphestTaskStatus::getTaskStatusName($old);
             $new_name = ManiphestTaskStatus::getTaskStatusName($new);
             $commit_phid = $this->getMetadataValue('commitPHID');
             if ($new_closed && !$old_closed) {
                 if ($new == ManiphestTaskStatus::getDuplicateStatus()) {
                     if ($commit_phid) {
                         return pht('%s closed %s as a duplicate by committing %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid), $this->renderHandleLink($commit_phid));
                     } else {
                         return pht('%s closed %s as a duplicate.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid));
                     }
                 } else {
                     if ($commit_phid) {
                         return pht('%s closed %s as "%s" by committing %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid), $new_name, $this->renderHandleLink($commit_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) {
                     if ($commit_phid) {
                         return pht('%s reopened %s as "%s" by committing %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid), $new_name, $this->renderHandleLink($commit_phid));
                     } else {
                         return pht('%s reopened %s as "%s".', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid), $new_name);
                     }
                 } else {
                     if ($commit_phid) {
                         return pht('%s changed the status of %s from "%s" to "%s" by committing %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid), $old_name, $new_name, $this->renderHandleLink($commit_phid));
                     } 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_UNBLOCK:
             $blocker_phid = key($new);
             $old_status = head($old);
             $new_status = head($new);
             $old_closed = ManiphestTaskStatus::isClosedStatus($old_status);
             $new_closed = ManiphestTaskStatus::isClosedStatus($new_status);
             $old_name = ManiphestTaskStatus::getTaskStatusName($old_status);
             $new_name = ManiphestTaskStatus::getTaskStatusName($new_status);
             if ($old_closed && !$new_closed) {
                 return pht('%s reopened %s, a subtask of %s, as "%s".', $this->renderHandleLink($author_phid), $this->renderHandleLink($blocker_phid), $this->renderHandleLink($object_phid), $new_name);
             } else {
                 if (!$old_closed && $new_closed) {
                     return pht('%s closed %s, a subtask of %s, as "%s".', $this->renderHandleLink($author_phid), $this->renderHandleLink($blocker_phid), $this->renderHandleLink($object_phid), $new_name);
                 } else {
                     return pht('%s changed the status of %s, a subtasktask of %s, ' . 'from "%s" to "%s".', $this->renderHandleLink($author_phid), $this->renderHandleLink($blocker_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_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_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_MERGED_INTO:
             return pht('%s merged task %s into %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid), $this->renderHandleLink($new));
         case self::TYPE_MERGED_FROM:
             return pht('%s merged %s task(s) %s into %s.', $this->renderHandleLink($author_phid), phutil_count($new), $this->renderHandleList($new), $this->renderHandleLink($object_phid));
     }
     return parent::getTitleForFeed();
 }
 public function processRequest()
 {
     $request = $this->getRequest();
     $user = $request->getUser();
     $task = id(new ManiphestTaskQuery())->setViewer($user)->withIDs(array($request->getStr('taskID')))->executeOne();
     if (!$task) {
         return new Aphront404Response();
     }
     $task_uri = '/' . $task->getMonogram();
     $transactions = array();
     $action = $request->getStr('action');
     // Compute new CCs added by @mentions. Several things can cause CCs to
     // be added as side effects: mentions, explicit CCs, users who aren't
     // CC'd interacting with the task, and ownership changes. We build up a
     // list of all the CCs and then construct a transaction for them at the
     // end if necessary.
     $added_ccs = PhabricatorMarkupEngine::extractPHIDsFromMentions($user, array($request->getStr('comments')));
     $cc_transaction = new ManiphestTransaction();
     $cc_transaction->setTransactionType(ManiphestTransaction::TYPE_CCS);
     $transaction = new ManiphestTransaction();
     $transaction->setTransactionType($action);
     switch ($action) {
         case ManiphestTransaction::TYPE_STATUS:
             $transaction->setNewValue($request->getStr('resolution'));
             break;
         case ManiphestTransaction::TYPE_OWNER:
             $assign_to = $request->getArr('assign_to');
             $assign_to = reset($assign_to);
             $transaction->setNewValue($assign_to);
             break;
         case ManiphestTransaction::TYPE_PROJECTS:
             $projects = $request->getArr('projects');
             $projects = array_merge($projects, $task->getProjectPHIDs());
             $projects = array_filter($projects);
             $projects = array_unique($projects);
             // TODO: Bleh.
             $project_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST;
             $transaction->setTransactionType(PhabricatorTransactions::TYPE_EDGE)->setMetadataValue('edge:type', $project_type)->setNewValue(array('+' => array_fuse($projects)));
             break;
         case ManiphestTransaction::TYPE_CCS:
             // Accumulate the new explicit CCs into the array that we'll add in
             // the CC transaction later.
             $added_ccs = array_merge($added_ccs, $request->getArr('ccs'));
             // Throw away the primary transaction.
             $transaction = null;
             break;
         case ManiphestTransaction::TYPE_PRIORITY:
             $transaction->setNewValue($request->getInt('priority'));
             break;
         case PhabricatorTransactions::TYPE_COMMENT:
             // Nuke this, we're going to create it below.
             $transaction = null;
             break;
         default:
             throw new Exception('unknown action');
     }
     if ($transaction) {
         $transactions[] = $transaction;
     }
     // When you interact with a task, we add you to the CC list so you get
     // further updates, and possibly assign the task to you if you took an
     // ownership action (closing it) but it's currently unowned. We also move
     // previous owners to CC if ownership changes. Detect all these conditions
     // and create side-effect transactions for them.
     $implicitly_claimed = false;
     if ($action == ManiphestTransaction::TYPE_OWNER) {
         if ($task->getOwnerPHID() == $transaction->getNewValue()) {
             // If this is actually no-op, don't generate the side effect.
         } else {
             // Otherwise, when a task is reassigned, move the previous owner to CC.
             $added_ccs[] = $task->getOwnerPHID();
         }
     }
     if ($action == ManiphestTransaction::TYPE_STATUS) {
         $resolution = $request->getStr('resolution');
         if (!$task->getOwnerPHID() && ManiphestTaskStatus::isClosedStatus($resolution)) {
             // Closing an unassigned task. Assign the user as the owner of
             // this task.
             $assign = new ManiphestTransaction();
             $assign->setTransactionType(ManiphestTransaction::TYPE_OWNER);
             $assign->setNewValue($user->getPHID());
             $transactions[] = $assign;
             $implicitly_claimed = true;
         }
     }
     $user_owns_task = false;
     if ($implicitly_claimed) {
         $user_owns_task = true;
     } else {
         if ($action == ManiphestTransaction::TYPE_OWNER) {
             if ($transaction->getNewValue() == $user->getPHID()) {
                 $user_owns_task = true;
             }
         } else {
             if ($task->getOwnerPHID() == $user->getPHID()) {
                 $user_owns_task = true;
             }
         }
     }
     if (!$user_owns_task) {
         // If we aren't making the user the new task owner and they aren't the
         // existing task owner, add them to CC unless they're aleady CC'd.
         if (!in_array($user->getPHID(), $task->getCCPHIDs())) {
             $added_ccs[] = $user->getPHID();
         }
     }
     // Evade no-effect detection in the new editor stuff until we can switch
     // to subscriptions.
     $added_ccs = array_filter(array_diff($added_ccs, $task->getCCPHIDs()));
     if ($added_ccs) {
         // We've added CCs, so include a CC transaction.
         $all_ccs = array_merge($task->getCCPHIDs(), $added_ccs);
         $cc_transaction->setNewValue($all_ccs);
         $transactions[] = $cc_transaction;
     }
     $comments = $request->getStr('comments');
     if (strlen($comments) || !$transactions) {
         $transactions[] = id(new ManiphestTransaction())->setTransactionType(PhabricatorTransactions::TYPE_COMMENT)->attachComment(id(new ManiphestTransactionComment())->setContent($comments));
     }
     $event = new PhabricatorEvent(PhabricatorEventType::TYPE_MANIPHEST_WILLEDITTASK, array('task' => $task, 'new' => false, 'transactions' => $transactions));
     $event->setUser($user);
     $event->setAphrontRequest($request);
     PhutilEventEngine::dispatchEvent($event);
     $task = $event->getValue('task');
     $transactions = $event->getValue('transactions');
     $editor = id(new ManiphestTransactionEditor())->setActor($user)->setContentSourceFromRequest($request)->setContinueOnMissingFields(true)->setContinueOnNoEffect($request->isContinueRequest());
     try {
         $editor->applyTransactions($task, $transactions);
     } catch (PhabricatorApplicationTransactionNoEffectException $ex) {
         return id(new PhabricatorApplicationTransactionNoEffectResponse())->setCancelURI($task_uri)->setException($ex);
     }
     $draft = id(new PhabricatorDraft())->loadOneWhere('authorPHID = %s AND draftKey = %s', $user->getPHID(), $task->getPHID());
     if ($draft) {
         $draft->delete();
     }
     $event = new PhabricatorEvent(PhabricatorEventType::TYPE_MANIPHEST_DIDEDITTASK, array('task' => $task, 'new' => false, 'transactions' => $transactions));
     $event->setUser($user);
     $event->setAphrontRequest($request);
     PhutilEventEngine::dispatchEvent($event);
     return id(new AphrontRedirectResponse())->setURI($task_uri);
 }