@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 PhabricatorProjectSearchEngine
4 extends PhabricatorApplicationSearchEngine {
5
6 public function getResultTypeDescription() {
7 return pht('Projects');
8 }
9
10 public function getApplicationClassName() {
11 return PhabricatorProjectApplication::class;
12 }
13
14 public function newQuery() {
15 return id(new PhabricatorProjectQuery())
16 ->needImages(true)
17 ->needMembers(true)
18 ->needWatchers(true);
19 }
20
21 protected function buildCustomSearchFields() {
22 $subtype_map = id(new PhabricatorProject())->newEditEngineSubtypeMap();
23 $hide_subtypes = ($subtype_map->getCount() == 1);
24
25 return array(
26 id(new PhabricatorSearchTextField())
27 ->setLabel(pht('Name'))
28 ->setKey('name')
29 ->setDescription(
30 pht(
31 '(Deprecated.) Search for projects with a given name or '.
32 'hashtag using tokenizer/datasource query matching rules. This '.
33 'is deprecated in favor of the more powerful "query" '.
34 'constraint.')),
35 id(new PhabricatorSearchStringListField())
36 ->setLabel(pht('Project Tags'))
37 ->setIsHidden(true)
38 ->setKey('slugs')
39 ->setDescription(
40 pht(
41 'Search for projects with particular hashtags.)')),
42 id(new PhabricatorUsersSearchField())
43 ->setLabel(pht('Members'))
44 ->setKey('memberPHIDs')
45 ->setConduitKey('members')
46 ->setDescription(pht('Search for projects with particular members.'))
47 ->setAliases(array('member', 'members')),
48 id(new PhabricatorUsersSearchField())
49 ->setLabel(pht('Watchers'))
50 ->setKey('watcherPHIDs')
51 ->setConduitKey('watchers')
52 ->setDescription(pht('Search for projects with particular watchers.'))
53 ->setAliases(array('watcher', 'watchers')),
54 id(new PhabricatorSearchSelectField())
55 ->setLabel(pht('Status'))
56 ->setKey('status')
57 ->setOptions($this->getStatusOptions())
58 ->setDefault('active'),
59 id(new PhabricatorSearchThreeStateField())
60 ->setLabel(pht('Milestones'))
61 ->setKey('isMilestone')
62 ->setOptions(
63 pht('(Show All)'),
64 pht('Show Only Milestones'),
65 pht('Hide Milestones'))
66 ->setDescription(
67 pht(
68 'Pass true to find only milestones, or false to omit '.
69 'milestones.')),
70 id(new PhabricatorSearchThreeStateField())
71 ->setLabel(pht('Root Projects'))
72 ->setKey('isRoot')
73 ->setOptions(
74 pht('(Show All)'),
75 pht('Show Only Root Projects'),
76 pht('Hide Root Projects'))
77 ->setDescription(
78 pht(
79 'Pass true to find only root projects, or false to omit '.
80 'root projects.')),
81 id(new PhabricatorSearchIntField())
82 ->setLabel(pht('Minimum Depth'))
83 ->setKey('minDepth')
84 ->setIsHidden(true)
85 ->setDescription(
86 pht(
87 'Find projects with a given minimum depth. Root projects '.
88 'have depth 0, their immediate children have depth 1, and '.
89 'so on.')),
90 id(new PhabricatorSearchIntField())
91 ->setLabel(pht('Maximum Depth'))
92 ->setKey('maxDepth')
93 ->setIsHidden(true)
94 ->setDescription(
95 pht(
96 'Find projects with a given maximum depth. Root projects '.
97 'have depth 0, their immediate children have depth 1, and '.
98 'so on.')),
99 id(new PhabricatorSearchDatasourceField())
100 ->setLabel(pht('Subtypes'))
101 ->setKey('subtypes')
102 ->setAliases(array('subtype'))
103 ->setDescription(
104 pht('Search for projects with given subtypes.'))
105 ->setDatasource(new PhabricatorProjectSubtypeDatasource())
106 ->setIsHidden($hide_subtypes),
107 id(new PhabricatorSearchCheckboxesField())
108 ->setLabel(pht('Icons'))
109 ->setKey('icons')
110 ->setDescription(pht('Search for projects with particular icons.'))
111 ->setOptions($this->getIconOptions()),
112 id(new PhabricatorSearchCheckboxesField())
113 ->setLabel(pht('Colors'))
114 ->setKey('colors')
115 ->setDescription(pht('Search for projects with particular colors.'))
116 ->setOptions($this->getColorOptions()),
117 id(new PhabricatorPHIDsSearchField())
118 ->setLabel(pht('Parent Projects'))
119 ->setKey('parentPHIDs')
120 ->setConduitKey('parents')
121 ->setAliases(array('parent', 'parents', 'parentPHID'))
122 ->setDescription(pht('Find direct subprojects of specified parents.')),
123 id(new PhabricatorPHIDsSearchField())
124 ->setLabel(pht('Ancestor Projects'))
125 ->setKey('ancestorPHIDs')
126 ->setConduitKey('ancestors')
127 ->setAliases(array('ancestor', 'ancestors', 'ancestorPHID'))
128 ->setDescription(
129 pht('Find all subprojects beneath specified ancestors.')),
130 );
131 }
132
133
134 protected function buildQueryFromParameters(array $map) {
135 $query = $this->newQuery();
136
137 if (strlen($map['name'])) {
138 $tokens = PhabricatorTypeaheadDatasource::tokenizeString($map['name']);
139 $query->withNameTokens($tokens);
140 }
141
142 if ($map['slugs']) {
143 $query->withSlugs($map['slugs']);
144 }
145
146 if ($map['memberPHIDs']) {
147 $query->withMemberPHIDs($map['memberPHIDs']);
148 }
149
150 if ($map['watcherPHIDs']) {
151 $query->withWatcherPHIDs($map['watcherPHIDs']);
152 }
153
154 if ($map['status']) {
155 $status = idx($this->getStatusValues(), $map['status']);
156 if ($status) {
157 $query->withStatus($status);
158 }
159 }
160
161 if ($map['icons']) {
162 $query->withIcons($map['icons']);
163 }
164
165 if ($map['colors']) {
166 $query->withColors($map['colors']);
167 }
168
169 if ($map['isMilestone'] !== null) {
170 $query->withIsMilestone($map['isMilestone']);
171 }
172
173 $min_depth = $map['minDepth'];
174 $max_depth = $map['maxDepth'];
175
176 if ($min_depth !== null || $max_depth !== null) {
177 if ($min_depth !== null && $max_depth !== null) {
178 if ($min_depth > $max_depth) {
179 throw new Exception(
180 pht(
181 'Search constraint "minDepth" must be no larger than '.
182 'search constraint "maxDepth".'));
183 }
184 }
185 }
186
187 if ($map['isRoot'] !== null) {
188 if ($map['isRoot']) {
189 if ($max_depth === null) {
190 $max_depth = 0;
191 } else {
192 $max_depth = min(0, $max_depth);
193 }
194
195 $query->withDepthBetween(null, 0);
196 } else {
197 if ($min_depth === null) {
198 $min_depth = 1;
199 } else {
200 $min_depth = max($min_depth, 1);
201 }
202 }
203 }
204
205 if ($min_depth !== null || $max_depth !== null) {
206 $query->withDepthBetween($min_depth, $max_depth);
207 }
208
209 if ($map['parentPHIDs']) {
210 $query->withParentProjectPHIDs($map['parentPHIDs']);
211 }
212
213 if ($map['ancestorPHIDs']) {
214 $query->withAncestorProjectPHIDs($map['ancestorPHIDs']);
215 }
216
217 if ($map['subtypes']) {
218 $query->withSubtypes($map['subtypes']);
219 }
220
221 return $query;
222 }
223
224 protected function getURI($path) {
225 return '/project/'.$path;
226 }
227
228 protected function getBuiltinQueryNames() {
229 $names = array();
230
231 if ($this->requireViewer()->isLoggedIn()) {
232 $names['joined'] = pht('Joined');
233 }
234
235 if ($this->requireViewer()->isLoggedIn()) {
236 $names['watching'] = pht('Watching');
237 }
238
239 $names['active'] = pht('Active');
240 $names['all'] = pht('All');
241
242 return $names;
243 }
244
245 public function buildSavedQueryFromBuiltin($query_key) {
246 $query = $this->newSavedQuery();
247 $query->setQueryKey($query_key);
248
249 $viewer_phid = $this->requireViewer()->getPHID();
250
251 // By default, do not show milestones in the list view.
252 $query->setParameter('isMilestone', false);
253
254 $active = PhabricatorProjectStatus::STATUS_ACTIVE_KEY;
255
256 switch ($query_key) {
257 case 'all':
258 return $query;
259 case 'active':
260 return $query
261 ->setParameter('status', $active);
262 case 'joined':
263 return $query
264 ->setParameter('memberPHIDs', array($viewer_phid))
265 ->setParameter('status', $active);
266 case 'watching':
267 return $query
268 ->setParameter('watcherPHIDs', array($viewer_phid))
269 ->setParameter('status', $active);
270 }
271
272 return parent::buildSavedQueryFromBuiltin($query_key);
273 }
274
275 private function getStatusOptions() {
276 $active = PhabricatorProjectStatus::STATUS_ACTIVE_KEY;
277 $archived = PhabricatorProjectStatus::STATUS_ARCHIVED_KEY;
278
279 return array(
280 $active => pht('Show Only Active Projects'),
281 $archived => pht('Show Only Archived Projects'),
282 'all' => pht('Show All Projects'),
283 );
284 }
285
286 private function getStatusValues() {
287 $active = PhabricatorProjectStatus::STATUS_ACTIVE_KEY;
288 $archived = PhabricatorProjectStatus::STATUS_ARCHIVED_KEY;
289
290 return array(
291 $active => PhabricatorProjectQuery::STATUS_ACTIVE,
292 $archived => PhabricatorProjectQuery::STATUS_ARCHIVED,
293 'all' => PhabricatorProjectQuery::STATUS_ANY,
294 );
295 }
296
297 private function getIconOptions() {
298 $options = array();
299
300 $set = new PhabricatorProjectIconSet();
301 foreach ($set->getIcons() as $icon) {
302 if ($icon->getIsDisabled()) {
303 continue;
304 }
305
306 $options[$icon->getKey()] = array(
307 id(new PHUIIconView())
308 ->setIcon($icon->getIcon()),
309 ' ',
310 $icon->getLabel(),
311 );
312 }
313
314 return $options;
315 }
316
317 private function getColorOptions() {
318 $options = array();
319
320 foreach (PhabricatorProjectIconSet::getColorMap() as $color => $name) {
321 $options[$color] = array(
322 id(new PHUITagView())
323 ->setType(PHUITagView::TYPE_SHADE)
324 ->setColor($color)
325 ->setName($name),
326 );
327 }
328
329 return $options;
330 }
331
332 /**
333 * @param array<PhabricatorProject> $projects
334 * @param PhabricatorSavedQuery $query
335 * @param array<PhabricatorObjectHandle> $handles
336 */
337 protected function renderResultList(
338 array $projects,
339 PhabricatorSavedQuery $query,
340 array $handles) {
341 assert_instances_of($projects, PhabricatorProject::class);
342 $viewer = $this->requireViewer();
343
344 $list = id(new PhabricatorProjectListView())
345 ->setViewer($viewer)
346 ->setProjects($projects)
347 ->setShowWatching(true)
348 ->setShowMember(true)
349 ->renderList();
350
351 return id(new PhabricatorApplicationSearchResultView())
352 ->setObjectList($list)
353 ->setNoDataString(pht('No projects found.'));
354 }
355
356 protected function getNewUserBody() {
357 $create_button = id(new PHUIButtonView())
358 ->setTag('a')
359 ->setText(pht('Create a Project'))
360 ->setHref('/project/edit/')
361 ->setColor(PHUIButtonView::GREEN);
362
363 $icon = $this->getApplication()->getIcon();
364 $app_name = $this->getApplication()->getName();
365 $view = id(new PHUIBigInfoView())
366 ->setIcon($icon)
367 ->setTitle(pht('Welcome to %s', $app_name))
368 ->setDescription(
369 pht('Projects are labels are tags. You can use them for a codebase, '.
370 'a team, a sprint, or anything you need to group or categorize.'))
371 ->addAction($create_button);
372
373 return $view;
374 }
375
376}