diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index d125ecc233..8f054b68b6 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -120,6 +120,7 @@ phutil_register_library_map(array( 'AphrontFormDateControlValue' => 'view/form/control/AphrontFormDateControlValue.php', 'AphrontFormDividerControl' => 'view/form/control/AphrontFormDividerControl.php', 'AphrontFormFileControl' => 'view/form/control/AphrontFormFileControl.php', + 'AphrontFormHandlesControl' => 'view/form/control/AphrontFormHandlesControl.php', 'AphrontFormMarkupControl' => 'view/form/control/AphrontFormMarkupControl.php', 'AphrontFormPasswordControl' => 'view/form/control/AphrontFormPasswordControl.php', 'AphrontFormPolicyControl' => 'view/form/control/AphrontFormPolicyControl.php', @@ -1310,9 +1311,11 @@ phutil_register_library_map(array( 'ManiphestTaskHeraldField' => 'applications/maniphest/herald/ManiphestTaskHeraldField.php', 'ManiphestTaskHeraldFieldGroup' => 'applications/maniphest/herald/ManiphestTaskHeraldFieldGroup.php', 'ManiphestTaskListController' => 'applications/maniphest/controller/ManiphestTaskListController.php', + 'ManiphestTaskListHTTPParameterType' => 'applications/maniphest/httpparametertype/ManiphestTaskListHTTPParameterType.php', 'ManiphestTaskListView' => 'applications/maniphest/view/ManiphestTaskListView.php', 'ManiphestTaskMailReceiver' => 'applications/maniphest/mail/ManiphestTaskMailReceiver.php', 'ManiphestTaskOpenStatusDatasource' => 'applications/maniphest/typeahead/ManiphestTaskOpenStatusDatasource.php', + 'ManiphestTaskPHIDResolver' => 'applications/maniphest/httpparametertype/ManiphestTaskPHIDResolver.php', 'ManiphestTaskPHIDType' => 'applications/maniphest/phid/ManiphestTaskPHIDType.php', 'ManiphestTaskPriority' => 'applications/maniphest/constants/ManiphestTaskPriority.php', 'ManiphestTaskPriorityDatasource' => 'applications/maniphest/typeahead/ManiphestTaskPriorityDatasource.php', @@ -2306,6 +2309,7 @@ phutil_register_library_map(array( 'PhabricatorHandlePool' => 'applications/phid/handle/pool/PhabricatorHandlePool.php', 'PhabricatorHandlePoolTestCase' => 'applications/phid/handle/pool/__tests__/PhabricatorHandlePoolTestCase.php', 'PhabricatorHandleQuery' => 'applications/phid/query/PhabricatorHandleQuery.php', + 'PhabricatorHandlesEditField' => 'applications/transactions/editfield/PhabricatorHandlesEditField.php', 'PhabricatorHarbormasterApplication' => 'applications/harbormaster/application/PhabricatorHarbormasterApplication.php', 'PhabricatorHarbormasterConfigOptions' => 'applications/harbormaster/config/PhabricatorHarbormasterConfigOptions.php', 'PhabricatorHash' => 'infrastructure/util/PhabricatorHash.php', @@ -3941,6 +3945,7 @@ phutil_register_library_map(array( 'AphrontFormDateControlValue' => 'Phobject', 'AphrontFormDividerControl' => 'AphrontFormControl', 'AphrontFormFileControl' => 'AphrontFormControl', + 'AphrontFormHandlesControl' => 'AphrontFormControl', 'AphrontFormMarkupControl' => 'AphrontFormControl', 'AphrontFormPasswordControl' => 'AphrontFormControl', 'AphrontFormPolicyControl' => 'AphrontFormControl', @@ -5308,9 +5313,11 @@ phutil_register_library_map(array( 'ManiphestTaskHeraldField' => 'HeraldField', 'ManiphestTaskHeraldFieldGroup' => 'HeraldFieldGroup', 'ManiphestTaskListController' => 'ManiphestController', + 'ManiphestTaskListHTTPParameterType' => 'AphrontListHTTPParameterType', 'ManiphestTaskListView' => 'ManiphestView', 'ManiphestTaskMailReceiver' => 'PhabricatorObjectMailReceiver', 'ManiphestTaskOpenStatusDatasource' => 'PhabricatorTypeaheadDatasource', + 'ManiphestTaskPHIDResolver' => 'PhabricatorPHIDResolver', 'ManiphestTaskPHIDType' => 'PhabricatorPHIDType', 'ManiphestTaskPriority' => 'ManiphestConstants', 'ManiphestTaskPriorityDatasource' => 'PhabricatorTypeaheadDatasource', @@ -6471,6 +6478,7 @@ phutil_register_library_map(array( 'PhabricatorHandlePool' => 'Phobject', 'PhabricatorHandlePoolTestCase' => 'PhabricatorTestCase', 'PhabricatorHandleQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'PhabricatorHandlesEditField' => 'PhabricatorPHIDListEditField', 'PhabricatorHarbormasterApplication' => 'PhabricatorApplication', 'PhabricatorHarbormasterConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorHash' => 'Phobject', diff --git a/src/applications/maniphest/controller/ManiphestTaskDetailController.php b/src/applications/maniphest/controller/ManiphestTaskDetailController.php index 4bd4d70430..a34c9f5df3 100644 --- a/src/applications/maniphest/controller/ManiphestTaskDetailController.php +++ b/src/applications/maniphest/controller/ManiphestTaskDetailController.php @@ -134,8 +134,6 @@ final class ManiphestTaskDetailController extends ManiphestController { $task, PhabricatorPolicyCapability::CAN_EDIT); - $can_create = $viewer->isLoggedIn(); - $view = id(new PhabricatorActionListView()) ->setUser($viewer) ->setObject($task) @@ -158,10 +156,26 @@ final class ManiphestTaskDetailController extends ManiphestController { ->setDisabled(!$can_edit) ->setWorkflow(true)); + $edit_config = id(new ManiphestEditEngine()) + ->setViewer($viewer) + ->loadDefaultEditConfiguration(); + + $can_create = (bool)$edit_config; + if ($can_create) { + $form_key = $edit_config->getIdentifier(); + $edit_uri = "/editpro/form/{$form_key}/?parent={$id}&template={$id}"; + $edit_uri = $this->getApplicationURI($edit_uri); + } else { + // TODO: This will usually give us a somewhat-reasonable error page, but + // could be a bit cleaner. + $edit_uri = "/editpro/{$id}/"; + $edit_uri = $this->getApplicationURI($edit_uri); + } + $view->addAction( id(new PhabricatorActionView()) ->setName(pht('Create Subtask')) - ->setHref($this->getApplicationURI("/task/create/?parent={$id}")) + ->setHref($edit_uri) ->setIcon('fa-level-down') ->setDisabled(!$can_create) ->setWorkflow(!$can_create)); diff --git a/src/applications/maniphest/editor/ManiphestEditEngine.php b/src/applications/maniphest/editor/ManiphestEditEngine.php index a872c52dfa..2a48ce29d4 100644 --- a/src/applications/maniphest/editor/ManiphestEditEngine.php +++ b/src/applications/maniphest/editor/ManiphestEditEngine.php @@ -69,6 +69,13 @@ final class ManiphestEditEngine } return array( + id(new PhabricatorHandlesEditField()) + ->setKey('parent') + ->setLabel(pht('Parent Task')) + ->setDescription(pht('Task to make this a subtask of.')) + ->setAliases(array('parentPHID')) + ->setTransactionType(ManiphestTransaction::TYPE_PARENT) + ->setSingleValue(null), id(new PhabricatorTextEditField()) ->setKey('title') ->setLabel(pht('Title')) diff --git a/src/applications/maniphest/editor/ManiphestTransactionEditor.php b/src/applications/maniphest/editor/ManiphestTransactionEditor.php index f014e13083..fc05c3dff3 100644 --- a/src/applications/maniphest/editor/ManiphestTransactionEditor.php +++ b/src/applications/maniphest/editor/ManiphestTransactionEditor.php @@ -26,6 +26,7 @@ final class ManiphestTransactionEditor $types[] = ManiphestTransaction::TYPE_MERGED_INTO; $types[] = ManiphestTransaction::TYPE_MERGED_FROM; $types[] = ManiphestTransaction::TYPE_UNBLOCK; + $types[] = ManiphestTransaction::TYPE_PARENT; $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; @@ -67,6 +68,8 @@ final class ManiphestTransactionEditor case ManiphestTransaction::TYPE_MERGED_INTO: case ManiphestTransaction::TYPE_MERGED_FROM: return null; + case ManiphestTransaction::TYPE_PARENT: + return null; } } @@ -88,6 +91,8 @@ final class ManiphestTransactionEditor case ManiphestTransaction::TYPE_MERGED_FROM: case ManiphestTransaction::TYPE_UNBLOCK: return $xaction->getNewValue(); + case ManiphestTransaction::TYPE_PARENT: + return $xaction->getNewValue(); } } @@ -155,6 +160,8 @@ final class ManiphestTransactionEditor return; case ManiphestTransaction::TYPE_MERGED_FROM: return; + case ManiphestTransaction::TYPE_PARENT: + return; } } @@ -163,6 +170,15 @@ final class ManiphestTransactionEditor PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { + case ManiphestTransaction::TYPE_PARENT: + $parent_phid = $xaction->getNewValue(); + $parent_type = ManiphestTaskDependsOnTaskEdgeType::EDGECONST; + $task_phid = $object->getPHID(); + + id(new PhabricatorEdgeEditor()) + ->addEdge($parent_phid, $parent_type, $task_phid) + ->save(); + break; case ManiphestTransaction::TYPE_PROJECT_COLUMN: $board_phid = idx($xaction->getNewValue(), 'projectPHID'); if (!$board_phid) { @@ -740,6 +756,17 @@ final class ManiphestTransactionEditor $errors[] = $error; } break; + case ManiphestTransaction::TYPE_PARENT: + if ($xactions && !$this->getIsNewObject()) { + $error = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Invalid'), + pht( + 'You can only select a parent task when creating a '. + 'transaction for the first time.'), + last($xactions)); + } + break; } return $errors; diff --git a/src/applications/maniphest/httpparametertype/ManiphestTaskListHTTPParameterType.php b/src/applications/maniphest/httpparametertype/ManiphestTaskListHTTPParameterType.php new file mode 100644 index 0000000000..d9e935ef43 --- /dev/null +++ b/src/applications/maniphest/httpparametertype/ManiphestTaskListHTTPParameterType.php @@ -0,0 +1,47 @@ +getValueWithType($type, $request, $key); + + return id(new ManiphestTaskPHIDResolver()) + ->setViewer($this->getViewer()) + ->resolvePHIDs($list); + } + + protected function getParameterTypeName() { + return 'list'; + } + + protected function getParameterFormatDescriptions() { + return array( + pht('Comma-separated list of task PHIDs.'), + pht('List of task PHIDs, as array.'), + pht('Comma-separated list of task IDs.'), + pht('List of task IDs, as array.'), + pht('Comma-separated list of task monograms.'), + pht('List of task monograms, as array.'), + pht('Mixture of PHIDs, IDs and monograms.'), + ); + } + + protected function getParameterExamples() { + return array( + 'v=PHID-TASK-1111', + 'v=PHID-TASK-1111,PHID-TASK-2222', + 'v[]=PHID-TASK-1111&v[]=PHID-TASK-2222', + 'v=123', + 'v=123,124', + 'v[]=123&v[]=124', + 'v=T123', + 'v=T123,T124', + 'v[]=T123&v[]=T124', + 'v=PHID-TASK-1111,123,T124', + 'v[]=PHID-TASK-1111&v[]=123&v[]=T124', + ); + } + +} diff --git a/src/applications/maniphest/httpparametertype/ManiphestTaskPHIDResolver.php b/src/applications/maniphest/httpparametertype/ManiphestTaskPHIDResolver.php new file mode 100644 index 0000000000..d9a4e5b82e --- /dev/null +++ b/src/applications/maniphest/httpparametertype/ManiphestTaskPHIDResolver.php @@ -0,0 +1,30 @@ + $name) { + if (ctype_digit($name)) { + $names[$key] = 'T'.$name; + } + } + + $query = id(new PhabricatorObjectQuery()) + ->setViewer($this->getViewer()); + + $tasks = id(new ManiphestTaskPHIDType()) + ->loadNamedObjects($query, $names); + + + $results = array(); + foreach ($tasks as $task) { + $task_phid = $task->getPHID(); + $results[$task->getID()] = $task_phid; + $results[$task->getMonogram()] = $task_phid; + } + + return $results; + } + +} diff --git a/src/applications/maniphest/storage/ManiphestTransaction.php b/src/applications/maniphest/storage/ManiphestTransaction.php index 024a292337..7d184a9b59 100644 --- a/src/applications/maniphest/storage/ManiphestTransaction.php +++ b/src/applications/maniphest/storage/ManiphestTransaction.php @@ -14,6 +14,7 @@ final class ManiphestTransaction const TYPE_MERGED_INTO = 'mergedinto'; const TYPE_MERGED_FROM = 'mergedfrom'; const TYPE_UNBLOCK = 'unblock'; + const TYPE_PARENT = 'parent'; // NOTE: this type is deprecated. Keep it around for legacy installs // so any transactions render correctly. @@ -147,6 +148,7 @@ final class ManiphestTransaction } break; case self::TYPE_SUBPRIORITY: + case self::TYPE_PARENT: return true; case self::TYPE_PROJECT_COLUMN: $old_cols = idx($this->getOldValue(), 'columnPHIDs'); diff --git a/src/applications/transactions/editengine/PhabricatorEditEngine.php b/src/applications/transactions/editengine/PhabricatorEditEngine.php index 6155787f1a..dd82363d6f 100644 --- a/src/applications/transactions/editengine/PhabricatorEditEngine.php +++ b/src/applications/transactions/editengine/PhabricatorEditEngine.php @@ -252,7 +252,7 @@ abstract class PhabricatorEditEngine 'getCreateSortKey'); } - private function loadDefaultEditConfiguration() { + public function loadDefaultEditConfiguration() { $query = $this->newConfigurationQuery() ->withIsEdit(true) ->withIsDisabled(false); diff --git a/src/applications/transactions/editfield/PhabricatorHandlesEditField.php b/src/applications/transactions/editfield/PhabricatorHandlesEditField.php new file mode 100644 index 0000000000..cc770cd011 --- /dev/null +++ b/src/applications/transactions/editfield/PhabricatorHandlesEditField.php @@ -0,0 +1,14 @@ +getIsSingleValue()) { + $value = array_slice($value, 0, 1); + } + return $value; + } + public function getValueForTransaction() { $new = parent::getValueForTransaction(); diff --git a/src/view/form/control/AphrontFormHandlesControl.php b/src/view/form/control/AphrontFormHandlesControl.php new file mode 100644 index 0000000000..bd6fa1a16d --- /dev/null +++ b/src/view/form/control/AphrontFormHandlesControl.php @@ -0,0 +1,36 @@ +getValue(); + } + + protected function renderInput() { + $value = $this->getValue(); + $viewer = $this->getUser(); + + $list = $viewer->renderHandleList($value); + $list = id(new PHUIBoxView()) + ->addPadding(PHUI::PADDING_SMALL_TOP) + ->appendChild($list); + + $inputs = array(); + foreach ($value as $phid) { + $inputs[] = phutil_tag( + 'input', + array( + 'type' => 'hidden', + 'name' => $this->getName().'[]', + 'value' => $phid, + )); + } + + return array($list, $inputs); + } + +}