3 final class PhabricatorSearchRelationshipController
4 extends PhabricatorSearchBaseController
{
6 public function handleRequest(AphrontRequest
$request) {
7 $viewer = $this->getViewer();
9 $object = $this->loadRelationshipObject();
11 return new Aphront404Response();
14 $relationship = $this->loadRelationship($object);
16 return new Aphront404Response();
19 $src_phid = $object->getPHID();
20 $edge_type = $relationship->getEdgeConstant();
22 // If this is a normal relationship, users can remove related objects. If
23 // it's a special relationship like a merge, we can't undo it, so we won't
24 // prefill the current related objects.
25 if ($relationship->canUndoRelationship()) {
26 $dst_phids = PhabricatorEdgeQuery
::loadDestinationPHIDs(
33 $all_phids = $dst_phids;
34 $all_phids[] = $src_phid;
36 $handles = $viewer->loadHandles($all_phids);
37 $src_handle = $handles[$src_phid];
39 $done_uri = $src_handle->getURI();
40 $initial_phids = $dst_phids;
42 $maximum = $relationship->getMaximumSelectionSize();
44 if ($request->isFormPost()) {
45 $phids = explode(';', $request->getStr('phids'));
46 $phids = array_filter($phids);
47 $phids = array_values($phids);
49 // The UI normally enforces this with Javascript, so this is just a
50 // sanity check and does not need to be particularly user-friendly.
51 if ($maximum && (count($phids) > $maximum)) {
54 'Too many relationships (%s, of type "%s").',
56 $relationship->getRelationshipConstant()));
59 $initial_phids = $request->getStrList('initialPHIDs');
61 // Apply the changes as adds and removes relative to the original state
62 // of the object when the dialog was rendered so that two users adding
63 // relationships at the same time don't race and overwrite one another.
64 $add_phids = array_diff($phids, $initial_phids);
65 $rem_phids = array_diff($initial_phids, $phids);
66 $all_phids = array_merge($add_phids, $rem_phids);
68 $capabilities = $relationship->getRequiredRelationshipCapabilities();
71 $dst_objects = id(new PhabricatorObjectQuery())
73 ->withPHIDs($all_phids)
74 ->setRaisePolicyExceptions(true)
75 ->requireCapabilities($capabilities)
77 $dst_objects = mpull($dst_objects, null, 'getPHID');
79 $dst_objects = array();
83 foreach ($add_phids as $add_phid) {
84 $dst_object = idx($dst_objects, $add_phid);
88 'You can not create a relationship to object "%s" because '.
89 'the object does not exist or could not be loaded.',
93 if ($add_phid == $src_phid) {
96 'You can not create a relationship to object "%s" because '.
97 'objects can not be related to themselves.',
101 if (!$relationship->canRelateObjects($object, $dst_object)) {
104 'You can not create a relationship (of type "%s") to object '.
105 '"%s" because it is not the right type of object for this '.
107 $relationship->getRelationshipConstant(),
111 } catch (Exception
$ex) {
112 return $this->newUnrelatableObjectResponse($ex, $done_uri);
115 $content_source = PhabricatorContentSource
::newFromRequest($request);
116 $relationship->setContentSource($content_source);
118 $editor = $object->getApplicationTransactionEditor()
120 ->setContentSource($content_source)
121 ->setContinueOnMissingFields(true)
122 ->setContinueOnNoEffect(true);
125 $xactions[] = $object->getApplicationTransactionTemplate()
126 ->setTransactionType(PhabricatorTransactions
::TYPE_EDGE
)
127 ->setMetadataValue('edge:type', $edge_type)
129 '+' => array_fuse($add_phids),
130 '-' => array_fuse($rem_phids),
133 $add_objects = array_select_keys($dst_objects, $add_phids);
134 $rem_objects = array_select_keys($dst_objects, $rem_phids);
136 if ($add_objects ||
$rem_objects) {
137 $more_xactions = $relationship->willUpdateRelationships(
141 foreach ($more_xactions as $xaction) {
142 $xactions[] = $xaction;
147 $editor->applyTransactions($object, $xactions);
149 if ($add_objects ||
$rem_objects) {
150 $relationship->didUpdateRelationships(
156 return id(new AphrontRedirectResponse())->setURI($done_uri);
157 } catch (PhabricatorEdgeCycleException
$ex) {
158 return $this->newGraphCycleResponse($ex, $done_uri);
162 $handles = iterator_to_array($handles);
163 $handles = array_select_keys($handles, $dst_phids);
165 $dialog_title = $relationship->getDialogTitleText();
166 $dialog_header = $relationship->getDialogHeaderText();
167 $dialog_button = $relationship->getDialogButtonText();
168 $dialog_instructions = $relationship->getDialogInstructionsText();
170 $source_uri = $relationship->getSourceURI($object);
172 $source = $relationship->newSource();
174 $filters = $source->getFilters();
175 $selected_filter = $source->getSelectedFilter();
177 return id(new PhabricatorObjectSelectorDialog())
179 ->setInitialPHIDs($initial_phids)
180 ->setHandles($handles)
181 ->setFilters($filters)
182 ->setSelectedFilter($selected_filter)
183 ->setExcluded($src_phid)
184 ->setCancelURI($done_uri)
185 ->setSearchURI($source_uri)
186 ->setTitle($dialog_title)
187 ->setHeader($dialog_header)
188 ->setButtonText($dialog_button)
189 ->setInstructions($dialog_instructions)
190 ->setMaximumSelectionSize($maximum)
194 private function newGraphCycleResponse(
195 PhabricatorEdgeCycleException
$ex,
198 $viewer = $this->getViewer();
199 $cycle = $ex->getCycle();
201 $handles = $this->loadViewerHandles($cycle);
203 foreach ($cycle as $cycle_phid) {
204 $names[] = $handles[$cycle_phid]->getFullName();
208 'You can not create that relationship because it would create a '.
209 'circular dependency:');
211 $list = implode(" \xE2\x86\x92 ", $names);
213 return $this->newDialog()
214 ->setTitle(pht('Circular Dependency'))
215 ->appendParagraph($message)
216 ->appendParagraph($list)
217 ->addCancelButton($done_uri);
220 private function newUnrelatableObjectResponse(Exception
$ex, $done_uri) {
221 $message = $ex->getMessage();
223 return $this->newDialog()
224 ->setTitle(pht('Invalid Relationship'))
225 ->appendParagraph($message)
226 ->addCancelButton($done_uri);