@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 PhabricatorSearchApplicationSearchEngine
4 extends PhabricatorApplicationSearchEngine {
5
6 private $resultSet;
7
8 public function getResultTypeDescription() {
9 return pht('Fulltext Search Results');
10 }
11
12 public function getApplicationClassName() {
13 return PhabricatorSearchApplication::class;
14 }
15
16 public function buildSavedQueryFromRequest(AphrontRequest $request) {
17 $saved = new PhabricatorSavedQuery();
18
19 $saved->setParameter('query', $request->getStr('query'));
20 $saved->setParameter(
21 'statuses',
22 $this->readListFromRequest($request, 'statuses'));
23 $saved->setParameter(
24 'types',
25 $this->readListFromRequest($request, 'types'));
26
27 $saved->setParameter(
28 'authorPHIDs',
29 $this->readUsersFromRequest($request, 'authorPHIDs'));
30
31 $saved->setParameter(
32 'ownerPHIDs',
33 $this->readUsersFromRequest($request, 'ownerPHIDs'));
34
35 $saved->setParameter(
36 'subscriberPHIDs',
37 $this->readSubscribersFromRequest($request, 'subscriberPHIDs'));
38
39 $saved->setParameter(
40 'projectPHIDs',
41 $this->readPHIDsFromRequest($request, 'projectPHIDs'));
42
43 return $saved;
44 }
45
46 /**
47 * @param PhabricatorSavedQuery $saved
48 * @return PhabricatorSearchDocumentQuery
49 */
50 public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) {
51 $query = new PhabricatorSearchDocumentQuery();
52
53 // Convert the saved query into a resolved form (without typeahead
54 // functions) which the fulltext search engines can execute.
55 $config = clone $saved;
56 $viewer = $this->requireViewer();
57
58 $datasource = id(new PhabricatorPeopleOwnerDatasource())
59 ->setViewer($viewer);
60 $owner_phids = $this->readOwnerPHIDs($config);
61 $owner_phids = $datasource->evaluateTokens($owner_phids);
62 foreach ($owner_phids as $key => $phid) {
63 if ($phid == PhabricatorPeopleNoOwnerDatasource::FUNCTION_TOKEN) {
64 $config->setParameter('withUnowned', true);
65 unset($owner_phids[$key]);
66 }
67 if ($phid == PhabricatorPeopleAnyOwnerDatasource::FUNCTION_TOKEN) {
68 $config->setParameter('withAnyOwner', true);
69 unset($owner_phids[$key]);
70 }
71 }
72 $config->setParameter('ownerPHIDs', $owner_phids);
73
74
75 $datasource = id(new PhabricatorPeopleUserFunctionDatasource())
76 ->setViewer($viewer);
77 $author_phids = $config->getParameter('authorPHIDs', array());
78 $author_phids = $datasource->evaluateTokens($author_phids);
79 $config->setParameter('authorPHIDs', $author_phids);
80
81
82 $datasource = id(new PhabricatorMetaMTAMailableFunctionDatasource())
83 ->setViewer($viewer);
84 $subscriber_phids = $config->getParameter('subscriberPHIDs', array());
85 $subscriber_phids = $datasource->evaluateTokens($subscriber_phids);
86 $config->setParameter('subscriberPHIDs', $subscriber_phids);
87
88
89 $query->withSavedQuery($config);
90
91 return $query;
92 }
93
94 public function buildSearchForm(
95 AphrontFormView $form,
96 PhabricatorSavedQuery $saved) {
97
98 $options = array();
99 $author_value = null;
100 $owner_value = null;
101 $subscribers_value = null;
102 $project_value = null;
103
104 $author_phids = $saved->getParameter('authorPHIDs', array());
105 $owner_phids = $this->readOwnerPHIDs($saved);
106 $subscriber_phids = $saved->getParameter('subscriberPHIDs', array());
107 $project_phids = $saved->getParameter('projectPHIDs', array());
108
109 $status_values = $saved->getParameter('statuses', array());
110 $status_values = array_fuse($status_values);
111
112 $statuses = array(
113 PhabricatorSearchRelationship::RELATIONSHIP_OPEN => pht('Open'),
114 PhabricatorSearchRelationship::RELATIONSHIP_CLOSED => pht('Closed'),
115 );
116 $status_control = id(new AphrontFormCheckboxControl())
117 ->setLabel(pht('Item Status'));
118 foreach ($statuses as $status => $name) {
119 $status_control->addCheckbox(
120 'statuses[]',
121 $status,
122 $name,
123 isset($status_values[$status]));
124 }
125
126 $type_values = $saved->getParameter('types', array());
127 $type_values = array_fuse($type_values);
128
129 $types_control = id(new AphrontFormTokenizerControl())
130 ->setLabel(pht('Item Types'))
131 ->setName('types')
132 ->setDatasource(new PhabricatorSearchDocumentTypeDatasource())
133 ->setValue($type_values);
134
135 $form
136 ->appendChild(
137 phutil_tag(
138 'input',
139 array(
140 'type' => 'hidden',
141 'name' => 'jump',
142 'value' => 'no',
143 )))
144 ->appendChild(
145 id(new AphrontFormTextControl())
146 ->setLabel(pht('Query'))
147 ->setName('query')
148 ->setValue($saved->getParameter('query')))
149 ->appendChild($status_control)
150 ->appendControl($types_control)
151 ->appendControl(
152 id(new AphrontFormTokenizerControl())
153 ->setName('authorPHIDs')
154 ->setLabel(pht('Authors'))
155 ->setDatasource(new PhabricatorPeopleUserFunctionDatasource())
156 ->setValue($author_phids))
157 ->appendControl(
158 id(new AphrontFormTokenizerControl())
159 ->setName('ownerPHIDs')
160 ->setLabel(pht('Owners'))
161 ->setDatasource(new PhabricatorPeopleOwnerDatasource())
162 ->setValue($owner_phids))
163 ->appendControl(
164 id(new AphrontFormTokenizerControl())
165 ->setName('subscriberPHIDs')
166 ->setLabel(pht('Subscribers'))
167 ->setDatasource(new PhabricatorMetaMTAMailableFunctionDatasource())
168 ->setValue($subscriber_phids))
169 ->appendControl(
170 id(new AphrontFormTokenizerControl())
171 ->setName('projectPHIDs')
172 ->setLabel(pht('Project Tags'))
173 ->setDatasource(new PhabricatorProjectDatasource())
174 ->setValue($project_phids));
175 }
176
177 protected function getURI($path) {
178 return '/search/'.$path;
179 }
180
181 protected function getBuiltinQueryNames() {
182 return array(
183 'all' => pht('All Items'),
184 'open' => pht('Open Items'),
185 'open-tasks' => pht('Open Tasks'),
186 );
187 }
188
189 /**
190 * @return PhabricatorSavedQuery
191 */
192 public function buildSavedQueryFromBuiltin($query_key) {
193 $query = $this->newSavedQuery();
194 $query->setQueryKey($query_key);
195
196 switch ($query_key) {
197 case 'all':
198 return $query;
199 case 'open':
200 return $query->setParameter('statuses', array('open'));
201 case 'open-tasks':
202 return $query
203 ->setParameter('statuses', array('open'))
204 ->setParameter('types', array(ManiphestTaskPHIDType::TYPECONST));
205 }
206
207 return parent::buildSavedQueryFromBuiltin($query_key);
208 }
209
210 /**
211 * @return array<string,string> PHIDType and its description, for example
212 * 'TASK => Maniphest Task' or 'DREV => Differential Revision'
213 */
214 public static function getIndexableDocumentTypes(
215 ?PhabricatorUser $viewer = null) {
216
217 // TODO: This is inelegant and not very efficient, but gets us reasonable
218 // results. It would be nice to do this more elegantly.
219
220 $objects = id(new PhutilClassMapQuery())
221 ->setAncestorClass(PhabricatorFulltextInterface::class)
222 ->execute();
223
224 $type_map = array();
225 foreach ($objects as $object) {
226 $phid_type = phid_get_type($object->generatePHID());
227 $type_map[$phid_type] = $object;
228 }
229
230 if ($viewer) {
231 $types = PhabricatorPHIDType::getAllInstalledTypes($viewer);
232 } else {
233 $types = PhabricatorPHIDType::getAllTypes();
234 }
235
236 $results = array();
237 foreach ($types as $type) {
238 $typeconst = $type->getTypeConstant();
239 $object = idx($type_map, $typeconst);
240 if ($object) {
241 $results[$typeconst] = $type->getTypeName();
242 }
243 }
244
245 asort($results);
246
247 return $results;
248 }
249
250 public function shouldUseOffsetPaging() {
251 return true;
252 }
253
254 /**
255 * @param array<string,PhabricatorObjectHandle> $results Array of pairs of
256 * the object's PHID as key and the corresponding PhabricatorObjectHandle
257 * @param PhabricatorSavedQuery $query
258 * @param array<string,PhabricatorObjectHandle> $handles Unused.
259 */
260 protected function renderResultList(
261 array $results,
262 PhabricatorSavedQuery $query,
263 array $handles) {
264
265 $result_set = $this->resultSet;
266 $fulltext_tokens = $result_set->getFulltextTokens();
267
268 $viewer = $this->requireViewer();
269 $list = new PHUIObjectItemListView();
270 $list->setNoDataString(pht('No results found.'));
271
272 if ($results) {
273 $objects = id(new PhabricatorObjectQuery())
274 ->setViewer($viewer)
275 ->withPHIDs(mpull($results, 'getPHID'))
276 ->execute();
277
278 foreach ($results as $phid => $handle) {
279 $view = id(new PhabricatorSearchResultView())
280 ->setHandle($handle)
281 ->setTokens($fulltext_tokens)
282 ->setObject(idx($objects, $phid))
283 ->render();
284 $list->addItem($view);
285 }
286 }
287
288 $fulltext_view = null;
289 if ($fulltext_tokens) {
290 require_celerity_resource('phabricator-search-results-css');
291
292 $fulltext_view = array();
293 foreach ($fulltext_tokens as $token) {
294 $fulltext_view[] = $token->newTag();
295 }
296 $fulltext_view = phutil_tag(
297 'div',
298 array(
299 'class' => 'phui-fulltext-tokens',
300 ),
301 array(
302 pht('Searched For:'),
303 ' ',
304 $fulltext_view,
305 ));
306 }
307
308 $result = new PhabricatorApplicationSearchResultView();
309 $result->setContent($fulltext_view);
310 $result->setObjectList($list);
311
312 return $result;
313 }
314
315 private function readOwnerPHIDs(PhabricatorSavedQuery $saved) {
316 $owner_phids = $saved->getParameter('ownerPHIDs', array());
317
318 // This was an old checkbox from before typeahead functions.
319 if ($saved->getParameter('withUnowned')) {
320 $owner_phids[] = PhabricatorPeopleNoOwnerDatasource::FUNCTION_TOKEN;
321 }
322
323 return $owner_phids;
324 }
325
326 protected function didExecuteQuery(PhabricatorPolicyAwareQuery $query) {
327 $this->resultSet = $query->getFulltextResultSet();
328 }
329
330}