@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<?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}