Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
49.09% |
27 / 55 |
|
53.85% |
7 / 13 |
CRAP | |
0.00% |
0 / 1 |
SolrDefault | |
49.09% |
27 / 55 |
|
53.85% |
7 / 13 |
186.53 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
6 | |||
getFirstIndexed | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getHighlightDetails | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setHighlightDetails | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getRawAuthorHighlights | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
3 | |||
getSnippetCaption | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getHighlightedSnippet | |
12.50% |
2 / 16 |
|
0.00% |
0 / 1 |
63.26 | |||
getHighlightedTitle | |
66.67% |
2 / 3 |
|
0.00% |
0 / 1 |
2.15 | |||
attachSearchService | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getChildRecordCount | |
16.67% |
2 / 12 |
|
0.00% |
0 / 1 |
13.26 | |||
getContainerRecordID | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
3 | |||
getWorkKeys | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
explainEnabled | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | |
3 | /** |
4 | * Default model for Solr records -- used when a more specific model based on |
5 | * the record_format field cannot be found. |
6 | * |
7 | * PHP version 8 |
8 | * |
9 | * Copyright (C) Villanova University 2010, 2022. |
10 | * Copyright (C) The National Library of Finland 2019. |
11 | * |
12 | * This program is free software; you can redistribute it and/or modify |
13 | * it under the terms of the GNU General Public License version 2, |
14 | * as published by the Free Software Foundation. |
15 | * |
16 | * This program is distributed in the hope that it will be useful, |
17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
19 | * GNU General Public License for more details. |
20 | * |
21 | * You should have received a copy of the GNU General Public License |
22 | * along with this program; if not, write to the Free Software |
23 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
24 | * |
25 | * @category VuFind |
26 | * @package RecordDrivers |
27 | * @author Demian Katz <demian.katz@villanova.edu> |
28 | * @author Ere Maijala <ere.maijala@helsinki.fi> |
29 | * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License |
30 | * @link https://vufind.org/wiki/development:plugins:record_drivers Wiki |
31 | */ |
32 | |
33 | namespace VuFind\RecordDriver; |
34 | |
35 | use VuFindSearch\Command\SearchCommand; |
36 | |
37 | use function count; |
38 | use function in_array; |
39 | use function is_array; |
40 | |
41 | /** |
42 | * Default model for Solr records -- used when a more specific model based on |
43 | * the record_format field cannot be found. |
44 | * |
45 | * This should be used as the base class for all Solr-based record models. |
46 | * |
47 | * @category VuFind |
48 | * @package RecordDrivers |
49 | * @author Demian Katz <demian.katz@villanova.edu> |
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/wiki/development:plugins:record_drivers Wiki |
53 | * |
54 | * @SuppressWarnings(PHPMD.ExcessivePublicCount) |
55 | */ |
56 | class SolrDefault extends DefaultRecord implements |
57 | Feature\PreviousUniqueIdInterface, |
58 | Feature\VersionAwareInterface |
59 | { |
60 | use Feature\HierarchyAwareTrait; |
61 | use Feature\PreviousUniqueIdTrait; |
62 | use Feature\VersionAwareTrait; |
63 | |
64 | /** |
65 | * These Solr fields should be used for snippets if available (listed in order |
66 | * of preference). |
67 | * |
68 | * @var array |
69 | */ |
70 | protected $preferredSnippetFields = [ |
71 | 'contents', 'topic', |
72 | ]; |
73 | |
74 | /** |
75 | * These Solr fields should NEVER be used for snippets. (We exclude author |
76 | * and title because they are already covered by displayed fields; we exclude |
77 | * spelling because it contains lots of fields jammed together and may cause |
78 | * glitchy output; we exclude ID because random numbers are not helpful). |
79 | * |
80 | * @var array |
81 | */ |
82 | protected $forbiddenSnippetFields = [ |
83 | 'author', 'title', 'title_short', 'title_full', |
84 | 'title_full_unstemmed', 'title_auth', 'title_sub', 'spelling', 'id', |
85 | 'ctrlnum', 'author_variant', 'author2_variant', 'fullrecord', |
86 | 'work_keys_str_mv', |
87 | ]; |
88 | |
89 | /** |
90 | * These are captions corresponding with Solr fields for use when displaying |
91 | * snippets. |
92 | * |
93 | * @var array |
94 | */ |
95 | protected $snippetCaptions = []; |
96 | |
97 | /** |
98 | * Should we include snippets in search results? |
99 | * |
100 | * @var bool |
101 | */ |
102 | protected $snippet = false; |
103 | |
104 | /** |
105 | * Highlighting details |
106 | * |
107 | * @var array |
108 | */ |
109 | protected $highlightDetails = []; |
110 | |
111 | /** |
112 | * Should we use hierarchy fields for simple container-child records linking? |
113 | * |
114 | * @var bool |
115 | */ |
116 | protected $containerLinking = false; |
117 | |
118 | /** |
119 | * Search results plugin manager |
120 | * |
121 | * @var \VuFindSearch\Service |
122 | */ |
123 | protected $searchService = null; |
124 | |
125 | /** |
126 | * If the explain feature is enabled |
127 | * |
128 | * @var bool |
129 | */ |
130 | protected $explainEnabled = false; |
131 | |
132 | /** |
133 | * Constructor |
134 | * |
135 | * @param \Laminas\Config\Config $mainConfig VuFind main configuration (omit |
136 | * for built-in defaults) |
137 | * @param \Laminas\Config\Config $recordConfig Record-specific configuration |
138 | * file (omit to use $mainConfig as $recordConfig) |
139 | * @param \Laminas\Config\Config $searchSettings Search-specific configuration |
140 | * file |
141 | */ |
142 | public function __construct( |
143 | $mainConfig = null, |
144 | $recordConfig = null, |
145 | $searchSettings = null |
146 | ) { |
147 | $this->setSourceIdentifiers('Solr'); |
148 | // Load snippet settings: |
149 | $this->snippet = !isset($searchSettings->General->snippets) |
150 | ? false : $searchSettings->General->snippets; |
151 | if ( |
152 | isset($searchSettings->Snippet_Captions) |
153 | && count($searchSettings->Snippet_Captions) > 0 |
154 | ) { |
155 | foreach ($searchSettings->Snippet_Captions as $key => $value) { |
156 | $this->snippetCaptions[$key] = $value; |
157 | } |
158 | } |
159 | // Container-contents linking |
160 | $this->containerLinking |
161 | = !isset($mainConfig->Hierarchy->simpleContainerLinks) |
162 | ? false : $mainConfig->Hierarchy->simpleContainerLinks; |
163 | |
164 | $this->explainEnabled = $searchSettings->Explain->enabled ?? false; |
165 | |
166 | parent::__construct($mainConfig, $recordConfig, $searchSettings); |
167 | } |
168 | |
169 | /** |
170 | * Get the date this record was first indexed (if set). |
171 | * |
172 | * @return string |
173 | */ |
174 | public function getFirstIndexed() |
175 | { |
176 | return $this->fields['first_indexed'] ?? ''; |
177 | } |
178 | |
179 | /** |
180 | * Get highlighting details from the object. |
181 | * |
182 | * @return array |
183 | */ |
184 | public function getHighlightDetails() |
185 | { |
186 | return $this->highlightDetails; |
187 | } |
188 | |
189 | /** |
190 | * Add highlighting details to the object. |
191 | * |
192 | * @param array $details Details to add |
193 | * |
194 | * @return void |
195 | */ |
196 | public function setHighlightDetails($details) |
197 | { |
198 | $this->highlightDetails = $details; |
199 | } |
200 | |
201 | /** |
202 | * Get highlighted author data, if available. |
203 | * |
204 | * @return array |
205 | */ |
206 | public function getRawAuthorHighlights() |
207 | { |
208 | // Don't check for highlighted values if highlighting is disabled: |
209 | return ($this->highlight && isset($this->highlightDetails['author'])) |
210 | ? $this->highlightDetails['author'] : []; |
211 | } |
212 | |
213 | /** |
214 | * Given a Solr field name, return an appropriate caption. |
215 | * |
216 | * @param string $field Solr field name |
217 | * |
218 | * @return mixed Caption if found, false if none available. |
219 | */ |
220 | public function getSnippetCaption($field) |
221 | { |
222 | return $this->snippetCaptions[$field] ?? false; |
223 | } |
224 | |
225 | /** |
226 | * Pick one line from the highlighted text (if any) to use as a snippet. |
227 | * |
228 | * @return mixed False if no snippet found, otherwise associative array |
229 | * with 'snippet' and 'caption' keys. |
230 | */ |
231 | public function getHighlightedSnippet() |
232 | { |
233 | // Only process snippets if the setting is enabled: |
234 | if ($this->snippet) { |
235 | // First check for preferred fields: |
236 | foreach ($this->preferredSnippetFields as $current) { |
237 | if (isset($this->highlightDetails[$current][0])) { |
238 | return [ |
239 | 'snippet' => $this->highlightDetails[$current][0], |
240 | 'caption' => $this->getSnippetCaption($current), |
241 | ]; |
242 | } |
243 | } |
244 | |
245 | // No preferred field found, so try for a non-forbidden field: |
246 | if ( |
247 | isset($this->highlightDetails) |
248 | && is_array($this->highlightDetails) |
249 | ) { |
250 | foreach ($this->highlightDetails as $key => $value) { |
251 | if ($value && !in_array($key, $this->forbiddenSnippetFields)) { |
252 | return [ |
253 | 'snippet' => $value[0], |
254 | 'caption' => $this->getSnippetCaption($key), |
255 | ]; |
256 | } |
257 | } |
258 | } |
259 | } |
260 | |
261 | // If we got this far, no snippet was found: |
262 | return false; |
263 | } |
264 | |
265 | /** |
266 | * Get a highlighted title string, if available. |
267 | * |
268 | * @return string |
269 | */ |
270 | public function getHighlightedTitle() |
271 | { |
272 | // Don't check for highlighted values if highlighting is disabled: |
273 | if (!$this->highlight) { |
274 | return ''; |
275 | } |
276 | return $this->highlightDetails['title'][0] ?? ''; |
277 | } |
278 | |
279 | /** |
280 | * Attach a Search Results Plugin Manager connection and related logic to |
281 | * the driver |
282 | * |
283 | * @param \VuFindSearch\Service $service Search Service Manager |
284 | * |
285 | * @return void |
286 | */ |
287 | public function attachSearchService(\VuFindSearch\Service $service) |
288 | { |
289 | $this->searchService = $service; |
290 | } |
291 | |
292 | /** |
293 | * Get the number of child records belonging to this record |
294 | * |
295 | * @return int Number of records |
296 | */ |
297 | public function getChildRecordCount() |
298 | { |
299 | // Shortcut: if this record is not the top record, let's not find out the |
300 | // count. This assumes that contained records cannot contain more records. |
301 | if ( |
302 | !$this->containerLinking |
303 | || empty($this->fields['is_hierarchy_id']) |
304 | || null === $this->searchService |
305 | ) { |
306 | return 0; |
307 | } |
308 | |
309 | $safeId = addcslashes($this->fields['is_hierarchy_id'], '"'); |
310 | $query = new \VuFindSearch\Query\Query( |
311 | 'hierarchy_parent_id:"' . $safeId . '"' |
312 | ); |
313 | // Disable highlighting for efficiency; not needed here: |
314 | $params = new \VuFindSearch\ParamBag(['hl' => ['false']]); |
315 | $command = new SearchCommand($this->sourceIdentifier, $query, 0, 0, $params); |
316 | return $this->searchService |
317 | ->invoke($command)->getResult()->getTotal(); |
318 | } |
319 | |
320 | /** |
321 | * Get the container record id. |
322 | * |
323 | * @return string Container record id (empty string if none) |
324 | */ |
325 | public function getContainerRecordID() |
326 | { |
327 | return $this->containerLinking |
328 | && !empty($this->fields['hierarchy_parent_id']) |
329 | ? $this->fields['hierarchy_parent_id'][0] : ''; |
330 | } |
331 | |
332 | /** |
333 | * Get work identification keys |
334 | * |
335 | * @return array |
336 | */ |
337 | public function getWorkKeys() |
338 | { |
339 | return $this->fields['work_keys_str_mv'] ?? []; |
340 | } |
341 | |
342 | /** |
343 | * Get if the explain features is enabled. |
344 | * |
345 | * @return bool |
346 | */ |
347 | public function explainEnabled() |
348 | { |
349 | return $this->explainEnabled; |
350 | } |
351 | } |