Wiki-Quellcode von Solr Search Macros

Version 4.1 von Daniel Herrmann am 2025/12/16 11:14

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