Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/applications/conpherence/query/ConpherenceThreadSearchEngine.php
12256 views
1
<?php
2
3
final class ConpherenceThreadSearchEngine
4
extends PhabricatorApplicationSearchEngine {
5
6
public function getResultTypeDescription() {
7
return pht('Conpherence Rooms');
8
}
9
10
public function getApplicationClassName() {
11
return 'PhabricatorConpherenceApplication';
12
}
13
14
public function newQuery() {
15
return id(new ConpherenceThreadQuery())
16
->needProfileImage(true);
17
}
18
19
protected function buildCustomSearchFields() {
20
return array(
21
id(new PhabricatorUsersSearchField())
22
->setLabel(pht('Participants'))
23
->setKey('participants')
24
->setAliases(array('participant')),
25
id(new PhabricatorSearchDatasourceField())
26
->setLabel(pht('Rooms'))
27
->setKey('phids')
28
->setDescription(pht('Search by room titles.'))
29
->setDatasource(id(new ConpherenceThreadDatasource())),
30
id(new PhabricatorSearchTextField())
31
->setLabel(pht('Room Contains Words'))
32
->setKey('fulltext'),
33
);
34
}
35
36
protected function getDefaultFieldOrder() {
37
return array(
38
'participants',
39
'...',
40
);
41
}
42
43
protected function shouldShowOrderField() {
44
return false;
45
}
46
47
protected function buildQueryFromParameters(array $map) {
48
$query = $this->newQuery();
49
if ($map['participants']) {
50
$query->withParticipantPHIDs($map['participants']);
51
}
52
if ($map['fulltext']) {
53
$query->withFulltext($map['fulltext']);
54
}
55
if ($map['phids']) {
56
$query->withPHIDs($map['phids']);
57
}
58
return $query;
59
}
60
61
protected function getURI($path) {
62
return '/conpherence/search/'.$path;
63
}
64
65
protected function getBuiltinQueryNames() {
66
$names = array();
67
68
$names['all'] = pht('All Rooms');
69
70
if ($this->requireViewer()->isLoggedIn()) {
71
$names['participant'] = pht('Joined Rooms');
72
}
73
74
return $names;
75
}
76
77
public function buildSavedQueryFromBuiltin($query_key) {
78
79
$query = $this->newSavedQuery();
80
$query->setQueryKey($query_key);
81
82
switch ($query_key) {
83
case 'all':
84
return $query;
85
case 'participant':
86
return $query->setParameter(
87
'participants',
88
array($this->requireViewer()->getPHID()));
89
}
90
91
return parent::buildSavedQueryFromBuiltin($query_key);
92
}
93
94
protected function renderResultList(
95
array $conpherences,
96
PhabricatorSavedQuery $query,
97
array $handles) {
98
assert_instances_of($conpherences, 'ConpherenceThread');
99
100
$viewer = $this->requireViewer();
101
102
$policy_objects = ConpherenceThread::loadViewPolicyObjects(
103
$viewer,
104
$conpherences);
105
106
$engines = array();
107
108
$fulltext = $query->getParameter('fulltext');
109
if ($fulltext !== null && strlen($fulltext) && $conpherences) {
110
$context = $this->loadContextMessages($conpherences, $fulltext);
111
112
$author_phids = array();
113
foreach ($context as $phid => $messages) {
114
$conpherence = $conpherences[$phid];
115
116
$engine = id(new PhabricatorMarkupEngine())
117
->setViewer($viewer)
118
->setContextObject($conpherence);
119
120
foreach ($messages as $group) {
121
foreach ($group as $message) {
122
$xaction = $message['xaction'];
123
if ($xaction) {
124
$author_phids[] = $xaction->getAuthorPHID();
125
$engine->addObject(
126
$xaction->getComment(),
127
PhabricatorApplicationTransactionComment::MARKUP_FIELD_COMMENT);
128
}
129
}
130
}
131
$engine->process();
132
133
$engines[$phid] = $engine;
134
}
135
136
$handles = $viewer->loadHandles($author_phids);
137
$handles = iterator_to_array($handles);
138
} else {
139
$context = array();
140
}
141
142
$content = array();
143
$list = new PHUIObjectItemListView();
144
$list->setUser($viewer);
145
foreach ($conpherences as $conpherence_phid => $conpherence) {
146
$created = phabricator_date($conpherence->getDateCreated(), $viewer);
147
$title = $conpherence->getTitle();
148
$monogram = $conpherence->getMonogram();
149
150
$icon_name = $conpherence->getPolicyIconName($policy_objects);
151
$icon = id(new PHUIIconView())
152
->setIcon($icon_name);
153
154
if ($fulltext === null || !strlen($fulltext)) {
155
$item = id(new PHUIObjectItemView())
156
->setObjectName($conpherence->getMonogram())
157
->setHeader($title)
158
->setHref('/'.$conpherence->getMonogram())
159
->setObject($conpherence)
160
->setImageURI($conpherence->getProfileImageURI())
161
->addIcon('none', $created)
162
->addIcon(
163
'none',
164
pht('Messages: %d', $conpherence->getMessageCount()))
165
->addAttribute(
166
array(
167
$icon,
168
' ',
169
pht(
170
'Last updated %s',
171
phabricator_datetime($conpherence->getDateModified(), $viewer)),
172
));
173
$list->addItem($item);
174
} else {
175
$messages = idx($context, $conpherence_phid);
176
$box = array();
177
$list = null;
178
if ($messages) {
179
foreach ($messages as $group) {
180
$rows = array();
181
foreach ($group as $message) {
182
$xaction = $message['xaction'];
183
if (!$xaction) {
184
continue;
185
}
186
187
$view = id(new ConpherenceTransactionView())
188
->setUser($viewer)
189
->setHandles($handles)
190
->setMarkupEngine($engines[$conpherence_phid])
191
->setConpherenceThread($conpherence)
192
->setConpherenceTransaction($xaction)
193
->setSearchResult(true)
194
->addClass('conpherence-fulltext-result');
195
196
if ($message['match']) {
197
$view->addClass('conpherence-fulltext-match');
198
}
199
200
$rows[] = $view;
201
}
202
$box[] = id(new PHUIBoxView())
203
->appendChild($rows)
204
->addClass('conpherence-fulltext-results');
205
}
206
}
207
$header = id(new PHUIHeaderView())
208
->setHeader($title)
209
->setHeaderIcon($icon_name)
210
->setHref('/'.$monogram);
211
212
$content[] = id(new PHUIObjectBoxView())
213
->setHeader($header)
214
->appendChild($box);
215
}
216
}
217
218
if ($list) {
219
$content = $list;
220
} else {
221
$content = id(new PHUIBoxView())
222
->addClass('conpherence-search-room-results')
223
->appendChild($content);
224
}
225
226
$result = new PhabricatorApplicationSearchResultView();
227
$result->setContent($content);
228
$result->setNoDataString(pht('No results found.'));
229
230
return $result;
231
}
232
233
private function loadContextMessages(array $threads, $fulltext) {
234
$phids = mpull($threads, 'getPHID');
235
236
// We want to load a few messages for each thread in the result list, to
237
// show some of the actual content hits to help the user find what they
238
// are looking for.
239
240
// This method is trying to batch this lookup in most cases, so we do
241
// between one and "a handful" of queries instead of one per thread in
242
// most cases. To do this:
243
//
244
// - Load a big block of results for all of the threads.
245
// - If we didn't get a full block back, we have everything that matches
246
// the query. Sort it out and exit.
247
// - Otherwise, some threads had a ton of hits, so we might not be
248
// getting everything we want (we could be getting back 1,000 hits for
249
// the first thread). Remove any threads which we have enough results
250
// for and try again.
251
// - Repeat until we have everything or every thread has enough results.
252
//
253
// In the worst case, we could end up degrading to one query per thread,
254
// but this is incredibly unlikely on real data.
255
256
// Size of the result blocks we're going to load.
257
$limit = 1000;
258
259
// Number of messages we want for each thread.
260
$want = 3;
261
262
$need = $phids;
263
$hits = array();
264
while ($need) {
265
$rows = id(new ConpherenceFulltextQuery())
266
->withThreadPHIDs($need)
267
->withFulltext($fulltext)
268
->setLimit($limit)
269
->execute();
270
271
foreach ($rows as $row) {
272
$hits[$row['threadPHID']][] = $row;
273
}
274
275
if (count($rows) < $limit) {
276
break;
277
}
278
279
foreach ($need as $key => $phid) {
280
if (count($hits[$phid]) >= $want) {
281
unset($need[$key]);
282
}
283
}
284
}
285
286
// Now that we have all the fulltext matches, throw away any extras that we
287
// aren't going to render so we don't need to do lookups on them.
288
foreach ($hits as $phid => $rows) {
289
if (count($rows) > $want) {
290
$hits[$phid] = array_slice($rows, 0, $want);
291
}
292
}
293
294
// For each fulltext match, we want to render a message before and after
295
// the match to give it some context. We already know the transactions
296
// before each match because the rows have a "previousTransactionPHID",
297
// but we need to do one more query to figure out the transactions after
298
// each match.
299
300
// Collect the transactions we want to find the next transactions for.
301
$after = array();
302
foreach ($hits as $phid => $rows) {
303
foreach ($rows as $row) {
304
$after[] = $row['transactionPHID'];
305
}
306
}
307
308
// Look up the next transactions.
309
if ($after) {
310
$after_rows = id(new ConpherenceFulltextQuery())
311
->withPreviousTransactionPHIDs($after)
312
->execute();
313
} else {
314
$after_rows = array();
315
}
316
317
// Build maps from PHIDs to the previous and next PHIDs.
318
$prev_map = array();
319
$next_map = array();
320
foreach ($after_rows as $row) {
321
$next_map[$row['previousTransactionPHID']] = $row['transactionPHID'];
322
}
323
324
foreach ($hits as $phid => $rows) {
325
foreach ($rows as $row) {
326
$prev = $row['previousTransactionPHID'];
327
if ($prev) {
328
$prev_map[$row['transactionPHID']] = $prev;
329
$next_map[$prev] = $row['transactionPHID'];
330
}
331
}
332
}
333
334
// Now we're going to collect the actual transaction PHIDs, in order, that
335
// we want to show for each thread.
336
$groups = array();
337
foreach ($hits as $thread_phid => $rows) {
338
$rows = ipull($rows, null, 'transactionPHID');
339
$done = array();
340
foreach ($rows as $phid => $row) {
341
if (isset($done[$phid])) {
342
continue;
343
}
344
$done[$phid] = true;
345
346
$group = array();
347
348
// Walk backward, finding all the previous results. We can just keep
349
// going until we run out of results because we've only loaded things
350
// that we want to show.
351
$prev = $phid;
352
while (true) {
353
if (!isset($prev_map[$prev])) {
354
// No previous transaction, so we're done.
355
break;
356
}
357
358
$prev = $prev_map[$prev];
359
360
if (isset($rows[$prev])) {
361
$match = true;
362
$done[$prev] = true;
363
} else {
364
$match = false;
365
}
366
367
$group[] = array(
368
'phid' => $prev,
369
'match' => $match,
370
);
371
}
372
373
if (count($group) > 1) {
374
$group = array_reverse($group);
375
}
376
377
$group[] = array(
378
'phid' => $phid,
379
'match' => true,
380
);
381
382
$next = $phid;
383
while (true) {
384
if (!isset($next_map[$next])) {
385
break;
386
}
387
388
$next = $next_map[$next];
389
390
if (isset($rows[$next])) {
391
$match = true;
392
$done[$next] = true;
393
} else {
394
$match = false;
395
}
396
397
$group[] = array(
398
'phid' => $next,
399
'match' => $match,
400
);
401
}
402
403
$groups[$thread_phid][] = $group;
404
}
405
}
406
407
// Load all the actual transactions we need.
408
$xaction_phids = array();
409
foreach ($groups as $thread_phid => $group) {
410
foreach ($group as $list) {
411
foreach ($list as $item) {
412
$xaction_phids[] = $item['phid'];
413
}
414
}
415
}
416
417
if ($xaction_phids) {
418
$xactions = id(new ConpherenceTransactionQuery())
419
->setViewer($this->requireViewer())
420
->withPHIDs($xaction_phids)
421
->needComments(true)
422
->execute();
423
$xactions = mpull($xactions, null, 'getPHID');
424
} else {
425
$xactions = array();
426
}
427
428
foreach ($groups as $thread_phid => $group) {
429
foreach ($group as $key => $list) {
430
foreach ($list as $lkey => $item) {
431
$xaction = idx($xactions, $item['phid']);
432
if ($xaction->shouldHide()) {
433
continue;
434
}
435
$groups[$thread_phid][$key][$lkey]['xaction'] = $xaction;
436
}
437
}
438
}
439
440
// TODO: Sort the groups chronologically?
441
442
return $groups;
443
}
444
445
}
446
447