Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
73.74% |
146 / 198 |
|
65.38% |
17 / 26 |
CRAP | |
0.00% |
0 / 1 |
AbstractSolrBackendFactory | |
73.74% |
146 / 198 |
|
65.38% |
17 / 26 |
134.89 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
__invoke | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
2 | |||
getPrioritizedConfigsForIndexSettings | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getMergedIndexConfig | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
4 | |||
getFlatIndexConfig | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
getIndexConfig | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
createBackend | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
4 | |||
createListeners | |
58.18% |
32 / 55 |
|
0.00% |
0 / 1 |
28.33 | |||
getIndexName | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
1 | |||
getSolrBaseUrls | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
getSolrUrl | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
2 | |||
getHiddenFilters | |
77.78% |
7 / 9 |
|
0.00% |
0 / 1 |
5.27 | |||
createConnector | |
89.66% |
26 / 29 |
|
0.00% |
0 / 1 |
6.04 | |||
getHttpOptions | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
createQueryBuilder | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
1 | |||
createLuceneSyntaxHelper | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
createSimilarBuilder | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
createRecordCollectionFactory | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
getCreateRecordCallback | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
loadSpecs | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
getDeduplicationListener | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
2 | |||
getCustomFilterListener | |
75.00% |
6 / 8 |
|
0.00% |
0 / 1 |
5.39 | |||
getHierarchicalFacetListener | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
1 | |||
getInjectHighlightingListener | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
getInjectConditionalFilterListener | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
2 | |||
getDefaultParametersListener | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | |
3 | /** |
4 | * Abstract factory for SOLR backends. |
5 | * |
6 | * PHP version 8 |
7 | * |
8 | * Copyright (C) Villanova University 2013. |
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 |
25 | * @author David Maus <maus@hab.de> |
26 | * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License |
27 | * @link https://vufind.org Main Site |
28 | */ |
29 | |
30 | namespace VuFind\Search\Factory; |
31 | |
32 | use Laminas\Config\Config; |
33 | use Psr\Container\ContainerInterface; |
34 | use VuFind\Search\Solr\CustomFilterListener; |
35 | use VuFind\Search\Solr\DeduplicationListener; |
36 | use VuFind\Search\Solr\DefaultParametersListener; |
37 | use VuFind\Search\Solr\FilterFieldConversionListener; |
38 | use VuFind\Search\Solr\HierarchicalFacetListener; |
39 | use VuFind\Search\Solr\InjectConditionalFilterListener; |
40 | use VuFind\Search\Solr\InjectHighlightingListener; |
41 | use VuFind\Search\Solr\InjectSpellingListener; |
42 | use VuFind\Search\Solr\MultiIndexListener; |
43 | use VuFind\Search\Solr\V3\ErrorListener as LegacyErrorListener; |
44 | use VuFind\Search\Solr\V4\ErrorListener; |
45 | use VuFindSearch\Backend\BackendInterface; |
46 | use VuFindSearch\Backend\Solr\Backend; |
47 | use VuFindSearch\Backend\Solr\Connector; |
48 | use VuFindSearch\Backend\Solr\HandlerMap; |
49 | use VuFindSearch\Backend\Solr\LuceneSyntaxHelper; |
50 | use VuFindSearch\Backend\Solr\QueryBuilder; |
51 | use VuFindSearch\Backend\Solr\Response\Json\RecordCollection; |
52 | use VuFindSearch\Backend\Solr\Response\Json\RecordCollectionFactory; |
53 | use VuFindSearch\Backend\Solr\SimilarBuilder; |
54 | use VuFindSearch\Response\RecordCollectionFactoryInterface; |
55 | |
56 | use function count; |
57 | use function is_object; |
58 | |
59 | /** |
60 | * Abstract factory for SOLR backends. |
61 | * |
62 | * @category VuFind |
63 | * @package Search |
64 | * @author David Maus <maus@hab.de> |
65 | * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License |
66 | * @link https://vufind.org Main Site |
67 | */ |
68 | abstract class AbstractSolrBackendFactory extends AbstractBackendFactory |
69 | { |
70 | use SharedListenersTrait; |
71 | |
72 | /** |
73 | * Logger. |
74 | * |
75 | * @var \Laminas\Log\LoggerInterface |
76 | */ |
77 | protected $logger; |
78 | |
79 | /** |
80 | * Primary configuration file identifier. |
81 | * |
82 | * @var string |
83 | */ |
84 | protected $mainConfig = 'config'; |
85 | |
86 | /** |
87 | * Search configuration file identifier. |
88 | * |
89 | * @var string |
90 | */ |
91 | protected $searchConfig; |
92 | |
93 | /** |
94 | * Facet configuration file identifier. |
95 | * |
96 | * @var string |
97 | */ |
98 | protected $facetConfig; |
99 | |
100 | /** |
101 | * YAML searchspecs filename. |
102 | * |
103 | * @var string |
104 | */ |
105 | protected $searchYaml; |
106 | |
107 | /** |
108 | * VuFind configuration reader |
109 | * |
110 | * @var \VuFind\Config\PluginManager |
111 | */ |
112 | protected $config; |
113 | |
114 | /** |
115 | * Name of index configuration setting to use to retrieve Solr index name |
116 | * (core or collection). |
117 | * |
118 | * @var string |
119 | */ |
120 | protected $indexNameSetting = 'default_core'; |
121 | |
122 | /** |
123 | * Solr index name (used as default if $this->indexNameSetting is unset in |
124 | * the config). |
125 | * |
126 | * @var string |
127 | */ |
128 | protected $defaultIndexName = ''; |
129 | |
130 | /** |
131 | * When looking up the Solr index name config setting, should we allow fallback |
132 | * into the main configuration (true), or limit ourselves to the search |
133 | * config (false)? |
134 | * |
135 | * @var bool |
136 | */ |
137 | protected $allowFallbackForIndexName = false; |
138 | |
139 | /** |
140 | * Solr field used to store unique identifiers |
141 | * |
142 | * @var string |
143 | */ |
144 | protected $uniqueKey = 'id'; |
145 | |
146 | /** |
147 | * Solr connector class |
148 | * |
149 | * @var string |
150 | */ |
151 | protected $connectorClass = Connector::class; |
152 | |
153 | /** |
154 | * Solr backend class |
155 | * |
156 | * @var string |
157 | */ |
158 | protected $backendClass = Backend::class; |
159 | |
160 | /** |
161 | * Record collection class for RecordCollectionFactory |
162 | * |
163 | * @var string |
164 | */ |
165 | protected $recordCollectionClass = RecordCollection::class; |
166 | |
167 | /** |
168 | * Record collection factory class |
169 | * |
170 | * @var string |
171 | */ |
172 | protected $recordCollectionFactoryClass = RecordCollectionFactory::class; |
173 | |
174 | /** |
175 | * Merged index configuration |
176 | * |
177 | * @var ?array |
178 | */ |
179 | protected $mergedIndexConfig = null; |
180 | |
181 | /** |
182 | * Constructor |
183 | */ |
184 | public function __construct() |
185 | { |
186 | parent::__construct(); |
187 | } |
188 | |
189 | /** |
190 | * Create service |
191 | * |
192 | * @param ContainerInterface $sm Service manager |
193 | * @param string $name Requested service name |
194 | * @param array $options Extra options (unused) |
195 | * |
196 | * @return Backend |
197 | * |
198 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) |
199 | */ |
200 | public function __invoke(ContainerInterface $sm, $name, array $options = null) |
201 | { |
202 | $this->setup($sm); |
203 | $this->config = $this->serviceLocator |
204 | ->get(\VuFind\Config\PluginManager::class); |
205 | if ($this->serviceLocator->has(\VuFind\Log\Logger::class)) { |
206 | $this->logger = $this->serviceLocator->get(\VuFind\Log\Logger::class); |
207 | } |
208 | $connector = $this->createConnector(); |
209 | $backend = $this->createBackend($connector); |
210 | $backend->setIdentifier($name); |
211 | $this->createListeners($backend); |
212 | return $backend; |
213 | } |
214 | |
215 | /** |
216 | * Return an ordered array of configurations to check for index configurations. |
217 | * |
218 | * @return string[] |
219 | */ |
220 | protected function getPrioritizedConfigsForIndexSettings(): array |
221 | { |
222 | return array_unique([$this->searchConfig, $this->mainConfig]); |
223 | } |
224 | |
225 | /** |
226 | * Merge together the Index sections of all eligible configuration files and |
227 | * return the result as an array. |
228 | * |
229 | * @return array |
230 | */ |
231 | protected function getMergedIndexConfig(): array |
232 | { |
233 | if (null === $this->mergedIndexConfig) { |
234 | $this->mergedIndexConfig = []; |
235 | foreach ($this->getPrioritizedConfigsForIndexSettings() as $configName) { |
236 | $config = $this->config->get($configName); |
237 | $this->mergedIndexConfig += isset($config->Index) |
238 | ? $config->Index->toArray() : []; |
239 | } |
240 | } |
241 | return $this->mergedIndexConfig; |
242 | } |
243 | |
244 | /** |
245 | * Get the Index section of the highest-priority configuration file (for use |
246 | * in cases where fallback is not desired). |
247 | * |
248 | * @return array |
249 | */ |
250 | protected function getFlatIndexConfig(): array |
251 | { |
252 | $configList = $this->getPrioritizedConfigsForIndexSettings(); |
253 | $configObj = $this->config->get($configList[0]); |
254 | return isset($configObj->Index) |
255 | ? $configObj->Index->toArray() : []; |
256 | } |
257 | |
258 | /** |
259 | * Get an index-related configuration setting. |
260 | * |
261 | * @param string $setting Name of setting |
262 | * @param mixed $default Default value if unset |
263 | * @param bool $fallback Should we fall back to main config if the |
264 | * setting is absent from the search config file? |
265 | * |
266 | * @return mixed |
267 | */ |
268 | protected function getIndexConfig( |
269 | string $setting, |
270 | $default = null, |
271 | bool $fallback = true |
272 | ) { |
273 | $config = $fallback |
274 | ? $this->getMergedIndexConfig() : $this->getFlatIndexConfig(); |
275 | return $config[$setting] ?? $default; |
276 | } |
277 | |
278 | /** |
279 | * Create the SOLR backend. |
280 | * |
281 | * @param Connector $connector Connector |
282 | * |
283 | * @return Backend |
284 | */ |
285 | protected function createBackend(Connector $connector) |
286 | { |
287 | $backend = new $this->backendClass($connector); |
288 | $pageSize = $this->getIndexConfig('record_batch_size', 100); |
289 | $maxClauses = $this->getIndexConfig('maxBooleanClauses', $pageSize); |
290 | if ($pageSize > 0 && $maxClauses > 0) { |
291 | $backend->setPageSize(min($pageSize, $maxClauses)); |
292 | } |
293 | $backend->setQueryBuilder($this->createQueryBuilder()); |
294 | $backend->setSimilarBuilder($this->createSimilarBuilder()); |
295 | if ($this->logger) { |
296 | $backend->setLogger($this->logger); |
297 | } |
298 | $backend->setRecordCollectionFactory($this->createRecordCollectionFactory()); |
299 | return $backend; |
300 | } |
301 | |
302 | /** |
303 | * Create listeners. |
304 | * |
305 | * @param Backend $backend Backend |
306 | * |
307 | * @return void |
308 | */ |
309 | protected function createListeners(Backend $backend) |
310 | { |
311 | $events = $this->serviceLocator->get('SharedEventManager'); |
312 | |
313 | // Load configurations: |
314 | $config = $this->config->get($this->mainConfig); |
315 | $search = $this->config->get($this->searchConfig); |
316 | $facet = $this->config->get($this->facetConfig); |
317 | |
318 | // Attach default parameters listener first so that any other listeners can |
319 | // override the parameters as necessary: |
320 | if (!empty($search->General->default_parameters)) { |
321 | $this->getDefaultParametersListener( |
322 | $backend, |
323 | $search->General->default_parameters->toArray() |
324 | )->attach($events); |
325 | } |
326 | |
327 | // Highlighting |
328 | $this->getInjectHighlightingListener($backend, $search)->attach($events); |
329 | |
330 | // Conditional Filters |
331 | if ( |
332 | isset($search->ConditionalHiddenFilters) |
333 | && $search->ConditionalHiddenFilters->count() > 0 |
334 | ) { |
335 | $this->getInjectConditionalFilterListener($backend, $search)->attach($events); |
336 | } |
337 | |
338 | // Spellcheck |
339 | if ($config->Spelling->enabled ?? true) { |
340 | $dictionaries = $config->Spelling->dictionaries?->toArray() ?? []; |
341 | if (empty($dictionaries)) { |
342 | // Respect the deprecated 'simple' configuration setting. |
343 | $dictionaries = ($config->Spelling->simple ?? false) |
344 | ? ['basicSpell'] : ['default', 'basicSpell']; |
345 | } |
346 | $spellingListener = new InjectSpellingListener( |
347 | $backend, |
348 | $dictionaries, |
349 | $this->logger |
350 | ); |
351 | $spellingListener->attach($events); |
352 | } |
353 | |
354 | // Apply field stripping if applicable: |
355 | if (isset($search->StripFields) && isset($search->IndexShards)) { |
356 | $strip = $search->StripFields->toArray(); |
357 | foreach ($strip as $k => $v) { |
358 | $strip[$k] = array_map('trim', explode(',', $v)); |
359 | } |
360 | $mindexListener = new MultiIndexListener( |
361 | $backend, |
362 | $search->IndexShards->toArray(), |
363 | $strip, |
364 | $this->loadSpecs() |
365 | ); |
366 | $mindexListener->attach($events); |
367 | } |
368 | |
369 | // Apply deduplication if applicable: |
370 | if (isset($search->Records->deduplication)) { |
371 | $this->getDeduplicationListener( |
372 | $backend, |
373 | $search->Records->deduplication |
374 | )->attach($events); |
375 | } |
376 | |
377 | // Attach hierarchical facet listener: |
378 | $this->getHierarchicalFacetListener($backend)->attach($events); |
379 | |
380 | // Apply legacy filter conversion if necessary: |
381 | $facets = $this->config->get($this->facetConfig); |
382 | if (!empty($facets->LegacyFields)) { |
383 | $filterFieldConversionListener = new FilterFieldConversionListener( |
384 | $facets->LegacyFields->toArray() |
385 | ); |
386 | $filterFieldConversionListener->attach($events); |
387 | } |
388 | |
389 | // Attach custom filter listener if needed: |
390 | if ($cfListener = $this->getCustomFilterListener($backend, $facets)) { |
391 | $cfListener->attach($events); |
392 | } |
393 | |
394 | // Attach hide facet value listener: |
395 | if ($hfvListener = $this->getHideFacetValueListener($backend, $facet)) { |
396 | $hfvListener->attach($events); |
397 | } |
398 | |
399 | // Attach error listeners for Solr 3.x and Solr 4.x (for backward |
400 | // compatibility with VuFind 1.x instances). |
401 | $legacyErrorListener = new LegacyErrorListener($backend->getIdentifier()); |
402 | $legacyErrorListener->attach($events); |
403 | $errorListener = new ErrorListener($backend->getIdentifier()); |
404 | $errorListener->attach($events); |
405 | } |
406 | |
407 | /** |
408 | * Get the name of the Solr index (core or collection). |
409 | * |
410 | * @return string |
411 | */ |
412 | protected function getIndexName() |
413 | { |
414 | return $this->getIndexConfig( |
415 | $this->indexNameSetting, |
416 | $this->defaultIndexName, |
417 | $this->allowFallbackForIndexName |
418 | ); |
419 | } |
420 | |
421 | /** |
422 | * Get the Solr base URL(s) (without the path to the specific index) |
423 | * |
424 | * @return string[] |
425 | */ |
426 | protected function getSolrBaseUrls(): array |
427 | { |
428 | $urls = $this->getIndexConfig('url', []); |
429 | return is_object($urls) ? $urls->toArray() : (array)$urls; |
430 | } |
431 | |
432 | /** |
433 | * Get the full Solr URL(s) (including index path part). |
434 | * |
435 | * @return string|array |
436 | */ |
437 | protected function getSolrUrl() |
438 | { |
439 | $indexName = $this->getIndexName(); |
440 | $urls = array_map( |
441 | function ($value) use ($indexName) { |
442 | return "$value/$indexName"; |
443 | }, |
444 | $this->getSolrBaseUrls() |
445 | ); |
446 | return count($urls) === 1 ? $urls[0] : $urls; |
447 | } |
448 | |
449 | /** |
450 | * Get all hidden filter settings. |
451 | * |
452 | * @return array |
453 | */ |
454 | protected function getHiddenFilters() |
455 | { |
456 | $search = $this->config->get($this->searchConfig); |
457 | $hf = []; |
458 | |
459 | // Hidden filters |
460 | if (isset($search->HiddenFilters)) { |
461 | foreach ($search->HiddenFilters as $field => $value) { |
462 | $hf[] = sprintf('%s:"%s"', $field, $value); |
463 | } |
464 | } |
465 | |
466 | // Raw hidden filters |
467 | if (isset($search->RawHiddenFilters)) { |
468 | foreach ($search->RawHiddenFilters as $filter) { |
469 | $hf[] = $filter; |
470 | } |
471 | } |
472 | |
473 | return $hf; |
474 | } |
475 | |
476 | /** |
477 | * Create the SOLR connector. |
478 | * |
479 | * @return Connector |
480 | */ |
481 | protected function createConnector() |
482 | { |
483 | $timeout = $this->getIndexConfig('timeout', 30); |
484 | $searchConfig = $this->config->get($this->searchConfig); |
485 | $defaultFields = $searchConfig->General->default_record_fields ?? '*'; |
486 | |
487 | if (($searchConfig->Explain->enabled ?? false) && !str_contains($defaultFields, 'score')) { |
488 | $defaultFields .= ',score'; |
489 | } |
490 | |
491 | $handlers = [ |
492 | 'select' => [ |
493 | 'fallback' => true, |
494 | 'defaults' => ['fl' => $defaultFields], |
495 | 'appends' => ['fq' => []], |
496 | ], |
497 | 'terms' => [ |
498 | 'functions' => ['terms'], |
499 | ], |
500 | ]; |
501 | |
502 | foreach ($this->getHiddenFilters() as $filter) { |
503 | array_push($handlers['select']['appends']['fq'], $filter); |
504 | } |
505 | |
506 | $connector = new $this->connectorClass( |
507 | $this->getSolrUrl(), |
508 | new HandlerMap($handlers), |
509 | function (string $url) use ($timeout) { |
510 | return $this->createHttpClient( |
511 | $timeout, |
512 | $this->getHttpOptions($url), |
513 | $url |
514 | ); |
515 | }, |
516 | $this->uniqueKey |
517 | ); |
518 | |
519 | if ($this->logger) { |
520 | $connector->setLogger($this->logger); |
521 | } |
522 | |
523 | if ($cache = $this->createConnectorCache($searchConfig)) { |
524 | $connector->setCache($cache); |
525 | } |
526 | |
527 | return $connector; |
528 | } |
529 | |
530 | /** |
531 | * Get HTTP options for the client |
532 | * |
533 | * @param string $url URL being requested |
534 | * |
535 | * @return array |
536 | * |
537 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) |
538 | */ |
539 | protected function getHttpOptions(string $url): array |
540 | { |
541 | return []; |
542 | } |
543 | |
544 | /** |
545 | * Create the query builder. |
546 | * |
547 | * @return QueryBuilder |
548 | */ |
549 | protected function createQueryBuilder() |
550 | { |
551 | $specs = $this->loadSpecs(); |
552 | $defaultDismax = $this->getIndexConfig('default_dismax_handler', 'dismax'); |
553 | $builder = new QueryBuilder($specs, $defaultDismax); |
554 | |
555 | // Configure builder: |
556 | $builder->setLuceneHelper($this->createLuceneSyntaxHelper()); |
557 | |
558 | return $builder; |
559 | } |
560 | |
561 | /** |
562 | * Create Lucene syntax helper. |
563 | * |
564 | * @return LuceneSyntaxHelper |
565 | */ |
566 | protected function createLuceneSyntaxHelper() |
567 | { |
568 | $search = $this->config->get($this->searchConfig); |
569 | $caseSensitiveBooleans = $search->General->case_sensitive_bools ?? true; |
570 | $caseSensitiveRanges = $search->General->case_sensitive_ranges ?? true; |
571 | return new LuceneSyntaxHelper($caseSensitiveBooleans, $caseSensitiveRanges); |
572 | } |
573 | |
574 | /** |
575 | * Create the similar records query builder. |
576 | * |
577 | * @return SimilarBuilder |
578 | */ |
579 | protected function createSimilarBuilder() |
580 | { |
581 | return new SimilarBuilder( |
582 | $this->config->get($this->searchConfig), |
583 | $this->uniqueKey |
584 | ); |
585 | } |
586 | |
587 | /** |
588 | * Create the record collection factory. |
589 | * |
590 | * @return RecordCollectionFactoryInterface |
591 | */ |
592 | protected function createRecordCollectionFactory(): RecordCollectionFactoryInterface |
593 | { |
594 | return new $this->recordCollectionFactoryClass( |
595 | $this->getCreateRecordCallback(), |
596 | $this->recordCollectionClass |
597 | ); |
598 | } |
599 | |
600 | /** |
601 | * Get the callback for creating a record. |
602 | * |
603 | * Returns a callable or null to use RecordCollectionFactory's default method. |
604 | * |
605 | * @return callable|null |
606 | */ |
607 | protected function getCreateRecordCallback(): ?callable |
608 | { |
609 | return null; |
610 | } |
611 | |
612 | /** |
613 | * Load the search specs. |
614 | * |
615 | * @return array |
616 | */ |
617 | protected function loadSpecs() |
618 | { |
619 | return $this->serviceLocator->get(\VuFind\Config\SearchSpecsReader::class) |
620 | ->get($this->searchYaml); |
621 | } |
622 | |
623 | /** |
624 | * Get a deduplication listener for the backend |
625 | * |
626 | * @param Backend $backend Search backend |
627 | * @param bool $enabled Whether deduplication is enabled |
628 | * |
629 | * @return DeduplicationListener |
630 | */ |
631 | protected function getDeduplicationListener(Backend $backend, $enabled) |
632 | { |
633 | return new DeduplicationListener( |
634 | $backend, |
635 | $this->serviceLocator, |
636 | $this->searchConfig, |
637 | 'datasources', |
638 | $enabled |
639 | ); |
640 | } |
641 | |
642 | /** |
643 | * Get a custom filter listener for the backend (or null if not needed). |
644 | * |
645 | * @param BackendInterface $backend Search backend |
646 | * @param Config $facet Configuration of facets |
647 | * |
648 | * @return mixed null|CustomFilterListener |
649 | */ |
650 | protected function getCustomFilterListener( |
651 | BackendInterface $backend, |
652 | Config $facet |
653 | ) { |
654 | $customField = $facet->CustomFilters->custom_filter_field ?? 'vufind'; |
655 | $normal = $inverted = []; |
656 | |
657 | foreach ($facet->CustomFilters->translated_filters ?? [] as $key => $val) { |
658 | $normal[$customField . ':"' . $key . '"'] = $val; |
659 | } |
660 | foreach ($facet->CustomFilters->inverted_filters ?? [] as $key => $val) { |
661 | $inverted[$customField . ':"' . $key . '"'] = $val; |
662 | } |
663 | return empty($normal) && empty($inverted) |
664 | ? null |
665 | : new CustomFilterListener($backend, $normal, $inverted); |
666 | } |
667 | |
668 | /** |
669 | * Get a hierarchical facet listener for the backend |
670 | * |
671 | * @param BackendInterface $backend Search backend |
672 | * |
673 | * @return HierarchicalFacetListener |
674 | */ |
675 | protected function getHierarchicalFacetListener(BackendInterface $backend) |
676 | { |
677 | return new HierarchicalFacetListener( |
678 | $backend, |
679 | $this->serviceLocator, |
680 | $this->facetConfig |
681 | ); |
682 | } |
683 | |
684 | /** |
685 | * Get a highlighting listener for the backend |
686 | * |
687 | * @param BackendInterface $backend Search backend |
688 | * @param Config $search Search configuration |
689 | * |
690 | * @return InjectHighlightingListener |
691 | */ |
692 | protected function getInjectHighlightingListener( |
693 | BackendInterface $backend, |
694 | Config $search |
695 | ) { |
696 | $fl = $search->General->highlighting_fields ?? '*'; |
697 | $extras = $search->General->extra_hl_params ?? []; |
698 | return new InjectHighlightingListener($backend, $fl, $extras); |
699 | } |
700 | |
701 | /** |
702 | * Get a Conditional Filter Listener |
703 | * |
704 | * @param BackendInterface $backend Search backend |
705 | * @param Config $search Search configuration |
706 | * |
707 | * @return InjectConditionalFilterListener |
708 | */ |
709 | protected function getInjectConditionalFilterListener(BackendInterface $backend, Config $search) |
710 | { |
711 | $listener = new InjectConditionalFilterListener( |
712 | $backend, |
713 | $search->ConditionalHiddenFilters->toArray() |
714 | ); |
715 | $listener->setAuthorizationService( |
716 | $this->serviceLocator |
717 | ->get(\LmcRbacMvc\Service\AuthorizationService::class) |
718 | ); |
719 | return $listener; |
720 | } |
721 | |
722 | /** |
723 | * Get a default parameters listener for the backend |
724 | * |
725 | * @param Backend $backend Search backend |
726 | * @param array $params Default parameters |
727 | * |
728 | * @return DeduplicationListener |
729 | */ |
730 | protected function getDefaultParametersListener(Backend $backend, array $params) |
731 | { |
732 | return new DefaultParametersListener($backend, $params); |
733 | } |
734 | } |