Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
28 / 28
100.00% covered (success)
100.00%
8 / 8
CRAP
100.00% covered (success)
100.00%
1 / 1
Spellcheck
100.00% covered (success)
100.00%
28 / 28
100.00% covered (success)
100.00%
8 / 8
19
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
4
 getQuery
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getSecondary
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 mergeWith
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
4
 getIterator
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 count
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 contains
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
6
 compareTermLength
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3/**
4 * SOLR spellcheck information.
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 ArrayObject;
33use Countable;
34use IteratorAggregate;
35use Traversable;
36
37use function is_array;
38use function strlen;
39
40/**
41 * SOLR spellcheck information.
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
48 */
49class Spellcheck implements IteratorAggregate, Countable
50{
51    /**
52     * Spellcheck terms mapped to term information.
53     *
54     * @var ArrayObject
55     */
56    protected $terms;
57
58    /**
59     * Spelling query that generated suggestions
60     *
61     * @var string
62     */
63    protected $query;
64
65    /**
66     * Secondary spelling suggestions (in case merged results are not useful).
67     *
68     * @var Spellcheck
69     */
70    protected $secondary = false;
71
72    /**
73     * Constructor.
74     *
75     * @param array  $spellcheck SOLR spellcheck information
76     * @param string $query      Spelling query that generated suggestions
77     *
78     * @return void
79     */
80    public function __construct(array $spellcheck, $query)
81    {
82        $this->terms = new ArrayObject();
83        // Solr 6.4 and before use an array of arrays with two elements, while
84        // from Solr 6.5 on the array is associative.
85        $list = isset($spellcheck[0]) ? new NamedList($spellcheck) : $spellcheck;
86        foreach ($list as $term => $info) {
87            if (is_array($info)) {
88                $this->terms->offsetSet($term, $info);
89            }
90        }
91        $this->query = $query;
92    }
93
94    /**
95     * Get spelling query.
96     *
97     * @return string
98     */
99    public function getQuery()
100    {
101        return $this->query;
102    }
103
104    /**
105     * Get secondary suggestions (or return false if none exist).
106     *
107     * @return Spellcheck|bool
108     */
109    public function getSecondary()
110    {
111        return $this->secondary;
112    }
113
114    /**
115     * Merge in other spellcheck information.
116     *
117     * @param Spellcheck $spellcheck Other spellcheck information
118     *
119     * @return void
120     */
121    public function mergeWith(Spellcheck $spellcheck)
122    {
123        // Merge primary suggestions:
124        $this->terms->uksort([$this, 'compareTermLength']);
125        foreach ($spellcheck as $term => $info) {
126            if (!$this->contains($term)) {
127                $this->terms->offsetSet($term, $info);
128            }
129        }
130
131        // Store secondary suggestions in case merge yielded non-useful
132        // result set:
133        if (!$this->secondary) {
134            $this->secondary = $spellcheck;
135        } else {
136            $this->secondary->mergeWith($spellcheck);
137        }
138    }
139
140    /// IteratorAggregate
141
142    /**
143     * Return aggregated iterator.
144     *
145     * @return Traversable
146     */
147    public function getIterator(): Traversable
148    {
149        return $this->terms->getIterator();
150    }
151
152    /// Countable
153
154    /**
155     * Return number of terms.
156     *
157     * @return int
158     */
159    public function count(): int
160    {
161        return $this->terms->count();
162    }
163
164    /// Internal API
165
166    /**
167     * Return true if we already have information for the term.
168     *
169     * @param string $term Term to check
170     *
171     * @return bool
172     */
173    protected function contains(string $term)
174    {
175        if ($this->terms->offsetExists($term)) {
176            return true;
177        }
178
179        $qTerm = preg_quote($term, '/');
180        $length = strlen($term);
181        foreach (array_keys((array)$this->terms) as $key) {
182            if ($length > strlen($key)) {
183                return false;
184            }
185            if (strstr($key, $term) && preg_match("/\b$qTerm\b/u", $key)) {
186                return true;
187            }
188        }
189        return false;
190    }
191
192    /**
193     * Compare length of two terms such that terms are sorted by descending
194     * length.
195     *
196     * This method belongs to the internal API but must be declared public in
197     * order to be used for ArrayObject::uksort().
198     *
199     * @param string $a First term
200     * @param string $b Second term
201     *
202     * @return integer
203     *
204     * @see http://www.php.net/manual/en/arrayobject.uksort.php
205     */
206    public function compareTermLength($a, $b)
207    {
208        return strlen($b) - strlen($a);
209    }
210}