Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
95.33% |
286 / 300 |
|
61.11% |
11 / 18 |
CRAP | |
0.00% |
0 / 1 |
GeniePlus | |
95.33% |
286 / 300 |
|
61.11% |
11 / 18 |
41 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
validateConfiguration | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
5 | |||
init | |
66.67% |
8 / 12 |
|
0.00% |
0 / 1 |
2.15 | |||
renewAccessToken | |
100.00% |
15 / 15 |
|
100.00% |
1 / 1 |
2 | |||
callApiWithToken | |
100.00% |
21 / 21 |
|
100.00% |
1 / 1 |
3 | |||
getFieldFromApiRecord | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
extractDisplayValues | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
apiStatusRecordToArray | |
100.00% |
62 / 62 |
|
100.00% |
1 / 1 |
5 | |||
getTemplateQueryPath | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
sanitizeQueryParam | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getStatus | |
100.00% |
13 / 13 |
|
100.00% |
1 / 1 |
1 | |||
getStatuses | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getHolding | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getPurchaseHistory | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getConfig | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
patronLogin | |
97.83% |
45 / 46 |
|
0.00% |
0 / 1 |
2 | |||
getMyProfile | |
98.70% |
76 / 77 |
|
0.00% |
0 / 1 |
10 | |||
getMyTransactions | |
92.59% |
25 / 27 |
|
0.00% |
0 / 1 |
1.00 |
1 | <?php |
2 | |
3 | /** |
4 | * GeniePlus API driver |
5 | * |
6 | * PHP version 8 |
7 | * |
8 | * Copyright (C) Villanova University 2022. |
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 Demian Katz <demian.katz@villanova.edu> |
26 | * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License |
27 | * @link https://vufind.org/wiki/development:plugins:ils_drivers Wiki |
28 | */ |
29 | |
30 | namespace VuFind\ILS\Driver; |
31 | |
32 | use VuFind\Exception\ILS as ILSException; |
33 | |
34 | use function count; |
35 | use function in_array; |
36 | |
37 | /** |
38 | * GeniePlus API driver |
39 | * |
40 | * @category VuFind |
41 | * @package ILS_Drivers |
42 | * @author Demian Katz <demian.katz@villanova.edu> |
43 | * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License |
44 | * @link https://vufind.org/wiki/development:plugins:ils_drivers Wiki |
45 | */ |
46 | class GeniePlus extends AbstractAPI |
47 | { |
48 | /** |
49 | * Status messages indicating available items |
50 | * |
51 | * @var string[] |
52 | */ |
53 | protected $availableStatuses; |
54 | |
55 | /** |
56 | * Access token |
57 | * |
58 | * @var string |
59 | */ |
60 | protected $token = null; |
61 | |
62 | /** |
63 | * Factory function for constructing the SessionContainer. |
64 | * |
65 | * @var callable |
66 | */ |
67 | protected $sessionFactory; |
68 | |
69 | /** |
70 | * Session cache |
71 | * |
72 | * @var \Laminas\Session\Container |
73 | */ |
74 | protected $sessionCache; |
75 | |
76 | /** |
77 | * Constructor |
78 | * |
79 | * @param callable $sessionFactory Factory function returning SessionContainer |
80 | * object |
81 | */ |
82 | public function __construct(callable $sessionFactory) |
83 | { |
84 | $this->sessionFactory = $sessionFactory; |
85 | } |
86 | |
87 | /** |
88 | * Support method for init(): make sure we have a valid configuration. |
89 | * |
90 | * @return void |
91 | * @throws ILSException |
92 | */ |
93 | protected function validateConfiguration(): void |
94 | { |
95 | $missingConfigs = []; |
96 | $requiredApiSettings = [ |
97 | 'base_url', |
98 | 'catalog_template', |
99 | 'database', |
100 | 'loan_template', |
101 | 'oauth_id', |
102 | 'username', |
103 | 'password', |
104 | 'patron_template', |
105 | ]; |
106 | foreach ($requiredApiSettings as $setting) { |
107 | if (!isset($this->config['API'][$setting])) { |
108 | $missingConfigs[] = "API/$setting"; |
109 | } |
110 | } |
111 | if (!isset($this->config['Patron']['field']['cat_password'])) { |
112 | $missingConfigs[] = 'Patron/field/cat_password'; |
113 | } |
114 | if (!empty($missingConfigs)) { |
115 | throw new ILSException( |
116 | 'Missing required GeniePlus.ini configuration setting(s): ' |
117 | . implode(', ', $missingConfigs) |
118 | ); |
119 | } |
120 | } |
121 | |
122 | /** |
123 | * Initialize the driver. |
124 | * |
125 | * Validate configuration and perform all resource-intensive tasks needed to |
126 | * make the driver active. |
127 | * |
128 | * @return void |
129 | */ |
130 | public function init() |
131 | { |
132 | $this->validateConfiguration(); |
133 | $this->availableStatuses |
134 | = (array)($this->config['Item']['available_statuses'] ?? []); |
135 | $cacheNamespace = md5( |
136 | $this->config['API']['database'] . '|' . $this->config['API']['base_url'] |
137 | ); |
138 | $this->sessionCache = ($this->sessionFactory)($cacheNamespace); |
139 | if ($this->sessionCache->genieplus_token ?? false) { |
140 | $this->token = $this->sessionCache->genieplus_token; |
141 | $this->debug( |
142 | 'Token taken from cache: ' . substr($this->token, 0, 30) . '...' |
143 | ); |
144 | } |
145 | } |
146 | |
147 | /** |
148 | * Renew the OAuth access token needed by the API. |
149 | * |
150 | * @return void |
151 | * @throws ILSException |
152 | */ |
153 | protected function renewAccessToken(): void |
154 | { |
155 | $params = [ |
156 | 'client_id' => $this->config['API']['oauth_id'], |
157 | 'grant_type' => 'password', |
158 | 'database' => $this->config['API']['database'], |
159 | 'username' => $this->config['API']['username'], |
160 | 'password' => $this->config['API']['password'], |
161 | ]; |
162 | $headers = [ |
163 | 'Accept: application/json', |
164 | ]; |
165 | $response = $this->makeRequest('POST', '/_oauth/token', $params, $headers); |
166 | $result = json_decode($response->getBody()); |
167 | if (!isset($result->access_token)) { |
168 | throw new ILSException('No access token in API response.'); |
169 | } |
170 | $this->token = $this->sessionCache->genieplus_token = $result->access_token; |
171 | } |
172 | |
173 | /** |
174 | * Call the API, with an access token added to the headers; renew token as |
175 | * needed. |
176 | * |
177 | * @param string $method GET/POST/PUT/DELETE/etc |
178 | * @param string $path API path (with a leading /) |
179 | * @param array $params Parameters object to be sent as data |
180 | * @param array $headers Additional headers |
181 | * |
182 | * @return \Laminas\Http\Response |
183 | */ |
184 | protected function callApiWithToken( |
185 | $method = 'GET', |
186 | $path = '/', |
187 | $params = [], |
188 | $headers = [] |
189 | ) { |
190 | $headers[] = 'Accept: application/json'; |
191 | if (null === $this->token) { |
192 | $this->renewAccessToken(); |
193 | } |
194 | $authHeader = "Authorization: Bearer {$this->token}"; |
195 | $response = $this->makeRequest( |
196 | $method, |
197 | $path, |
198 | $params, |
199 | array_merge($headers, [$authHeader]), |
200 | [401, 403] |
201 | ); |
202 | if ($response->getStatusCode() > 400) { |
203 | $this->renewAccessToken(); |
204 | $authHeader = "Authorization: Bearer {$this->token}"; |
205 | $response = $this->makeRequest( |
206 | $method, |
207 | $path, |
208 | $params, |
209 | array_merge($headers, [$authHeader]) |
210 | ); |
211 | } |
212 | return $response; |
213 | } |
214 | |
215 | /** |
216 | * Extract a field from an API response. |
217 | * |
218 | * @param array $record Record containing field |
219 | * @param string $field Name of field to extract |
220 | * @param string $type Type of field being looked up (e.g. Item, Patron) |
221 | * |
222 | * @return array |
223 | */ |
224 | protected function getFieldFromApiRecord($record, $field, $type = 'Item') |
225 | { |
226 | $fieldName = $this->config[$type]['field'][$field] ?? ''; |
227 | return $record[$fieldName] ?? []; |
228 | } |
229 | |
230 | /** |
231 | * Extract display values from an API response field. |
232 | * |
233 | * @param array $field Array of values from API |
234 | * |
235 | * @return array |
236 | */ |
237 | protected function extractDisplayValues($field): array |
238 | { |
239 | $callback = function ($value) { |
240 | return $value['display']; |
241 | }; |
242 | return array_map($callback, $field); |
243 | } |
244 | |
245 | /** |
246 | * Extract holdings data from an API response. Return an array of arrays |
247 | * representing 852 fields (indexed by subfield code). |
248 | * |
249 | * @param array $record Record from API response |
250 | * |
251 | * @return array |
252 | */ |
253 | protected function apiStatusRecordToArray($record): array |
254 | { |
255 | $bibId = current( |
256 | $this->extractDisplayValues( |
257 | $this->getFieldFromApiRecord($record, 'id') |
258 | ) |
259 | ); |
260 | $barcodes = $this->extractDisplayValues( |
261 | $this->getFieldFromApiRecord($record, 'barcode') |
262 | ); |
263 | $callNos = $this->extractDisplayValues( |
264 | $this->getFieldFromApiRecord($record, 'callnumber') |
265 | ); |
266 | $dueDates = $this->extractDisplayValues( |
267 | $this->getFieldFromApiRecord($record, 'duedate') |
268 | ); |
269 | $locations = $this->extractDisplayValues( |
270 | $this->getFieldFromApiRecord($record, 'location') |
271 | ); |
272 | $statuses = $this->extractDisplayValues( |
273 | $this->getFieldFromApiRecord($record, 'status') |
274 | ); |
275 | $volumes = $this->extractDisplayValues( |
276 | $this->getFieldFromApiRecord($record, 'volume') |
277 | ); |
278 | $total = max( |
279 | [ |
280 | count($barcodes), |
281 | count($callNos), |
282 | count($dueDates), |
283 | count($locations), |
284 | count($statuses), |
285 | count($volumes), |
286 | ] |
287 | ); |
288 | $result = []; |
289 | for ($i = 0; $i < $total; $i++) { |
290 | $availability = in_array($statuses[$i] ?? '', $this->availableStatuses) |
291 | ? 1 : 0; |
292 | $result[] = [ |
293 | 'id' => $bibId, |
294 | 'availability' => $availability, |
295 | 'status' => $statuses[$i] ?? '', |
296 | 'location' => $locations[$i] ?? '', |
297 | 'reserve' => 'N', // not supported |
298 | 'callnumber' => $callNos[$i] ?? '', |
299 | 'duedate' => $dueDates[$i] ?? '', |
300 | 'number' => $volumes[$i] ?? ($i + 1), |
301 | 'barcode' => $barcodes[$i] ?? '', |
302 | ]; |
303 | } |
304 | $sortParts = array_map( |
305 | 'trim', |
306 | explode( |
307 | ' ', |
308 | strtolower($this->config['Item']['sort'] ?? 'none') |
309 | ) |
310 | ); |
311 | $sortField = $sortParts[0]; |
312 | if ($sortField !== 'none') { |
313 | $sortDirection = ($sortParts[1] ?? 'asc') === 'asc' ? 1 : -1; |
314 | $callback = function ($a, $b) use ($sortField, $sortDirection) { |
315 | return strnatcmp($a[$sortField] ?? '', $b[$sortField] ?? '') |
316 | * $sortDirection; |
317 | }; |
318 | usort($result, $callback); |
319 | } |
320 | return $result; |
321 | } |
322 | |
323 | /** |
324 | * Get the search path to query a template. |
325 | * |
326 | * @param string $template Name of template to query |
327 | * |
328 | * @return string |
329 | */ |
330 | protected function getTemplateQueryPath(string $template): string |
331 | { |
332 | $database = $this->config['API']['database']; |
333 | return "/_rest/databases/$database/templates/$template/search-result"; |
334 | } |
335 | |
336 | /** |
337 | * Sanitize a value for inclusion as a single-quoted value in a query string. |
338 | * |
339 | * @param string $value Value to sanitize |
340 | * |
341 | * @return string Sanitized value |
342 | */ |
343 | protected function sanitizeQueryParam(string $value): string |
344 | { |
345 | // The query language used by GeniePlus doubles quotes to escape them. |
346 | return str_replace("'", "''", $value); |
347 | } |
348 | |
349 | /** |
350 | * Get Status |
351 | * |
352 | * This is responsible for retrieving the status information of a certain |
353 | * record. |
354 | * |
355 | * @param string $id The record id to retrieve the holdings for |
356 | * |
357 | * @return mixed On success, an associative array with the following keys: |
358 | * id, availability (boolean), status, location, reserve, callnumber. |
359 | */ |
360 | public function getStatus($id) |
361 | { |
362 | $template = $this->config['API']['catalog_template']; |
363 | $path = $this->getTemplateQueryPath($template); |
364 | $idField = $this->config['Item']['field']['id'] ?? 'UniqRecNum'; |
365 | $safeId = $this->sanitizeQueryParam($id); |
366 | $params = [ |
367 | 'page-size' => 100, |
368 | 'page' => 0, |
369 | 'fields' => implode(',', $this->config['Item']['field'] ?? []), |
370 | 'command' => "$idField == '$safeId'", |
371 | ]; |
372 | $json = $this->callApiWithToken('GET', $path, $params)->getBody(); |
373 | $response = json_decode($json, true); |
374 | return $this->apiStatusRecordToArray($response['records'][0] ?? []); |
375 | } |
376 | |
377 | /** |
378 | * Get Statuses |
379 | * |
380 | * This is responsible for retrieving the status information for a |
381 | * collection of records. |
382 | * |
383 | * @param array $ids The array of record ids to retrieve the status for |
384 | * |
385 | * @return mixed An array of getStatus() return values on success. |
386 | */ |
387 | public function getStatuses($ids) |
388 | { |
389 | return array_map([$this, 'getStatus'], $ids); |
390 | } |
391 | |
392 | /** |
393 | * Get Holding |
394 | * |
395 | * This is responsible for retrieving the holding information of a certain |
396 | * record. |
397 | * |
398 | * @param string $id The record id to retrieve the holdings for |
399 | * @param array $patron Patron data |
400 | * @param array $options Extra options (not currently used) |
401 | * |
402 | * @return mixed On success, an associative array with the following keys: |
403 | * id, availability (boolean), status, location, reserve, callnumber, duedate, |
404 | * number, barcode. |
405 | * |
406 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) |
407 | */ |
408 | public function getHolding($id, array $patron = null, array $options = []) |
409 | { |
410 | return $this->getStatus($id); |
411 | } |
412 | |
413 | /** |
414 | * Get Purchase History |
415 | * |
416 | * This is responsible for retrieving the acquisitions history data for the |
417 | * specific record (usually recently received issues of a serial). |
418 | * |
419 | * @param string $id The record id to retrieve the info for |
420 | * |
421 | * @return mixed An array with the acquisitions data on success. |
422 | * |
423 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) |
424 | */ |
425 | public function getPurchaseHistory($id) |
426 | { |
427 | // Not supported here: |
428 | return []; |
429 | } |
430 | |
431 | /** |
432 | * Public Function which retrieves feature-specific settings from the |
433 | * driver ini file. |
434 | * |
435 | * @param string $function The name of the feature to be checked |
436 | * @param array $params Optional feature-specific parameters (array) |
437 | * |
438 | * @return array An array with key-value pairs. |
439 | * |
440 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) |
441 | */ |
442 | public function getConfig($function, $params = []) |
443 | { |
444 | if ('getMyTransactions' === $function) { |
445 | return $this->config['Transactions'] ?? [ |
446 | 'max_results' => 100, |
447 | ]; |
448 | } |
449 | |
450 | return false; |
451 | } |
452 | |
453 | /** |
454 | * Patron Login |
455 | * |
456 | * This is responsible for authenticating a patron against the catalog. |
457 | * |
458 | * @param string $username The patron username |
459 | * @param string $password The patron password |
460 | * |
461 | * @throws ILSException |
462 | * @return mixed Associative array of patron info on successful login, |
463 | * null on unsuccessful login. |
464 | */ |
465 | public function patronLogin($username, $password) |
466 | { |
467 | $template = $this->config['API']['patron_template']; |
468 | $path = $this->getTemplateQueryPath($template); |
469 | $userField = $this->config['Patron']['field']['cat_username'] ?? 'Email'; |
470 | $passField = $this->config['Patron']['field']['cat_password']; |
471 | $safeUser = $this->sanitizeQueryParam($username); |
472 | $safePass = $this->sanitizeQueryParam($password); |
473 | $idField = $this->config['Patron']['field']['id'] ?? 'ID'; |
474 | $nameField = $this->config['Patron']['field']['name'] ?? 'Name'; |
475 | $emailField = $this->config['Patron']['field']['email'] ?? 'Email'; |
476 | $params = [ |
477 | 'page-size' => 1, |
478 | 'page' => 0, |
479 | 'fields' => implode(',', [$idField, $nameField, $emailField]), |
480 | 'command' => "$userField == '$safeUser' AND $passField == '$safePass'", |
481 | ]; |
482 | $json = $this->callApiWithToken('GET', $path, $params)->getBody(); |
483 | $response = json_decode($json, true); |
484 | $user = $response['records'][0] ?? []; |
485 | if (empty($user)) { |
486 | return null; |
487 | } |
488 | $id = current( |
489 | $this->extractDisplayValues( |
490 | $this->getFieldFromApiRecord($user, 'id', 'Patron') |
491 | ) |
492 | ); |
493 | $email = current( |
494 | $this->extractDisplayValues( |
495 | $this->getFieldFromApiRecord($user, 'email', 'Patron') |
496 | ) |
497 | ); |
498 | $name = current( |
499 | $this->extractDisplayValues( |
500 | $this->getFieldFromApiRecord($user, 'name', 'Patron') |
501 | ) |
502 | ); |
503 | [$last, $first] = explode(',', $name, 2); |
504 | return [ |
505 | 'id' => $id, |
506 | 'firstname' => trim($first), |
507 | 'lastname' => trim($last), |
508 | 'cat_username' => trim($username), |
509 | 'cat_password' => trim($password), |
510 | 'email' => $email, |
511 | 'major' => null, |
512 | 'college' => null, |
513 | ]; |
514 | } |
515 | |
516 | /** |
517 | * Get Patron Profile |
518 | * |
519 | * This is responsible for retrieving the profile for a specific patron. |
520 | * |
521 | * @param array $patron The patron array |
522 | * |
523 | * @return array Array of the patron's profile data on success. |
524 | */ |
525 | public function getMyProfile($patron) |
526 | { |
527 | $template = $this->config['API']['patron_template']; |
528 | $path = $this->getTemplateQueryPath($template); |
529 | $idField = $this->config['Patron']['field']['id'] ?? 'ID'; |
530 | $safeId = $this->sanitizeQueryParam($patron['id']); |
531 | $fields = [ |
532 | $this->config['Patron']['field']['address1'] ?? 'Address1', |
533 | $this->config['Patron']['field']['address2'] ?? 'Address2', |
534 | $this->config['Patron']['field']['zip'] ?? 'ZipCode', |
535 | $this->config['Patron']['field']['city'] ?? 'City', |
536 | $this->config['Patron']['field']['state'] ?? 'StateProv.CodeDesc', |
537 | $this->config['Patron']['field']['country'] ?? 'Country.CodeDesc', |
538 | $this->config['Patron']['field']['phone'] ?? 'PhoneNumber', |
539 | $this->config['Patron']['field']['expiration_date'] ?? 'ExpiryDate', |
540 | ]; |
541 | $params = [ |
542 | 'page-size' => 1, |
543 | 'page' => 0, |
544 | 'fields' => implode(',', $fields), |
545 | 'command' => "$idField == '$safeId'", |
546 | ]; |
547 | $json = $this->callApiWithToken('GET', $path, $params)->getBody(); |
548 | $response = json_decode($json, true); |
549 | $user = $response['records'][0] ?? []; |
550 | if (empty($user)) { |
551 | throw new \Exception("Unable to fetch patron $safeId"); |
552 | } |
553 | $addr1 = current( |
554 | $this->extractDisplayValues( |
555 | $this->getFieldFromApiRecord($user, 'address1', 'Patron') |
556 | ) |
557 | ); |
558 | $addr2 = current( |
559 | $this->extractDisplayValues( |
560 | $this->getFieldFromApiRecord($user, 'address2', 'Patron') |
561 | ) |
562 | ); |
563 | $zip = current( |
564 | $this->extractDisplayValues( |
565 | $this->getFieldFromApiRecord($user, 'zip', 'Patron') |
566 | ) |
567 | ); |
568 | $city = current( |
569 | $this->extractDisplayValues( |
570 | $this->getFieldFromApiRecord($user, 'city', 'Patron') |
571 | ) |
572 | ); |
573 | $state = current( |
574 | $this->extractDisplayValues( |
575 | $this->getFieldFromApiRecord($user, 'state', 'Patron') |
576 | ) |
577 | ); |
578 | $country = current( |
579 | $this->extractDisplayValues( |
580 | $this->getFieldFromApiRecord($user, 'country', 'Patron') |
581 | ) |
582 | ); |
583 | $phone = current( |
584 | $this->extractDisplayValues( |
585 | $this->getFieldFromApiRecord($user, 'phone', 'Patron') |
586 | ) |
587 | ); |
588 | $expirationDate = current( |
589 | $this->extractDisplayValues( |
590 | $this->getFieldFromApiRecord($user, 'expiration_date', 'Patron') |
591 | ) |
592 | ); |
593 | $cityAndState = trim($city . (!empty($city) ? ', ' : '') . $state); |
594 | return [ |
595 | 'firstname' => $patron['firstname'], |
596 | 'lastname' => $patron['lastname'], |
597 | 'address1' => empty($addr1) ? null : $addr1, |
598 | 'address2' => empty($addr2) ? null : $addr2, |
599 | 'zip' => empty($zip) ? null : $zip, |
600 | 'city' => empty($city) ? null : $cityAndState, |
601 | 'country' => empty($country) ? null : $country, |
602 | 'phone' => empty($phone) ? null : $phone, |
603 | 'expiration_date' => empty($expirationDate) ? null : $expirationDate, |
604 | ]; |
605 | } |
606 | |
607 | /** |
608 | * Get Patron Transactions |
609 | * |
610 | * This is responsible for retrieving all transactions (i.e. checked out items) |
611 | * by a specific patron. |
612 | * |
613 | * @param array $patron The patron array from patronLogin |
614 | * @param array $params Parameters |
615 | * |
616 | * @return mixed Array of the patron's transactions on success. |
617 | */ |
618 | public function getMyTransactions($patron, $params = []) |
619 | { |
620 | $patronTemplate = $this->config['API']['patron_template']; |
621 | $loanTemplate = $this->config['API']['loan_template']; |
622 | $path = $this->getTemplateQueryPath($loanTemplate); |
623 | $idField = $patronTemplate . '.' |
624 | . ($this->config['Patron']['field']['id'] ?? 'ID'); |
625 | $safeId = $this->sanitizeQueryParam($patron['id']); |
626 | $barcodeField = $this->config['Item']['field']['barcode'] |
627 | ?? 'Inventory.Barcode'; |
628 | $bibIdField = $this->config['Loan']['field']['bib_id'] |
629 | ?? 'Inventory.Inventory@Catalog.UniqRecNum'; |
630 | $dueField = $this->config['Loan']['field']['duedate'] ?? 'ClaimDate'; |
631 | $archiveField = $this->config['Loan']['field']['archive'] ?? 'Archive'; |
632 | $fields = [$barcodeField, $bibIdField, $dueField]; |
633 | $params = [ |
634 | 'page-size' => $params['limit'] ?? 100, |
635 | 'page' => ($params['page'] ?? 1) - 1, |
636 | 'fields' => implode(',', $fields), |
637 | 'command' => "$idField == '$safeId' AND $archiveField == 'No'", |
638 | ]; |
639 | $json = $this->callApiWithToken('GET', $path, $params)->getBody(); |
640 | $response = json_decode($json, true); |
641 | $callback = function ($entry) use ($barcodeField, $bibIdField, $dueField) { |
642 | return [ |
643 | 'id' => $entry[$bibIdField][0]['display'] ?? null, |
644 | 'item_id' => $entry[$barcodeField][0]['display'] ?? null, |
645 | 'duedate' => $entry[$dueField][0]['display'] ?? null, |
646 | ]; |
647 | }; |
648 | return [ |
649 | 'count' => $response['total'] ?? 0, |
650 | 'records' => array_map($callback, $response['records'] ?? []), |
651 | ]; |
652 | } |
653 | } |