Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
44.53% |
57 / 128 |
|
17.65% |
3 / 17 |
CRAP | |
0.00% |
0 / 1 |
SearchRequestModel | |
44.53% |
57 / 128 |
|
17.65% |
3 / 17 |
1034.99 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
formatDateLimiter | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
42 | |||
setParameters | |
23.53% |
4 / 17 |
|
0.00% |
0 / 1 |
54.72 | |||
convertToQueryString | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
convertToQueryStringParameterArray | |
66.67% |
20 / 30 |
|
0.00% |
0 / 1 |
37.33 | |||
convertToSearchRequestJSON | |
56.60% |
30 / 53 |
|
0.00% |
0 / 1 |
66.23 | |||
isParameterIndexed | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getIndexedParameterName | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
addAction | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
addQuery | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
addLimiter | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
addExpander | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
addfilter | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
escapeSpecialCharacters | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
escapeSpecialCharactersForActions | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
__get | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
6 | |||
__set | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 |
1 | <?php |
2 | |
3 | /** |
4 | * EBSCO EDS API Search Model |
5 | * |
6 | * PHP version 8 |
7 | * |
8 | * Copyright (C) Serials Solutions 2011. |
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 EBSCOIndustries |
24 | * @package EBSCO |
25 | * @author Michelle Milton <mmilton@epnet.com> |
26 | * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License |
27 | * @link https://vufind.org |
28 | */ |
29 | |
30 | namespace VuFindSearch\Backend\EDS; |
31 | |
32 | use function count; |
33 | use function intval; |
34 | use function strlen; |
35 | |
36 | /** |
37 | * EBSCO EDS API Search Model |
38 | * |
39 | * @category EBSCOIndustries |
40 | * @package EBSCO |
41 | * @author Michelle Milton <mmilton@epnet.com> |
42 | * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License |
43 | * @link https://vufind.org |
44 | */ |
45 | class SearchRequestModel |
46 | { |
47 | /** |
48 | * What to search for, formatted as [{boolean operator},][{field code}:]{term} |
49 | * |
50 | * @var array |
51 | */ |
52 | protected $query = []; |
53 | |
54 | /** |
55 | * Whether or not to return facets with the search results. valid values are |
56 | * 'y' or 'n' |
57 | * |
58 | * @var string |
59 | */ |
60 | protected $includeFacets; |
61 | |
62 | /** |
63 | * Array of filters to apply to the search |
64 | * |
65 | * @var array |
66 | */ |
67 | protected $facetFilters = []; |
68 | |
69 | /** |
70 | * Sort option to apply |
71 | * |
72 | * @var string |
73 | */ |
74 | protected $sort; |
75 | |
76 | /** |
77 | * Options to limit the results by |
78 | * |
79 | * @var array |
80 | */ |
81 | protected $limiters = []; |
82 | |
83 | /** |
84 | * Mode to be effective in the search |
85 | * |
86 | * @var string |
87 | */ |
88 | protected $searchMode; |
89 | |
90 | /** |
91 | * Expanders to use. Comma separated. |
92 | * |
93 | * @var array |
94 | */ |
95 | protected $expanders = []; |
96 | |
97 | /** |
98 | * Requested level of detail to return the results with |
99 | * |
100 | * @var string |
101 | */ |
102 | protected $view; |
103 | |
104 | /** |
105 | * Number of records to return |
106 | * |
107 | * @var int |
108 | */ |
109 | protected $resultsPerPage; |
110 | |
111 | /** |
112 | * Page number of records to return. This is used in conjunction with the |
113 | * {@link $resultsPerPage} to determine the set of records to return. |
114 | * |
115 | * @var int |
116 | */ |
117 | protected $pageNumber; |
118 | |
119 | /** |
120 | * Whether or not to highlight the search term in the results. |
121 | * |
122 | * @var bool |
123 | */ |
124 | protected $highlight; |
125 | |
126 | /** |
127 | * Collection of user actions to apply to current request |
128 | * |
129 | * @var array |
130 | */ |
131 | protected $actions = []; |
132 | |
133 | /** |
134 | * Constructor |
135 | * |
136 | * Sets up the EDS API Search Request model |
137 | * |
138 | * @param array $parameters parameters to populate request |
139 | */ |
140 | public function __construct($parameters = []) |
141 | { |
142 | $this->setParameters($parameters); |
143 | } |
144 | |
145 | /** |
146 | * Format a date limiter |
147 | * |
148 | * @param string $filter Filter value |
149 | * |
150 | * @return string |
151 | */ |
152 | protected function formatDateLimiter($filter) |
153 | { |
154 | // PublicationDate:[xxxx TO xxxx] |
155 | $dates = substr($filter, 17); |
156 | $dates = substr($dates, 0, strlen($dates) - 1); |
157 | $parts = explode(' TO ', $dates, 2); |
158 | $start = $end = null; |
159 | if (count($parts) == 2) { |
160 | $start = trim($parts[0]); |
161 | $end = trim($parts[1]); |
162 | } |
163 | if ('*' == $start || null == $start) { |
164 | $start = '0000'; |
165 | } |
166 | if ('*' == $end || null == $end) { |
167 | $end = date('Y') + 1; |
168 | } |
169 | return "DT1:$start-01/$end-12"; |
170 | } |
171 | |
172 | /** |
173 | * Set properties from parameters |
174 | * |
175 | * @param array $parameters Parameters to set |
176 | * |
177 | * @return void |
178 | */ |
179 | public function setParameters($parameters = []) |
180 | { |
181 | foreach ($parameters as $key => $values) { |
182 | switch ($key) { |
183 | case 'filters': |
184 | $cnt = 1; |
185 | foreach ($values as $filter) { |
186 | if (str_starts_with($filter, 'LIMIT|')) { |
187 | $this->addLimiter(substr($filter, 6)); |
188 | } elseif (str_starts_with($filter, 'EXPAND:')) { |
189 | $this->addExpander(substr($filter, 7)); |
190 | } elseif (str_starts_with($filter, 'SEARCHMODE:')) { |
191 | $this->searchMode = substr($filter, 11, null); |
192 | } elseif (str_starts_with($filter, 'PublicationDate')) { |
193 | $this->addLimiter($this->formatDateLimiter($filter)); |
194 | } else { |
195 | $this->addFilter("$cnt,$filter"); |
196 | $cnt++; |
197 | } |
198 | } |
199 | break; |
200 | default: |
201 | if (property_exists($this, $key)) { |
202 | $this->$key = $values; |
203 | } |
204 | } |
205 | } |
206 | } |
207 | |
208 | /** |
209 | * Converts properties to a querystring to send to the EdsAPI |
210 | * |
211 | * @return string |
212 | */ |
213 | public function convertToQueryString() |
214 | { |
215 | return http_build_query($this->convertToQueryStringParameterArray()); |
216 | } |
217 | |
218 | /** |
219 | * Converts properties to a querystring to send to the EdsAPI |
220 | * |
221 | * @return string |
222 | */ |
223 | public function convertToQueryStringParameterArray() |
224 | { |
225 | $qs = []; |
226 | if (isset($this->query) && 0 < count($this->query)) { |
227 | $formatQuery = function ($json) { |
228 | $query = json_decode($json, true); |
229 | $queryString = empty($query['bool']) |
230 | ? '' : ($query['bool'] . ','); |
231 | if (!empty($query['field'])) { |
232 | $queryString .= $query['field'] . ':'; |
233 | } |
234 | $queryString .= static::escapeSpecialCharacters($query['term']); |
235 | return $queryString; |
236 | }; |
237 | $qs['query-x'] = array_map($formatQuery, $this->query); |
238 | } |
239 | |
240 | if (isset($this->facetFilters) && 0 < count($this->facetFilters)) { |
241 | $formatFilter = function ($raw) { |
242 | [$field, $value] = explode(':', $raw, 2); |
243 | return $field . ':' . static::escapeSpecialCharacters($value); |
244 | }; |
245 | $qs['facetfilter'] = array_map($formatFilter, $this->facetFilters); |
246 | } |
247 | |
248 | if (isset($this->limiters) && 0 < count($this->limiters)) { |
249 | $qs['limiter'] = $this->limiters; |
250 | } |
251 | |
252 | if (isset($this->actions) && 0 < count($this->actions)) { |
253 | $qs['action-x'] = $this->actions; |
254 | } |
255 | |
256 | if (isset($this->includeFacets)) { |
257 | $qs['includefacets'] = $this->includeFacets; |
258 | } |
259 | |
260 | if (isset($this->sort)) { |
261 | $qs['sort'] = $this->sort; |
262 | } |
263 | |
264 | if (isset($this->searchMode)) { |
265 | $qs['searchmode'] = $this->searchMode; |
266 | } |
267 | |
268 | if (isset($this->expanders) && 0 < count($this->expanders)) { |
269 | $qs['expander'] = implode(',', $this->expanders); |
270 | } |
271 | |
272 | if (isset($this->view)) { |
273 | $qs['view'] = $this->view; |
274 | } |
275 | |
276 | if (isset($this->resultsPerPage)) { |
277 | $qs['resultsperpage'] = $this->resultsPerPage; |
278 | } |
279 | |
280 | if (isset($this->pageNumber)) { |
281 | $qs['pagenumber'] = $this->pageNumber; |
282 | } |
283 | |
284 | $highlightVal = isset($this->highlight) && $this->highlight ? 'y' : 'n'; |
285 | $qs['highlight'] = $highlightVal; |
286 | |
287 | return $qs; |
288 | } |
289 | |
290 | /** |
291 | * Converts properties to a search request JSON document to send to the EdsAPI |
292 | * |
293 | * @return string |
294 | */ |
295 | public function convertToSearchRequestJSON() |
296 | { |
297 | $json = new \stdClass(); |
298 | $json->SearchCriteria = new \stdClass(); |
299 | $json->RetrievalCriteria = new \stdClass(); |
300 | $json->Actions = null; |
301 | if (isset($this->query) && 0 < count($this->query)) { |
302 | $json->SearchCriteria->Queries = []; |
303 | foreach ($this->query as $queryJson) { |
304 | $query = json_decode($queryJson, true); |
305 | $queryObj = new \stdClass(); |
306 | if (!empty($query['bool'])) { |
307 | $queryObj->BooleanOperator = $query['bool']; |
308 | } |
309 | if (!empty($query['field'])) { |
310 | $queryObj->FieldCode = $query['field']; |
311 | } |
312 | $queryObj->Term = $query['term']; |
313 | $json->SearchCriteria->Queries[] = $queryObj; |
314 | } |
315 | } |
316 | |
317 | if (isset($this->facetFilters) && 0 < count($this->facetFilters)) { |
318 | $json->SearchCriteria->FacetFilters = []; |
319 | foreach ($this->facetFilters as $currentFilter) { |
320 | [$id, $filter] = explode(',', $currentFilter, 2); |
321 | [$field, $value] = explode(':', $filter, 2); |
322 | $filterObj = new \stdClass(); |
323 | $filterObj->FilterId = $id; |
324 | $valueObj = new \stdClass(); |
325 | $valueObj->Id = $field; |
326 | $valueObj->Value = $value; |
327 | $filterObj->FacetValues = [$valueObj]; |
328 | $json->SearchCriteria->FacetFilters[] = $filterObj; |
329 | } |
330 | } |
331 | |
332 | if (isset($this->limiters) && 0 < count($this->limiters)) { |
333 | $json->SearchCriteria->Limiters = []; |
334 | foreach ($this->limiters as $limiter) { |
335 | [$id, $values] = explode(':', $limiter, 2); |
336 | $limiterObj = new \stdClass(); |
337 | $limiterObj->Id = $id; |
338 | $limiterObj->Values = explode(',', $values); |
339 | $json->SearchCriteria->Limiters[] = $limiterObj; |
340 | } |
341 | } |
342 | |
343 | if (isset($this->actions) && 0 < count($this->actions)) { |
344 | $json->Actions = $this->actions; |
345 | } |
346 | |
347 | $json->SearchCriteria->IncludeFacets = $this->includeFacets ?? 'y'; |
348 | |
349 | if (isset($this->sort)) { |
350 | $json->SearchCriteria->Sort = $this->sort; |
351 | } |
352 | |
353 | if (isset($this->searchMode)) { |
354 | $json->SearchCriteria->SearchMode = $this->searchMode; |
355 | } |
356 | |
357 | if (isset($this->expanders) && 0 < count($this->expanders)) { |
358 | $json->SearchCriteria->Expanders = $this->expanders; |
359 | } |
360 | |
361 | if (isset($this->view)) { |
362 | $json->RetrievalCriteria->View = $this->view; |
363 | } |
364 | |
365 | if (isset($this->resultsPerPage)) { |
366 | $json->RetrievalCriteria->ResultsPerPage = intval($this->resultsPerPage); |
367 | } |
368 | |
369 | if (isset($this->pageNumber)) { |
370 | $json->RetrievalCriteria->PageNumber = intval($this->pageNumber); |
371 | } |
372 | |
373 | $highlightVal = isset($this->highlight) && $this->highlight ? 'y' : 'n'; |
374 | $json->RetrievalCriteria->Highlight = $highlightVal; |
375 | |
376 | return json_encode($json, JSON_PRETTY_PRINT); |
377 | } |
378 | |
379 | /** |
380 | * Determines whether or not a querystring parameter is indexed |
381 | * |
382 | * @param string $value parameter key to check |
383 | * |
384 | * @return bool |
385 | */ |
386 | public static function isParameterIndexed($value) |
387 | { |
388 | // Indexed parameter names end with '-x' |
389 | return str_ends_with($value, '-x'); |
390 | } |
391 | |
392 | /** |
393 | * Get the querystring parameter name of an indexed parameter to send to the Eds |
394 | * Api |
395 | * |
396 | * @param string $value Indexed parameter name |
397 | * |
398 | * @return string |
399 | */ |
400 | public static function getIndexedParameterName($value) |
401 | { |
402 | // Indexed parameter names end with '-x' |
403 | return substr($value, 0, -2); |
404 | } |
405 | |
406 | /** |
407 | * Add a new action |
408 | * |
409 | * @param string $action Action to add to the existing collection of actions |
410 | * |
411 | * @return void |
412 | */ |
413 | public function addAction($action) |
414 | { |
415 | $this->actions[] = $action; |
416 | } |
417 | |
418 | /** |
419 | * Add a new query expression |
420 | * |
421 | * @param string $query Query expression to add |
422 | * |
423 | * @return void |
424 | */ |
425 | public function addQuery($query) |
426 | { |
427 | $this->query[] = $query; |
428 | } |
429 | |
430 | /** |
431 | * Add a new limiter |
432 | * |
433 | * @param string $limiter Limiter to add |
434 | * |
435 | * @return void |
436 | */ |
437 | public function addLimiter($limiter) |
438 | { |
439 | $this->limiters[] = $limiter; |
440 | } |
441 | |
442 | /** |
443 | * Add a new expander |
444 | * |
445 | * @param string $expander Expander to add |
446 | * |
447 | * @return void |
448 | */ |
449 | public function addExpander($expander) |
450 | { |
451 | $this->expanders[] = $expander; |
452 | } |
453 | |
454 | /** |
455 | * Add a new facet filter |
456 | * |
457 | * @param string $facetFilter Facet Filter to add |
458 | * |
459 | * @return void |
460 | */ |
461 | public function addfilter($facetFilter) |
462 | { |
463 | $this->facetFilters[] = $facetFilter; |
464 | } |
465 | |
466 | /** |
467 | * Escape characters that may be present in the parameter syntax |
468 | * |
469 | * @param string $value The value to escape |
470 | * |
471 | * @return string The value with special characters escaped |
472 | */ |
473 | public static function escapeSpecialCharacters($value) |
474 | { |
475 | return addcslashes($value, ':,'); |
476 | } |
477 | |
478 | /** |
479 | * Escape characters that may be present in the action parameter syntax |
480 | * |
481 | * @param string $value The value to escape |
482 | * |
483 | * @return string The value with special characters escaped |
484 | */ |
485 | public static function escapeSpecialCharactersForActions($value) |
486 | { |
487 | return addcslashes($value, ':,()'); |
488 | } |
489 | |
490 | /** |
491 | * Magic getter |
492 | * |
493 | * @param string $property Property to retrieve |
494 | * |
495 | * @return mixed |
496 | */ |
497 | public function __get($property) |
498 | { |
499 | if (property_exists($this, $property)) { |
500 | return $this->$property; |
501 | } |
502 | } |
503 | |
504 | /** |
505 | * Magic setter |
506 | * |
507 | * @param string $property Property to set |
508 | * @param mixed $value Value to set |
509 | * |
510 | * @return SearchRequestModel |
511 | */ |
512 | public function __set($property, $value) |
513 | { |
514 | if (property_exists($this, $property)) { |
515 | $this->$property = $value; |
516 | } |
517 | |
518 | return $this; |
519 | } |
520 | } |