Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
14.07% |
46 / 327 |
|
15.00% |
6 / 40 |
CRAP | |
0.00% |
0 / 1 |
Connection | |
14.07% |
46 / 327 |
|
15.00% |
6 / 40 |
15007.52 | |
0.00% |
0 / 1 |
__construct | |
75.00% |
6 / 8 |
|
0.00% |
0 / 1 |
3.14 | |||
setHoldConfig | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
getDriverClass | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
initializeDriver | |
42.86% |
3 / 7 |
|
0.00% |
0 / 1 |
4.68 | |||
hasNoILSFailover | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
failOverToNoILS | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
20 | |||
getDriver | |
50.00% |
4 / 8 |
|
0.00% |
0 / 1 |
10.50 | |||
setDriver | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
getDriverConfig | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
checkFunction | |
53.33% |
8 / 15 |
|
0.00% |
0 / 1 |
5.63 | |||
checkMethodHolds | |
0.00% |
0 / 28 |
|
0.00% |
0 / 1 |
110 | |||
checkMethodcancelHolds | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
90 | |||
checkMethodRenewals | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
90 | |||
checkMethodStorageRetrievalRequests | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
30 | |||
checkMethodcancelStorageRetrievalRequests | |
0.00% |
0 / 22 |
|
0.00% |
0 / 1 |
56 | |||
checkMethodILLRequests | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
42 | |||
checkMethodcancelILLRequests | |
0.00% |
0 / 22 |
|
0.00% |
0 / 1 |
56 | |||
checkMethodchangePassword | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
12 | |||
checkMethodgetMyTransactions | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
12 | |||
checkMethodgetMyTransactionHistory | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
12 | |||
checkMethodpurgeTransactionHistory | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
12 | |||
checkMethodpatronLogin | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getHelpText | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
checkRequestIsValid | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
20 | |||
checkStorageRetrievalRequestIsValid | |
0.00% |
0 / 15 |
|
0.00% |
0 / 1 |
20 | |||
checkILLRequestIsValid | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
20 | |||
getHoldsMode | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getOfflineMode | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
30 | |||
getTitleHoldsMode | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
hasHoldings | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
20 | |||
loginIsHidden | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
20 | |||
checkCapability | |
46.15% |
6 / 13 |
|
0.00% |
0 / 1 |
8.90 | |||
getHoldingsTextFieldNames | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
getPasswordPolicy | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
2 | |||
getMyTransactions | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
6 | |||
getHolding | |
0.00% |
0 / 30 |
|
0.00% |
0 / 1 |
132 | |||
getStatus | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
getStatuses | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
getStatusParser | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
12 | |||
__call | |
41.67% |
5 / 12 |
|
0.00% |
0 / 1 |
7.18 |
1 | <?php |
2 | |
3 | /** |
4 | * Catalog Connection Class |
5 | * |
6 | * This wrapper works with a driver class to pass information from the ILS to |
7 | * VuFind. |
8 | * |
9 | * PHP version 8 |
10 | * |
11 | * Copyright (C) Villanova University 2007. |
12 | * |
13 | * This program is free software; you can redistribute it and/or modify |
14 | * it under the terms of the GNU General Public License version 2, |
15 | * as published by the Free Software Foundation. |
16 | * |
17 | * This program is distributed in the hope that it will be useful, |
18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
20 | * GNU General Public License for more details. |
21 | * |
22 | * You should have received a copy of the GNU General Public License |
23 | * along with this program; if not, write to the Free Software |
24 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
25 | * |
26 | * @category VuFind |
27 | * @package ILS_Drivers |
28 | * @author Andrew S. Nagy <vufind-tech@lists.sourceforge.net> |
29 | * @author Demian Katz <demian.katz@villanova.edu> |
30 | * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License |
31 | * @link https://vufind.org/wiki/development:plugins:ils_drivers Wiki |
32 | */ |
33 | |
34 | namespace VuFind\ILS; |
35 | |
36 | use Laminas\Log\LoggerAwareInterface; |
37 | use VuFind\Exception\BadConfig; |
38 | use VuFind\Exception\ILS as ILSException; |
39 | use VuFind\I18n\Translator\TranslatorAwareInterface; |
40 | use VuFind\ILS\Driver\DriverInterface; |
41 | use VuFind\ILS\Logic\AvailabilityStatus; |
42 | |
43 | use function call_user_func_array; |
44 | use function count; |
45 | use function func_get_args; |
46 | use function get_class; |
47 | use function intval; |
48 | use function is_array; |
49 | use function is_callable; |
50 | use function is_object; |
51 | |
52 | /** |
53 | * Catalog Connection Class |
54 | * |
55 | * This wrapper works with a driver class to pass information from the ILS to |
56 | * VuFind. |
57 | * |
58 | * @category VuFind |
59 | * @package ILS_Drivers |
60 | * @author Andrew S. Nagy <vufind-tech@lists.sourceforge.net> |
61 | * @author Demian Katz <demian.katz@villanova.edu> |
62 | * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License |
63 | * @link https://vufind.org/wiki/development:plugins:ils_drivers Wiki |
64 | */ |
65 | class Connection implements TranslatorAwareInterface, LoggerAwareInterface |
66 | { |
67 | use \VuFind\I18n\Translator\TranslatorAwareTrait; |
68 | use \VuFind\Log\LoggerAwareTrait; |
69 | |
70 | /** |
71 | * Has the driver been initialized yet? |
72 | * |
73 | * @var bool |
74 | */ |
75 | protected $driverInitialized = false; |
76 | |
77 | /** |
78 | * The object of the appropriate driver. |
79 | * |
80 | * @var object |
81 | */ |
82 | protected $driver = null; |
83 | |
84 | /** |
85 | * ILS configuration |
86 | * |
87 | * @var \Laminas\Config\Config |
88 | */ |
89 | protected $config; |
90 | |
91 | /** |
92 | * Holds mode |
93 | * |
94 | * @var string |
95 | */ |
96 | protected $holdsMode = 'disabled'; |
97 | |
98 | /** |
99 | * Title-level holds mode |
100 | * |
101 | * @var string |
102 | */ |
103 | protected $titleHoldsMode = 'disabled'; |
104 | |
105 | /** |
106 | * Driver plugin manager |
107 | * |
108 | * @var \VuFind\ILS\Driver\PluginManager |
109 | */ |
110 | protected $driverManager; |
111 | |
112 | /** |
113 | * Configuration loader |
114 | * |
115 | * @var \VuFind\Config\PluginManager |
116 | */ |
117 | protected $configReader; |
118 | |
119 | /** |
120 | * Is the current ILS driver failing? |
121 | * |
122 | * @var bool |
123 | */ |
124 | protected $failing = false; |
125 | |
126 | /** |
127 | * Request object |
128 | * |
129 | * @var \Laminas\Http\Request |
130 | */ |
131 | protected $request; |
132 | |
133 | /** |
134 | * Constructor |
135 | * |
136 | * @param \Laminas\Config\Config $config Configuration |
137 | * representing the [Catalog] section of config.ini |
138 | * @param \VuFind\ILS\Driver\PluginManager $driverManager Driver plugin manager |
139 | * @param \VuFind\Config\PluginManager $configReader Configuration loader |
140 | * @param \Laminas\Http\Request $request Request object |
141 | */ |
142 | public function __construct( |
143 | \Laminas\Config\Config $config, |
144 | \VuFind\ILS\Driver\PluginManager $driverManager, |
145 | \VuFind\Config\PluginManager $configReader, |
146 | \Laminas\Http\Request $request = null |
147 | ) { |
148 | if (!isset($config->driver)) { |
149 | throw new \Exception('ILS driver setting missing.'); |
150 | } |
151 | if (!$driverManager->has($config->driver)) { |
152 | throw new \Exception('ILS driver missing: ' . $config->driver); |
153 | } |
154 | $this->config = $config; |
155 | $this->configReader = $configReader; |
156 | $this->driverManager = $driverManager; |
157 | $this->request = $request; |
158 | } |
159 | |
160 | /** |
161 | * Set the hold configuration for the connection. |
162 | * |
163 | * @param \VuFind\ILS\HoldSettings $settings Hold settings |
164 | * |
165 | * @return Connection |
166 | */ |
167 | public function setHoldConfig($settings) |
168 | { |
169 | $this->holdsMode = $settings->getHoldsMode(); |
170 | $this->titleHoldsMode = $settings->getTitleHoldsMode(); |
171 | return $this; |
172 | } |
173 | |
174 | /** |
175 | * Get class name of the driver object. |
176 | * |
177 | * @return string |
178 | */ |
179 | public function getDriverClass() |
180 | { |
181 | return get_class($this->getDriver(false)); |
182 | } |
183 | |
184 | /** |
185 | * Initialize the ILS driver. |
186 | * |
187 | * @return void |
188 | */ |
189 | protected function initializeDriver() |
190 | { |
191 | try { |
192 | $this->driver->setConfig($this->getDriverConfig()); |
193 | } catch (\Exception $e) { |
194 | // Any errors thrown during configuration should be cast to BadConfig |
195 | // so we can handle them differently from other runtime problems. |
196 | throw $e instanceof BadConfig |
197 | ? $e |
198 | : new BadConfig('Failure during configuration.', 0, $e); |
199 | } |
200 | $this->driver->init(); |
201 | $this->driverInitialized = true; |
202 | } |
203 | |
204 | /** |
205 | * Are we configured to fail over to the NoILS driver on error? |
206 | * |
207 | * @return bool |
208 | */ |
209 | protected function hasNoILSFailover() |
210 | { |
211 | // If we're configured to fail over to the NoILS driver, do so now: |
212 | return isset($this->config->loadNoILSOnFailure) |
213 | && $this->config->loadNoILSOnFailure; |
214 | } |
215 | |
216 | /** |
217 | * If configured, fail over to the NoILS driver and return true; otherwise, |
218 | * return false. |
219 | * |
220 | * @param \Exception $e The exception that triggered the failover. |
221 | * |
222 | * @return bool |
223 | */ |
224 | protected function failOverToNoILS(\Exception $e = null) |
225 | { |
226 | // If the exception is caused by a configuration error, the administrator |
227 | // needs to fix it, but failing over to NoILS will mask the error and cause |
228 | // confusion. We shouldn't do that! |
229 | if ($e instanceof BadConfig) { |
230 | return false; |
231 | } |
232 | |
233 | // If we got this far, we want to proceed with failover... |
234 | $this->failing = true; |
235 | |
236 | // Only fail over if we're configured to allow it and we haven't already |
237 | // done so! |
238 | if ($this->hasNoILSFailover()) { |
239 | $noILS = $this->driverManager->get('NoILS'); |
240 | if ($noILS::class != $this->getDriverClass()) { |
241 | $this->setDriver($noILS); |
242 | $this->initializeDriver(); |
243 | return true; |
244 | } |
245 | } |
246 | return false; |
247 | } |
248 | |
249 | /** |
250 | * Get access to the driver object. |
251 | * |
252 | * @param bool $init Should we initialize the driver (if necessary), or load it |
253 | * "as-is"? |
254 | * |
255 | * @throws \Exception |
256 | * @return object |
257 | */ |
258 | public function getDriver($init = true) |
259 | { |
260 | if (null === $this->driver) { |
261 | $this->setDriver($this->driverManager->get($this->config->driver)); |
262 | } |
263 | if (!$this->driverInitialized && $init) { |
264 | try { |
265 | $this->initializeDriver(); |
266 | } catch (\Exception $e) { |
267 | if (!$this->failOverToNoILS($e)) { |
268 | throw $e; |
269 | } |
270 | } |
271 | } |
272 | return $this->driver; |
273 | } |
274 | |
275 | /** |
276 | * Set a driver object. |
277 | * |
278 | * @param DriverInterface $driver Driver to set. |
279 | * @param bool $initialized Is this driver already initialized? |
280 | * |
281 | * @return void |
282 | */ |
283 | public function setDriver(DriverInterface $driver, $initialized = false) |
284 | { |
285 | $this->driverInitialized = $initialized; |
286 | $this->driver = $driver; |
287 | } |
288 | |
289 | /** |
290 | * Get configuration for the ILS driver. We will load an .ini file named |
291 | * after the driver class if it exists; otherwise we will return an empty |
292 | * array. |
293 | * |
294 | * @return array |
295 | */ |
296 | public function getDriverConfig() |
297 | { |
298 | // Determine config file name based on class name: |
299 | $parts = explode('\\', $this->getDriverClass()); |
300 | $config = $this->configReader->get(end($parts)); |
301 | return is_object($config) ? $config->toArray() : []; |
302 | } |
303 | |
304 | /** |
305 | * Check Function |
306 | * |
307 | * This is responsible for checking the driver configuration to determine |
308 | * if the system supports a particular function. |
309 | * |
310 | * @param string $function The name of the function to check. |
311 | * @param array $params (optional) An array of function-specific parameters |
312 | * |
313 | * @return mixed On success, an associative array with specific function keys |
314 | * and values; on failure, false. |
315 | */ |
316 | public function checkFunction($function, $params = null) |
317 | { |
318 | try { |
319 | // Extract the configuration from the driver if available: |
320 | $functionConfig = $this->checkCapability( |
321 | 'getConfig', |
322 | [$function, $params], |
323 | true |
324 | ) ? $this->getDriver()->getConfig($function, $params) : false; |
325 | |
326 | // See if we have a corresponding check method to analyze the response: |
327 | $checkMethod = 'checkMethod' . $function; |
328 | if (!method_exists($this, $checkMethod)) { |
329 | return false; |
330 | } |
331 | |
332 | // Send back the settings: |
333 | return $this->$checkMethod($functionConfig, $params); |
334 | } catch (ILSException $e) { |
335 | $this->logError( |
336 | "checkFunction($function) with params: " . $this->varDump($params) |
337 | . ' failed: ' . $e->getMessage() |
338 | ); |
339 | return false; |
340 | } |
341 | } |
342 | |
343 | /** |
344 | * Check Holds |
345 | * |
346 | * A support method for checkFunction(). This is responsible for checking |
347 | * the driver configuration to determine if the system supports Holds. |
348 | * |
349 | * @param array $functionConfig The Hold configuration values |
350 | * @param array $params An array of function-specific params (or null) |
351 | * |
352 | * @return mixed On success, an associative array with specific function keys |
353 | * and values either for placing holds via a form or a URL; on failure, false. |
354 | */ |
355 | protected function checkMethodHolds($functionConfig, $params) |
356 | { |
357 | $response = false; |
358 | |
359 | // We pass an array containing $params to checkCapability since $params |
360 | // should contain 'id' and 'patron' keys; this isn't exactly the same as |
361 | // the full parameter expected by placeHold() but should contain the |
362 | // necessary details for determining eligibility. |
363 | if ( |
364 | $this->getHoldsMode() != 'none' |
365 | && $this->checkCapability('placeHold', [$params ?: []]) |
366 | && isset($functionConfig['HMACKeys']) |
367 | ) { |
368 | $response = ['function' => 'placeHold']; |
369 | $response['HMACKeys'] = explode(':', $functionConfig['HMACKeys']); |
370 | if (isset($functionConfig['defaultRequiredDate'])) { |
371 | $response['defaultRequiredDate'] |
372 | = $functionConfig['defaultRequiredDate']; |
373 | } |
374 | if (isset($functionConfig['extraHoldFields'])) { |
375 | $response['extraHoldFields'] = $functionConfig['extraHoldFields']; |
376 | } |
377 | if (!empty($functionConfig['updateFields'])) { |
378 | $response['updateFields'] = array_map( |
379 | 'trim', |
380 | explode(':', $functionConfig['updateFields']) |
381 | ); |
382 | } |
383 | $response['helpText'] |
384 | = $this->getHelpText($functionConfig['helpText'] ?? ''); |
385 | $response['updateHelpText'] |
386 | = $this->getHelpText($functionConfig['updateHelpText'] ?? ''); |
387 | if (isset($functionConfig['consortium'])) { |
388 | $response['consortium'] = $functionConfig['consortium']; |
389 | } |
390 | $response['pickUpLocationCheckLimit'] |
391 | = intval($functionConfig['pickUpLocationCheckLimit'] ?? 0); |
392 | } else { |
393 | $id = $params['id'] ?? null; |
394 | if ($this->checkCapability('getHoldLink', [$id, []])) { |
395 | $response = ['function' => 'getHoldLink']; |
396 | } |
397 | } |
398 | return $response; |
399 | } |
400 | |
401 | /** |
402 | * Check Cancel Holds |
403 | * |
404 | * A support method for checkFunction(). This is responsible for checking |
405 | * the driver configuration to determine if the system supports Cancelling Holds. |
406 | * |
407 | * @param array $functionConfig The Cancel Hold configuration values |
408 | * @param array $params An array of function-specific params (or null) |
409 | * |
410 | * @return mixed On success, an associative array with specific function keys |
411 | * and values either for cancelling holds via a form or a URL; |
412 | * on failure, false. |
413 | * |
414 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) |
415 | */ |
416 | protected function checkMethodcancelHolds($functionConfig, $params) |
417 | { |
418 | $response = false; |
419 | |
420 | // We can't pass exactly accurate parameters to checkCapability in this |
421 | // context, so we'll just pass along $params as the best available |
422 | // approximation. |
423 | if ( |
424 | isset($this->config->cancel_holds_enabled) |
425 | && $this->config->cancel_holds_enabled == true |
426 | && $this->checkCapability('cancelHolds', [$params ?: []]) |
427 | ) { |
428 | $response = ['function' => 'cancelHolds']; |
429 | } elseif ( |
430 | isset($this->config->cancel_holds_enabled) |
431 | && $this->config->cancel_holds_enabled == true |
432 | && $this->checkCapability('getCancelHoldLink', [$params ?: []]) |
433 | ) { |
434 | $response = ['function' => 'getCancelHoldLink']; |
435 | } |
436 | return $response; |
437 | } |
438 | |
439 | /** |
440 | * Check Renewals |
441 | * |
442 | * A support method for checkFunction(). This is responsible for checking |
443 | * the driver configuration to determine if the system supports Renewing Items. |
444 | * |
445 | * @param array $functionConfig The Renewal configuration values |
446 | * @param array $params An array of function-specific params (or null) |
447 | * |
448 | * @return mixed On success, an associative array with specific function keys |
449 | * and values either for renewing items via a form or a URL; on failure, false. |
450 | * |
451 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) |
452 | */ |
453 | protected function checkMethodRenewals($functionConfig, $params) |
454 | { |
455 | $response = false; |
456 | |
457 | // We can't pass exactly accurate parameters to checkCapability in this |
458 | // context, so we'll just pass along $params as the best available |
459 | // approximation. |
460 | if ( |
461 | isset($this->config->renewals_enabled) |
462 | && $this->config->renewals_enabled == true |
463 | && $this->checkCapability('renewMyItems', [$params ?: []]) |
464 | ) { |
465 | $response = ['function' => 'renewMyItems']; |
466 | } elseif ( |
467 | isset($this->config->renewals_enabled) |
468 | && $this->config->renewals_enabled == true |
469 | && $this->checkCapability('renewMyItemsLink', [$params ?: []]) |
470 | ) { |
471 | $response = ['function' => 'renewMyItemsLink']; |
472 | } |
473 | return $response; |
474 | } |
475 | |
476 | /** |
477 | * Check Storage Retrieval Request |
478 | * |
479 | * A support method for checkFunction(). This is responsible for checking |
480 | * the driver configuration to determine if the system supports storage |
481 | * retrieval requests. |
482 | * |
483 | * @param array $functionConfig The storage retrieval request configuration |
484 | * values |
485 | * @param array $params An array of function-specific params (or null) |
486 | * |
487 | * @return mixed On success, an associative array with specific function keys |
488 | * and values either for placing requests via a form; on failure, false. |
489 | */ |
490 | protected function checkMethodStorageRetrievalRequests($functionConfig, $params) |
491 | { |
492 | $response = false; |
493 | |
494 | // $params doesn't include all of the keys used by |
495 | // placeStorageRetrievalRequest, but it is the best we can do in the context. |
496 | $check = $this->checkCapability( |
497 | 'placeStorageRetrievalRequest', |
498 | [$params ?: []] |
499 | ); |
500 | if ($check && isset($functionConfig['HMACKeys'])) { |
501 | $response = ['function' => 'placeStorageRetrievalRequest']; |
502 | $response['HMACKeys'] = explode(':', $functionConfig['HMACKeys']); |
503 | if (isset($functionConfig['extraFields'])) { |
504 | $response['extraFields'] = $functionConfig['extraFields']; |
505 | } |
506 | $response['helpText'] |
507 | = $this->getHelpText($functionConfig['helpText'] ?? ''); |
508 | } |
509 | return $response; |
510 | } |
511 | |
512 | /** |
513 | * Check Cancel Storage Retrieval Requests |
514 | * |
515 | * A support method for checkFunction(). This is responsible for checking |
516 | * the driver configuration to determine if the system supports Cancelling |
517 | * Storage Retrieval Requests. |
518 | * |
519 | * @param array $functionConfig The Cancel function configuration values |
520 | * @param array $params An array of function-specific params (or null) |
521 | * |
522 | * @return mixed On success, an associative array with specific function keys |
523 | * and values either for cancelling requests via a form or a URL; |
524 | * on failure, false. |
525 | * |
526 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) |
527 | */ |
528 | protected function checkMethodcancelStorageRetrievalRequests( |
529 | $functionConfig, |
530 | $params |
531 | ) { |
532 | $response = false; |
533 | |
534 | if ( |
535 | isset($this->config->cancel_storage_retrieval_requests_enabled) |
536 | && $this->config->cancel_storage_retrieval_requests_enabled |
537 | ) { |
538 | $check = $this->checkCapability( |
539 | 'cancelStorageRetrievalRequests', |
540 | [$params ?: []] |
541 | ); |
542 | if ($check) { |
543 | $response = ['function' => 'cancelStorageRetrievalRequests']; |
544 | } else { |
545 | $cancelParams = [ |
546 | $params ?: [], |
547 | $params['patron'] ?? null, |
548 | ]; |
549 | $check2 = $this->checkCapability( |
550 | 'getCancelStorageRetrievalRequestLink', |
551 | $cancelParams |
552 | ); |
553 | if ($check2) { |
554 | $response = [ |
555 | 'function' => 'getCancelStorageRetrievalRequestLink', |
556 | ]; |
557 | } |
558 | } |
559 | } |
560 | return $response; |
561 | } |
562 | |
563 | /** |
564 | * Check ILL Request |
565 | * |
566 | * A support method for checkFunction(). This is responsible for checking |
567 | * the driver configuration to determine if the system supports storage |
568 | * retrieval requests. |
569 | * |
570 | * @param array $functionConfig The ILL request configuration values |
571 | * @param array $params An array of function-specific params (or null) |
572 | * |
573 | * @return mixed On success, an associative array with specific function keys |
574 | * and values either for placing requests via a form; on failure, false. |
575 | */ |
576 | protected function checkMethodILLRequests($functionConfig, $params) |
577 | { |
578 | $response = false; |
579 | |
580 | // $params doesn't include all of the keys used by |
581 | // placeILLRequest, but it is the best we can do in the context. |
582 | if ( |
583 | $this->checkCapability('placeILLRequest', [$params ?: []]) |
584 | && isset($functionConfig['HMACKeys']) |
585 | ) { |
586 | $response = ['function' => 'placeILLRequest']; |
587 | if (isset($functionConfig['defaultRequiredDate'])) { |
588 | $response['defaultRequiredDate'] |
589 | = $functionConfig['defaultRequiredDate']; |
590 | } |
591 | $response['HMACKeys'] = explode(':', $functionConfig['HMACKeys']); |
592 | if (isset($functionConfig['extraFields'])) { |
593 | $response['extraFields'] = $functionConfig['extraFields']; |
594 | } |
595 | $response['helpText'] |
596 | = $this->getHelpText($functionConfig['helpText']); |
597 | } |
598 | return $response; |
599 | } |
600 | |
601 | /** |
602 | * Check Cancel ILL Requests |
603 | * |
604 | * A support method for checkFunction(). This is responsible for checking |
605 | * the driver configuration to determine if the system supports Cancelling |
606 | * ILL Requests. |
607 | * |
608 | * @param array $functionConfig The Cancel function configuration values |
609 | * @param array $params An array of function-specific params (or null) |
610 | * |
611 | * @return mixed On success, an associative array with specific function keys |
612 | * and values either for cancelling requests via a form or a URL; |
613 | * on failure, false. |
614 | * |
615 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) |
616 | */ |
617 | protected function checkMethodcancelILLRequests($functionConfig, $params) |
618 | { |
619 | $response = false; |
620 | |
621 | if ( |
622 | isset($this->config->cancel_ill_requests_enabled) |
623 | && $this->config->cancel_ill_requests_enabled |
624 | ) { |
625 | $check = $this->checkCapability( |
626 | 'cancelILLRequests', |
627 | [$params ?: []] |
628 | ); |
629 | if ($check) { |
630 | $response = ['function' => 'cancelILLRequests']; |
631 | } else { |
632 | $cancelParams = [ |
633 | $params ?: [], |
634 | $params['patron'] ?? null, |
635 | ]; |
636 | $check2 = $this->checkCapability( |
637 | 'getCancelILLRequestLink', |
638 | $cancelParams |
639 | ); |
640 | if ($check2) { |
641 | $response = [ |
642 | 'function' => 'getCancelILLRequestLink', |
643 | ]; |
644 | } |
645 | } |
646 | } |
647 | return $response; |
648 | } |
649 | |
650 | /** |
651 | * Check Password Change |
652 | * |
653 | * A support method for checkFunction(). This is responsible for checking |
654 | * the driver configuration to determine if the system supports changing |
655 | * password. |
656 | * |
657 | * @param array $functionConfig The password change configuration values |
658 | * @param array $params Patron data |
659 | * |
660 | * @return mixed On success, an associative array with specific function keys |
661 | * and values either for cancelling requests via a form or a URL; |
662 | * on failure, false. |
663 | * |
664 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) |
665 | */ |
666 | protected function checkMethodchangePassword($functionConfig, $params) |
667 | { |
668 | if ($this->checkCapability('changePassword', [$params ?: []])) { |
669 | return ['function' => 'changePassword']; |
670 | } |
671 | return false; |
672 | } |
673 | |
674 | /** |
675 | * Check Current Loans |
676 | * |
677 | * A support method for checkFunction(). This is responsible for checking |
678 | * the driver configuration to determine if the system supports current |
679 | * loans. |
680 | * |
681 | * @param array $functionConfig Function configuration |
682 | * @param array $params Patron data |
683 | * |
684 | * @return mixed On success, an associative array with specific function keys |
685 | * and values; on failure, false. |
686 | * |
687 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) |
688 | */ |
689 | protected function checkMethodgetMyTransactions($functionConfig, $params) |
690 | { |
691 | if ($this->checkCapability('getMyTransactions', [$params ?: []])) { |
692 | return $functionConfig; |
693 | } |
694 | return false; |
695 | } |
696 | |
697 | /** |
698 | * Check Historic Loans |
699 | * |
700 | * A support method for checkFunction(). This is responsible for checking |
701 | * the driver configuration to determine if the system supports historic |
702 | * loans. |
703 | * |
704 | * @param array $functionConfig Function configuration |
705 | * @param array $params Patron data |
706 | * |
707 | * @return mixed On success, an associative array with specific function keys |
708 | * and values; on failure, false. |
709 | * |
710 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) |
711 | */ |
712 | protected function checkMethodgetMyTransactionHistory($functionConfig, $params) |
713 | { |
714 | if ($this->checkCapability('getMyTransactionHistory', [$params ?: []])) { |
715 | return $functionConfig; |
716 | } |
717 | return false; |
718 | } |
719 | |
720 | /** |
721 | * Check Purge Historic Loans |
722 | * |
723 | * A support method for checkFunction(). This is responsible for checking |
724 | * the driver configuration to determine if the system supports purging of |
725 | * historic loans. |
726 | * |
727 | * @param array $functionConfig Function configuration |
728 | * @param array $params Patron data |
729 | * |
730 | * @return mixed On success, an associative array with specific function keys |
731 | * and values; on failure, false. |
732 | * |
733 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) |
734 | */ |
735 | protected function checkMethodpurgeTransactionHistory($functionConfig, $params) |
736 | { |
737 | if ($this->checkCapability('purgeTransactionHistory', [$params ?: []])) { |
738 | return $functionConfig; |
739 | } |
740 | return false; |
741 | } |
742 | |
743 | /** |
744 | * Check Patron login |
745 | * |
746 | * A support method for checkFunction(). This is responsible for checking |
747 | * the driver configuration to determine if the system supports patron login. |
748 | * It is currently assumed that all drivers do. |
749 | * |
750 | * @param array $functionConfig The patronLogin configuration values |
751 | * @param array $params An array of function-specific params (or null) |
752 | * |
753 | * @return mixed On success, an associative array with specific function keys |
754 | * and values for login; on failure, false. |
755 | */ |
756 | protected function checkMethodpatronLogin($functionConfig, $params) |
757 | { |
758 | return $functionConfig; |
759 | } |
760 | |
761 | /** |
762 | * Get proper help text from the function config |
763 | * |
764 | * @param string|array $helpText Help text(s) |
765 | * |
766 | * @return string Language-specific help text |
767 | */ |
768 | protected function getHelpText($helpText) |
769 | { |
770 | if (is_array($helpText)) { |
771 | $lang = $this->getTranslatorLocale(); |
772 | return $helpText[$lang] ?? $helpText['*'] ?? ''; |
773 | } |
774 | return $helpText; |
775 | } |
776 | |
777 | /** |
778 | * Check Request is Valid |
779 | * |
780 | * This is responsible for checking if a request is valid from hold.php |
781 | * |
782 | * @param string $id A Bibliographic ID |
783 | * @param array $data Collected Holds Data |
784 | * @param array $patron Patron related data |
785 | * |
786 | * @return mixed The result of the checkRequestIsValid function if it |
787 | * exists, true if it does not |
788 | */ |
789 | public function checkRequestIsValid($id, $data, $patron) |
790 | { |
791 | try { |
792 | $params = [$id, $data, $patron]; |
793 | if ($this->checkCapability('checkRequestIsValid', $params)) { |
794 | return $this->getDriver()->checkRequestIsValid($id, $data, $patron); |
795 | } |
796 | } catch (\Exception $e) { |
797 | if ($this->failOverToNoILS($e)) { |
798 | return call_user_func_array([$this, __METHOD__], func_get_args()); |
799 | } |
800 | throw $e; |
801 | } |
802 | // If the driver has no checkRequestIsValid method, we will assume that |
803 | // all requests are valid - failure can be handled later after the user |
804 | // attempts to place an illegal hold |
805 | return true; |
806 | } |
807 | |
808 | /** |
809 | * Check Storage Retrieval Request is Valid |
810 | * |
811 | * This is responsible for checking if a storage retrieval request is valid |
812 | * |
813 | * @param string $id A Bibliographic ID |
814 | * @param array $data Collected Holds Data |
815 | * @param array $patron Patron related data |
816 | * |
817 | * @return mixed The result of the checkStorageRetrievalRequestIsValid |
818 | * function if it exists, false if it does not |
819 | */ |
820 | public function checkStorageRetrievalRequestIsValid($id, $data, $patron) |
821 | { |
822 | try { |
823 | $check = $this->checkCapability( |
824 | 'checkStorageRetrievalRequestIsValid', |
825 | [$id, $data, $patron] |
826 | ); |
827 | if ($check) { |
828 | return $this->getDriver()->checkStorageRetrievalRequestIsValid( |
829 | $id, |
830 | $data, |
831 | $patron |
832 | ); |
833 | } |
834 | } catch (\Exception $e) { |
835 | if ($this->failOverToNoILS($e)) { |
836 | return call_user_func_array([$this, __METHOD__], func_get_args()); |
837 | } |
838 | throw $e; |
839 | } |
840 | // If the driver has no checkStorageRetrievalRequestIsValid method, we |
841 | // will assume that the request is not valid |
842 | return false; |
843 | } |
844 | |
845 | /** |
846 | * Check ILL Request is Valid |
847 | * |
848 | * This is responsible for checking if an ILL request is valid |
849 | * |
850 | * @param string $id A Bibliographic ID |
851 | * @param array $data Collected Holds Data |
852 | * @param array $patron Patron related data |
853 | * |
854 | * @return mixed The result of the checkILLRequestIsValid |
855 | * function if it exists, false if it does not |
856 | */ |
857 | public function checkILLRequestIsValid($id, $data, $patron) |
858 | { |
859 | try { |
860 | $params = [$id, $data, $patron]; |
861 | if ($this->checkCapability('checkILLRequestIsValid', $params)) { |
862 | return $this->getDriver()->checkILLRequestIsValid( |
863 | $id, |
864 | $data, |
865 | $patron |
866 | ); |
867 | } |
868 | } catch (\Exception $e) { |
869 | if ($this->failOverToNoILS($e)) { |
870 | return call_user_func_array([$this, __METHOD__], func_get_args()); |
871 | } |
872 | throw $e; |
873 | } |
874 | // If the driver has no checkILLRequestIsValid method, we |
875 | // will assume that the request is not valid |
876 | return false; |
877 | } |
878 | |
879 | /** |
880 | * Get Holds Mode |
881 | * |
882 | * This is responsible for returning the holds mode |
883 | * |
884 | * @return string The Holds mode |
885 | */ |
886 | public function getHoldsMode() |
887 | { |
888 | return $this->holdsMode; |
889 | } |
890 | |
891 | /** |
892 | * Get Offline Mode |
893 | * |
894 | * This is responsible for returning the offline mode |
895 | * |
896 | * @param bool $healthCheck Perform a health check in addition to consulting |
897 | * the ILS status? |
898 | * |
899 | * @return string|bool "ils-offline" for systems where the main ILS is offline, |
900 | * "ils-none" for systems which do not use an ILS, false for online systems. |
901 | */ |
902 | public function getOfflineMode($healthCheck = false) |
903 | { |
904 | // If we have NoILS failover configured, force driver initialization so |
905 | // we can know we are checking the offline mode against the correct driver. |
906 | if ($this->hasNoILSFailover()) { |
907 | $this->getDriver(); |
908 | } |
909 | |
910 | // If we need to perform a health check, try to do a random item lookup |
911 | // before proceeding. |
912 | if ($healthCheck) { |
913 | $this->getStatus($this->config->healthCheckId ?? '1'); |
914 | } |
915 | |
916 | // If we're encountering failures, let's go into ils-offline mode if |
917 | // the ILS driver does not natively support getOfflineMode(). |
918 | $default = $this->failing ? 'ils-offline' : false; |
919 | |
920 | // Graceful degradation -- return false if no method supported. |
921 | return $this->checkCapability('getOfflineMode') |
922 | ? $this->getDriver()->getOfflineMode() : $default; |
923 | } |
924 | |
925 | /** |
926 | * Get Title Holds Mode |
927 | * |
928 | * This is responsible for returning the Title holds mode |
929 | * |
930 | * @return string The Title Holds mode |
931 | */ |
932 | public function getTitleHoldsMode() |
933 | { |
934 | return $this->titleHoldsMode; |
935 | } |
936 | |
937 | /** |
938 | * Has Holdings |
939 | * |
940 | * Obtain information on whether or not the item has holdings |
941 | * |
942 | * @param string $id A bibliographic id |
943 | * |
944 | * @return bool true on success, false on failure |
945 | */ |
946 | public function hasHoldings($id) |
947 | { |
948 | // Graceful degradation -- return true if no method supported. |
949 | try { |
950 | return $this->checkCapability('hasHoldings', [$id]) |
951 | ? $this->getDriver()->hasHoldings($id) : true; |
952 | } catch (\Exception $e) { |
953 | if ($this->failOverToNoILS($e)) { |
954 | return call_user_func_array([$this, __METHOD__], func_get_args()); |
955 | } |
956 | throw $e; |
957 | } |
958 | } |
959 | |
960 | /** |
961 | * Get Hidden Login Mode |
962 | * |
963 | * This is responsible for indicating whether login should be hidden. |
964 | * |
965 | * @return bool true if the login should be hidden, false if not |
966 | */ |
967 | public function loginIsHidden() |
968 | { |
969 | // Graceful degradation -- return false if no method supported. |
970 | try { |
971 | return $this->checkCapability('loginIsHidden') |
972 | ? $this->getDriver()->loginIsHidden() : false; |
973 | } catch (\Exception $e) { |
974 | if ($this->failOverToNoILS($e)) { |
975 | return call_user_func_array([$this, __METHOD__], func_get_args()); |
976 | } |
977 | throw $e; |
978 | } |
979 | } |
980 | |
981 | /** |
982 | * Check driver capability -- return true if the driver supports the specified |
983 | * method; false otherwise. |
984 | * |
985 | * @param string $method Method to check |
986 | * @param array $params Array of passed parameters (optional) |
987 | * @param bool $throw Whether to throw exceptions instead of returning false |
988 | * |
989 | * @return bool |
990 | * @throws ILSException |
991 | */ |
992 | public function checkCapability($method, $params = [], $throw = false) |
993 | { |
994 | try { |
995 | // If we have NoILS failover disabled, we can check capabilities of |
996 | // the driver class without wasting time initializing it; if NoILS |
997 | // failover is enabled, we have to initialize the driver object now |
998 | // to be sure we are checking capabilities on the appropriate class. |
999 | $driverToCheck = $this->getDriver($this->hasNoILSFailover()); |
1000 | |
1001 | // First check that the function is callable: |
1002 | if (is_callable([$driverToCheck, $method])) { |
1003 | // At least drivers implementing the __call() magic method must also |
1004 | // implement supportsMethod() to verify that the method is actually |
1005 | // usable: |
1006 | if (method_exists($driverToCheck, 'supportsMethod')) { |
1007 | return $this->getDriver()->supportsMethod($method, $params); |
1008 | } |
1009 | return true; |
1010 | } |
1011 | } catch (ILSException $e) { |
1012 | $this->logError( |
1013 | "checkCapability($method) with params: " . $this->varDump($params) |
1014 | . ' failed: ' . $e->getMessage() |
1015 | ); |
1016 | if ($throw) { |
1017 | throw $e; |
1018 | } |
1019 | } |
1020 | |
1021 | // If we got this far, the feature is unsupported: |
1022 | return false; |
1023 | } |
1024 | |
1025 | /** |
1026 | * Get Names of Textual Holdings Fields |
1027 | * |
1028 | * Obtain information on which textual holdings fields should be displayed |
1029 | * |
1030 | * @return string[] |
1031 | */ |
1032 | public function getHoldingsTextFieldNames() |
1033 | { |
1034 | return isset($this->config->holdings_text_fields) |
1035 | ? $this->config->holdings_text_fields->toArray() |
1036 | : ['holdings_notes', 'summary', 'supplements', 'indexes']; |
1037 | } |
1038 | |
1039 | /** |
1040 | * Get the password policy from the driver |
1041 | * |
1042 | * @param array $patron Patron data |
1043 | * |
1044 | * @return bool|array Password policy array or false if unsupported |
1045 | */ |
1046 | public function getPasswordPolicy($patron) |
1047 | { |
1048 | return $this->checkCapability( |
1049 | 'getConfig', |
1050 | ['changePassword', compact('patron')] |
1051 | ) ? $this->getDriver()->getConfig('changePassword', compact('patron')) |
1052 | : false; |
1053 | } |
1054 | |
1055 | /** |
1056 | * Get Patron Transactions |
1057 | * |
1058 | * This is responsible for retrieving all transactions (i.e. checked out items) |
1059 | * by a specific patron. |
1060 | * |
1061 | * @param array $patron The patron array from patronLogin |
1062 | * @param array $params Parameters |
1063 | * |
1064 | * @return mixed Array of the patron's transactions |
1065 | */ |
1066 | public function getMyTransactions($patron, $params = []) |
1067 | { |
1068 | $result = $this->__call('getMyTransactions', [$patron, $params]); |
1069 | |
1070 | // Support also older driver return value: |
1071 | if (!isset($result['count'])) { |
1072 | $result = [ |
1073 | 'count' => count($result), |
1074 | 'records' => $result, |
1075 | ]; |
1076 | } |
1077 | |
1078 | return $result; |
1079 | } |
1080 | |
1081 | /** |
1082 | * Get holdings |
1083 | * |
1084 | * Retrieve holdings from ILS driver class and normalize result array and availability if needed. |
1085 | * |
1086 | * @param string $id The record id to retrieve the holdings for |
1087 | * @param array $patron Patron data |
1088 | * @param array $options Additional options |
1089 | * |
1090 | * @return array Array with holding data |
1091 | */ |
1092 | public function getHolding($id, $patron = null, $options = []) |
1093 | { |
1094 | // Get pagination options for holdings tab: |
1095 | $params = compact('id', 'patron'); |
1096 | $config = $this->checkCapability('getConfig', ['Holdings', $params]) |
1097 | ? $this->getDriver()->getConfig('Holdings', $params) : []; |
1098 | if (empty($config['itemLimit'])) { |
1099 | // Use itemLimit in Holds as fallback for backward compatibility: |
1100 | $config |
1101 | = $this->checkCapability('getConfig', ['Holds', $params]) |
1102 | ? $this->getDriver()->getConfig('Holds', $params) : []; |
1103 | } |
1104 | $itemLimit = !empty($config['itemLimit']) ? $config['itemLimit'] : null; |
1105 | |
1106 | $page = $this->request ? $this->request->getQuery('page', 1) : 1; |
1107 | $offset = ($itemLimit && is_numeric($itemLimit)) |
1108 | ? ($page * $itemLimit) - $itemLimit |
1109 | : null; |
1110 | $defaultOptions = compact('page', 'itemLimit', 'offset'); |
1111 | $finalOptions = $options + $defaultOptions; |
1112 | |
1113 | // Get the holdings from the ILS |
1114 | $holdings = $this->__call('getHolding', [$id, $patron, $finalOptions]); |
1115 | |
1116 | // Return all the necessary details: |
1117 | if (!isset($holdings['holdings'])) { |
1118 | $holdings = [ |
1119 | 'total' => count($holdings), |
1120 | 'holdings' => $holdings, |
1121 | 'electronic_holdings' => [], |
1122 | ]; |
1123 | } else { |
1124 | if (!isset($holdings['total'])) { |
1125 | $holdings['total'] = count($holdings['holdings']); |
1126 | } |
1127 | if (!isset($holdings['electronic_holdings'])) { |
1128 | $holdings['electronic_holdings'] = []; |
1129 | } |
1130 | } |
1131 | |
1132 | // parse availability and status to AvailabilityStatus object |
1133 | $holdings['holdings'] = array_map($this->getStatusParser(), $holdings['holdings']); |
1134 | $holdings['electronic_holdings'] = array_map($this->getStatusParser(), $holdings['electronic_holdings']); |
1135 | $holdings['page'] = $finalOptions['page']; |
1136 | $holdings['itemLimit'] = $finalOptions['itemLimit']; |
1137 | return $holdings; |
1138 | } |
1139 | |
1140 | /** |
1141 | * Get status |
1142 | * |
1143 | * Retrieve status from ILS driver class and normalize availability if needed. |
1144 | * |
1145 | * @param string $id The record id to retrieve the status for |
1146 | * |
1147 | * @return array Array with holding data |
1148 | */ |
1149 | public function getStatus($id) |
1150 | { |
1151 | $status = $this->__call('getStatus', [$id]); |
1152 | |
1153 | // parse availability and status to AvailabilityStatus object |
1154 | return array_map($this->getStatusParser(), $status); |
1155 | } |
1156 | |
1157 | /** |
1158 | * Get statuses |
1159 | * |
1160 | * Retrieve statuses from ILS driver class and normalize availability if needed. |
1161 | * |
1162 | * @param string $ids The record ids to retrieve the statuses for |
1163 | * |
1164 | * @return array Array with holding data |
1165 | */ |
1166 | public function getStatuses($ids) |
1167 | { |
1168 | $statuses = $this->__call('getStatuses', [$ids]); |
1169 | |
1170 | return array_map(function ($status) { |
1171 | // parse availability and status to AvailabilityStatus object |
1172 | return array_map($this->getStatusParser(), $status); |
1173 | }, $statuses); |
1174 | } |
1175 | |
1176 | /** |
1177 | * Get a function that parses availability and status to an AvailabilityStatus object if necessary. |
1178 | * |
1179 | * @return callable |
1180 | */ |
1181 | public function getStatusParser() |
1182 | { |
1183 | return function ($item) { |
1184 | if (!(($item['availability'] ?? null) instanceof AvailabilityStatus)) { |
1185 | $availability = $item['availability'] ?? false; |
1186 | if ($item['use_unknown_message'] ?? false) { |
1187 | $availability = Logic\AvailabilityStatusInterface::STATUS_UNKNOWN; |
1188 | } |
1189 | $item['availability'] = new AvailabilityStatus( |
1190 | $availability, |
1191 | $item['status'] ?? '' |
1192 | ); |
1193 | unset($item['status']); |
1194 | unset($item['use_unknown_message']); |
1195 | } |
1196 | return $item; |
1197 | }; |
1198 | } |
1199 | |
1200 | /** |
1201 | * Default method -- pass along calls to the driver if available; return |
1202 | * false otherwise. This allows custom functions to be implemented in |
1203 | * the driver without constant modification to the connection class. |
1204 | * |
1205 | * @param string $methodName The name of the called method. |
1206 | * @param array $params Array of passed parameters. |
1207 | * |
1208 | * @throws ILSException |
1209 | * @return mixed Varies by method (false if undefined method) |
1210 | */ |
1211 | public function __call($methodName, $params) |
1212 | { |
1213 | try { |
1214 | if ($this->checkCapability($methodName, $params)) { |
1215 | return call_user_func_array( |
1216 | [$this->getDriver(), $methodName], |
1217 | $params |
1218 | ); |
1219 | } |
1220 | } catch (\Exception $e) { |
1221 | if ($this->failOverToNoILS($e)) { |
1222 | return call_user_func_array([$this, __METHOD__], func_get_args()); |
1223 | } |
1224 | throw $e; |
1225 | } |
1226 | throw new ILSException( |
1227 | 'Cannot call method: ' . $this->getDriverClass() . '::' . $methodName |
1228 | ); |
1229 | } |
1230 | } |