2011-05-16 20:43:39 +02:00
|
|
|
<?php
|
|
|
|
|
2011-09-14 17:02:31 +02:00
|
|
|
/**
|
|
|
|
* @group search
|
|
|
|
*/
|
2012-03-10 00:46:25 +01:00
|
|
|
final class PhabricatorSearchAttachController
|
|
|
|
extends PhabricatorSearchBaseController {
|
2011-05-16 20:43:39 +02:00
|
|
|
|
|
|
|
private $phid;
|
|
|
|
private $type;
|
2011-06-14 16:55:04 +02:00
|
|
|
private $action;
|
|
|
|
|
2011-07-05 22:18:47 +02:00
|
|
|
const ACTION_ATTACH = 'attach';
|
|
|
|
const ACTION_MERGE = 'merge';
|
|
|
|
const ACTION_DEPENDENCIES = 'dependencies';
|
2012-04-05 02:34:25 +02:00
|
|
|
const ACTION_EDGE = 'edge';
|
2011-05-16 20:43:39 +02:00
|
|
|
|
|
|
|
public function willProcessRequest(array $data) {
|
|
|
|
$this->phid = $data['phid'];
|
|
|
|
$this->type = $data['type'];
|
2011-06-14 16:55:04 +02:00
|
|
|
$this->action = idx($data, 'action', self::ACTION_ATTACH);
|
2011-05-16 20:43:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public function processRequest() {
|
|
|
|
|
|
|
|
$request = $this->getRequest();
|
|
|
|
$user = $request->getUser();
|
|
|
|
|
2011-07-25 18:42:26 +02:00
|
|
|
$handle_data = new PhabricatorObjectHandleData(array($this->phid));
|
2013-03-01 02:15:09 +01:00
|
|
|
$handle_data->setViewer($user);
|
2011-07-25 18:42:26 +02:00
|
|
|
$handles = $handle_data->loadHandles();
|
2011-05-16 20:43:39 +02:00
|
|
|
$handle = $handles[$this->phid];
|
|
|
|
|
|
|
|
$object_type = $handle->getType();
|
|
|
|
$attach_type = $this->type;
|
|
|
|
|
2011-07-25 18:42:26 +02:00
|
|
|
$objects = $handle_data->loadObjects();
|
|
|
|
$object = idx($objects, $this->phid);
|
2011-05-16 20:43:39 +02:00
|
|
|
|
|
|
|
if (!$object) {
|
|
|
|
return new Aphront404Response();
|
|
|
|
}
|
|
|
|
|
2012-07-19 05:41:42 +02:00
|
|
|
$edge_type = null;
|
|
|
|
switch ($this->action) {
|
|
|
|
case self::ACTION_EDGE:
|
|
|
|
case self::ACTION_DEPENDENCIES:
|
2012-07-20 17:59:39 +02:00
|
|
|
case self::ACTION_ATTACH:
|
2012-07-19 05:41:42 +02:00
|
|
|
$edge_type = $this->getEdgeType($object_type, $attach_type);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2011-05-16 20:43:39 +02:00
|
|
|
if ($request->isFormPost()) {
|
|
|
|
$phids = explode(';', $request->getStr('phids'));
|
|
|
|
$phids = array_filter($phids);
|
|
|
|
$phids = array_values($phids);
|
2011-06-14 16:55:04 +02:00
|
|
|
|
2012-07-19 05:41:42 +02:00
|
|
|
if ($edge_type) {
|
2013-07-20 00:59:29 +02:00
|
|
|
$do_txn = $object instanceof PhabricatorApplicationTransactionInterface;
|
2012-07-19 05:41:42 +02:00
|
|
|
$old_phids = PhabricatorEdgeQuery::loadDestinationPHIDs(
|
|
|
|
$this->phid,
|
|
|
|
$edge_type);
|
|
|
|
$add_phids = $phids;
|
|
|
|
$rem_phids = array_diff($old_phids, $add_phids);
|
|
|
|
|
2013-07-20 00:59:29 +02:00
|
|
|
if ($do_txn) {
|
|
|
|
|
|
|
|
$txn_editor = $object->getApplicationTransactionEditor()
|
|
|
|
->setActor($user)
|
|
|
|
->setContentSourceFromRequest($request);
|
|
|
|
$txn_template = $object->getApplicationTransactionObject()
|
|
|
|
->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
|
|
|
|
->setMetadataValue('edge:type', $edge_type)
|
|
|
|
->setNewValue(array(
|
|
|
|
'+' => array_fuse($add_phids),
|
|
|
|
'-' => array_fuse($rem_phids)));
|
|
|
|
$txn_editor->applyTransactions($object, array($txn_template));
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
$editor = id(new PhabricatorEdgeEditor());
|
|
|
|
$editor->setActor($user);
|
|
|
|
foreach ($add_phids as $phid) {
|
|
|
|
$editor->addEdge($this->phid, $edge_type, $phid);
|
|
|
|
}
|
|
|
|
foreach ($rem_phids as $phid) {
|
|
|
|
$editor->removeEdge($this->phid, $edge_type, $phid);
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
$editor->save();
|
|
|
|
} catch (PhabricatorEdgeCycleException $ex) {
|
|
|
|
$this->raiseGraphCycleException($ex);
|
|
|
|
}
|
2012-07-19 05:41:42 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return id(new AphrontReloadResponse())->setURI($handle->getURI());
|
|
|
|
} else {
|
2012-07-20 17:59:39 +02:00
|
|
|
return $this->performMerge($object, $handle, $phids);
|
2011-06-14 16:55:04 +02:00
|
|
|
}
|
2011-05-16 20:43:39 +02:00
|
|
|
} else {
|
2012-07-19 05:41:42 +02:00
|
|
|
if ($edge_type) {
|
|
|
|
$phids = PhabricatorEdgeQuery::loadDestinationPHIDs(
|
|
|
|
$this->phid,
|
|
|
|
$edge_type);
|
|
|
|
} else {
|
2012-07-20 17:59:39 +02:00
|
|
|
// This is a merge.
|
|
|
|
$phids = array();
|
2011-06-14 16:55:04 +02:00
|
|
|
}
|
2011-05-16 20:43:39 +02:00
|
|
|
}
|
|
|
|
|
2011-07-25 19:50:34 +02:00
|
|
|
$strings = $this->getStrings();
|
2011-06-14 16:55:04 +02:00
|
|
|
|
2012-09-05 04:02:56 +02:00
|
|
|
$handles = $this->loadViewerHandles($phids);
|
2011-05-16 20:43:39 +02:00
|
|
|
|
|
|
|
$obj_dialog = new PhabricatorObjectSelectorDialog();
|
|
|
|
$obj_dialog
|
|
|
|
->setUser($user)
|
|
|
|
->setHandles($handles)
|
2013-07-20 00:59:29 +02:00
|
|
|
->setFilters($this->getFilters($strings))
|
2011-07-25 19:50:34 +02:00
|
|
|
->setSelectedFilter($strings['selected'])
|
2012-04-04 03:34:55 +02:00
|
|
|
->setExcluded($this->phid)
|
2011-05-16 20:43:39 +02:00
|
|
|
->setCancelURI($handle->getURI())
|
|
|
|
->setSearchURI('/search/select/'.$attach_type.'/')
|
2011-07-25 19:50:34 +02:00
|
|
|
->setTitle($strings['title'])
|
|
|
|
->setHeader($strings['header'])
|
|
|
|
->setButtonText($strings['button'])
|
|
|
|
->setInstructions($strings['instructions']);
|
2011-05-16 20:43:39 +02:00
|
|
|
|
|
|
|
$dialog = $obj_dialog->buildDialog();
|
|
|
|
|
|
|
|
return id(new AphrontDialogResponse())->setDialog($dialog);
|
|
|
|
}
|
|
|
|
|
2011-06-14 16:55:04 +02:00
|
|
|
private function performMerge(
|
|
|
|
ManiphestTask $task,
|
|
|
|
PhabricatorObjectHandle $handle,
|
|
|
|
array $phids) {
|
|
|
|
|
|
|
|
$user = $this->getRequest()->getUser();
|
|
|
|
$response = id(new AphrontReloadResponse())->setURI($handle->getURI());
|
|
|
|
|
|
|
|
$phids = array_fill_keys($phids, true);
|
|
|
|
unset($phids[$task->getPHID()]); // Prevent merging a task into itself.
|
|
|
|
|
|
|
|
if (!$phids) {
|
|
|
|
return $response;
|
|
|
|
}
|
|
|
|
|
|
|
|
$targets = id(new ManiphestTask())->loadAllWhere(
|
|
|
|
'phid in (%Ls) ORDER BY id ASC',
|
|
|
|
array_keys($phids));
|
|
|
|
|
|
|
|
if (empty($targets)) {
|
|
|
|
return $response;
|
|
|
|
}
|
|
|
|
|
|
|
|
$editor = new ManiphestTransactionEditor();
|
2012-10-10 19:18:23 +02:00
|
|
|
$editor->setActor($user);
|
2011-06-14 16:55:04 +02:00
|
|
|
|
|
|
|
$task_names = array();
|
|
|
|
|
|
|
|
$merge_into_name = 'T'.$task->getID();
|
|
|
|
|
|
|
|
$cc_vector = array();
|
|
|
|
$cc_vector[] = $task->getCCPHIDs();
|
|
|
|
foreach ($targets as $target) {
|
|
|
|
$cc_vector[] = $target->getCCPHIDs();
|
|
|
|
$cc_vector[] = array(
|
|
|
|
$target->getAuthorPHID(),
|
|
|
|
$target->getOwnerPHID());
|
|
|
|
|
|
|
|
$close_task = id(new ManiphestTransaction())
|
|
|
|
->setAuthorPHID($user->getPHID())
|
|
|
|
->setTransactionType(ManiphestTransactionType::TYPE_STATUS)
|
|
|
|
->setNewValue(ManiphestTaskStatus::STATUS_CLOSED_DUPLICATE)
|
|
|
|
->setComments("\xE2\x9C\x98 Merged into {$merge_into_name}.");
|
|
|
|
|
|
|
|
$editor->applyTransactions($target, array($close_task));
|
|
|
|
|
|
|
|
$task_names[] = 'T'.$target->getID();
|
|
|
|
}
|
|
|
|
$all_ccs = array_mergev($cc_vector);
|
|
|
|
$all_ccs = array_filter($all_ccs);
|
|
|
|
$all_ccs = array_unique($all_ccs);
|
|
|
|
|
|
|
|
$task_names = implode(', ', $task_names);
|
|
|
|
|
|
|
|
$add_ccs = id(new ManiphestTransaction())
|
|
|
|
->setAuthorPHID($user->getPHID())
|
|
|
|
->setTransactionType(ManiphestTransactionType::TYPE_CCS)
|
|
|
|
->setNewValue($all_ccs)
|
|
|
|
->setComments("\xE2\x97\x80 Merged tasks: {$task_names}.");
|
|
|
|
$editor->applyTransactions($task, array($add_ccs));
|
|
|
|
|
|
|
|
return $response;
|
|
|
|
}
|
2011-07-25 19:50:34 +02:00
|
|
|
|
|
|
|
private function getStrings() {
|
|
|
|
switch ($this->type) {
|
|
|
|
case PhabricatorPHIDConstants::PHID_TYPE_DREV:
|
|
|
|
$noun = 'Revisions';
|
|
|
|
$selected = 'created';
|
|
|
|
break;
|
|
|
|
case PhabricatorPHIDConstants::PHID_TYPE_TASK:
|
|
|
|
$noun = 'Tasks';
|
|
|
|
$selected = 'assigned';
|
|
|
|
break;
|
2012-04-05 02:34:25 +02:00
|
|
|
case PhabricatorPHIDConstants::PHID_TYPE_CMIT:
|
|
|
|
$noun = 'Commits';
|
|
|
|
$selected = 'created';
|
|
|
|
break;
|
2013-07-20 00:59:29 +02:00
|
|
|
case PhabricatorPHIDConstants::PHID_TYPE_MOCK:
|
|
|
|
$noun = 'Mocks';
|
|
|
|
$selected = 'created';
|
|
|
|
break;
|
2011-07-25 19:50:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
switch ($this->action) {
|
2012-04-05 02:34:25 +02:00
|
|
|
case self::ACTION_EDGE:
|
2011-07-25 19:50:34 +02:00
|
|
|
case self::ACTION_ATTACH:
|
|
|
|
$dialog_title = "Manage Attached {$noun}";
|
|
|
|
$header_text = "Currently Attached {$noun}";
|
|
|
|
$button_text = "Save {$noun}";
|
|
|
|
$instructions = null;
|
|
|
|
break;
|
|
|
|
case self::ACTION_MERGE:
|
|
|
|
$dialog_title = "Merge Duplicate Tasks";
|
|
|
|
$header_text = "Tasks To Merge";
|
|
|
|
$button_text = "Merge {$noun}";
|
|
|
|
$instructions =
|
|
|
|
"These tasks will be merged into the current task and then closed. ".
|
|
|
|
"The current task will grow stronger.";
|
|
|
|
break;
|
2011-07-05 22:18:47 +02:00
|
|
|
case self::ACTION_DEPENDENCIES:
|
|
|
|
$dialog_title = "Edit Dependencies";
|
|
|
|
$header_text = "Current Dependencies";
|
|
|
|
$button_text = "Save Dependencies";
|
|
|
|
$instructions = null;
|
|
|
|
break;
|
2011-07-25 19:50:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return array(
|
|
|
|
'target_plural_noun' => $noun,
|
|
|
|
'selected' => $selected,
|
|
|
|
'title' => $dialog_title,
|
|
|
|
'header' => $header_text,
|
|
|
|
'button' => $button_text,
|
|
|
|
'instructions' => $instructions,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2013-07-20 00:59:29 +02:00
|
|
|
private function getFilters(array $strings) {
|
|
|
|
if ($this->type == PhabricatorPHIDConstants::PHID_TYPE_MOCK) {
|
|
|
|
$filters = array(
|
|
|
|
'created' => 'Created By Me',
|
|
|
|
'all' => 'All '.$strings['target_plural_noun'],
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
$filters = array(
|
|
|
|
'assigned' => 'Assigned to Me',
|
|
|
|
'created' => 'Created By Me',
|
|
|
|
'open' => 'All Open '.$strings['target_plural_noun'],
|
|
|
|
'all' => 'All '.$strings['target_plural_noun'],
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $filters;
|
|
|
|
}
|
|
|
|
|
2012-04-05 02:34:25 +02:00
|
|
|
private function getEdgeType($src_type, $dst_type) {
|
|
|
|
$t_cmit = PhabricatorPHIDConstants::PHID_TYPE_CMIT;
|
|
|
|
$t_task = PhabricatorPHIDConstants::PHID_TYPE_TASK;
|
2012-07-19 05:42:06 +02:00
|
|
|
$t_drev = PhabricatorPHIDConstants::PHID_TYPE_DREV;
|
2013-07-20 00:59:29 +02:00
|
|
|
$t_mock = PhabricatorPHIDConstants::PHID_TYPE_MOCK;
|
2012-04-05 02:34:25 +02:00
|
|
|
|
|
|
|
$map = array(
|
|
|
|
$t_cmit => array(
|
|
|
|
$t_task => PhabricatorEdgeConfig::TYPE_COMMIT_HAS_TASK,
|
|
|
|
),
|
|
|
|
$t_task => array(
|
|
|
|
$t_cmit => PhabricatorEdgeConfig::TYPE_TASK_HAS_COMMIT,
|
2012-07-19 05:41:42 +02:00
|
|
|
$t_task => PhabricatorEdgeConfig::TYPE_TASK_DEPENDS_ON_TASK,
|
2012-07-20 17:59:39 +02:00
|
|
|
$t_drev => PhabricatorEdgeConfig::TYPE_TASK_HAS_RELATED_DREV,
|
2013-07-20 00:59:29 +02:00
|
|
|
$t_mock => PhabricatorEdgeConfig::TYPE_TASK_HAS_MOCK,
|
2012-04-05 02:34:25 +02:00
|
|
|
),
|
2012-07-19 05:42:06 +02:00
|
|
|
$t_drev => array(
|
|
|
|
$t_drev => PhabricatorEdgeConfig::TYPE_DREV_DEPENDS_ON_DREV,
|
2012-07-20 17:59:39 +02:00
|
|
|
$t_task => PhabricatorEdgeConfig::TYPE_DREV_HAS_RELATED_TASK,
|
2012-07-19 05:42:06 +02:00
|
|
|
),
|
2013-07-20 00:59:29 +02:00
|
|
|
$t_mock => array(
|
|
|
|
$t_task => PhabricatorEdgeConfig::TYPE_MOCK_HAS_TASK,
|
|
|
|
),
|
2012-04-05 02:34:25 +02:00
|
|
|
);
|
|
|
|
|
|
|
|
if (empty($map[$src_type][$dst_type])) {
|
2012-07-19 05:41:42 +02:00
|
|
|
return null;
|
2012-04-05 02:34:25 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return $map[$src_type][$dst_type];
|
|
|
|
}
|
|
|
|
|
2012-07-19 05:41:42 +02:00
|
|
|
private function raiseGraphCycleException(PhabricatorEdgeCycleException $ex) {
|
|
|
|
$cycle = $ex->getCycle();
|
|
|
|
|
2012-09-05 04:02:56 +02:00
|
|
|
$handles = $this->loadViewerHandles($cycle);
|
2012-07-19 05:41:42 +02:00
|
|
|
$names = array();
|
|
|
|
foreach ($cycle as $cycle_phid) {
|
|
|
|
$names[] = $handles[$cycle_phid]->getFullName();
|
|
|
|
}
|
|
|
|
$names = implode(" \xE2\x86\x92 ", $names);
|
|
|
|
throw new Exception(
|
|
|
|
"You can not create that dependency, because it would create a ".
|
|
|
|
"circular dependency: {$names}.");
|
|
|
|
}
|
|
|
|
|
2011-05-16 20:43:39 +02:00
|
|
|
}
|