Wiki-Quellcode von Solr Search Macros

Zuletzt geändert von Daniel Herrmann am 2026/02/04 20:23

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

Community

https://wiki.makerspace-darmstadt.de/bin/download/Panels/MKSP%20Slack/Slack_MKSP.png

Wir benutzen Slack, um miteinander zu kommunizieren. Melde Dich an und werde Teil unserer Maker-Community!

Zum Slack Workspace

Frage? FAQ!

Du hast eine Frage, die sich nicht direkt im Wiki findet?

Natürlich kannst Du die Frage jederzeit gerne in der Slack Community stellen. Bevor Du das machst, schau doch bitte einmal auf unserer Homepage bei den Häufig gestellten Fragen vorbei. Wir versuchen diese Fragen stets aktuell zu halten, eventuell hilft Dir das ja schon weiter.

Offene Werkstatt

Ohne Anmeldung einfach vorbei kommen. Am besten bringst Du direkt den ausgefüllten Haftungsauschluss mit.

Jeden Donnerstag ab 19 Uhr

Während der offenen Werkstatt kannst Du einfach vorbei kommen und an Deinem Projekt arbeiten. Bitte beachte aber, dass zur Verwendung der Maschinen eine Einweisung erforderlich ist, die Du gegebenfalls vorher absolvieren musst. Wenn ein Mitglied mit entsprechender Einweisung vor Ort ist und Zeit hat, helfen wir natürlich gerne aus. Dies können wir aber nicht garantieren, da Rundgänge Priorität haben.

Sprich Dich idealerweise schon vor der offenen Werkstatt mit einem Mitglied in unserem Slack ab. So kannst Du sicherstellen, dass Du auf jeden Fall arbeiten kannst.

Übrigens: Du kannst Dich mit einem Mitglied gerne auch außerhalb der offenen Werkstattzeiten zum Arbeiten verabreden!

Führungen und Rundgänge

Im Rahmen der offenen Werkstatt bieten wir euch auch gerne einen Rundgang durch unsere Werkstatt. Hier könnt ihr den Verein und unser Konzept kennenlernen sowie die Maschinen und Möglichkeiten der Werkstatt gezeigt bekommen.

Jeden Donnerstag wird eine Führung angeboten:

  • Um 19:15 Uhr (bitte um 19:00 Uhr da sein)

Der Rundgang dauert ca. 45 Minuten und ihr habt natürlich auch die Möglichkeit, eure Fragen loszuwerden.

Bitte beachtet folgendes: Die Werkstatt beinhaltet gefährliche Maschinen. Bringt daher bitte nach Möglichkeit den ausgefüllten und unterschrieben Haftungsauschluss schon mit. Dieser kann aber auch vor Ort ausgefüllt werden, das verzögert allerdings die Abläufe.