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