@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
at recaptime-dev/main 235 lines 6.7 kB view raw
1<?php 2 3abstract class PhabricatorProjectController extends PhabricatorController { 4 5 private $project; 6 private $profileMenu; 7 private $profileMenuEngine; 8 9 protected function setProject(PhabricatorProject $project) { 10 $this->project = $project; 11 return $this; 12 } 13 14 protected function getProject() { 15 return $this->project; 16 } 17 18 protected function loadProject() { 19 return $this->loadProjectWithCapabilities( 20 array( 21 PhabricatorPolicyCapability::CAN_VIEW, 22 )); 23 } 24 25 protected function loadProjectForEdit() { 26 return $this->loadProjectWithCapabilities( 27 array( 28 PhabricatorPolicyCapability::CAN_VIEW, 29 PhabricatorPolicyCapability::CAN_EDIT, 30 )); 31 } 32 33 private function loadProjectWithCapabilities(array $capabilities) { 34 $viewer = $this->getViewer(); 35 $request = $this->getRequest(); 36 37 $id = nonempty( 38 $request->getURIData('projectID'), 39 $request->getURIData('id')); 40 41 $slug = $request->getURIData('slug'); 42 43 if ($slug) { 44 $normal_slug = PhabricatorSlug::normalizeProjectSlug($slug); 45 $is_abnormal = ($slug !== $normal_slug); 46 $normal_uri = "/tag/{$normal_slug}/"; 47 } else { 48 $is_abnormal = false; 49 } 50 51 $query = id(new PhabricatorProjectQuery()) 52 ->setViewer($viewer) 53 ->requireCapabilities($capabilities) 54 ->needMembers(true) 55 ->needWatchers(true) 56 ->needImages(true) 57 ->needSlugs(true); 58 59 if ($slug) { 60 $query->withSlugs(array($slug)); 61 } else { 62 $query->withIDs(array($id)); 63 } 64 65 $policy_exception = null; 66 try { 67 $project = $query->executeOne(); 68 } catch (PhabricatorPolicyException $ex) { 69 $policy_exception = $ex; 70 $project = null; 71 } 72 73 if (!$project) { 74 // This project legitimately does not exist, so just 404 the user. 75 if (!$policy_exception) { 76 return new Aphront404Response(); 77 } 78 79 // Here, the project exists but the user can't see it. If they are 80 // using a non-canonical slug to view the project, redirect to the 81 // canonical slug. If they're already using the canonical slug, rethrow 82 // the exception to give them the policy error. 83 if ($is_abnormal) { 84 return id(new AphrontRedirectResponse())->setURI($normal_uri); 85 } else { 86 throw $policy_exception; 87 } 88 } 89 90 // The user can view the project, but is using a noncanonical slug. 91 // Redirect to the canonical slug. 92 $primary_slug = $project->getPrimarySlug(); 93 if ($slug && ($slug !== $primary_slug)) { 94 // Things like milestones do not have a '/tag/something/' page. 95 if (!phutil_nonempty_string($primary_slug)) { 96 return new Aphront404Response(); 97 } 98 $primary_uri = "/tag/{$primary_slug}/"; 99 return id(new AphrontRedirectResponse())->setURI($primary_uri); 100 } 101 102 $this->setProject($project); 103 104 return null; 105 } 106 107 protected function buildApplicationCrumbs() { 108 return $this->newApplicationCrumbs('profile'); 109 } 110 111 protected function newWorkboardCrumbs() { 112 return $this->newApplicationCrumbs('workboard'); 113 } 114 115 private function newApplicationCrumbs($mode) { 116 $crumbs = parent::buildApplicationCrumbs(); 117 118 $project = $this->getProject(); 119 if ($project) { 120 $ancestors = $project->getAncestorProjects(); 121 $ancestors = array_reverse($ancestors); 122 $ancestors[] = $project; 123 foreach ($ancestors as $ancestor) { 124 if ($ancestor->getPHID() === $project->getPHID()) { 125 // Link the current project's crumb to its profile no matter what, 126 // since we're already on the right context page for it and linking 127 // to the current page isn't helpful. 128 $crumb_uri = $ancestor->getProfileURI(); 129 } else { 130 switch ($mode) { 131 case 'workboard': 132 if ($ancestor->getHasWorkboard()) { 133 $crumb_uri = $ancestor->getWorkboardURI(); 134 } else { 135 $crumb_uri = $ancestor->getProfileURI(); 136 } 137 break; 138 case 'profile': 139 default: 140 $crumb_uri = $ancestor->getProfileURI(); 141 break; 142 } 143 } 144 $archived = $ancestor->isArchived(); 145 $crumbs->addTextCrumb($ancestor->getName(), $crumb_uri, $archived); 146 } 147 } 148 149 return $crumbs; 150 } 151 152 protected function getProfileMenuEngine() { 153 if (!$this->profileMenuEngine) { 154 $viewer = $this->getViewer(); 155 $project = $this->getProject(); 156 if ($project) { 157 $engine = id(new PhabricatorProjectProfileMenuEngine()) 158 ->setViewer($viewer) 159 ->setController($this) 160 ->setProfileObject($project); 161 $this->profileMenuEngine = $engine; 162 } 163 } 164 165 return $this->profileMenuEngine; 166 } 167 168 protected function setProfileMenuEngine( 169 PhabricatorProjectProfileMenuEngine $engine) { 170 $this->profileMenuEngine = $engine; 171 return $this; 172 } 173 174 protected function newCardResponse( 175 $board_phid, 176 $object_phid, 177 ?PhabricatorProjectColumnOrder $ordering = null, 178 $sounds = array()) { 179 180 $viewer = $this->getViewer(); 181 182 $request = $this->getRequest(); 183 $visible_phids = $request->getStrList('visiblePHIDs'); 184 if (!$visible_phids) { 185 $visible_phids = array(); 186 } 187 188 $engine = id(new PhabricatorBoardResponseEngine()) 189 ->setViewer($viewer) 190 ->setBoardPHID($board_phid) 191 ->setUpdatePHIDs(array($object_phid)) 192 ->setVisiblePHIDs($visible_phids) 193 ->setSounds($sounds); 194 195 if ($ordering) { 196 $engine->setOrdering($ordering); 197 } 198 199 return $engine->buildResponse(); 200 } 201 202 public function renderHashtags(array $tags) { 203 $result = array(); 204 foreach ($tags as $key => $tag) { 205 $result[] = '#'.$tag; 206 } 207 return implode(', ', $result); 208 } 209 210 final protected function newNavigation( 211 PhabricatorProject $project, 212 $item_identifier) { 213 214 $engine = $this->getProfileMenuEngine(); 215 216 $view_list = $engine->newProfileMenuItemViewList(); 217 218 // See PHI1247. If the "Workboard" item is removed from the menu, we will 219 // not be able to select it. This can happen if a user removes the item, 220 // then manually navigate to the workboard URI (or follows an older link). 221 // In this case, just render the menu with no selected item. 222 if ($view_list->getViewsWithItemIdentifier($item_identifier)) { 223 $view_list->setSelectedViewWithItemIdentifier($item_identifier); 224 } 225 226 $navigation = $view_list->newNavigationView(); 227 228 if ($item_identifier === PhabricatorProject::ITEM_WORKBOARD) { 229 $navigation->addClass('project-board-nav'); 230 } 231 232 return $navigation; 233 } 234 235}