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

Wir benutzen Slack, um miteinander zu kommunizieren. Melde Dich an und werde Teil unserer Maker-Community!
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.