Page MenuHomestyx hydra

No OneTemporary

diff --git a/src/applications/project/controller/PhabricatorProjectBoardReorderController.php b/src/applications/project/controller/PhabricatorProjectBoardReorderController.php
index 425c27b5f0..011bbd069a 100644
--- a/src/applications/project/controller/PhabricatorProjectBoardReorderController.php
+++ b/src/applications/project/controller/PhabricatorProjectBoardReorderController.php
@@ -1,138 +1,149 @@
<?php
final class PhabricatorProjectBoardReorderController
extends PhabricatorProjectBoardController {
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$projectid = $request->getURIData('projectID');
$project = id(new PhabricatorProjectQuery())
->setViewer($viewer)
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->withIDs(array($projectid))
->executeOne();
if (!$project) {
return new Aphront404Response();
}
$this->setProject($project);
$project_id = $project->getID();
$board_uri = $this->getApplicationURI("board/{$project_id}/");
$reorder_uri = $this->getApplicationURI("board/{$project_id}/reorder/");
if ($request->isFormPost()) {
// User clicked "Done", make sure the page reloads to show the new
// column order.
return id(new AphrontRedirectResponse())->setURI($board_uri);
}
$columns = id(new PhabricatorProjectColumnQuery())
->setViewer($viewer)
->withProjectPHIDs(array($project->getPHID()))
->execute();
$columns = msort($columns, 'getSequence');
$column_phid = $request->getStr('columnPHID');
if ($column_phid && $request->validateCSRF()) {
$columns = mpull($columns, null, 'getPHID');
if (empty($columns[$column_phid])) {
return new Aphront404Response();
}
$target_column = $columns[$column_phid];
$new_sequence = $request->getInt('sequence');
if ($new_sequence < 0) {
return new Aphront404Response();
}
// TODO: For now, we're not recording any transactions here. We probably
// should, but this sort of edit is extremely trivial.
// Resequence the columns so that the moved column has the correct
// sequence number. Move columns after it up one place in the sequence.
$new_map = array();
foreach ($columns as $phid => $column) {
$value = $column->getSequence();
if ($column->getPHID() == $column_phid) {
$value = $new_sequence;
} else if ($column->getSequence() >= $new_sequence) {
$value = $value + 1;
}
$new_map[$phid] = $value;
}
// Sort the columns into their new ordering.
asort($new_map);
// Now, compact the ordering and adjust any columns that need changes.
$project->openTransaction();
$sequence = 0;
foreach ($new_map as $phid => $ignored) {
$new_value = $sequence++;
$cur_value = $columns[$phid]->getSequence();
if ($new_value != $cur_value) {
$columns[$phid]->setSequence($new_value)->save();
}
}
$project->saveTransaction();
return id(new AphrontAjaxResponse())->setContent(
array(
'sequenceMap' => mpull($columns, 'getSequence', 'getPHID'),
));
}
$list_id = celerity_generate_unique_node_id();
$list = id(new PHUIObjectItemListView())
->setUser($viewer)
->setID($list_id)
->setFlush(true);
foreach ($columns as $column) {
+ // Don't allow milestone columns to be reordered.
+ $proxy = $column->getProxy();
+ if ($proxy && $proxy->isMilestone()) {
+ continue;
+ }
+
+ // At least for now, don't show subproject column.
+ if ($proxy) {
+ continue;
+ }
+
$item = id(new PHUIObjectItemView())
->setHeader($column->getDisplayName())
- ->addIcon('none', $column->getDisplayType());
+ ->addIcon($column->getHeaderIcon(), $column->getDisplayType());
if ($column->isHidden()) {
$item->setDisabled(true);
}
$item->setGrippable(true);
$item->addSigil('board-column');
$item->setMetadata(
array(
'columnPHID' => $column->getPHID(),
'columnSequence' => $column->getSequence(),
));
$list->addItem($item);
}
Javelin::initBehavior(
'reorder-columns',
array(
'listID' => $list_id,
'reorderURI' => $reorder_uri,
));
$note = id(new PHUIInfoView())
->appendChild(pht('Drag and drop columns to reorder them.'))
->setSeverity(PHUIInfoView::SEVERITY_NOTICE);
return $this->newDialog()
->setTitle(pht('Reorder Columns'))
->setWidth(AphrontDialogView::WIDTH_FORM)
->appendChild($note)
->appendChild($list)
->addSubmitButton(pht('Done'));
}
}
diff --git a/src/applications/project/controller/PhabricatorProjectColumnDetailController.php b/src/applications/project/controller/PhabricatorProjectColumnDetailController.php
index ca25c089fb..e008c832d9 100644
--- a/src/applications/project/controller/PhabricatorProjectColumnDetailController.php
+++ b/src/applications/project/controller/PhabricatorProjectColumnDetailController.php
@@ -1,123 +1,130 @@
<?php
final class PhabricatorProjectColumnDetailController
extends PhabricatorProjectBoardController {
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$id = $request->getURIData('id');
$project_id = $request->getURIData('projectID');
$project = id(new PhabricatorProjectQuery())
->setViewer($viewer)
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
))
->withIDs(array($project_id))
->needImages(true)
->executeOne();
if (!$project) {
return new Aphront404Response();
}
$this->setProject($project);
+ $project_id = $project->getID();
+
$column = id(new PhabricatorProjectColumnQuery())
->setViewer($viewer)
->withIDs(array($id))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
))
->executeOne();
if (!$column) {
return new Aphront404Response();
}
$timeline = $this->buildTransactionTimeline(
$column,
new PhabricatorProjectColumnTransactionQuery());
$timeline->setShouldTerminate(true);
$title = $column->getDisplayName();
$header = $this->buildHeaderView($column);
$actions = $this->buildActionView($column);
$properties = $this->buildPropertyView($column, $actions);
+ $crumbs = $this->buildApplicationCrumbs();
+ $crumbs->addTextCrumb(pht('Workboard'), "/project/board/{$project_id}/");
+ $crumbs->addTextCrumb(pht('Column: %s', $title));
+
$box = id(new PHUIObjectBoxView())
->setHeader($header)
->addPropertyList($properties);
$nav = $this->getProfileMenu();
return $this->newPage()
->setTitle($title)
->setNavigation($nav)
+ ->setCrumbs($crumbs)
->appendChild(
array(
$box,
$timeline,
));
}
private function buildHeaderView(PhabricatorProjectColumn $column) {
$viewer = $this->getRequest()->getUser();
$header = id(new PHUIHeaderView())
->setUser($viewer)
->setHeader($column->getDisplayName())
->setPolicyObject($column);
if ($column->isHidden()) {
$header->setStatus('fa-ban', 'dark', pht('Hidden'));
}
return $header;
}
private function buildActionView(PhabricatorProjectColumn $column) {
$viewer = $this->getRequest()->getUser();
$id = $column->getID();
$project_id = $this->getProject()->getID();
$base_uri = '/board/'.$project_id.'/';
$actions = id(new PhabricatorActionListView())
->setUser($viewer);
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$column,
PhabricatorPolicyCapability::CAN_EDIT);
$actions->addAction(
id(new PhabricatorActionView())
->setName(pht('Edit Column'))
->setIcon('fa-pencil')
->setHref($this->getApplicationURI($base_uri.'edit/'.$id.'/'))
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit));
return $actions;
}
private function buildPropertyView(
PhabricatorProjectColumn $column,
PhabricatorActionListView $actions) {
$viewer = $this->getRequest()->getUser();
$properties = id(new PHUIPropertyListView())
->setUser($viewer)
->setObject($column)
->setActionList($actions);
$limit = $column->getPointLimit();
$properties->addProperty(
pht('Point Limit'),
$limit ? $limit : pht('No Limit'));
return $properties;
}
}
diff --git a/src/applications/project/controller/PhabricatorProjectColumnEditController.php b/src/applications/project/controller/PhabricatorProjectColumnEditController.php
index 77f90b56cb..5ebc721c69 100644
--- a/src/applications/project/controller/PhabricatorProjectColumnEditController.php
+++ b/src/applications/project/controller/PhabricatorProjectColumnEditController.php
@@ -1,154 +1,156 @@
<?php
final class PhabricatorProjectColumnEditController
extends PhabricatorProjectBoardController {
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$id = $request->getURIData('id');
$project_id = $request->getURIData('projectID');
$project = id(new PhabricatorProjectQuery())
->setViewer($viewer)
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->withIDs(array($project_id))
->needImages(true)
->executeOne();
if (!$project) {
return new Aphront404Response();
}
$this->setProject($project);
$is_new = ($id ? false : true);
if (!$is_new) {
$column = id(new PhabricatorProjectColumnQuery())
->setViewer($viewer)
->withIDs(array($id))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$column) {
return new Aphront404Response();
}
} else {
$column = PhabricatorProjectColumn::initializeNewColumn($viewer);
}
$e_name = null;
$e_limit = null;
$v_limit = $column->getPointLimit();
$v_name = $column->getName();
$validation_exception = null;
$base_uri = '/board/'.$project_id.'/';
if ($is_new) {
// we want to go back to the board
$view_uri = $this->getApplicationURI($base_uri);
} else {
$view_uri = $this->getApplicationURI($base_uri.'column/'.$id.'/');
}
if ($request->isFormPost()) {
$v_name = $request->getStr('name');
$v_limit = $request->getStr('limit');
if ($is_new) {
$column->setProjectPHID($project->getPHID());
$column->attachProject($project);
$columns = id(new PhabricatorProjectColumnQuery())
->setViewer($viewer)
->withProjectPHIDs(array($project->getPHID()))
->execute();
$new_sequence = 1;
if ($columns) {
$values = mpull($columns, 'getSequence');
$new_sequence = max($values) + 1;
}
$column->setSequence($new_sequence);
}
$xactions = array();
- $type_name = PhabricatorProjectColumnTransaction::TYPE_NAME;
- $xactions[] = id(new PhabricatorProjectColumnTransaction())
- ->setTransactionType($type_name)
- ->setNewValue($v_name);
+ if (!$column->getProxy()) {
+ $type_name = PhabricatorProjectColumnTransaction::TYPE_NAME;
+ $xactions[] = id(new PhabricatorProjectColumnTransaction())
+ ->setTransactionType($type_name)
+ ->setNewValue($v_name);
+ }
$type_limit = PhabricatorProjectColumnTransaction::TYPE_LIMIT;
$xactions[] = id(new PhabricatorProjectColumnTransaction())
->setTransactionType($type_limit)
->setNewValue($v_limit);
try {
$editor = id(new PhabricatorProjectColumnTransactionEditor())
->setActor($viewer)
->setContinueOnNoEffect(true)
->setContentSourceFromRequest($request)
->applyTransactions($column, $xactions);
return id(new AphrontRedirectResponse())->setURI($view_uri);
} catch (PhabricatorApplicationTransactionValidationException $ex) {
$e_name = $ex->getShortMessage($type_name);
$e_limit = $ex->getShortMessage($type_limit);
$validation_exception = $ex;
}
}
- $form = new AphrontFormView();
- $form
- ->setUser($request->getUser())
- ->appendChild(
+ $form = id(new AphrontFormView())
+ ->setUser($request->getUser());
+
+ if (!$column->getProxy()) {
+ $form->appendChild(
id(new AphrontFormTextControl())
->setValue($v_name)
->setLabel(pht('Name'))
->setName('name')
- ->setError($e_name)
- ->setCaption(
- pht('This will be displayed as the header of the column.')))
- ->appendChild(
- id(new AphrontFormTextControl())
- ->setValue($v_limit)
- ->setLabel(pht('Point Limit'))
- ->setName('limit')
- ->setError($e_limit)
- ->setCaption(
- pht('Maximum number of points of tasks allowed in the column.')));
+ ->setError($e_name));
+ }
+ $form->appendChild(
+ id(new AphrontFormTextControl())
+ ->setValue($v_limit)
+ ->setLabel(pht('Point Limit'))
+ ->setName('limit')
+ ->setError($e_limit)
+ ->setCaption(
+ pht('Maximum number of points of tasks allowed in the column.')));
if ($is_new) {
$title = pht('Create Column');
$submit = pht('Create Column');
} else {
$title = pht('Edit %s', $column->getDisplayName());
$submit = pht('Save Column');
}
$form->appendChild(
id(new AphrontFormSubmitControl())
->setValue($submit)
->addCancelButton($view_uri));
$form_box = id(new PHUIObjectBoxView())
->setHeaderText($title)
->setValidationException($validation_exception)
->setForm($form);
$nav = $this->getProfileMenu();
return $this->newPage()
->setTitle($title)
->setNavigation($nav)
->appendChild($form_box);
}
}
diff --git a/src/applications/project/engine/PhabricatorBoardLayoutEngine.php b/src/applications/project/engine/PhabricatorBoardLayoutEngine.php
index 31be95816b..4415e2fdf9 100644
--- a/src/applications/project/engine/PhabricatorBoardLayoutEngine.php
+++ b/src/applications/project/engine/PhabricatorBoardLayoutEngine.php
@@ -1,580 +1,582 @@
<?php
final class PhabricatorBoardLayoutEngine extends Phobject {
private $viewer;
private $boardPHIDs;
private $objectPHIDs;
private $boards;
private $columnMap = array();
private $objectColumnMap = array();
private $boardLayout = array();
private $remQueue = array();
private $addQueue = array();
public function setViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer;
return $this;
}
public function getViewer() {
return $this->viewer;
}
public function setBoardPHIDs(array $board_phids) {
$this->boardPHIDs = array_fuse($board_phids);
return $this;
}
public function getBoardPHIDs() {
return $this->boardPHIDs;
}
public function setObjectPHIDs(array $object_phids) {
$this->objectPHIDs = array_fuse($object_phids);
return $this;
}
public function getObjectPHIDs() {
return $this->objectPHIDs;
}
public function executeLayout() {
$viewer = $this->getViewer();
$boards = $this->loadBoards();
if (!$boards) {
return $this;
}
$columns = $this->loadColumns($boards);
$positions = $this->loadPositions($boards);
foreach ($boards as $board_phid => $board) {
$board_columns = idx($columns, $board_phid);
// Don't layout boards with no columns. These boards need to be formally
// created first.
if (!$columns) {
continue;
}
$board_positions = idx($positions, $board_phid, array());
$this->layoutBoard($board, $board_columns, $board_positions);
}
return $this;
}
public function getColumns($board_phid) {
$columns = idx($this->boardLayout, $board_phid, array());
return array_select_keys($this->columnMap, array_keys($columns));
}
public function getColumnObjectPHIDs($board_phid, $column_phid) {
$columns = idx($this->boardLayout, $board_phid, array());
$positions = idx($columns, $column_phid, array());
return mpull($positions, 'getObjectPHID');
}
public function getObjectColumns($board_phid, $object_phid) {
$board_map = idx($this->objectColumnMap, $board_phid, array());
$column_phids = idx($board_map, $object_phid);
if (!$column_phids) {
return array();
}
return array_select_keys($this->columnMap, $column_phids);
}
public function queueRemovePosition(
$board_phid,
$column_phid,
$object_phid) {
$board_layout = idx($this->boardLayout, $board_phid, array());
$positions = idx($board_layout, $column_phid, array());
$position = idx($positions, $object_phid);
if ($position) {
$this->remQueue[] = $position;
// If this position hasn't been saved yet, get it out of the add queue.
if (!$position->getID()) {
foreach ($this->addQueue as $key => $add_position) {
if ($add_position === $position) {
unset($this->addQueue[$key]);
}
}
}
}
unset($this->boardLayout[$board_phid][$column_phid][$object_phid]);
return $this;
}
public function queueAddPositionBefore(
$board_phid,
$column_phid,
$object_phid,
$before_phid) {
return $this->queueAddPositionRelative(
$board_phid,
$column_phid,
$object_phid,
$before_phid,
true);
}
public function queueAddPositionAfter(
$board_phid,
$column_phid,
$object_phid,
$after_phid) {
return $this->queueAddPositionRelative(
$board_phid,
$column_phid,
$object_phid,
$after_phid,
false);
}
public function queueAddPosition(
$board_phid,
$column_phid,
$object_phid) {
return $this->queueAddPositionRelative(
$board_phid,
$column_phid,
$object_phid,
null,
true);
}
private function queueAddPositionRelative(
$board_phid,
$column_phid,
$object_phid,
$relative_phid,
$is_before) {
$board_layout = idx($this->boardLayout, $board_phid, array());
$positions = idx($board_layout, $column_phid, array());
// Check if the object is already in the column, and remove it if it is.
$object_position = idx($positions, $object_phid);
unset($positions[$object_phid]);
if (!$object_position) {
$object_position = id(new PhabricatorProjectColumnPosition())
->setBoardPHID($board_phid)
->setColumnPHID($column_phid)
->setObjectPHID($object_phid);
}
$found = false;
if (!$positions) {
$object_position->setSequence(0);
} else {
foreach ($positions as $position) {
if (!$found) {
if ($relative_phid === null) {
$is_match = true;
} else {
$position_phid = $position->getObjectPHID();
$is_match = ($relative_phid == $position_phid);
}
if ($is_match) {
$found = true;
$sequence = $position->getSequence();
if (!$is_before) {
$sequence++;
}
$object_position->setSequence($sequence++);
if (!$is_before) {
// If we're inserting after this position, continue the loop so
// we don't update it.
continue;
}
}
}
if ($found) {
$position->setSequence($sequence++);
$this->addQueue[] = $position;
}
}
}
if ($relative_phid && !$found) {
throw new Exception(
pht(
'Unable to find object "%s" in column "%s" on board "%s".',
$relative_phid,
$column_phid,
$board_phid));
}
$this->addQueue[] = $object_position;
$positions[$object_phid] = $object_position;
$positions = msort($positions, 'getOrderingKey');
$this->boardLayout[$board_phid][$column_phid] = $positions;
return $this;
}
public function applyPositionUpdates() {
foreach ($this->remQueue as $position) {
if ($position->getID()) {
$position->delete();
}
}
$this->remQueue = array();
$adds = array();
$updates = array();
foreach ($this->addQueue as $position) {
$id = $position->getID();
if ($id) {
$updates[$id] = $position;
} else {
$adds[] = $position;
}
}
$this->addQueue = array();
$table = new PhabricatorProjectColumnPosition();
$conn_w = $table->establishConnection('w');
$pairs = array();
foreach ($updates as $id => $position) {
// This is ugly because MySQL gets upset with us if it is configured
// strictly and we attempt inserts which can't work. We'll never actually
// do these inserts since they'll always collide (triggering the ON
// DUPLICATE KEY logic), so we just provide dummy values in order to get
// there.
$pairs[] = qsprintf(
$conn_w,
'(%d, %d, "", "", "")',
$id,
$position->getSequence());
}
if ($pairs) {
queryfx(
$conn_w,
'INSERT INTO %T (id, sequence, boardPHID, columnPHID, objectPHID)
VALUES %Q ON DUPLICATE KEY UPDATE sequence = VALUES(sequence)',
$table->getTableName(),
implode(', ', $pairs));
}
foreach ($adds as $position) {
$position->save();
}
return $this;
}
private function loadBoards() {
$viewer = $this->getViewer();
$board_phids = $this->getBoardPHIDs();
$boards = id(new PhabricatorObjectQuery())
->setViewer($viewer)
->withPHIDs($board_phids)
->execute();
$boards = mpull($boards, null, 'getPHID');
foreach ($boards as $key => $board) {
if (!$board->getHasWorkboard()) {
unset($boards[$key]);
}
}
return $boards;
}
private function loadColumns(array $boards) {
$viewer = $this->getViewer();
$columns = id(new PhabricatorProjectColumnQuery())
->setViewer($viewer)
->withProjectPHIDs(array_keys($boards))
->execute();
- $columns = msort($columns, 'getSequence');
+ $columns = msort($columns, 'getOrderingKey');
$columns = mpull($columns, null, 'getPHID');
$need_children = array();
foreach ($boards as $phid => $board) {
if ($board->getHasMilestones() || $board->getHasSubprojects()) {
$need_children[] = $phid;
}
}
if ($need_children) {
$children = id(new PhabricatorProjectQuery())
->setViewer($viewer)
->withParentProjectPHIDs($need_children)
->execute();
$children = mpull($children, null, 'getPHID');
$children = mgroup($children, 'getParentProjectPHID');
} else {
$children = array();
}
$columns = mgroup($columns, 'getProjectPHID');
foreach ($boards as $board_phid => $board) {
$board_columns = idx($columns, $board_phid, array());
// If the project has milestones, create any missing columns.
if ($board->getHasMilestones() || $board->getHasSubprojects()) {
$child_projects = idx($children, $board_phid, array());
$next_sequence = last($board_columns)->getSequence() + 1;
$proxy_columns = mpull($board_columns, null, 'getProxyPHID');
foreach ($child_projects as $child_phid => $child) {
if (isset($proxy_columns[$child_phid])) {
continue;
}
$new_column = PhabricatorProjectColumn::initializeNewColumn($viewer)
->attachProject($board)
->attachProxy($child)
->setSequence($next_sequence++)
->setProjectPHID($board_phid)
->setProxyPHID($child_phid);
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
$new_column->save();
unset($unguarded);
$board_columns[$new_column->getPHID()] = $new_column;
}
}
+ $board_columns = msort($board_columns, 'getOrderingKey');
+
$columns[$board_phid] = $board_columns;
}
foreach ($columns as $board_phid => $board_columns) {
foreach ($board_columns as $board_column) {
$column_phid = $board_column->getPHID();
$this->columnMap[$column_phid] = $board_column;
}
}
return $columns;
}
private function loadPositions(array $boards) {
$viewer = $this->getViewer();
$object_phids = $this->getObjectPHIDs();
if (!$object_phids) {
return array();
}
$positions = id(new PhabricatorProjectColumnPositionQuery())
->setViewer($viewer)
->withBoardPHIDs(array_keys($boards))
->withObjectPHIDs($object_phids)
->execute();
$positions = msort($positions, 'getOrderingKey');
$positions = mgroup($positions, 'getBoardPHID');
return $positions;
}
private function layoutBoard(
$board,
array $columns,
array $positions) {
$viewer = $this->getViewer();
$board_phid = $board->getPHID();
$position_groups = mgroup($positions, 'getObjectPHID');
$layout = array();
foreach ($columns as $column) {
$column_phid = $column->getPHID();
$layout[$column_phid] = array();
if ($column->isDefaultColumn()) {
$default_phid = $column_phid;
}
}
// Find all the columns which are proxies for other objects.
$proxy_map = array();
foreach ($columns as $column) {
$proxy_phid = $column->getProxyPHID();
if ($proxy_phid) {
$proxy_map[$proxy_phid] = $column->getPHID();
}
}
$object_phids = $this->getObjectPHIDs();
// If we have proxies, we need to force cards into the correct proxy
// columns.
if ($proxy_map) {
$edge_query = id(new PhabricatorEdgeQuery())
->withSourcePHIDs($object_phids)
->withEdgeTypes(
array(
PhabricatorProjectObjectHasProjectEdgeType::EDGECONST,
));
$edge_query->execute();
$project_phids = $edge_query->getDestinationPHIDs();
$project_phids = array_fuse($project_phids);
} else {
$project_phids = array();
}
if ($project_phids) {
$projects = id(new PhabricatorProjectQuery())
->setViewer($viewer)
->withPHIDs($project_phids)
->execute();
$projects = mpull($projects, null, 'getPHID');
} else {
$projects = array();
}
// Build a map from every project that any task is tagged with to the
// ancestor project which has a column on this board, if one exists.
$ancestor_map = array();
foreach ($projects as $phid => $project) {
if (isset($proxy_map[$phid])) {
$ancestor_map[$phid] = $proxy_map[$phid];
} else {
$seen = array($phid);
foreach ($project->getAncestorProjects() as $ancestor) {
$ancestor_phid = $ancestor->getPHID();
$seen[] = $ancestor_phid;
if (isset($proxy_map[$ancestor_phid])) {
foreach ($seen as $project_phid) {
$ancestor_map[$project_phid] = $proxy_map[$ancestor_phid];
}
}
}
}
}
foreach ($object_phids as $object_phid) {
$positions = idx($position_groups, $object_phid, array());
// First, check for objects that have corresponding proxy columns. We're
// going to overwrite normal column positions if a tag belongs to a proxy
// column, since you can't be in normal columns if you're in proxy
// columns.
$proxy_hits = array();
if ($proxy_map) {
$object_project_phids = $edge_query->getDestinationPHIDs(
array(
$object_phid,
));
foreach ($object_project_phids as $project_phid) {
if (isset($ancestor_map[$project_phid])) {
$proxy_hits[] = $ancestor_map[$project_phid];
}
}
}
if ($proxy_hits) {
// TODO: For now, only one column hit is permissible.
$proxy_hits = array_slice($proxy_hits, 0, 1);
$proxy_hits = array_fuse($proxy_hits);
// Check the object positions: we hope to find a position in each
// column the object should be part of. We're going to drop any
// invalid positions and create new positions where positions are
// missing.
foreach ($positions as $key => $position) {
$column_phid = $position->getColumnPHID();
if (isset($proxy_hits[$column_phid])) {
// Valid column, mark the position as found.
unset($proxy_hits[$column_phid]);
} else {
// Invalid column, ignore the position.
unset($positions[$key]);
}
}
// Create new positions for anything we haven't found.
foreach ($proxy_hits as $proxy_hit) {
$new_position = id(new PhabricatorProjectColumnPosition())
->setBoardPHID($board_phid)
->setColumnPHID($proxy_hit)
->setObjectPHID($object_phid)
->setSequence(0);
$this->addQueue[] = $new_position;
$positions[] = $new_position;
}
} else {
// Ignore any positions in columns which no longer exist. We don't
// actively destory them because the rest of the code ignores them and
// there's no real need to destroy the data.
foreach ($positions as $key => $position) {
$column_phid = $position->getColumnPHID();
if (empty($columns[$column_phid])) {
unset($positions[$key]);
}
}
// If the object has no position, put it on the default column.
if (!$positions) {
$new_position = id(new PhabricatorProjectColumnPosition())
->setBoardPHID($board_phid)
->setColumnPHID($default_phid)
->setObjectPHID($object_phid)
->setSequence(0);
$this->addQueue[] = $new_position;
$positions = array(
$new_position,
);
}
}
foreach ($positions as $position) {
$column_phid = $position->getColumnPHID();
$layout[$column_phid][$object_phid] = $position;
}
}
foreach ($layout as $column_phid => $map) {
$map = msort($map, 'getOrderingKey');
$layout[$column_phid] = $map;
foreach ($map as $object_phid => $position) {
$this->objectColumnMap[$board_phid][$object_phid][] = $column_phid;
}
}
$this->boardLayout[$board_phid] = $layout;
}
}
diff --git a/src/applications/project/storage/PhabricatorProjectColumn.php b/src/applications/project/storage/PhabricatorProjectColumn.php
index a63312438c..0ab6ec89c0 100644
--- a/src/applications/project/storage/PhabricatorProjectColumn.php
+++ b/src/applications/project/storage/PhabricatorProjectColumn.php
@@ -1,219 +1,239 @@
<?php
final class PhabricatorProjectColumn
extends PhabricatorProjectDAO
implements
PhabricatorApplicationTransactionInterface,
PhabricatorPolicyInterface,
PhabricatorDestructibleInterface {
const STATUS_ACTIVE = 0;
const STATUS_HIDDEN = 1;
const DEFAULT_ORDER = 'natural';
const ORDER_NATURAL = 'natural';
const ORDER_PRIORITY = 'priority';
protected $name;
protected $status;
protected $projectPHID;
protected $proxyPHID;
protected $sequence;
protected $properties = array();
private $project = self::ATTACHABLE;
private $proxy = self::ATTACHABLE;
public static function initializeNewColumn(PhabricatorUser $user) {
return id(new PhabricatorProjectColumn())
->setName('')
- ->setStatus(self::STATUS_ACTIVE);
+ ->setStatus(self::STATUS_ACTIVE)
+ ->attachProxy(null);
}
protected function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_SERIALIZATION => array(
'properties' => self::SERIALIZATION_JSON,
),
self::CONFIG_COLUMN_SCHEMA => array(
'name' => 'text255',
'status' => 'uint32',
'sequence' => 'uint32',
'proxyPHID' => 'phid?',
),
self::CONFIG_KEY_SCHEMA => array(
'key_status' => array(
'columns' => array('projectPHID', 'status', 'sequence'),
),
'key_sequence' => array(
'columns' => array('projectPHID', 'sequence'),
),
'key_proxy' => array(
'columns' => array('projectPHID', 'proxyPHID'),
'unique' => true,
),
),
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
PhabricatorProjectColumnPHIDType::TYPECONST);
}
public function attachProject(PhabricatorProject $project) {
$this->project = $project;
return $this;
}
public function getProject() {
return $this->assertAttached($this->project);
}
public function attachProxy($proxy) {
$this->proxy = $proxy;
return $this;
}
public function getProxy() {
return $this->assertAttached($this->proxy);
}
public function isDefaultColumn() {
return (bool)$this->getProperty('isDefault');
}
public function isHidden() {
return ($this->getStatus() == self::STATUS_HIDDEN);
}
public function getDisplayName() {
$proxy = $this->getProxy();
if ($proxy) {
return $proxy->getProxyColumnName();
}
$name = $this->getName();
if (strlen($name)) {
return $name;
}
if ($this->isDefaultColumn()) {
return pht('Backlog');
}
return pht('Unnamed Column');
}
public function getDisplayType() {
if ($this->isDefaultColumn()) {
return pht('(Default)');
}
if ($this->isHidden()) {
return pht('(Hidden)');
}
return null;
}
public function getDisplayClass() {
$proxy = $this->getProxy();
if ($proxy) {
return $proxy->getProxyColumnClass();
}
return null;
}
public function getHeaderIcon() {
$proxy = $this->getProxy();
if ($proxy) {
return $proxy->getProxyColumnIcon();
}
if ($this->isHidden()) {
return 'fa-eye-slash';
}
return null;
}
public function getProperty($key, $default = null) {
return idx($this->properties, $key, $default);
}
public function setProperty($key, $value) {
$this->properties[$key] = $value;
return $this;
}
public function getPointLimit() {
return $this->getProperty('pointLimit');
}
public function setPointLimit($limit) {
$this->setProperty('pointLimit', $limit);
return $this;
}
+ public function getOrderingKey() {
+ $proxy = $this->getProxy();
+
+ // Normal columns and subproject columns go first, in a user-controlled
+ // order.
+
+ // All the milestone columns go last, in their sequential order.
+
+ if (!$proxy || !$proxy->isMilestone()) {
+ $group = 'A';
+ $sequence = $this->getSequence();
+ } else {
+ $group = 'B';
+ $sequence = $proxy->getMilestoneNumber();
+ }
+
+ return sprintf('%s%012d', $group, $sequence);
+ }
+
/* -( PhabricatorApplicationTransactionInterface )------------------------- */
public function getApplicationTransactionEditor() {
return new PhabricatorProjectColumnTransactionEditor();
}
public function getApplicationTransactionObject() {
return $this;
}
public function getApplicationTransactionTemplate() {
return new PhabricatorProjectColumnTransaction();
}
public function willRenderTimeline(
PhabricatorApplicationTransactionView $timeline,
AphrontRequest $request) {
return $timeline;
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
);
}
public function getPolicy($capability) {
return $this->getProject()->getPolicy($capability);
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return $this->getProject()->hasAutomaticCapability(
$capability,
$viewer);
}
public function describeAutomaticCapability($capability) {
return pht('Users must be able to see a project to see its board.');
}
/* -( PhabricatorDestructibleInterface )----------------------------------- */
public function destroyObjectPermanently(
PhabricatorDestructionEngine $engine) {
$this->openTransaction();
$this->delete();
$this->saveTransaction();
}
}

File Metadata

Mime Type
text/x-diff
Expires
Thu, Jul 3, 6:56 PM (4 h, 1 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
166216
Default Alt Text
(37 KB)

Event Timeline