Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
95.93% |
212 / 221 |
|
95.45% |
21 / 22 |
CRAP | |
0.00% |
0 / 1 |
Params | |
95.93% |
212 / 221 |
|
95.45% |
21 / 22 |
92 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
1 | |||
initFromRequest | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
5 | |||
initSearch | |
57.14% |
12 / 21 |
|
0.00% |
0 / 1 |
5.26 | |||
setBasicSearch | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
3 | |||
initSort | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
3 | |||
setSort | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
3 | |||
addFilter | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
6 | |||
addHiddenFilter | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
6 | |||
removeFilter | |
100.00% |
16 / 16 |
|
100.00% |
1 / 1 |
9 | |||
removeAllFilters | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
4 | |||
addFacet | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
3 | |||
addCheckboxFacet | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
6 | |||
resetFacetConfig | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getBackendParameters | |
100.00% |
21 / 21 |
|
100.00% |
1 / 1 |
5 | |||
addDefaultFilters | |
100.00% |
13 / 13 |
|
100.00% |
1 / 1 |
7 | |||
proxyMethod | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
translateFacetName | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
isBlenderFilter | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
translateFilter | |
100.00% |
30 / 30 |
|
100.00% |
1 / 1 |
14 | |||
addLowerLevelHierarchicalFilterValues | |
100.00% |
21 / 21 |
|
100.00% |
1 / 1 |
6 | |||
translateSearchType | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
translateSort | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 |
1 | <?php |
2 | |
3 | /** |
4 | * Blender Search Parameters |
5 | * |
6 | * PHP version 8 |
7 | * |
8 | * Copyright (C) The National Library of Finland 2015-2022. |
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_Blender |
25 | * @author Ere Maijala <ere.maijala@helsinki.fi> |
26 | * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License |
27 | * @link https://vufind.org Main Page |
28 | */ |
29 | |
30 | namespace VuFind\Search\Blender; |
31 | |
32 | use VuFind\Search\Base\Params as BaseParams; |
33 | use VuFind\Search\Solr\HierarchicalFacetHelper; |
34 | use VuFindSearch\ParamBag; |
35 | |
36 | use function array_slice; |
37 | use function call_user_func_array; |
38 | use function count; |
39 | use function func_get_args; |
40 | use function in_array; |
41 | use function is_callable; |
42 | |
43 | /** |
44 | * Blender Search Parameters |
45 | * |
46 | * @category VuFind |
47 | * @package Search_Blender |
48 | * @author Ere Maijala <ere.maijala@helsinki.fi> |
49 | * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License |
50 | * @link https://vufind.org Main Page |
51 | */ |
52 | class Params extends \VuFind\Search\Solr\Params |
53 | { |
54 | /** |
55 | * Search params for backends |
56 | * |
57 | * @var \VuFind\Search\Base\Params[] |
58 | */ |
59 | protected $searchParams; |
60 | |
61 | /** |
62 | * Blender configuration |
63 | * |
64 | * @var \Laminas\Config\Config |
65 | */ |
66 | protected $blenderConfig; |
67 | |
68 | /** |
69 | * Blender mappings |
70 | * |
71 | * @var array |
72 | */ |
73 | protected $mappings; |
74 | |
75 | /** |
76 | * Current filters not supported by a backend |
77 | * |
78 | * @var array |
79 | */ |
80 | protected $unsupportedFilters = []; |
81 | |
82 | /** |
83 | * Constructor |
84 | * |
85 | * @param \VuFind\Search\Base\Options $options Options to use |
86 | * @param \VuFind\Config\PluginManager $configLoader Config loader |
87 | * @param HierarchicalFacetHelper $facetHelper Hierarchical facet helper |
88 | * @param array $searchParams Search params for backends |
89 | * @param \Laminas\Config\Config $blenderConfig Blender configuration |
90 | * @param array $mappings Blender mappings |
91 | */ |
92 | public function __construct( |
93 | \VuFind\Search\Base\Options $options, |
94 | \VuFind\Config\PluginManager $configLoader, |
95 | HierarchicalFacetHelper $facetHelper, |
96 | array $searchParams, |
97 | \Laminas\Config\Config $blenderConfig, |
98 | array $mappings |
99 | ) { |
100 | // Assign these first; they are needed during parent's construct: |
101 | $this->searchParams = $searchParams; |
102 | $this->blenderConfig = $blenderConfig; |
103 | $this->mappings = $mappings; |
104 | |
105 | parent::__construct( |
106 | $options, |
107 | $configLoader, |
108 | $facetHelper |
109 | ); |
110 | } |
111 | |
112 | /** |
113 | * Pull the search parameters |
114 | * |
115 | * @param \Laminas\Stdlib\Parameters $request Parameter object representing user |
116 | * request. |
117 | * |
118 | * @return void |
119 | */ |
120 | public function initFromRequest($request) |
121 | { |
122 | $this->unsupportedFilters = []; |
123 | |
124 | // First do a basic init without filters, facets etc. that are processed via |
125 | // methods called by parent's initFromRequest: |
126 | $filteredParams = [ |
127 | 'lookfor', |
128 | 'type', |
129 | 'sort', |
130 | 'filter', |
131 | 'hiddenFilters', |
132 | 'daterange', |
133 | ]; |
134 | foreach ($this->searchParams as $params) { |
135 | $translatedRequest = clone $request; |
136 | foreach (array_keys($translatedRequest->getArrayCopy()) as $key) { |
137 | // Check for filtered param or advanced search types: |
138 | if (in_array($key, $filteredParams) || preg_match('/^type\d+$/', $key)) { |
139 | $translatedRequest->offsetUnset($key); |
140 | } |
141 | } |
142 | $params->initFromRequest($translatedRequest); |
143 | } |
144 | parent::initFromRequest($request); |
145 | } |
146 | |
147 | /** |
148 | * Initialize the object's search settings from a request object. |
149 | * |
150 | * @param \Laminas\Stdlib\Parameters $request Parameter object representing user |
151 | * request. |
152 | * |
153 | * @return void |
154 | */ |
155 | protected function initSearch($request) |
156 | { |
157 | foreach ($this->searchParams as $params) { |
158 | $backendId = $params->getSearchClassId(); |
159 | // Clone request to avoid tampering the original one: |
160 | $translatedRequest = clone $request; |
161 | // Map basic search type: |
162 | if ($type = $translatedRequest->get('type')) { |
163 | $translatedRequest->set( |
164 | 'type', |
165 | $this->translateSearchType($type, $backendId) |
166 | ); |
167 | } |
168 | // Map advanced search types: |
169 | $i = 0; |
170 | while ($types = $translatedRequest->get("type$i")) { |
171 | $translatedRequest->set( |
172 | "type$i", |
173 | array_map( |
174 | function ($type) use ($backendId) { |
175 | return $this->translateSearchType($type, $backendId); |
176 | }, |
177 | (array)$types |
178 | ) |
179 | ); |
180 | ++$i; |
181 | } |
182 | $params->initSearch($translatedRequest); |
183 | } |
184 | parent::initSearch($request); |
185 | } |
186 | |
187 | /** |
188 | * Set a basic search query: |
189 | * |
190 | * @param string $lookfor The search query |
191 | * @param string $handler The search handler (null for default) |
192 | * |
193 | * @return void |
194 | */ |
195 | public function setBasicSearch($lookfor, $handler = null) |
196 | { |
197 | foreach ($this->searchParams as $params) { |
198 | $backendId = $params->getSearchClassId(); |
199 | $params->setBasicSearch( |
200 | $lookfor, |
201 | $handler |
202 | ? $this->translateSearchType($handler, $backendId) : $handler |
203 | ); |
204 | } |
205 | parent::setBasicSearch($lookfor, $handler); |
206 | } |
207 | |
208 | /** |
209 | * Get the value for which type of sorting to use |
210 | * |
211 | * @param \Laminas\Stdlib\Parameters $request Parameter object representing user |
212 | * request. |
213 | * |
214 | * @return void |
215 | */ |
216 | protected function initSort($request) |
217 | { |
218 | foreach ($this->searchParams as $params) { |
219 | $backendId = $params->getSearchClassId(); |
220 | // Clone request to avoid tampering the original one: |
221 | $translatedRequest = clone $request; |
222 | // Map sort: |
223 | if ($sort = $translatedRequest->get('sort')) { |
224 | $translatedRequest->set( |
225 | 'sort', |
226 | $this->translateSort($sort, $backendId) |
227 | ); |
228 | } |
229 | $params->initSort($translatedRequest); |
230 | } |
231 | parent::initSort($request); |
232 | } |
233 | |
234 | /** |
235 | * Set the sorting value (note: sort will be set to default if an illegal |
236 | * or empty value is passed in). |
237 | * |
238 | * @param string $sort New sort value (null for default) |
239 | * @param bool $force Set sort value without validating it? |
240 | * |
241 | * @return void |
242 | */ |
243 | public function setSort($sort, $force = false) |
244 | { |
245 | foreach ($this->searchParams as $params) { |
246 | $backendId = $params->getSearchClassId(); |
247 | $params->setSort( |
248 | $sort ? $this->translateSort($sort, $backendId) : $sort, |
249 | $force |
250 | ); |
251 | } |
252 | parent::setSort($sort, $force); |
253 | } |
254 | |
255 | /** |
256 | * Take a filter string and add it into the protected |
257 | * array checking for duplicates. |
258 | * |
259 | * @param string $newFilter A filter string from url : "field:value" |
260 | * |
261 | * @return void |
262 | */ |
263 | public function addFilter($newFilter) |
264 | { |
265 | parent::addFilter($newFilter); |
266 | if ($this->isBlenderFilter($newFilter)) { |
267 | return; |
268 | } |
269 | foreach ($this->searchParams as $params) { |
270 | $backendId = $params->getSearchClassId(); |
271 | if ($translated = $this->translateFilter($newFilter, $backendId)) { |
272 | foreach ($translated as $current) { |
273 | if (null !== $current) { |
274 | $params->addFilter($current); |
275 | } |
276 | } |
277 | } else { |
278 | // Add the filter to the list of unsupported filters: |
279 | $this->unsupportedFilters[$backendId][] |
280 | = $this->parseFilter($newFilter); |
281 | } |
282 | } |
283 | } |
284 | |
285 | /** |
286 | * Take a filter string and add it into the protected hidden filters |
287 | * array checking for duplicates. |
288 | * |
289 | * @param string $newFilter A filter string from url : "field:value" |
290 | * |
291 | * @return void |
292 | */ |
293 | public function addHiddenFilter($newFilter) |
294 | { |
295 | parent::addHiddenFilter($newFilter); |
296 | if ($this->isBlenderFilter($newFilter)) { |
297 | return; |
298 | } |
299 | foreach ($this->searchParams as $params) { |
300 | $backendId = $params->getSearchClassId(); |
301 | if ($translated = $this->translateFilter($newFilter, $backendId)) { |
302 | foreach ($translated as $current) { |
303 | if (null !== $current) { |
304 | $params->addHiddenFilter($current); |
305 | } |
306 | } |
307 | } else { |
308 | // Add the filter to the list of unsupported filters: |
309 | $this->unsupportedFilters[$backendId][] |
310 | = $this->parseFilter($newFilter); |
311 | } |
312 | } |
313 | } |
314 | |
315 | /** |
316 | * Remove a filter from the list. |
317 | * |
318 | * @param string $oldFilter A filter string from url : "field:value" |
319 | * |
320 | * @return void |
321 | */ |
322 | public function removeFilter($oldFilter) |
323 | { |
324 | parent::removeFilter($oldFilter); |
325 | if ($this->isBlenderFilter($oldFilter)) { |
326 | return; |
327 | } |
328 | |
329 | // Update list of unsupported filters: |
330 | if ($this->unsupportedFilters) { |
331 | $parsed = $this->parseFilter($oldFilter); |
332 | foreach ($this->unsupportedFilters as $backendId => $filters) { |
333 | $updatedFilters = $filters; |
334 | foreach ($filters as $key => $filter) { |
335 | if ($parsed === $filter) { |
336 | unset($updatedFilters[$key]); |
337 | } |
338 | } |
339 | $this->unsupportedFilters[$backendId] = $updatedFilters; |
340 | } |
341 | } |
342 | |
343 | foreach ($this->searchParams as $params) { |
344 | $backendId = $params->getSearchClassId(); |
345 | if ($translated = $this->translateFilter($oldFilter, $backendId)) { |
346 | foreach ($translated as $current) { |
347 | $params->removeFilter($current); |
348 | } |
349 | } |
350 | } |
351 | } |
352 | |
353 | /** |
354 | * Remove all filters from the list. |
355 | * |
356 | * @param string $field Name of field to remove filters from (null to remove |
357 | * all filters from all fields) |
358 | * |
359 | * @return void |
360 | */ |
361 | public function removeAllFilters($field = null) |
362 | { |
363 | $this->unsupportedFilters = []; |
364 | if (null === $field) { |
365 | $this->proxyMethod(__FUNCTION__, func_get_args()); |
366 | return; |
367 | } |
368 | |
369 | parent::removeAllFilters($field); |
370 | foreach ($this->searchParams as $params) { |
371 | $backendId = $params->getSearchClassId(); |
372 | if ($translated = $this->translateFacetName($field, $backendId)) { |
373 | $params->removeAllFilters($translated); |
374 | } |
375 | } |
376 | } |
377 | |
378 | /** |
379 | * Add a field to facet on. |
380 | * |
381 | * @param string $newField Field name |
382 | * @param string $newAlias Optional on-screen display label |
383 | * @param bool $ored Should we treat this as an ORed facet? |
384 | * |
385 | * @return void |
386 | */ |
387 | public function addFacet($newField, $newAlias = null, $ored = false) |
388 | { |
389 | parent::addFacet($newField, $newAlias, $ored); |
390 | foreach ($this->searchParams as $params) { |
391 | $backendId = $params->getSearchClassId(); |
392 | if ($translated = $this->translateFacetName($newField, $backendId)) { |
393 | $params->addFacet($translated, $newAlias, $ored); |
394 | } |
395 | } |
396 | } |
397 | |
398 | /** |
399 | * Add a checkbox facet. When the checkbox is checked, the specified filter |
400 | * will be applied to the search. When the checkbox is not checked, no filter |
401 | * will be applied. |
402 | * |
403 | * @param string $filter [field]:[value] pair to associate with checkbox |
404 | * @param string $desc Description to associate with the checkbox |
405 | * @param bool $dynamic Is this being added dynamically (true) or in response |
406 | * to a user configuration (false)? |
407 | * |
408 | * @return void |
409 | */ |
410 | public function addCheckboxFacet($filter, $desc, $dynamic = false) |
411 | { |
412 | parent::addCheckboxFacet($filter, $desc, $dynamic); |
413 | if ($this->isBlenderFilter($filter)) { |
414 | return; |
415 | } |
416 | foreach ($this->searchParams as $params) { |
417 | $backendId = $params->getSearchClassId(); |
418 | if ($translated = $this->translateFilter($filter, $backendId)) { |
419 | foreach ($translated as $current) { |
420 | if (null !== $current) { |
421 | $params->addCheckboxFacet($current, $desc, $dynamic); |
422 | } |
423 | } |
424 | } |
425 | } |
426 | } |
427 | |
428 | /** |
429 | * Reset the current facet configuration. |
430 | * |
431 | * @return void |
432 | */ |
433 | public function resetFacetConfig() |
434 | { |
435 | $this->proxyMethod(__FUNCTION__, func_get_args()); |
436 | } |
437 | |
438 | /** |
439 | * Create search backend parameters for advanced features. |
440 | * |
441 | * @return ParamBag |
442 | */ |
443 | public function getBackendParameters(): ParamBag |
444 | { |
445 | $result = parent::getBackendParameters(); |
446 | foreach ($this->unsupportedFilters as $backendId => $filters) { |
447 | if ($filters) { |
448 | $result->add('fq', "-blender_backend:\"$backendId\""); |
449 | } |
450 | } |
451 | foreach ($this->searchParams as $params) { |
452 | $backendId = $params->getSearchClassId(); |
453 | if (!is_callable([$params, 'getBackendParameters'])) { |
454 | throw new \Exception( |
455 | "Backend $backendId missing support for getBackendParameters" |
456 | ); |
457 | } |
458 | |
459 | // Clone params so that adding any default filters does not affect the |
460 | // original instance: |
461 | $params = clone $params; |
462 | $this->addDefaultFilters($params, $backendId); |
463 | |
464 | $result->set( |
465 | "query_$backendId", |
466 | $params->getQuery() |
467 | ); |
468 | $result->set( |
469 | "params_$backendId", |
470 | $params->getBackendParameters() |
471 | ); |
472 | } |
473 | return $result; |
474 | } |
475 | |
476 | /** |
477 | * Add default filters to the given params |
478 | * |
479 | * @param BaseParams $params Params |
480 | * @param string $backendId Backend ID |
481 | * |
482 | * @return void |
483 | */ |
484 | protected function addDefaultFilters(BaseParams $params, string $backendId): void |
485 | { |
486 | foreach ($this->mappings['Facets']['Fields'] ?? [] as $fieldConfig) { |
487 | $mappings = $fieldConfig['Mappings'][$backendId] ?? []; |
488 | $defaultValue = $mappings['DefaultValue'] ?? null; |
489 | if (null !== $defaultValue) { |
490 | $translatedField = $mappings['Field']; |
491 | $filterList = $params->getFilterList(); |
492 | $found = false; |
493 | foreach ($filterList as $filters) { |
494 | foreach ($filters as $filter) { |
495 | if ($filter['field'] === $translatedField) { |
496 | $found = true; |
497 | break; |
498 | } |
499 | } |
500 | } |
501 | if (!$found) { |
502 | $params->addFilter("$translatedField:$defaultValue"); |
503 | } |
504 | } |
505 | } |
506 | } |
507 | |
508 | /** |
509 | * Proxy a method call to parent class and all backend params classes |
510 | * |
511 | * @param string $method Method |
512 | * @param array $params Method parameters |
513 | * |
514 | * @return mixed |
515 | */ |
516 | protected function proxyMethod(string $method, array $params) |
517 | { |
518 | $result = call_user_func_array(parent::class . "::$method", $params); |
519 | foreach ($this->searchParams as $searchParams) { |
520 | $result = call_user_func_array([$searchParams, $method], $params); |
521 | } |
522 | return $result; |
523 | } |
524 | |
525 | /** |
526 | * Translate a facet field name |
527 | * |
528 | * @param string $field Facet field |
529 | * @param string $backendId Backend ID |
530 | * |
531 | * @return string |
532 | */ |
533 | protected function translateFacetName(string $field, string $backendId): string |
534 | { |
535 | $fieldConfig = $this->mappings['Facets']['Fields'][$field] ?? []; |
536 | return $fieldConfig['Mappings'][$backendId]['Field'] ?? ''; |
537 | } |
538 | |
539 | /** |
540 | * Check if the filter is a special Blender filter |
541 | * |
542 | * @param string $filter Filter |
543 | * |
544 | * @return bool |
545 | */ |
546 | protected function isBlenderFilter(string $filter): bool |
547 | { |
548 | [, $field] = $this->parseFilterAndPrefix($filter); |
549 | return 'blender_backend' === $field; |
550 | } |
551 | |
552 | /** |
553 | * Translate a filter |
554 | * |
555 | * @param string $filter Filter |
556 | * @param string $backendId Backend ID |
557 | * |
558 | * @return array |
559 | */ |
560 | protected function translateFilter(string $filter, string $backendId): array |
561 | { |
562 | [$prefix, $field, $value] = $this->parseFilterAndPrefix($filter); |
563 | |
564 | $fieldConfig = $this->mappings['Facets']['Fields'][$field] ?? []; |
565 | if ($ignore = $fieldConfig['Mappings'][$backendId]['Ignore'] ?? '') { |
566 | if (true === $ignore || in_array($value, (array)$ignore)) { |
567 | return [null]; |
568 | } |
569 | } |
570 | $mappings = $fieldConfig['Mappings'][$backendId] ?? []; |
571 | $translatedField = $mappings['Field'] ?? ''; |
572 | if (!$mappings || !$translatedField) { |
573 | // Facet not supported by the backend |
574 | return []; |
575 | } |
576 | |
577 | // Map filter value |
578 | $facetType = $fieldConfig['Type'] ?? 'normal'; |
579 | if ('boolean' === $facetType) { |
580 | $value = (bool)$value; |
581 | } |
582 | $resultValues = []; |
583 | foreach ($mappings['Values'] ?? [$value => $value] as $k => $v) { |
584 | if ('boolean' === $facetType) { |
585 | $v = (bool)$v; |
586 | } |
587 | if ($value === $v) { |
588 | $resultValues[] = $k; |
589 | } |
590 | } |
591 | if ($mappings['Hierarchical'] ?? false) { |
592 | $resultValues = $this->addLowerLevelHierarchicalFilterValues( |
593 | $value, |
594 | $resultValues, |
595 | $mappings['Values'] ?? [] |
596 | ); |
597 | } |
598 | |
599 | // If the result is more than one value, convert an AND search to OR: |
600 | if ('' === $prefix && count($resultValues) > 1) { |
601 | $prefix = '~'; |
602 | } |
603 | |
604 | $result = []; |
605 | foreach ($resultValues as $value) { |
606 | $result[] = $prefix . $translatedField . ':' . $value; |
607 | } |
608 | |
609 | return $result; |
610 | } |
611 | |
612 | /** |
613 | * Handle any lower level mappings when translating hierarchical facets. |
614 | * |
615 | * This ensures that selecting a facet value higher in a hierarchy than the |
616 | * mapped value still adds the correct filter. |
617 | * Example: |
618 | * - Backend's value 'journal' is mapped to hierarchical value |
619 | * '1/Journal/eJournal/'. |
620 | * - When user selects the top level facet '0/Journal/', it needs to be |
621 | * reflected as 'journal' in the backend. |
622 | * |
623 | * @param mixed $value Filter value |
624 | * @param array $resultValues Current resulting filter values |
625 | * @param array $mappings Value mappings |
626 | * |
627 | * @return array Updated filter values |
628 | */ |
629 | protected function addLowerLevelHierarchicalFilterValues( |
630 | $value, |
631 | array $resultValues, |
632 | array $mappings |
633 | ): array { |
634 | $levelOffset = -1; |
635 | do { |
636 | $levelGood = false; |
637 | foreach ($mappings as $k => $v) { |
638 | $parts = explode('/', $v); |
639 | $partCount = count($parts); |
640 | if ($parts[0] <= 0 || $partCount <= 2) { |
641 | continue; |
642 | } |
643 | $level = $parts[0] + $levelOffset; |
644 | if ($level < 0) { |
645 | continue; |
646 | } |
647 | $levelGood = true; |
648 | $levelValue = $level . '/' |
649 | . implode( |
650 | '/', |
651 | array_slice($parts, 1, $level + 1) |
652 | ) . '/'; |
653 | if ($value === $levelValue) { |
654 | $resultValues[] = $k; |
655 | } |
656 | } |
657 | --$levelOffset; |
658 | } while ($levelGood); |
659 | |
660 | return $resultValues; |
661 | } |
662 | |
663 | /** |
664 | * Translate a search type |
665 | * |
666 | * @param string $type Search type |
667 | * @param string $backendId Backend ID |
668 | * |
669 | * @return string |
670 | */ |
671 | protected function translateSearchType(string $type, string $backendId): string |
672 | { |
673 | $mappings = $this->mappings['Search']['Fields'][$type]['Mappings'] ?? []; |
674 | return $mappings[$backendId] ?? ''; |
675 | } |
676 | |
677 | /** |
678 | * Translate a sort option |
679 | * |
680 | * @param string $sort Sort option |
681 | * @param string $backendId Backend ID |
682 | * |
683 | * @return string |
684 | */ |
685 | protected function translateSort(string $sort, string $backendId): string |
686 | { |
687 | $mappings = $this->mappings['Sorting']['Fields'][$sort]['Mappings'] ?? []; |
688 | return $mappings[$backendId] ?? ''; |
689 | } |
690 | } |