Differential D20636 Diff 49221 src/applications/project/controller/PhabricatorProjectColumnBulkMoveController.php
Changeset View
Changeset View
Standalone View
Standalone View
src/applications/project/controller/PhabricatorProjectColumnBulkMoveController.php
| <?php | <?php | ||||
| final class PhabricatorProjectColumnBulkMoveController | final class PhabricatorProjectColumnBulkMoveController | ||||
| extends PhabricatorProjectBoardController { | extends PhabricatorProjectBoardController { | ||||
| public function handleRequest(AphrontRequest $request) { | public function handleRequest(AphrontRequest $request) { | ||||
| $viewer = $request->getViewer(); | $viewer = $request->getViewer(); | ||||
| $response = $this->loadProject(); | $response = $this->loadProject(); | ||||
| if ($response) { | if ($response) { | ||||
| return $response; | return $response; | ||||
| } | } | ||||
| $project = $this->getProject(); | // See T13316. If we're operating in "column" mode, we're going to skip | ||||
| // the prompt for a project and just have the user select a target column. | |||||
| // In "project" mode, we prompt them for a project first. | |||||
| $is_column_mode = ($request->getURIData('mode') === 'column'); | |||||
| $src_project = $this->getProject(); | |||||
| $state = $this->getViewState(); | $state = $this->getViewState(); | ||||
| $board_uri = $state->newWorkboardURI(); | $board_uri = $state->newWorkboardURI(); | ||||
| $layout_engine = $state->getLayoutEngine(); | $layout_engine = $state->getLayoutEngine(); | ||||
| $board_phid = $project->getPHID(); | $board_phid = $src_project->getPHID(); | ||||
| $columns = $layout_engine->getColumns($board_phid); | $columns = $layout_engine->getColumns($board_phid); | ||||
| $columns = mpull($columns, null, 'getID'); | $columns = mpull($columns, null, 'getID'); | ||||
| $column_id = $request->getURIData('columnID'); | $column_id = $request->getURIData('columnID'); | ||||
| $move_column = idx($columns, $column_id); | $src_column = idx($columns, $column_id); | ||||
| if (!$move_column) { | if (!$src_column) { | ||||
| return new Aphront404Response(); | return new Aphront404Response(); | ||||
| } | } | ||||
| $move_task_phids = $layout_engine->getColumnObjectPHIDs( | $move_task_phids = $layout_engine->getColumnObjectPHIDs( | ||||
| $board_phid, | $board_phid, | ||||
| $move_column->getPHID()); | $src_column->getPHID()); | ||||
| $tasks = $state->getObjects(); | $tasks = $state->getObjects(); | ||||
| $move_tasks = array_select_keys($tasks, $move_task_phids); | $move_tasks = array_select_keys($tasks, $move_task_phids); | ||||
| $move_tasks = id(new PhabricatorPolicyFilter()) | $move_tasks = id(new PhabricatorPolicyFilter()) | ||||
| ->setViewer($viewer) | ->setViewer($viewer) | ||||
| ->requireCapabilities(array(PhabricatorPolicyCapability::CAN_EDIT)) | ->requireCapabilities(array(PhabricatorPolicyCapability::CAN_EDIT)) | ||||
| ->apply($move_tasks); | ->apply($move_tasks); | ||||
| if (!$move_tasks) { | if (!$move_tasks) { | ||||
| return $this->newDialog() | return $this->newDialog() | ||||
| ->setTitle(pht('No Movable Tasks')) | ->setTitle(pht('No Movable Tasks')) | ||||
| ->appendParagraph( | ->appendParagraph( | ||||
| pht( | pht( | ||||
| 'The selected column contains no visible tasks which you '. | 'The selected column contains no visible tasks which you '. | ||||
| 'have permission to move.')) | 'have permission to move.')) | ||||
| ->addCancelButton($board_uri); | ->addCancelButton($board_uri); | ||||
| } | } | ||||
| $move_project_phid = $project->getPHID(); | $dst_project_phid = null; | ||||
| $move_column_phid = null; | $dst_project = null; | ||||
| $move_project = null; | $has_project = false; | ||||
| $move_column = null; | if ($is_column_mode) { | ||||
| $columns = null; | $has_project = true; | ||||
| $errors = array(); | $dst_project_phid = $src_project->getPHID(); | ||||
| } else { | |||||
| if ($request->isFormOrHiSecPost()) { | if ($request->isFormOrHiSecPost()) { | ||||
| $move_project_phid = head($request->getArr('moveProjectPHID')); | $has_project = $request->getStr('hasProject'); | ||||
| if (!$move_project_phid) { | if ($has_project) { | ||||
| $move_project_phid = $request->getStr('moveProjectPHID'); | // We may read this from a tokenizer input as an array, or from a | ||||
| // hidden input as a string. | |||||
| $dst_project_phid = head($request->getArr('dstProjectPHID')); | |||||
| if (!$dst_project_phid) { | |||||
| $dst_project_phid = $request->getStr('dstProjectPHID'); | |||||
| } | |||||
| } | |||||
| } | } | ||||
| } | |||||
| $errors = array(); | |||||
| $hidden = array(); | |||||
| if (!$move_project_phid) { | if ($has_project) { | ||||
| if ($request->getBool('hasProject')) { | if (!$dst_project_phid) { | ||||
| $errors[] = pht('Choose a project to move tasks to.'); | $errors[] = pht('Choose a project to move tasks to.'); | ||||
| } | |||||
| } else { | } else { | ||||
| $target_project = id(new PhabricatorProjectQuery()) | $dst_project = id(new PhabricatorProjectQuery()) | ||||
| ->setViewer($viewer) | ->setViewer($viewer) | ||||
| ->withPHIDs(array($move_project_phid)) | ->withPHIDs(array($dst_project_phid)) | ||||
| ->executeOne(); | ->executeOne(); | ||||
| if (!$target_project) { | if (!$dst_project) { | ||||
| $errors[] = pht('You must choose a valid project.'); | $errors[] = pht('Choose a valid project to move tasks to.'); | ||||
| } else if (!$project->getHasWorkboard()) { | |||||
| $errors[] = pht( | |||||
| 'You must choose a project with a workboard.'); | |||||
| } else { | |||||
| $move_project = $target_project; | |||||
| } | } | ||||
| if (!$dst_project->getHasWorkboard()) { | |||||
| $errors[] = pht('You must choose a project with a workboard.'); | |||||
| $dst_project = null; | |||||
| } | } | ||||
| } | |||||
| } | |||||
| if ($dst_project) { | |||||
| $same_project = ($src_project->getID() === $dst_project->getID()); | |||||
| if ($move_project) { | $layout_engine = id(new PhabricatorBoardLayoutEngine()) | ||||
| $move_engine = id(new PhabricatorBoardLayoutEngine()) | |||||
| ->setViewer($viewer) | ->setViewer($viewer) | ||||
| ->setBoardPHIDs(array($move_project->getPHID())) | ->setBoardPHIDs(array($dst_project->getPHID())) | ||||
| ->setFetchAllBoards(true) | ->setFetchAllBoards(true) | ||||
| ->executeLayout(); | ->executeLayout(); | ||||
| $columns = $move_engine->getColumns($move_project->getPHID()); | $dst_columns = $layout_engine->getColumns($dst_project->getPHID()); | ||||
| $columns = mpull($columns, null, 'getPHID'); | $dst_columns = mpull($columns, null, 'getPHID'); | ||||
| foreach ($columns as $key => $column) { | $has_column = false; | ||||
| if ($column->isHidden()) { | $dst_column = null; | ||||
| unset($columns[$key]); | |||||
| // If we're performing a move on the same board, default the | |||||
| // control value to the current column. | |||||
| if ($same_project) { | |||||
| $dst_column_phid = $src_column->getPHID(); | |||||
| } else { | |||||
| $dst_column_phid = null; | |||||
| } | |||||
| if ($request->isFormOrHiSecPost()) { | |||||
| $has_column = $request->getStr('hasColumn'); | |||||
| if ($has_column) { | |||||
| $dst_column_phid = $request->getStr('dstColumnPHID'); | |||||
| } | } | ||||
| } | } | ||||
| $move_column_phid = $request->getStr('moveColumnPHID'); | if ($has_column) { | ||||
| if (!$move_column_phid) { | $dst_column = idx($dst_columns, $dst_column_phid); | ||||
| if ($request->getBool('hasColumn')) { | if (!$dst_column) { | ||||
| $errors[] = pht('Choose a column to move tasks to.'); | $errors[] = pht('Choose a column to move tasks to.'); | ||||
| } | |||||
| } else { | } else { | ||||
| if (empty($columns[$move_column_phid])) { | if ($dst_column->isHidden()) { | ||||
| $errors[] = pht( | $errors[] = pht('You can not move tasks to a hidden column.'); | ||||
| 'Choose a valid column on the target workboard to move '. | $dst_column = null; | ||||
| 'tasks to.'); | } else if ($dst_column->getPHID() === $src_column->getPHID()) { | ||||
| } else if ($columns[$move_column_phid]->getID() == $column_id) { | $errors[] = pht('You can not move tasks from a column to itself.'); | ||||
| $errors[] = pht( | $dst_column = null; | ||||
| 'You can not move tasks from a column to itself.'); | |||||
| } else { | |||||
| $move_column = $columns[$move_column_phid]; | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| if ($move_column && $move_project) { | if ($dst_column) { | ||||
| foreach ($move_tasks as $move_task) { | foreach ($move_tasks as $move_task) { | ||||
| $xactions = array(); | $xactions = array(); | ||||
| // If we're switching projects, get out of the old project first | // If we're switching projects, get out of the old project first | ||||
| // and move to the new project. | // and move to the new project. | ||||
| if ($move_project->getID() != $project->getID()) { | if (!$same_project) { | ||||
| $xactions[] = id(new ManiphestTransaction()) | $xactions[] = id(new ManiphestTransaction()) | ||||
| ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) | ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) | ||||
| ->setMetadataValue( | ->setMetadataValue( | ||||
| 'edge:type', | 'edge:type', | ||||
| PhabricatorProjectObjectHasProjectEdgeType::EDGECONST) | PhabricatorProjectObjectHasProjectEdgeType::EDGECONST) | ||||
| ->setNewValue( | ->setNewValue( | ||||
| array( | array( | ||||
| '-' => array( | '-' => array( | ||||
| $project->getPHID() => $project->getPHID(), | $src_project->getPHID() => $src_project->getPHID(), | ||||
| ), | ), | ||||
| '+' => array( | '+' => array( | ||||
| $move_project->getPHID() => $move_project->getPHID(), | $dst_project->getPHID() => $dst_project->getPHID(), | ||||
| ), | ), | ||||
| )); | )); | ||||
| } | } | ||||
| $xactions[] = id(new ManiphestTransaction()) | $xactions[] = id(new ManiphestTransaction()) | ||||
| ->setTransactionType(PhabricatorTransactions::TYPE_COLUMNS) | ->setTransactionType(PhabricatorTransactions::TYPE_COLUMNS) | ||||
| ->setNewValue( | ->setNewValue( | ||||
| array( | array( | ||||
| array( | array( | ||||
| 'columnPHID' => $move_column->getPHID(), | 'columnPHID' => $dst_column->getPHID(), | ||||
| ), | ), | ||||
| )); | )); | ||||
| $editor = id(new ManiphestTransactionEditor()) | $editor = id(new ManiphestTransactionEditor()) | ||||
| ->setActor($viewer) | ->setActor($viewer) | ||||
| ->setContinueOnMissingFields(true) | ->setContinueOnMissingFields(true) | ||||
| ->setContinueOnNoEffect(true) | ->setContinueOnNoEffect(true) | ||||
| ->setContentSourceFromRequest($request) | ->setContentSourceFromRequest($request) | ||||
| ->setCancelURI($board_uri); | ->setCancelURI($board_uri); | ||||
| $editor->applyTransactions($move_task, $xactions); | $editor->applyTransactions($move_task, $xactions); | ||||
| } | } | ||||
| return id(new AphrontRedirectResponse()) | // If we did a move on the same workboard, redirect and preserve the | ||||
| ->setURI($board_uri); | // state parameters. If we moved to a different workboard, go there | ||||
| // with clean default state. | |||||
| if ($same_project) { | |||||
| $done_uri = $board_uri; | |||||
| } else { | |||||
| $done_uri = $dst_project->getWorkboardURI(); | |||||
| } | } | ||||
| if ($move_project) { | return id(new AphrontRedirectResponse())->setURI($done_uri); | ||||
| $column_form = id(new AphrontFormView()) | } | ||||
| ->setViewer($viewer) | |||||
| ->appendControl( | $title = pht('Move Tasks to Column'); | ||||
| $form = id(new AphrontFormView()) | |||||
| ->setViewer($viewer); | |||||
| // If we're moving between projects, add a reminder about which project | |||||
| // you selected in the previous step. | |||||
| if (!$is_column_mode) { | |||||
| $form->appendControl( | |||||
| id(new AphrontFormStaticControl()) | |||||
| ->setLabel(pht('Project')) | |||||
| ->setValue($dst_project->getDisplayName())); | |||||
| } | |||||
| $form->appendControl( | |||||
| id(new AphrontFormSelectControl()) | id(new AphrontFormSelectControl()) | ||||
| ->setName('moveColumnPHID') | ->setName('dstColumnPHID') | ||||
| ->setLabel(pht('Move to Column')) | ->setLabel(pht('Move to Column')) | ||||
| ->setValue($move_column_phid) | ->setValue($dst_column_phid) | ||||
| ->setOptions(mpull($columns, 'getDisplayName', 'getPHID'))); | ->setOptions(mpull($dst_columns, 'getDisplayName', 'getPHID'))); | ||||
| return $this->newWorkboardDialog() | $submit = pht('Move Tasks'); | ||||
| ->setTitle(pht('Move Tasks')) | |||||
| ->setWidth(AphrontDialogView::WIDTH_FORM) | |||||
| ->setErrors($errors) | |||||
| ->addHiddenInput('moveProjectPHID', $move_project->getPHID()) | |||||
| ->addHiddenInput('hasColumn', true) | |||||
| ->addHiddenInput('hasProject', true) | |||||
| ->appendParagraph( | |||||
| pht( | |||||
| 'Choose a column on the %s workboard to move tasks to:', | |||||
| $viewer->renderHandle($move_project->getPHID()))) | |||||
| ->appendForm($column_form) | |||||
| ->addSubmitButton(pht('Move Tasks')) | |||||
| ->addCancelButton($board_uri); | |||||
| } | |||||
| if ($move_project_phid) { | $hidden['dstProjectPHID'] = $dst_project->getPHID(); | ||||
| $move_project_phid_value = array($move_project_phid); | $hidden['hasColumn'] = true; | ||||
| $hidden['hasProject'] = true; | |||||
| } else { | } else { | ||||
| $move_project_phid_value = array(); | $title = pht('Move Tasks to Project'); | ||||
| if ($dst_project_phid) { | |||||
| $dst_project_phid_value = array($dst_project_phid); | |||||
| } else { | |||||
| $dst_project_phid_value = array(); | |||||
| } | } | ||||
| $project_form = id(new AphrontFormView()) | $form = id(new AphrontFormView()) | ||||
| ->setViewer($viewer) | ->setViewer($viewer) | ||||
| ->appendControl( | ->appendControl( | ||||
| id(new AphrontFormTokenizerControl()) | id(new AphrontFormTokenizerControl()) | ||||
| ->setName('moveProjectPHID') | ->setName('dstProjectPHID') | ||||
| ->setLimit(1) | ->setLimit(1) | ||||
| ->setLabel(pht('Move to Project')) | ->setLabel(pht('Move to Project')) | ||||
| ->setValue($move_project_phid_value) | ->setValue($dst_project_phid_value) | ||||
| ->setDatasource(new PhabricatorProjectDatasource())); | ->setDatasource(new PhabricatorProjectDatasource())); | ||||
| return $this->newWorkboardDialog() | $submit = pht('Continue'); | ||||
| ->setTitle(pht('Move Tasks')) | |||||
| $hidden['hasProject'] = true; | |||||
| } | |||||
| $dialog = $this->newWorkboardDialog() | |||||
| ->setWidth(AphrontDialogView::WIDTH_FORM) | ->setWidth(AphrontDialogView::WIDTH_FORM) | ||||
| ->setTitle($title) | |||||
| ->setErrors($errors) | ->setErrors($errors) | ||||
| ->addHiddenInput('hasProject', true) | ->appendForm($form) | ||||
| ->appendForm($project_form) | ->addSubmitButton($submit) | ||||
| ->addSubmitButton(pht('Continue')) | |||||
| ->addCancelButton($board_uri); | ->addCancelButton($board_uri); | ||||
| foreach ($hidden as $key => $value) { | |||||
| $dialog->addHiddenInput($key, $value); | |||||
| } | |||||
| return $dialog; | |||||
| } | } | ||||
| } | } | ||||