Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
36.23% covered (danger)
36.23%
25 / 69
20.00% covered (danger)
20.00%
1 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
HierarchicalFacetListener
36.23% covered (danger)
36.23%
25 / 69
20.00% covered (danger)
20.00%
1 / 5
173.36
0.00% covered (danger)
0.00%
0 / 1
 __construct
90.91% covered (success)
90.91%
20 / 22
0.00% covered (danger)
0.00%
0 / 1
5.02
 attach
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 onSearchPost
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
42
 processHierarchicalFacets
0.00% covered (danger)
0.00%
0 / 24
0.00% covered (danger)
0.00%
0 / 1
90
 formatFacetField
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
12
1<?php
2
3/**
4 * Solr hierarchical facet listener.
5 *
6 * PHP version 8
7 *
8 * Copyright (C) Villanova University 2013.
9 * Copyright (C) The National Library of Finland 2014.
10 *
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License version 2,
13 * as published by the Free Software Foundation.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
23 *
24 * @category VuFind
25 * @package  Search
26 * @author   David Maus <maus@hab.de>
27 * @author   Ere Maijala <ere.maijala@helsinki.fi>
28 * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
29 * @link     https://vufind.org Main Site
30 */
31
32namespace VuFind\Search\Solr;
33
34use Laminas\EventManager\EventInterface;
35use Laminas\EventManager\SharedEventManagerInterface;
36use Laminas\ServiceManager\ServiceLocatorInterface;
37use VuFind\I18n\TranslatableString;
38use VuFindSearch\Backend\BackendInterface;
39use VuFindSearch\Service;
40
41use function in_array;
42use function is_array;
43
44/**
45 * Solr hierarchical facet handling listener.
46 *
47 * @category VuFind
48 * @package  Search
49 * @author   David Maus <maus@hab.de>
50 * @author   Ere Maijala <ere.maijala@helsinki.fi>
51 * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
52 * @link     https://vufind.org Main Site
53 */
54class HierarchicalFacetListener
55{
56    /**
57     * Backend.
58     *
59     * @var BackendInterface
60     */
61    protected $backend;
62
63    /**
64     * Service container.
65     *
66     * @var ServiceLocatorInterface
67     */
68    protected $serviceLocator;
69
70    /**
71     * Facet configuration.
72     *
73     * @var Config
74     */
75    protected $facetConfig;
76
77    /**
78     * Facet helper.
79     *
80     * @var HierarchicalFacetHelper
81     */
82    protected $facetHelper;
83
84    /**
85     * Facet display styles.
86     *
87     * @var array
88     */
89    protected $displayStyles;
90
91    /**
92     * Hierarchy level separators
93     *
94     * @var array
95     */
96    protected $separators;
97
98    /**
99     * Facet settings
100     *
101     * @var array
102     */
103    protected $translatedFacets = [];
104
105    /**
106     * Text domains for translated facets
107     *
108     * @var array
109     */
110    protected $translatedFacetsTextDomains = [];
111
112    /**
113     * Constructor.
114     *
115     * @param BackendInterface        $backend        Search backend
116     * @param ServiceLocatorInterface $serviceLocator Service locator
117     * @param string                  $facetConfig    Facet config file id
118     *
119     * @return void
120     */
121    public function __construct(
122        BackendInterface $backend,
123        ServiceLocatorInterface $serviceLocator,
124        $facetConfig
125    ) {
126        $this->backend = $backend;
127        $this->serviceLocator = $serviceLocator;
128
129        $config = $this->serviceLocator->get(\VuFind\Config\PluginManager::class);
130        $this->facetConfig = $config->get($facetConfig);
131        $this->facetHelper = $this->serviceLocator
132            ->get(\VuFind\Search\Solr\HierarchicalFacetHelper::class);
133
134        $specialFacets = $this->facetConfig->SpecialFacets;
135        $this->displayStyles
136            = isset($specialFacets->hierarchicalFacetDisplayStyles)
137            ? $specialFacets->hierarchicalFacetDisplayStyles->toArray()
138            : [];
139        $this->separators
140            = isset($specialFacets->hierarchicalFacetSeparators)
141            ? $specialFacets->hierarchicalFacetSeparators->toArray()
142            : [];
143
144        $translatedFacets = $this->facetConfig->Advanced_Settings->translated_facets
145            ?? [];
146        foreach ($translatedFacets as $current) {
147            $parts = explode(':', $current);
148            $this->translatedFacets[] = $parts[0];
149            if (isset($parts[1])) {
150                $this->translatedFacetsTextDomains[$parts[0]] = $parts[1];
151            }
152        }
153    }
154
155    /**
156     * Attach listener to shared event manager.
157     *
158     * @param SharedEventManagerInterface $manager Shared event manager
159     *
160     * @return void
161     */
162    public function attach(
163        SharedEventManagerInterface $manager
164    ) {
165        $manager->attach(
166            Service::class,
167            Service::EVENT_POST,
168            [$this, 'onSearchPost']
169        );
170    }
171
172    /**
173     * Format hierarchical facets accordingly
174     *
175     * @param EventInterface $event Event
176     *
177     * @return EventInterface
178     */
179    public function onSearchPost(EventInterface $event)
180    {
181        $command = $event->getParam('command');
182
183        if ($command->getTargetIdentifier() !== $this->backend->getIdentifier()) {
184            return $event;
185        }
186        $context = $command->getContext();
187        if (
188            $context == 'search' || $context == 'retrieve'
189            || $context == 'retrieveBatch' || $context == 'similar'
190        ) {
191            $this->processHierarchicalFacets($event);
192        }
193        return $event;
194    }
195
196    /**
197     * Process hierarchical facets and format them accordingly
198     *
199     * @param EventInterface $event Event
200     *
201     * @return void
202     */
203    protected function processHierarchicalFacets($event)
204    {
205        if (empty($this->facetConfig->SpecialFacets->hierarchical)) {
206            return;
207        }
208        $result = $event->getParam('command')->getResult();
209        foreach ($result->getRecords() as $record) {
210            $fields = $record->getRawData();
211            foreach ($this->facetConfig->SpecialFacets->hierarchical as $facetName) {
212                if (!isset($fields[$facetName])) {
213                    continue;
214                }
215                if (is_array($fields[$facetName])) {
216                    $allLevels = ($this->displayStyles[$facetName] ?? '') === 'full';
217                    foreach ($fields[$facetName] as &$value) {
218                        // Include a translation for each value only if we don't
219                        // display full hierarchy or this is the deepest hierarchy
220                        // level available
221                        if (
222                            !$allLevels
223                            || $this->facetHelper->isDeepestFacetLevel(
224                                $fields[$facetName],
225                                $value
226                            )
227                        ) {
228                            $value = $this->formatFacetField($facetName, $value);
229                        } else {
230                            $value
231                                = new TranslatableString((string)$value, '', false);
232                        }
233                    }
234                    // Unset the reference:
235                    unset($value);
236                    $fields[$facetName] = array_unique($fields[$facetName]);
237                } else {
238                    $fields[$facetName]
239                        = $this->formatFacetField($facetName, $fields[$facetName]);
240                }
241            }
242
243            $record->setRawData($fields);
244        }
245    }
246
247    /**
248     * Format a facet field according to the settings
249     *
250     * @param string $facet Facet field
251     * @param string $value Facet value
252     *
253     * @return string Formatted field
254     */
255    protected function formatFacetField($facet, $value)
256    {
257        $allLevels = isset($this->displayStyles[$facet])
258            ? $this->displayStyles[$facet] == 'full'
259            : false;
260        $separator = $this->separators[$facet] ?? '/';
261        $domain = in_array($facet, $this->translatedFacets)
262            ? ($this->translatedFacetsTextDomains[$facet] ?? 'default')
263            : false;
264        $value = $this->facetHelper
265            ->formatDisplayText($value, $allLevels, $separator, $domain);
266
267        return $value;
268    }
269}