Code source wiki de Solr Search Macros

Modifié par Thomas Mortagne le 2022/06/29 13:01

Afficher les derniers auteurs
1 {{template name="hierarchy_macros.vm" /}}
2
3 {{velocity output='false'}}
4 #set ($rangePattern = $regextool.compile('^[\[{](.+) TO (.+)[\]}]$'))
5 #set ($wildcardPattern = $regextool.compile('^\(.*\*.*\)$'))
6
7 #macro (displaySearchForm)
8 #set($void = $services.progress.startStep('#displaySearchForm'))
9 {{html clean="false"}}
10 <form class="search-form row" action="$doc.getURL()" role="search">
11 <div class="hidden">
12 <input type="hidden" name="sort" value="$!escapetool.xml($sort)"/>
13 <input type="hidden" name="sortOrder" value="$!escapetool.xml($sortOrder)"/>
14 <input type="hidden" name="highlight" value="$highlightEnabled"/>
15 <input type="hidden" name="facet" value="$facetEnabled"/>
16 ## The parameter used to determine if the request has been redirected with default search filters.
17 <input type="hidden" name="r" value="$!escapetool.xml($request.r)"/>
18 #if ("$!request.debug" != '')
19 <input type="hidden" name="debug" value="$escapetool.xml($request.debug)"/>
20 #end
21 ## Preserve the current facet values when submitting a new search query.
22 #foreach ($entry in $request.parameterMap.entrySet())
23 #if ($entry.key.startsWith('f_') || $entry.key.startsWith('l_'))
24 #foreach ($value in $entry.value)
25 <input type="hidden" name="$escapetool.xml($entry.key)" value="$escapetool.xml($value)"/>
26 #end
27 #end
28 #end
29 </div>
30 <div class="col-xs-12 col-sm-6">
31 <div class="input-group">
32 <input type="search" name="text" class="form-control withTip useTitleAsTip"
33 title="$services.localization.render('search.page.bar.query.title')" value="$escapetool.xml($text)"/>
34 <span class="input-group-btn">
35 <button type="submit" class="btn btn-primary">
36 $services.icon.renderHTML('search')
37 <span class="sr-only">$services.localization.render('search.page.bar.submit')</span>
38 </button>
39 </span>
40 </div>
41 </div>
42 </form>
43 {{/html}}
44 #set($void = $services.progress.endStep())
45 #end
46
47 #macro (displaySearchDebugInfo)
48 (% class="search-debug" %)(((
49 === Debug Information ===
50 #set ($debugMap = $searchResponse.debugMap)
51 #if ($debugMap)
52
53 {{html clean="false"}}
54 <dl>
55 <dt>Query Parser</dt>
56 <dd>$!escapetool.xml($debugMap.get('QParser'))</dd>
57 <dt>Parsed Query</dt>
58 <dd>$!escapetool.xml($debugMap.get('parsedquery_toString'))</dd>
59 <dt>Filter Queries</dt>
60 <dd>
61 <ul>
62 #foreach ($filterQuery in $debugMap.get('filter_queries'))
63 <li>$!escapetool.xml($filterQuery)</li>
64 #end
65 </ul>
66 </dd>
67 <dt>Processing Time</dt>
68 <dd>
69 #displayProcessingTime($debugMap.get('timing'))
70 </dd>
71 </dl>
72 {{/html}}
73 #end
74 )))
75 #end
76
77 #macro (displayProcessingTime $timing)
78 <ul>
79 ## The timing is not a Map but a NamedList.
80 #foreach ($entry in $timing)
81 <li>
82 $!escapetool.xml($entry.key):
83 #if ($entry.value.time && $entry.value.size() > 1)
84 #displayProcessingTime($entry.value)
85 #else
86 $!escapetool.xml($entry.value)
87 #end
88 </li>
89 #end
90 </ul>
91 #end
92
93 #macro (displaySearchFacets $searchResponse)
94 #set($void = $services.progress.startStep('#displaySearchFacets'))
95 (% class="search-facets collapsed-xs xform" %)(((
96 (% class="search-facets-header" %)(((
97 **{{translation key="solr.facets.title"/}}** (% class="pull-right visible-xs" %)$services.icon.render('search-plus')
98
99 (% class="xHint" %)
100 {{translation key="solr.facets.hint"/}}
101 )))
102 (% class="search-facets-actions" %)(((
103 #set ($resetParameters = {})
104 #foreach ($parameter in $request.parameterMap.entrySet())
105 #if ($parameter.key.startsWith('f_') || $parameter.key.startsWith('l_'))
106 #set ($discard = $resetParameters.put($parameter.key, []))
107 #end
108 #end
109 #extendQueryString($url $resetParameters)
110 [[{{translation key="solr.facets.resetAll"}}>>path:$url
111 ||class="search-facets-action-reset"]]## Continue in the same paragraph.
112 {{html clean="false"}}
113 <a href="#" class="search-facets-action-collapseAll hidden">
114 $escapetool.xml($services.localization.render('solr.facets.collapseAll'))
115 </a>
116 <a href="#" class="search-facets-action-expandAll hidden">
117 $escapetool.xml($services.localization.render('solr.facets.expandAll'))
118 </a>
119 <span class="clearfloats"></span>
120 {{/html}}
121 )))
122 {{html clean="false"}}
123 #foreach ($facetField in $searchResponse.facetFields)
124 #displaySearchFacet($facetField)
125 #end
126 {{/html}}
127 )))
128 #set($void = $services.progress.endStep())
129 #end
130
131 #macro (displaySearchFacet $facetField)
132 #set ($facetRequestParameter = "f_$facetField.name")
133 #set ($facetRequestValues = $request.getParameterValues($facetRequestParameter))
134 #set ($facetValues = [])
135 #foreach ($facetValue in $facetField.values)
136 ## Keep only the values that have at least one match or that are specified on the request.
137 #if ($facetValue.count > 0 || ($facetRequestValues && $facetRequestValues.contains($facetValue.name)))
138 #set ($discard = $facetValues.add($facetValue))
139 #end
140 #end
141 ## Facets that perform a 'facet.prefix'-based drill down (see https://wiki.apache.org/solr/HierarchicalFaceting) don't
142 ## have any values (not even with 0 count) when the prefix specified on the request doesn't have any "sub-values", but
143 ## we still want to display them to allow the user to reset the filter.
144 #if ($facetValues.size() > 0 || $facetRequestValues)
145 ## Show active facets (that have selected values or that have an explicit limit on the number of values, i.e.
146 ## pagination) as expanded. Collapse the rest, otherwise you have to scroll to see all the available facets.
147 #set ($facetValuesLimit = $request.getParameter("l_$facetField.name"))
148 <div class="search-facet#if ($facetRequestValues || $facetValuesLimit) expanded#end" data-name="$facetField.name">
149 #displaySearchFacetHeader($facetField)
150 #displaySearchFacetBody($facetField)
151 </div>
152 #end
153 #end
154
155 #macro (getXClassProperty $solrFieldName $property $classPropertyReference)
156 ## Remove the 'property.' prefix and the data type suffix.
157 #set ($stringReference = $stringtool.substringBeforeLast($solrFieldName.substring(9), '_'))
158 ## Note that the class property reference is resolved relative to the current wiki. This means the class must be
159 ## available on the wiki where the search is performed.
160 #set ($classPropertyReference = $NULL)
161 #setVariable("$classPropertyReference" $services.model.resolveClassProperty($stringReference, 'solr'))
162 #set ($classDocument = $xwiki.getDocument($classPropertyReference.parent))
163 #set ($property = $NULL)
164 #setVariable("$property" $classDocument.xWikiClass.get($classPropertyReference.name))
165 #end
166
167 #macro (displaySearchFacetHeader $facetField)
168 #set ($facetPrettyNameKey = "solr.field.$facetField.name")
169 #if ($services.localization.get($facetPrettyNameKey))
170 #set ($facetPrettyName = $services.localization.render($facetPrettyNameKey))
171 #elseif ($facetField.name.startsWith('property.'))
172 ## Display the translated property pretty name.
173 #getXClassProperty($facetField.name $property $classPropertyReference)
174 #set ($facetPrettyName = $property.translatedPrettyName)
175 #if ("$!facetPrettyName" == '')
176 #set ($facetPrettyName = $classPropertyReference.name)
177 #end
178 #else
179 #set ($facetPrettyName = $facetField.name)
180 #end
181 <div class="search-facet-header">$escapetool.xml($facetPrettyName)</div>
182 #end
183
184 #macro (displaySearchFacetBody $facetField)
185 <div class="search-facet-body">
186 #set ($facetDisplayer = $solrConfig.facetDisplayers.get($facetField.name))
187 #if (!$facetDisplayer && $facetField.name.startsWith('property.'))
188 ## Choose a facet displayer based on the property type.
189 #getXClassProperty($facetField.name $property)
190 ## We rely on configuration instead of using a naming convention like "Main.Solr${property.classType}Facet"
191 ## because most of the property types don't need a custom facet displayer.
192 #set ($facetDisplayer = $solrConfig.facetDisplayersByPropertyType.get($property.classType))
193 #end
194 #if ($facetDisplayer)
195 #set ($facetDisplayer = $xwiki.getDocument($facetDisplayer))
196 #if ("$!facetDisplayer.content" != '')
197 $!facetDisplayer.getRenderedContent(false)
198 #else
199 #displaySearchFacetValues($facetValues)
200 #end
201 #else
202 #displaySearchFacetValues($facetValues)
203 #end
204 </div>
205 #end
206
207 #macro (displaySearchFacetValues $facetValues $customQueryStringParameters $customValueDisplayer)
208 #if ($facetValues.size() > 0)
209 <ul>
210 #displaySearchFacetValuesLimited($facetValues $customQueryStringParameters $customValueDisplayer)
211 </ul>
212 #end
213 #end
214
215 #macro (displaySearchFacetValuesLimited $facetValues $customQueryStringParameters $customValueDisplayer)
216 #set ($limitRequestParameter = "l_$facetField.name")
217 #set ($limit = $numbertool.toNumber($request.getParameter($limitRequestParameter)).intValue())
218 #if ("$!limit" == '')
219 #set ($limit = $solrConfig.facetPaginationStep)
220 #end
221 #set ($limit = $mathtool.max($mathtool.min($limit, $facetValues.size()), 0))
222 #foreach ($facetValue in $facetValues)
223 #if ($foreach.index < $limit)
224 <li>#displaySearchFacetValue($facetValue $customQueryStringParameters $customValueDisplayer)</li>
225 #else
226 #extendQueryString($url {$limitRequestParameter: [$mathtool.add($limit, $solrConfig.facetPaginationStep)]})
227 <li><a href="$url" class="more">&hellip; $escapetool.xml($services.localization.render(
228 'solr.facets.moreValues', [$mathtool.sub($facetValues.size(), $limit)]))</a></li>
229 #break
230 #end
231 #end
232 #end
233
234 #macro (displaySearchFacetValue $facetValue $customQueryStringParameters $customValueDisplayer)
235 #set ($selectedValues = [])
236 #if ($facetRequestValues)
237 #set ($discard = $selectedValues.addAll($facetRequestValues.subList(0, $facetRequestValues.size())))
238 #end
239 #set ($selected = $selectedValues.remove($facetValue.name))
240 #if (!$selected)
241 #set ($discard = $selectedValues.add($facetValue.name))
242 #end
243 ## Reset the pagination because the number of results can change when a facet is applied.
244 #set ($queryStringParameters = {$facetRequestParameter: $selectedValues, 'firstIndex': []})
245 #if ($customQueryStringParameters)
246 #set ($discard = $queryStringParameters.putAll($customQueryStringParameters))
247 #end
248 #extendQueryString($url $queryStringParameters)
249 <div class="itemCount">$facetValue.count</div>
250 <a href="$url" class="itemName#if ($selected) selected#end#if ($facetValue.name == '') empty#end">
251 #if ($facetValue.name == '')
252 #set ($facetPrettyValueKey = "solr.field.${facetField.name}.emptyValue")
253 #if (!$services.localization.get($facetPrettyValueKey))
254 #set ($facetPrettyValueKey = "solr.facets.emptyValue")
255 #end
256 #set ($facetPrettyValue = $services.localization.render($facetPrettyValueKey))
257 #else
258 #set ($facetPrettyValue = $facetValue.name)
259 #end
260 #if ($customValueDisplayer)
261 #evaluate("${escapetool.h}${customValueDisplayer}(${escapetool.d}facetPrettyValue)")
262 #else
263 $escapetool.xml($facetPrettyValue)
264 #end
265 </a>
266 <div class="clearfloats"></div>
267 #end
268
269 #**
270 * If the facet has values specified on the request then keep only those that are included in the list of matched facet
271 * values. Don't use this macro for date or range facets because in this case the values specified on the request are
272 * never found as is in the list of facet values (e.g. a range will match multiple facet values). This macro ensures
273 * that the URL to select/unselect a facet value doesn't keep unmatched values (otherwise the URL will have values that
274 * you cannot remove using the facet UI).
275 *#
276 #macro (retainMatchedRequestValues)
277 #if ($facetRequestValues)
278 #set ($matchedValues = [])
279 #foreach ($facetValue in $facetValues)
280 #set ($discard = $matchedValues.add($facetValue.name))
281 #end
282 #set ($matchedRequestValues = [])
283 #set ($discard = $matchedRequestValues.addAll($facetRequestValues.subList(0, $facetRequestValues.size())))
284 #set ($discard = $matchedRequestValues.retainAll($matchedValues))
285 #set ($facetRequestValues = $matchedRequestValues)
286 #end
287 #end
288
289 #macro (displaySearchResultsSort)
290 #set ($defaultSortOrder = $solrConfig.sortFields.get($type))
291 #if (!$defaultSortOrder)
292 #set ($defaultSortOrder = {'score': 'desc'})
293 #end
294 #set ($sortOrderSymbol = {
295 'asc': $services.icon.render('caret-up'),
296 'desc': $services.icon.render('caret-down')
297 })
298 (% class="search-options" %)
299 * {{translation key="solr.options"/}}
300 #if($highlightEnabled)#extendQueryString($url {'highlight': [false]})#else#extendQueryString($url {'highlight': [true]})#end
301 * [[{{translation key="solr.options.highlight"/}}>>path:${url}||class="options-item#if($highlightEnabled) active#end" title="$services.localization.render('solr.options.highlight.title')"]]
302 #if($facetEnabled)#extendQueryString($url {'facet': [false]})#else#extendQueryString($url {'facet': [true]})#end
303 * [[{{translation key="solr.options.facet"/}}>>path:${url}||class="options-item#if($facetEnabled) active#end" title="$services.localization.render('solr.options.facet.title')"]]
304
305 (% class="search-results-sort" %)
306 * {{translation key="solr.sortBy"/}}
307 #foreach ($entry in $defaultSortOrder.entrySet())
308 #set ($class = 'sort-item')
309 #set ($sortOrderIndicator = $NULL)
310 #set ($targetSortOrder = $entry.value)
311 #if ($sort == $entry.key)
312 #set ($class = "$class active")
313 #set ($sortOrderHint = $services.localization.render("solr.sortOrder.$sortOrder"))
314 #set ($sortOrderIndicator = "(% class=""sort-item-order"" title=""$sortOrderHint"" %)$sortOrderSymbol.get($sortOrder)(%%)")
315 #set ($targetSortOrder = "#if ($sortOrder == 'asc')desc#{else}asc#end")
316 #end
317 #extendQueryString($url {'sort': [$entry.key], 'sortOrder': [$targetSortOrder]})
318 * [[{{translation key="solr.sortBy.$entry.key"/}}$!sortOrderIndicator>>path:${url}||class="$class"]]
319 #end
320 #end
321
322 #macro (extendQueryString $url $extraParameters)
323 #set ($parameters = {})
324 #set ($discard = $parameters.putAll($request.getParameterMap()))
325 #set ($discard = $parameters.putAll($extraParameters))
326 #set ($queryString = $escapetool.url($parameters))
327 #set ($url = $NULL)
328 #setVariable("$url" $doc.getURL('view', $queryString))
329 #end
330
331 #macro (displaySearchResults)
332 #set ($results = $searchResponse.results)
333 #set ($paginationParameters = {
334 'url': $doc.getURL('view', "$!request.queryString.replaceAll('firstIndex=[0-9]*', '')"),
335 'totalItems': $results.numFound,
336 'defaultItemsPerPage': $rows,
337 'position': 'top'
338 })
339 {{html clean="false"}}#pagination($paginationParameters){{/html}}
340 (% class="search-results" %)(((
341 #foreach ($searchResult in $results)
342 #displaySearchResult($searchResult)
343 #end
344 )))
345 #set ($discard = $paginationParameters.put('position', 'bottom'))
346 {{html clean="false"}}#pagination($paginationParameters){{/html}}
347
348 #displayRSSLink()
349 #end
350
351 #macro (displayRSSLink)
352 {{html clean="false"}}
353 #set ($parameters = {})
354 ## We keep most of the current request parameters so that the RSS feed matches the current search query and filters.
355 #set ($discard = $parameters.putAll($request.getParameterMap()))
356 ## The feed will provide the most recent results that match the search query and filters.
357 #set ($discard = $parameters.put('sort', 'date'))
358 #set ($discard = $parameters.put('sortOrder', 'desc'))
359 ## Reset the pagination so that only the top results are included.
360 #set ($discard = $parameters.remove('firstIndex'))
361 ## Add the parameters required to output the RSS feed instead of the search UI.
362 #set ($discard = $parameters.put('outputSyntax', 'plain'))
363 #set ($discard = $parameters.put('media', 'rss'))
364 <a href="$doc.getURL('get', $escapetool.url($parameters))" class="hasIcon iconRSS">
365 $services.localization.render('search.rss', ["[$escapetool.xml($text)]"])
366 </a>
367 {{/html}}
368 #end
369
370 #macro (displaySearchResult $searchResult)
371 #set ($searchResultReference = $services.solr.resolve($searchResult))
372 (% class="search-result type-$searchResult.type.toLowerCase()" %)(((
373 ## We use the HTML macro here mainly because we don't have a way to escape the wiki syntax in the data provided by the user.
374 {{html clean="false"}}
375 #evaluate("${escapetool.h}displaySearchResult_$searchResult.type.toLowerCase()(${escapetool.d}searchResult)")
376 #displaySearchResultHighlighting($searchResult)
377 {{/html}}
378 #if ($debug)
379
380 ## Scoring debug data.
381 ## The reason we used a separate HTML block with no cleaning is because the scoring debug data may contain some
382 ## characters that are considered invalid by JDOM library which is used for parsing the HTML when cleaning is on.
383 ## E.g. "0x0 is not a legal XML character" (org.jdom.IllegalDataException).
384 {{html clean="false"}}
385 <div class="search-result-debug">$!escapetool.xml($searchResponse.explainMap.get($searchResult.id))</div>
386 {{/html}}
387 #end
388 )))
389 #end
390
391 #macro (displaySearchResult_document $searchResult)
392 #displaySearchResultTitle()
393 #displaySearchResultLocation()
394 <div class="search-result-author">
395 $services.localization.render('core.footer.modification', [
396 "#displayUserProfileLink($searchResult.author $searchResult.author_display)",
397 $xwiki.formatDate($searchResult.date)
398 ])
399 </div>
400 #end
401
402 #macro (displaySearchResult_attachment $searchResult)
403 <h2 class="search-result-title">
404 $services.icon.renderHTML('attach')
405 #set ($attachmentURL = $xwiki.getURL($searchResultReference))
406 #set ($downloadHint = $services.localization.render('core.viewers.attachments.download'))
407 <a href="$attachmentURL" title="$escapetool.xml($downloadHint)">
408 $escapetool.xml($searchResultReference.name)
409 </a>
410 #set ($attachmentHistoryURL = $xwiki.getURL($searchResultReference, 'viewattachrev', $NULL))
411 #set ($historyHint = $services.localization.render('core.viewers.attachments.showHistory'))
412 <a href="$attachmentHistoryURL" title="$escapetool.xml($historyHint)" class="search-result-version">
413 $escapetool.xml($searchResult.attversion)
414 </a>
415 </h2>
416 #displaySearchResultLocation($searchResult)
417 <div class="search-result-uploader">
418 #set ($uploader = "#displayUserProfileLink($searchResult.attauthor.get(0) $searchResult.attauthor_display.get(0))")
419 #set ($uploadDate = $xwiki.formatDate($searchResult.attdate.get(0)))
420 #set ($fileSize = "#dynamicsize($searchResult.attsize.get(0))")
421 $services.localization.render('solr.result.uploadedBy', [$uploader, $uploadDate, $fileSize])
422 </div>
423 <div class="search-result-mediaType">$services.localization.render('solr.result.mediaType',
424 [$escapetool.xml($searchResult.mimetype.get(0))])</div>
425 #end
426
427 #macro (displaySearchResult_object $searchResult)
428 <h2 class="search-result-title">
429 $services.icon.renderHTML('cubes')
430 $escapetool.xml("${searchResult.get('class').get(0)}[$searchResult.number]")
431 </h2>
432 #displaySearchResultLocation($searchResult)
433 #end
434
435 #macro (displaySearchResult_object_property $searchResult)
436 <h2 class="search-result-title">
437 $services.icon.renderHTML('cube') $escapetool.xml($searchResult.propertyname)
438 </h2>
439 #displaySearchResultLocation($searchResult)
440 #end
441
442 #macro (displaySearchResultTitle)
443 #set ($showLocale = $searchResult.locale != '' && $searchResult.locale != "$xcontext.locale")
444 #set ($titleURL = $xwiki.getURL($searchResultReference))
445 #if ($showLocale)
446 #set ($titleURL = $xwiki.getURL($searchResultReference, 'view', "language=$searchResult.locale"))
447 #end
448 <h2 class="search-result-title">
449 $services.icon.renderHTML('file-white')
450 <a href="$titleURL">$escapetool.xml($searchResult.title_)</a>
451 #if ($showLocale)
452 <span title="$escapetool.xml($services.localization.render('solr.result.language'))"
453 class="search-result-language" >($escapetool.xml($searchResult.locale))</span>
454 #end
455 </h2>
456 #end
457
458 #macro (displaySearchResultLocation $searchResult)
459 <div class="search-result-location">
460 $services.localization.render('solr.result.locatedIn')
461 #set ($locationOptions = {
462 'excludeSelf': true,
463 'limit': 6
464 })
465 #hierarchy($searchResultReference $locationOptions)
466 </div>
467 #end
468
469 #macro (displayUserProfileLink $userReference $userName)
470 #if ($userReference)
471 ## We could test if the specified user exists but we want to speed up the search.
472 <a href="$xwiki.getURL($userReference)">$escapetool.xml($userName)</a>##
473 #else
474 $services.localization.render('core.users.unknownUser')##
475 #end
476 #end
477
478 #macro (displaySearchResultHighlighting $searchResult)
479 #getSearchResultHighlighting($searchResult $highlighting)
480 #if ($highlighting.size() > 0)
481 <dl class="search-result-highlights">
482 #foreach ($entry in $highlighting)
483 <dt>
484 #if ($services.localization.get("solr.field.$entry.field"))
485 $services.localization.render("solr.field.$entry.field")
486 #elseif ($entry.field.startsWith('property.'))
487 #getXClassProperty($entry.field $property $classPropertyReference)
488 #set ($propertyPrettyName = $property.translatedPrettyName)
489 #if ("$!propertyPrettyName" == '')
490 #set ($propertyPrettyName = $classPropertyReference.name)
491 #end
492 $propertyPrettyName
493 #else
494 $entry.field
495 #end
496 </dt>
497 <dd>#displaySearchResultMatches($entry.matches)</dd>
498 #end
499 </dl>
500 #if ($highlighting.size() > 1)
501 ## We wrap the link in a DIV because otherwise the HTML cleaning generates a paragraph.
502 <div>
503 <a href="#" class="search-result-highlightAll hidden">
504 $escapetool.xml($services.localization.render('solr.result.highlightAll'))
505 </a>
506 </div>
507 #end
508 #end
509 #end
510
511 #macro (displaySearchResultMatches $matches)
512 #foreach ($match in $matches)
513 #if ($foreach.count > 1)
514 <span class="separator">&hellip;</span>
515 #end
516 <blockquote class="search-result-highlight">$match</blockquote>
517 #end
518 #end
519
520 #macro (getSearchResultHighlighting $searchResult $return)
521 #set ($highlighting = $searchResponse.highlighting.get($searchResult.id))
522 #set ($highlightingByLanguage = {})
523 #foreach ($entry in $highlighting.entrySet())
524 ## Remove the language suffix (e.g. __, _en, _fr, _de) from the field name.
525 #set ($field = $stringtool.removeEnd($entry.key, '__'))
526 #set ($language = $stringtool.substringAfterLast($field, '_'))
527 #if ($services.localization.toLocale($language))
528 #set ($field = $stringtool.substringBeforeLast($field, '_'))
529 #else
530 #set ($language = '')
531 #end
532 #set ($matchesByLanguage = $highlightingByLanguage.get($field))
533 #if (!$matchesByLanguage)
534 #set ($matchesByLanguage = {})
535 #set ($discard = $highlightingByLanguage.put($field, $matchesByLanguage))
536 #end
537 #set ($discard = $matchesByLanguage.put($language, $entry.value))
538 #end
539 ## Keep only the matches correspoding to the search result locale.
540 #set ($highlighting = [])
541 ## Fields with a higher index will be displayed first. Fields that are not included will be displayed at the end.
542 #set ($fieldPriority = ['filename', 'attcontent', 'objcontent', 'comment', 'propertyname', 'propertyvalue', 'title', 'doccontent'])
543 #foreach ($entry in $highlightingByLanguage.entrySet())
544 #set ($matches = $entry.value.get($searchResult.locale))
545 #if (!$matches)
546 ## This should not happen but let's play safe.
547 #set ($matches = $entry.value.entrySet().iterator().next().value)
548 #end
549 ## Sanitize the matches.
550 #foreach ($match in $matches)
551 #set ($match = $match.replace('<span class="search-text-highlight">', "\u0011"))
552 #set ($match = $match.replace('<span class="search-text-highlight-stop"></span></span>', "\u0013"))
553 #set ($match = $escapetool.xml($match))
554 #set ($match = $match.replace("\u0011", '<span class="search-text-highlight">'))
555 #set ($match = $match.replace("\u0013", '</span>'))
556 #set ($discard = $matches.set($mathtool.sub($foreach.count, 1), $match))
557 #end
558 #set ($discard = $highlighting.add({
559 'field': $entry.key,
560 'priority': $fieldPriority.indexOf($entry.key),
561 'matches': $matches
562 }))
563 #end
564 #set ($highlighting = $collectiontool.sort($highlighting, 'priority:desc'))
565 #set ($return = $NULL)
566 #setVariable("$return" $highlighting)
567 #end
568
569 #macro (getSearchResults)
570 #set ($queryString = "$!{text}")
571 ##
572 ## Create the query and set the query string.
573 #set ($query = $services.query.createQuery($queryString, 'solr'))
574 ##
575 ## Set query parameters.
576 #set ($discard = $query.setLimit($rows))
577 #set ($discard = $query.setOffset($start))
578 #set ($discard = $query.bindValue('sort', "${sort} ${sortOrder}"))
579 #set ($discard = $query.bindValue('tie', $solrConfig.tieBreaker))
580 #set ($discard = $query.bindValue('mm', $solrConfig.minShouldMatch))
581 #setQueryFields($query)
582 #setPhraseFields($query)
583 #setFacetFields($query)
584 #setFilterQuery($query)
585 #setHighlightQuery($query)
586 #if ($debug)
587 #set ($discard = $query.bindValue('debugQuery', 'on'))
588 #end
589 ##
590 ## Execute the query.
591 #set ($searchResponse = $query.execute()[0])
592 #end
593
594 #macro (setQueryFields $query)
595 ## Specify which index fields are matched when a free text search is performed.
596 #if ($boost == '')
597 #if ($solrConfig.queryFields.substring(0, 0) == '')
598 ## If the value of the 'queryFields' parameter is a string then it means that the same query fields are used for
599 ## all result types.
600 #set ($boost = $solrConfig.queryFields)
601 #else
602 ## There are different query fields for each result type.
603 #set ($boost = $solrConfig.queryFields.get($type))
604 #end
605 #end
606 #if ("$!boost" != '')
607 #set ($discard = $query.bindValue('qf', $boost))
608 #end
609 #end
610
611 #macro (setPhraseFields $query)
612 ## Set the main phrase field parameter boosts so that queries with all search terms
613 ## in close proximity have high relevance
614 #if ($solrConfig.phraseFields.substring(0, 0) == '')
615 ## If the value of the 'phraseFields' parameter is a string then it means that the
616 ## same query fields are used for all result types.
617 #set ($phraseFieldsBoost = $solrConfig.phraseFields)
618 #else
619 ## There are different phrase fields for each result type.
620 ## Including type = null, which will result from all facets being deselected
621 #set ($phraseFieldsBoost = $solrConfig.phraseFields.get("$!type"))
622 #end
623 #if ("$!phraseFieldsBoost" != '')
624 #set ($discard = $query.bindValue('pf', $phraseFieldsBoost))
625 #set ($discard = $query.bindValue('ps', $solrConfig.phraseFieldSlop))
626 #end
627 ## Set the bigram phrase field parameter boosts so that queries with groups of two
628 ## search terms in close proximity have high relevance
629 #if ($solrConfig.bigramPhraseFields.substring(0, 0) == '')
630 ## If the value of the 'bigramPhraseFields' parameter is a string then it means that the
631 ## same query fields are used for all result types.
632 #set ($bigramPhraseFieldsBoost = $solrConfig.bigramPhraseFields)
633 #else
634 ## There are different phrase fields for each result type.
635 ## Including type = null, which will result from all facets being deselected
636 #set ($bigramPhraseFieldsBoost = $solrConfig.bigramPhraseFields.get("$!type"))
637 #end
638 #if ("$!bigramPhraseFieldsBoost" != '')
639 #set ($discard = $query.bindValue('pf2', $bigramPhraseFieldsBoost))
640 #set ($discard = $query.bindValue('ps2', $solrConfig.bigramPhraseFieldSlop))
641 #end
642 ## Set the trigram phrase field parameter boosts so that queries with groups of three
643 ## search terms in close proximity have high relevance.
644 ## Generally (pf boost) > (pf3 boost) > (pf2 boost)
645 #if ($solrConfig.trigramPhraseFields.substring(0, 0) == '')
646 ## If the value of the 'trigramPhraseFields' parameter is a string then it means that the
647 ## same query fields are used for all result types.
648 #set ($trigramPhraseFieldsBoost = $solrConfig.trigramPhraseFields)
649 #else
650 ## There are different phrase fields for each result type.
651 ## including type = null, which will result from all facets being deselected
652 #set ($trigramPhraseFieldsBoost = $solrConfig.trigramPhraseFields.get("$!type"))
653 #end
654 #if ("$!trigramPhraseFieldsBoost" != '')
655 #set ($discard = $query.bindValue('pf3', $trigramPhraseFieldsBoost))
656 #set ($discard = $query.bindValue('ps3', $solrConfig.trigramPhraseFieldSlop))
657 #end
658 #end
659
660 #macro (setFacetFields $query)
661 #set ($discard = $query.bindValue('facet', $facetEnabled))
662 #if ($facetEnabled)
663 ## The facets are displayed in this order so keep the most important facets first.
664 #set ($facetFields = $solrConfig.facetFields)
665 ## In order to support multi-select faceting we need to exclude the corresponding filters when faceting.
666 ## See http://wiki.apache.org/solr/SimpleFacetParameters#Multi-Select_Faceting_and_LocalParams
667 #set ($facetFieldsWithFilterExcludes = [])
668 ## The type facet doesn't support multiple selection because we use different query fields for different result
669 ## types so the number of matches for the type facet changes when a result type is selected/unselected.
670 ## We don't allow multiple selection on the space facet because we perform a 'facet.prefix'-based drill down.
671 #set ($singleSelectionFacets = ['type', 'space_facet'])
672 #foreach ($facet in $facetFields)
673 #set ($excludeTaggedFilter = '')
674 #if (!$singleSelectionFacets.contains($facet))
675 #set ($excludeTaggedFilter = "{!ex=$facet}")
676 #end
677 #set ($discard = $facetFieldsWithFilterExcludes.add("$excludeTaggedFilter$facet"))
678 #end
679 #set ($discard = $query.bindValue('facet.field', $facetFieldsWithFilterExcludes))
680 #end
681 #end
682
683 #macro (setFilterQuery $query)
684 ##
685 ## Collect the query filters.
686 #set ($filters = {})
687 ## Add the default filters if not specified in the configuration.
688 #if (!$solrConfig.filterQuery || $solrConfig.filterQuery.isEmpty())
689 ## Uncomment the following line of code if you want to search by default also in:
690 ## * the default translation of documents that are not translated in the current locale
691 ## * the "xx" translation if the current locale "xx_YY" doesn't have a translation available
692 ## (e.g. "pt" when "pt_BR" is not available)
693 ## See the discussion on XWIKI-9977.
694 ##set ($discard = $filters.put('locales', ["$xcontext.locale"]))
695 #if (!$xcontext.isMainWiki())
696 ## Subwikis search by default in their content only.
697 #set ($discard = $filters.put('wiki', [$xcontext.database]))
698 #elseif ($solrConfig.wikisSearchableFromMainWiki)
699 ## The list of wikis that are searched by default can be configured.
700 #set ($discard = $filters.put('wiki', $solrConfig.wikisSearchableFromMainWiki))
701 #end
702 #if ($xwiki.getUserPreference('displayHiddenDocuments') != 1)
703 #set ($discard = $filters.put('hidden', [false]))
704 #end
705 #end
706 ## Add the facets.
707 #set ($prefixFacets = ['space_facet'])
708 #foreach ($parameter in $request.parameterMap.entrySet())
709 #if ($parameter.key.startsWith('f_'))
710 #set ($fieldName = $parameter.key.substring(2))
711 #set ($escapedValues = [])
712 #foreach ($value in $parameter.value)
713 #set ($discard = $escapedValues.add("#escapeFilterValue($value)"))
714 #end
715 #set ($discard = $filters.put($fieldName, $escapedValues))
716 #if ($prefixFacets.contains($fieldName))
717 #set ($parts = $parameter.value.get(0).split('/', 2))
718 #set ($length = $numbertool.toNumber($parts.get(0)).intValue() + 1)
719 #set ($prefix = "$length/$parts.get(1)")
720 #set ($discard = $query.bindValue("f.${fieldName}.facet.prefix", $prefix))
721 #set ($discard = $prefixFacets.remove($fieldName))
722 #end
723 #end
724 #end
725 ## Specify the initial prefix for the remaining prefix facets.
726 #foreach ($facet in $prefixFacets)
727 #set ($discard = $query.bindValue("f.${facet}.facet.prefix", '0/'))
728 #end
729 ##
730 ## Build the filter query.
731 #set ($filterQuery = [])
732 #if ($solrConfig.filterQuery)
733 #set ($discard = $filterQuery.addAll($solrConfig.filterQuery))
734 #end
735 #foreach ($filter in $filters.entrySet())
736 ## Use OR between different values of the same filter/facet.
737 ## Tag the filter so that we can exclude it when faceting in order to support multi-select faceting.
738 #set ($discard = $filterQuery.add("{!tag=$filter.key}$filter.key:($!stringtool.join($filter.value, ' OR '))"))
739 #end
740 #set ($discard = $query.bindValue('fq', $filterQuery))
741 #end
742
743 #macro(setHighlightQuery $query)
744 #set ($discard = $query.bindValue('hl', $highlightEnabled))
745 #end
746
747 #macro (escapeFilterValue $value)
748 ## Check if the given value is a range.
749 #if ($rangePattern.matcher($value).matches() || $wildcardPattern.matcher($value).matches())##
750 $value##
751 #else##
752 "$stringtool.replaceEach($value, ['\', '"'], ['\\', '\"'])"##
753 #end##
754 #end
755
756 #macro (processRequestParameters)
757 #set ($text = "$!request.text")
758 #set ($boost = "$!request.boost")
759 #set ($debug = "$!request.debug" != '')
760 ##
761 ## Highlight enabled
762 ## First check the request, then the configuration and enable it by default
763 #if ($request.highlight)
764 #set ($highlightEnabled = $request.highlight != 'false')
765 #elseif ($solrConfig.containsKey('highlightEnabled'))
766 #set ($highlightEnabled = $solrConfig.highlightEnabled)
767 #else
768 #set ($highlightEnabled = true)
769 #end
770 ##
771 ## Facet enabled
772 ## First check the request, then the configuration and enable it by default
773 #if ($request.facet)
774 #set ($facetEnabled = $request.facet != 'false')
775 #elseif ($solrConfig.containsKey('facetEnabled'))
776 #set ($facetEnabled = $solrConfig.facetEnabled)
777 #else
778 #set ($facetEnabled = true)
779 #end
780 ##
781 ## Pagination
782 #set ($rows = $numbertool.toNumber($request.rows).intValue())
783 #if ("$!rows" == '')
784 #set ($rows = 10)
785 #end
786 #set ($start = $numbertool.toNumber($request.firstIndex).intValue())
787 #if ("$!start" == '')
788 #set ($start = 0)
789 #end
790 ##
791 ## Sort
792 #set ($sort = $request.sort)
793 #if ("$!sort" == '')
794 #set ($sort = 'score')
795 #end
796 #set ($sortOrder = $request.sortOrder)
797 #if ("$!sortOrder" == '')
798 #set ($sortOrder = 'desc')
799 #elseif ($sortOrder != 'desc')
800 #set ($sortOrder = 'asc')
801 #end
802 ##
803 ## Result type
804 ## We store the selected result type because we need it to decide what search and sort fields to use.
805 #set ($type = $request.getParameterValues('f_type'))
806 #if ($type && $type.size() == 1)
807 #set ($type = $type.get(0))
808 #else
809 ## Extract the result type from the filter query, if specified.
810 #foreach ($item in $solrConfig.filterQuery)
811 #if ($item.startsWith('type:'))
812 #set ($type = $item.substring(5))
813 #break
814 #end
815 #end
816 #end
817 #end
818
819 #macro (displaySearchUI)
820 #set($void = $services.progress.startStep('#displaySearchUI'))
821 #set($void = $services.progress.pushLevel())
822 #set ($discard = $xwiki.ssx.use('Main.SolrSearch'))
823 #set ($discard = $xwiki.jsx.use('Main.SolrSearch'))
824 ## Disable the document extra data: comments, attachments, history...
825 #set ($displayDocExtra = false)
826 #processRequestParameters()
827 (% class="search-ui" %)(((
828 #if ($xcontext.action == 'get')
829 {{html clean="false"}}
830 ## The search UI is updated dynamically through AJAX and we need to pull the skin extensions.
831 ## We put the skin extension imports inside a <noscript> element to prevent jQuery from fetching the JavaScript
832 ## files automatically (we want to fetch only the new JavaScript files).
833 <noscript class="hidden skin-extension-imports">#skinExtensionHooks</noscript>
834 {{/html}}
835
836 #end
837 #displaySearchForm()
838 #if ($text != '')
839 #getSearchResults()
840 #if ($debug)
841 #displaySearchDebugInfo()
842 #end
843 (% class="search-results-container row" %)(((
844 #if ($facetEnabled)
845 (% class="col-xs-12 col-sm-4 col-sm-push-8 col-md-3 col-md-push-9" %)(((
846 #displaySearchFacets($searchResponse)
847 )))
848 #end
849 (% class="search-results-left col-xs-12#if ($facetEnabled) col-sm-8 col-sm-pull-4 col-md-9 col-md-pull-3#end" %)
850 (((
851 #displaySearchResultsSort()
852
853 #displaySearchResults()
854 )))
855 )))
856 #end
857 )))
858 #set($void = $services.progress.popLevel())
859 #set($void = $services.progress.endStep())
860 #end
861
862 #macro (outputRSSFeed)
863 ##
864 ## Get the search results.
865 ##
866 #processRequestParameters()
867 #getSearchResults()
868 #set ($list = [])
869 #set ($results = $searchResponse.results)
870 #foreach ($searchResult in $results)
871 #set ($searchResultDocumentReference = $services.solr.resolveDocument($searchResult))
872 #set ($discard = $list.add("$searchResultDocumentReference"))
873 #end
874 ##
875 ## Compute the feed URI.
876 ##
877 #set ($parameters = {})
878 #set ($discard = $parameters.putAll($request.getParameterMap()))
879 #set ($discard = $parameters.remove('outputSyntax'))
880 #set ($discard = $parameters.remove('media'))
881 #set ($feedURI = $doc.getExternalURL('view', $escapetool.url($parameters)))
882 ##
883 ## Configure the feed.
884 ##
885 #set ($feed = $xwiki.feed.getDocumentFeed($list, {}))
886 #set ($discard = $feed.setLink($feedURI))
887 #set ($discard = $feed.setUri($feedURI))
888 #set ($discard = $feed.setAuthor('XWiki'))
889 #set ($title = $services.localization.render('search.rss', ["[$text]"]))
890 #set ($discard = $feed.setTitle($title))
891 #set ($discard = $feed.setDescription($title))
892 #set ($discard = $feed.setLanguage("$xcontext.locale"))
893 #set ($discard = $feed.setCopyright($xwiki.getXWikiPreference('copyright')))
894 ##
895 ## Output the feed.
896 ##
897 #set ($discard = $response.setContentType('application/rss+xml'))
898 $xwiki.feed.getFeedOutput($feed, 'rss_2.0')
899 #end
900
901 #macro (handleSolrSearchRequest)
902 ## Preselect facet values only for the facets that are enabled.
903 #set ($discard = $solrConfig.facetQuery.keySet().retainAll($solrConfig.facetFields))
904 #if ($request.media == 'rss')
905 #outputRSSFeed()
906 #elseif ("$!request.r" == '1' || $solrConfig.facetQuery.isEmpty())
907 #displaySearchUI()
908 #else
909 ## Redirect using preselected facet values.
910 #set ($extraParams = {})
911 #foreach ($entry in $solrConfig.facetQuery.entrySet())
912 #set ($discard = $extraParams.put("f_$entry.key", $entry.value))
913 #end
914 ## Prevent redirect loop.
915 #set ($extraParams.r = 1)
916 #extendQueryString($url $extraParams)
917 $response.sendRedirect($url)
918 #end
919 #end
920 {{/velocity}}
#getBlogDocument($space $blogDoc)