@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
3final class PhabricatorProjectProfileController
4 extends PhabricatorProjectController {
5
6 public function shouldAllowPublic() {
7 return true;
8 }
9
10 public function handleRequest(AphrontRequest $request) {
11 $response = $this->loadProject();
12 if ($response) {
13 return $response;
14 }
15
16 $viewer = $request->getUser();
17 $project = $this->getProject();
18 $id = $project->getID();
19 $picture = $project->getProfileImageURI();
20 $icon = $project->getDisplayIconIcon();
21 $icon_name = $project->getDisplayIconName();
22 $tag = id(new PHUITagView())
23 ->setIcon($icon)
24 ->setName($icon_name)
25 ->addClass('project-view-header-tag')
26 ->setType(PHUITagView::TYPE_SHADE);
27
28 $header = id(new PHUIHeaderView())
29 ->setHeader(array($project->getDisplayName(), $tag))
30 ->setViewer($viewer)
31 ->setPolicyObject($project)
32 ->setProfileHeader(true);
33
34 if ($project->getStatus() == PhabricatorProjectStatus::STATUS_ACTIVE) {
35 $header->setStatus('fa-check', 'bluegrey', pht('Active'));
36 } else {
37 $header->setStatus('fa-ban', 'red', pht('Archived'));
38 }
39
40 $can_edit = PhabricatorPolicyFilter::hasCapability(
41 $viewer,
42 $project,
43 PhabricatorPolicyCapability::CAN_EDIT);
44
45 if ($can_edit) {
46 $header->setImageEditURL($this->getApplicationURI("picture/{$id}/"));
47 }
48
49 $properties = $this->buildPropertyListView($project);
50
51 $watch_action = $this->renderWatchAction($project);
52 $header->addActionLink($watch_action);
53
54 $subtype = $project->newSubtypeObject();
55 if ($subtype && $subtype->hasTagView()) {
56 $subtype_tag = $subtype->newTagView();
57 $header->addTag($subtype_tag);
58 }
59
60 $milestone_list = $this->buildMilestoneList($project);
61 $subproject_list = $this->buildSubprojectList($project);
62
63 $member_list = id(new PhabricatorProjectMemberListView())
64 ->setViewer($viewer)
65 ->setProject($project)
66 ->setLimit(10)
67 ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
68 ->setUserPHIDs($project->getMemberPHIDs());
69
70 $watcher_list = id(new PhabricatorProjectWatcherListView())
71 ->setViewer($viewer)
72 ->setProject($project)
73 ->setLimit(10)
74 ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
75 ->setUserPHIDs($project->getWatcherPHIDs());
76
77 $nav = $this->newNavigation(
78 $project,
79 PhabricatorProject::ITEM_PROFILE);
80
81 $query = id(new PhabricatorFeedQuery())
82 ->setViewer($viewer)
83 ->withFilterPHIDs(array($project->getPHID()))
84 ->setLimit(50)
85 ->setReturnPartialResultsOnOverheat(true);
86
87 $stories = $query->execute();
88
89 $overheated_view = null;
90 $is_overheated = $query->getIsOverheated();
91 if ($is_overheated) {
92 $overheated_message =
93 PhabricatorApplicationSearchController::newOverheatedError(
94 (bool)$stories);
95
96 $overheated_view = id(new PHUIInfoView())
97 ->setSeverity(PHUIInfoView::SEVERITY_WARNING)
98 ->setTitle(pht('Query Overheated'))
99 ->setErrors(
100 array(
101 $overheated_message,
102 ));
103 }
104
105 $view_all = id(new PHUIButtonView())
106 ->setTag('a')
107 ->setIcon('fa-list-ul')
108 ->setText(pht('View All'))
109 ->setHref('/feed/?projectPHIDs='.$project->getPHID());
110
111 $feed_header = id(new PHUIHeaderView())
112 ->setHeader(pht('Recent Activity'))
113 ->addActionLink($view_all);
114
115 $feed = $this->renderStories($stories);
116 $feed = id(new PHUIObjectBoxView())
117 ->setHeader($feed_header)
118 ->addClass('project-view-feed')
119 ->appendChild(
120 array(
121 $overheated_view,
122 $feed,
123 ));
124
125 require_celerity_resource('project-view-css');
126
127 $home = id(new PHUITwoColumnView())
128 ->setHeader($header)
129 ->addClass('project-view-home')
130 ->addClass('project-view-people-home')
131 ->setMainColumn(
132 array(
133 $properties,
134 $feed,
135 ))
136 ->setSideColumn(
137 array(
138 $milestone_list,
139 $subproject_list,
140 $member_list,
141 $watcher_list,
142 ));
143
144 $crumbs = $this->buildApplicationCrumbs();
145 $crumbs->setBorder(true);
146
147 return $this->newPage()
148 ->setNavigation($nav)
149 ->setCrumbs($crumbs)
150 ->setTitle($project->getDisplayName())
151 ->setPageObjectPHIDs(array($project->getPHID()))
152 ->appendChild($home);
153 }
154
155 private function buildPropertyListView(
156 PhabricatorProject $project) {
157 $request = $this->getRequest();
158 $viewer = $request->getUser();
159
160 $view = id(new PHUIPropertyListView())
161 ->setViewer($viewer)
162 ->setObject($project);
163
164 $field_list = PhabricatorCustomField::getObjectFields(
165 $project,
166 PhabricatorCustomField::ROLE_VIEW);
167 $field_list->appendFieldsToPropertyList($project, $viewer, $view);
168
169 if (!$view->hasAnyProperties()) {
170 return null;
171 }
172
173 $header = id(new PHUIHeaderView())
174 ->setHeader(pht('Details'));
175
176 $view = id(new PHUIObjectBoxView())
177 ->setHeader($header)
178 ->appendChild($view)
179 ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
180 ->addClass('project-view-properties');
181
182 return $view;
183 }
184
185 /**
186 * @param array<PhabricatorFeedStory> $stories
187 */
188 private function renderStories(array $stories) {
189 assert_instances_of($stories, PhabricatorFeedStory::class);
190
191 $builder = new PhabricatorFeedBuilder($stories);
192 $builder->setUser($this->getRequest()->getUser());
193 $builder->setShowHovercards(true);
194 $view = $builder->buildView();
195
196 return $view;
197 }
198
199 private function renderWatchAction(PhabricatorProject $project) {
200 $viewer = $this->getViewer();
201 $id = $project->getID();
202
203 if (!$viewer->isLoggedIn()) {
204 $is_watcher = false;
205 $is_ancestor = false;
206 } else {
207 $viewer_phid = $viewer->getPHID();
208 $is_watcher = $project->isUserWatcher($viewer_phid);
209 $is_ancestor = $project->isUserAncestorWatcher($viewer_phid);
210 }
211
212 if ($is_ancestor && !$is_watcher) {
213 $watch_icon = 'fa-eye';
214 $watch_text = pht('Watching Ancestor');
215 $watch_href = "/project/watch/{$id}/?via=profile";
216 $watch_disabled = true;
217 } else if (!$is_watcher) {
218 $watch_icon = 'fa-eye';
219 $watch_text = pht('Watch Project');
220 $watch_href = "/project/watch/{$id}/?via=profile";
221 $watch_disabled = false;
222 } else {
223 $watch_icon = 'fa-eye-slash';
224 $watch_text = pht('Unwatch Project');
225 $watch_href = "/project/unwatch/{$id}/?via=profile";
226 $watch_disabled = false;
227 }
228
229 return id(new PHUIButtonView())
230 ->setTag('a')
231 ->setWorkflow(true)
232 ->setIcon($watch_icon)
233 ->setText($watch_text)
234 ->setHref($watch_href)
235 ->setDisabled($watch_disabled);
236 }
237
238 private function buildMilestoneList(PhabricatorProject $project) {
239 if (!$project->getHasMilestones()) {
240 return null;
241 }
242
243 $viewer = $this->getViewer();
244 $id = $project->getID();
245
246 $milestones = id(new PhabricatorProjectQuery())
247 ->setViewer($viewer)
248 ->withParentProjectPHIDs(array($project->getPHID()))
249 ->needImages(true)
250 ->withIsMilestone(true)
251 ->withStatuses(
252 array(
253 PhabricatorProjectStatus::STATUS_ACTIVE,
254 ))
255 ->setOrderVector(array('milestoneNumber', 'id'))
256 ->execute();
257 if (!$milestones) {
258 return null;
259 }
260
261 $milestone_list = id(new PhabricatorProjectListView())
262 ->setViewer($viewer)
263 ->setProjects($milestones)
264 ->renderList();
265
266 $view_all = id(new PHUIButtonView())
267 ->setTag('a')
268 ->setIcon('fa-list-ul')
269 ->setText(pht('View All'))
270 ->setHref("/project/subprojects/{$id}/");
271
272 $header = id(new PHUIHeaderView())
273 ->setHeader(pht('Milestones'))
274 ->addActionLink($view_all);
275
276 return id(new PHUIObjectBoxView())
277 ->setHeader($header)
278 ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
279 ->setObjectList($milestone_list);
280 }
281
282 private function buildSubprojectList(PhabricatorProject $project) {
283 if (!$project->getHasSubprojects()) {
284 return null;
285 }
286
287 $viewer = $this->getViewer();
288 $id = $project->getID();
289
290 $limit = 25;
291
292 $subprojects = id(new PhabricatorProjectQuery())
293 ->setViewer($viewer)
294 ->withParentProjectPHIDs(array($project->getPHID()))
295 ->needImages(true)
296 ->withStatuses(
297 array(
298 PhabricatorProjectStatus::STATUS_ACTIVE,
299 ))
300 ->withIsMilestone(false)
301 ->setLimit($limit)
302 ->execute();
303 if (!$subprojects) {
304 return null;
305 }
306
307 $subproject_list = id(new PhabricatorProjectListView())
308 ->setUser($viewer)
309 ->setProjects($subprojects)
310 ->renderList();
311
312 $view_all = id(new PHUIButtonView())
313 ->setTag('a')
314 ->setIcon('fa-list-ul')
315 ->setText(pht('View All'))
316 ->setHref("/project/subprojects/{$id}/");
317
318 $header = id(new PHUIHeaderView())
319 ->setHeader(pht('Subprojects'))
320 ->addActionLink($view_all);
321
322 return id(new PHUIObjectBoxView())
323 ->setHeader($header)
324 ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
325 ->setObjectList($subproject_list);
326 }
327
328}