Something went wrong. Try again.
@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
Something went wrong. Try again.
9.0 kB · 246 lines
PHP
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247<?php
final class PhabricatorProjectBoardImportController extends PhabricatorProjectBoardController {
public function handleRequest(AphrontRequest $request) { $viewer = $request->getViewer(); $project_id = $request->getURIData('projectID');
$project = id(new PhabricatorProjectQuery()) ->setViewer($viewer) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) ->withIDs(array($project_id)) ->executeOne(); if (!$project) { return new Aphront404Response(); } $this->setProject($project);
$project_id = $project->getID(); $board_uri = $this->getApplicationURI("board/{$project_id}/");
// See PHI1025. We only want to prevent the import if the board already has // real columns. If it has proxy columns (for example, for milestones) you // can still import columns from another board. $columns = id(new PhabricatorProjectColumnQuery()) ->setViewer($viewer) ->withProjectPHIDs(array($project->getPHID())) ->withIsProxyColumn(false) ->execute(); if ($columns) { return $this->newDialog() ->setTitle(pht('Workboard Already Has Columns')) ->appendParagraph( pht( 'You can not import columns into this workboard because it '. 'already has columns. You can only import into an empty '. 'workboard.')) ->addCancelButton($board_uri); }
if ($request->isFormPost()) { $import_phid = $request->getArr('importProjectPHID'); $import_phid = reset($import_phid);
$import_columns = id(new PhabricatorProjectColumnQuery()) ->setViewer($viewer) ->withProjectPHIDs(array($import_phid)) ->withIsProxyColumn(false) ->execute(); if (!$import_columns) { return $this->newDialog() ->setTitle(pht('Source Workboard Has No Columns')) ->appendParagraph( pht( 'You can not import columns from that workboard because it has '. 'no importable columns.')) ->addCancelButton($board_uri); }
$table = id(new PhabricatorProjectColumn()) ->openTransaction(); foreach ($import_columns as $import_column) { if ($import_column->isHidden()) { continue; }
$new_column = PhabricatorProjectColumn::initializeNewColumn($viewer) ->setSequence($import_column->getSequence()) ->setProjectPHID($project->getPHID()) ->setName($import_column->getName()) ->setProperties($import_column->getProperties()) ->save(); } $xactions = array(); $xactions[] = id(new PhabricatorProjectTransaction()) ->setTransactionType( PhabricatorProjectWorkboardTransaction::TRANSACTIONTYPE) ->setNewValue(1);
id(new PhabricatorProjectTransactionEditor()) ->setActor($viewer) ->setContentSourceFromRequest($request) ->setContinueOnNoEffect(true) ->setContinueOnMissingFields(true) ->applyTransactions($project, $xactions);
$table->saveTransaction();
return id(new AphrontRedirectResponse())->setURI($board_uri); }
// Default value. The Tokenizer wants an array of phids. $tokenizer_value = array();
// Default to the previous milestone, if available. $value_candidate = $this->getPreviousMilestoneIfHasImportableColumns( $viewer, $project);
if ($value_candidate) { $tokenizer_value[] = $value_candidate->getPHID(); }
$proj_selector = id(new AphrontFormTokenizerControl()) ->setName('importProjectPHID') ->setUser($viewer) ->setValue($tokenizer_value) ->setDatasource(id(new PhabricatorProjectDatasource()) ->setParameters(array('mustHaveColumns' => true)) ->setLimit(1));
return $this->newDialog() ->setTitle(pht('Import Columns')) ->setWidth(AphrontDialogView::WIDTH_FORM) ->appendParagraph(pht('Choose a project or a milestone to import '. 'columns from:')) ->appendChild($proj_selector) ->addCancelButton($board_uri) ->addSubmitButton(pht('Import')); }
/** * Starting from a milestone, get the previous milestone, * but only if it has at least one column that could be imported. * @param PhabricatorUser $viewer Current user * @param PhabricatorProject $milestone Current milestone with no * workboard yet. * Technically, this parameter * could also be a project, * and projects are silently * rejected if passed here. * @return PhabricatorProject|null The milestone preceding * your specified milestone, * but only if it has at least * one importable column; * null in any other case. */ private function getPreviousMilestoneIfHasImportableColumns( PhabricatorUser $viewer, PhabricatorProject $milestone): ?PhabricatorProject {
// We can suggest something, only if you are creating workboard // on a milestone. if (!$milestone->isMilestone()) { return null; }
// Possible design choices: // 1. Suggest the most recent milestone (excluding this specific one). // CONS: Suggesting the most recent milestone doesn't make much sense, // when I want to create a workboard from a milestone from // a year ago. // Side note: to suggest the 'most recent', we need at least one // more query to find that number. It needs a "SELECT MAX(n)". // 2. Suggest the precedent milestone. // PRO: convenient when you already worked on a workboard, and you just // create an additional milestone; convenient also when you create // a workboard on a old milestone, so as to maintain the // workboard structure of that time. // Side note: finding the previous milestone is also extremely // easy. We avoid the extra query "SELECT MAX(n)". // // So we go with: 2. Suggest the precedent milestone. $previous_milestone_num = (int)$milestone->getMilestoneNumber(); $previous_milestone_num--; if ($previous_milestone_num < 1) { return null; }
$previous_milestone = $this->getProjectMilestoneFromNumber( $viewer, $milestone->getParentProjectPHID(), $previous_milestone_num);
if (!$previous_milestone) { return null; }
// Micro-optimization to avoid querying columns. if (!$previous_milestone->getHasWorkboard()) { return null; }
// Check if this milestone has at least one // existing column which could be imported, or we can cause the error // 'Source Workboard Has No Columns', // and it would be more confusing than useful. $example_column = $this->getOneImportableProjectColumn( $viewer, $previous_milestone->getPHID());
if (!$example_column) { return null; }
return $previous_milestone; }
/** * Get the milestone of a project from its milestone number. * @param PhabricatorUser $viewer Current user * @param string $proj_phid Project PHID * @param int $number Milestone number */ private function getProjectMilestoneFromNumber( PhabricatorUser $viewer, string $proj_phid, int $number): ?PhabricatorProject {
$query_proj = new PhabricatorProjectQuery(); return $query_proj ->setViewer($viewer) ->withParentProjectPHIDs(array($proj_phid)) ->withIsMilestone(true) ->withMilestoneNumberBetween($number, $number) ->executeOne(); }
/** * Get whatever non-proxy column on the workboard, * if existing, except for the default "Backlog" column. * @param PhabricatorUser $viewer Current user * @param string $proj_phid Project PHID * @return PhabricatorProjectColumn|null Column, or null * if there are none. */ private function getOneImportableProjectColumn( PhabricatorUser $viewer, string $proj_phid): ?PhabricatorProjectColumn {
// Query one (whatever) project column suitable for the import. // This code is inspired from PhabricatorProjectDatasource, // looking at its parameter 'mustHaveColumns'. $query_columns = new PhabricatorProjectColumnQuery(); return $query_columns ->setViewer($viewer) ->withProjectPHIDs(array($proj_phid)) ->withIsProxyColumn(false) ->setLimit(1) ->executeOne(); }
}