mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-15 11:22:40 +01:00
2a7545a452
Summary: Ref T4788. Fixes T7820. This updates the "Merge Duplicates In" interaction, and adds a "Close as Duplicate" action. These are the last interactions that were using the old code, so it removes that code. Merges are now recorded as real edges, so we can show them in the UI later on (originally from T9390, etc). Also provides more general support for relationships which need EDIT permission, not-undoable relationships like merges, preventing relating an object to itself, and relationship side effects like merges. Finally, fixes a couple of behaviors around typing an exact object name (like `T123`) to find the related object. Test Plan: - Merged tasks into the current task. - Closed the current task as a duplicate of another task. - Edited other relationships. - Searched for tasks, commits, etc., by object monogram. Reviewers: chad Reviewed By: chad Maniphest Tasks: T4788, T7820 Differential Revision: https://secure.phabricator.com/D16196
219 lines
6.9 KiB
PHP
219 lines
6.9 KiB
PHP
<?php
|
|
|
|
final class PhabricatorSearchRelationshipController
|
|
extends PhabricatorSearchBaseController {
|
|
|
|
public function handleRequest(AphrontRequest $request) {
|
|
$viewer = $this->getViewer();
|
|
|
|
$object = $this->loadRelationshipObject();
|
|
if (!$object) {
|
|
return new Aphront404Response();
|
|
}
|
|
|
|
$relationship = $this->loadRelationship($object);
|
|
if (!$relationship) {
|
|
return new Aphront404Response();
|
|
}
|
|
|
|
$src_phid = $object->getPHID();
|
|
$edge_type = $relationship->getEdgeConstant();
|
|
|
|
// If this is a normal relationship, users can remove related objects. If
|
|
// it's a special relationship like a merge, we can't undo it, so we won't
|
|
// prefill the current related objects.
|
|
if ($relationship->canUndoRelationship()) {
|
|
$dst_phids = PhabricatorEdgeQuery::loadDestinationPHIDs(
|
|
$src_phid,
|
|
$edge_type);
|
|
} else {
|
|
$dst_phids = array();
|
|
}
|
|
|
|
$all_phids = $dst_phids;
|
|
$all_phids[] = $src_phid;
|
|
|
|
$handles = $viewer->loadHandles($all_phids);
|
|
$src_handle = $handles[$src_phid];
|
|
|
|
$done_uri = $src_handle->getURI();
|
|
$initial_phids = $dst_phids;
|
|
|
|
if ($request->isFormPost()) {
|
|
$phids = explode(';', $request->getStr('phids'));
|
|
$phids = array_filter($phids);
|
|
$phids = array_values($phids);
|
|
|
|
$initial_phids = $request->getStrList('initialPHIDs');
|
|
|
|
// Apply the changes as adds and removes relative to the original state
|
|
// of the object when the dialog was rendered so that two users adding
|
|
// relationships at the same time don't race and overwrite one another.
|
|
$add_phids = array_diff($phids, $initial_phids);
|
|
$rem_phids = array_diff($initial_phids, $phids);
|
|
$all_phids = array_merge($add_phids, $rem_phids);
|
|
|
|
$capabilities = $relationship->getRequiredRelationshipCapabilities();
|
|
|
|
if ($all_phids) {
|
|
$dst_objects = id(new PhabricatorObjectQuery())
|
|
->setViewer($viewer)
|
|
->withPHIDs($all_phids)
|
|
->setRaisePolicyExceptions(true)
|
|
->requireCapabilities($capabilities)
|
|
->execute();
|
|
$dst_objects = mpull($dst_objects, null, 'getPHID');
|
|
} else {
|
|
$dst_objects = array();
|
|
}
|
|
|
|
try {
|
|
foreach ($add_phids as $add_phid) {
|
|
$dst_object = idx($dst_objects, $add_phid);
|
|
if (!$dst_object) {
|
|
throw new Exception(
|
|
pht(
|
|
'You can not create a relationship to object "%s" because '.
|
|
'the object does not exist or could not be loaded.',
|
|
$add_phid));
|
|
}
|
|
|
|
if ($add_phid == $src_phid) {
|
|
throw new Exception(
|
|
pht(
|
|
'You can not create a relationship to object "%s" because '.
|
|
'objects can not be related to themselves.',
|
|
$add_phid));
|
|
}
|
|
|
|
if (!$relationship->canRelateObjects($object, $dst_object)) {
|
|
throw new Exception(
|
|
pht(
|
|
'You can not create a relationship (of type "%s") to object '.
|
|
'"%s" because it is not the right type of object for this '.
|
|
'relationship.',
|
|
$relationship->getRelationshipConstant(),
|
|
$add_phid));
|
|
}
|
|
}
|
|
} catch (Exception $ex) {
|
|
return $this->newUnrelatableObjectResponse($ex, $done_uri);
|
|
}
|
|
|
|
$content_source = PhabricatorContentSource::newFromRequest($request);
|
|
$relationship->setContentSource($content_source);
|
|
|
|
$editor = $object->getApplicationTransactionEditor()
|
|
->setActor($viewer)
|
|
->setContentSource($content_source)
|
|
->setContinueOnMissingFields(true)
|
|
->setContinueOnNoEffect(true);
|
|
|
|
$xactions = array();
|
|
$xactions[] = $object->getApplicationTransactionTemplate()
|
|
->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
|
|
->setMetadataValue('edge:type', $edge_type)
|
|
->setNewValue(array(
|
|
'+' => array_fuse($add_phids),
|
|
'-' => array_fuse($rem_phids),
|
|
));
|
|
|
|
$add_objects = array_select_keys($dst_objects, $add_phids);
|
|
$rem_objects = array_select_keys($dst_objects, $rem_phids);
|
|
|
|
if ($add_objects || $rem_objects) {
|
|
$more_xactions = $relationship->willUpdateRelationships(
|
|
$object,
|
|
$add_objects,
|
|
$rem_objects);
|
|
foreach ($more_xactions as $xaction) {
|
|
$xactions[] = $xaction;
|
|
}
|
|
}
|
|
|
|
try {
|
|
$editor->applyTransactions($object, $xactions);
|
|
|
|
if ($add_objects || $rem_objects) {
|
|
$relationship->didUpdateRelationships(
|
|
$object,
|
|
$add_objects,
|
|
$rem_objects);
|
|
}
|
|
|
|
return id(new AphrontRedirectResponse())->setURI($done_uri);
|
|
} catch (PhabricatorEdgeCycleException $ex) {
|
|
return $this->newGraphCycleResponse($ex, $done_uri);
|
|
}
|
|
}
|
|
|
|
$handles = iterator_to_array($handles);
|
|
$handles = array_select_keys($handles, $dst_phids);
|
|
|
|
// TODO: These are hard-coded for now.
|
|
$filters = array(
|
|
'assigned' => pht('Assigned to Me'),
|
|
'created' => pht('Created By Me'),
|
|
'open' => pht('All Open Objects'),
|
|
'all' => pht('All Objects'),
|
|
);
|
|
|
|
$dialog_title = $relationship->getDialogTitleText();
|
|
$dialog_header = $relationship->getDialogHeaderText();
|
|
$dialog_button = $relationship->getDialogButtonText();
|
|
$dialog_instructions = $relationship->getDialogInstructionsText();
|
|
|
|
$source_uri = $relationship->getSourceURI($object);
|
|
|
|
return id(new PhabricatorObjectSelectorDialog())
|
|
->setUser($viewer)
|
|
->setInitialPHIDs($initial_phids)
|
|
->setHandles($handles)
|
|
->setFilters($filters)
|
|
->setSelectedFilter('created')
|
|
->setExcluded($src_phid)
|
|
->setCancelURI($done_uri)
|
|
->setSearchURI($source_uri)
|
|
->setTitle($dialog_title)
|
|
->setHeader($dialog_header)
|
|
->setButtonText($dialog_button)
|
|
->setInstructions($dialog_instructions)
|
|
->buildDialog();
|
|
}
|
|
|
|
private function newGraphCycleResponse(
|
|
PhabricatorEdgeCycleException $ex,
|
|
$done_uri) {
|
|
|
|
$viewer = $this->getViewer();
|
|
$cycle = $ex->getCycle();
|
|
|
|
$handles = $this->loadViewerHandles($cycle);
|
|
$names = array();
|
|
foreach ($cycle as $cycle_phid) {
|
|
$names[] = $handles[$cycle_phid]->getFullName();
|
|
}
|
|
|
|
$message = pht(
|
|
'You can not create that relationship because it would create a '.
|
|
'circular dependency:');
|
|
|
|
$list = implode(" \xE2\x86\x92 ", $names);
|
|
|
|
return $this->newDialog()
|
|
->setTitle(pht('Circular Dependency'))
|
|
->appendParagraph($message)
|
|
->appendParagraph($list)
|
|
->addCancelButton($done_uri);
|
|
}
|
|
|
|
private function newUnrelatableObjectResponse(Exception $ex, $done_uri) {
|
|
$message = $ex->getMessage();
|
|
|
|
return $this->newDialog()
|
|
->setTitle(pht('Invalid Relationship'))
|
|
->appendParagraph($message)
|
|
->addCancelButton($done_uri);
|
|
}
|
|
|
|
}
|