Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.23% |
2 / 877 |
|
0.00% |
0 / 32 |
CRAP | |
0.00% |
0 / 1 |
Virtua | |
0.23% |
2 / 877 |
|
0.00% |
0 / 32 |
39529.69 | |
0.00% |
0 / 1 |
init | |
10.53% |
2 / 19 |
|
0.00% |
0 / 1 |
4.87 | |||
getStatus | |
0.00% |
0 / 140 |
|
0.00% |
0 / 1 |
1482 | |||
getStatuses | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
getHolding | |
0.00% |
0 / 76 |
|
0.00% |
0 / 1 |
552 | |||
checkHoldAllowed | |
0.00% |
0 / 50 |
|
0.00% |
0 / 1 |
272 | |||
getField | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
12 | |||
renderPartSubPattern | |
0.00% |
0 / 111 |
|
0.00% |
0 / 1 |
600 | |||
renderSubPattern | |
0.00% |
0 / 16 |
|
0.00% |
0 / 1 |
20 | |||
renderOtherPattern | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
20 | |||
renderPattern | |
0.00% |
0 / 18 |
|
0.00% |
0 / 1 |
30 | |||
renderSerialHoldings | |
0.00% |
0 / 45 |
|
0.00% |
0 / 1 |
110 | |||
getPurchaseHistory | |
0.00% |
0 / 22 |
|
0.00% |
0 / 1 |
12 | |||
getAll853 | |
0.00% |
0 / 18 |
|
0.00% |
0 / 1 |
30 | |||
patronLogin | |
0.00% |
0 / 35 |
|
0.00% |
0 / 1 |
20 | |||
getMyProfile | |
0.00% |
0 / 31 |
|
0.00% |
0 / 1 |
30 | |||
getMyFines | |
0.00% |
0 / 21 |
|
0.00% |
0 / 1 |
12 | |||
getMyHolds | |
0.00% |
0 / 18 |
|
0.00% |
0 / 1 |
12 | |||
getMyTransactions | |
0.00% |
0 / 21 |
|
0.00% |
0 / 1 |
12 | |||
getCourses | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
12 | |||
findReserves | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
12 | |||
getOpeningHours | |
0.00% |
0 / 50 |
|
0.00% |
0 / 1 |
30 | |||
placeHold | |
0.00% |
0 / 39 |
|
0.00% |
0 / 1 |
42 | |||
getCancelHoldDetails | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
cancelHolds | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
cancelHold | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
12 | |||
fakeLogin | |
0.00% |
0 / 21 |
|
0.00% |
0 / 1 |
2 | |||
getRenewDetails | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
renewMyItems | |
0.00% |
0 / 38 |
|
0.00% |
0 / 1 |
30 | |||
getSuppressedAuthorityRecords | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
12 | |||
getApiBaseUrl | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
6 | |||
getConfiguredLanguage | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
httpRequest | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
56 |
1 | <?php |
2 | |
3 | /** |
4 | * VTLS Virtua Driver |
5 | * |
6 | * PHP version 8 |
7 | * |
8 | * Copyright (C) University of Southern Queensland 2008. |
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 Greg Pendlebury <vufind-tech@lists.sourceforge.net> |
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\Date\DateException; |
33 | use VuFind\Exception\ILS as ILSException; |
34 | |
35 | use function count; |
36 | use function in_array; |
37 | use function is_array; |
38 | use function strlen; |
39 | |
40 | /** |
41 | * VTLS Virtua Driver |
42 | * |
43 | * @category VuFind |
44 | * @package ILS_Drivers |
45 | * @author Greg Pendlebury <vufind-tech@lists.sourceforge.net> |
46 | * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License |
47 | * @link https://vufind.org/wiki/development:plugins:ils_drivers Wiki |
48 | */ |
49 | class Virtua extends AbstractBase implements \VuFindHttp\HttpServiceAwareInterface |
50 | { |
51 | use \VuFindHttp\HttpServiceAwareTrait; |
52 | |
53 | /** |
54 | * Oracle connection |
55 | * |
56 | * @var \VuFind\Connection\Oracle |
57 | */ |
58 | protected $db; |
59 | |
60 | /** |
61 | * Initialize the driver. |
62 | * |
63 | * Validate configuration and perform all resource-intensive tasks needed to |
64 | * make the driver active. |
65 | * |
66 | * @throws ILSException |
67 | * @return void |
68 | */ |
69 | public function init() |
70 | { |
71 | if (empty($this->config)) { |
72 | throw new ILSException('Configuration needs to be set.'); |
73 | } |
74 | |
75 | // Define Database Name |
76 | $tns = '(DESCRIPTION=' . |
77 | '(ADDRESS_LIST=' . |
78 | '(ADDRESS=' . |
79 | '(PROTOCOL=TCP)' . |
80 | '(HOST=' . $this->config['Catalog']['host'] . ')' . |
81 | '(PORT=' . $this->config['Catalog']['port'] . ')' . |
82 | ')' . |
83 | ')' . |
84 | '(CONNECT_DATA=' . |
85 | '(SERVICE_NAME=' . $this->config['Catalog']['service'] . ')' . |
86 | ')' . |
87 | ')'; |
88 | $this->db = new \VuFind\Connection\Oracle( |
89 | $this->config['Catalog']['user'], |
90 | $this->config['Catalog']['password'], |
91 | $tns |
92 | ); |
93 | } |
94 | |
95 | /** |
96 | * Get Status |
97 | * |
98 | * This is responsible for retrieving the status information of a certain |
99 | * record. |
100 | * |
101 | * @param string $id The record id to retrieve the holdings for |
102 | * |
103 | * @throws ILSException |
104 | * @return mixed On success, an associative array with the following keys: |
105 | * id, availability (boolean), status, location, reserve, callnumber. |
106 | */ |
107 | public function getStatus($id) |
108 | { |
109 | $holding = []; |
110 | |
111 | // Strip off the prefix from vtls exports |
112 | $db_id = str_replace('vtls', '', $id); |
113 | |
114 | // Build SQL Statement |
115 | $sql = 'SELECT d.itemid AS item_id, c.due_date, s.name AS status, ' . |
116 | 's.status_code, l.name AS location, ' . |
117 | 'SUBSTR(d.location, 0, 1) as camp_id, ' . |
118 | "DECODE(h.req_num, null, 'N', 'Y') AS reserve, " . |
119 | 'b.call_number AS bib_call_num, ' . |
120 | 'i.call_number AS item_call_num ' . |
121 | 'FROM dbadmin.itemdetl2 d, dbadmin.location l, ' . |
122 | 'dbadmin.statdetl sd, dbadmin.item_status s, ' . |
123 | 'dbadmin.circdetl c, dbadmin.bibliographic_fields b, ' . |
124 | 'dbadmin.item_call_number i, ' . |
125 | |
126 | '(SELECT d1.itemid, MAX(h1.request_control_number) AS req_num ' . |
127 | ' FROM dbadmin.itemdetl2 d1, dbadmin.hlrcdetl h1 ' . |
128 | ' WHERE (d1.itemid = h1.itemid ' . |
129 | ' OR (d1.bibid = h1.bibid ' . |
130 | ' AND ' . |
131 | ' h1.itemid is null)) ' . |
132 | ' AND d1.bibid = :bib_id ' . |
133 | ' GROUP BY d1.itemid ' . |
134 | ') h ' . |
135 | |
136 | 'WHERE d.location = l.location_id ' . |
137 | 'AND d.itemid = sd.itemid (+) ' . |
138 | 'AND sd.stat = s.status_code (+) ' . |
139 | 'AND d.itemid = c.itemid (+) ' . |
140 | 'AND d.itemid = h.itemid (+) ' . |
141 | 'AND d.itemid = i.itemid (+) ' . |
142 | 'AND d.bibid = b.bib_id ' . |
143 | 'AND d.bibid = :bib_id'; |
144 | |
145 | // Bind our bib_id and execute |
146 | $fields = ['bib_id:string' => $db_id]; |
147 | $result = $this->db->simpleSelect($sql, $fields); |
148 | |
149 | // If there are no results, lets try again because it has no items |
150 | if (count($result) == 0) { |
151 | $sql = 'SELECT b.call_number ' . |
152 | 'FROM dbadmin.bibliographic_fields b ' . |
153 | 'WHERE b.bib_id = :bib_id'; |
154 | $result = $this->db->simpleSelect($sql, $fields); |
155 | |
156 | if (count($result) > 0) { |
157 | $new_holding = [ |
158 | 'id' => $id, |
159 | 'availability' => false, |
160 | 'reserve' => 'Y', |
161 | 'status' => null, |
162 | 'location' => 'Toowoomba', |
163 | 'campus' => 'Toowoomba', |
164 | 'callnumber' => $result[0]['CALL_NUMBER'], |
165 | ]; |
166 | |
167 | switch ($result[0]['CALL_NUMBER']) { |
168 | case 'ELECTRONIC RESOURCE': |
169 | $new_holding['availability'] = true; |
170 | $new_holding['status'] = null; |
171 | $new_holding['location'] = 'Online'; |
172 | $new_holding['reserve'] = 'N'; |
173 | $holding[] = $new_holding; |
174 | return $holding; |
175 | break; |
176 | case 'ON ORDER': |
177 | $new_holding['status'] = 'ON ORDER'; |
178 | $new_holding['location'] = 'Pending...'; |
179 | $holding[] = $new_holding; |
180 | return $holding; |
181 | break; |
182 | case 'ORDER CANCELLED': |
183 | $new_holding['status'] = 'ORDER CANCELLED'; |
184 | $new_holding['location'] = 'None'; |
185 | $holding[] = $new_holding; |
186 | return $holding; |
187 | break; |
188 | case 'MISSING': |
189 | $new_holding['status'] = 'MISSING'; |
190 | $new_holding['location'] = 'Unknown'; |
191 | $holding[] = $new_holding; |
192 | return $holding; |
193 | break; |
194 | |
195 | default: |
196 | // Still haven't found it. Let's check if it has a serials |
197 | // holding location |
198 | $call_number = $result[0]['CALL_NUMBER']; |
199 | $sql = 'SELECT l.name, ' . |
200 | 'SUBSTR(l.location_id, 0, 1) as camp_id ' . |
201 | 'FROM dbadmin.holdlink h, location l ' . |
202 | 'WHERE h.location = l.location_id ' . |
203 | 'AND h.bibid = :bib_id'; |
204 | $result = $this->db->simpleSelect($sql, $fields); |
205 | |
206 | if (count($result) > 0) { |
207 | foreach ($result as $r) { |
208 | $tmp_holding = $new_holding; |
209 | // TODO: create a configuration file mechanism for |
210 | // specifying locations so we can eliminate these |
211 | // hard-coded USQ-specific values. |
212 | switch ($r['CAMP_ID']) { |
213 | case 4: |
214 | $campus = 'Fraser Coast'; |
215 | break; |
216 | case 5: |
217 | $campus = 'Springfield'; |
218 | break; |
219 | default: |
220 | $campus = 'Toowoomba'; |
221 | break; |
222 | } |
223 | |
224 | $tmp_holding['status'] = 'Not For Loan'; |
225 | $tmp_holding['location'] = $r['NAME']; |
226 | $tmp_holding['reserve'] = 'N'; |
227 | $tmp_holding['campus'] = $campus; |
228 | $tmp_holding['callnumber'] = $call_number; |
229 | $holding[] = $tmp_holding; |
230 | } |
231 | return $holding; |
232 | } else { |
233 | // Still haven't found anything? Return nothing then... |
234 | return $holding; |
235 | } |
236 | break; |
237 | } |
238 | } else { |
239 | // Still haven't found anything? Return nothing then... |
240 | return $holding; |
241 | } |
242 | } |
243 | |
244 | // Build Holdings Array |
245 | foreach ($result as $row) { |
246 | // TODO: create a configuration file mechanism for specifying locations |
247 | // so we can eliminate these hard-coded USQ-specific values. |
248 | switch ($row['CAMP_ID']) { |
249 | case 4: |
250 | $campus = 'Fraser Coast'; |
251 | break; |
252 | case 5: |
253 | $campus = 'Springfield'; |
254 | break; |
255 | default: |
256 | $campus = 'Toowoomba'; |
257 | break; |
258 | } |
259 | |
260 | // If it has a due date... not available |
261 | if ($row['DUE_DATE'] != null) { |
262 | $available = false; |
263 | } else { |
264 | // All these statuses are also unavailable |
265 | // TODO: make these configurable through Virtua.ini. |
266 | switch ($row['STATUS_CODE']) { |
267 | case '5402': // '24 hour hold' |
268 | case '4401': // 'At Repair' |
269 | case '5400': // 'Being Processed' |
270 | case '2101': // 'Damaged Item' |
271 | case '7400': // 'Fraser Coast only' |
272 | case '5700': // 'IN TRANSIT' |
273 | case '7700': // 'Invoiced' |
274 | case '3400': // 'Invoiced - Re-ordered' |
275 | case '4600': // 'LONG OVERDUE' |
276 | case '4700': // 'MISSING' |
277 | case '4705': // 'ON HOLD' |
278 | case '5710': // 'REQUESTED FOR HOLD' |
279 | case '5401': // 'Staff Use' |
280 | $available = false; |
281 | break; |
282 | // Otherwise it's available |
283 | case '7200': // 'External Loan Only' |
284 | case '3100': // 'In Library use only' |
285 | case '2700': // 'Limited Loan' |
286 | case '2701': // 'Not For Loan' |
287 | case '2100': // 'Not for loan' |
288 | case '5401': // 'On Display' |
289 | default: |
290 | $available = true; |
291 | break; |
292 | } |
293 | } |
294 | |
295 | $holding[] = [ |
296 | 'id' => $id, |
297 | 'availability' => $available, |
298 | 'status' => $row['STATUS'], |
299 | 'location' => $row['LOCATION'], |
300 | 'reserve' => $row['RESERVE'], |
301 | 'campus' => $campus, |
302 | 'callnumber' => $row['BIB_CALL_NUM'], |
303 | ]; |
304 | } |
305 | |
306 | return $holding; |
307 | } |
308 | |
309 | /** |
310 | * Get Statuses |
311 | * |
312 | * This is responsible for retrieving the status information for a |
313 | * collection of records. |
314 | * |
315 | * @param array $idList The array of record ids to retrieve the status for |
316 | * |
317 | * @throws ILSException |
318 | * @return array An array of getStatus() return values on success. |
319 | */ |
320 | public function getStatuses($idList) |
321 | { |
322 | $status = []; |
323 | foreach ($idList as $id) { |
324 | $status[] = $this->getStatus($id); |
325 | } |
326 | return $status; |
327 | } |
328 | |
329 | /** |
330 | * Get Holding |
331 | * |
332 | * This is responsible for retrieving the holding information of a certain |
333 | * record. |
334 | * |
335 | * @param string $id The record id to retrieve the holdings for |
336 | * @param array $patron Patron data |
337 | * @param array $options Extra options (not currently used) |
338 | * |
339 | * @throws DateException |
340 | * @throws ILSException |
341 | * @return array On success, an associative array with the following |
342 | * keys: id, availability (boolean), status, location, reserve, callnumber, |
343 | * duedate, number, barcode. |
344 | * |
345 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) |
346 | */ |
347 | public function getHolding($id, array $patron = null, array $options = []) |
348 | { |
349 | // Strip off the prefix from vtls exports |
350 | $db_id = str_replace('vtls', '', $id); |
351 | $fields = ['bib_id:string' => $db_id]; |
352 | |
353 | $holds = 'SELECT d1.itemid, MAX(h1.request_control_number) AS req_num ' . |
354 | 'FROM dbadmin.itemdetl2 d1, dbadmin.hlrcdetl h1 ' . |
355 | 'WHERE d1.itemid = h1.itemid ' . |
356 | 'AND d1.bibid = :bib_id ' . |
357 | 'GROUP BY d1.itemid'; |
358 | |
359 | $bib_reqs = 'SELECT h.bibid, count(*) as bib_req ' . |
360 | 'FROM hlrcdetl h ' . |
361 | 'WHERE h.itemid = 0 ' . |
362 | 'GROUP BY h.bibid'; |
363 | |
364 | $item_reqs = 'SELECT h.itemid, count(*) as item_req ' . |
365 | 'FROM hlrcdetl h ' . |
366 | 'WHERE h.itemid <> 0 ' . |
367 | 'GROUP BY h.itemid'; |
368 | |
369 | $issues = 'SELECT MAX(s.issue_id) AS latest_issue, h.bibid ' . |
370 | 'FROM serials_issue s, holdlink h ' . |
371 | 'WHERE h.bibid = :bib_id ' . |
372 | 'AND h.holdingsid = s.holdingsid ' . |
373 | 'GROUP BY h.bibid'; |
374 | |
375 | $reserve_class = 'SELECT DISTINCT item_id, item_class ' . |
376 | ' FROM reserve_item_v'; |
377 | |
378 | // Build SQL Statement |
379 | $sql = 'SELECT d.itemid as item_id, d.copyno, d.barcode, c.due_date, ' . |
380 | 's.name as status, s.status_code, ' . |
381 | 'l.name as location, l.location_id, b.call_number as bib_call_num, ' . |
382 | 'i.call_number as item_call_num, ' . |
383 | 'iss.latest_issue, r.item_class as reserve_item_class, ' . |
384 | 'ic.item_class, d.units, ' . |
385 | 'br.bib_req, ir.item_req ' . |
386 | 'FROM dbadmin.itemdetl2 d, dbadmin.location l, ' . |
387 | 'dbadmin.statdetl sd, dbadmin.item_status s, ' . |
388 | 'dbadmin.circdetl c, dbadmin.bibliographic_fields b, ' . |
389 | 'dbadmin.item_call_number i, item_class_v ic, ' . |
390 | "($holds) h, ($bib_reqs) br, ($item_reqs) ir, ($issues) iss, " . |
391 | "($reserve_class) r " . |
392 | 'WHERE d.location = l.location_id ' . |
393 | 'AND d.itemclass = ic.item_class_id ' . |
394 | 'AND d.itemid = sd.itemid (+) ' . |
395 | 'AND sd.stat = s.status_code (+) ' . |
396 | 'AND d.itemid = c.itemid (+) ' . |
397 | 'AND d.itemid = h.itemid (+) ' . |
398 | 'AND d.bibid = br.bibid (+) ' . |
399 | 'AND d.itemid = ir.itemid (+) ' . |
400 | 'AND d.itemid = i.itemid (+) ' . |
401 | 'AND d.itemid = r.item_id (+) ' . |
402 | 'AND d.bibid = iss.bibid (+) ' . |
403 | 'AND d.bibid = b.bib_id ' . |
404 | 'AND d.bibid = :bib_id ' . |
405 | 'ORDER BY l.location_id, d.units_sort_form desc, d.copyno'; |
406 | //print "<div style='display:none;'>$sql</div>"; |
407 | |
408 | $result = $this->db->simpleSelect($sql, $fields); |
409 | if ($result === false) { |
410 | throw new ILSException($this->db->getHtmlError()); |
411 | } |
412 | |
413 | // Build Holdings Array |
414 | $holding = []; |
415 | foreach ($result as $row) { |
416 | // If it's reserved or has a due date... not available |
417 | if ($row['DUE_DATE'] != null || $row['REQ_COUNT'] != null) { |
418 | $available = false; |
419 | } else { |
420 | // All these statuses are also unavailable |
421 | // TODO: Make this configurable through Virtua.ini. |
422 | switch ($row['STATUS']) { |
423 | case 'Fraser Coast only': |
424 | case 'Being Processed': |
425 | case 'Not For Loan': |
426 | case 'Not for loan': |
427 | case 'Invoiced': |
428 | case 'IN TRANSIT': |
429 | case 'Staff Use': |
430 | case 'MISSING': |
431 | case 'Damaged Item': |
432 | case 'At Repair': |
433 | case 'ON ORDER': |
434 | case 'ON HOLD': |
435 | case 'Springfield Only': |
436 | case 'Part Order Received': |
437 | $available = false; |
438 | break; |
439 | // Otherwise it's available |
440 | default: |
441 | $available = true; |
442 | break; |
443 | } |
444 | } |
445 | |
446 | // Call number |
447 | if ($row['ITEM_CALL_NUM'] != null) { |
448 | $call_num = $row['ITEM_CALL_NUM']; |
449 | } else { |
450 | $call_num = $row['BIB_CALL_NUM']; |
451 | } |
452 | |
453 | $temp = [ |
454 | 'id' => $id, |
455 | 'availability' => $available, |
456 | 'status' => $row['STATUS'], |
457 | 'status_code' => $row['STATUS_CODE'], |
458 | 'location' => $row['LOCATION'], |
459 | 'location_code' => $row['LOCATION_ID'], |
460 | 'reserve' => $row['ITEM_REQ'] + $row['BIB_REQ'], |
461 | 'callnumber' => $call_num, |
462 | 'duedate' => $row['DUE_DATE'], |
463 | 'number' => $row['COPYNO'], |
464 | 'barcode' => $row['BARCODE'], |
465 | 'itemclass' => $row['ITEM_CLASS'], |
466 | 'units' => $row['UNITS'], |
467 | 'resitemclass' => $row['RESERVE_ITEM_CLASS'], |
468 | ]; |
469 | |
470 | // Add to the holdings array |
471 | $holding[] = $temp; |
472 | } |
473 | |
474 | return (count($holding) != 0 && $patron['id'] != null) |
475 | ? $this->checkHoldAllowed($patron['id'], $holding) : $holding; |
476 | } |
477 | |
478 | /** |
479 | * Check if this patron is allowed to place a request. |
480 | * - Return the holdings array with true/false and a reason. |
481 | * |
482 | * Because of the location comparisons with the patron's |
483 | * location that occur here we also take the oppurtunity |
484 | * to push their "Home" location to the top. |
485 | * |
486 | * @param string $patron_id ID of patron |
487 | * @param array $holdings Holdings information from getHolding |
488 | * |
489 | * @return array Holdings info augmented with req_allowed field |
490 | */ |
491 | protected function checkHoldAllowed($patron_id, $holdings) |
492 | { |
493 | // Get the patron type |
494 | $sql = 'SELECT p.patron_type_id ' . |
495 | 'FROM patron_type_patron p, patron_barcode b ' . |
496 | 'WHERE b.patron_id = p.patron_id ' . |
497 | 'AND b.barcode = :patron'; |
498 | $fields = ['patron:string' => $patron_id]; |
499 | $result = $this->db->simpleSelect($sql, $fields); |
500 | |
501 | // We should have 1 row and only 1 row. |
502 | if (count($result) != 1) { |
503 | return $holdings; |
504 | } |
505 | $patron_type = $result[0]['PATRON_TYPE_ID']; |
506 | |
507 | // A matrix of patron types and locations |
508 | // TODO: Make this configurable through Virtua.ini. |
509 | $type_list = [ |
510 | 'Externals' => ['AX', 'AD', 'BX', 'BD', 'EX', 'ED', 'GX', 'GD', |
511 | 'RX', 'SX', 'SD', 'XS', 'CC', 'RD'], |
512 | 'Super User' => ['LP', 'OC'], |
513 | // 1 => Toowoomba |
514 | '1' => ['AU', 'AM', 'BU', 'BM', 'EU', 'EM', 'GU', 'GM', 'RI', 'SU', |
515 | 'SM', 'SC', 'RB', 'OT', 'ST', 'FC', 'LS'], |
516 | // 5 => Springfield |
517 | '5' => ['US', 'ES', 'PS', 'AS', 'GS', 'TS', 'TAS', 'EPS', 'XVS', |
518 | 'XPS'], |
519 | // 4 => Fraser Coast |
520 | '4' => ['UF', 'PF', 'AF'], |
521 | ]; |
522 | // Where is the patron from? |
523 | $location = ''; |
524 | foreach ($type_list as $loc => $patron_types) { |
525 | if (in_array($patron_type, $patron_types)) { |
526 | $location = $loc; |
527 | } |
528 | } |
529 | // Requestable Statuses |
530 | // TODO: Make this configurable through Virtua.ini. |
531 | $status_list = [ |
532 | '4401', // At Repair |
533 | '4705', // ON HOLD |
534 | '5400', // Being Processed |
535 | '5401', // On Display |
536 | '5402', // 24 Hour Hold |
537 | '5700', // IN TRANSIT |
538 | ]; |
539 | // Who can place reservations on available items |
540 | $available_locs = [ |
541 | '1' => ['5', '4'], |
542 | '4' => [], |
543 | '5' => [], |
544 | ]; |
545 | // Who can place reservations on UNavailable items |
546 | $unavailable_locs = [ |
547 | '1' => ['1', '5', '4'], |
548 | '4' => [], |
549 | '5' => ['5'], |
550 | ]; |
551 | // Who can place reservations on STATUS items |
552 | $status_locs = [ |
553 | '1' => ['1', '5', '4'], |
554 | '4' => [], |
555 | '5' => ['5'], |
556 | ]; |
557 | |
558 | // Set a flag for super users, better then |
559 | // the full function call inside the loop |
560 | if (in_array($patron_type, $type_list['Super User'])) { |
561 | $super_user = true; |
562 | } else { |
563 | $super_user = false; |
564 | } |
565 | // External Users cannot place a request |
566 | if (in_array($patron_type, $type_list['Externals'])) { |
567 | return $holdings; |
568 | } |
569 | |
570 | /* |
571 | * Finished our basic tests, the real logic starts here |
572 | */ |
573 | $sorted = []; // We'll put items from the patron's location in here |
574 | $return = []; // Everything else in here |
575 | foreach ($holdings as $h) { |
576 | $item_loc_code = null; |
577 | // Super Users (eg. Off-Camp, and Lending) can request anything |
578 | if ($super_user) { |
579 | $h['req_allowed'] = true; |
580 | } else { |
581 | // Everyone else we need to do some lookups |
582 | |
583 | // Can't find their location? |
584 | if ($location == '') { |
585 | $h['req_allowed'] = false; |
586 | } else { |
587 | // Known location |
588 | $can_req = false; |
589 | // Details about this item -- note that we use 1/0 for |
590 | // $item_is_out since digits display better in on screen |
591 | // debugging than booleans. |
592 | $item_is_out = $h['duedate'] ? '1' : '0'; |
593 | $item_loc_code = substr($h['location_code'], 0, 1); |
594 | $item_stat_code = $h['status_code']; |
595 | |
596 | // The item is on loan ... |
597 | if ($item_is_out) { |
598 | // ... and has a requestable status or no status ... |
599 | if ( |
600 | in_array($item_stat_code, $status_list) |
601 | || $item_stat_code === null |
602 | ) { |
603 | // ... can this user borrow on loan items at this |
604 | // location? |
605 | $can_req = in_array( |
606 | $location, |
607 | $unavailable_locs[$item_loc_code] |
608 | ); |
609 | } |
610 | } else { |
611 | // The item is NOT on loan ... |
612 | |
613 | // ... and has a requestable status ... |
614 | if (in_array($item_stat_code, $status_list)) { |
615 | // ... can the user borrow status items at this location? |
616 | $can_req |
617 | = in_array($location, $status_locs[$item_loc_code]); |
618 | } else { |
619 | // ... and DOESN'T have a requestable status ... |
620 | if ($item_stat_code !== null) { |
621 | // ... but has a status, so it can't be requested. |
622 | } else { |
623 | // ... can the user borrow available items at this |
624 | // location? |
625 | $can_req = in_array( |
626 | $location, |
627 | $available_locs[$item_loc_code] |
628 | ); |
629 | } |
630 | } |
631 | } |
632 | /* DEBUGGING */ |
633 | //$can_req = $can_req ? "Y" : "N"; |
634 | //$h['req_allowed'] = "O:$item_is_out |
635 | // L:$item_loc_code S:$item_stat_code : $can_req"; |
636 | /* Normal Return value */ |
637 | $h['req_allowed'] = $can_req; |
638 | } |
639 | } |
640 | // Item from this patron's "Home" |
641 | if ($item_loc_code == $location) { |
642 | $sorted[] = $h; |
643 | } else { |
644 | $return[] = $h; |
645 | } |
646 | } // End holdings loop |
647 | return array_merge($sorted, $return); |
648 | } |
649 | |
650 | /* START - Serials functions */ |
651 | |
652 | /** |
653 | * Simple utility -- retrieve data matching a code |
654 | * |
655 | * @param array $data Data to search |
656 | * @param string $code Code to search for |
657 | * |
658 | * @return mixed |
659 | */ |
660 | protected function getField($data, $code) |
661 | { |
662 | foreach ($data as $d) { |
663 | if ($d['code'] == $code) { |
664 | return $d['data']; |
665 | } |
666 | } |
667 | return null; |
668 | } |
669 | |
670 | /** |
671 | * Patterns coming in here are either all chrono |
672 | * patterns, or no chrono patterns. |
673 | * |
674 | * This function takes care of the final string |
675 | * render for each pattern subpart. |
676 | * |
677 | * @param array $data Data to render |
678 | * |
679 | * @return string |
680 | */ |
681 | protected function renderPartSubPattern($data) |
682 | { |
683 | $end_time = $start_string = null; |
684 | |
685 | // Handle empty patterns |
686 | if (count($data) == 0) { |
687 | return ''; |
688 | } |
689 | |
690 | // Test the first element |
691 | $is_chrono = strpos($data['pattern'][0], '('); |
692 | $return_string = ''; |
693 | |
694 | // NON chrono |
695 | if ($is_chrono === false) { |
696 | $i = 0; |
697 | foreach ($data['pattern'] as $d) { |
698 | $return_string .= $d . ' ' . $data['data'][$i] . ' '; |
699 | $i++; |
700 | } |
701 | } else { |
702 | // Chrono |
703 | // Important note: strtotime() expects |
704 | // 01/02/2000 = 2nd Jan 2000 |
705 | // 01-02-2000 = 1st Feb 2000 <= Use hyphens |
706 | $pattern = implode('', $data['pattern']); |
707 | switch (strtolower(trim($pattern))) { |
708 | // Error case |
709 | case '': |
710 | return null; |
711 | break; |
712 | // Year only |
713 | case '(year)': |
714 | return $data['data'][0] . ' '; |
715 | break; |
716 | // Year + Month |
717 | case '(year)(month)': |
718 | $months = explode('-', $data['data'][1]); |
719 | $m = count($months); |
720 | $years = explode('-', $data['data'][0]); |
721 | $y = count($years); |
722 | $my = $m . $y; |
723 | |
724 | $start_time = strtotime('01-' . $months[0] . '-' . $years[0]); |
725 | $end_string = 'F Y'; |
726 | |
727 | switch ($my) { |
728 | // January 2000 - February 2001 |
729 | case '22': |
730 | $start_string = 'F Y'; |
731 | $end_time |
732 | = strtotime('01-' . $months[1] . '-' . $years[1]); |
733 | break; |
734 | // January - February 2000 |
735 | case '21': |
736 | $start_string = 'F'; |
737 | $end_time |
738 | = strtotime('01-' . $months[1] . '-' . $years[0]); |
739 | break; |
740 | // January 2000 |
741 | case '11': |
742 | $start_string = 'F Y'; |
743 | $end_time = null; |
744 | break; |
745 | // January 2000 - January 2001 |
746 | case '12': |
747 | $start_string = 'F Y'; |
748 | $end_time |
749 | = strtotime('01-' . $months[0] . '-' . $years[1]); |
750 | break; |
751 | } |
752 | if ($end_time != null) { |
753 | return date($start_string, $start_time) . ' - ' . |
754 | date($end_string, $end_time); |
755 | } else { |
756 | return date($start_string, $start_time); |
757 | } |
758 | break; |
759 | // Year + Month + Day |
760 | case '(year)(month)(day)': |
761 | $days = explode('-', $data['data'][2]); |
762 | $d = count($days); |
763 | $months = explode('-', $data['data'][1]); |
764 | $m = count($months); |
765 | $years = explode('-', $data['data'][0]); |
766 | $y = count($years); |
767 | $dmy = $d . $m . $y; |
768 | |
769 | $start_time |
770 | = strtotime($days[0] . '-' . $months[0] . '-' . $years[0]); |
771 | $end_string = 'jS F Y'; |
772 | |
773 | switch ($dmy) { |
774 | // 01 January 2000 |
775 | case '111': |
776 | $start_string = 'jS F Y'; |
777 | $end_time = null; |
778 | break; |
779 | // 01 January 2000 - 01 January 2001 |
780 | case '112': |
781 | $start_string = 'jS F Y'; |
782 | $end_time = strtotime( |
783 | $days[0] . '-' . $months[0] . '-' . $years[1] |
784 | ); |
785 | break; |
786 | // 01 January - 01 February 2000 |
787 | case '121': |
788 | $start_string = 'jS F'; |
789 | $end_time = strtotime( |
790 | $days[0] . '-' . $months[1] . '-' . $years[0] |
791 | ); |
792 | break; |
793 | // 01 January 2000 - 01 February 2001 |
794 | case '122': |
795 | $start_string = 'jS F Y'; |
796 | $end_time = strtotime( |
797 | $days[0] . '-' . $months[1] . '-' . $years[1] |
798 | ); |
799 | break; |
800 | // 01 - 02 January 2000 |
801 | case '211': |
802 | $start_string = 'jS'; |
803 | $end_time = strtotime( |
804 | $days[1] . '-' . $months[0] . '-' . $years[0] |
805 | ); |
806 | break; |
807 | // 01 January 2000 - 02 January 2001 |
808 | case '212': |
809 | $start_string = 'jS F Y'; |
810 | $end_time = strtotime( |
811 | $days[1] . '-' . $months[0] . '-' . $years[1] |
812 | ); |
813 | break; |
814 | // 01 January - 02 February 2000 |
815 | case '221': |
816 | $start_string = 'jS F'; |
817 | $end_time = strtotime( |
818 | $days[1] . '-' . $months[1] . '-' . $years[0] |
819 | ); |
820 | break; |
821 | // 01 January 2000 - 02 February 2001 |
822 | case '222': |
823 | $start_string = 'jS F Y'; |
824 | $end_time = strtotime( |
825 | $days[1] . '-' . $months[1] . '-' . $years[1] |
826 | ); |
827 | break; |
828 | } |
829 | if ($end_time != null) { |
830 | return date($start_string, $start_time) . ' - ' . |
831 | date($end_string, $end_time); |
832 | } else { |
833 | return date($start_string, $start_time); |
834 | } |
835 | break; |
836 | default: |
837 | $i = 0; |
838 | foreach ($data['pattern'] as $d) { |
839 | $return_string .= $d . ' ' . $data['data'][$i] . ' '; |
840 | $i++; |
841 | } |
842 | break; |
843 | } |
844 | } |
845 | |
846 | return $return_string; |
847 | } |
848 | |
849 | /** |
850 | * Breaks up the full pattern into chrono and other |
851 | * chrono = (year) etc... ie. gets replaced inline |
852 | * other = most enum holdings or 'Pt.'... ie. get concatenated |
853 | * |
854 | * The same sub function handles both, but they must be |
855 | * sent in like groups. |
856 | * |
857 | * @param array $data Data to render |
858 | * |
859 | * @return string |
860 | */ |
861 | protected function renderSubPattern($data) |
862 | { |
863 | $return_string = ''; |
864 | $sub_pattern = []; |
865 | $i = 0; |
866 | foreach ($data['pattern'] as $p) { |
867 | // Is this chrono pattern element? |
868 | $is_ch_pattern = strpos($p, '('); |
869 | |
870 | // If it's not, render what we have so far |
871 | // and clear the array |
872 | if ($is_ch_pattern === false) { |
873 | $return_string .= $this->renderPartSubPattern($sub_pattern); |
874 | $sub_pattern = []; |
875 | } |
876 | |
877 | // Add the current element to the array |
878 | $sub_pattern['pattern'][] = $data['pattern'][$i]; |
879 | $sub_pattern['data'][] = $data['data'][$i]; |
880 | |
881 | // Now if the current element is not a |
882 | // chrono pattern element we render it |
883 | // on it's own and clear the array again |
884 | if ($is_ch_pattern === false) { |
885 | $return_string .= $this->renderPartSubPattern($sub_pattern); |
886 | $sub_pattern = []; |
887 | } |
888 | $i++; |
889 | } |
890 | // Render the last segment of the array |
891 | $return_string .= $this->renderPartSubPattern($sub_pattern); |
892 | return $return_string; |
893 | } |
894 | |
895 | /** |
896 | * Currently used to handled note SUBfields |
897 | * eg. 863/z, not 866 generally |
898 | * but anything non enum and chrono |
899 | * related ends up here. |
900 | * |
901 | * @param array $data Data to render |
902 | * |
903 | * @return array |
904 | */ |
905 | protected function renderOtherPattern($data) |
906 | { |
907 | $return = []; |
908 | $i = 0; |
909 | foreach ($data['data'] as $d) { |
910 | switch ($data['pattern_code'][$i]) { |
911 | case 'z': |
912 | $return['notes'][] = $d; |
913 | break; |
914 | default: |
915 | $return[$data['pattern_code'][$i]][] = $d; |
916 | break; |
917 | } |
918 | $i++; |
919 | } |
920 | return $return; |
921 | } |
922 | |
923 | /** |
924 | * Renders individual holdings against a pattern |
925 | * Note fields and prediction patterns are handled |
926 | * separately |
927 | * |
928 | * @param array $patterns Pattern data |
929 | * @param array $field Field data |
930 | * |
931 | * @return array |
932 | */ |
933 | protected function renderPattern($patterns, $field) |
934 | { |
935 | $return = []; |
936 | // Check we have a pattern and the pattern exists |
937 | if (isset($field['pattern']) && isset($patterns[$field['pattern']])) { |
938 | // Enumeration, Chonology and Other fields |
939 | $enum_chrono = [ |
940 | 'a', 'b', 'c', 'd', 'e', 'f', 'i', 'j', 'k', 'l', 'm', |
941 | ]; |
942 | $this_en_ch = ['pattern' => [], 'data' => []]; |
943 | $this_other = ['pattern' => [], 'data' => []]; |
944 | |
945 | $pattern = $patterns[$field['pattern']]; |
946 | // Foreach subfield |
947 | foreach ($field['data'] as $d) { |
948 | // Get the pattern for the subfield |
949 | $p = $this->getField($pattern, $d['code']); |
950 | // Put into the sub pattern |
951 | // ... Enumeration/Chronology |
952 | if (in_array($d['code'], $enum_chrono)) { |
953 | $this_en_ch['pattern_code'][] = $d['code']; |
954 | $this_en_ch['pattern'][] = $p; |
955 | $this_en_ch['data'][] = $d['data']; |
956 | } else { |
957 | // ... Other |
958 | $this_other['pattern_code'][] = $d['code']; |
959 | $this_other['pattern'][] = $p; |
960 | $this_other['data'][] = $d['data']; |
961 | } |
962 | |
963 | $return['en_ch'] = $this->renderSubPattern($this_en_ch); |
964 | $return['other'] = $this->renderOtherPattern($this_other); |
965 | } |
966 | } else { |
967 | // Otherwise just return the a subfield as a note |
968 | $return['other']['notes'][] = $this->getField($field['data'], 'a'); |
969 | } |
970 | return $return; |
971 | } |
972 | |
973 | /** |
974 | * A function turning holdings marc into an array of display ready strings. |
975 | * |
976 | * @param array $holdings_marc Holdings data from MARC. |
977 | * |
978 | * @return array |
979 | */ |
980 | protected function renderSerialHoldings($holdings_marc) |
981 | { |
982 | // Convert to one line per tag |
983 | $data_set = []; |
984 | foreach ($holdings_marc as $row) { |
985 | if ( |
986 | $row['SUBFIELD_DATA'] != null |
987 | && trim($row['SUBFIELD_DATA']) != '' |
988 | ) { |
989 | $data_set[$row['FIELD_SEQUENCE']][] = [ |
990 | 'tag' => trim($row['FIELD_TAG']), |
991 | 'code' => trim($row['SUBFIELD_CODE']), |
992 | 'data' => trim($row['SUBFIELD_DATA']), |
993 | ]; |
994 | } |
995 | } |
996 | |
997 | // Prepare the set for sorting on '8' subfields, also move the tag data out |
998 | $sort_set = []; |
999 | // Loop through each sequence |
1000 | foreach ($data_set as $row) { |
1001 | $tag = ''; |
1002 | $data = []; |
1003 | $sort_rule = ''; |
1004 | $sort_order = ''; |
1005 | // And each subfield |
1006 | foreach ($row as $subfield) { |
1007 | // Found the '8' subfield |
1008 | if ($subfield['code'] == 8) { |
1009 | // Grab the tag for this sequence whilst here |
1010 | $tag = $subfield['tag']; |
1011 | $sort = explode('.', $subfield['data']); |
1012 | $sort_rule = $sort[0]; |
1013 | $sort_order = $sort[1] ?? 0; |
1014 | $sort_order = sprintf('%05d', $sort_order); |
1015 | } else { |
1016 | // Everything else goes in the data bucket |
1017 | $data[] = [ |
1018 | 'code' => $subfield['code'], |
1019 | 'data' => $subfield['data'], |
1020 | ]; |
1021 | } |
1022 | } |
1023 | $sort_set[$sort_rule . '.' . $sort_order] = [ |
1024 | 'tag' => $tag, |
1025 | 'data' => $data, |
1026 | ]; |
1027 | } |
1028 | |
1029 | // Sort the float array |
1030 | krsort($sort_set); |
1031 | |
1032 | // Remove the prediction patterns from the list |
1033 | // and drop sort orders or holdings. |
1034 | $patterns = []; |
1035 | $holdings_data = []; |
1036 | foreach ($sort_set as $sort => $row) { |
1037 | $rule = explode('.', $sort); |
1038 | if ($row['tag'] == 853) { |
1039 | $patterns[$rule[0]] = $row['data']; |
1040 | } else { |
1041 | $holdings_data[] = [ |
1042 | 'pattern' => $rule[0], |
1043 | 'data' => $row['data'], |
1044 | ]; |
1045 | } |
1046 | } |
1047 | |
1048 | // Render all the holdings now |
1049 | $rendered_list = []; |
1050 | foreach ($holdings_data as $row) { |
1051 | $rendered_list[] = $this->renderPattern($patterns, $row); |
1052 | } |
1053 | |
1054 | return $rendered_list; |
1055 | } |
1056 | |
1057 | /** |
1058 | * Get Purchase History |
1059 | * |
1060 | * This is responsible for retrieving the acquisitions history data for the |
1061 | * specific record (usually recently received issues of a serial). |
1062 | * |
1063 | * @param string $id The record id to retrieve the info for |
1064 | * |
1065 | * @throws ILSException |
1066 | * @return array An array with the acquisitions data on success. |
1067 | */ |
1068 | public function getPurchaseHistory($id) |
1069 | { |
1070 | // Strip off the prefix from vtls exports |
1071 | $db_id = str_replace('vtls', '', $id); |
1072 | $fields = ['bib_id:string' => $db_id]; |
1073 | |
1074 | // Let's go check if this bib id is for a serial |
1075 | $sql = 'SELECT h.holdingsid, l.name ' . |
1076 | 'FROM dbadmin.holdlink h, dbadmin.location l ' . |
1077 | 'WHERE h.bibid = :bib_id ' . |
1078 | 'AND h.masked = 0 ' . |
1079 | 'AND h.location = l.location_id'; |
1080 | |
1081 | $result = $this->db->simpleSelect($sql, $fields); |
1082 | |
1083 | // Results indicate serial holdings |
1084 | if (count($result) == 0) { |
1085 | return []; |
1086 | } |
1087 | |
1088 | $sql = 'SELECT * ' . |
1089 | 'FROM dbadmin.iso_2709 i ' . |
1090 | 'WHERE i.id = :hid ' . |
1091 | 'AND i.idtype = 104 ' . |
1092 | "AND i.field_tag in ('853', '863', '866') " . |
1093 | 'ORDER BY i.field_sequence, i.subfield_sequence'; |
1094 | |
1095 | $data = []; |
1096 | foreach ($result as $row) { |
1097 | $fields = ['hid:string' => $row['HOLDINGSID']]; |
1098 | $hresult = $this->db->simpleSelect($sql, $fields); |
1099 | $data[$row['NAME']] = $this->renderSerialHoldings($hresult); |
1100 | } |
1101 | |
1102 | return $data; |
1103 | } |
1104 | |
1105 | /** |
1106 | * Used for TESTING only. Grabs all prediction |
1107 | * patterns in the system for analysis |
1108 | * |
1109 | * @return array |
1110 | */ |
1111 | public function getAll853() |
1112 | { |
1113 | $sql = 'SELECT * ' . |
1114 | 'FROM dbadmin.iso_2709 i ' . |
1115 | 'WHERE i.idtype = 104 ' . |
1116 | "AND i.field_tag in ('853') " . |
1117 | 'ORDER BY i.field_sequence, i.subfield_sequence'; |
1118 | $hresult = $this->db->simpleSelect($sql); |
1119 | if (count($hresult) == 0) { |
1120 | return null; |
1121 | } |
1122 | |
1123 | $data_set = []; |
1124 | foreach ($hresult as $row) { |
1125 | if ( |
1126 | $row['SUBFIELD_DATA'] != null |
1127 | && trim($row['SUBFIELD_DATA']) != '' |
1128 | ) { |
1129 | $data_set[$row['ID'] . '_' . $row['FIELD_SEQUENCE']][] = [ |
1130 | 'id' => trim($row['ID']), |
1131 | 'code' => trim($row['SUBFIELD_CODE']), |
1132 | 'data' => trim($row['SUBFIELD_DATA']), |
1133 | ]; |
1134 | } |
1135 | } |
1136 | return $data_set; |
1137 | } |
1138 | |
1139 | /* END - Serials functions */ |
1140 | |
1141 | /** |
1142 | * Patron Login |
1143 | * |
1144 | * This is responsible for authenticating a patron against the catalog. |
1145 | * |
1146 | * @param string $barcode The patron barcode |
1147 | * @param string $password The patron password |
1148 | * |
1149 | * @throws ILSException |
1150 | * @return mixed Associative array of patron info on successful login, |
1151 | * null on unsuccessful login. |
1152 | */ |
1153 | public function patronLogin($barcode, $password) |
1154 | { |
1155 | $sql = 'SELECT i.id, b.barcode, i.subfield_data AS password, p.name, ' . |
1156 | 'p.e_mail_address_primary, p.department ' . |
1157 | 'FROM dbadmin.iso_2709 i, dbadmin.patron p, dbadmin.patron_barcode b ' . |
1158 | 'WHERE i.idtype = 105 ' . |
1159 | "AND i.field_tag = '015' " . |
1160 | "AND i.subfield_code = 'b' " . |
1161 | 'AND p.patron_id = i.id ' . |
1162 | 'AND b.patron_id = i.id ' . |
1163 | 'AND i.id = ( ' . |
1164 | ' SELECT p.patron_id AS id ' . |
1165 | ' FROM dbadmin.patron_barcode p ' . |
1166 | ' WHERE UPPER(p.barcode) = UPPER(:barcode) ' . |
1167 | ')'; |
1168 | |
1169 | $fields = ['barcode:string' => $barcode]; |
1170 | $result = $this->db->simpleSelect($sql, $fields); |
1171 | |
1172 | if (count($result) > 0) { |
1173 | // Valid Password |
1174 | if ($result[0]['PASSWORD'] == $password) { |
1175 | $user = []; |
1176 | $split = strpos($result[0]['NAME'], ','); |
1177 | $last_name = trim(substr($result[0]['NAME'], 0, $split)); |
1178 | $first_name = trim(substr($result[0]['NAME'], $split + 1)); |
1179 | $split = strpos($first_name, ' '); |
1180 | if ($split !== false) { |
1181 | $first_name = trim(substr($first_name, 0, $split)); |
1182 | } |
1183 | |
1184 | $user['id'] = trim($result[0]['ID']); |
1185 | $user['firstname'] = trim($first_name); |
1186 | $user['lastname'] = trim($last_name); |
1187 | $user['cat_username'] = strtoupper(trim($result[0]['BARCODE'])); |
1188 | $user['cat_password'] = trim($result[0]['PASSWORD']); |
1189 | $user['email'] = trim($result[0]['E_MAIL_ADDRESS_PRIMARY']); |
1190 | $user['major'] = trim($result[0]['DEPARTMENT']); |
1191 | $user['college'] = null; |
1192 | |
1193 | return $user; |
1194 | } else { |
1195 | // Invalid Password |
1196 | return null; |
1197 | } |
1198 | } else { |
1199 | // User not found |
1200 | return null; |
1201 | } |
1202 | } |
1203 | |
1204 | /** |
1205 | * Get Patron Profile |
1206 | * |
1207 | * This is responsible for retrieving the profile for a specific patron. |
1208 | * |
1209 | * @param array $patron The patron array |
1210 | * |
1211 | * @throws ILSException |
1212 | * @return array Array of the patron's profile data on success. |
1213 | */ |
1214 | public function getMyProfile($patron) |
1215 | { |
1216 | $sql = 'SELECT p.name, p.street_address_1, p.street_address_2, p.city, ' . |
1217 | 'p.postal_code, p.telephone_primary, t.name as patron_type ' . |
1218 | 'FROM dbadmin.patron_type_patron pt, dbadmin.patron p, ' . |
1219 | 'dbadmin.patron_type t ' . |
1220 | 'WHERE p.patron_id = pt.patron_id ' . |
1221 | 'AND t.patron_type_id = pt.patron_type_id ' . |
1222 | 'AND p.patron_id = :patron_id'; |
1223 | |
1224 | $fields = ['patron_id:string' => $patron['id']]; |
1225 | $result = $this->db->simpleSelect($sql, $fields); |
1226 | |
1227 | if (count($result) > 0) { |
1228 | $split = strpos($result[0]['NAME'], ','); |
1229 | $last_name = substr($result[0]['NAME'], 0, $split); |
1230 | $first_name = substr($result[0]['NAME'], $split + 1); |
1231 | $split = strpos($result[0]['NAME'], ' '); |
1232 | if ($split !== false) { |
1233 | $first_name = substr($first_name, 0, $split); |
1234 | } |
1235 | |
1236 | $patron = [ |
1237 | 'firstname' => trim($first_name), |
1238 | 'lastname' => trim($last_name), |
1239 | 'address1' => trim($result[0]['STREET_ADDRESS_1']), |
1240 | 'address2' => trim($result[0]['STREET_ADDRESS_2']), |
1241 | 'zip' => trim($result[0]['POSTAL_CODE']), |
1242 | 'phone' => trim($result[0]['TELEPHONE_PRIMARY']), |
1243 | 'group' => trim($result[0]['PATRON_TYPE']), |
1244 | ]; |
1245 | |
1246 | if ($result[0]['CITY'] != null) { |
1247 | if (strlen($patron['address2']) > 0) { |
1248 | $patron['address2'] .= ', ' . trim($result[0]['CITY']); |
1249 | } else { |
1250 | $patron['address2'] = trim($result[0]['CITY']); |
1251 | } |
1252 | } |
1253 | |
1254 | return $patron; |
1255 | } else { |
1256 | return null; |
1257 | } |
1258 | } |
1259 | |
1260 | /** |
1261 | * Get Patron Fines |
1262 | * |
1263 | * This is responsible for retrieving all fines by a specific patron. |
1264 | * |
1265 | * @param array $patron The patron array from patronLogin |
1266 | * |
1267 | * @throws DateException |
1268 | * @throws ILSException |
1269 | * @return mixed Array of the patron's fines on success. |
1270 | */ |
1271 | public function getMyFines($patron) |
1272 | { |
1273 | $fineList = []; |
1274 | |
1275 | $sql = 'SELECT a.assessment_amount fine_amount, f.description, ' . |
1276 | 'a.balance, a.item_due_date due_date, i.bibid bib_id ' . |
1277 | 'FROM patron_account a, fine_code_v f, itemdetl2 i ' . |
1278 | 'WHERE a.state = 0 ' . |
1279 | 'AND a.balance > 0 ' . |
1280 | 'AND a.itemid = i.itemid ' . |
1281 | 'AND a.fine_code_id = f.fine_code_id ' . |
1282 | 'AND a.patron_id = :patron_id'; |
1283 | |
1284 | $fields = ['patron_id:string' => $patron['id']]; |
1285 | $result = $this->db->simpleSelect($sql, $fields); |
1286 | |
1287 | if (count($result) > 0) { |
1288 | foreach ($result as $row) { |
1289 | $fineList[] = [ |
1290 | 'amount' => $row['FINE_AMOUNT'] * 100, |
1291 | 'fine' => $row['DESCRIPTION'], |
1292 | 'balance' => $row['BALANCE'] * 100, |
1293 | 'duedate' => $row['DUE_DATE'], |
1294 | 'id' => 'vtls' . sprintf('%09d', (int)$row['BIB_ID']), |
1295 | ]; |
1296 | } |
1297 | } |
1298 | return $fineList; |
1299 | } |
1300 | |
1301 | /** |
1302 | * Get Patron Holds |
1303 | * |
1304 | * This is responsible for retrieving all holds by a specific patron. |
1305 | * |
1306 | * @param array $patron The patron array from patronLogin |
1307 | * |
1308 | * @throws DateException |
1309 | * @throws ILSException |
1310 | * @return array Array of the patron's holds on success. |
1311 | */ |
1312 | public function getMyHolds($patron) |
1313 | { |
1314 | $holdList = []; |
1315 | |
1316 | $sql = 'SELECT h.bibid, l.name pickup_location, h.pickup_any_location, ' . |
1317 | 'h.date_last_needed, h.date_placed, h.request_control_number ' . |
1318 | 'FROM dbadmin.hlrcdetl h, dbadmin.location l ' . |
1319 | 'WHERE h.pickup_location = l.location_id ' . |
1320 | 'AND h.patron_id = :patron_id'; |
1321 | |
1322 | $fields = ['patron_id:string' => $patron['id']]; |
1323 | $result = $this->db->simpleSelect($sql, $fields); |
1324 | |
1325 | if (count($result) > 0) { |
1326 | foreach ($result as $row) { |
1327 | $holdList[] = [ |
1328 | 'id' => 'vtls' . sprintf('%09d', (int)$row['BIBID']), |
1329 | 'location' => $row['PICKUP_LOCATION'], |
1330 | 'expire' => $row['DATE_LAST_NEEDED'], |
1331 | 'create' => $row['DATE_PLACED'], |
1332 | 'reqnum' => $row['REQUEST_CONTROL_NUMBER'], |
1333 | ]; |
1334 | } |
1335 | } |
1336 | return $holdList; |
1337 | } |
1338 | |
1339 | /** |
1340 | * Get Patron Transactions |
1341 | * |
1342 | * This is responsible for retrieving all transactions (i.e. checked out items) |
1343 | * by a specific patron. |
1344 | * |
1345 | * @param array $patron The patron array from patronLogin |
1346 | * |
1347 | * @throws DateException |
1348 | * @throws ILSException |
1349 | * @return array Array of the patron's transactions on success. |
1350 | */ |
1351 | public function getMyTransactions($patron) |
1352 | { |
1353 | $transList = []; |
1354 | |
1355 | $bib_reqs = 'SELECT h.bibid, count(*) as bib_req ' . |
1356 | 'FROM hlrcdetl h ' . |
1357 | 'WHERE h.itemid = 0 ' . |
1358 | 'GROUP BY h.bibid'; |
1359 | $item_reqs = 'SELECT h.itemid, count(*) as item_req ' . |
1360 | 'FROM hlrcdetl h ' . |
1361 | 'WHERE h.itemid <> 0 ' . |
1362 | 'GROUP BY h.itemid'; |
1363 | |
1364 | $sql = 'SELECT i.bibid, i.itemid, c.due_date, i.barcode, ' . |
1365 | 'c.renew_count, (br.bib_req + ir.item_req) as req_count ' . |
1366 | "FROM circdetl c, itemdetl2 i, ($bib_reqs) br, ($item_reqs) ir " . |
1367 | 'WHERE c.itemid = i.itemid ' . |
1368 | 'AND i.bibid = br.bibid (+) ' . |
1369 | 'AND i.itemid = ir.itemid (+) ' . |
1370 | 'AND c.patron_id = :patron_id ' . |
1371 | 'ORDER BY c.due_date'; |
1372 | |
1373 | $fields = ['patron_id:string' => $patron['id']]; |
1374 | $result = $this->db->simpleSelect($sql, $fields); |
1375 | |
1376 | if (count($result) > 0) { |
1377 | foreach ($result as $row) { |
1378 | $transList[] = [ |
1379 | 'duedate' => $row['DUE_DATE'], |
1380 | 'barcode' => $row['BARCODE'], |
1381 | 'renew' => $row['RENEW_COUNT'], |
1382 | 'request' => $row['REQ_COUNT'], |
1383 | // IDs need to show as 'vtls000589589' |
1384 | 'id' => 'vtls' . sprintf('%09d', (int)$row['BIBID']), |
1385 | ]; |
1386 | } |
1387 | } |
1388 | return $transList; |
1389 | } |
1390 | |
1391 | /** |
1392 | * Get Courses |
1393 | * |
1394 | * Obtain a list of courses for use in limiting the reserves list. |
1395 | * |
1396 | * @throws ILSException |
1397 | * @return array An associative array with key = ID, value = name. |
1398 | */ |
1399 | public function getCourses() |
1400 | { |
1401 | $courseList = []; |
1402 | |
1403 | $sql = 'SELECT DISTINCT l.course_id ' . |
1404 | 'FROM reserve_list_v l, reserve_item_v i ' . |
1405 | 'WHERE l.Reserve_list_id = i.Reserve_list_id ' . |
1406 | 'AND SYSDATE BETWEEN i.Begin_date AND i.End_date ' . |
1407 | 'ORDER BY l.course_id'; |
1408 | $result = $this->db->simpleSelect($sql); |
1409 | |
1410 | if (count($result) > 0) { |
1411 | foreach ($result as $row) { |
1412 | $courseList[] = $row['COURSE_ID']; |
1413 | } |
1414 | } |
1415 | |
1416 | return $courseList; |
1417 | } |
1418 | |
1419 | /** |
1420 | * Find Reserves |
1421 | * |
1422 | * Obtain information on course reserves. |
1423 | * |
1424 | * @param string $course ID from getCourses (empty string to match all) |
1425 | * @param string $inst ID from getInstructors (empty string to match all) |
1426 | * @param string $dept ID from getDepartments (empty string to match all) |
1427 | * |
1428 | * @throws ILSException |
1429 | * @return array An array of associative arrays representing reserve items. |
1430 | * |
1431 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) |
1432 | */ |
1433 | public function findReserves($course, $inst = false, $dept = false) |
1434 | { |
1435 | $recordList = []; |
1436 | |
1437 | $sql = 'SELECT DISTINCT d.bibid ' . |
1438 | 'FROM reserve_item_v i, reserve_list_v l, itemdetl2 d ' . |
1439 | 'WHERE i.Reserve_list_id = l.Reserve_list_id ' . |
1440 | 'AND SYSDATE BETWEEN i.Begin_date AND i.End_date ' . |
1441 | 'AND i.Item_id = d.itemid ' . |
1442 | 'AND l.Course_id = :course'; |
1443 | $fields = ['course:string' => $course]; |
1444 | $result = $this->db->simpleSelect($sql, $fields); |
1445 | |
1446 | if (count($result) > 0) { |
1447 | foreach ($result as $row) { |
1448 | $recordList[] = 'vtls' . sprintf('%09d', (int)$row['BIBID']); |
1449 | } |
1450 | } |
1451 | |
1452 | return $recordList; |
1453 | } |
1454 | |
1455 | /** |
1456 | * Retrieve the opening hours for all campuses. |
1457 | * - Used on the home page to show time information. |
1458 | * |
1459 | * @param string $fake_time Optional time string (for debugging purposes) |
1460 | * |
1461 | * @return array Opening hours information. |
1462 | */ |
1463 | public function getOpeningHours($fake_time = null) |
1464 | { |
1465 | // Change this value for debugging |
1466 | // eg. strtotime('25-12-2009') = Christmas |
1467 | if ($fake_time) { |
1468 | $time = strtotime($fake_time); |
1469 | } else { |
1470 | $time = strtotime('now'); |
1471 | } |
1472 | $today = date('d-m-Y', $time); |
1473 | $time_format = 'H:i:s'; |
1474 | |
1475 | // Fix Date Handling |
1476 | $this->db->simpleSql( |
1477 | "ALTER SESSION SET NLS_DATE_FORMAT = 'DD-MM-YY HH24:MI:SS'" |
1478 | ); |
1479 | |
1480 | // Normal opening hours |
1481 | $sql = 'SELECT campus, open_time, close_time, status ' . |
1482 | 'FROM usq_sr_open_normal n ' . |
1483 | 'WHERE UPPER(dayofweek) = UPPER(:dow)'; |
1484 | $fields = ['dow:string' => date('l', $time)]; |
1485 | $result = $this->db->simpleSelect($sql, $fields); |
1486 | if (count($result) == 0) { |
1487 | return []; |
1488 | } |
1489 | |
1490 | // Create our return data structure |
1491 | $times = []; |
1492 | foreach ($result as $row) { |
1493 | // Remember times come out with no date, add in today. |
1494 | $times[$row['CAMPUS']] = [ |
1495 | 'open' => "$today " . |
1496 | date($time_format, strtotime($row['OPEN_TIME'])), |
1497 | 'close' => "$today " . |
1498 | date($time_format, strtotime($row['CLOSE_TIME'])), |
1499 | 'status' => $row['STATUS'], |
1500 | ]; |
1501 | } |
1502 | |
1503 | // Opening hours exceptions |
1504 | $day = strtolower(date('D', $time)); |
1505 | // Lowest priority row (numericaly, ie. 1 = most important) |
1506 | $priority = 'SELECT e.campus, MIN(e.priority) as priority ' . |
1507 | 'FROM usq_sr_open_except e ' . |
1508 | "WHERE to_date(:today,'dd/mm/yyyy') " . |
1509 | 'BETWEEN e.except_date_from AND e.except_date_to ' . |
1510 | " AND app_$day = 1 " . |
1511 | 'GROUP BY e.campus'; |
1512 | // Retrieve Exceptions |
1513 | $sql = 'SELECT e.campus, e.open_time, e.close_time, e.status, e.reason ' . |
1514 | "FROM ($priority) p, usq_sr_open_except e " . |
1515 | 'WHERE e.campus = p.campus ' . |
1516 | 'AND e.priority = p.priority ' . |
1517 | "AND to_date(:today,'dd/mm/yyyy') " . |
1518 | 'BETWEEN e.except_date_from AND e.except_date_to ' . |
1519 | "AND app_$day = 1"; |
1520 | $fields = ['today:string' => date('d/m/Y', $time)]; |
1521 | $exceptions = $this->db->simpleSelect($sql, $fields); |
1522 | |
1523 | foreach ($exceptions as $row) { |
1524 | $times[$row['CAMPUS']] = [ |
1525 | // Remember times come out with no date, add in today. |
1526 | 'open' => "$today " |
1527 | . date($time_format, strtotime($row['OPEN_TIME'])), |
1528 | 'close' => "$today " |
1529 | . date($time_format, strtotime($row['CLOSE_TIME'])), |
1530 | 'status' => $row['STATUS'], |
1531 | 'reason' => $row['REASON'], |
1532 | ]; |
1533 | } |
1534 | return $times; |
1535 | } |
1536 | |
1537 | /** |
1538 | * Place Hold |
1539 | * |
1540 | * Attempts to place a hold or recall on a particular item and returns |
1541 | * an array with result details or throws an exception on failure of support |
1542 | * classes |
1543 | * |
1544 | * @param array $holdDetails An array of item and patron data |
1545 | * |
1546 | * @throws ILSException |
1547 | * @return mixed An array of data on the request including |
1548 | * whether or not it was successful and a system message (if available) |
1549 | */ |
1550 | public function placeHold($holdDetails) |
1551 | { |
1552 | // Extract key values from the hold details |
1553 | $patron_id = $holdDetails['patron']; |
1554 | $req_level = $holdDetails['req_level']; |
1555 | $pickup_loc = $holdDetails['pickUpLocation']; |
1556 | $item_id = $holdDetails['item_id']; |
1557 | $last_date = $holdDetails['requiredBy']; |
1558 | |
1559 | // Assume an error response: |
1560 | $response = ['success' => false, 'status' => 'hold_error_fail']; |
1561 | |
1562 | // Validate input |
1563 | // * Request level |
1564 | $allowed_req_levels = [ |
1565 | 'item' => 0, |
1566 | 'bib' => 1, |
1567 | 'volume' => 2, |
1568 | ]; |
1569 | if (!in_array($req_level, array_keys($allowed_req_levels))) { |
1570 | return $response; |
1571 | } |
1572 | // * Pickup Location |
1573 | $allowed_pickup_locs = [ |
1574 | 'Toowoomba' => '10000', |
1575 | 'Fraser Coast' => '40000', |
1576 | 'Springfield' => '50000', |
1577 | ]; |
1578 | if (!in_array($pickup_loc, array_keys($allowed_pickup_locs))) { |
1579 | return $response; |
1580 | } |
1581 | // * Last Date - Valid date and a future date |
1582 | $ts_last_date = strtotime($last_date); |
1583 | if ($ts_last_date == 0 || $ts_last_date <= strtotime('now')) { |
1584 | return $response; |
1585 | } |
1586 | |
1587 | // Still here? Guess the request is valid, lets send it to virtua |
1588 | $virtua_url = $this->getApiBaseUrl() . '?' . |
1589 | // Standard stuff |
1590 | 'search=NOSRCH&function=REQUESTS&reqreqtype=0&reqtype=0' . |
1591 | '&reqscr=2&reqreqlevel=2&reqidtype=127&reqmincircperiod=' . |
1592 | // Item ID |
1593 | "&reqidno=$item_id" . |
1594 | // Patron barcode |
1595 | "&reqpatronbarcode=$patron_id" . |
1596 | // Request Level |
1597 | '&reqautoadjustlevel=' . $allowed_req_levels[$req_level] . |
1598 | // Pickup location |
1599 | '&reqpickuplocation=' . $allowed_pickup_locs[$pickup_loc] . |
1600 | // Last Date |
1601 | '&reqexpireday=' . date('j', $ts_last_date) . |
1602 | '&reqexpiremonth=' . date('n', $ts_last_date) . |
1603 | '&reqexpireyear=' . date('Y', $ts_last_date); |
1604 | |
1605 | // Get the response |
1606 | $result = $this->httpRequest($virtua_url); |
1607 | |
1608 | // Look for an error message |
1609 | $error_message = 'Your request was not processed.'; |
1610 | $test = strpos($result, $error_message); |
1611 | |
1612 | // If we succeeded, override the default fail message with success: |
1613 | if ($test === false) { |
1614 | $response['success'] = true; |
1615 | $response['status'] = 'hold_success'; |
1616 | } |
1617 | |
1618 | return $response; |
1619 | } |
1620 | |
1621 | /** |
1622 | * Get Cancel Hold Details |
1623 | * |
1624 | * In order to cancel a hold, Voyager requires the patron details an item ID |
1625 | * and a recall ID. This function returns the item id and recall id as a string |
1626 | * separated by a pipe, which is then submitted as form data in Hold.php. This |
1627 | * value is then extracted by the CancelHolds function. |
1628 | * |
1629 | * @param array $holdDetails A single hold array from getMyHolds |
1630 | * @param array $patron Patron information from patronLogin |
1631 | * |
1632 | * @return string Data for use in a form field |
1633 | * |
1634 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) |
1635 | */ |
1636 | public function getCancelHoldDetails($holdDetails, $patron = []) |
1637 | { |
1638 | throw new \Exception('TODO: implement getCancelHoldDetails.'); |
1639 | } |
1640 | |
1641 | /** |
1642 | * Cancel Holds |
1643 | * |
1644 | * Attempts to Cancel a hold or recall on a particular item. The |
1645 | * data in $cancelDetails['details'] is determined by getCancelHoldDetails(). |
1646 | * |
1647 | * @param array $cancelDetails An array of item and patron data |
1648 | * |
1649 | * @return array An array of data on each request including |
1650 | * whether or not it was successful and a system message (if available) |
1651 | * |
1652 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) |
1653 | */ |
1654 | public function cancelHolds($cancelDetails) |
1655 | { |
1656 | // TODO: implement standard VuFind holds API; utilize cancelHold() |
1657 | // below as a support method. |
1658 | throw new \Exception('cancelHolds() is not supported yet.'); |
1659 | } |
1660 | |
1661 | /** |
1662 | * Cancel a request in virtua. |
1663 | * - Return true/false for success/failure. |
1664 | * |
1665 | * @param string $request_number ID of hold to cancel |
1666 | * |
1667 | * @return bool |
1668 | */ |
1669 | protected function cancelHold($request_number) |
1670 | { |
1671 | $virtua_url = $this->getApiBaseUrl() . '?' . |
1672 | // Standard stuff |
1673 | 'search=NOSRCH&function=REQUESTS&reqreqtype=1&reqtype=0' . |
1674 | '&reqscr=4&reqreqlevel=2&reqidtype=127' . |
1675 | //"&reqidno=1000651541" . |
1676 | "&reqctrlnum=$request_number"; |
1677 | |
1678 | try { |
1679 | // Get the response |
1680 | $result = $this->httpRequest($virtua_url); |
1681 | // Invalid server response. It's probably down |
1682 | } catch (\Exception $e) { |
1683 | return false; |
1684 | } |
1685 | |
1686 | // Look for an error message |
1687 | $error_message = 'Your request could not be deleted.'; |
1688 | $test = strpos($result, $error_message); |
1689 | |
1690 | // Return true unless we find the error |
1691 | if ($test === false) { |
1692 | return true; |
1693 | } else { |
1694 | return false; |
1695 | } |
1696 | } |
1697 | |
1698 | /** |
1699 | * Fake a virtua login on the patron's behalf. |
1700 | * - Return a session id. |
1701 | * |
1702 | * @param array $patron Array with cat_username/cat_password keys |
1703 | * |
1704 | * @return string Session ID |
1705 | */ |
1706 | protected function fakeLogin($patron) |
1707 | { |
1708 | $virtua_url = $this->getApiBaseUrl(); |
1709 | $postParams = [ |
1710 | 'SourceScreen' => 'INITREQ', |
1711 | 'conf' => './chameleon.conf', |
1712 | 'elementcount' => '1', |
1713 | 'function' => 'PATRONATTEMPT', |
1714 | 'host' => $this->config['Catalog']['host_string'], |
1715 | 'lng' => $this->getConfiguredLanguage(), |
1716 | 'login' => '1', |
1717 | 'pos' => '1', |
1718 | 'rootsearch' => 'KEYWORD', |
1719 | 'search' => 'NOSRCH', |
1720 | 'skin' => 'homepage', |
1721 | 'patronid' => $patron['cat_username'], |
1722 | 'patronpassword' => $patron['cat_password'], |
1723 | 'patronhost' => $this->config['Catalog']['patron_host'], |
1724 | ]; |
1725 | |
1726 | // Get the response |
1727 | $result = $this->httpRequest($virtua_url, $postParams); |
1728 | // Now find the sessionid. There should be one in the meta tags, |
1729 | // so we can look for the first one in the document |
1730 | // eg. <meta http-equiv="Refresh" content="30000; |
1731 | // url=http://libwebtest2.usq.edu.au:80/cgi-bin/chameleon?sessionid= |
1732 | //2009071712483605131&skin=homepage&lng=en&inst= |
1733 | //consortium&conf=.%26%23047%3bchameleon.conf&timedout=1" /> |
1734 | $start = strpos($result, 'sessionid=') + 10; |
1735 | $end = strpos($result, '&skin='); |
1736 | return substr($result, $start, $end - $start); |
1737 | } |
1738 | |
1739 | /** |
1740 | * Get Renew Details |
1741 | * |
1742 | * In order to renew an item, Voyager requires the patron details and an item |
1743 | * id. This function returns the item id as a string which is then used |
1744 | * as submitted form data in checkedOut.php. This value is then extracted by |
1745 | * the RenewMyItems function. |
1746 | * |
1747 | * @param array $checkOutDetails An array of item data |
1748 | * |
1749 | * @return string Data for use in a form field |
1750 | * |
1751 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) |
1752 | */ |
1753 | public function getRenewDetails($checkOutDetails) |
1754 | { |
1755 | throw new \Exception('TODO: implement getRenewDetails'); |
1756 | } |
1757 | |
1758 | /** |
1759 | * Renew My Items |
1760 | * |
1761 | * Function for attempting to renew a patron's items. The data in |
1762 | * $renewDetails['details'] is determined by getRenewDetails(). |
1763 | * |
1764 | * @param array $renewDetails An array of data required for renewing items |
1765 | * including the Patron ID and an array of renewal IDS |
1766 | * |
1767 | * @return array An array of renewal information keyed by item ID |
1768 | */ |
1769 | public function renewMyItems($renewDetails) |
1770 | { |
1771 | $item_list = $renewDetails['details']; |
1772 | $patron = $renewDetails['patron']; |
1773 | |
1774 | // Get items out on loan at the moment |
1775 | $result = $this->getMyTransactions($patron); |
1776 | // Make it more accessible - by barcode |
1777 | $initial = []; |
1778 | foreach ($result as $row) { |
1779 | $initial[$row['barcode']] = $row; |
1780 | } |
1781 | |
1782 | // Fake a login to get an authenticated session |
1783 | $session_id = $this->fakeLogin($patron); |
1784 | |
1785 | $virtua_url = $this->getApiBaseUrl(); |
1786 | |
1787 | // Have to use raw post data because of the way |
1788 | // virtua expects the barcodes to come across. |
1789 | $post_data = 'function=' . 'RENEWAL'; |
1790 | $post_data .= '&search=' . 'PATRON'; |
1791 | $post_data .= '&sessionid=' . "$session_id"; |
1792 | $post_data .= '&skin=' . 'homepage'; |
1793 | $post_data .= '&lng=' . $this->getConfiguredLanguage(); |
1794 | $post_data .= '&inst=' . 'consortium'; |
1795 | $post_data .= '&conf=' . urlencode('./chameleon.conf'); |
1796 | $post_data .= '&u1=' . '12'; |
1797 | $post_data .= '&SourceScreen=' . 'PATRONACTIVITY'; |
1798 | $post_data .= '&pos=' . '1'; |
1799 | $post_data .= '&patronid=' . $patron['cat_username']; |
1800 | $post_data .= '&patronhost=' |
1801 | . urlencode($this->config['Catalog']['patron_host']); |
1802 | $post_data .= '&host=' |
1803 | . urlencode($this->config['Catalog']['host_string']); |
1804 | $post_data .= '&itembarcode=' . implode('&itembarcode=', $item_list); |
1805 | $post_data .= '&submit=' . 'Renew'; |
1806 | $post_data .= '&reset=' . 'Clear'; |
1807 | |
1808 | $result = $this->httpRequest($virtua_url, null, $post_data); |
1809 | |
1810 | // Get items out on loan with renewed info |
1811 | $result = $this->getMyTransactions($patron); |
1812 | |
1813 | // Foreach item currently on loan |
1814 | $return = []; |
1815 | foreach ($result as $row) { |
1816 | // Did we even attempt to renew? |
1817 | if (in_array($row['barcode'], $item_list)) { |
1818 | // Yes, so check if the due date changed |
1819 | if ($row['duedate'] != $initial[$row['barcode']]['duedate']) { |
1820 | $row['error'] = false; |
1821 | $row['renew_text'] = 'Item successfully renewed.'; |
1822 | } else { |
1823 | $row['error'] = true; |
1824 | $row['renew_text'] = 'Item renewal failed.'; |
1825 | } |
1826 | $return[] = $row; |
1827 | } else { |
1828 | // No attempt to renew this item |
1829 | $return[] = $row; |
1830 | } |
1831 | } |
1832 | return $return; |
1833 | } |
1834 | |
1835 | /** |
1836 | * Get suppressed authority records |
1837 | * |
1838 | * @return array ID numbers of suppressed authority records in the system. |
1839 | */ |
1840 | public function getSuppressedAuthorityRecords() |
1841 | { |
1842 | $list = []; |
1843 | |
1844 | $sql = 'select auth_id ' . |
1845 | 'from state_record_authority ' . |
1846 | 'WHERE STATE_ID = 1'; |
1847 | |
1848 | $result = $this->db->simpleSelect($sql); |
1849 | |
1850 | if ($result === false) { |
1851 | throw new ILSException( |
1852 | 'An error occurred while connecting to Virtua' |
1853 | ); |
1854 | } |
1855 | |
1856 | foreach ($result as $row) { |
1857 | $list[] = 'vtls' . str_pad($row['AUTH_ID'], 9, '0', STR_PAD_LEFT); |
1858 | } |
1859 | return $list; |
1860 | } |
1861 | |
1862 | /** |
1863 | * Support method -- get base URL for API requests. |
1864 | * |
1865 | * @return string |
1866 | */ |
1867 | protected function getApiBaseUrl() |
1868 | { |
1869 | // Get the iPortal server |
1870 | $host = $this->config['Catalog']['webhost']; |
1871 | $path = isset($this->config['Catalog']['cgi_token']) |
1872 | ? trim($this->config['Catalog']['cgi_token'], '/') |
1873 | : 'cgi-bin'; |
1874 | return "http://{$host}/{$path}/chameleon"; |
1875 | } |
1876 | |
1877 | /** |
1878 | * Support method -- determine the language from the configuration. |
1879 | * |
1880 | * @return string |
1881 | */ |
1882 | protected function getConfiguredLanguage() |
1883 | { |
1884 | return $this->config['Catalog']['language'] ?? 'en'; |
1885 | } |
1886 | |
1887 | /** |
1888 | * Support method -- perform an HTTP request. This will be a GET request unless |
1889 | * either $postParams or $rawPost is set to a non-null value. |
1890 | * |
1891 | * @param string $url Target URL for request |
1892 | * @param array $postParams Associative array of POST parameters (null for |
1893 | * none). |
1894 | * @param string $rawPost String representing raw POST parameters (null for |
1895 | * none). |
1896 | * |
1897 | * @throws ILSException |
1898 | * @return string Response body |
1899 | */ |
1900 | protected function httpRequest($url, $postParams = null, $rawPost = null) |
1901 | { |
1902 | $method = (null === $postParams && null === $rawPost) ? 'GET' : 'POST'; |
1903 | |
1904 | try { |
1905 | $client = $this->httpService->createClient($url); |
1906 | if (is_array($postParams)) { |
1907 | $client->setParameterPost($postParams); |
1908 | } |
1909 | if (null !== $rawPost) { |
1910 | $client->setRawBody($rawPost); |
1911 | $client->setEncType('application/x-www-form-urlencoded'); |
1912 | } |
1913 | $result = $client->setMethod($method)->send(); |
1914 | } catch (\Exception $e) { |
1915 | $this->throwAsIlsException($e); |
1916 | } |
1917 | |
1918 | if (!$result->isSuccess()) { |
1919 | throw new ILSException('HTTP error'); |
1920 | } |
1921 | |
1922 | return $result->getBody(); |
1923 | } |
1924 | |
1925 | /* Methods yet to be implemented -- see Voyager driver for examples |
1926 | |
1927 | public function getNewItems($page, $limit, $daysOld, $fundId = null) |
1928 | |
1929 | public function getFunds() |
1930 | |
1931 | public function getSuppressedRecords() |
1932 | */ |
1933 | } |