Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F2892497
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Advanced/Developer...
View Handle
View Hovercard
Size
21 KB
Referenced Files
None
Subscribers
None
View Options
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
Details
Attached
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)
Attached To
Mode
rP Phorge
Attached
Detach File
Event Timeline
Log In to Comment