Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
95.00% covered (success)
95.00%
38 / 40
88.24% covered (warning)
88.24%
15 / 17
CRAP
0.00% covered (danger)
0.00%
0 / 1
RecordCollection
95.00% covered (success)
95.00%
38 / 40
88.24% covered (warning)
88.24%
15 / 17
25
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 getSpellcheck
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 getTotal
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getFacets
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
4
 setFilteredFacetCounts
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getFilteredFacetCounts
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setFacets
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getQueryFacets
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getPivotFacets
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 getGroups
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getHighlighting
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getCursorMark
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getMaxScore
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getResponseHeader
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getSolrParameters
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getSpellcheckQuery
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getRawSpellcheckSuggestions
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3/**
4 * Simple JSON-based record collection.
5 *
6 * PHP version 8
7 *
8 * Copyright (C) Villanova University 2010.
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
28 */
29
30namespace VuFindSearch\Backend\Solr\Response\Json;
31
32use VuFindSearch\Response\AbstractRecordCollection;
33
34use function array_key_exists;
35
36/**
37 * Simple JSON-based record collection.
38 *
39 * @category VuFind
40 * @package  Search
41 * @author   David Maus <maus@hab.de>
42 * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
43 * @link     https://vufind.org
44 */
45class RecordCollection extends AbstractRecordCollection
46{
47    /**
48     * Template of deserialized SOLR response.
49     *
50     * @see \VuFindSearch\Backend\Solr\Response\Json\RecordCollection::__construct()
51     *
52     * @var array
53     */
54    protected static $template = [
55        'responseHeader' => [],
56        'response'       => ['numFound' => 0, 'start' => 0],
57        'spellcheck'     => ['suggestions' => []],
58        'facet_counts'   => [],
59    ];
60
61    /**
62     * Deserialized SOLR response.
63     *
64     * @var array
65     */
66    protected $response;
67
68    /**
69     * Spellcheck information.
70     *
71     * @var Spellcheck
72     */
73    protected $spellcheck;
74
75    /**
76     * Facet fields.
77     *
78     * @var array
79     */
80    protected $facetFields = null;
81
82    /**
83     * How many facet values have been filtered out, indexed by field.
84     *
85     * @var array
86     */
87    protected $filteredFacetCounts = [];
88
89    /**
90     * Constructor.
91     *
92     * @param array $response Deserialized SOLR response
93     *
94     * @return void
95     */
96    public function __construct(array $response)
97    {
98        if (
99            array_key_exists('response', $response)
100            && null === $response['response']
101        ) {
102            unset($response['response']);
103        }
104        $this->response = array_replace_recursive(static::$template, $response);
105        $this->offset = $this->response['response']['start'];
106        $this->rewind();
107    }
108
109    /**
110     * Return spellcheck information.
111     *
112     * @return Spellcheck
113     */
114    public function getSpellcheck()
115    {
116        if (!$this->spellcheck) {
117            $this->spellcheck = new Spellcheck(
118                $this->getRawSpellcheckSuggestions(),
119                $this->getSpellcheckQuery()
120            );
121        }
122        return $this->spellcheck;
123    }
124
125    /**
126     * Return total number of records found.
127     *
128     * @return int
129     */
130    public function getTotal()
131    {
132        return $this->response['response']['numFound'];
133    }
134
135    /**
136     * Return available facets.
137     *
138     * Returns an associative array with the field name as key. The value is an
139     * associative array of available facets for the field, indexed by facet value.
140     *
141     * @return array
142     */
143    public function getFacets()
144    {
145        if (null === $this->facetFields) {
146            $this->facetFields = [];
147            $facetFieldData = $this->response['facet_counts']['facet_fields'] ?? [];
148            foreach ($facetFieldData as $field => $facetData) {
149                $values = [];
150                foreach ($facetData as $value) {
151                    $values[$value[0]] = $value[1];
152                }
153                $this->facetFields[$field] = $values;
154            }
155        }
156        return $this->facetFields;
157    }
158
159    /**
160     * Set filtered facet data.
161     *
162     * @param array $counts Counts of filtered facet values, indexed by field name.
163     *
164     * @return void
165     */
166    public function setFilteredFacetCounts(array $counts): void
167    {
168        $this->filteredFacetCounts = $counts;
169    }
170
171    /**
172     * Get filtered facet data.
173     *
174     * @return array
175     */
176    public function getFilteredFacetCounts(): array
177    {
178        return $this->filteredFacetCounts;
179    }
180
181    /**
182     * Set facets.
183     *
184     * @param array $facets Facet fields
185     *
186     * @return void
187     */
188    public function setFacets(array $facets): void
189    {
190        $this->facetFields = $facets;
191    }
192
193    /**
194     * Return available query facets.
195     *
196     * Returns an associative array with the internal field name as key. The
197     * value is an associative array of the available facets for the field,
198     * indexed by facet value.
199     *
200     * @return array
201     */
202    public function getQueryFacets()
203    {
204        return $this->response['facet_counts']['facet_queries'] ?? [];
205    }
206
207    /**
208     * Return available pivot facets.
209     *
210     * Returns an associative array with the internal field name as key. The
211     * value is an associative array of the available facets for the field,
212     * indexed by facet value.
213     *
214     * @return array
215     */
216    public function getPivotFacets()
217    {
218        $result = [];
219        foreach (
220            $this->response['facet_counts']['facet_pivot'] ?? [] as $facetData
221        ) {
222            foreach ($facetData as $current) {
223                $result[$current['value']] = $current;
224            }
225        }
226        return $result;
227    }
228
229    /**
230     * Get grouped results.
231     *
232     * @return array
233     */
234    public function getGroups()
235    {
236        return $this->response['grouped'] ?? [];
237    }
238
239    /**
240     * Get highlighting details.
241     *
242     * @return array
243     */
244    public function getHighlighting()
245    {
246        return $this->response['highlighting'] ?? [];
247    }
248
249    /**
250     * Get cursorMark.
251     *
252     * @return string
253     */
254    public function getCursorMark()
255    {
256        return $this->response['nextCursorMark'] ?? '';
257    }
258
259    /**
260     * Gets the highest relevance to search.
261     *
262     * @return mixed
263     */
264    public function getMaxScore()
265    {
266        return $this->response['response']['maxScore'] ?? null;
267    }
268
269    /**
270     * Get response header.
271     *
272     * @return array
273     */
274    public function getResponseHeader()
275    {
276        return $this->response['responseHeader'] ?? [];
277    }
278
279    /**
280     * Get raw Solr input parameters from the response.
281     *
282     * @return array
283     */
284    protected function getSolrParameters()
285    {
286        return $this->response['responseHeader']['params'] ?? [];
287    }
288
289    /**
290     * Extract the best matching Spellcheck query from the raw Solr input parameters.
291     *
292     * @return string
293     */
294    protected function getSpellcheckQuery()
295    {
296        $params = $this->getSolrParameters();
297        return $params['spellcheck.q'] ?? ($params['q'] ?? '');
298    }
299
300    /**
301     * Get raw Solr Spellcheck suggestions.
302     *
303     * @return array
304     */
305    protected function getRawSpellcheckSuggestions()
306    {
307        return $this->response['spellcheck']['suggestions'] ?? [];
308    }
309}