Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.49% |
2 / 405 |
|
0.00% |
0 / 28 |
CRAP | |
0.00% |
0 / 1 |
Polaris | |
0.49% |
2 / 405 |
|
0.00% |
0 / 28 |
5179.58 | |
0.00% |
0 / 1 |
init | |
22.22% |
2 / 9 |
|
0.00% |
0 / 1 |
7.23 | |||
makeRequest | |
0.00% |
0 / 32 |
|
0.00% |
0 / 1 |
30 | |||
formatJSONTime | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
6 | |||
encodeJSONTime | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
getMyHolds | |
0.00% |
0 / 23 |
|
0.00% |
0 / 1 |
12 | |||
getStatus | |
0.00% |
0 / 29 |
|
0.00% |
0 / 1 |
56 | |||
getStatuses | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
6 | |||
getConfig | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
getHolding | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
placeHold | |
0.00% |
0 / 39 |
|
0.00% |
0 / 1 |
30 | |||
getPickUpLocations | |
0.00% |
0 / 15 |
|
0.00% |
0 / 1 |
20 | |||
getDefaultPickUpLocation | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getPurchaseHistory | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getNewItems | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
findReserves | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
patronLogin | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
6 | |||
getMyFines | |
0.00% |
0 / 19 |
|
0.00% |
0 / 1 |
6 | |||
getMyProfile | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
2 | |||
getMyTransactions | |
0.00% |
0 / 21 |
|
0.00% |
0 / 1 |
12 | |||
renewMyItems | |
0.00% |
0 / 39 |
|
0.00% |
0 / 1 |
20 | |||
getRenewDetails | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
cancelHolds | |
0.00% |
0 / 24 |
|
0.00% |
0 / 1 |
12 | |||
getCancelHoldDetails | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getCheckoutHistory | |
0.00% |
0 / 35 |
|
0.00% |
0 / 1 |
30 | |||
getHoldCount | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
20 | |||
suspendHolds | |
0.00% |
0 / 29 |
|
0.00% |
0 / 1 |
12 | |||
getSuspendHoldDetails | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
reactivateHolds | |
0.00% |
0 / 30 |
|
0.00% |
0 / 1 |
12 |
1 | <?php |
2 | |
3 | /** |
4 | * Polaris ILS Driver |
5 | * |
6 | * PHP version 8 |
7 | * |
8 | * |
9 | * This program is free software; you can redistribute it and/or modify |
10 | * it under the terms of the GNU General Public License version 2, |
11 | * as published by the Free Software Foundation. |
12 | * |
13 | * This program is distributed in the hope that it will be useful, |
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
16 | * GNU General Public License for more details. |
17 | * |
18 | * You should have received a copy of the GNU General Public License |
19 | * along with this program; if not, write to the Free Software |
20 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
21 | * |
22 | * @category VuFind |
23 | * @package ILS_Drivers |
24 | * @author BookSite <vufind-tech@lists.sourceforge.net> |
25 | * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License |
26 | * @link https://vufind.org/wiki/development:plugins:ils_drivers Wiki |
27 | */ |
28 | |
29 | namespace VuFind\ILS\Driver; |
30 | |
31 | use VuFind\Exception\ILS as ILSException; |
32 | |
33 | use function count; |
34 | use function intval; |
35 | use function strlen; |
36 | |
37 | /** |
38 | * VuFind Connector for Polaris |
39 | * |
40 | * Based on Polaris 1.4 API |
41 | * |
42 | * @category VuFind |
43 | * @package ILS_Drivers |
44 | * @author BookSite <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 Polaris extends AbstractBase implements \VuFindHttp\HttpServiceAwareInterface |
49 | { |
50 | use \VuFindHttp\HttpServiceAwareTrait; |
51 | |
52 | /** |
53 | * Web services host |
54 | * |
55 | * @var string |
56 | */ |
57 | protected $ws_host; |
58 | |
59 | /** |
60 | * Web services application path |
61 | * |
62 | * @var string |
63 | */ |
64 | protected $ws_app; |
65 | |
66 | /** |
67 | * Web services ID |
68 | * |
69 | * @var string |
70 | */ |
71 | protected $ws_api_id; |
72 | |
73 | /** |
74 | * Web services key |
75 | * |
76 | * @var string |
77 | */ |
78 | protected $ws_api_key; |
79 | |
80 | /** |
81 | * Default pick up location |
82 | * |
83 | * @var string |
84 | */ |
85 | protected $defaultPickUpLocation; |
86 | |
87 | /** |
88 | * Web services requesting organization ID |
89 | * |
90 | * @var string |
91 | */ |
92 | protected $ws_requestingorgid; |
93 | |
94 | /** |
95 | * Initialize the driver. |
96 | * |
97 | * Validate configuration and perform all resource-intensive tasks needed to |
98 | * make the driver active. |
99 | * |
100 | * @throws ILSException |
101 | * @return void |
102 | */ |
103 | public function init() |
104 | { |
105 | if (empty($this->config) || !isset($this->config['PAPI'])) { |
106 | throw new ILSException('Configuration needs to be set.'); |
107 | } |
108 | |
109 | // Define Polaris PAPI parameters |
110 | $this->ws_host = $this->config['PAPI']['ws_host']; |
111 | $this->ws_app = $this->config['PAPI']['ws_app']; |
112 | $this->ws_api_id = $this->config['PAPI']['ws_api_id']; |
113 | $this->ws_api_key = $this->config['PAPI']['ws_api_key']; |
114 | $this->ws_requestingorgid = $this->config['PAPI']['ws_requestingorgid']; |
115 | $this->defaultPickUpLocation |
116 | = $this->config['Holds']['defaultPickUpLocation'] ?? null; |
117 | } |
118 | |
119 | /** |
120 | * Make Request |
121 | * |
122 | * Makes a request to the Polaris Restful API |
123 | * |
124 | * @param string $api_query Query string for request |
125 | * @param string $http_method HTTP method (default = GET) |
126 | * @param string $patronpassword Patron password (optional) |
127 | * @param bool $json Optional JSON attachment |
128 | * |
129 | * @throws ILSException |
130 | * @return obj |
131 | */ |
132 | protected function makeRequest( |
133 | $api_query, |
134 | $http_method = 'GET', |
135 | $patronpassword = '', |
136 | $json = false |
137 | ) { |
138 | // auth has to be in GMT, otherwise use config-level TZ |
139 | $site_config_TZ = date_default_timezone_get(); |
140 | date_default_timezone_set('GMT'); |
141 | $date = date('D, d M Y H:i:s T'); |
142 | date_default_timezone_set($site_config_TZ); |
143 | |
144 | $url = $this->ws_host . $this->ws_app . $api_query; |
145 | |
146 | $signature_text = $http_method . $url . $date . $patronpassword; |
147 | $signature = base64_encode( |
148 | hash_hmac('sha1', $signature_text, $this->ws_api_key, true) |
149 | ); |
150 | |
151 | $auth_token = "PWS {$this->ws_api_id}:$signature"; |
152 | $http_headers = [ |
153 | 'Content-type: application/json', |
154 | 'Accept: application/json', |
155 | "PolarisDate: $date", |
156 | "Authorization: $auth_token", |
157 | ]; |
158 | |
159 | try { |
160 | $client = $this->httpService->createClient($url); |
161 | |
162 | // Attach JSON if necessary |
163 | $json_data = null; |
164 | if ($json !== false) { |
165 | $json_data = json_encode($json); |
166 | $client->setRawBody($json_data); |
167 | $client->setEncType('application/json'); |
168 | } |
169 | |
170 | // httpService doesn't explicitly support PUT, so add this: |
171 | if ($http_method == 'PUT') { |
172 | $http_headers[] = 'Content-Length: ' . strlen($json_data); |
173 | } |
174 | $client->setHeaders($http_headers); |
175 | $client->setMethod($http_method); |
176 | $result = $client->send(); |
177 | } catch (\Exception $e) { |
178 | $this->throwAsIlsException($e); |
179 | } |
180 | |
181 | if (!$result->isSuccess()) { |
182 | throw new ILSException('HTTP error'); |
183 | } |
184 | |
185 | return json_decode($result->getBody()); |
186 | } |
187 | |
188 | /** |
189 | * Return human-readable date from text like Date(1360051200000-0800) |
190 | * |
191 | * @param string $jsontime Input |
192 | * |
193 | * @return string |
194 | */ |
195 | public function formatJSONTime($jsontime) |
196 | { |
197 | preg_match('/Date\((\d+)\-(\d){2}(\d){2}\)/', $jsontime, $matches); |
198 | if (count($matches) > 0) { |
199 | $matchestmp = intval($matches[1] / 1000); |
200 | $date = date('n-j-Y', $matchestmp); |
201 | } else { |
202 | $date = 'n/a'; |
203 | } |
204 | return $date; |
205 | } |
206 | |
207 | /** |
208 | * Encode from human-readable date to text like Date(1360051200000-0800) |
209 | * |
210 | * @param string $date Input |
211 | * |
212 | * @return string |
213 | */ |
214 | public function encodeJSONTime($date) |
215 | { |
216 | // auth has to be in GMT, otherwise use config-level TZ |
217 | //$site_config_TZ = date_default_timezone_get(); |
218 | //date_default_timezone_set('GMT'); |
219 | $unix_time = strtotime($date); |
220 | //date_default_timezone_set($site_config_TZ); |
221 | |
222 | $json_time = '/Date(' . $unix_time . '000)/'; |
223 | return $json_time; |
224 | } |
225 | |
226 | /** |
227 | * Get Patron Holds |
228 | * |
229 | * This is responsible for retrieving all holds by a specific patron. |
230 | * |
231 | * @param array $patron The patron array from patronLogin |
232 | * |
233 | * @return mixed Array of the patron's holds on success. |
234 | */ |
235 | public function getMyHolds($patron) |
236 | { |
237 | $holds = []; |
238 | $response = $this->makeRequest( |
239 | "patron/{$patron['cat_username']}/holdrequests/all", |
240 | 'GET', |
241 | $patron['cat_password'] |
242 | ); |
243 | $holds_response_array = $response->PatronHoldRequestsGetRows; |
244 | foreach ($holds_response_array as $holds_response) { |
245 | // only display item if it is NOT expired |
246 | if ($holds_response->StatusID > 8) { |
247 | continue; |
248 | } |
249 | |
250 | $create = $this->formatJSONTime($holds_response->ActivationDate); |
251 | $expire = $this->formatJSONTime($holds_response->ExpirationDate); |
252 | |
253 | $holds[] = [ |
254 | 'type' => $holds_response->StatusDescription, |
255 | 'id' => $holds_response->BibID, |
256 | 'location' => $holds_response->PickupBranchName, |
257 | 'reqnum' => $holds_response->HoldRequestID, |
258 | 'expire' => $expire, |
259 | 'create' => $create, |
260 | 'position' => $holds_response->QueuePosition, |
261 | 'title' => $holds_response->Title, |
262 | ]; |
263 | } |
264 | return $holds; |
265 | } |
266 | |
267 | /** |
268 | * Get Status |
269 | * |
270 | * This is responsible for retrieving the status information of a certain |
271 | * record. |
272 | * |
273 | * @param string $id The record id to retrieve the holdings for |
274 | * |
275 | * @return mixed On success, an associative array with the following keys: |
276 | * id, availability (boolean), status, location, reserve, callnumber. |
277 | */ |
278 | public function getStatus($id) |
279 | { |
280 | $holding = []; |
281 | $response = $this->makeRequest("bib/$id/holdings"); |
282 | $holdings_response_array = $response->BibHoldingsGetRows; |
283 | |
284 | $copy_count = 0; |
285 | foreach ($holdings_response_array as $holdings_response) { |
286 | //$holdings_response = $holdings_response_array[0]; |
287 | $copy_count++; |
288 | |
289 | $availability = 0; |
290 | if ( |
291 | ($holdings_response->CircStatus == 'In') |
292 | || ($holdings_response->CircStatus == 'Just Returned') |
293 | || ($holdings_response->CircStatus == 'On Shelf') |
294 | || ($holdings_response->CircStatus == 'Available - Check shelves') |
295 | ) { |
296 | $availability = 1; |
297 | } |
298 | |
299 | $duedate = ''; |
300 | if ($holdings_response->DueDate) { |
301 | $duedate = date('n-j-Y', strtotime($holdings_response->DueDate)); |
302 | } |
303 | |
304 | $holding[] = [ |
305 | 'availability' => $availability, |
306 | 'id' => $id, |
307 | 'status' => $holdings_response->CircStatus, |
308 | 'location' => $holdings_response->LocationName, |
309 | //'reserve' => 'No', |
310 | 'callnumber' => $holdings_response->CallNumber, |
311 | 'duedate' => $duedate, |
312 | //'number' => $holdings_response->ItemsIn, |
313 | 'number' => $copy_count, |
314 | 'barcode' => $holdings_response->Barcode, |
315 | 'shelf_location' => $holdings_response->ShelfLocation, |
316 | 'collection_name' => $holdings_response->CollectionName, |
317 | //'per_item_holdable' => $per_item_holdable, |
318 | //'designation' => $designation, |
319 | 'holdable' => $holdings_response->Holdable, |
320 | ]; |
321 | } |
322 | return $holding; |
323 | } |
324 | |
325 | /** |
326 | * Get Statuses |
327 | * |
328 | * This is responsible for retrieving the status information for a |
329 | * collection of records. |
330 | * |
331 | * @param array $ids The array of record ids to retrieve the status for |
332 | * |
333 | * @return mixed An array of getStatus() return values on success. |
334 | */ |
335 | public function getStatuses($ids) |
336 | { |
337 | $items = []; |
338 | $count = 0; |
339 | foreach ($ids as $id) { |
340 | $items[$count] = $this->getStatus($id); |
341 | $count++; |
342 | } |
343 | return $items; |
344 | } |
345 | |
346 | /** |
347 | * Public Function which retrieves renew, hold and cancel settings from the |
348 | * driver ini file. |
349 | * |
350 | * @param string $function The name of the feature to be checked |
351 | * @param array $params Optional feature-specific parameters (array) |
352 | * |
353 | * @return array An array with key-value pairs. |
354 | * |
355 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) |
356 | */ |
357 | public function getConfig($function, $params = []) |
358 | { |
359 | if (isset($this->config[$function])) { |
360 | $functionConfig = $this->config[$function]; |
361 | } else { |
362 | $functionConfig = false; |
363 | } |
364 | return $functionConfig; |
365 | } |
366 | |
367 | /** |
368 | * Get Holding |
369 | * |
370 | * This is responsible for retrieving the holding information of a certain |
371 | * record. |
372 | * |
373 | * @param string $id The record id to retrieve the holdings for |
374 | * @param array $patron Patron data |
375 | * @param array $options Extra options (not currently used) |
376 | * |
377 | * @return mixed On success, an associative array with the following |
378 | * keys: id, availability (boolean), status, location, reserve, callnumber, |
379 | * duedate, number, barcode. |
380 | * |
381 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) |
382 | */ |
383 | public function getHolding($id, array $patron = null, array $options = []) |
384 | { |
385 | return $this->getStatus($id); |
386 | } |
387 | |
388 | /** |
389 | * Place Hold |
390 | * |
391 | * Attempts to place a hold or recall on a particular item and returns |
392 | * an array with result details. |
393 | * |
394 | * @param array $holdDetails An array of item and patron data |
395 | * |
396 | * @return mixed An array of data on the request including |
397 | * whether or not it was successful and a system message (if available) |
398 | */ |
399 | public function placeHold($holdDetails) |
400 | { |
401 | // what do workstation & userid really mean in this context? |
402 | $workstationid = '1'; |
403 | $userid = '1'; |
404 | |
405 | // all activations are for now(), for now. |
406 | // microtime is msec or sec?? seems to have changed |
407 | $activationdate = '/Date(' . intval(microtime(true) * 1000) . ')/'; |
408 | if (empty($holdDetails['barcode'])) { |
409 | $holdDetails['barcode'] = ''; |
410 | } |
411 | |
412 | $jsonrequest = [ |
413 | 'PatronID' => $holdDetails['patron']['id'], |
414 | 'BibID' => $holdDetails['id'], |
415 | 'ItemBarcode' => $holdDetails['barcode'], |
416 | 'VolumeNumber' => '', |
417 | 'Designation' => '', |
418 | 'PickupOrgID' => $holdDetails['pickUpLocation'], |
419 | 'IsBorrowByMail' => '0', |
420 | 'PatronNotes' => $holdDetails['comment'], |
421 | 'ActivationDate' => $activationdate, |
422 | 'WorkstationID' => $workstationid, |
423 | 'UserID' => $userid, |
424 | 'RequestingOrgID' => $this->ws_requestingorgid, |
425 | 'TargetGUID' => '', |
426 | ]; |
427 | |
428 | $response = $this->makeRequest('holdrequest', 'POST', '', $jsonrequest); |
429 | |
430 | if ($response->StatusValue == 1) { |
431 | return [ 'success' => true, 'sysMessage' => $response->Message ]; |
432 | } elseif ($response->StatusValue == 5) { |
433 | // auto say "yes" to Conditional: Accept even with existing holds |
434 | // response |
435 | $reply_jsonrequest = [ |
436 | // apparent bug in API, TxnGroupQualifer missing final "i" |
437 | 'TxnGroupQualifier' => $response->TxnGroupQualifer, |
438 | 'TxnQualifier' => $response->TxnQualifier, |
439 | 'RequestingOrgID' => $this->ws_requestingorgid, |
440 | 'Answer' => 1, |
441 | 'State' => 5, |
442 | ]; |
443 | |
444 | $reply_response = $this->makeRequest( |
445 | "holdrequest/{$response->RequestGUID}", |
446 | 'PUT', |
447 | '', |
448 | $reply_jsonrequest |
449 | ); |
450 | |
451 | if ($reply_response->StatusValue == 1) { |
452 | // auto-reply success |
453 | return [ 'success' => true, 'sysMessage' => $response->Message ]; |
454 | } else { |
455 | return [ 'success' => false, 'sysMessage' => $response->Message ]; |
456 | } |
457 | } else { |
458 | return [ 'success' => false, 'sysMessage' => $response->Message ]; |
459 | } |
460 | } |
461 | |
462 | /** |
463 | * Get Pick Up Locations |
464 | * |
465 | * This is responsible for getting a list of valid library locations for |
466 | * holds / recall retrieval |
467 | * |
468 | * @param array $patron Patron information returned by the patronLogin |
469 | * method. |
470 | * @param array $holdDetails Optional array, only passed in when getting a list |
471 | * in the context of placing or editing a hold. When placing a hold, it contains |
472 | * most of the same values passed to placeHold, minus the patron data. When |
473 | * editing a hold it contains all the hold information returned by getMyHolds. |
474 | * May be used to limit the pickup options or may be ignored. The driver must |
475 | * not add new options to the return array based on this data or other areas of |
476 | * VuFind may behave incorrectly. |
477 | * |
478 | * @throws ILSException |
479 | * @return array An array of associative arrays with locationID |
480 | * and locationDisplay keys |
481 | * |
482 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) |
483 | */ |
484 | public function getPickUpLocations($patron = false, $holdDetails = null) |
485 | { |
486 | $locations = []; |
487 | if (isset($this->ws_pickUpLocations)) { |
488 | // hardcoded pickup locations in the .ini file? or... |
489 | foreach ($this->ws_pickUpLocations as $code => $library) { |
490 | $locations[] = [ |
491 | 'locationID' => $code, |
492 | 'locationDisplay' => $library, |
493 | ]; |
494 | } |
495 | } else { |
496 | // we get them from the API |
497 | $response = $this->makeRequest('organizations/branch'); |
498 | $locations_response_array = $response->OrganizationsGetRows; |
499 | foreach ($locations_response_array as $location_response) { |
500 | $locations[] = [ |
501 | 'locationID' => $location_response->OrganizationID, |
502 | 'locationDisplay' => $location_response->Name, |
503 | ]; |
504 | } |
505 | } |
506 | return $locations; |
507 | } |
508 | |
509 | /** |
510 | * Get Default Pick Up Location |
511 | * |
512 | * Returns the default pick up location set in VoyagerRestful.ini |
513 | * |
514 | * @param array $patron Patron information returned by the patronLogin |
515 | * method. |
516 | * @param array $holdDetails Optional array, only passed in when getting a list |
517 | * in the context of placing a hold; contains most of the same values passed to |
518 | * placeHold, minus the patron data. May be used to limit the pickup options |
519 | * or may be ignored. |
520 | * |
521 | * @return string The default pickup location for the patron. |
522 | * |
523 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) |
524 | */ |
525 | public function getDefaultPickUpLocation($patron = false, $holdDetails = null) |
526 | { |
527 | return $this->defaultPickUpLocation; |
528 | } |
529 | |
530 | /** |
531 | * Get Purchase History |
532 | * |
533 | * This is responsible for retrieving the acquisitions history data for the |
534 | * specific record (usually recently received issues of a serial). |
535 | * |
536 | * @param string $id The record id to retrieve the info for |
537 | * |
538 | * @return mixed An array with the acquisitions data on success. |
539 | */ |
540 | public function getPurchaseHistory($id) |
541 | { |
542 | return []; |
543 | } |
544 | |
545 | /** |
546 | * Get New Items |
547 | * |
548 | * Retrieve the IDs of items recently added to the catalog. |
549 | * |
550 | * @param int $page Page number of results to retrieve (counting starts at 1) |
551 | * @param int $limit The size of each page of results to retrieve |
552 | * @param int $daysOld The maximum age of records to retrieve in days (max. 30) |
553 | * @param int $fundId optional fund ID to use for limiting results (use a value |
554 | * returned by getFunds, or exclude for no limit); note that "fund" may be a |
555 | * misnomer - if funds are not an appropriate way to limit your new item |
556 | * results, you can return a different set of values from getFunds. The |
557 | * important thing is that this parameter supports an ID returned by getFunds, |
558 | * whatever that may mean. |
559 | * |
560 | * @return array Associative array with 'count' and 'results' keys |
561 | * |
562 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) |
563 | */ |
564 | public function getNewItems($page, $limit, $daysOld, $fundId = null) |
565 | { |
566 | return ['count' => 0, 'results' => []]; |
567 | } |
568 | |
569 | /** |
570 | * Find Reserves |
571 | * |
572 | * Obtain information on course reserves. |
573 | * |
574 | * @param string $course ID from getCourses (empty string to match all) |
575 | * @param string $inst ID from getInstructors (empty string to match all) |
576 | * @param string $dept ID from getDepartments (empty string to match all) |
577 | * |
578 | * @return mixed An array of associative arrays representing reserve items. |
579 | * |
580 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) |
581 | */ |
582 | public function findReserves($course, $inst, $dept) |
583 | { |
584 | return []; |
585 | } |
586 | |
587 | /** |
588 | * Patron Login |
589 | * |
590 | * This is responsible for authenticating a patron against the catalog. |
591 | * |
592 | * @param string $username The patron username |
593 | * @param string $password The patron password |
594 | * |
595 | * @return mixed Associative array of patron info on successful login, |
596 | * null on unsuccessful login. |
597 | */ |
598 | public function patronLogin($username, $password) |
599 | { |
600 | // username == barcode |
601 | $response = $this->makeRequest("patron/$username", 'GET', "$password"); |
602 | |
603 | if (!$response->ValidPatron) { |
604 | return null; |
605 | } |
606 | |
607 | $user = []; |
608 | |
609 | $user['id'] = $response->PatronID; |
610 | $user['firstname'] = null; |
611 | $user['lastname'] = null; |
612 | $user['cat_username'] = $response->PatronBarcode; |
613 | $user['cat_password'] = $password; |
614 | $user['email'] = null; |
615 | $user['major'] = null; |
616 | $user['college'] = null; |
617 | |
618 | return $user; |
619 | } |
620 | |
621 | /** |
622 | * Get Patron Fines |
623 | * |
624 | * This is responsible for retrieving all fines by a specific patron. |
625 | * |
626 | * @param array $patron The patron array from patronLogin |
627 | * |
628 | * @return mixed Array of the patron's fines on success. |
629 | */ |
630 | public function getMyFines($patron) |
631 | { |
632 | $fineList = []; |
633 | |
634 | $response = $this->makeRequest( |
635 | "patron/{$patron['cat_username']}/account/outstanding", |
636 | 'GET', |
637 | $patron['cat_password'] |
638 | ); |
639 | $fines_response_array = $response->PatronAccountGetRows; |
640 | |
641 | foreach ($fines_response_array as $fines_response) { |
642 | $fineList[] = [ |
643 | // fees in vufind are in pennies |
644 | 'amount' => $fines_response->TransactionAmount * 100, |
645 | 'checkout' => $this->formatJSONTime($fines_response->CheckOutDate), |
646 | 'fine' => $fines_response->FeeDescription, |
647 | 'balance' => $fines_response->OutstandingAmount * 100, |
648 | 'duedate' => $this->formatJSONTime($fines_response->DueDate), |
649 | 'createdate' => $this->formatJSONTime($fines_response->TransactionDate), |
650 | 'id' => $fines_response->BibID, |
651 | 'title' => $fines_response->Title, |
652 | ]; |
653 | } |
654 | |
655 | return $fineList; |
656 | } |
657 | |
658 | /** |
659 | * Get Patron Profile |
660 | * |
661 | * This is responsible for retrieving the profile for a specific patron. |
662 | * |
663 | * @param array $patron The patron array |
664 | * |
665 | * @throws ILSException |
666 | * @return array Array of the patron's profile data on success. |
667 | */ |
668 | public function getMyProfile($patron) |
669 | { |
670 | // firstname, lastname, address1, address2, zip, phone, group |
671 | $response = $this->makeRequest( |
672 | "patron/{$patron['cat_username']}/basicdata", |
673 | 'GET', |
674 | $patron['cat_password'] |
675 | ); |
676 | $profile_response = $response->PatronBasicData; |
677 | $profile = [ |
678 | 'firstname' => $profile_response->NameFirst, |
679 | 'lastname' => $profile_response->NameLast, |
680 | 'phone' => $profile_response->PhoneNumber, |
681 | ]; |
682 | return $profile; |
683 | } |
684 | |
685 | /** |
686 | * Get Patron Transactions |
687 | * |
688 | * This is responsible for retrieving all transactions (i.e. checked out items) |
689 | * by a specific patron. |
690 | * |
691 | * @param array $patron The patron array from patronLogin |
692 | * |
693 | * @return mixed Array of associative arrays of the patron's transactions on |
694 | * success. |
695 | */ |
696 | public function getMyTransactions($patron) |
697 | { |
698 | // duedate, id, barcode, renew (count), request (pending count), |
699 | // volume (vol number), publication_year, renewable, message, title, item_id |
700 | // polaris apis: PatronItemsOutGet |
701 | $transactions = []; |
702 | $response = $this->makeRequest( |
703 | "patron/{$patron['cat_username']}/itemsout/all", |
704 | 'GET', |
705 | $patron['cat_password'] |
706 | ); |
707 | |
708 | foreach ($response->PatronItemsOutGetRows as $trResponse) { |
709 | // any more renewals available? |
710 | if (($trResponse->RenewalLimit - $trResponse->RenewalCount) > 0) { |
711 | $renewable = true; |
712 | } else { |
713 | $renewable = false; |
714 | } |
715 | $transactions[] = [ |
716 | 'duedate' => $this->formatJSONTime($trResponse->DueDate), |
717 | 'id' => $trResponse->BibID, |
718 | 'barcode' => $trResponse->Barcode, |
719 | 'renew' => $trResponse->RenewalCount, |
720 | 'renewLimit' => $trResponse->RenewalLimit, |
721 | 'renewable' => $renewable, |
722 | 'title' => $trResponse->Title, |
723 | 'item_id' => $trResponse->ItemID, |
724 | ]; |
725 | } |
726 | return $transactions; |
727 | } |
728 | |
729 | /** |
730 | * Renew My Items |
731 | * |
732 | * Function for attempting to renew a patron's items. The data in |
733 | * $renewDetails['details'] is determined by getRenewDetails(). |
734 | * |
735 | * @param array $renewDetails An array of data required for renewing items |
736 | * including the Patron ID and an array of renewal IDS |
737 | * |
738 | * @return array An array of renewal information keyed by item ID |
739 | */ |
740 | public function renewMyItems($renewDetails) |
741 | { |
742 | $renew_ids = $renewDetails['details']; |
743 | $patron = $renewDetails['patron']; |
744 | $count = 0; |
745 | $item_response = []; |
746 | $item_blocks = []; |
747 | |
748 | foreach ($renew_ids as $renew_id) { |
749 | $jsonrequest = []; |
750 | $jsonrequest['Action'] = 'renew'; |
751 | $jsonrequest['LogonBranchID'] = '1'; |
752 | $jsonrequest['LogonUserID'] = '1'; |
753 | $jsonrequest['LogonWorkstationID'] = '1'; |
754 | $jsonrequest['RenewData']['IgnoreOverrideErrors'] = 'true'; |
755 | |
756 | $response = $this->makeRequest( |
757 | "patron/{$patron['cat_username']}/itemsout/$renew_id", |
758 | 'PUT', |
759 | $patron['cat_password'], |
760 | $jsonrequest |
761 | ); |
762 | if ($response->PAPIErrorCode == 0) { |
763 | $count++; |
764 | $item_response[$renew_id] = [ |
765 | 'success' => true, |
766 | 'new_date' => $this->formatJSONTime( |
767 | $response->ItemRenewResult->DueDateRows[0]->DueDate |
768 | ), |
769 | 'item_id' => |
770 | $response->ItemRenewResult->DueDateRows[0]->ItemRecordID, |
771 | ]; |
772 | } elseif ($response->PAPIErrorCode == -2) { |
773 | $item_blocks[$renew_id] |
774 | = $response->ItemRenewResult->BlockRows[0]->ErrorDesc; |
775 | $item_response[$renew_id] = [ |
776 | 'success' => -1, |
777 | 'new_date' => false, |
778 | 'item_id' => $response->ItemRenewResult->BlockRows[0]->ItemRecordID, |
779 | 'sysMessage' => $response->ItemRenewResult->BlockRows[0]->ErrorDesc, |
780 | ]; |
781 | } |
782 | } |
783 | $result = [ |
784 | 'count' => $count, 'details' => $item_response, |
785 | 'blocks' => $item_blocks, |
786 | ]; |
787 | |
788 | return $result; |
789 | } |
790 | |
791 | /** |
792 | * Get Renew Details |
793 | * |
794 | * In order to renew an item, Voyager requires the patron details and an item |
795 | * id. This function returns the item id as a string which is then used |
796 | * as submitted form data in checkedOut.php. This value is then extracted by |
797 | * the RenewMyItems function. |
798 | * |
799 | * @param array $checkOutDetails An array of item data |
800 | * |
801 | * @return string Data for use in a form field |
802 | */ |
803 | public function getRenewDetails($checkOutDetails) |
804 | { |
805 | $renewDetails = $checkOutDetails['item_id']; |
806 | return $renewDetails; |
807 | } |
808 | |
809 | /** |
810 | * Cancel Holds |
811 | * |
812 | * Attempts to Cancel a hold or recall on a particular item. The |
813 | * data in $cancelDetails['details'] is determined by getCancelHoldDetails(). |
814 | * |
815 | * @param array $cancelDetails An array of item and patron data |
816 | * |
817 | * @return array An array of data on each request including whether or not it |
818 | * was successful and a system message (if available) |
819 | */ |
820 | public function cancelHolds($cancelDetails) |
821 | { |
822 | $hold_ids = $cancelDetails['details']; |
823 | $patron = $cancelDetails['patron']; |
824 | $count = 0; |
825 | $item_response = []; |
826 | |
827 | foreach ($hold_ids as $hold_id) { |
828 | $response = $this->makeRequest( |
829 | "patron/{$patron['cat_username']}/holdrequests/$hold_id/cancelled" |
830 | . '?wsid=1&userid=1', |
831 | 'PUT', |
832 | $patron['cat_password'] |
833 | ); |
834 | |
835 | if ($response->PAPIErrorCode == 0) { |
836 | $count++; |
837 | $item_response[$hold_id] = [ |
838 | 'success' => true, |
839 | 'status' => 'hold_cancel_success', |
840 | ]; |
841 | } else { |
842 | $item_response[$hold_id] = [ |
843 | 'success' => false, |
844 | 'status' => 'hold_cancel_fail', |
845 | 'sysMessage' => 'Failure calling ILS to cancel hold', |
846 | ]; |
847 | } |
848 | } |
849 | |
850 | $result = [ 'count' => $count, 'items' => $item_response ]; |
851 | return $result; |
852 | } |
853 | |
854 | /** |
855 | * Get Cancel Hold Details |
856 | * |
857 | * @param array $holdDetails A single hold array from getMyHolds |
858 | * @param array $patron Patron information from patronLogin |
859 | * |
860 | * @return string Data for use in a form field (just request id is all Polaris |
861 | * needs) |
862 | * |
863 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) |
864 | */ |
865 | public function getCancelHoldDetails($holdDetails, $patron = []) |
866 | { |
867 | return $holdDetails['reqnum']; |
868 | } |
869 | |
870 | /** |
871 | * Get Checkout History |
872 | * |
873 | * Returns the patrons checkout / reading history |
874 | * |
875 | * @param array $patron The patron array from patronLogin |
876 | * |
877 | * @return mixed Array of the patron's checkouts on success. |
878 | */ |
879 | public function getCheckoutHistory($patron) |
880 | { |
881 | // get number of pages, only get most recent max 200 items (last 2 pages) |
882 | // TODO: use real pagination, not just recent items. |
883 | $items_per_page = 100; |
884 | |
885 | $response = $this->makeRequest( |
886 | "patron/{$patron['cat_username']}/readinghistory?rowsperpage=1&page=-1", |
887 | 'GET', |
888 | $patron['cat_password'] |
889 | ); |
890 | |
891 | // error code returns number of results |
892 | $count = $response->PAPIErrorCode; |
893 | |
894 | if ($count == 0) { |
895 | return; |
896 | } |
897 | |
898 | $pages = ceil($count / $items_per_page); |
899 | |
900 | $penultimate_page = $pages - 1; |
901 | |
902 | if ($penultimate_page > 0) { |
903 | $page_offset = $penultimate_page; |
904 | } else { |
905 | $page_offset = $pages; |
906 | } |
907 | |
908 | $checkouts = []; |
909 | while ($page_offset <= $pages) { |
910 | $response = $this->makeRequest( |
911 | "patron/{$patron['cat_username']}/readinghistory?rowsperpage=" |
912 | . "$items_per_page&page=$page_offset", |
913 | 'GET', |
914 | $patron['cat_password'] |
915 | ); |
916 | |
917 | $checkout_history_array = $response->PatronReadingHistoryGetRows; |
918 | foreach ($checkout_history_array as $checkout_response) { |
919 | $date = $this->formatJSONTime($checkout_response->CheckOutDate); |
920 | $checkouts[] = [ |
921 | 'id' => $checkout_response->BibID, |
922 | 'title' => $checkout_response->Title, |
923 | 'format' => $checkout_response->FormatDescription, |
924 | 'location' => $checkout_response->LoaningBranchName, |
925 | 'date' => $date, |
926 | 'author' => $checkout_response->Author, |
927 | ]; |
928 | } |
929 | $page_offset++; |
930 | } |
931 | // show most recent checkouts first |
932 | $checkouts = array_reverse($checkouts); |
933 | |
934 | return $checkouts; |
935 | } |
936 | |
937 | /** |
938 | * Get Hold Count |
939 | * |
940 | * Returns the count of a hold based on API call to bibid |
941 | * |
942 | * @param array $id bib id |
943 | * |
944 | * @return string count of holds |
945 | */ |
946 | public function getHoldCount($id) |
947 | { |
948 | $response = $this->makeRequest("bib/$id"); |
949 | $holdings_response_array = $response->BibGetRows; |
950 | $hold_count = 0; |
951 | foreach ($holdings_response_array as $response) { |
952 | if ($response->ElementID == '8') { |
953 | // that's the current holds field, could also be pulled by label |
954 | // instead? |
955 | if ($response->Value > 0) { |
956 | $hold_count = $response->Value; |
957 | } |
958 | break; |
959 | } |
960 | } |
961 | return $hold_count; |
962 | } |
963 | |
964 | /** |
965 | * Suspend Holds |
966 | * |
967 | * Attempts to Suspend a hold or recall on a particular item. The |
968 | * data in $suspendDetails['details'] is determined by getSuspendHoldDetails(). |
969 | * |
970 | * @param array $suspendDetails An array of item and patron data |
971 | * |
972 | * @return array An array of data on each request including whether or not it |
973 | * was successful and a system message (if available) |
974 | */ |
975 | public function suspendHolds($suspendDetails) |
976 | { |
977 | $hold_ids = $suspendDetails['details']; |
978 | $patron = $suspendDetails['patron']; |
979 | |
980 | $jsondate = $this->encodeJSONTime($suspendDetails['date']); |
981 | |
982 | $count = 0; |
983 | $item_response = []; |
984 | |
985 | foreach ($hold_ids as $hold_id) { |
986 | $jsonrequest = [ |
987 | 'UserID' => '1', |
988 | 'ActivationDate' => "$jsondate", |
989 | ]; |
990 | |
991 | $response = $this->makeRequest( |
992 | "patron/{$patron['cat_username']}/holdrequests/$hold_id/inactive", |
993 | 'PUT', |
994 | $patron['cat_password'], |
995 | $jsonrequest |
996 | ); |
997 | |
998 | if ($response->PAPIErrorCode == 0) { |
999 | $count++; |
1000 | $item_response[$hold_id] = [ |
1001 | 'success' => true, |
1002 | 'status' => 'hold_suspend_success', |
1003 | ]; |
1004 | } else { |
1005 | $item_response[$hold_id] = [ |
1006 | 'success' => false, |
1007 | 'status' => 'hold_suspend_fail', |
1008 | 'sysMessage' => 'Failure calling ILS to suspend hold', |
1009 | ]; |
1010 | } |
1011 | } |
1012 | |
1013 | $result = [ 'count' => $count, 'items' => $item_response ]; |
1014 | return $result; |
1015 | } |
1016 | |
1017 | /** |
1018 | * Get Suspend Hold Details |
1019 | * |
1020 | * @param array $holdDetails An array of item data |
1021 | * |
1022 | * @return string Data for use in a form field (just request id is all Polaris |
1023 | * needs) |
1024 | */ |
1025 | public function getSuspendHoldDetails($holdDetails) |
1026 | { |
1027 | return $holdDetails['reqnum']; |
1028 | } |
1029 | |
1030 | /** |
1031 | * Reactivate Holds |
1032 | * |
1033 | * Attempts to Reactivate a hold or recall on a particular item. The |
1034 | * data in $reactivateDetails['details'] is determined by |
1035 | * getReactivateHoldDetails(). |
1036 | * |
1037 | * @param array $reactivateDetails An array of item and patron data |
1038 | * |
1039 | * @return array An array of data on each request including whether or not it |
1040 | * was successful and a system message (if available) |
1041 | */ |
1042 | public function reactivateHolds($reactivateDetails) |
1043 | { |
1044 | $hold_ids = $reactivateDetails['details']; |
1045 | $patron = $reactivateDetails['patron']; |
1046 | |
1047 | $date = date('d/M/Y'); |
1048 | $jsondate = $this->encodeJSONTime($date); |
1049 | |
1050 | $count = 0; |
1051 | $item_response = []; |
1052 | |
1053 | foreach ($hold_ids as $hold_id) { |
1054 | $jsonrequest = [ |
1055 | 'UserID' => '1', |
1056 | 'ActivationDate' => "$jsondate", |
1057 | ]; |
1058 | |
1059 | $response = $this->makeRequest( |
1060 | "patron/{$patron['cat_username']}/holdrequests/$hold_id/active", |
1061 | 'PUT', |
1062 | $patron['cat_password'], |
1063 | $jsonrequest |
1064 | ); |
1065 | |
1066 | if ($response->PAPIErrorCode == 0) { |
1067 | $count++; |
1068 | $item_response[$hold_id] = [ |
1069 | 'success' => true, |
1070 | 'status' => 'hold_reactivate_success', |
1071 | ]; |
1072 | } else { |
1073 | $item_response[$hold_id] = [ |
1074 | 'success' => false, |
1075 | 'status' => 'hold_reactivate_fail', |
1076 | 'sysMessage' => 'Failure calling ILS to reactivate hold', |
1077 | ]; |
1078 | } |
1079 | } |
1080 | |
1081 | $result = [ 'count' => $count, 'items' => $item_response ]; |
1082 | return $result; |
1083 | } |
1084 | } |