Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.27% |
1 / 367 |
|
0.00% |
0 / 21 |
CRAP | |
0.00% |
0 / 1 |
HorizonXMLAPI | |
0.27% |
1 / 367 |
|
0.00% |
0 / 21 |
5505.36 | |
0.00% |
0 / 1 |
init | |
11.11% |
1 / 9 |
|
0.00% |
0 / 1 |
1.70 | |||
getConfig | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
processHoldingRow | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
2 | |||
determineRenewability | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
12 | |||
processTransactionsRow | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
2 | |||
getPickUpLocations | |
0.00% |
0 / 40 |
|
0.00% |
0 / 1 |
42 | |||
getDefaultPickUpLocation | |
0.00% |
0 / 21 |
|
0.00% |
0 / 1 |
30 | |||
makeRequest | |
0.00% |
0 / 21 |
|
0.00% |
0 / 1 |
42 | |||
getSession | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
12 | |||
registerUser | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
6 | |||
checkRequestIsValid | |
0.00% |
0 / 23 |
|
0.00% |
0 / 1 |
30 | |||
getItems | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
6 | |||
renewItems | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
6 | |||
placeRequest | |
0.00% |
0 / 51 |
|
0.00% |
0 / 1 |
30 | |||
cancelRequest | |
0.00% |
0 / 43 |
|
0.00% |
0 / 1 |
110 | |||
placeHold | |
0.00% |
0 / 23 |
|
0.00% |
0 / 1 |
30 | |||
cancelHolds | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
12 | |||
processRenewals | |
0.00% |
0 / 29 |
|
0.00% |
0 / 1 |
30 | |||
renewMyItems | |
0.00% |
0 / 22 |
|
0.00% |
0 / 1 |
30 | |||
getRenewDetails | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getCancelHoldDetails | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | |
3 | /** |
4 | * Horizon ILS Driver (w/ XML API support) |
5 | * |
6 | * PHP version 8 |
7 | * |
8 | * Copyright (C) Villanova University 2007. |
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 ILS_Drivers |
25 | * @author Matt Mackey <vufind-tech@lists.sourceforge.net> |
26 | * @author Ray Cummins <vufind-tech@lists.sourceforge.net> |
27 | * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License |
28 | * @link https://vufind.org/wiki/development:plugins:ils_drivers Wiki |
29 | */ |
30 | |
31 | namespace VuFind\ILS\Driver; |
32 | |
33 | use VuFind\Exception\ILS as ILSException; |
34 | |
35 | use function in_array; |
36 | use function is_array; |
37 | |
38 | /** |
39 | * Horizon ILS Driver (w/ XML API support) |
40 | * |
41 | * @category VuFind |
42 | * @package ILS_Drivers |
43 | * @author Matt Mackey <vufind-tech@lists.sourceforge.net> |
44 | * @author Ray Cummins <vufind-tech@lists.sourceforge.net> |
45 | * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License |
46 | * @link https://vufind.org/wiki/development:plugins:ils_drivers Wiki |
47 | */ |
48 | class HorizonXMLAPI extends Horizon implements \VuFindHttp\HttpServiceAwareInterface |
49 | { |
50 | use \VuFindHttp\HttpServiceAwareTrait; |
51 | |
52 | /** |
53 | * API profile |
54 | * |
55 | * @var string |
56 | */ |
57 | protected $wsProfile; |
58 | |
59 | /** |
60 | * API URL |
61 | * |
62 | * @var string |
63 | */ |
64 | protected $wsURL; |
65 | |
66 | /** |
67 | * Available pickup locations for holds |
68 | * |
69 | * @var array |
70 | */ |
71 | protected $wsPickUpLocations; |
72 | |
73 | /** |
74 | * Defaut pickup location for holds |
75 | * |
76 | * @var string |
77 | */ |
78 | protected $wsDefaultPickUpLocation; |
79 | |
80 | /** |
81 | * Date format used by API |
82 | * |
83 | * @var string |
84 | */ |
85 | protected $wsDateFormat; |
86 | |
87 | /** |
88 | * Initialize the driver. |
89 | * |
90 | * Validate configuration and perform all resource-intensive tasks needed to |
91 | * make the driver active. |
92 | * |
93 | * @throws ILSException |
94 | * @return void |
95 | */ |
96 | public function init() |
97 | { |
98 | parent::init(); |
99 | |
100 | // Process Config |
101 | $this->wsProfile = $this->config['Webservices']['profile']; |
102 | $this->wsURL = $this->config['Webservices']['HIPurl']; |
103 | $this->wsPickUpLocations |
104 | = $this->config['pickUpLocations'] ?? false; |
105 | |
106 | $this->wsDefaultPickUpLocation |
107 | = $this->config['Holds']['defaultPickUpLocation'] ?? false; |
108 | |
109 | $this->wsDateFormat |
110 | = $this->config['Webservices']['dateformat'] ?? 'd/m/Y'; |
111 | } |
112 | |
113 | /** |
114 | * Public Function which retrieves renew, hold and cancel settings from the |
115 | * driver ini file. |
116 | * |
117 | * @param string $function The name of the feature to be checked |
118 | * @param array $params Optional feature-specific parameters (array) |
119 | * |
120 | * @return array An array with key-value pairs. |
121 | * |
122 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) |
123 | */ |
124 | public function getConfig($function, $params = []) |
125 | { |
126 | if (isset($this->config[$function])) { |
127 | $functionConfig = $this->config[$function]; |
128 | } else { |
129 | $functionConfig = false; |
130 | } |
131 | return $functionConfig; |
132 | } |
133 | |
134 | /** |
135 | * Protected support method for getHolding. |
136 | * |
137 | * @param string $id Bib Id |
138 | * @param array $row SQL Row Data |
139 | * @param array $patron Patron Array |
140 | * |
141 | * @return array Keyed data |
142 | */ |
143 | protected function processHoldingRow($id, $row, $patron) |
144 | { |
145 | $itemData = [ |
146 | 'id' => $row['ITEM_ID'], |
147 | 'level' => 'item', |
148 | ]; |
149 | |
150 | $holding = parent::processHoldingRow($id, $row, $patron); |
151 | $holding += [ |
152 | 'addLink' => $this->checkRequestIsValid($id, $itemData, $patron), |
153 | ]; |
154 | return $holding; |
155 | } |
156 | |
157 | /** |
158 | * Determine Renewability |
159 | * |
160 | * This is responsible for determining if an item is renewable |
161 | * |
162 | * @param string $requested The number of times an item has been requested |
163 | * |
164 | * @return array $renewData Array of the renewability status and associated |
165 | * message |
166 | */ |
167 | protected function determineRenewability($requested) |
168 | { |
169 | $renewData = []; |
170 | |
171 | $renewData['renewable'] = ($requested == 0) ? true : false; |
172 | |
173 | if (!$renewData['renewable']) { |
174 | $renewData['message'] = 'renew_item_requested'; |
175 | } else { |
176 | $renewData['message'] = false; |
177 | } |
178 | |
179 | return $renewData; |
180 | } |
181 | |
182 | /** |
183 | * Protected support method for getMyTransactions. |
184 | * |
185 | * @param array $row An array of keyed data |
186 | * |
187 | * @return array Keyed data for display by template files |
188 | */ |
189 | protected function processTransactionsRow($row) |
190 | { |
191 | $transactions = parent::processTransactionsRow($row); |
192 | $renewData = $this->determineRenewability($row['REQUEST']); |
193 | $transactions['renewable'] = $renewData['renewable']; |
194 | $transactions['message'] = $renewData['message']; |
195 | return $transactions; |
196 | } |
197 | |
198 | /* Horizon XML API Functions */ |
199 | |
200 | /** |
201 | * Get Pick Up Locations |
202 | * |
203 | * This is responsible for getting a list of valid library locations for |
204 | * holds / recall retrieval |
205 | * |
206 | * @param array $patron Patron information returned by the patronLogin |
207 | * method. |
208 | * @param array $holdDetails Optional array, only passed in when getting a list |
209 | * in the context of placing or editing a hold. When placing a hold, it contains |
210 | * most of the same values passed to placeHold, minus the patron data. When |
211 | * editing a hold it contains all the hold information returned by getMyHolds. |
212 | * May be used to limit the pickup options or may be ignored. The driver must |
213 | * not add new options to the return array based on this data or other areas of |
214 | * VuFind may behave incorrectly. |
215 | * |
216 | * @throws ILSException |
217 | * @return array An array of associative arrays with locationID and |
218 | * locationDisplay keys |
219 | * |
220 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) |
221 | */ |
222 | public function getPickUpLocations($patron, $holdDetails = null) |
223 | { |
224 | $pickresponse = []; |
225 | if ($this->wsPickUpLocations == false) { |
226 | // Select |
227 | $sqlSelect = [ |
228 | 'l.location LOCATIONID', |
229 | 'l.name LOCATIONDISPLAY', |
230 | ]; |
231 | |
232 | // From |
233 | $sqlFrom = ['pickup_location_sort pls']; |
234 | |
235 | // Join |
236 | $sqlJoin = [ |
237 | 'location l on l.location = pls.pickup_location', |
238 | 'borrower b on b.location = pls.location', |
239 | 'borrower_barcode bb on bb.borrower# = b.borrower#', |
240 | ]; |
241 | |
242 | // Where |
243 | $sqlWhere = [ |
244 | 'pls.display = 1', |
245 | 'bb.bbarcode="' . addslashes($patron['id']) . '"', |
246 | ]; |
247 | |
248 | // Order by |
249 | $sqlOrder = ['l.name']; |
250 | |
251 | $sqlArray = [ |
252 | 'expressions' => $sqlSelect, |
253 | 'from' => $sqlFrom, |
254 | 'join' => $sqlJoin, |
255 | 'where' => $sqlWhere, |
256 | 'order' => $sqlOrder, |
257 | ]; |
258 | |
259 | $sql = $this->buildSqlFromArray($sqlArray); |
260 | |
261 | try { |
262 | $sqlStmt = $this->db->query($sql); |
263 | |
264 | foreach ($sqlStmt as $row) { |
265 | $pickresponse[] = [ |
266 | 'locationID' => $row['LOCATIONID'], |
267 | 'locationDisplay' => $row['LOCATIONDISPLAY'], |
268 | ]; |
269 | } |
270 | } catch (\Exception $e) { |
271 | $this->throwAsIlsException($e); |
272 | } |
273 | } elseif (isset($this->wsPickUpLocations)) { |
274 | foreach ($this->wsPickUpLocations as $code => $library) { |
275 | $pickresponse[] = [ |
276 | 'locationID' => $code, |
277 | 'locationDisplay' => $library, |
278 | ]; |
279 | } |
280 | } |
281 | return $pickresponse; |
282 | } |
283 | |
284 | /** |
285 | * Get Default Pick Up Location |
286 | * |
287 | * This is responsible for retrieving the pickup location for a logged in patron. |
288 | * |
289 | * @param array $patron Patron information returned by the patronLogin |
290 | * method. |
291 | * @param array $holdDetails Optional array, only passed in when getting a list |
292 | * in the context of placing a hold; contains most of the same values passed to |
293 | * placeHold, minus the patron data. May be used to limit the pickup options |
294 | * or may be ignored. |
295 | * |
296 | * @return string The default pickup location for the patron. |
297 | * |
298 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) |
299 | */ |
300 | public function getDefaultPickUpLocation($patron = false, $holdDetails = null) |
301 | { |
302 | if ($this->wsDefaultPickUpLocation == false) { |
303 | // Select |
304 | $sqlSelect = ['b.location LOCATION']; |
305 | |
306 | // From |
307 | $sqlFrom = ['borrower b']; |
308 | |
309 | // Join |
310 | $sqlJoin = ['borrower_barcode bb on bb.borrower# = b.borrower#']; |
311 | |
312 | // Where |
313 | $sqlWhere = ['bb.bbarcode="' . addslashes($patron['id']) . '"']; |
314 | |
315 | $sqlArray = [ |
316 | 'expressions' => $sqlSelect, |
317 | 'from' => $sqlFrom, |
318 | 'join' => $sqlJoin, |
319 | 'where' => $sqlWhere, |
320 | ]; |
321 | |
322 | $sql = $this->buildSqlFromArray($sqlArray); |
323 | |
324 | try { |
325 | $sqlStmt = $this->db->query($sql); |
326 | |
327 | foreach ($sqlStmt as $row) { |
328 | $defaultPickUpLocation = $row['LOCATION']; |
329 | return $defaultPickUpLocation; |
330 | } |
331 | } catch (\Exception $e) { |
332 | $this->throwAsIlsException($e); |
333 | } |
334 | } elseif (isset($this->wsDefaultPickUpLocation)) { |
335 | return $this->wsDefaultPickUpLocation; |
336 | } |
337 | // If we didn't return above, there were no values. |
338 | return null; |
339 | } |
340 | |
341 | /** |
342 | * Make Request |
343 | * |
344 | * Makes a request to the Horizon API |
345 | * |
346 | * @param array $params A keyed array of query data |
347 | * @param string $mode The http request method to use (Default of GET) |
348 | * |
349 | * @return obj A Simple XML Object loaded with the xml data returned by the API |
350 | */ |
351 | protected function makeRequest($params = false, $mode = 'GET') |
352 | { |
353 | $queryString = []; |
354 | // Build Url Base |
355 | $urlParams = $this->wsURL; |
356 | |
357 | // Add Params |
358 | foreach ($params as $key => $param) { |
359 | if (is_array($param)) { |
360 | foreach ($param as $sub) { |
361 | $queryString[] = $key . '=' . urlencode($sub); |
362 | } |
363 | } else { |
364 | // This is necessary as Horizon expects spaces to be represented by |
365 | // "+" rather than the url_encode "%20" for Pick Up Locations |
366 | $queryString[] = $key . '=' . |
367 | str_replace('%20', '+', urlencode($param)); |
368 | } |
369 | } |
370 | |
371 | // Build Params |
372 | $urlParams .= '?' . implode('&', $queryString); |
373 | |
374 | // Create Proxy Request |
375 | $client = $this->httpService->createClient($urlParams, $mode); |
376 | |
377 | // Send Request and Retrieve Response |
378 | $result = $client->send(); |
379 | if (!$result->isSuccess()) { |
380 | throw new ILSException('Problem with XML API.'); |
381 | } |
382 | $xmlResponse = $result->getBody(); |
383 | |
384 | $oldLibXML = libxml_use_internal_errors(); |
385 | libxml_use_internal_errors(true); |
386 | $simpleXML = simplexml_load_string($xmlResponse); |
387 | libxml_use_internal_errors($oldLibXML); |
388 | |
389 | if ($simpleXML === false) { |
390 | return false; |
391 | } |
392 | return $simpleXML; |
393 | } |
394 | |
395 | /** |
396 | * Get Session |
397 | * |
398 | * Gets a Horizon session |
399 | * |
400 | * @return mixed A session string on success, boolean false on failure |
401 | */ |
402 | protected function getSession() |
403 | { |
404 | $params = ['profile' => $this->wsProfile, |
405 | 'menu' => 'account', |
406 | 'GetXML' => 'true', |
407 | ]; |
408 | |
409 | $response = $this->makeRequest($params); |
410 | |
411 | if ($response && $response->session) { |
412 | $session = (string)$response->session; |
413 | return $session; |
414 | } |
415 | |
416 | return false; |
417 | } |
418 | |
419 | /** |
420 | * Register User |
421 | * |
422 | * Associates a user with a session |
423 | * |
424 | * @param string $userBarcode A valid Horizon user barcode |
425 | * @param string $userPassword A valid Horizon user password (pin) |
426 | * |
427 | * @return bool true on success, false on failure |
428 | */ |
429 | protected function registerUser($userBarcode, $userPassword) |
430 | { |
431 | // Get Session |
432 | $session = $this->getSession(); |
433 | |
434 | $params = ['session' => $session, |
435 | 'profile' => $this->wsProfile, |
436 | 'menu' => 'account', |
437 | 'sec1' => $userBarcode, |
438 | 'sec2' => $userPassword, |
439 | 'GetXML' => 'true', |
440 | ]; |
441 | |
442 | $response = $this->makeRequest($params); |
443 | |
444 | $auth = (string)$response->security->auth; |
445 | |
446 | if ($auth == 'true') { |
447 | return $session; |
448 | } |
449 | |
450 | return false; |
451 | } |
452 | |
453 | /** |
454 | * Check if Request is Valid |
455 | * |
456 | * Determines if a user can place a hold or recall on a specific item |
457 | * |
458 | * @param string $bibId An item's Bib ID |
459 | * @param string $itemData Array containing item id and hold level |
460 | * @param array $patron Patron Array Data |
461 | * |
462 | * @return bool true if the request can be made, false if it cannot |
463 | */ |
464 | public function checkRequestIsValid($bibId, $itemData, $patron) |
465 | { |
466 | // Register Account |
467 | $session = $this->registerUser( |
468 | $patron['cat_username'], |
469 | $patron['cat_password'] |
470 | ); |
471 | if ($session) { |
472 | $params = [ |
473 | 'session' => $session, |
474 | 'profile' => $this->wsProfile, |
475 | 'bibkey' => $bibId, |
476 | 'aspect' => 'submenu13', |
477 | 'lang' => 'eng', |
478 | 'menu' => 'request', |
479 | 'submenu' => 'none', |
480 | 'source' => '~!horizon', |
481 | 'uri' => '', |
482 | 'GetXML' => 'true', |
483 | ]; |
484 | |
485 | // set itemkey only if available and level is not title-level |
486 | if ($itemData['item_id'] != '' && $itemData['level'] != 'title') { |
487 | $params += ['itemkey' => $itemData['item_id']]; |
488 | } |
489 | |
490 | $initResponse = $this->makeRequest($params); |
491 | |
492 | if ($initResponse->request_confirm) { |
493 | return true; |
494 | } |
495 | } |
496 | return false; |
497 | } |
498 | |
499 | /** |
500 | * Get Items |
501 | * |
502 | * Gets a list of items on loan |
503 | * |
504 | * @param string $session A valid Horizon session key |
505 | * |
506 | * @return obj A Simple XML Object |
507 | */ |
508 | protected function getItems($session) |
509 | { |
510 | $params = ['session' => $session, |
511 | 'profile' => $this->wsProfile, |
512 | 'menu' => 'account', |
513 | 'submenu' => 'itemsout', |
514 | 'GetXML' => 'true', |
515 | ]; |
516 | |
517 | $response = $this->makeRequest($params); |
518 | |
519 | if ($response->itemsoutdata) { |
520 | return $response->itemsoutdata; |
521 | } |
522 | |
523 | return false; |
524 | } |
525 | |
526 | /** |
527 | * Renew Items |
528 | * |
529 | * Submits a renewal request to the Horizon API and returns the results |
530 | * |
531 | * @param string $session A valid Horizon session key |
532 | * @param array $items A list of items to be renewed |
533 | * |
534 | * @return obj A Simple XML Object |
535 | */ |
536 | protected function renewItems($session, $items) |
537 | { |
538 | $params = ['session' => $session, |
539 | 'profile' => $this->wsProfile, |
540 | 'menu' => 'account', |
541 | 'submenu' => 'itemsout', |
542 | 'renewitemkeys' => $items, |
543 | 'renewitems' => 'Renew', |
544 | 'GetXML' => 'true', |
545 | ]; |
546 | |
547 | $response = $this->makeRequest($params); |
548 | |
549 | if ($response->itemsoutdata) { |
550 | return $response->itemsoutdata; |
551 | } |
552 | |
553 | return false; |
554 | } |
555 | |
556 | /** |
557 | * Place Request |
558 | * |
559 | * Submits a hold request to the Horizon XML API and processes the result |
560 | * |
561 | * @param string $session A valid Horizon session key |
562 | * @param array $requestDetails An array of request details |
563 | * |
564 | * @return array An array witk keys indicating the a success (boolean), |
565 | * status (string) and sysMessage (string) if available |
566 | */ |
567 | protected function placeRequest($session, $requestDetails) |
568 | { |
569 | $params = ['session' => $session, |
570 | 'profile' => $this->wsProfile, |
571 | 'bibkey' => $requestDetails['bibId'], |
572 | 'aspect' => 'submenu13', |
573 | 'lang' => 'eng', |
574 | 'menu' => 'request', |
575 | 'submenu' => 'none', |
576 | 'source' => '~!horizon', |
577 | 'uri' => '', |
578 | 'GetXML' => 'true', |
579 | ]; |
580 | |
581 | // set itemkey only if available |
582 | if ($requestDetails['itemId'] != '') { |
583 | $params += ['itemkey' => $requestDetails['itemId']]; |
584 | } |
585 | |
586 | $initResponse = $this->makeRequest($params); |
587 | |
588 | if ($initResponse->request_confirm) { |
589 | $confirmParams = [ |
590 | 'session' => $session, |
591 | 'profile' => $this->wsProfile, |
592 | 'bibkey' => $requestDetails['bibId'], |
593 | 'aspect' => 'advanced', |
594 | 'lang' => 'eng', |
595 | 'menu' => 'request', |
596 | 'submenu' => 'none', |
597 | 'source' => '~!horizon', |
598 | 'uri' => '', |
599 | 'link' => 'direct', |
600 | 'request_finish' => 'Request', |
601 | 'cl' => 'PlaceRequestjsp', |
602 | 'pickuplocation' => $requestDetails['pickuplocation'], |
603 | 'notifyby' => $requestDetails['notify'], |
604 | 'GetXML' => 'true', |
605 | ]; |
606 | |
607 | $request = $this->makeRequest($confirmParams); |
608 | |
609 | if ($request->request_success) { |
610 | $response = [ |
611 | 'success' => true, |
612 | 'status' => 'hold_success', |
613 | ]; |
614 | } else { |
615 | $response = [ |
616 | 'success' => false, |
617 | 'status' => 'hold_error_fail', |
618 | ]; |
619 | } |
620 | } else { |
621 | $sysMessage = false; |
622 | if ($initResponse->alert->message) { |
623 | $sysMessage = (string)$initResponse->alert->message; |
624 | } |
625 | $response = [ |
626 | 'success' => false, |
627 | 'status' => 'hold_error_fail', |
628 | 'sysMessage' => $sysMessage, |
629 | ]; |
630 | } |
631 | return $response; |
632 | } |
633 | |
634 | /** |
635 | * Cancel Request |
636 | * |
637 | * Submits a cancel request to the Horizon API and processes the result |
638 | * |
639 | * @param string $session A valid Horizon session key |
640 | * @param Array $data An array of item data |
641 | * |
642 | * @return array An array of cancel information keyed by item ID plus |
643 | * the number of successful cancels |
644 | */ |
645 | protected function cancelRequest($session, $data) |
646 | { |
647 | $responseItems = []; |
648 | |
649 | $params = ['session' => $session, |
650 | 'profile' => $this->wsProfile, |
651 | 'lang' => 'eng', |
652 | 'menu' => 'account', |
653 | 'submenu' => 'holds', |
654 | 'cancelhold' => 'Cancel Request', |
655 | 'GetXML' => 'true', |
656 | ]; |
657 | |
658 | $cancelData = []; |
659 | foreach ($data as $values) { |
660 | $cancelData[] = $values['bib_id'] . ':' . $values['item_id']; |
661 | } |
662 | |
663 | $params += ['waitingholdselected' => $cancelData]; |
664 | |
665 | $response = $this->makeRequest($params); |
666 | |
667 | $count = 0; |
668 | // No Indication of Success or Failure |
669 | if ($response !== false && !$response->error->message) { |
670 | $keys = []; |
671 | // Get a list of bib keys from waiting items |
672 | $currentHolds = $response->holdsdata->waiting->waitingitem; |
673 | foreach ($currentHolds as $hold) { |
674 | foreach ($hold->key as $key) { |
675 | $keys[] = (string)$key; |
676 | } |
677 | } |
678 | |
679 | // Go through the submited bib ids and look for a match |
680 | foreach ($data as $values) { |
681 | $itemID = $values['item_id']; |
682 | // If the bib id is matched, the cancel must have failed |
683 | if (in_array($values['bib_id'], $keys)) { |
684 | $responseItems[$itemID] = [ |
685 | 'success' => false, 'status' => 'hold_cancel_fail', |
686 | ]; |
687 | } else { |
688 | $responseItems[$itemID] = [ |
689 | 'success' => true, 'status' => 'hold_cancel_success', |
690 | |
691 | ]; |
692 | $count = $count + 1; |
693 | } |
694 | } |
695 | } else { |
696 | $message = false; |
697 | if ($response->error->message) { |
698 | $message = (string)$response->error->message; |
699 | } |
700 | foreach ($data as $values) { |
701 | $itemID = $values['item_id']; |
702 | $responseItems[$itemID] = [ |
703 | 'success' => false, |
704 | 'status' => 'hold_cancel_fail', |
705 | 'sysMessage' => $message, |
706 | ]; |
707 | } |
708 | } |
709 | $result = ['count' => $count, 'items' => $responseItems]; |
710 | return $result; |
711 | } |
712 | |
713 | /** |
714 | * Place Hold |
715 | * |
716 | * Attempts to place a hold or recall on a particular item and returns |
717 | * an array with result details or throws an exception on failure of support |
718 | * classes |
719 | * |
720 | * @param array $holdDetails An array of item and patron data |
721 | * |
722 | * @throws ILSException |
723 | * @return mixed An array of data on the request including |
724 | * whether or not it was successful and a system message (if available) |
725 | */ |
726 | public function placeHold($holdDetails) |
727 | { |
728 | $userBarcode = $holdDetails['patron']['id']; |
729 | $userPassword = $holdDetails['patron']['cat_password']; |
730 | $bibId = $holdDetails['id']; |
731 | $itemId = $holdDetails['item_id']; |
732 | $level = $holdDetails['level']; |
733 | $pickUpLocationID = !empty($holdDetails['pickUpLocation']) |
734 | ? $holdDetails['pickUpLocation'] |
735 | : $this->getDefaultPickUpLocation(); |
736 | $notify = $this->config['Holds']['notify']; |
737 | |
738 | $requestDetails = [ |
739 | 'bibId' => $bibId, |
740 | 'pickuplocation' => strtoupper($pickUpLocationID), |
741 | 'notify' => $notify, |
742 | ]; |
743 | |
744 | if ($level != 'title' && $itemId != '') { |
745 | $requestDetails += ['itemId' => $itemId]; |
746 | } |
747 | |
748 | // Register Account |
749 | $session = $this->registerUser($userBarcode, $userPassword); |
750 | if ($session) { |
751 | $response = $this->placeRequest($session, $requestDetails); |
752 | } else { |
753 | $response = [ |
754 | 'success' => false, 'status' => 'authentication_error_admin', |
755 | ]; |
756 | } |
757 | |
758 | return $response; |
759 | } |
760 | |
761 | /** |
762 | * Cancel Holds |
763 | * |
764 | * Attempts to Cancel a hold or recall on a particular item. The |
765 | * data in $cancelDetails['details'] is determined by getCancelHoldDetails(). |
766 | * |
767 | * @param array $cancelDetails An array of item and patron data |
768 | * |
769 | * @return array An array of data on each request including |
770 | * whether or not it was successful and a system message (if available) |
771 | */ |
772 | public function cancelHolds($cancelDetails) |
773 | { |
774 | $cancelIDs = []; |
775 | $details = $cancelDetails['details']; |
776 | $userBarcode = $cancelDetails['patron']['id']; |
777 | $userPassword = $cancelDetails['patron']['cat_password']; |
778 | |
779 | foreach ($details as $cancelItem) { |
780 | [$bibID, $itemID] = explode('|', $cancelItem); |
781 | $cancelIDs[] = ['bib_id' => $bibID, 'item_id' => $itemID]; |
782 | } |
783 | |
784 | // Register Account |
785 | $session = $this->registerUser($userBarcode, $userPassword); |
786 | if ($session) { |
787 | $response = $this->cancelRequest($session, $cancelIDs); |
788 | } else { |
789 | $response = [ |
790 | 'success' => false, 'sysMessage' => 'authentication_error_admin', |
791 | ]; |
792 | } |
793 | return $response; |
794 | } |
795 | |
796 | /** |
797 | * Process Renewals |
798 | * |
799 | * This is responsible for processing renewals and is necessary |
800 | * as result of renew attempt is not returned |
801 | * |
802 | * @param array $renewIDs A list of the items being renewed |
803 | * @param array $origData A Simple XML array of loan data before the |
804 | * renewal attempt |
805 | * @param array $renewData A Simple XML array of loan data after the |
806 | * renewal attempt |
807 | * |
808 | * @return array An Array specifying the results of each renewal attempt |
809 | */ |
810 | protected function processRenewals($renewIDs, $origData, $renewData) |
811 | { |
812 | $response = ['ids' => $renewIDs]; |
813 | $i = 0; |
814 | foreach ($origData->itemout as $item) { |
815 | $ikey = (string)$item->ikey; |
816 | if (in_array($ikey, $renewIDs)) { |
817 | $response['details'][$ikey]['item_id'] = $ikey; |
818 | $origRenewals = (string)$item->numrenewals; |
819 | $currentRenewals = (string)$renewData->itemout[$i]->numrenewals; |
820 | |
821 | $dueDate = (string)$renewData->itemout[$i]->duedate; |
822 | $renewerror = (string)$renewData->itemout[$i]->renewerror; |
823 | |
824 | // Convert Horizon Format to display format |
825 | if (!empty($dueDate)) { |
826 | $dueDate = $this->dateFormat->convertToDisplayDate( |
827 | $this->wsDateFormat, |
828 | $dueDate |
829 | ); |
830 | } |
831 | |
832 | if ($currentRenewals > $origRenewals) { |
833 | $response['details'][$ikey] = [ |
834 | 'item_id' => $ikey, |
835 | 'new_date' => $dueDate, |
836 | 'success' => true, |
837 | ]; |
838 | } else { |
839 | $response['details'][$ikey] = [ |
840 | 'item_id' => $ikey, |
841 | 'new_date' => '', |
842 | 'success' => false, |
843 | 'sysMessage' => $renewerror, |
844 | ]; |
845 | } |
846 | } |
847 | $i++; |
848 | } |
849 | return $response; |
850 | } |
851 | |
852 | /** |
853 | * Renew My Items |
854 | * |
855 | * Function for attempting to renew a patron's items. The data in |
856 | * $renewDetails['details'] is determined by getRenewDetails(). |
857 | * |
858 | * @param array $renewDetails An array of data required for renewing items |
859 | * including the Patron ID and an array of renewal IDS |
860 | * |
861 | * @return array An array of renewal information keyed by item ID |
862 | */ |
863 | public function renewMyItems($renewDetails) |
864 | { |
865 | $renewItemKeys = []; |
866 | $renewIDs = []; |
867 | $renewals = $renewDetails['details']; |
868 | $userBarcode = $renewDetails['patron']['id']; |
869 | $userPassword = $renewDetails['patron']['cat_password']; |
870 | |
871 | $session = $this->registerUser($userBarcode, $userPassword); |
872 | if ($session) { |
873 | // Get Items |
874 | $origData = $this->getItems($session); |
875 | if ($origData) { |
876 | // Build Params |
877 | foreach ($renewals as $item) { |
878 | [$itemID, $barcode] = explode('|', $item); |
879 | $renewItemKeys[] = $barcode; |
880 | $renewIDs[] = $itemID; |
881 | } |
882 | // Renew Items |
883 | $renewData = $this->renewItems($session, $renewItemKeys); |
884 | if ($renewData) { |
885 | $response = $this->processRenewals( |
886 | $renewIDs, |
887 | $origData, |
888 | $renewData |
889 | ); |
890 | return $response; |
891 | } |
892 | } |
893 | } |
894 | |
895 | return ['blocks' => ['authentication_error_admin']]; |
896 | } |
897 | |
898 | /** |
899 | * Get Renew Details |
900 | * |
901 | * In order to renew an item, Voyager requires the patron details and an item |
902 | * id. This function returns the item id as a string which is then used |
903 | * as submitted form data in checkedOut.php. This value is then extracted by |
904 | * the RenewMyItems function. |
905 | * |
906 | * @param array $checkOutDetails An array of item data |
907 | * |
908 | * @return string Data for use in a form field |
909 | */ |
910 | public function getRenewDetails($checkOutDetails) |
911 | { |
912 | return $checkOutDetails['item_id'] . '|' . $checkOutDetails['barcode']; |
913 | } |
914 | |
915 | /** |
916 | * Get Cancel Hold Details |
917 | * |
918 | * In order to cancel a hold, Voyager requires the patron details an item ID |
919 | * and a recall ID. This function returns the item id and recall id as a string |
920 | * separated by a pipe, which is then submitted as form data in Hold.php. This |
921 | * value is then extracted by the CancelHolds function. |
922 | * |
923 | * @param array $holdDetails A single hold array from getMyHolds |
924 | * @param array $patron Patron information from patronLogin |
925 | * |
926 | * @return string Data for use in a form field |
927 | * |
928 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) |
929 | */ |
930 | public function getCancelHoldDetails($holdDetails, $patron = []) |
931 | { |
932 | $cancelDetails = $holdDetails['id'] . '|' . $holdDetails['item_id']; |
933 | return $cancelDetails; |
934 | } |
935 | } |