Christopher Johnson (WMDE) has uploaded a new change for review. https://gerrit.wikimedia.org/r/261185
Change subject: fixes several upstream breaking changes current with 10ed33052361be82cbc09884118d65ec0601bd55 27.12.2015 Bug: T122486 ...................................................................... fixes several upstream breaking changes current with 10ed33052361be82cbc09884118d65ec0601bd55 27.12.2015 Bug: T122486 Change-Id: I418d643a0949599ad507071fb14ffb3ff89bf102 --- M src/__phutil_library_map__.php M src/application/SprintApplication.php M src/controller/SprintController.php A src/controller/SprintProjectController.php M src/controller/SprintProjectProfileController.php M src/controller/board/SprintBoardTaskEditController.php A src/controller/board/SprintManiphestEditEngine.php 7 files changed, 654 insertions(+), 813 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/phabricator/extensions/Sprint refs/changes/85/261185/1 diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 84de703..3fa5040 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -61,7 +61,9 @@ 'SprintListController' => 'controller/SprintListController.php', 'SprintListDataProvider' => 'storage/SprintListDataProvider.php', 'SprintListTableView' => 'view/SprintListTableView.php', + 'SprintManiphestEditEngine' => 'controller/board/SprintManiphestEditEngine.php', 'SprintPoints' => 'util/SprintPoints.php', + 'SprintProjectController' => 'controller/SprintProjectController.php', 'SprintProjectCustomField' => 'customfield/SprintProjectCustomField.php', 'SprintProjectProfileController' => 'controller/SprintProjectProfileController.php', 'SprintProjectViewController' => 'controller/SprintProjectViewController.php', @@ -131,12 +133,14 @@ 'SprintIsSprintField' => 'SprintProjectCustomField', 'SprintListController' => 'SprintController', 'SprintListTableView' => 'Phobject', + 'SprintManiphestEditEngine' => 'PhabricatorEditEngine', 'SprintPoints' => 'Phobject', + 'SprintProjectController' => 'SprintController', 'SprintProjectCustomField' => array( 'PhabricatorProjectCustomField', 'PhabricatorStandardCustomFieldInterface', ), - 'SprintProjectProfileController' => 'SprintController', + 'SprintProjectProfileController' => 'SprintProjectController', 'SprintProjectViewController' => 'SprintController', 'SprintQuery' => 'SprintDAO', 'SprintQueryTest' => 'SprintTestCase', diff --git a/src/application/SprintApplication.php b/src/application/SprintApplication.php index 336bb86..6d7a58b 100644 --- a/src/application/SprintApplication.php +++ b/src/application/SprintApplication.php @@ -72,8 +72,8 @@ // all routes following point to default controllers 'archive/(?P<id>[1-9]\d*)/' => 'PhabricatorProjectArchiveController', - 'details/(?P<id>[1-9]\d*)/' - => 'PhabricatorProjectEditDetailsController', + $this->getEditRoutePattern('edit/') + => 'PhabricatorProjectEditController', 'feed/(?P<id>[1-9]\d*)/' => 'PhabricatorProjectFeedController', 'icon/(?P<id>[1-9]\d*)/' diff --git a/src/controller/SprintController.php b/src/controller/SprintController.php index 143fc2f..e202a3f 100644 --- a/src/controller/SprintController.php +++ b/src/controller/SprintController.php @@ -24,7 +24,7 @@ } public function buildApplicationMenu() { - return $this->buildSideNavView($this->getUser(), + return $this->buildSprintNavView($this->getUser(), $this->setApplicationURI(), true)->getMenu(); } @@ -45,7 +45,7 @@ /** * @param PhutilURI $uri */ - public function buildSideNavView($viewer, $uri, $for_app = false) { + public function buildSprintNavView($viewer, $uri, $for_app = false) { $request = $this->getRequest(); $id = $request->getURIData('id'); $slug = $request->getURIData('slug'); diff --git a/src/controller/SprintProjectController.php b/src/controller/SprintProjectController.php new file mode 100644 index 0000000..a3f628f --- /dev/null +++ b/src/controller/SprintProjectController.php @@ -0,0 +1,181 @@ +<?php + +abstract class SprintProjectController extends SprintController { + + private $project; + + protected function setProject(PhabricatorProject $project) { + $this->project = $project; + return $this; + } + + protected function getProject() { + return $this->project; + } + + protected function loadProject() { + $viewer = $this->getViewer(); + $request = $this->getRequest(); + + $id = $request->getURIData('id'); + $slug = $request->getURIData('slug'); + + if ($slug) { + $normal_slug = PhabricatorSlug::normalizeProjectSlug($slug); + $is_abnormal = ($slug !== $normal_slug); + $normal_uri = "/tag/{$normal_slug}/"; + } else { + $is_abnormal = false; + } + + $query = id(new PhabricatorProjectQuery()) + ->setViewer($viewer) + ->needMembers(true) + ->needWatchers(true) + ->needImages(true) + ->needSlugs(true); + + if ($slug) { + $query->withSlugs(array($slug)); + } else { + $query->withIDs(array($id)); + } + + $policy_exception = null; + try { + $project = $query->executeOne(); + } catch (PhabricatorPolicyException $ex) { + $policy_exception = $ex; + $project = null; + } + + if (!$project) { + // This project legitimately does not exist, so just 404 the user. + if (!$policy_exception) { + return new Aphront404Response(); + } + + // Here, the project exists but the user can't see it. If they are + // using a non-canonical slug to view the project, redirect to the + // canonical slug. If they're already using the canonical slug, rethrow + // the exception to give them the policy error. + if ($is_abnormal) { + return id(new AphrontRedirectResponse())->setURI($normal_uri); + } else { + throw $policy_exception; + } + } + + // The user can view the project, but is using a noncanonical slug. + // Redirect to the canonical slug. + $primary_slug = $project->getPrimarySlug(); + if ($slug && ($slug !== $primary_slug)) { + $primary_uri = "/tag/{$primary_slug}/"; + return id(new AphrontRedirectResponse())->setURI($primary_uri); + } + + $this->setProject($project); + + return null; + } + + public function buildApplicationMenu() { + return $this->buildSideNavView(true)->getMenu(); + } + + public function buildSideNavView($for_app = false) { + $project = $this->getProject(); + + $nav = new AphrontSideNavFilterView(); + $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); + + $viewer = $this->getViewer(); + + $id = null; + if ($for_app) { + if ($project) { + $id = $project->getID(); + $nav->addFilter("profile/{$id}/", pht('Profile')); + $nav->addFilter("board/{$id}/", pht('Workboard')); + $nav->addFilter("members/{$id}/", pht('Members')); + $nav->addFilter("feed/{$id}/", pht('Feed')); + $nav->addFilter("details/{$id}/", pht('Edit Details')); + } + $nav->addFilter('create', pht('Create Project')); + } + + if (!$id) { + id(new PhabricatorProjectSearchEngine()) + ->setViewer($viewer) + ->addNavigationItems($nav->getMenu()); + } + + $nav->selectFilter(null); + + return $nav; + } + + public function buildIconNavView(PhabricatorProject $project) { + $this->setProject($project); + $viewer = $this->getViewer(); + $id = $project->getID(); + $picture = $project->getProfileImageURI(); + $name = $project->getName(); + + $columns = id(new PhabricatorProjectColumnQuery()) + ->setViewer($viewer) + ->withProjectPHIDs(array($project->getPHID())) + ->execute(); + if ($columns) { + $board_icon = 'fa-columns'; + } else { + $board_icon = 'fa-columns grey'; + } + + $nav = new AphrontSideNavFilterView(); + $nav->setIconNav(true); + $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); + $nav->addIcon("profile/{$id}/", $name, null, $picture); + + $class = 'PhabricatorManiphestApplication'; + if (PhabricatorApplication::isClassInstalledForViewer($class, $viewer)) { + $phid = $project->getPHID(); + $nav->addIcon("board/{$id}/", pht('Workboard'), $board_icon); + $query_uri = urisprintf( + '/maniphest/?statuses=open()&projects=%s#R', + $phid); + $nav->addIcon(null, pht('Open Tasks'), 'fa-anchor', null, $query_uri); + } + + $nav->addIcon("feed/{$id}/", pht('Feed'), 'fa-newspaper-o'); + $nav->addIcon("members/{$id}/", pht('Members'), 'fa-group'); + $nav->addIcon("details/{$id}/", pht('Edit Details'), 'fa-pencil'); + + if (PhabricatorEnv::getEnvConfig('phabricator.show-prototypes')) { + $nav->addIcon("subprojects/{$id}/", pht('Subprojects'), 'fa-sitemap'); + $nav->addIcon("milestones/{$id}/", pht('Milestones'), 'fa-map-marker'); + } + + + return $nav; + } + + protected function buildApplicationCrumbs() { + $crumbs = parent::buildApplicationCrumbs(); + + $project = $this->getProject(); + if ($project) { + $ancestors = $project->getAncestorProjects(); + $ancestors = array_reverse($ancestors); + $ancestors[] = $project; + foreach ($ancestors as $ancestor) { + $crumbs->addTextCrumb( + $project->getName(), + $project->getURI()); + } + } + + return $crumbs; + } + +} diff --git a/src/controller/SprintProjectProfileController.php b/src/controller/SprintProjectProfileController.php index 05f32a0..4bdd02e 100644 --- a/src/controller/SprintProjectProfileController.php +++ b/src/controller/SprintProjectProfileController.php @@ -1,7 +1,7 @@ <?php final class SprintProjectProfileController - extends SprintController { + extends SprintProjectController { public function shouldAllowPublic() { return true; @@ -10,27 +10,13 @@ public function handleRequest(AphrontRequest $request) { $viewer = $request->getViewer(); - $query = id(new PhabricatorProjectQuery()) - ->setViewer($viewer) - ->needMembers(true) - ->needWatchers(true) - ->needImages(true) - ->needSlugs(true); - $id = $request->getURIData('id'); - $slug = $request->getURIData('slug'); - if ($slug) { - $query->withSlugs(array($slug)); - } else { - $query->withIDs(array($id)); + $response = $this->loadProject(); + if ($response) { + return $response; } - $project = $query->executeOne(); - if (!$project) { - return new Aphront404Response(); - } - if ($slug && $slug != $project->getPrimarySlug()) { - return id(new AphrontRedirectResponse()) - ->setURI('/tag/'.$project->getPrimarySlug().'/'); - } + + $project = $this->getProject(); + $id = $project->getID(); $picture = $project->getProfileImageURI(); @@ -60,15 +46,15 @@ $nav = $this->buildIconNavView($project); $nav->selectFilter("profile/{$id}/"); - $nav->appendChild($object_box); - $nav->appendChild($timeline); + $crumbs = $this->buildApplicationCrumbs(); - return $this->buildApplicationPage( - $nav, - array( - 'title' => $project->getName(), - 'pageObjects' => array($project->getPHID()), - )); + return $this->newPage() + ->setNavigation($nav) + ->setCrumbs($crumbs) + ->setTitle($project->getName()) + ->setPageObjectPHIDs(array($project->getPHID())) + ->appendChild($object_box) + ->appendChild($timeline); } private function buildActionListView(PhabricatorProject $project) { @@ -79,8 +65,7 @@ $view = id(new PhabricatorActionListView()) ->setUser($viewer) - ->setObject($project) - ->setObjectURI($request->getRequestURI()); + ->setObject($project); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, @@ -91,7 +76,7 @@ id(new PhabricatorActionView()) ->setName(pht('Edit Details')) ->setIcon('fa-pencil') - ->setHref($this->getApplicationURI("details/{$id}/"))); + ->setHref($this->getApplicationURI("edit/{$id}/"))); $view->addAction( id(new PhabricatorActionView()) @@ -217,5 +202,70 @@ return $view; } + protected function loadProject() { + $viewer = $this->getViewer(); + $request = $this->getRequest(); + + $id = $request->getURIData('id'); + $slug = $request->getURIData('slug'); + + if ($slug) { + $normal_slug = PhabricatorSlug::normalizeProjectSlug($slug); + $is_abnormal = ($slug !== $normal_slug); + $normal_uri = "/tag/{$normal_slug}/"; + } else { + $is_abnormal = false; + } + + $query = id(new PhabricatorProjectQuery()) + ->setViewer($viewer) + ->needMembers(true) + ->needWatchers(true) + ->needImages(true) + ->needSlugs(true); + + if ($slug) { + $query->withSlugs(array($slug)); + } else { + $query->withIDs(array($id)); + } + + $policy_exception = null; + try { + $project = $query->executeOne(); + } catch (PhabricatorPolicyException $ex) { + $policy_exception = $ex; + $project = null; + } + + if (!$project) { + // This project legitimately does not exist, so just 404 the user. + if (!$policy_exception) { + return new Aphront404Response(); + } + + // Here, the project exists but the user can't see it. If they are + // using a non-canonical slug to view the project, redirect to the + // canonical slug. If they're already using the canonical slug, rethrow + // the exception to give them the policy error. + if ($is_abnormal) { + return id(new AphrontRedirectResponse())->setURI($normal_uri); + } else { + throw $policy_exception; + } + } + + // The user can view the project, but is using a noncanonical slug. + // Redirect to the canonical slug. + $primary_slug = $project->getPrimarySlug(); + if ($slug && ($slug !== $primary_slug)) { + $primary_uri = "/tag/{$primary_slug}/"; + return id(new AphrontRedirectResponse())->setURI($primary_uri); + } + + $this->setProject($project); + + return null; + } } diff --git a/src/controller/board/SprintBoardTaskEditController.php b/src/controller/board/SprintBoardTaskEditController.php index 60529d4..755e95f 100644 --- a/src/controller/board/SprintBoardTaskEditController.php +++ b/src/controller/board/SprintBoardTaskEditController.php @@ -3,782 +3,13 @@ final class SprintBoardTaskEditController extends ManiphestController { public function handleRequest(AphrontRequest $request) { - $viewer = $this->getViewer(); - $id = $request->getURIData('id'); - - $response_type = $request->getStr('responseType', 'task'); - $order = $request->getStr('order', PhabricatorProjectColumn::DEFAULT_ORDER); - - $can_edit_assign = $this->hasApplicationCapability( - ManiphestEditAssignCapability::CAPABILITY); - $can_edit_policies = $this->hasApplicationCapability( - ManiphestEditPoliciesCapability::CAPABILITY); - $can_edit_priority = $this->hasApplicationCapability( - ManiphestEditPriorityCapability::CAPABILITY); - $can_edit_projects = $this->hasApplicationCapability( - ManiphestEditProjectsCapability::CAPABILITY); - $can_edit_status = $this->hasApplicationCapability( - ManiphestEditStatusCapability::CAPABILITY); - $can_create_projects = PhabricatorPolicyFilter::hasCapability( - $viewer, - PhabricatorApplication::getByClass('PhabricatorProjectApplication'), - ProjectCreateProjectsCapability::CAPABILITY); - - $parent_task = null; - $template_id = null; - - if ($id) { - $task = id(new ManiphestTaskQuery()) - ->setViewer($viewer) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->withIDs(array($id)) - ->needSubscriberPHIDs(true) - ->needProjectPHIDs(true) - ->executeOne(); - if (!$task) { - return new Aphront404Response(); - } - } else { - $task = ManiphestTask::initializeNewTask($viewer); - - // We currently do not allow you to set the task status when creating - // a new task, although now that statuses are custom it might make - // sense. - $can_edit_status = false; - - // These allow task creation with defaults. - if (!$request->isFormPost()) { - $task->setTitle($request->getStr('title')); - - if ($can_edit_projects) { - $projects = $request->getStr('projects'); - if ($projects) { - $tokens = $request->getStrList('projects'); - - $type_project = PhabricatorProjectProjectPHIDType::TYPECONST; - foreach ($tokens as $key => $token) { - if (phid_get_type($token) == $type_project) { - // If this is formatted like a PHID, leave it as-is. - continue; - } - - if (preg_match('/^#/', $token)) { - // If this already has a "#", leave it as-is. - continue; - } - - // Add a "#" prefix. - $tokens[$key] = '#'.$token; - } - - $default_projects = id(new PhabricatorObjectQuery()) - ->setViewer($viewer) - ->withNames($tokens) - ->execute(); - $default_projects = mpull($default_projects, 'getPHID'); - - if ($default_projects) { - $task->attachProjectPHIDs($default_projects); - } - } - } - - if ($can_edit_priority) { - $priority = $request->getInt('priority'); - if ($priority !== null) { - $priority_map = ManiphestTaskPriority::getTaskPriorityMap(); - if (isset($priority_map[$priority])) { - $task->setPriority($priority); - } - } - } - - $task->setDescription($request->getStr('description')); - - if ($can_edit_assign) { - $assign = $request->getStr('assign'); - if (strlen($assign)) { - $assign_user = id(new PhabricatorPeopleQuery()) - ->setViewer($viewer) - ->withUsernames(array($assign)) - ->executeOne(); - if (!$assign_user) { - $assign_user = id(new PhabricatorPeopleQuery()) - ->setViewer($viewer) - ->withPHIDs(array($assign)) - ->executeOne(); - } - - if ($assign_user) { - $task->setOwnerPHID($assign_user->getPHID()); - } - } - } - } - - $template_id = $request->getInt('template'); - - // You can only have a parent task if you're creating a new task. - $parent_id = $request->getInt('parent'); - if (strlen($parent_id)) { - $parent_task = id(new ManiphestTaskQuery()) - ->setViewer($viewer) - ->withIDs(array($parent_id)) - ->executeOne(); - if (!$parent_task) { - return new Aphront404Response(); - } - if (!$template_id) { - $template_id = $parent_id; - } - } - } - - $errors = array(); - $e_title = true; - - $field_list = PhabricatorCustomField::getObjectFields( - $task, - PhabricatorCustomField::ROLE_EDIT); - $field_list->setViewer($viewer); - $field_list->readFieldsFromStorage($task); - - $aux_fields = $field_list->getFields(); - - $v_space = $task->getSpacePHID(); - - if ($request->isFormPost()) { - $changes = array(); - - $new_title = $request->getStr('title'); - $new_desc = $request->getStr('description'); - $new_status = $request->getStr('status'); - $v_space = $request->getStr('spacePHID'); - - if (!$task->getID()) { - $workflow = 'create'; - } else { - $workflow = ''; - } - - $changes[ManiphestTransaction::TYPE_TITLE] = $new_title; - $changes[ManiphestTransaction::TYPE_DESCRIPTION] = $new_desc; - - if ($can_edit_status) { - $changes[ManiphestTransaction::TYPE_STATUS] = $new_status; - } else if (!$task->getID()) { - // Create an initial status transaction for the burndown chart. - // TODO: We can probably remove this once Facts comes online. - $changes[ManiphestTransaction::TYPE_STATUS] = $task->getStatus(); - } - - $owner_tokenizer = $request->getArr('assigned_to'); - $owner_phid = reset($owner_tokenizer); - - if (!strlen($new_title)) { - $e_title = pht('Required'); - $errors[] = pht('Title is required.'); - } - - $old_values = array(); - foreach ($aux_fields as $aux_arr_key => $aux_field) { - // TODO: This should be buildFieldTransactionsFromRequest() once we - // switch to ApplicationTransactions properly. - - $aux_old_value = $aux_field->getOldValueForApplicationTransactions(); - $aux_field->readValueFromRequest($request); - $aux_new_value = $aux_field->getNewValueForApplicationTransactions(); - - // TODO: We're faking a call to the ApplicaitonTransaction validation - // logic here. We need valid objects to pass, but they aren't used - // in a meaningful way. For now, build User objects. Once the Maniphest - // objects exist, this will switch over automatically. This is a big - // hack but shouldn't be long for this world. - $placeholder_editor = id(new PhabricatorUserProfileEditor()) - ->setActor($viewer); - - $field_errors = $aux_field->validateApplicationTransactions( - $placeholder_editor, - PhabricatorTransactions::TYPE_CUSTOMFIELD, - array( - id(new ManiphestTransaction()) - ->setOldValue($aux_old_value) - ->setNewValue($aux_new_value), - )); - - foreach ($field_errors as $error) { - $errors[] = $error->getMessage(); - } - - $old_values[$aux_field->getFieldKey()] = $aux_old_value; - } - - if ($errors) { - $task->setTitle($new_title); - $task->setDescription($new_desc); - $task->setPriority($request->getInt('priority')); - $task->setOwnerPHID($owner_phid); - $task->attachSubscriberPHIDs($request->getArr('cc')); - $task->attachProjectPHIDs($request->getArr('projects')); - } else { - - if ($can_edit_priority) { - $changes[ManiphestTransaction::TYPE_PRIORITY] = - $request->getInt('priority'); - } - if ($can_edit_assign) { - $changes[ManiphestTransaction::TYPE_OWNER] = $owner_phid; - } - - $changes[PhabricatorTransactions::TYPE_SUBSCRIBERS] = - array('=' => $request->getArr('cc')); - - if ($can_edit_projects) { - $projects = $request->getArr('projects'); - $changes[PhabricatorTransactions::TYPE_EDGE] = - $projects; - $column_phid = $request->getStr('columnPHID'); - // allow for putting a task in a project column at creation -only- - if (!$task->getID() && $column_phid && $projects) { - $column = id(new PhabricatorProjectColumnQuery()) - ->setViewer($viewer) - ->withProjectPHIDs($projects) - ->withPHIDs(array($column_phid)) - ->executeOne(); - if ($column) { - $changes[ManiphestTransaction::TYPE_PROJECT_COLUMN] = - array( - 'new' => array( - 'projectPHID' => $column->getProjectPHID(), - 'columnPHIDs' => array($column_phid), - ), - 'old' => array( - 'projectPHID' => $column->getProjectPHID(), - 'columnPHIDs' => array(), - ), - ); - } - } - } - - if ($can_edit_policies) { - $changes[PhabricatorTransactions::TYPE_SPACE] = $v_space; - $changes[PhabricatorTransactions::TYPE_VIEW_POLICY] = - $request->getStr('viewPolicy'); - $changes[PhabricatorTransactions::TYPE_EDIT_POLICY] = - $request->getStr('editPolicy'); - } - - $template = new ManiphestTransaction(); - $transactions = array(); - - foreach ($changes as $type => $value) { - $transaction = clone $template; - $transaction->setTransactionType($type); - if ($type == ManiphestTransaction::TYPE_PROJECT_COLUMN) { - $transaction->setNewValue($value['new']); - $transaction->setOldValue($value['old']); - } else if ($type == PhabricatorTransactions::TYPE_EDGE) { - $project_type = - PhabricatorProjectObjectHasProjectEdgeType::EDGECONST; - $transaction - ->setMetadataValue('edge:type', $project_type) - ->setNewValue( - array( - '=' => array_fuse($value), - )); - } else { - $transaction->setNewValue($value); - } - $transactions[] = $transaction; - } - - if ($aux_fields) { - foreach ($aux_fields as $aux_field) { - $transaction = clone $template; - $transaction->setTransactionType( - PhabricatorTransactions::TYPE_CUSTOMFIELD); - $aux_key = $aux_field->getFieldKey(); - $transaction->setMetadataValue('customfield:key', $aux_key); - $old = idx($old_values, $aux_key); - $new = $aux_field->getNewValueForApplicationTransactions(); - - $transaction->setOldValue($old); - $transaction->setNewValue($new); - - $transactions[] = $transaction; - } - } - - if ($transactions) { - $is_new = !$task->getID(); - - $event = new PhabricatorEvent( - PhabricatorEventType::TYPE_MANIPHEST_WILLEDITTASK, - array( - 'task' => $task, - 'new' => $is_new, - 'transactions' => $transactions, - )); - $event->setUser($viewer); - $event->setAphrontRequest($request); - PhutilEventEngine::dispatchEvent($event); - - $task = $event->getValue('task'); - $transactions = $event->getValue('transactions'); - - $editor = id(new ManiphestTransactionEditor()) - ->setActor($viewer) - ->setContentSourceFromRequest($request) - ->setContinueOnNoEffect(true) - ->applyTransactions($task, $transactions); - - $event = new PhabricatorEvent( - PhabricatorEventType::TYPE_MANIPHEST_DIDEDITTASK, - array( - 'task' => $task, - 'new' => $is_new, - 'transactions' => $transactions, - )); - $event->setUser($viewer); - $event->setAphrontRequest($request); - PhutilEventEngine::dispatchEvent($event); - } - - - if ($parent_task) { - // TODO: This should be transactional now. - id(new PhabricatorEdgeEditor()) - ->addEdge( - $parent_task->getPHID(), - ManiphestTaskDependsOnTaskEdgeType::EDGECONST, - $task->getPHID()) - ->save(); - $workflow = $parent_task->getID(); - } - - if ($request->isAjax()) { - switch ($response_type) { - case 'card': - $owner = null; - if ($task->getOwnerPHID()) { - $owner = id(new PhabricatorHandleQuery()) - ->setViewer($viewer) - ->withPHIDs(array($task->getOwnerPHID())) - ->executeOne(); - } - - $project = $this->getSprintProjectforTask($viewer, $projects); - - $tasks = id(new SprintBoardTaskCard()) - ->setViewer($viewer) - ->setProject($project) - ->setTask($task) - ->setOwner($owner) - ->setCanEdit(true) - ->getItem(); - - $column = id(new PhabricatorProjectColumnQuery()) - ->setViewer($viewer) - ->withPHIDs(array($request->getStr('columnPHID'))) - ->executeOne(); - if (!$column) { - return new Aphront404Response(); - } - - // re-load projects for accuracy as they are not re-loaded via - // the editor - $project_phids = PhabricatorEdgeQuery::loadDestinationPHIDs( - $task->getPHID(), - PhabricatorProjectObjectHasProjectEdgeType::EDGECONST); - $task->attachProjectPHIDs($project_phids); - $remove_from_board = false; - if (!in_array($column->getProjectPHID(), $project_phids)) { - $remove_from_board = true; - } - - $positions = id(new PhabricatorProjectColumnPositionQuery()) - ->setViewer($viewer) - ->withColumns(array($column)) - ->execute(); - $task_phids = mpull($positions, 'getObjectPHID'); - - $column_tasks = id(new ManiphestTaskQuery()) - ->setViewer($viewer) - ->withPHIDs($task_phids) - ->execute(); - - if ($order == PhabricatorProjectColumn::ORDER_NATURAL) { - // TODO: This is a little bit awkward, because PHP and JS use - // slightly different sort order parameters to achieve the same - // effect. It would be good to unify this a bit at some point. - $sort_map = array(); - foreach ($positions as $position) { - $sort_map[$position->getObjectPHID()] = array( - -$position->getSequence(), - $position->getID(), - ); - } - } else { - $sort_map = mpull( - $column_tasks, - 'getPrioritySortVector', - 'getPHID'); - } - - $data = array( - 'sortMap' => $sort_map, - 'removeFromBoard' => $remove_from_board, - ); - break; - case 'task': - default: - $tasks = $this->renderSingleTask($task); - $data = array(); - break; - } - return id(new AphrontAjaxResponse())->setContent( - array( - 'tasks' => $tasks, - 'data' => $data, - )); - } - - $redirect_uri = '/T'.$task->getID(); - - if ($workflow) { - $redirect_uri .= '?workflow='.$workflow; - } - - return id(new AphrontRedirectResponse()) - ->setURI($redirect_uri); - } - } else { - if (!$task->getID()) { - $task->attachSubscriberPHIDs(array( - $viewer->getPHID(), - )); - if ($template_id) { - $template_task = id(new ManiphestTaskQuery()) - ->setViewer($viewer) - ->withIDs(array($template_id)) - ->needSubscriberPHIDs(true) - ->needProjectPHIDs(true) - ->executeOne(); - if ($template_task) { - $cc_phids = array_unique(array_merge( - $template_task->getSubscriberPHIDs(), - array($viewer->getPHID()))); - $task->attachSubscriberPHIDs($cc_phids); - $task->attachProjectPHIDs($template_task->getProjectPHIDs()); - $task->setOwnerPHID($template_task->getOwnerPHID()); - $task->setPriority($template_task->getPriority()); - $task->setViewPolicy($template_task->getViewPolicy()); - $task->setEditPolicy($template_task->getEditPolicy()); - - $v_space = $template_task->getSpacePHID(); - - $template_fields = PhabricatorCustomField::getObjectFields( - $template_task, - PhabricatorCustomField::ROLE_EDIT); - - $fields = $template_fields->getFields(); - foreach ($fields as $key => $field) { - if (!$field->shouldCopyWhenCreatingSimilarTask()) { - unset($fields[$key]); - } - if (empty($aux_fields[$key])) { - unset($fields[$key]); - } - } - - if ($fields) { - id(new PhabricatorCustomFieldList($fields)) - ->setViewer($viewer) - ->readFieldsFromStorage($template_task); - - foreach ($fields as $key => $field) { - $aux_fields[$key]->setValueFromStorage( - $field->getValueForStorage()); - } - } - } - } - } - } - - $error_view = null; - if ($errors) { - $error_view = new PHUIInfoView(); - $error_view->setErrors($errors); - } - - $priority_map = ManiphestTaskPriority::getTaskPriorityMap(); - - if ($task->getOwnerPHID()) { - $assigned_value = array($task->getOwnerPHID()); - } else { - $assigned_value = array(); - } - - if ($task->getSubscriberPHIDs()) { - $cc_value = $task->getSubscriberPHIDs(); - } else { - $cc_value = array(); - } - - if ($task->getProjectPHIDs()) { - $projects_value = $task->getProjectPHIDs(); - } else { - $projects_value = array(); - } - - $cancel_id = nonempty($task->getID(), $template_id); - if ($cancel_id) { - $cancel_uri = '/T'.$cancel_id; - } else { - $cancel_uri = '/maniphest/'; - } - - if ($task->getID()) { - $button_name = pht('Save Task'); - $header_name = pht('Edit Task'); - } else if ($parent_task) { - $cancel_uri = '/T'.$parent_task->getID(); - $button_name = pht('Create Task'); - $header_name = pht('Create New Subtask'); - } else { - $button_name = pht('Create Task'); - $header_name = pht('Create New Task'); - } - - require_celerity_resource('maniphest-task-edit-css'); - - $project_tokenizer_id = celerity_generate_unique_node_id(); - - $form = new AphrontFormView(); - $form - ->setUser($viewer) - ->addHiddenInput('template', $template_id) - ->addHiddenInput('responseType', $response_type) - ->addHiddenInput('order', $order) - ->addHiddenInput('ungrippable', $request->getStr('ungrippable')) - ->addHiddenInput('columnPHID', $request->getStr('columnPHID')); - - if ($parent_task) { - $form - ->appendChild( - id(new AphrontFormStaticControl()) - ->setLabel(pht('Parent Task')) - ->setValue($viewer->renderHandle($parent_task->getPHID()))) - ->addHiddenInput('parent', $parent_task->getID()); - } - - $form - ->appendChild( - id(new AphrontFormTextAreaControl()) - ->setLabel(pht('Title')) - ->setName('title') - ->setError($e_title) - ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_SHORT) - ->setValue($task->getTitle())); - - if ($can_edit_status) { - // See T4819. - $status_map = ManiphestTaskStatus::getTaskStatusMap(); - $dup_status = ManiphestTaskStatus::getDuplicateStatus(); - - if ($task->getStatus() != $dup_status) { - unset($status_map[$dup_status]); - } - - $form - ->appendChild( - id(new AphrontFormSelectControl()) - ->setLabel(pht('Status')) - ->setName('status') - ->setValue($task->getStatus()) - ->setOptions($status_map)); - } - - $policies = id(new PhabricatorPolicyQuery()) - ->setViewer($viewer) - ->setObject($task) - ->execute(); - - if ($can_edit_assign) { - $form->appendControl( - id(new AphrontFormTokenizerControl()) - ->setLabel(pht('Assigned To')) - ->setName('assigned_to') - ->setValue($assigned_value) - ->setUser($viewer) - ->setDatasource(new PhabricatorPeopleDatasource()) - ->setLimit(1)); - } - - $form - ->appendControl( - id(new AphrontFormTokenizerControl()) - ->setLabel(pht('CC')) - ->setName('cc') - ->setValue($cc_value) - ->setUser($viewer) - ->setDatasource(new PhabricatorMetaMTAMailableDatasource())); - - if ($can_edit_priority) { - $form - ->appendChild( - id(new AphrontFormSelectControl()) - ->setLabel(pht('Priority')) - ->setName('priority') - ->setOptions($priority_map) - ->setValue($task->getPriority())); - } - - if ($can_edit_policies) { - $form - ->appendChild( - id(new AphrontFormPolicyControl()) - ->setUser($viewer) - ->setCapability(PhabricatorPolicyCapability::CAN_VIEW) - ->setPolicyObject($task) - ->setPolicies($policies) - ->setSpacePHID($v_space) - ->setName('viewPolicy')) - ->appendChild( - id(new AphrontFormPolicyControl()) - ->setUser($viewer) - ->setCapability(PhabricatorPolicyCapability::CAN_EDIT) - ->setPolicyObject($task) - ->setPolicies($policies) - ->setName('editPolicy')); - } - - if ($can_edit_projects) { - $caption = null; - if ($can_create_projects) { - $caption = javelin_tag( - 'a', - array( - 'href' => '/project/create/', - 'mustcapture' => true, - 'sigil' => 'project-create', - ), - pht('Create New Project')); - } - $form - ->appendControl( - id(new AphrontFormTokenizerControl()) - ->setLabel(pht('Projects')) - ->setName('projects') - ->setValue($projects_value) - ->setID($project_tokenizer_id) - ->setCaption($caption) - ->setDatasource(new PhabricatorProjectDatasource())); - } - - $field_list->appendFieldsToForm($form); - - require_celerity_resource('phui-info-view-css'); - - Javelin::initBehavior('project-create', array( - 'tokenizerID' => $project_tokenizer_id, - )); - - $description_control = id(new PhabricatorRemarkupControl()) - ->setLabel(pht('Description')) - ->setName('description') - ->setID('description-textarea') - ->setValue($task->getDescription()) - ->setUser($viewer); - - $form - ->appendChild($description_control); - - if ($request->isAjax()) { - $dialog = id(new AphrontDialogView()) - ->setUser($viewer) - ->setWidth(AphrontDialogView::WIDTH_FULL) - ->setTitle($header_name) - ->appendChild( - array( - $error_view, - $form->buildLayoutView(), - )) - ->addCancelButton($cancel_uri) - ->addSubmitButton($button_name); - return id(new AphrontDialogResponse())->setDialog($dialog); - } - - $form - ->appendChild( - id(new AphrontFormSubmitControl()) - ->addCancelButton($cancel_uri) - ->setValue($button_name)); - - $form_box = id(new PHUIObjectBoxView()) - ->setHeaderText($header_name) - ->setFormErrors($errors) - ->setForm($form); - - $preview = id(new PHUIRemarkupPreviewPanel()) - ->setHeader(pht('Description Preview')) - ->setControlID('description-textarea') - ->setPreviewURI($this->getApplicationURI('task/descriptionpreview/')); - - if ($task->getID()) { - $page_objects = array($task->getPHID()); - } else { - $page_objects = array(); - } - - $crumbs = $this->buildApplicationCrumbs(); - - if ($task->getID()) { - $crumbs->addTextCrumb('T'.$task->getID(), '/T'.$task->getID()); - } - - $crumbs->addTextCrumb($header_name); - - $title = $header_name; - - return $this->newPage() - ->setTitle($title) - ->setCrumbs($crumbs) - ->setPageObjectPHIDs($page_objects) - ->appendChild( - array( - $form_box, - $preview, - )); - } - - private function getSprintProjectforTask($viewer, $projects) { - $project = null; - - if ($projects) { - $query = id(new PhabricatorProjectQuery()) - ->setViewer($viewer) - ->withPHIDs($projects); - } else { - return null; - } - - $projects = $query->execute(); - - foreach ($projects as $project) { - $sprintquery = id(new SprintQuery()) - ->setPHID($project->getPHID()); - if ($sprintquery->getIsSprint()) { - return $project; - } - } - + return id(new SprintManiphestEditEngine()) + ->setController($this) + ->addContextParameter('ungrippable') + ->addContextParameter('responseType') + ->addContextParameter('columnPHID') + ->addContextParameter('order') + ->buildResponse(); } } diff --git a/src/controller/board/SprintManiphestEditEngine.php b/src/controller/board/SprintManiphestEditEngine.php new file mode 100644 index 0000000..cce13c9 --- /dev/null +++ b/src/controller/board/SprintManiphestEditEngine.php @@ -0,0 +1,375 @@ +<?php + +final class SprintManiphestEditEngine + extends PhabricatorEditEngine { + + const ENGINECONST = 'sprint.maniphest.task'; + + public function getEngineName() { + return pht('Maniphest Tasks'); + } + + public function getSummaryHeader() { + return pht('Configure Maniphest Task Forms'); + } + + public function getSummaryText() { + return pht('Configure how users create and edit tasks.'); + } + + public function getEngineApplicationClass() { + return 'PhabricatorManiphestApplication'; + } + + protected function newEditableObject() { + return ManiphestTask::initializeNewTask($this->getViewer()); + } + + protected function newObjectQuery() { + return id(new ManiphestTaskQuery()); + } + + protected function getObjectCreateTitleText($object) { + return pht('Create New Task'); + } + + protected function getObjectEditTitleText($object) { + return pht('Edit %s %s', $object->getMonogram(), $object->getTitle()); + } + + protected function getObjectEditShortText($object) { + return $object->getMonogram(); + } + + protected function getObjectCreateShortText() { + return pht('Create Task'); + } + + protected function getEditorURI() { + return $this->getApplication()->getApplicationURI('task/edit/'); + } + + protected function getCommentViewHeaderText($object) { + return pht('Weigh In'); + } + + protected function getCommentViewButtonText($object) { + return pht('Set Sail for Adventure'); + } + + protected function getObjectViewURI($object) { + return '/'.$object->getMonogram(); + } + + protected function buildCustomEditFields($object) { + $status_map = $this->getTaskStatusMap($object); + $priority_map = $this->getTaskPriorityMap($object); + + if ($object->isClosed()) { + $default_status = ManiphestTaskStatus::getDefaultStatus(); + } else { + $default_status = ManiphestTaskStatus::getDefaultClosedStatus(); + } + + if ($object->getOwnerPHID()) { + $owner_value = array($object->getOwnerPHID()); + } else { + $owner_value = array($this->getViewer()->getPHID()); + } + + return array( + id(new PhabricatorHandlesEditField()) + ->setKey('parent') + ->setLabel(pht('Parent Task')) + ->setDescription(pht('Task to make this a subtask of.')) + ->setConduitDescription(pht('Create as a subtask of another task.')) + ->setConduitTypeDescription(pht('PHID of the parent task.')) + ->setAliases(array('parentPHID')) + ->setTransactionType(ManiphestTransaction::TYPE_PARENT) + ->setHandleParameterType(new ManiphestTaskListHTTPParameterType()) + ->setSingleValue(null) + ->setIsReorderable(false) + ->setIsDefaultable(false) + ->setIsLockable(false), + id(new PhabricatorHandlesEditField()) + ->setKey('column') + ->setLabel(pht('Column')) + ->setDescription(pht('Workboard column to create this task into.')) + ->setConduitDescription(pht('Create into a workboard column.')) + ->setConduitTypeDescription(pht('PHID of workboard column.')) + ->setAliases(array('columnPHID')) + ->setTransactionType(ManiphestTransaction::TYPE_COLUMN) + ->setSingleValue(null) + ->setIsInvisible(true) + ->setIsReorderable(false) + ->setIsDefaultable(false) + ->setIsLockable(false), + id(new PhabricatorTextEditField()) + ->setKey('title') + ->setLabel(pht('Title')) + ->setDescription(pht('Name of the task.')) + ->setConduitDescription(pht('Rename the task.')) + ->setConduitTypeDescription(pht('New task name.')) + ->setTransactionType(ManiphestTransaction::TYPE_TITLE) + ->setIsRequired(true) + ->setValue($object->getTitle()), + id(new PhabricatorUsersEditField()) + ->setKey('owner') + ->setAliases(array('ownerPHID', 'assign', 'assigned')) + ->setLabel(pht('Assigned To')) + ->setDescription(pht('User who is responsible for the task.')) + ->setConduitDescription(pht('Reassign the task.')) + ->setConduitTypeDescription( + pht('New task owner, or `null` to unassign.')) + ->setTransactionType(ManiphestTransaction::TYPE_OWNER) + ->setIsCopyable(true) + ->setSingleValue($object->getOwnerPHID()) + ->setCommentActionLabel(pht('Assign / Claim')) + ->setCommentActionValue($owner_value), + id(new PhabricatorSelectEditField()) + ->setKey('status') + ->setLabel(pht('Status')) + ->setDescription(pht('Status of the task.')) + ->setConduitDescription(pht('Change the task status.')) + ->setConduitTypeDescription(pht('New task status constant.')) + ->setTransactionType(ManiphestTransaction::TYPE_STATUS) + ->setIsCopyable(true) + ->setValue($object->getStatus()) + ->setOptions($status_map) + ->setCommentActionLabel(pht('Change Status')) + ->setCommentActionValue($default_status), + id(new PhabricatorSelectEditField()) + ->setKey('priority') + ->setLabel(pht('Priority')) + ->setDescription(pht('Priority of the task.')) + ->setConduitDescription(pht('Change the priority of the task.')) + ->setConduitTypeDescription(pht('New task priority constant.')) + ->setTransactionType(ManiphestTransaction::TYPE_PRIORITY) + ->setIsCopyable(true) + ->setValue($object->getPriority()) + ->setOptions($priority_map) + ->setCommentActionLabel(pht('Change Priority')), + id(new PhabricatorRemarkupEditField()) + ->setKey('description') + ->setLabel(pht('Description')) + ->setDescription(pht('Task description.')) + ->setConduitDescription(pht('Update the task description.')) + ->setConduitTypeDescription(pht('New task description.')) + ->setTransactionType(ManiphestTransaction::TYPE_DESCRIPTION) + ->setValue($object->getDescription()) + ->setPreviewPanel( + id(new PHUIRemarkupPreviewPanel()) + ->setHeader(pht('Description Preview'))), + ); + } + + private function getTaskStatusMap(ManiphestTask $task) { + $status_map = ManiphestTaskStatus::getTaskStatusMap(); + + $current_status = $task->getStatus(); + + // If the current status is something we don't recognize (maybe an older + // status which was deleted), put a dummy entry in the status map so that + // saving the form doesn't destroy any data by accident. + if (idx($status_map, $current_status) === null) { + $status_map[$current_status] = pht('<Unknown: %s>', $current_status); + } + + $dup_status = ManiphestTaskStatus::getDuplicateStatus(); + foreach ($status_map as $status => $status_name) { + // Always keep the task's current status. + if ($status == $current_status) { + continue; + } + + // Don't allow tasks to be changed directly into "Closed, Duplicate" + // status. Instead, you have to merge them. See T4819. + if ($status == $dup_status) { + unset($status_map[$status]); + continue; + } + + // Don't let new or existing tasks be moved into a disabled status. + if (ManiphestTaskStatus::isDisabledStatus($status)) { + unset($status_map[$status]); + continue; + } + } + + return $status_map; + } + + private function getTaskPriorityMap(ManiphestTask $task) { + $priority_map = ManiphestTaskPriority::getTaskPriorityMap(); + $current_priority = $task->getPriority(); + + // If the current value isn't a legitimate one, put it in the dropdown + // anyway so saving the form doesn't cause a side effects. + if (idx($priority_map, $current_priority) === null) { + $priority_map[$current_priority] = pht( + '<Unknown: %s>', + $current_priority); + } + + foreach ($priority_map as $priority => $priority_name) { + // Always keep the current priority. + if ($priority == $current_priority) { + continue; + } + + if (ManiphestTaskPriority::isDisabledPriority($priority)) { + unset($priority_map[$priority]); + continue; + } + } + + return $priority_map; + } + + protected function newEditResponse( + AphrontRequest $request, + $object, + array $xactions) { + + if ($request->isAjax()) { + // Reload the task to make sure we pick up the final task state. + $viewer = $this->getViewer(); + $task = id(new ManiphestTaskQuery()) + ->setViewer($viewer) + ->withIDs(array($object->getID())) + ->needSubscriberPHIDs(true) + ->needProjectPHIDs(true) + ->executeOne(); + + switch ($request->getStr('responseType')) { + case 'card': + return $this->buildCardResponse($task); + default: + return $this->buildListResponse($task); + } + + } + + return parent::newEditResponse($request, $object, $xactions); + } + + private function buildListResponse(ManiphestTask $task) { + $controller = $this->getController(); + + $payload = array( + 'tasks' => $controller->renderSingleTask($task), + 'data' => array(), + ); + + return id(new AphrontAjaxResponse())->setContent($payload); + } + + private function buildCardResponse(ManiphestTask $task) { + $controller = $this->getController(); + $request = $controller->getRequest(); + $viewer = $request->getViewer(); + + $column_phid = $request->getStr('columnPHID'); + $order = $request->getStr('order'); + + $column = id(new PhabricatorProjectColumnQuery()) + ->setViewer($viewer) + ->withPHIDs(array($column_phid)) + ->executeOne(); + if (!$column) { + return new Aphront404Response(); + } + + // If the workboard's project has been removed from the card's project + // list, we are going to remove it from the board completely. + $project_map = array_fuse($task->getProjectPHIDs()); + $remove_card = empty($project_map[$column->getProjectPHID()]); + + $positions = id(new PhabricatorProjectColumnPositionQuery()) + ->setViewer($viewer) + ->withColumns(array($column)) + ->execute(); + $task_phids = mpull($positions, 'getObjectPHID'); + + $column_tasks = id(new ManiphestTaskQuery()) + ->setViewer($viewer) + ->withPHIDs($task_phids) + ->execute(); + + if ($order == PhabricatorProjectColumn::ORDER_NATURAL) { + // TODO: This is a little bit awkward, because PHP and JS use + // slightly different sort order parameters to achieve the same + // effect. It would be good to unify this a bit at some point. + $sort_map = array(); + foreach ($positions as $position) { + $sort_map[$position->getObjectPHID()] = array( + -$position->getSequence(), + $position->getID(), + ); + } + } else { + $sort_map = mpull( + $column_tasks, + 'getPrioritySortVector', + 'getPHID'); + } + + $data = array( + 'removeFromBoard' => $remove_card, + 'sortMap' => $sort_map, + ); + + // TODO: This should just use HandlePool once we get through the EditEngine + // transition. + $owner = null; + if ($task->getOwnerPHID()) { + $owner = id(new PhabricatorHandleQuery()) + ->setViewer($viewer) + ->withPHIDs(array($task->getOwnerPHID())) + ->executeOne(); + } + + $projects = $request->getArr('projectPHIDs'); + $project = $this->getSprintProjectforTask($viewer, $projects); + + $tasks = id(new SprintBoardTaskCard()) + ->setViewer($viewer) + ->setProject($project) + ->setTask($task) + ->setOwner($owner) + ->setCanEdit(true) + ->getItem(); + + $payload = array( + 'tasks' => $tasks, + 'data' => $data, + ); + + return id(new AphrontAjaxResponse())->setContent($payload); + } + + private function getSprintProjectforTask($viewer, $projects) { + $project = null; + + if ($projects) { + $query = id(new PhabricatorProjectQuery()) + ->setViewer($viewer) + ->withPHIDs($projects); + } else { + return null; + } + + $projects = $query->execute(); + + foreach ($projects as $project) { + $sprintquery = id(new SprintQuery()) + ->setPHID($project->getPHID()); + if ($sprintquery->getIsSprint()) { + return $project; + } + } + + } + + +} -- To view, visit https://gerrit.wikimedia.org/r/261185 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I418d643a0949599ad507071fb14ffb3ff89bf102 Gerrit-PatchSet: 1 Gerrit-Project: phabricator/extensions/Sprint Gerrit-Branch: master Gerrit-Owner: Christopher Johnson (WMDE) <christopher.john...@wikimedia.de> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits