Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
58.33% |
133 / 228 |
|
13.04% |
3 / 23 |
CRAP | |
0.00% |
0 / 1 |
Params | |
58.33% |
133 / 228 |
|
13.04% |
3 / 23 |
634.53 | |
0.00% |
0 / 1 |
__construct | |
66.67% |
8 / 12 |
|
0.00% |
0 / 1 |
4.59 | |||
getFilterSettings | |
95.83% |
23 / 24 |
|
0.00% |
0 / 1 |
9 | |||
getFacetSettings | |
66.67% |
20 / 30 |
|
0.00% |
0 / 1 |
23.33 | |||
initSearch | |
75.00% |
3 / 4 |
|
0.00% |
0 / 1 |
2.06 | |||
setFacetContains | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
setFacetContainsIgnoreCase | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
setFacetOffset | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
setFacetPrefix | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
setFacetSort | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
setIndexSortedFacets | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
initFacetList | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
initAdvancedFacets | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
initHomePageFacets | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
6 | |||
initNewItemsFacets | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
6 | |||
initFilters | |
57.14% |
4 / 7 |
|
0.00% |
0 / 1 |
3.71 | |||
setQueryIDs | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
6 | |||
getQueryIDLimit | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
normalizeSort | |
100.00% |
33 / 33 |
|
100.00% |
1 / 1 |
7 | |||
getBackendParameters | |
82.05% |
32 / 39 |
|
0.00% |
0 / 1 |
18.67 | |||
setPivotFacets | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getPivotFacets | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
formatFilterListEntry | |
0.00% |
0 / 41 |
|
0.00% |
0 / 1 |
90 | |||
getCheckboxFacets | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
4 |
1 | <?php |
2 | |
3 | /** |
4 | * Solr aspect of the Search Multi-class (Params) |
5 | * |
6 | * PHP version 8 |
7 | * |
8 | * Copyright (C) Villanova University 2011. |
9 | * |
10 | * This program is free software; you can redistribute it and/or modify |
11 | * it under the terms of the GNU General Public License version 2, |
12 | * as published by the Free Software Foundation. |
13 | * |
14 | * This program is distributed in the hope that it will be useful, |
15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
17 | * GNU General Public License for more details. |
18 | * |
19 | * You should have received a copy of the GNU General Public License |
20 | * along with this program; if not, write to the Free Software |
21 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
22 | * |
23 | * @category VuFind |
24 | * @package Search_Solr |
25 | * @author Demian Katz <demian.katz@villanova.edu> |
26 | * @author Ere Maijala <ere.maijala@helsinki.fi> |
27 | * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License |
28 | * @link https://vufind.org Main Page |
29 | */ |
30 | |
31 | namespace VuFind\Search\Solr; |
32 | |
33 | use VuFindSearch\ParamBag; |
34 | |
35 | use function count; |
36 | use function in_array; |
37 | use function is_array; |
38 | |
39 | /** |
40 | * Solr Search Parameters |
41 | * |
42 | * @category VuFind |
43 | * @package Search_Solr |
44 | * @author Demian Katz <demian.katz@villanova.edu> |
45 | * @author Ere Maijala <ere.maijala@helsinki.fi> |
46 | * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License |
47 | * @link https://vufind.org Main Page |
48 | */ |
49 | class Params extends \VuFind\Search\Base\Params |
50 | { |
51 | use \VuFind\Search\Params\FacetLimitTrait; |
52 | use \VuFind\Search\Params\FacetRestrictionsTrait; |
53 | |
54 | /** |
55 | * Search with facet.contains |
56 | * cf. https://lucene.apache.org/solr/guide/7_3/faceting.html |
57 | * |
58 | * @var string |
59 | */ |
60 | protected $facetContains = null; |
61 | |
62 | /** |
63 | * Ignore Case when using facet.contains |
64 | * cf. https://lucene.apache.org/solr/guide/7_3/faceting.html |
65 | * |
66 | * @var bool |
67 | */ |
68 | protected $facetContainsIgnoreCase = null; |
69 | |
70 | /** |
71 | * Offset for facet results |
72 | * |
73 | * @var int |
74 | */ |
75 | protected $facetOffset = null; |
76 | |
77 | /** |
78 | * Prefix for facet searching |
79 | * |
80 | * @var string |
81 | */ |
82 | protected $facetPrefix = null; |
83 | |
84 | /** |
85 | * Sorting order for facet search results |
86 | * |
87 | * @var string |
88 | */ |
89 | protected $facetSort = null; |
90 | |
91 | /** |
92 | * Sorting order of single facet by index |
93 | * |
94 | * @var array |
95 | */ |
96 | protected $indexSortedFacets = null; |
97 | |
98 | /** |
99 | * Fields for visual faceting |
100 | * |
101 | * @var string |
102 | */ |
103 | protected $pivotFacets = null; |
104 | |
105 | /** |
106 | * Hierarchical Facet Helper |
107 | * |
108 | * @var HierarchicalFacetHelper |
109 | */ |
110 | protected $facetHelper; |
111 | |
112 | /** |
113 | * Are we searching by ID only (instead of a normal query)? |
114 | * |
115 | * @var bool |
116 | */ |
117 | protected $searchingById = false; |
118 | |
119 | /** |
120 | * Config sections to search for facet labels if no override configuration |
121 | * is set. |
122 | * |
123 | * @var array |
124 | */ |
125 | protected $defaultFacetLabelSections |
126 | = ['Advanced', 'HomePage', 'ResultsTop', 'Results', 'ExtraFacetLabels']; |
127 | |
128 | /** |
129 | * Config sections to search for checkbox facet labels if no override |
130 | * configuration is set. |
131 | * |
132 | * @var array |
133 | */ |
134 | protected $defaultFacetLabelCheckboxSections = ['CheckboxFacets']; |
135 | |
136 | /** |
137 | * Constructor |
138 | * |
139 | * @param \VuFind\Search\Base\Options $options Options to use |
140 | * @param \VuFind\Config\PluginManager $configLoader Config loader |
141 | * @param HierarchicalFacetHelper $facetHelper Hierarchical facet helper |
142 | */ |
143 | public function __construct( |
144 | $options, |
145 | \VuFind\Config\PluginManager $configLoader, |
146 | HierarchicalFacetHelper $facetHelper = null |
147 | ) { |
148 | parent::__construct($options, $configLoader); |
149 | $this->facetHelper = $facetHelper; |
150 | |
151 | // Use basic facet limit by default, if set: |
152 | $config = $configLoader->get($options->getFacetsIni()); |
153 | $this->initFacetLimitsFromConfig($config->Results_Settings ?? null); |
154 | $this->initFacetRestrictionsFromConfig($config->Results_Settings ?? null); |
155 | if (isset($config->LegacyFields)) { |
156 | $this->facetAliases = $config->LegacyFields->toArray(); |
157 | } |
158 | if ( |
159 | isset($config->Results_Settings->sorted_by_index) |
160 | && count($config->Results_Settings->sorted_by_index) > 0 |
161 | ) { |
162 | $this->setIndexSortedFacets( |
163 | $config->Results_Settings->sorted_by_index->toArray() |
164 | ); |
165 | } |
166 | } |
167 | |
168 | /** |
169 | * Return the current filters as an array of strings ['field:filter'] |
170 | * |
171 | * @return array $filterQuery |
172 | */ |
173 | public function getFilterSettings() |
174 | { |
175 | // Define Filter Query |
176 | $filterQuery = []; |
177 | $orFilters = []; |
178 | $filterList = array_merge_recursive( |
179 | $this->getHiddenFilters(), |
180 | $this->filterList |
181 | ); |
182 | foreach ($filterList as $field => $filter) { |
183 | if ($orFacet = str_starts_with($field, '~')) { |
184 | $field = substr($field, 1); |
185 | } |
186 | foreach ($filter as $value) { |
187 | // Special case -- complex filter, that should be taken as-is: |
188 | if ($field == '#') { |
189 | $q = $value; |
190 | } elseif ( |
191 | str_ends_with($value, '*') |
192 | || preg_match('/\[[^\]]+\s+TO\s+[^\]]+\]/', $value) |
193 | ) { |
194 | // Special case -- allow trailing wildcards and ranges |
195 | $q = $field . ':' . $value; |
196 | } else { |
197 | $q = $field . ':"' . addcslashes($value, '"\\') . '"'; |
198 | } |
199 | if ($orFacet) { |
200 | $orFilters[$field] ??= []; |
201 | $orFilters[$field][] = $q; |
202 | } else { |
203 | $filterQuery[] = $q; |
204 | } |
205 | } |
206 | } |
207 | foreach ($orFilters as $field => $parts) { |
208 | $filterQuery[] = '{!tag=' . $field . '_filter}' . $field |
209 | . ':(' . implode(' OR ', $parts) . ')'; |
210 | } |
211 | return $filterQuery; |
212 | } |
213 | |
214 | /** |
215 | * Return current facet configurations |
216 | * |
217 | * @return array $facetSet |
218 | */ |
219 | public function getFacetSettings() |
220 | { |
221 | // Build a list of facets we want from the index |
222 | $facetSet = []; |
223 | |
224 | if (!empty($this->facetConfig)) { |
225 | $facetSet['limit'] = $this->facetLimit; |
226 | foreach (array_keys($this->facetConfig) as $facetField) { |
227 | $fieldLimit = $this->getFacetLimitForField($facetField); |
228 | if ($fieldLimit != $this->facetLimit) { |
229 | $facetSet["f.{$facetField}.facet.limit"] = $fieldLimit; |
230 | } |
231 | $fieldPrefix = $this->getFacetPrefixForField($facetField); |
232 | if (!empty($fieldPrefix)) { |
233 | $facetSet["f.{$facetField}.facet.prefix"] = $fieldPrefix; |
234 | } |
235 | $fieldMatches = $this->getFacetMatchesForField($facetField); |
236 | if (!empty($fieldMatches)) { |
237 | $facetSet["f.{$facetField}.facet.matches"] = $fieldMatches; |
238 | } |
239 | if ($this->getFacetOperator($facetField) == 'OR') { |
240 | $facetField = '{!ex=' . $facetField . '_filter}' . $facetField; |
241 | } |
242 | $facetSet['field'][] = $facetField; |
243 | } |
244 | if ($this->facetContains != null) { |
245 | $facetSet['contains'] = $this->facetContains; |
246 | } |
247 | if ($this->facetContainsIgnoreCase != null) { |
248 | $facetSet['contains.ignoreCase'] |
249 | = $this->facetContainsIgnoreCase ? 'true' : 'false'; |
250 | } |
251 | if ($this->facetOffset != null) { |
252 | $facetSet['offset'] = $this->facetOffset; |
253 | } |
254 | if ($this->facetPrefix != null) { |
255 | $facetSet['prefix'] = $this->facetPrefix; |
256 | } |
257 | $facetSet['sort'] = $this->facetSort ?: 'count'; |
258 | if ($this->indexSortedFacets != null) { |
259 | foreach ($this->indexSortedFacets as $field) { |
260 | $facetSet["f.{$field}.facet.sort"] = 'index'; |
261 | } |
262 | } |
263 | } |
264 | return $facetSet; |
265 | } |
266 | |
267 | /** |
268 | * Initialize the object's search settings from a request object. |
269 | * |
270 | * @param \Laminas\Stdlib\Parameters $request Parameter object representing user |
271 | * request. |
272 | * |
273 | * @return void |
274 | */ |
275 | protected function initSearch($request) |
276 | { |
277 | // Special case -- did we get a list of IDs instead of a standard query? |
278 | $ids = $request->get('overrideIds', null); |
279 | if (is_array($ids)) { |
280 | $this->setQueryIDs($ids); |
281 | } else { |
282 | // Use standard initialization: |
283 | parent::initSearch($request); |
284 | } |
285 | } |
286 | |
287 | /** |
288 | * Set Facet Contains |
289 | * |
290 | * @param string $p the new contains value |
291 | * |
292 | * @return void |
293 | */ |
294 | public function setFacetContains($p) |
295 | { |
296 | $this->facetContains = $p; |
297 | } |
298 | |
299 | /** |
300 | * Set Facet Contains Ignore Case |
301 | * |
302 | * @param bool $val the new boolean value |
303 | * |
304 | * @return void |
305 | */ |
306 | public function setFacetContainsIgnoreCase($val) |
307 | { |
308 | $this->facetContainsIgnoreCase = $val; |
309 | } |
310 | |
311 | /** |
312 | * Set Facet Offset |
313 | * |
314 | * @param int $o the new offset value |
315 | * |
316 | * @return void |
317 | */ |
318 | public function setFacetOffset($o) |
319 | { |
320 | $this->facetOffset = $o; |
321 | } |
322 | |
323 | /** |
324 | * Set Facet Prefix |
325 | * |
326 | * @param string $p the new prefix value |
327 | * |
328 | * @return void |
329 | */ |
330 | public function setFacetPrefix($p) |
331 | { |
332 | $this->facetPrefix = $p; |
333 | } |
334 | |
335 | /** |
336 | * Set Facet Sorting |
337 | * |
338 | * @param string $s the new sorting action value |
339 | * |
340 | * @return void |
341 | */ |
342 | public function setFacetSort($s) |
343 | { |
344 | $this->facetSort = $s; |
345 | } |
346 | |
347 | /** |
348 | * Set Index Facet Sorting |
349 | * |
350 | * @param array $s the facets sorted by index |
351 | * |
352 | * @return void |
353 | */ |
354 | public function setIndexSortedFacets(array $s) |
355 | { |
356 | $this->indexSortedFacets = $s; |
357 | } |
358 | |
359 | /** |
360 | * Initialize facet settings for the specified configuration sections. |
361 | * |
362 | * @param string $facetList Config section containing fields to activate |
363 | * @param string $facetSettings Config section containing related settings |
364 | * @param string $cfgFile Name of configuration to load (null to load |
365 | * default facets configuration). |
366 | * |
367 | * @return bool True if facets set, false if no settings found |
368 | */ |
369 | protected function initFacetList($facetList, $facetSettings, $cfgFile = null) |
370 | { |
371 | $config = $this->configLoader |
372 | ->get($cfgFile ?? $this->getOptions()->getFacetsIni()); |
373 | $this->initFacetLimitsFromConfig($config->$facetSettings ?? null); |
374 | return parent::initFacetList($facetList, $facetSettings, $cfgFile); |
375 | } |
376 | |
377 | /** |
378 | * Initialize facet settings for the advanced search screen. |
379 | * |
380 | * @return void |
381 | */ |
382 | public function initAdvancedFacets() |
383 | { |
384 | $this->initFacetList('Advanced', 'Advanced_Settings'); |
385 | } |
386 | |
387 | /** |
388 | * Initialize facet settings for the home page. |
389 | * |
390 | * @return void |
391 | */ |
392 | public function initHomePageFacets() |
393 | { |
394 | // Load Advanced settings if HomePage settings are missing (legacy support): |
395 | if (!$this->initFacetList('HomePage', 'HomePage_Settings')) { |
396 | $this->initAdvancedFacets(); |
397 | } |
398 | } |
399 | |
400 | /** |
401 | * Initialize facet settings for the new items page. |
402 | * |
403 | * @return void |
404 | */ |
405 | public function initNewItemsFacets() |
406 | { |
407 | // Load Advanced settings if NewItems settings are missing (fallback to defaults): |
408 | if (!$this->initFacetList('NewItems', 'NewItems_Settings')) { |
409 | $this->initAdvancedFacets(); |
410 | } |
411 | } |
412 | |
413 | /** |
414 | * Add filters to the object based on values found in the request object. |
415 | * |
416 | * @param \Laminas\Stdlib\Parameters $request Parameter object representing user |
417 | * request. |
418 | * |
419 | * @return void |
420 | */ |
421 | protected function initFilters($request) |
422 | { |
423 | // Use the default behavior of the parent class, but add support for the |
424 | // special illustrations filter. |
425 | parent::initFilters($request); |
426 | switch ($request->get('illustration', -1)) { |
427 | case 1: |
428 | $this->addFilter('illustrated:Illustrated'); |
429 | break; |
430 | case 0: |
431 | $this->addFilter('illustrated:"Not Illustrated"'); |
432 | break; |
433 | } |
434 | } |
435 | |
436 | /** |
437 | * Override the normal search behavior with an explicit array of IDs that must |
438 | * be retrieved. |
439 | * |
440 | * @param array $ids Record IDs to load |
441 | * |
442 | * @return void |
443 | */ |
444 | public function setQueryIDs($ids) |
445 | { |
446 | // No need for spell checking or highlighting on an ID query! |
447 | $this->getOptions()->spellcheckEnabled(false); |
448 | $this->getOptions()->disableHighlighting(); |
449 | |
450 | // Special case -- no IDs to set: |
451 | if (empty($ids)) { |
452 | $this->setOverrideQuery('NOT *:*'); |
453 | return; |
454 | } |
455 | |
456 | $callback = function ($i) { |
457 | return '"' . addcslashes($i, '"') . '"'; |
458 | }; |
459 | $ids = array_map($callback, $ids); |
460 | $this->searchingById = true; |
461 | $this->setOverrideQuery('id:(' . implode(' OR ', $ids) . ')'); |
462 | } |
463 | |
464 | /** |
465 | * Get the maximum number of IDs that may be sent to setQueryIDs (-1 for no |
466 | * limit). |
467 | * |
468 | * @return int |
469 | */ |
470 | public function getQueryIDLimit() |
471 | { |
472 | $config = $this->configLoader->get($this->getOptions()->getMainIni()); |
473 | return $config->Index->maxBooleanClauses ?? 1024; |
474 | } |
475 | |
476 | /** |
477 | * Normalize sort parameters. |
478 | * |
479 | * @param string $sort Sort parameter |
480 | * |
481 | * @return string |
482 | */ |
483 | protected function normalizeSort($sort) |
484 | { |
485 | static $table = [ |
486 | 'year' => ['field' => 'publishDateSort', 'order' => 'desc'], |
487 | 'publishDateSort' => ['field' => 'publishDateSort', 'order' => 'desc'], |
488 | 'author' => ['field' => 'author_sort', 'order' => 'asc'], |
489 | 'authorStr' => ['field' => 'author_sort', 'order' => 'asc'], |
490 | 'title' => ['field' => 'title_sort', 'order' => 'asc'], |
491 | 'relevance' => ['field' => 'score', 'order' => 'desc'], |
492 | 'callnumber' => ['field' => 'callnumber-sort', 'order' => 'asc'], |
493 | ]; |
494 | $tieBreaker = $this->getOptions()->getSortTieBreaker(); |
495 | if ($tieBreaker) { |
496 | $sort .= ',' . $tieBreaker; |
497 | } |
498 | |
499 | $normalized = []; |
500 | $fields = []; |
501 | foreach (explode(',', $sort) as $component) { |
502 | $parts = explode(' ', trim($component)); |
503 | $field = reset($parts); |
504 | $order = next($parts); |
505 | if (isset($table[$field])) { |
506 | $normalized[] = sprintf( |
507 | '%s %s', |
508 | $table[$field]['field'], |
509 | $order ?: $table[$field]['order'] |
510 | ); |
511 | $fields[] = $field; |
512 | } else { |
513 | if (!in_array($field, $fields)) { |
514 | $normalized[] = sprintf( |
515 | '%s %s', |
516 | $field, |
517 | $order ?: 'asc' |
518 | ); |
519 | $fields[] = $field; |
520 | } |
521 | } |
522 | } |
523 | return implode(',', $normalized); |
524 | } |
525 | |
526 | /** |
527 | * Create search backend parameters for advanced features. |
528 | * |
529 | * @return ParamBag |
530 | */ |
531 | public function getBackendParameters() |
532 | { |
533 | $backendParams = new ParamBag(); |
534 | |
535 | // Spellcheck |
536 | $backendParams->set( |
537 | 'spellcheck', |
538 | $this->getOptions()->spellcheckEnabled() ? 'true' : 'false' |
539 | ); |
540 | |
541 | // Facets |
542 | $facets = $this->getFacetSettings(); |
543 | if (!empty($facets)) { |
544 | $backendParams->add('facet', 'true'); |
545 | |
546 | foreach ($facets as $key => $value) { |
547 | // prefix keys with "facet" unless they already have a "f." prefix: |
548 | $fullKey = str_starts_with($key, 'f.') ? $key : "facet.$key"; |
549 | $backendParams->add($fullKey, $value); |
550 | } |
551 | $backendParams->add('facet.mincount', 1); |
552 | } |
553 | |
554 | // Filters |
555 | $filters = $this->getFilterSettings(); |
556 | foreach ($filters as $filter) { |
557 | $backendParams->add('fq', $filter); |
558 | } |
559 | |
560 | // Shards |
561 | $allShards = $this->getOptions()->getShards(); |
562 | $shards = $this->getSelectedShards(); |
563 | if (empty($shards)) { |
564 | $shards = array_keys($allShards); |
565 | } |
566 | |
567 | // If we have selected shards, we need to format them: |
568 | if (!empty($shards)) { |
569 | $selectedShards = []; |
570 | foreach ($shards as $current) { |
571 | $selectedShards[$current] = $allShards[$current]; |
572 | } |
573 | $backendParams->add('shards', implode(',', $selectedShards)); |
574 | } |
575 | |
576 | // Sort |
577 | $sort = $this->getSort(); |
578 | if ($sort) { |
579 | // If we have an empty search with relevance sort as the primary sort |
580 | // field, see if there is an override configured: |
581 | $sortFields = explode(',', $sort); |
582 | $allTerms = trim($this->getQuery()->getAllTerms() ?? ''); |
583 | if ( |
584 | 'relevance' === $sortFields[0] |
585 | && ('' === $allTerms || '*:*' === $allTerms || $this->searchingById) |
586 | && ($relOv = $this->getOptions()->getEmptySearchRelevanceOverride()) |
587 | ) { |
588 | $sort = $relOv; |
589 | } |
590 | $backendParams->add('sort', $this->normalizeSort($sort)); |
591 | } |
592 | |
593 | // Highlighting -- on by default, but we should disable if necessary: |
594 | if (!$this->getOptions()->highlightEnabled()) { |
595 | $backendParams->add('hl', 'false'); |
596 | } |
597 | |
598 | // Pivot facets for visual results |
599 | |
600 | if ($pf = $this->getPivotFacets()) { |
601 | $backendParams->add('facet.pivot', $pf); |
602 | $backendParams->set('facet', 'true'); |
603 | } |
604 | |
605 | return $backendParams; |
606 | } |
607 | |
608 | /** |
609 | * Set pivot facet fields to use for visual results |
610 | * |
611 | * @param string $facets A comma-separated list of fields |
612 | * |
613 | * @return void |
614 | */ |
615 | public function setPivotFacets($facets) |
616 | { |
617 | $this->pivotFacets = $facets; |
618 | } |
619 | |
620 | /** |
621 | * Get pivot facet information for visual facets |
622 | * |
623 | * @return string |
624 | */ |
625 | public function getPivotFacets() |
626 | { |
627 | return $this->pivotFacets; |
628 | } |
629 | |
630 | /** |
631 | * Format a single filter for use in getFilterList(). |
632 | * |
633 | * @param string $field Field name |
634 | * @param string $value Field value |
635 | * @param string $operator Operator (AND/OR/NOT) |
636 | * @param bool $translate Should we translate the label? |
637 | * |
638 | * @return array |
639 | */ |
640 | protected function formatFilterListEntry($field, $value, $operator, $translate) |
641 | { |
642 | $filter = parent::formatFilterListEntry( |
643 | $field, |
644 | $value, |
645 | $operator, |
646 | $translate |
647 | ); |
648 | |
649 | $hierarchicalFacets = $this->getOptions()->getHierarchicalFacets(); |
650 | $hierarchicalFacetSeparators |
651 | = $this->getOptions()->getHierarchicalFacetSeparators(); |
652 | // Convert range queries to a language-non-specific format: |
653 | $caseInsensitiveRegex = '/^\(\[(.*) TO (.*)\] OR \[(.*) TO (.*)\]\)$/'; |
654 | if (preg_match('/^\[(.*) TO (.*)\]$/', $value, $matches)) { |
655 | // Simple case: [X TO Y] |
656 | $filter['displayText'] = $matches[1] . ' - ' . $matches[2]; |
657 | } elseif (preg_match($caseInsensitiveRegex, $value, $matches)) { |
658 | // Case insensitive case: [x TO y] OR [X TO Y]; convert |
659 | // only if values in both ranges match up! |
660 | if ( |
661 | strtolower($matches[3]) == strtolower($matches[1]) |
662 | && strtolower($matches[4]) == strtolower($matches[2]) |
663 | ) { |
664 | $filter['displayText'] = $matches[1] . ' - ' . $matches[2]; |
665 | } |
666 | } elseif ($this->facetHelper && in_array($field, $hierarchicalFacets)) { |
667 | // Display hierarchical facet levels nicely |
668 | $separator = $hierarchicalFacetSeparators[$field] ?? '/'; |
669 | if (!$translate) { |
670 | $filter['displayText'] = $this->facetHelper->formatDisplayText( |
671 | $filter['displayText'], |
672 | true, |
673 | $separator |
674 | )->getDisplayString(); |
675 | } else { |
676 | $domain = $this->getOptions() |
677 | ->getTextDomainForTranslatedFacet($field); |
678 | |
679 | // Provide translation of each separate element as a default |
680 | // while allowing one to translate the full string too: |
681 | $parts = $this->facetHelper |
682 | ->getFilterStringParts($filter['value']); |
683 | $translated = []; |
684 | foreach ($parts as $part) { |
685 | $translated[] = $this->translate([$domain, $part]); |
686 | } |
687 | $translatedParts = implode($separator, $translated); |
688 | |
689 | $parts = array_map( |
690 | function ($part) { |
691 | return $part->getDisplayString(); |
692 | }, |
693 | $parts |
694 | ); |
695 | $str = implode($separator, $parts); |
696 | $filter['displayText'] |
697 | = $this->translate([$domain, $str], [], $translatedParts); |
698 | } |
699 | } |
700 | |
701 | return $filter; |
702 | } |
703 | |
704 | /** |
705 | * Get information on the current state of the boolean checkbox facets. |
706 | * |
707 | * @param array $include List of checkbox filters to return (null for all) |
708 | * @param bool $includeDynamic Should we include dynamically-generated |
709 | * checkboxes that are not part of the include list above? |
710 | * |
711 | * @return array |
712 | */ |
713 | public function getCheckboxFacets( |
714 | array $include = null, |
715 | bool $includeDynamic = true |
716 | ) { |
717 | // Grab checkbox facet details using the standard method: |
718 | $facets = parent::getCheckboxFacets($include, $includeDynamic); |
719 | |
720 | $config = $this->configLoader->get($this->getOptions()->getFacetsIni()); |
721 | $filterField = $config->CustomFilters->custom_filter_field ?? 'vufind'; |
722 | |
723 | // Special case -- inverted checkbox facets should always appear, even on |
724 | // the "no results" screen, since setting them actually EXPANDS rather than |
725 | // reduces the result set. |
726 | foreach ($facets as $i => $facet) { |
727 | // Append colon on end to ensure that $customFilter is always set. |
728 | [$field, $customFilter] = explode(':', $facet['filter'] . ':'); |
729 | if ( |
730 | $field == $filterField |
731 | && isset($config->CustomFilters->inverted_filters[$customFilter]) |
732 | ) { |
733 | $facets[$i]['alwaysVisible'] = true; |
734 | } |
735 | } |
736 | |
737 | // Return modified list: |
738 | return $facets; |
739 | } |
740 | } |