Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
65 / 65
100.00% covered (success)
100.00%
6 / 6
CRAP
100.00% covered (success)
100.00%
1 / 1
MultiIndexListener
100.00% covered (success)
100.00%
65 / 65
100.00% covered (success)
100.00%
6 / 6
23
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 attach
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 onSearchPre
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
5
 getFields
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
4
 getSearchSpecs
100.00% covered (success)
100.00%
19 / 19
100.00% covered (success)
100.00%
1 / 1
5
 stripSpecsQueryFields
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
7
1<?php
2
3/**
4 * MultiIndex listener class file.
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
30namespace VuFind\Search\Solr;
31
32use Laminas\EventManager\EventInterface;
33use Laminas\EventManager\SharedEventManagerInterface;
34use VuFindSearch\Backend\BackendInterface;
35use VuFindSearch\Service;
36
37use function in_array;
38use function is_array;
39
40/**
41 * MultiIndex listener class file.
42 *
43 * @category VuFind
44 * @package  Search
45 * @author   David Maus <maus@hab.de>
46 * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
47 * @link     https://vufind.org Main Site
48 */
49class MultiIndexListener
50{
51    /**
52     * Backend.
53     *
54     * @var BackendInterface
55     */
56    protected $backend;
57
58    /**
59     * Available shards, indexed by name.
60     *
61     * @var array
62     */
63    protected $shards;
64
65    /**
66     * Fields to strip, indexed by shard name.
67     *
68     * @var array
69     */
70    protected $stripfields;
71
72    /**
73     * Base search specs.
74     *
75     * @var array
76     */
77    protected $specs;
78
79    /**
80     * Constructor.
81     *
82     * @param BackendInterface $backend     Backend
83     * @param array            $shards      Available shards, indexed by name
84     * @param array            $stripfields Fields to strip, indexed by shard name
85     * @param array            $specs       Base search specs
86     *
87     * @return void
88     */
89    public function __construct(
90        BackendInterface $backend,
91        array $shards,
92        array $stripfields,
93        array $specs
94    ) {
95        $this->specs       = $specs;
96        $this->backend     = $backend;
97        $this->shards      = $shards;
98        $this->stripfields = $stripfields;
99    }
100
101    /**
102     * Attach listener to shared event manager.
103     *
104     * @param SharedEventManagerInterface $manager Shared event manager
105     *
106     * @return void
107     */
108    public function attach(SharedEventManagerInterface $manager)
109    {
110        $manager->attach(
111            Service::class,
112            Service::EVENT_PRE,
113            [$this, 'onSearchPre']
114        );
115    }
116
117    /**
118     * VuFindSearch.pre()
119     *
120     * @param EventInterface $event Event
121     *
122     * @return EventInterface
123     */
124    public function onSearchPre(EventInterface $event)
125    {
126        $command = $event->getParam('command');
127        if ($command->getTargetIdentifier() === $this->backend->getIdentifier()) {
128            $params = $command->getSearchParameters();
129            $allShardsContexts = ['retrieve', 'retrieveBatch'];
130            if (in_array($command->getContext(), $allShardsContexts)) {
131                // If we're retrieving by id(s), we should pull all shards to be
132                // sure we find the right record(s).
133                $params->set('shards', implode(',', $this->shards));
134            } else {
135                // In any other context, we should make sure our field values are
136                // all legal.
137
138                // Normalize array of strings containing comma-separated values to
139                // simple array of values; check if $params->get('shards') returns
140                // an array to prevent invalid argument warnings.
141                $shards = $params->get('shards');
142                $shards = explode(
143                    ',',
144                    implode(',', (is_array($shards) ? $shards : []))
145                );
146                $fields = $this->getFields($shards);
147                $specs  = $this->getSearchSpecs($fields);
148                $this->backend->getQueryBuilder()->setSpecs($specs);
149                $facets = $params->get('facet.field') ?: [];
150                $params->set('facet.field', array_diff($facets, $fields));
151            }
152        }
153        return $event;
154    }
155
156    /// Internal API
157
158    /**
159     * Return array of fields to strip.
160     *
161     * @param array $shards Active shards
162     *
163     * @return array
164     */
165    protected function getFields(array $shards)
166    {
167        $fields = [];
168        foreach ($this->stripfields as $name => $strip) {
169            if (isset($this->shards[$name])) {
170                $uri = $this->shards[$name];
171                if (in_array($uri, $shards)) {
172                    $fields = array_merge($fields, $strip);
173                }
174            }
175        }
176        return array_unique($fields);
177    }
178
179    /**
180     * Strip fields from base search specs.
181     *
182     * @param array $fields Fields to strip
183     *
184     * @return array
185     */
186    protected function getSearchSpecs(array $fields)
187    {
188        $specs  = [];
189        $fields = array_merge(
190            $fields,
191            array_map(
192                function ($field) {
193                    return "-{$field}";
194                },
195                $fields
196            )
197        );
198        foreach ($this->specs as $handler => $spec) {
199            $specs[$handler] = [];
200            foreach ($spec as $component => $settings) {
201                switch ($component) {
202                    case 'QueryFields':
203                        $specs[$handler][$component]
204                            = $this->stripSpecsQueryFields($settings, $fields);
205                        break;
206                    default:
207                        $specs[$handler][$component] = $settings;
208                        break;
209                }
210            }
211        }
212        return $specs;
213    }
214
215    /**
216     * Strip fields from a search specs QueryFields section.
217     *
218     * @param array $settings QueryField section
219     * @param array $fields   Fields to strip
220     *
221     * @return array
222     */
223    protected function stripSpecsQueryFields(array $settings, array $fields)
224    {
225        $stripped = [];
226        foreach ($settings as $field => $rule) {
227            if (is_numeric($field)) {
228                $group = [];
229                $type  = reset($rule);
230                while (next($rule) !== false) {
231                    if (!in_array(key($rule), $fields)) {
232                        $group[key($rule)] = current($rule);
233                    }
234                }
235                if ($group) {
236                    array_unshift($group, $type);
237                    $stripped[$field] = $group;
238                }
239            } else {
240                if (!in_array($field, $fields, true)) {
241                    $stripped[$field] = $rule;
242                }
243            }
244        }
245        return $stripped;
246    }
247}