Page MenuHomePhorge

No OneTemporary

diff --git a/src/applications/maniphest/editor/ManiphestEditEngine.php b/src/applications/maniphest/editor/ManiphestEditEngine.php
index e338450aa9..6133838975 100644
--- a/src/applications/maniphest/editor/ManiphestEditEngine.php
+++ b/src/applications/maniphest/editor/ManiphestEditEngine.php
@@ -1,352 +1,353 @@
<?php
final class ManiphestEditEngine
extends PhabricatorEditEngine {
const ENGINECONST = '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))
+ ->withBoardPHIDs(array($column->getProjectPHID()))
+ ->withColumnPHIDs(array($column->getPHID()))
->execute();
$task_phids = mpull($positions, 'getObjectPHID');
$column_tasks = id(new ManiphestTaskQuery())
->setViewer($viewer)
->withPHIDs($task_phids)
->needProjectPHIDs(true)
->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();
}
$tasks = id(new ProjectBoardTaskCard())
->setViewer($viewer)
->setTask($task)
->setOwner($owner)
->setProject($column->getProject())
->setCanEdit(true)
->getItem();
$tasks->addClass('phui-workcard');
$payload = array(
'tasks' => $tasks,
'data' => $data,
);
return id(new AphrontAjaxResponse())->setContent($payload);
}
}
diff --git a/src/applications/project/query/PhabricatorProjectColumnPositionQuery.php b/src/applications/project/query/PhabricatorProjectColumnPositionQuery.php
index 3348b4054d..438c558e6e 100644
--- a/src/applications/project/query/PhabricatorProjectColumnPositionQuery.php
+++ b/src/applications/project/query/PhabricatorProjectColumnPositionQuery.php
@@ -1,306 +1,77 @@
<?php
final class PhabricatorProjectColumnPositionQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $boardPHIDs;
private $objectPHIDs;
- private $columns;
-
- private $needColumns;
- private $skipImplicitCreate;
+ private $columnPHIDs;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withBoardPHIDs(array $board_phids) {
$this->boardPHIDs = $board_phids;
return $this;
}
public function withObjectPHIDs(array $object_phids) {
$this->objectPHIDs = $object_phids;
return $this;
}
- /**
- * Find objects in specific columns.
- *
- * NOTE: Using this method activates logic which constructs virtual
- * column positions for objects not in any column, if you pass a default
- * column. Normally these results are not returned.
- *
- * @param list<PhabricatorProjectColumn> Columns to look for objects in.
- * @return this
- */
- public function withColumns(array $columns) {
- assert_instances_of($columns, 'PhabricatorProjectColumn');
- $this->columns = $columns;
- return $this;
- }
-
- public function needColumns($need_columns) {
- $this->needColumns = true;
- return $this;
- }
-
-
- /**
- * Skip implicit creation of column positions which are implied but do not
- * yet exist.
- *
- * This is primarily useful internally.
- *
- * @param bool True to skip implicit creation of column positions.
- * @return this
- */
- public function setSkipImplicitCreate($skip) {
- $this->skipImplicitCreate = $skip;
+ public function withColumnPHIDs(array $column_phids) {
+ $this->columnPHIDs = $column_phids;
return $this;
}
- // NOTE: For now, boards are always attached to projects. However, they might
- // not be in the future. This generalization just anticipates a future where
- // we let other types of objects (like users) have boards, or let boards
- // contain other types of objects.
-
- private function newPositionObject() {
+ public function newResultObject() {
return new PhabricatorProjectColumnPosition();
}
- private function newColumnQuery() {
- return new PhabricatorProjectColumnQuery();
- }
-
- private function getBoardMembershipEdgeTypes() {
- return array(
- PhabricatorProjectProjectHasObjectEdgeType::EDGECONST,
- );
- }
-
- private function getBoardMembershipPHIDTypes() {
- return array(
- ManiphestTaskPHIDType::TYPECONST,
- );
- }
-
protected function loadPage() {
- $table = $this->newPositionObject();
- $conn_r = $table->establishConnection('r');
-
- // We're going to find results by combining two queries: one query finds
- // objects on a board column, while the other query finds objects not on
- // any board column and virtually puts them on the default column.
-
- $unions = array();
-
- // First, find all the stuff that's actually on a column.
-
- $unions[] = qsprintf(
- $conn_r,
- 'SELECT * FROM %T %Q',
- $table->getTableName(),
- $this->buildWhereClause($conn_r));
-
- // If we have a default column, find all the stuff that's not in any
- // column and put it in the default column.
-
- $must_type_filter = false;
- if ($this->columns && !$this->skipImplicitCreate) {
- $default_map = array();
- foreach ($this->columns as $column) {
- if ($column->isDefaultColumn()) {
- $default_map[$column->getProjectPHID()] = $column->getPHID();
- }
- }
-
- if ($default_map) {
- $where = array();
-
- // Find the edges attached to the boards we have default columns for.
-
- $where[] = qsprintf(
- $conn_r,
- 'e.src IN (%Ls)',
- array_keys($default_map));
-
- // Find only edges which describe a board relationship.
-
- $where[] = qsprintf(
- $conn_r,
- 'e.type IN (%Ld)',
- $this->getBoardMembershipEdgeTypes());
-
- if ($this->boardPHIDs !== null) {
- // This should normally be redundant, but construct it anyway if
- // the caller has told us to.
- $where[] = qsprintf(
- $conn_r,
- 'e.src IN (%Ls)',
- $this->boardPHIDs);
- }
-
- if ($this->objectPHIDs !== null) {
- $where[] = qsprintf(
- $conn_r,
- 'e.dst IN (%Ls)',
- $this->objectPHIDs);
- }
-
- $where[] = qsprintf(
- $conn_r,
- 'p.id IS NULL');
-
- $where = $this->formatWhereClause($where);
-
- $unions[] = qsprintf(
- $conn_r,
- 'SELECT NULL id, e.src boardPHID, NULL columnPHID, e.dst objectPHID,
- 0 sequence
- FROM %T e LEFT JOIN %T p
- ON e.src = p.boardPHID AND e.dst = p.objectPHID
- %Q',
- PhabricatorEdgeConfig::TABLE_NAME_EDGE,
- $table->getTableName(),
- $where);
-
- $must_type_filter = true;
- }
- }
-
- $data = queryfx_all(
- $conn_r,
- '%Q %Q %Q',
- implode(' UNION ALL ', $unions),
- $this->buildOrderClause($conn_r),
- $this->buildLimitClause($conn_r));
-
- // If we've picked up objects not in any column, we need to filter out any
- // matched objects which have the wrong edge type.
- if ($must_type_filter) {
- $allowed_types = array_fuse($this->getBoardMembershipPHIDTypes());
- foreach ($data as $id => $row) {
- if ($row['columnPHID'] === null) {
- $object_phid = $row['objectPHID'];
- if (empty($allowed_types[phid_get_type($object_phid)])) {
- unset($data[$id]);
- }
- }
- }
- }
-
- $positions = $table->loadAllFromArray($data);
-
- // Find the implied positions which don't exist yet. If there are any,
- // we're going to create them.
- $create = array();
- foreach ($positions as $position) {
- if ($position->getColumnPHID() === null) {
- $column_phid = idx($default_map, $position->getBoardPHID());
- $position->setColumnPHID($column_phid);
-
- $create[] = $position;
- }
- }
-
- if ($create) {
- // If we're adding several objects to a column, insert the column
- // position objects in object ID order. This means that newly added
- // objects float to the top, and when a group of newly added objects
- // float up at the same time, the most recently created ones end up
- // highest in the list.
-
- $objects = id(new PhabricatorObjectQuery())
- ->setViewer(PhabricatorUser::getOmnipotentUser())
- ->withPHIDs(mpull($create, 'getObjectPHID'))
- ->execute();
- $objects = mpull($objects, null, 'getPHID');
- $objects = msort($objects, 'getID');
-
- $create = mgroup($create, 'getObjectPHID');
- $create = array_select_keys($create, array_keys($objects)) + $create;
-
- $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
-
- foreach ($create as $object_phid => $create_positions) {
- foreach ($create_positions as $create_position) {
- $create_position->save();
- }
- }
-
- unset($unguarded);
- }
-
- return $positions;
- }
-
- protected function willFilterPage(array $page) {
-
- if ($this->needColumns) {
- $column_phids = mpull($page, 'getColumnPHID');
- $columns = $this->newColumnQuery()
- ->setParentQuery($this)
- ->setViewer($this->getViewer())
- ->withPHIDs($column_phids)
- ->execute();
- $columns = mpull($columns, null, 'getPHID');
-
- foreach ($page as $key => $position) {
- $column = idx($columns, $position->getColumnPHID());
- if (!$column) {
- unset($page[$key]);
- continue;
- }
-
- $position->attachColumn($column);
- }
- }
-
- return $page;
+ return $this->loadStandardPage($this->newResultObject());
}
- protected function buildWhereClause(AphrontDatabaseConnection $conn_r) {
+ protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = array();
if ($this->ids !== null) {
$where[] = qsprintf(
- $conn_r,
+ $conn,
'id IN (%Ld)',
$this->ids);
}
if ($this->boardPHIDs !== null) {
$where[] = qsprintf(
- $conn_r,
+ $conn,
'boardPHID IN (%Ls)',
$this->boardPHIDs);
}
if ($this->objectPHIDs !== null) {
$where[] = qsprintf(
- $conn_r,
+ $conn,
'objectPHID IN (%Ls)',
$this->objectPHIDs);
}
- if ($this->columns !== null) {
+ if ($this->columnPHIDs !== null) {
$where[] = qsprintf(
- $conn_r,
+ $conn,
'columnPHID IN (%Ls)',
- mpull($this->columns, 'getPHID'));
+ $this->columnPHIDs);
}
- // NOTE: Explicitly not building the paging clause here, since it won't
- // work with the UNION.
-
- return $this->formatWhereClause($where);
+ return $where;
}
public function getQueryApplicationClass() {
return 'PhabricatorProjectApplication';
}
}

File Metadata

Mime Type
text/x-diff
Expires
Jan 19 2025, 16:54 (7 w, 21 h ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1126682
Default Alt Text
(21 KB)

Event Timeline