@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Drive modular task relationships through a new "relationships" controller

Summary: Ref T11179. This is basically a "pro" controller to replace the SearchAttach controller. It does basically the same stuff, just in a (mostly) more modern and modular way.

Test Plan:
- Added and removed mocks.
- Added and removed revisions.
- Everything worked just like it did before.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T11179

Differential Revision: https://secure.phabricator.com/D16163

+266 -18
+2
src/__phutil_library_map__.php
··· 3394 3394 'PhabricatorSearchOrderController' => 'applications/search/controller/PhabricatorSearchOrderController.php', 3395 3395 'PhabricatorSearchOrderField' => 'applications/search/field/PhabricatorSearchOrderField.php', 3396 3396 'PhabricatorSearchRelationship' => 'applications/search/constants/PhabricatorSearchRelationship.php', 3397 + 'PhabricatorSearchRelationshipController' => 'applications/search/controller/PhabricatorSearchRelationshipController.php', 3397 3398 'PhabricatorSearchResultBucket' => 'applications/search/buckets/PhabricatorSearchResultBucket.php', 3398 3399 'PhabricatorSearchResultBucketGroup' => 'applications/search/buckets/PhabricatorSearchResultBucketGroup.php', 3399 3400 'PhabricatorSearchResultView' => 'applications/search/view/PhabricatorSearchResultView.php', ··· 8213 8214 'PhabricatorSearchOrderController' => 'PhabricatorSearchBaseController', 8214 8215 'PhabricatorSearchOrderField' => 'PhabricatorSearchField', 8215 8216 'PhabricatorSearchRelationship' => 'Phobject', 8217 + 'PhabricatorSearchRelationshipController' => 'PhabricatorSearchBaseController', 8216 8218 'PhabricatorSearchResultBucket' => 'Phobject', 8217 8219 'PhabricatorSearchResultBucketGroup' => 'Phobject', 8218 8220 'PhabricatorSearchResultView' => 'AphrontView',
+16
src/applications/maniphest/relationship/ManiphestTaskHasCommitRelationship.php
··· 24 24 return false; 25 25 } 26 26 27 + public function canRelateObjects($src, $dst) { 28 + return ($dst instanceof PhabricatorRepositoryCommit); 29 + } 30 + 31 + public function getDialogTitleText() { 32 + return pht('Edit Related Commits'); 33 + } 34 + 35 + public function getDialogHeaderText() { 36 + return pht('Current Commits'); 37 + } 38 + 39 + public function getDialogButtonText() { 40 + return pht('Save Related Commits'); 41 + } 42 + 27 43 }
+16
src/applications/maniphest/relationship/ManiphestTaskHasMockRelationship.php
··· 17 17 return 'fa-camera-retro'; 18 18 } 19 19 20 + public function canRelateObjects($src, $dst) { 21 + return ($dst instanceof PholioMock); 22 + } 23 + 24 + public function getDialogTitleText() { 25 + return pht('Edit Related Mocks'); 26 + } 27 + 28 + public function getDialogHeaderText() { 29 + return pht('Current Mocks'); 30 + } 31 + 32 + public function getDialogButtonText() { 33 + return pht('Save Related Mocks'); 34 + } 35 + 20 36 }
+16
src/applications/maniphest/relationship/ManiphestTaskHasRevisionRelationship.php
··· 17 17 return 'fa-cog'; 18 18 } 19 19 20 + public function canRelateObjects($src, $dst) { 21 + return ($dst instanceof DifferentialRevision); 22 + } 23 + 24 + public function getDialogTitleText() { 25 + return pht('Edit Related Revisions'); 26 + } 27 + 28 + public function getDialogHeaderText() { 29 + return pht('Current Revisions'); 30 + } 31 + 32 + public function getDialogButtonText() { 33 + return pht('Save Related Revisions'); 34 + } 35 + 20 36 }
+2
src/applications/search/application/PhabricatorSearchApplication.php
··· 41 41 'delete/(?P<queryKey>[^/]+)/(?P<engine>[^/]+)/' 42 42 => 'PhabricatorSearchDeleteController', 43 43 'order/(?P<engine>[^/]+)/' => 'PhabricatorSearchOrderController', 44 + 'rel/(?P<relationshipKey>[^/]+)/(?P<sourcePHID>[^/]+)/' 45 + => 'PhabricatorSearchRelationshipController', 44 46 ), 45 47 ); 46 48 }
+198
src/applications/search/controller/PhabricatorSearchRelationshipController.php
··· 1 + <?php 2 + 3 + final class PhabricatorSearchRelationshipController 4 + extends PhabricatorSearchBaseController { 5 + 6 + public function handleRequest(AphrontRequest $request) { 7 + $viewer = $this->getViewer(); 8 + 9 + $phid = $request->getURIData('sourcePHID'); 10 + $object = id(new PhabricatorObjectQuery()) 11 + ->setViewer($viewer) 12 + ->withPHIDs(array($phid)) 13 + ->requireCapabilities( 14 + array( 15 + PhabricatorPolicyCapability::CAN_VIEW, 16 + PhabricatorPolicyCapability::CAN_EDIT, 17 + )) 18 + ->executeOne(); 19 + if (!$object) { 20 + return new Aphront404Response(); 21 + } 22 + 23 + $list = PhabricatorObjectRelationshipList::newForObject( 24 + $viewer, 25 + $object); 26 + 27 + $relationship_key = $request->getURIData('relationshipKey'); 28 + $relationship = $list->getRelationship($relationship_key); 29 + if (!$relationship) { 30 + return new Aphront404Response(); 31 + } 32 + 33 + $src_phid = $object->getPHID(); 34 + $edge_type = $relationship->getEdgeConstant(); 35 + 36 + $dst_phids = PhabricatorEdgeQuery::loadDestinationPHIDs( 37 + $src_phid, 38 + $edge_type); 39 + 40 + $all_phids = $dst_phids; 41 + $all_phids[] = $src_phid; 42 + 43 + $handles = $viewer->loadHandles($all_phids); 44 + $src_handle = $handles[$src_phid]; 45 + 46 + $done_uri = $src_handle->getURI(); 47 + 48 + if ($request->isFormPost()) { 49 + $phids = explode(';', $request->getStr('phids')); 50 + $phids = array_filter($phids); 51 + $phids = array_values($phids); 52 + 53 + // TODO: Embed these in the form instead, to gracefully resolve 54 + // concurrent edits like we do for subscribers and projects. 55 + $old_phids = $dst_phids; 56 + 57 + $add_phids = $phids; 58 + $rem_phids = array_diff($old_phids, $add_phids); 59 + 60 + if ($add_phids) { 61 + $dst_objects = id(new PhabricatorObjectQuery()) 62 + ->setViewer($viewer) 63 + ->withPHIDs($phids) 64 + ->setRaisePolicyExceptions(true) 65 + ->execute(); 66 + $dst_objects = mpull($dst_objects, null, 'getPHID'); 67 + } else { 68 + $dst_objects = array(); 69 + } 70 + 71 + try { 72 + foreach ($add_phids as $add_phid) { 73 + $dst_object = idx($dst_objects, $add_phid); 74 + if (!$dst_object) { 75 + throw new Exception( 76 + pht( 77 + 'You can not create a relationship to object "%s" because '. 78 + 'the object does not exist or could not be loaded.', 79 + $add_phid)); 80 + } 81 + 82 + if (!$relationship->canRelateObjects($object, $dst_object)) { 83 + throw new Exception( 84 + pht( 85 + 'You can not create a relationship (of type "%s") to object '. 86 + '"%s" because it is not the right type of object for this '. 87 + 'relationship.', 88 + $relationship->getRelationshipConstant(), 89 + $add_phid)); 90 + } 91 + } 92 + } catch (Exception $ex) { 93 + return $this->newUnrelatableObjectResponse($ex, $done_uri); 94 + } 95 + 96 + $editor = $object->getApplicationTransactionEditor() 97 + ->setActor($viewer) 98 + ->setContentSourceFromRequest($request) 99 + ->setContinueOnMissingFields(true) 100 + ->setContinueOnNoEffect(true); 101 + 102 + $xactions = array(); 103 + $xactions[] = $object->getApplicationTransactionTemplate() 104 + ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) 105 + ->setMetadataValue('edge:type', $edge_type) 106 + ->setNewValue(array( 107 + '+' => array_fuse($add_phids), 108 + '-' => array_fuse($rem_phids), 109 + )); 110 + 111 + try { 112 + $editor->applyTransactions($object, $xactions); 113 + 114 + return id(new AphrontRedirectResponse())->setURI($done_uri); 115 + } catch (PhabricatorEdgeCycleException $ex) { 116 + return $this->newGraphCycleResponse($ex, $done_uri); 117 + } 118 + } 119 + 120 + $handles = iterator_to_array($handles); 121 + $handles = array_select_keys($handles, $dst_phids); 122 + 123 + // TODO: These are hard-coded for now. 124 + $filters = array( 125 + 'assigned' => pht('Assigned to Me'), 126 + 'created' => pht('Created By Me'), 127 + 'open' => pht('All Open Objects'), 128 + 'all' => pht('All Objects'), 129 + ); 130 + 131 + $dialog_title = $relationship->getDialogTitleText(); 132 + $dialog_header = $relationship->getDialogHeaderText(); 133 + $dialog_button = $relationship->getDialogButtonText(); 134 + $dialog_instructions = $relationship->getDialogInstructionsText(); 135 + 136 + // TODO: Remove this, this is just legacy support. 137 + $legacy_kinds = array( 138 + ManiphestTaskHasCommitEdgeType::EDGECONST => 'CMIT', 139 + ManiphestTaskHasMockEdgeType::EDGECONST => 'MOCK', 140 + ManiphestTaskHasRevisionEdgeType::EDGECONST => 'DREV', 141 + ); 142 + 143 + $edge_type = $relationship->getEdgeConstant(); 144 + $legacy_kind = idx($legacy_kinds, $edge_type); 145 + if (!$legacy_kind) { 146 + throw new Exception( 147 + pht('Only specific legacy relationships are supported!')); 148 + } 149 + 150 + return id(new PhabricatorObjectSelectorDialog()) 151 + ->setUser($viewer) 152 + ->setHandles($handles) 153 + ->setFilters($filters) 154 + ->setSelectedFilter('created') 155 + ->setExcluded($phid) 156 + ->setCancelURI($done_uri) 157 + ->setSearchURI("/search/select/{$legacy_kind}/edge/") 158 + ->setTitle($dialog_title) 159 + ->setHeader($dialog_header) 160 + ->setButtonText($dialog_button) 161 + ->setInstructions($dialog_instructions) 162 + ->buildDialog(); 163 + } 164 + 165 + private function newGraphCycleResponse( 166 + PhabricatorEdgeCycleException $ex, 167 + $done_uri) { 168 + 169 + $viewer = $this->getViewer(); 170 + $cycle = $ex->getCycle(); 171 + 172 + $handles = $this->loadViewerHandles($cycle); 173 + $names = array(); 174 + foreach ($cycle as $cycle_phid) { 175 + $names[] = $handles[$cycle_phid]->getFullName(); 176 + } 177 + 178 + $message = pht( 179 + 'You can not create that relationship because it would create a '. 180 + 'circular dependency: %s.', 181 + implode(" \xE2\x86\x92 ", $names)); 182 + 183 + return $this->newDialog() 184 + ->setTitle(pht('Circular Dependency')) 185 + ->appendParagraph($message) 186 + ->addCancelButton($done_uri); 187 + } 188 + 189 + private function newUnrelatableObjectResponse(Exception $ex, $done_uri) { 190 + $message = $ex->getMessage(); 191 + 192 + return $this->newDialog() 193 + ->setTitle(pht('Invalid Relationship')) 194 + ->appendParagraph($message) 195 + ->addCancelButton($done_uri); 196 + } 197 + 198 + }
+12 -18
src/applications/search/relationship/PhabricatorObjectRelationship.php
··· 24 24 abstract protected function getActionName(); 25 25 abstract protected function getActionIcon(); 26 26 27 + abstract public function canRelateObjects($src, $dst); 28 + 29 + abstract public function getDialogTitleText(); 30 + abstract public function getDialogHeaderText(); 31 + abstract public function getDialogButtonText(); 32 + 33 + public function getDialogInstructionsText() { 34 + return null; 35 + } 36 + 27 37 public function shouldAppearInActionMenu() { 28 38 return true; 29 39 } ··· 58 68 59 69 private function getActionURI($object) { 60 70 $phid = $object->getPHID(); 61 - 62 - // TODO: Remove this, this is just legacy support for the current 63 - // controller until a new one gets built. 64 - $legacy_kinds = array( 65 - ManiphestTaskHasCommitEdgeType::EDGECONST => 'CMIT', 66 - ManiphestTaskHasMockEdgeType::EDGECONST => 'MOCK', 67 - ManiphestTaskHasRevisionEdgeType::EDGECONST => 'DREV', 68 - ); 69 - 70 - $edge_type = $this->getEdgeConstant(); 71 - $legacy_kind = idx($legacy_kinds, $edge_type); 72 - if (!$legacy_kind) { 73 - throw new Exception( 74 - pht( 75 - 'Only specific legacy relationships are supported!')); 76 - } 77 - 78 - return "/search/attach/{$phid}/{$legacy_kind}/"; 71 + $type = $this->getRelationshipConstant(); 72 + return "/search/rel/{$type}/{$phid}/"; 79 73 } 80 74 81 75 }
+4
src/applications/search/relationship/PhabricatorObjectRelationshipList.php
··· 71 71 ->setSubmenu($actions); 72 72 } 73 73 74 + public function getRelationship($key) { 75 + return idx($this->relationships, $key); 76 + } 77 + 74 78 public static function newForObject(PhabricatorUser $viewer, $object) { 75 79 $relationships = PhabricatorObjectRelationship::getAllRelationships(); 76 80