Path: blob/master/src/applications/maniphest/editor/ManiphestEditEngine.php
12256 views
<?php12final class ManiphestEditEngine3extends PhabricatorEditEngine {45const ENGINECONST = 'maniphest.task';67public function getEngineName() {8return pht('Maniphest Tasks');9}1011public function getSummaryHeader() {12return pht('Configure Maniphest Task Forms');13}1415public function getSummaryText() {16return pht('Configure how users create and edit tasks.');17}1819public function getEngineApplicationClass() {20return 'PhabricatorManiphestApplication';21}2223public function isDefaultQuickCreateEngine() {24return true;25}2627public function getQuickCreateOrderVector() {28return id(new PhutilSortVector())->addInt(100);29}3031protected function newEditableObject() {32return ManiphestTask::initializeNewTask($this->getViewer());33}3435protected function newObjectQuery() {36return id(new ManiphestTaskQuery());37}3839protected function getObjectCreateTitleText($object) {40return pht('Create New Task');41}4243protected function getObjectEditTitleText($object) {44return pht('Edit Task: %s', $object->getTitle());45}4647protected function getObjectEditShortText($object) {48return $object->getMonogram();49}5051protected function getObjectCreateShortText() {52return pht('Create Task');53}5455protected function getObjectName() {56return pht('Task');57}5859protected function getEditorURI() {60return $this->getApplication()->getApplicationURI('task/edit/');61}6263protected function getCommentViewHeaderText($object) {64return pht('Weigh In');65}6667protected function getCommentViewButtonText($object) {68return pht('Set Sail for Adventure');69}7071protected function getObjectViewURI($object) {72return '/'.$object->getMonogram();73}7475protected function buildCustomEditFields($object) {76$status_map = $this->getTaskStatusMap($object);77$priority_map = $this->getTaskPriorityMap($object);7879$alias_map = ManiphestTaskPriority::getTaskPriorityAliasMap();8081if ($object->isClosed()) {82$default_status = ManiphestTaskStatus::getDefaultStatus();83} else {84$default_status = ManiphestTaskStatus::getDefaultClosedStatus();85}8687if ($object->getOwnerPHID()) {88$owner_value = array($object->getOwnerPHID());89} else {90$owner_value = array($this->getViewer()->getPHID());91}9293$column_documentation = pht(<<<EODOCS94You can use this transaction type to create a task into a particular workboard95column, or move an existing task between columns.9697The transaction value can be specified in several forms. Some are simpler but98less powerful, while others are more complex and more powerful.99100The simplest valid value is a single column PHID:101102```lang=json103"PHID-PCOL-1111"104```105106This will move the task into that column, or create the task into that column107if you are creating a new task. If the task is currently on the board, it will108be moved out of any exclusive columns. If the task is not currently on the109board, it will be added to the board.110111You can also perform multiple moves at the same time by passing a list of112PHIDs:113114```lang=json115["PHID-PCOL-2222", "PHID-PCOL-3333"]116```117118This is equivalent to performing each move individually.119120The most complex and most powerful form uses a dictionary to provide additional121information about the move, including an optional specific position within the122column.123124The target column should be identified as `columnPHID`, and you may select a125position by passing either `beforePHIDs` or `afterPHIDs`, specifying the PHIDs126of tasks currently in the column that you want to move this task before or127after:128129```lang=json130[131{132"columnPHID": "PHID-PCOL-4444",133"beforePHIDs": ["PHID-TASK-5555"]134}135]136```137138When you specify multiple PHIDs, the task will be moved adjacent to the first139valid PHID found in either of the lists. This allows positional moves to140generally work as users expect even if the client view of the board has fallen141out of date and some of the nearby tasks have moved elsewhere.142EODOCS143);144145$column_map = $this->getColumnMap($object);146147$fields = array(148id(new PhabricatorHandlesEditField())149->setKey('parent')150->setLabel(pht('Parent Task'))151->setDescription(pht('Task to make this a subtask of.'))152->setConduitDescription(pht('Create as a subtask of another task.'))153->setConduitTypeDescription(pht('PHID of the parent task.'))154->setAliases(array('parentPHID'))155->setTransactionType(ManiphestTaskParentTransaction::TRANSACTIONTYPE)156->setHandleParameterType(new ManiphestTaskListHTTPParameterType())157->setSingleValue(null)158->setIsReorderable(false)159->setIsDefaultable(false)160->setIsLockable(false),161id(new PhabricatorColumnsEditField())162->setKey('column')163->setLabel(pht('Column'))164->setDescription(pht('Create a task in a workboard column.'))165->setConduitDescription(166pht('Move a task to one or more workboard columns.'))167->setConduitTypeDescription(168pht('List of columns to move the task to.'))169->setConduitDocumentation($column_documentation)170->setAliases(array('columnPHID', 'columns', 'columnPHIDs'))171->setTransactionType(PhabricatorTransactions::TYPE_COLUMNS)172->setIsReorderable(false)173->setIsDefaultable(false)174->setIsLockable(false)175->setCommentActionLabel(pht('Move on Workboard'))176->setCommentActionOrder(2000)177->setColumnMap($column_map),178id(new PhabricatorTextEditField())179->setKey('title')180->setLabel(pht('Title'))181->setBulkEditLabel(pht('Set title to'))182->setDescription(pht('Name of the task.'))183->setConduitDescription(pht('Rename the task.'))184->setConduitTypeDescription(pht('New task name.'))185->setTransactionType(ManiphestTaskTitleTransaction::TRANSACTIONTYPE)186->setIsRequired(true)187->setValue($object->getTitle()),188id(new PhabricatorUsersEditField())189->setKey('owner')190->setAliases(array('ownerPHID', 'assign', 'assigned'))191->setLabel(pht('Assigned To'))192->setBulkEditLabel(pht('Assign to'))193->setDescription(pht('User who is responsible for the task.'))194->setConduitDescription(pht('Reassign the task.'))195->setConduitTypeDescription(196pht('New task owner, or `null` to unassign.'))197->setTransactionType(ManiphestTaskOwnerTransaction::TRANSACTIONTYPE)198->setIsCopyable(true)199->setIsNullable(true)200->setSingleValue($object->getOwnerPHID())201->setCommentActionLabel(pht('Assign / Claim'))202->setCommentActionValue($owner_value),203id(new PhabricatorSelectEditField())204->setKey('status')205->setLabel(pht('Status'))206->setBulkEditLabel(pht('Set status to'))207->setDescription(pht('Status of the task.'))208->setConduitDescription(pht('Change the task status.'))209->setConduitTypeDescription(pht('New task status constant.'))210->setTransactionType(ManiphestTaskStatusTransaction::TRANSACTIONTYPE)211->setIsCopyable(true)212->setValue($object->getStatus())213->setOptions($status_map)214->setCommentActionLabel(pht('Change Status'))215->setCommentActionValue($default_status),216id(new PhabricatorSelectEditField())217->setKey('priority')218->setLabel(pht('Priority'))219->setBulkEditLabel(pht('Set priority to'))220->setDescription(pht('Priority of the task.'))221->setConduitDescription(pht('Change the priority of the task.'))222->setConduitTypeDescription(pht('New task priority constant.'))223->setTransactionType(ManiphestTaskPriorityTransaction::TRANSACTIONTYPE)224->setIsCopyable(true)225->setValue($object->getPriorityKeyword())226->setOptions($priority_map)227->setOptionAliases($alias_map)228->setCommentActionLabel(pht('Change Priority')),229);230231if (ManiphestTaskPoints::getIsEnabled()) {232$points_label = ManiphestTaskPoints::getPointsLabel();233$action_label = ManiphestTaskPoints::getPointsActionLabel();234235$fields[] = id(new PhabricatorPointsEditField())236->setKey('points')237->setLabel($points_label)238->setBulkEditLabel($action_label)239->setDescription(pht('Point value of the task.'))240->setConduitDescription(pht('Change the task point value.'))241->setConduitTypeDescription(pht('New task point value.'))242->setTransactionType(ManiphestTaskPointsTransaction::TRANSACTIONTYPE)243->setIsCopyable(true)244->setValue($object->getPoints())245->setCommentActionLabel($action_label);246}247248$fields[] = id(new PhabricatorRemarkupEditField())249->setKey('description')250->setLabel(pht('Description'))251->setBulkEditLabel(pht('Set description to'))252->setDescription(pht('Task description.'))253->setConduitDescription(pht('Update the task description.'))254->setConduitTypeDescription(pht('New task description.'))255->setTransactionType(ManiphestTaskDescriptionTransaction::TRANSACTIONTYPE)256->setValue($object->getDescription())257->setPreviewPanel(258id(new PHUIRemarkupPreviewPanel())259->setHeader(pht('Description Preview')));260261$parent_type = ManiphestTaskDependedOnByTaskEdgeType::EDGECONST;262$subtask_type = ManiphestTaskDependsOnTaskEdgeType::EDGECONST;263$commit_type = ManiphestTaskHasCommitEdgeType::EDGECONST;264265$src_phid = $object->getPHID();266if ($src_phid) {267$edge_query = id(new PhabricatorEdgeQuery())268->withSourcePHIDs(array($src_phid))269->withEdgeTypes(270array(271$parent_type,272$subtask_type,273$commit_type,274));275$edge_query->execute();276277$parent_phids = $edge_query->getDestinationPHIDs(278array($src_phid),279array($parent_type));280281$subtask_phids = $edge_query->getDestinationPHIDs(282array($src_phid),283array($subtask_type));284285$commit_phids = $edge_query->getDestinationPHIDs(286array($src_phid),287array($commit_type));288} else {289$parent_phids = array();290$subtask_phids = array();291$commit_phids = array();292}293294$fields[] = id(new PhabricatorHandlesEditField())295->setKey('parents')296->setLabel(pht('Parents'))297->setDescription(pht('Parent tasks.'))298->setConduitDescription(pht('Change the parents of this task.'))299->setConduitTypeDescription(pht('List of parent task PHIDs.'))300->setUseEdgeTransactions(true)301->setIsFormField(false)302->setTransactionType(PhabricatorTransactions::TYPE_EDGE)303->setMetadataValue('edge:type', $parent_type)304->setValue($parent_phids);305306$fields[] = id(new PhabricatorHandlesEditField())307->setKey('subtasks')308->setLabel(pht('Subtasks'))309->setDescription(pht('Subtasks.'))310->setConduitDescription(pht('Change the subtasks of this task.'))311->setConduitTypeDescription(pht('List of subtask PHIDs.'))312->setUseEdgeTransactions(true)313->setIsFormField(false)314->setTransactionType(PhabricatorTransactions::TYPE_EDGE)315->setMetadataValue('edge:type', $subtask_type)316->setValue($subtask_phids);317318$fields[] = id(new PhabricatorHandlesEditField())319->setKey('commits')320->setLabel(pht('Commits'))321->setDescription(pht('Related commits.'))322->setConduitDescription(pht('Change the related commits for this task.'))323->setConduitTypeDescription(pht('List of related commit PHIDs.'))324->setUseEdgeTransactions(true)325->setIsFormField(false)326->setTransactionType(PhabricatorTransactions::TYPE_EDGE)327->setMetadataValue('edge:type', $commit_type)328->setValue($commit_phids);329330return $fields;331}332333private function getTaskStatusMap(ManiphestTask $task) {334$status_map = ManiphestTaskStatus::getTaskStatusMap();335336$current_status = $task->getStatus();337338// If the current status is something we don't recognize (maybe an older339// status which was deleted), put a dummy entry in the status map so that340// saving the form doesn't destroy any data by accident.341if (idx($status_map, $current_status) === null) {342$status_map[$current_status] = pht('<Unknown: %s>', $current_status);343}344345$dup_status = ManiphestTaskStatus::getDuplicateStatus();346foreach ($status_map as $status => $status_name) {347// Always keep the task's current status.348if ($status == $current_status) {349continue;350}351352// Don't allow tasks to be changed directly into "Closed, Duplicate"353// status. Instead, you have to merge them. See T4819.354if ($status == $dup_status) {355unset($status_map[$status]);356continue;357}358359// Don't let new or existing tasks be moved into a disabled status.360if (ManiphestTaskStatus::isDisabledStatus($status)) {361unset($status_map[$status]);362continue;363}364}365366return $status_map;367}368369private function getTaskPriorityMap(ManiphestTask $task) {370$priority_map = ManiphestTaskPriority::getTaskPriorityMap();371$priority_keywords = ManiphestTaskPriority::getTaskPriorityKeywordsMap();372$current_priority = $task->getPriority();373$results = array();374375foreach ($priority_map as $priority => $priority_name) {376$disabled = ManiphestTaskPriority::isDisabledPriority($priority);377if ($disabled && !($priority == $current_priority)) {378continue;379}380381$keyword = head(idx($priority_keywords, $priority));382$results[$keyword] = $priority_name;383}384385// If the current value isn't a legitimate one, put it in the dropdown386// anyway so saving the form doesn't cause any side effects.387if (idx($priority_map, $current_priority) === null) {388$results[ManiphestTaskPriority::UNKNOWN_PRIORITY_KEYWORD] = pht(389'<Unknown: %s>',390$current_priority);391}392393return $results;394}395396protected function newEditResponse(397AphrontRequest $request,398$object,399array $xactions) {400401$response_type = $request->getStr('responseType');402$is_card = ($response_type === 'card');403404if ($is_card) {405// Reload the task to make sure we pick up the final task state.406$viewer = $this->getViewer();407$task = id(new ManiphestTaskQuery())408->setViewer($viewer)409->withIDs(array($object->getID()))410->needSubscriberPHIDs(true)411->needProjectPHIDs(true)412->executeOne();413414return $this->buildCardResponse($task);415}416417return parent::newEditResponse($request, $object, $xactions);418}419420private function buildCardResponse(ManiphestTask $task) {421$controller = $this->getController();422$request = $controller->getRequest();423$viewer = $request->getViewer();424425$column_phid = $request->getStr('columnPHID');426427$visible_phids = $request->getStrList('visiblePHIDs');428if (!$visible_phids) {429$visible_phids = array();430}431432$column = id(new PhabricatorProjectColumnQuery())433->setViewer($viewer)434->withPHIDs(array($column_phid))435->executeOne();436if (!$column) {437return new Aphront404Response();438}439440$board_phid = $column->getProjectPHID();441$object_phid = $task->getPHID();442443$order = $request->getStr('order');444if ($order) {445$ordering = PhabricatorProjectColumnOrder::getOrderByKey($order);446$ordering = id(clone $ordering)447->setViewer($viewer);448} else {449$ordering = null;450}451452$engine = id(new PhabricatorBoardResponseEngine())453->setViewer($viewer)454->setBoardPHID($board_phid)455->setUpdatePHIDs(array($object_phid))456->setVisiblePHIDs($visible_phids);457458if ($ordering) {459$engine->setOrdering($ordering);460}461462return $engine->buildResponse();463}464465private function getColumnMap(ManiphestTask $task) {466$phid = $task->getPHID();467if (!$phid) {468return array();469}470471$board_phids = PhabricatorEdgeQuery::loadDestinationPHIDs(472$phid,473PhabricatorProjectObjectHasProjectEdgeType::EDGECONST);474if (!$board_phids) {475return array();476}477478$viewer = $this->getViewer();479480$layout_engine = id(new PhabricatorBoardLayoutEngine())481->setViewer($viewer)482->setBoardPHIDs($board_phids)483->setObjectPHIDs(array($task->getPHID()))484->executeLayout();485486$map = array();487foreach ($board_phids as $board_phid) {488$in_columns = $layout_engine->getObjectColumns($board_phid, $phid);489$in_columns = mpull($in_columns, null, 'getPHID');490491$all_columns = $layout_engine->getColumns($board_phid);492if (!$all_columns) {493// This could be a project with no workboard, or a project the viewer494// does not have permission to see.495continue;496}497498$board = head($all_columns)->getProject();499500$options = array();501foreach ($all_columns as $column) {502$name = $column->getDisplayName();503504$is_hidden = $column->isHidden();505$is_selected = isset($in_columns[$column->getPHID()]);506507// Don't show hidden, subproject or milestone columns in this map508// unless the object is currently in the column.509$skip_column = ($is_hidden || $column->getProxyPHID());510if ($skip_column) {511if (!$is_selected) {512continue;513}514}515516if ($is_hidden) {517$name = pht('(%s)', $name);518}519520if ($is_selected) {521$name = pht("\xE2\x97\x8F %s", $name);522} else {523$name = pht("\xE2\x97\x8B %s", $name);524}525526$option = array(527'key' => $column->getPHID(),528'label' => $name,529'selected' => (bool)$is_selected,530);531532$options[] = $option;533}534535$map[] = array(536'label' => $board->getDisplayName(),537'options' => $options,538);539}540541$map = isort($map, 'label');542$map = array_values($map);543544return $map;545}546547548}549550551