Differential D20636 Diff 49236 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; | |||||
} | } | ||||
} | } |