@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 PhabricatorProjectMembersViewController
4 extends PhabricatorProjectController {
5
6 public function handleRequest(AphrontRequest $request) {
7 $viewer = $request->getViewer();
8 $id = $request->getURIData('id');
9
10 $project = id(new PhabricatorProjectQuery())
11 ->setViewer($viewer)
12 ->withIDs(array($id))
13 ->needMembers(true)
14 ->needWatchers(true)
15 ->needImages(true)
16 ->executeOne();
17 if (!$project) {
18 return new Aphront404Response();
19 }
20
21 $this->setProject($project);
22 $title = pht('Members and Watchers');
23 $curtain = $this->buildCurtainView($project);
24
25 $member_list = id(new PhabricatorProjectMemberListView())
26 ->setViewer($viewer)
27 ->setProject($project)
28 ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
29 ->setUserPHIDs($project->getMemberPHIDs())
30 ->setShowNote(true);
31
32 $watcher_list = id(new PhabricatorProjectWatcherListView())
33 ->setViewer($viewer)
34 ->setProject($project)
35 ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
36 ->setUserPHIDs($project->getWatcherPHIDs())
37 ->setShowNote(true);
38
39 $nav = $this->newNavigation(
40 $project,
41 PhabricatorProject::ITEM_MEMBERS);
42
43 $crumbs = $this->buildApplicationCrumbs();
44 $crumbs->addTextCrumb(pht('Members'));
45 $crumbs->setBorder(true);
46
47 $header = id(new PHUIHeaderView())
48 ->setHeader($title)
49 ->setHeaderIcon('fa-group');
50
51 require_celerity_resource('project-view-css');
52
53 $view = id(new PHUITwoColumnView())
54 ->setHeader($header)
55 ->setCurtain($curtain)
56 ->addClass('project-view-home')
57 ->addClass('project-view-people-home')
58 ->setMainColumn(array(
59 $member_list,
60 $watcher_list,
61 ));
62
63 return $this->newPage()
64 ->setNavigation($nav)
65 ->setCrumbs($crumbs)
66 ->setTitle(array($project->getName(), $title))
67 ->appendChild($view);
68 }
69
70 private function buildCurtainView(PhabricatorProject $project) {
71 $viewer = $this->getViewer();
72 $id = $project->getID();
73
74 $curtain = $this->newCurtainView();
75
76 $is_locked = $project->getIsMembershipLocked();
77
78 $can_edit = PhabricatorPolicyFilter::hasCapability(
79 $viewer,
80 $project,
81 PhabricatorPolicyCapability::CAN_EDIT);
82
83 $supports_edit = $project->supportsEditMembers();
84
85 $can_join = $supports_edit && PhabricatorPolicyFilter::hasCapability(
86 $viewer,
87 $project,
88 PhabricatorPolicyCapability::CAN_JOIN);
89
90 $can_leave = $supports_edit && (!$is_locked || $can_edit);
91
92 $viewer_phid = $viewer->getPHID();
93
94 if (!$project->isUserMember($viewer_phid)) {
95 $curtain->addAction(
96 id(new PhabricatorActionView())
97 ->setHref('/project/update/'.$project->getID().'/join/')
98 ->setIcon('fa-plus')
99 ->setDisabled(!$can_join)
100 ->setWorkflow(true)
101 ->setName(pht('Join Project')));
102 } else {
103 $curtain->addAction(
104 id(new PhabricatorActionView())
105 ->setHref('/project/update/'.$project->getID().'/leave/')
106 ->setIcon('fa-times')
107 ->setDisabled(!$can_leave)
108 ->setWorkflow(true)
109 ->setName(pht('Leave Project')));
110 }
111
112 if (!$project->isUserWatcher($viewer->getPHID())) {
113 $curtain->addAction(
114 id(new PhabricatorActionView())
115 ->setWorkflow(true)
116 ->setHref('/project/watch/'.$project->getID().'/')
117 ->setIcon('fa-eye')
118 ->setName(pht('Watch Project')));
119 } else {
120 $curtain->addAction(
121 id(new PhabricatorActionView())
122 ->setWorkflow(true)
123 ->setHref('/project/unwatch/'.$project->getID().'/')
124 ->setIcon('fa-eye-slash')
125 ->setName(pht('Unwatch Project')));
126 }
127
128 $can_silence = $project->isUserMember($viewer_phid);
129 $is_silenced = $this->isProjectSilenced($project);
130
131 if ($is_silenced) {
132 $silence_text = pht('Enable Mail');
133 } else {
134 $silence_text = pht('Disable Mail');
135 }
136
137 $curtain->addAction(
138 id(new PhabricatorActionView())
139 ->setName($silence_text)
140 ->setIcon('fa-envelope-o')
141 ->setHref("/project/silence/{$id}/")
142 ->setWorkflow(true)
143 ->setDisabled(!$can_silence));
144
145 $can_add = $can_edit && $supports_edit;
146
147 $curtain->addAction(
148 id(new PhabricatorActionView())
149 ->setName(pht('Add Members'))
150 ->setIcon('fa-user-plus')
151 ->setHref("/project/members/{$id}/add/")
152 ->setWorkflow(true)
153 ->setDisabled(!$can_add));
154
155 $can_lock = $can_edit && $supports_edit && $this->hasApplicationCapability(
156 ProjectCanLockProjectsCapability::CAPABILITY);
157
158 if ($is_locked) {
159 $lock_name = pht('Unlock Project');
160 $lock_icon = 'fa-unlock';
161 } else {
162 $lock_name = pht('Lock Project');
163 $lock_icon = 'fa-lock';
164 }
165
166 $curtain->addAction(
167 id(new PhabricatorActionView())
168 ->setName($lock_name)
169 ->setIcon($lock_icon)
170 ->setHref($this->getApplicationURI("lock/{$id}/"))
171 ->setDisabled(!$can_lock)
172 ->setWorkflow(true));
173
174 if ($project->isMilestone()) {
175 $icon_key = PhabricatorProjectIconSet::getMilestoneIconKey();
176 $header = PhabricatorProjectIconSet::getIconName($icon_key);
177 $note = pht(
178 'Members of the parent project are members of this project.');
179 $show_join = false;
180 } else if ($project->getHasSubprojects()) {
181 $header = pht('Parent Project');
182 $note = pht(
183 'Members of all subprojects are members of this project.');
184 $show_join = false;
185 } else if ($project->getIsMembershipLocked()) {
186 $header = pht('Locked Project');
187 $note = pht(
188 'Users with access may join this project, but may not leave.');
189 $show_join = true;
190 } else {
191 $header = pht('Normal Project');
192 $note = pht('Users with access may join and leave this project.');
193 $show_join = true;
194 }
195
196 $curtain->newPanel()
197 ->setHeaderText($header)
198 ->appendChild($note);
199
200 if ($show_join) {
201 $descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions(
202 $viewer,
203 $project);
204
205 $curtain->newPanel()
206 ->setHeaderText(pht('Joinable By'))
207 ->appendChild($descriptions[PhabricatorPolicyCapability::CAN_JOIN]);
208 }
209
210 return $curtain;
211 }
212
213 private function isProjectSilenced(PhabricatorProject $project) {
214 $viewer = $this->getViewer();
215
216 $viewer_phid = $viewer->getPHID();
217 if (!$viewer_phid) {
218 return false;
219 }
220
221 $edge_type = PhabricatorProjectSilencedEdgeType::EDGECONST;
222 $silenced = PhabricatorEdgeQuery::loadDestinationPHIDs(
223 $project->getPHID(),
224 $edge_type);
225 $silenced = array_fuse($silenced);
226 return isset($silenced[$viewer_phid]);
227 }
228
229}