Page MenuHomestyx hydra

No OneTemporary

diff --git a/src/applications/maniphest/editor/ManiphestTransactionEditor.php b/src/applications/maniphest/editor/ManiphestTransactionEditor.php
index 2c8f7b8c05..f4069bf183 100644
--- a/src/applications/maniphest/editor/ManiphestTransactionEditor.php
+++ b/src/applications/maniphest/editor/ManiphestTransactionEditor.php
@@ -1,542 +1,538 @@
<?php
final class ManiphestTransactionEditor
extends PhabricatorApplicationTransactionEditor {
private $heraldEmailPHIDs = array();
public function getTransactionTypes() {
$types = parent::getTransactionTypes();
$types[] = PhabricatorTransactions::TYPE_COMMENT;
$types[] = PhabricatorTransactions::TYPE_EDGE;
$types[] = ManiphestTransaction::TYPE_PRIORITY;
$types[] = ManiphestTransaction::TYPE_STATUS;
$types[] = ManiphestTransaction::TYPE_TITLE;
$types[] = ManiphestTransaction::TYPE_DESCRIPTION;
$types[] = ManiphestTransaction::TYPE_OWNER;
$types[] = ManiphestTransaction::TYPE_CCS;
$types[] = ManiphestTransaction::TYPE_SUBPRIORITY;
$types[] = ManiphestTransaction::TYPE_PROJECT_COLUMN;
$types[] = ManiphestTransaction::TYPE_UNBLOCK;
$types[] = PhabricatorTransactions::TYPE_VIEW_POLICY;
$types[] = PhabricatorTransactions::TYPE_EDIT_POLICY;
return $types;
}
protected function getCustomTransactionOldValue(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case ManiphestTransaction::TYPE_PRIORITY:
if ($this->getIsNewObject()) {
return null;
}
return (int)$object->getPriority();
case ManiphestTransaction::TYPE_STATUS:
if ($this->getIsNewObject()) {
return null;
}
return $object->getStatus();
case ManiphestTransaction::TYPE_TITLE:
if ($this->getIsNewObject()) {
return null;
}
return $object->getTitle();
case ManiphestTransaction::TYPE_DESCRIPTION:
if ($this->getIsNewObject()) {
return null;
}
return $object->getDescription();
case ManiphestTransaction::TYPE_OWNER:
return nonempty($object->getOwnerPHID(), null);
case ManiphestTransaction::TYPE_CCS:
return array_values(array_unique($object->getCCPHIDs()));
case ManiphestTransaction::TYPE_PROJECT_COLUMN:
// These are pre-populated.
return $xaction->getOldValue();
case ManiphestTransaction::TYPE_SUBPRIORITY:
return $object->getSubpriority();
}
}
protected function getCustomTransactionNewValue(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case ManiphestTransaction::TYPE_PRIORITY:
return (int)$xaction->getNewValue();
case ManiphestTransaction::TYPE_CCS:
return array_values(array_unique($xaction->getNewValue()));
case ManiphestTransaction::TYPE_OWNER:
return nonempty($xaction->getNewValue(), null);
case ManiphestTransaction::TYPE_STATUS:
case ManiphestTransaction::TYPE_TITLE:
case ManiphestTransaction::TYPE_DESCRIPTION:
case ManiphestTransaction::TYPE_SUBPRIORITY:
case ManiphestTransaction::TYPE_PROJECT_COLUMN:
case ManiphestTransaction::TYPE_UNBLOCK:
return $xaction->getNewValue();
}
}
protected function transactionHasEffect(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
$old = $xaction->getOldValue();
$new = $xaction->getNewValue();
switch ($xaction->getTransactionType()) {
case ManiphestTransaction::TYPE_CCS:
sort($old);
sort($new);
return ($old !== $new);
case ManiphestTransaction::TYPE_PROJECT_COLUMN:
$new_column_phids = $new['columnPHIDs'];
$old_column_phids = $old['columnPHIDs'];
sort($new_column_phids);
sort($old_column_phids);
return ($old !== $new);
}
return parent::transactionHasEffect($object, $xaction);
}
protected function applyCustomInternalTransaction(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case ManiphestTransaction::TYPE_PRIORITY:
return $object->setPriority($xaction->getNewValue());
case ManiphestTransaction::TYPE_STATUS:
return $object->setStatus($xaction->getNewValue());
case ManiphestTransaction::TYPE_TITLE:
return $object->setTitle($xaction->getNewValue());
case ManiphestTransaction::TYPE_DESCRIPTION:
return $object->setDescription($xaction->getNewValue());
case ManiphestTransaction::TYPE_OWNER:
$phid = $xaction->getNewValue();
// Update the "ownerOrdering" column to contain the full name of the
// owner, if the task is assigned.
$handle = null;
if ($phid) {
$handle = id(new PhabricatorHandleQuery())
->setViewer($this->getActor())
->withPHIDs(array($phid))
->executeOne();
}
if ($handle) {
$object->setOwnerOrdering($handle->getName());
} else {
$object->setOwnerOrdering(null);
}
return $object->setOwnerPHID($phid);
case ManiphestTransaction::TYPE_CCS:
return $object->setCCPHIDs($xaction->getNewValue());
case ManiphestTransaction::TYPE_SUBPRIORITY:
$data = $xaction->getNewValue();
$new_sub = $this->getNextSubpriority(
$data['newPriority'],
$data['newSubpriorityBase'],
$data['direction']);
$object->setSubpriority($new_sub);
return;
case ManiphestTransaction::TYPE_PROJECT_COLUMN:
// these do external (edge) updates
return;
}
}
protected function expandTransaction(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
$xactions = parent::expandTransaction($object, $xaction);
switch ($xaction->getTransactionType()) {
case ManiphestTransaction::TYPE_SUBPRIORITY:
$data = $xaction->getNewValue();
$new_pri = $data['newPriority'];
if ($new_pri != $object->getPriority()) {
$xactions[] = id(new ManiphestTransaction())
->setTransactionType(ManiphestTransaction::TYPE_PRIORITY)
->setNewValue($new_pri);
}
break;
default:
break;
}
return $xactions;
}
protected function applyCustomExternalTransaction(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case ManiphestTransaction::TYPE_PROJECT_COLUMN:
$board_phid = idx($xaction->getNewValue(), 'projectPHID');
if (!$board_phid) {
throw new Exception(
pht("Expected 'projectPHID' in column transaction."));
}
$new_phids = idx($xaction->getNewValue(), 'columnPHIDs', array());
if (count($new_phids) !== 1) {
throw new Exception(
pht("Expected exactly one 'columnPHIDs' in column transaction."));
}
$positions = id(new PhabricatorProjectColumnPositionQuery())
->setViewer($this->requireActor())
->withObjectPHIDs(array($object->getPHID()))
->withBoardPHIDs(array($board_phid))
->execute();
// Remove all existing column positions on the board.
foreach ($positions as $position) {
- if (!$position->getID()) {
- // This is an ephemeral position, so don't try to destroy it.
- continue;
- }
$position->delete();
}
// Add the new column position.
foreach ($new_phids as $phid) {
id(new PhabricatorProjectColumnPosition())
->setBoardPHID($board_phid)
->setColumnPHID($phid)
->setObjectPHID($object->getPHID())
// TODO: Do real sequence stuff.
->setSequence(0)
->save();
}
break;
default:
break;
}
}
protected function applyFinalEffects(
PhabricatorLiskDAO $object,
array $xactions) {
// When we change the status of a task, update tasks this tasks blocks
// with a message to the effect of "alincoln resolved blocking task Txxx."
$unblock_xaction = null;
foreach ($xactions as $xaction) {
switch ($xaction->getTransactionType()) {
case ManiphestTransaction::TYPE_STATUS:
$unblock_xaction = $xaction;
break;
}
}
if ($unblock_xaction !== null) {
$blocked_phids = PhabricatorEdgeQuery::loadDestinationPHIDs(
$object->getPHID(),
PhabricatorEdgeConfig::TYPE_TASK_DEPENDED_ON_BY_TASK);
if ($blocked_phids) {
// In theory we could apply these through policies, but that seems a
// little bit surprising. For now, use the actor's vision.
$blocked_tasks = id(new ManiphestTaskQuery())
->setViewer($this->getActor())
->withPHIDs($blocked_phids)
->execute();
$old = $unblock_xaction->getOldValue();
$new = $unblock_xaction->getNewValue();
foreach ($blocked_tasks as $blocked_task) {
$unblock_xactions = array();
$unblock_xactions[] = id(new ManiphestTransaction())
->setTransactionType(ManiphestTransaction::TYPE_UNBLOCK)
->setOldValue(array($object->getPHID() => $old))
->setNewValue(array($object->getPHID() => $new));
// TODO: We should avoid notifiying users about these indirect
// changes if they are getting a notification about the current
// change, so you don't get a pile of extra notifications if you are
// subscribed to this task.
id(new ManiphestTransactionEditor())
->setActor($this->getActor())
->setContentSource($this->getContentSource())
->setContinueOnNoEffect(true)
->setContinueOnMissingFields(true)
->applyTransactions($blocked_task, $unblock_xactions);
}
}
}
return $xactions;
}
protected function shouldSendMail(
PhabricatorLiskDAO $object,
array $xactions) {
$xactions = mfilter($xactions, 'shouldHide', true);
return $xactions;
}
protected function getMailSubjectPrefix() {
return PhabricatorEnv::getEnvConfig('metamta.maniphest.subject-prefix');
}
protected function getMailThreadID(PhabricatorLiskDAO $object) {
return 'maniphest-task-'.$object->getPHID();
}
protected function getMailTo(PhabricatorLiskDAO $object) {
return array(
$object->getOwnerPHID(),
$this->requireActor()->getPHID(),
);
}
protected function getMailCC(PhabricatorLiskDAO $object) {
$phids = array();
foreach ($object->getCCPHIDs() as $phid) {
$phids[] = $phid;
}
foreach (parent::getMailCC($object) as $phid) {
$phids[] = $phid;
}
foreach ($this->heraldEmailPHIDs as $phid) {
$phids[] = $phid;
}
return $phids;
}
protected function buildReplyHandler(PhabricatorLiskDAO $object) {
return id(new ManiphestReplyHandler())
->setMailReceiver($object);
}
protected function buildMailTemplate(PhabricatorLiskDAO $object) {
$id = $object->getID();
$title = $object->getTitle();
return id(new PhabricatorMetaMTAMail())
->setSubject("T{$id}: {$title}")
->addHeader('Thread-Topic', "T{$id}: ".$object->getOriginalTitle());
}
protected function buildMailBody(
PhabricatorLiskDAO $object,
array $xactions) {
$body = parent::buildMailBody($object, $xactions);
if ($this->getIsNewObject()) {
$body->addTextSection(
pht('TASK DESCRIPTION'),
$object->getDescription());
}
$body->addTextSection(
pht('TASK DETAIL'),
PhabricatorEnv::getProductionURI('/T'.$object->getID()));
return $body;
}
protected function shouldPublishFeedStory(
PhabricatorLiskDAO $object,
array $xactions) {
return $this->shouldSendMail($object, $xactions);
}
protected function supportsSearch() {
return true;
}
protected function shouldApplyHeraldRules(
PhabricatorLiskDAO $object,
array $xactions) {
return true;
}
protected function buildHeraldAdapter(
PhabricatorLiskDAO $object,
array $xactions) {
return id(new HeraldManiphestTaskAdapter())
->setTask($object);
}
protected function didApplyHeraldRules(
PhabricatorLiskDAO $object,
HeraldAdapter $adapter,
HeraldTranscript $transcript) {
// TODO: Convert these to transactions. The way Maniphest deals with these
// transactions is currently unconventional and messy.
$save_again = false;
$cc_phids = $adapter->getCcPHIDs();
if ($cc_phids) {
$existing_cc = $object->getCCPHIDs();
$new_cc = array_unique(array_merge($cc_phids, $existing_cc));
$object->setCCPHIDs($new_cc);
$object->save();
}
$this->heraldEmailPHIDs = $adapter->getEmailPHIDs();
$xactions = array();
$assign_phid = $adapter->getAssignPHID();
if ($assign_phid) {
$xactions[] = id(new ManiphestTransaction())
->setTransactionType(ManiphestTransaction::TYPE_OWNER)
->setNewValue($assign_phid);
}
$project_phids = $adapter->getProjectPHIDs();
if ($project_phids) {
$project_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST;
$xactions[] = id(new ManiphestTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
->setMetadataValue('edge:type', $project_type)
->setNewValue(
array(
'+' => array_fuse($project_phids),
));
}
return $xactions;
}
protected function requireCapabilities(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
parent::requireCapabilities($object, $xaction);
$app_capability_map = array(
ManiphestTransaction::TYPE_PRIORITY =>
ManiphestEditPriorityCapability::CAPABILITY,
ManiphestTransaction::TYPE_STATUS =>
ManiphestEditStatusCapability::CAPABILITY,
ManiphestTransaction::TYPE_OWNER =>
ManiphestEditAssignCapability::CAPABILITY,
PhabricatorTransactions::TYPE_EDIT_POLICY =>
ManiphestEditPoliciesCapability::CAPABILITY,
PhabricatorTransactions::TYPE_VIEW_POLICY =>
ManiphestEditPoliciesCapability::CAPABILITY,
);
$transaction_type = $xaction->getTransactionType();
$app_capability = null;
if ($transaction_type == PhabricatorTransactions::TYPE_EDGE) {
switch ($xaction->getMetadataValue('edge:type')) {
case PhabricatorProjectObjectHasProjectEdgeType::EDGECONST:
$app_capability = ManiphestEditProjectsCapability::CAPABILITY;
break;
}
} else {
$app_capability = idx($app_capability_map, $transaction_type);
}
if ($app_capability) {
$app = id(new PhabricatorApplicationQuery())
->setViewer($this->getActor())
->withClasses(array('PhabricatorManiphestApplication'))
->executeOne();
PhabricatorPolicyFilter::requireCapability(
$this->getActor(),
$app,
$app_capability);
}
}
protected function adjustObjectForPolicyChecks(
PhabricatorLiskDAO $object,
array $xactions) {
$copy = parent::adjustObjectForPolicyChecks($object, $xactions);
foreach ($xactions as $xaction) {
switch ($xaction->getTransactionType()) {
case ManiphestTransaction::TYPE_OWNER:
$copy->setOwnerPHID($xaction->getNewValue());
break;
default:
continue;
}
}
return $copy;
}
private function getNextSubpriority($pri, $sub, $dir = '>') {
switch ($dir) {
case '>':
$order = 'ASC';
break;
case '<':
$order = 'DESC';
break;
default:
throw new Exception('$dir must be ">" or "<".');
break;
}
if ($sub === null) {
$base = 0;
} else {
$base = $sub;
}
if ($sub === null) {
$next = id(new ManiphestTask())->loadOneWhere(
'priority = %d ORDER BY subpriority %Q LIMIT 1',
$pri,
$order);
if ($next) {
if ($dir == '>') {
return $next->getSubpriority() - ((double)(2 << 16));
} else {
return $next->getSubpriority() + ((double)(2 << 16));
}
}
} else {
$next = id(new ManiphestTask())->loadOneWhere(
'priority = %d AND subpriority %Q %f ORDER BY subpriority %Q LIMIT 1',
$pri,
$dir,
$sub,
$order);
if ($next) {
return ($sub + $next->getSubpriority()) / 2;
}
}
if ($dir == '>') {
return $base + (double)(2 << 32);
} else {
return $base - (double)(2 << 32);
}
}
}
diff --git a/src/applications/project/query/PhabricatorProjectColumnPositionQuery.php b/src/applications/project/query/PhabricatorProjectColumnPositionQuery.php
index 27786737e0..60e9b2de58 100644
--- a/src/applications/project/query/PhabricatorProjectColumnPositionQuery.php
+++ b/src/applications/project/query/PhabricatorProjectColumnPositionQuery.php
@@ -1,258 +1,290 @@
<?php
final class PhabricatorProjectColumnPositionQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $boardPHIDs;
private $objectPHIDs;
private $columns;
private $needColumns;
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;
}
// 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() {
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) {
$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) {
- $position->makeEphemeral();
$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;
}
private function buildWhereClause($conn_r) {
$where = array();
if ($this->ids !== null) {
$where[] = qsprintf(
$conn_r,
'id IN (%Ld)',
$this->ids);
}
if ($this->boardPHIDs !== null) {
$where[] = qsprintf(
$conn_r,
'boardPHID IN (%Ls)',
$this->boardPHIDs);
}
if ($this->objectPHIDs !== null) {
$where[] = qsprintf(
$conn_r,
'objectPHID IN (%Ls)',
$this->objectPHIDs);
}
if ($this->columns !== null) {
$where[] = qsprintf(
$conn_r,
'columnPHID IN (%Ls)',
mpull($this->columns, 'getPHID'));
}
// NOTE: Explicitly not building the paging clause here, since it won't
// work with the UNION.
return $this->formatWhereClause($where);
}
public function getQueryApplicationClass() {
return 'PhabricatorProjectApplication';
}
}

File Metadata

Mime Type
text/x-diff
Expires
Fri, Nov 14, 5:30 PM (1 d, 1 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
337246
Default Alt Text
(25 KB)

Event Timeline