Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.89% |
3 / 336 |
|
5.56% |
1 / 18 |
CRAP | |
0.00% |
0 / 1 |
Koha | |
0.89% |
3 / 336 |
|
5.56% |
1 / 18 |
5698.66 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
init | |
8.70% |
2 / 23 |
|
0.00% |
0 / 1 |
24.03 | |||
getHolding | |
0.00% |
0 / 67 |
|
0.00% |
0 / 1 |
210 | |||
getHoldLink | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getMyFines | |
0.00% |
0 / 28 |
|
0.00% |
0 / 1 |
42 | |||
getMyHolds | |
0.00% |
0 / 22 |
|
0.00% |
0 / 1 |
12 | |||
getMyProfile | |
0.00% |
0 / 24 |
|
0.00% |
0 / 1 |
12 | |||
getMyTransactions | |
0.00% |
0 / 20 |
|
0.00% |
0 / 1 |
12 | |||
getAccountBlocks | |
0.00% |
0 / 19 |
|
0.00% |
0 / 1 |
56 | |||
getMyTransactionHistory | |
0.00% |
0 / 50 |
|
0.00% |
0 / 1 |
132 | |||
getPurchaseHistory | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getStatus | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getStatuses | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
getSuppressedRecords | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
patronLogin | |
0.00% |
0 / 40 |
|
0.00% |
0 / 1 |
56 | |||
displayDate | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
20 | |||
displayDateTime | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
12 | |||
getConfig | |
0.00% |
0 / 16 |
|
0.00% |
0 / 1 |
12 |
1 | <?php |
2 | |
3 | /** |
4 | * Koha ILS Driver |
5 | * |
6 | * PHP version 8 |
7 | * |
8 | * Copyright (C) Ayesha Abed Library, BRAC University 2010. |
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 Altaf Mahmud, System Programmer <altaf.mahmud@gmail.com> |
26 | * @author David Maus <maus@hab.de> |
27 | * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License |
28 | * @link https://vufind.org/wiki/development:plugins:ils_drivers Wiki |
29 | */ |
30 | |
31 | namespace VuFind\ILS\Driver; |
32 | |
33 | use PDO; |
34 | use PDOException; |
35 | use VuFind\Date\DateException; |
36 | use VuFind\Exception\ILS as ILSException; |
37 | |
38 | use function count; |
39 | |
40 | /** |
41 | * VuFind Driver for Koha (version: 3.02) |
42 | * |
43 | * @category VuFind |
44 | * @package ILS_Drivers |
45 | * @author Altaf Mahmud, System Programmer <altaf.mahmud@gmail.com> |
46 | * @author David Maus <maus@hab.de> |
47 | * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License |
48 | * @link https://vufind.org/wiki/development:plugins:ils_drivers Wiki |
49 | */ |
50 | class Koha extends AbstractBase |
51 | { |
52 | /** |
53 | * Database connection |
54 | * |
55 | * @var PDO |
56 | */ |
57 | protected $db; |
58 | |
59 | /** |
60 | * ILS base URL |
61 | * |
62 | * @var string |
63 | */ |
64 | protected $ilsBaseUrl; |
65 | |
66 | /** |
67 | * Location codes |
68 | * |
69 | * @var array |
70 | */ |
71 | protected $locCodes; |
72 | |
73 | /** |
74 | * Date converter object |
75 | * |
76 | * @var \VuFind\Date\Converter |
77 | */ |
78 | protected $dateConverter = null; |
79 | |
80 | /** |
81 | * Should we validate passwords against Koha system? |
82 | * |
83 | * @var boolean |
84 | */ |
85 | protected $validatePasswords; |
86 | |
87 | /** |
88 | * Default terms for block types, can be overridden by configuration |
89 | * |
90 | * @var array |
91 | */ |
92 | protected $blockTerms = [ |
93 | 'SUSPENSION' => 'Account Suspended', |
94 | 'OVERDUES' => 'Account Blocked (Overdue Items)', |
95 | 'MANUAL' => 'Account Blocked', |
96 | 'DISCHARGE' => 'Account Blocked for Discharge', |
97 | ]; |
98 | |
99 | /** |
100 | * Display comments for patron debarments, see Koha.ini |
101 | * |
102 | * @var array |
103 | */ |
104 | protected $showBlockComments; |
105 | |
106 | /** |
107 | * Constructor |
108 | * |
109 | * @param \VuFind\Date\Converter $dateConverter Date converter |
110 | */ |
111 | public function __construct(\VuFind\Date\Converter $dateConverter) |
112 | { |
113 | $this->dateConverter = $dateConverter; |
114 | } |
115 | |
116 | /** |
117 | * Initialize the driver. |
118 | * |
119 | * Validate configuration and perform all resource-intensive tasks needed to |
120 | * make the driver active. |
121 | * |
122 | * @throws ILSException |
123 | * @return void |
124 | */ |
125 | public function init() |
126 | { |
127 | if (empty($this->config)) { |
128 | throw new ILSException('Configuration needs to be set.'); |
129 | } |
130 | |
131 | //Connect to MySQL |
132 | $this->db = new PDO( |
133 | 'mysql:host=' . $this->config['Catalog']['host'] . |
134 | ';port=' . $this->config['Catalog']['port'] . |
135 | ';dbname=' . $this->config['Catalog']['database'], |
136 | $this->config['Catalog']['username'], |
137 | $this->config['Catalog']['password'], |
138 | [PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8'] |
139 | ); |
140 | // Throw PDOExceptions if something goes wrong |
141 | $this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); |
142 | // Return result set like mysql_fetch_assoc() |
143 | $this->db->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); |
144 | |
145 | //Storing the base URL of ILS |
146 | $this->ilsBaseUrl = $this->config['Catalog']['url']; |
147 | |
148 | // Location codes are defined in 'Koha.ini' file according to current |
149 | // version (3.02) |
150 | $this->locCodes = $this->config['Location_Codes']; |
151 | |
152 | // If we are using SAML/Shibboleth for authentication for both ourselves |
153 | // and Koha then we can't validate the patrons passwords against Koha as |
154 | // they won't have one. (Double negative logic used so that if the config |
155 | // option isn't present in Koha.ini then ILS passwords will be validated) |
156 | $this->validatePasswords |
157 | = empty($this->config['Catalog']['dontValidatePasswords']); |
158 | |
159 | // Now override the default with any defined in the `Koha.ini` config file |
160 | foreach (['SUSPENSION','OVERDUES','MANUAL','DISCHARGE'] as $blockType) { |
161 | if (!empty($this->config['Blocks'][$blockType])) { |
162 | $this->blockTerms[$blockType] = $this->config['Blocks'][$blockType]; |
163 | } |
164 | } |
165 | |
166 | // Allow the users to set if an account block's comments should be included |
167 | // by setting the block type to true or false () in the `Koha.ini` config |
168 | // file (defaults to false if not present) |
169 | $this->showBlockComments = []; |
170 | |
171 | foreach (['SUSPENSION','OVERDUES','MANUAL','DISCHARGE'] as $blockType) { |
172 | $this->showBlockComments[$blockType] |
173 | = !empty($this->config['Show_Block_Comments'][$blockType]); |
174 | } |
175 | } |
176 | |
177 | /** |
178 | * Get Holding |
179 | * |
180 | * This is responsible for retrieving the holding information of a certain |
181 | * record. |
182 | * |
183 | * @param string $id The record id to retrieve the holdings for |
184 | * @param array $patron Patron data |
185 | * @param array $options Extra options (not currently used) |
186 | * |
187 | * @throws DateException |
188 | * @throws ILSException |
189 | * @return array On success, an associative array with the following |
190 | * keys: id, availability (boolean), status, location, reserve, callnumber, |
191 | * duedate, number, barcode. |
192 | * |
193 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) |
194 | */ |
195 | public function getHolding($id, array $patron = null, array $options = []) |
196 | { |
197 | $holding = []; |
198 | $available = true; |
199 | $duedate = $status = ''; |
200 | $inum = 0; |
201 | $loc = $shelf = ''; |
202 | $sql = 'select itemnumber as ITEMNO, location as LOCATION, ' . |
203 | 'holdingbranch as HLDBRNCH, reserves as RESERVES, itemcallnumber as ' . |
204 | 'CALLNO, barcode as BARCODE, copynumber as COPYNO, ' . |
205 | 'enumchron AS ENUMCHRON, notforloan as NOTFORLOAN' . |
206 | ' from items where biblionumber = :id' . |
207 | ' order by itemnumber'; |
208 | try { |
209 | $itemSqlStmt = $this->db->prepare($sql); |
210 | $itemSqlStmt->execute([':id' => $id]); |
211 | foreach ($itemSqlStmt->fetchAll() as $rowItem) { |
212 | $inum = $rowItem['ITEMNO']; |
213 | $sql = 'select date_due as DUEDATE from issues ' . |
214 | 'where itemnumber = :inum'; |
215 | |
216 | switch ($rowItem['NOTFORLOAN']) { |
217 | case 0: |
218 | // If the item is available for loan, then check its current |
219 | // status |
220 | $issueSqlStmt = $this->db->prepare($sql); |
221 | $issueSqlStmt->execute([':inum' => $inum]); |
222 | $rowIssue = $issueSqlStmt->fetch(); |
223 | if ($rowIssue) { |
224 | $available = false; |
225 | $status = 'Checked out'; |
226 | $duedate = $this->displayDateTime($rowIssue['DUEDATE']); |
227 | } else { |
228 | $available = true; |
229 | $status = 'Available'; |
230 | // No due date for an available item |
231 | $duedate = ''; |
232 | } |
233 | break; |
234 | case 1: // The item is not available for loan |
235 | default: |
236 | $available = false; |
237 | $status = 'Not for loan'; |
238 | $duedate = ''; |
239 | break; |
240 | } |
241 | |
242 | //Retrieving the full branch name |
243 | if (null != ($loc = $rowItem['HLDBRNCH'])) { |
244 | $sql = 'select branchname as BNAME from branches where ' . |
245 | 'branchcode = :loc'; |
246 | $locSqlStmt = $this->db->prepare($sql); |
247 | $locSqlStmt->execute([':loc' => $loc]); |
248 | $row = $locSqlStmt->fetch(); |
249 | if ($row) { |
250 | $loc = $row['BNAME']; |
251 | } |
252 | } else { |
253 | $loc = 'Unknown'; |
254 | } |
255 | |
256 | //Retrieving the location (shelf types) |
257 | $shelf = $rowItem['LOCATION']; |
258 | $loc = (null != $shelf) |
259 | ? $loc . ': ' . ($this->locCodes[$shelf] ?? $shelf) |
260 | : $loc . ': ' . 'Unknown'; |
261 | |
262 | //A default value is stored for null |
263 | $holding[] = [ |
264 | 'id' => $id, |
265 | 'availability' => $available, |
266 | 'item_num' => $rowItem['ITEMNO'], |
267 | 'status' => $status, |
268 | 'location' => $loc, |
269 | 'reserve' => (null == $rowItem['RESERVES']) |
270 | ? 'Unknown' : $rowItem['RESERVES'], |
271 | 'callnumber' => (null == $rowItem['CALLNO']) |
272 | ? 'Unknown' : $rowItem['CALLNO'], |
273 | 'duedate' => $duedate, |
274 | 'barcode' => (null == $rowItem['BARCODE']) |
275 | ? 'Unknown' : $rowItem['BARCODE'], |
276 | 'number' => (null == $rowItem['COPYNO']) |
277 | ? 'Unknown' : $rowItem['COPYNO'], |
278 | 'enumchron' => $rowItem['ENUMCHRON'] ?? null, |
279 | ]; |
280 | } |
281 | } catch (PDOException $e) { |
282 | $this->throwAsIlsException($e); |
283 | } |
284 | return $holding; |
285 | } |
286 | |
287 | /** |
288 | * Get Hold Link |
289 | * |
290 | * The goal for this method is to return a URL to a "place hold" web page on |
291 | * the ILS OPAC. This is used for ILSs that do not support an API or method |
292 | * to place Holds. |
293 | * |
294 | * @param string $id The id of the bib record |
295 | * @param array $details Item details from getHoldings return array |
296 | * |
297 | * @return string URL to ILS's OPAC's place hold screen. |
298 | * |
299 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) |
300 | */ |
301 | public function getHoldLink($id, $details) |
302 | { |
303 | // Web link of the ILS for placing hold on the item |
304 | return $this->ilsBaseUrl . "/cgi-bin/koha/opac-reserve.pl?biblionumber=$id"; |
305 | } |
306 | |
307 | /** |
308 | * Get Patron Fines |
309 | * |
310 | * This is responsible for retrieving all fines by a specific patron. |
311 | * |
312 | * @param array $patron The patron array from patronLogin |
313 | * |
314 | * @throws DateException |
315 | * @throws ILSException |
316 | * @return mixed Array of the patron's fines on success. |
317 | */ |
318 | public function getMyFines($patron) |
319 | { |
320 | $sql = $sqlStmt = $row = ''; |
321 | $id = 0; |
322 | $fineLst = []; |
323 | try { |
324 | $id = $patron['id']; |
325 | $sql = 'select round(accountlines.amount*100) as AMOUNT, ' . |
326 | 'issues.issuedate as CHECKOUT, ' . |
327 | 'accountlines.description as FINE, ' . |
328 | 'round(accountlines.amountoutstanding*100) as BALANCE, ' . |
329 | 'issues.date_due as DUEDATE, items.biblionumber as BIBNO ' . |
330 | 'from accountlines join issues on ' . |
331 | 'accountlines.borrowernumber = issues.borrowernumber and ' . |
332 | 'accountlines.itemnumber = issues.itemnumber ' . |
333 | 'join items on accountlines.itemnumber = items.itemnumber ' . |
334 | 'where accountlines.borrowernumber = :id'; |
335 | $sqlStmt = $this->db->prepare($sql); |
336 | $sqlStmt->execute([':id' => $id]); |
337 | foreach ($sqlStmt->fetchAll() as $row) { |
338 | $fineLst[] = [ |
339 | 'amount' => (null == $row['AMOUNT']) ? 0 : $row['AMOUNT'], |
340 | 'checkout' => $this->displayDate($row['CHECKOUT']), |
341 | 'fine' => (null == $row['FINE']) ? 'Unknown' : $row['FINE'], |
342 | 'balance' => (null == $row['BALANCE']) ? 0 : $row['BALANCE'], |
343 | 'duedate' => $this->displayDate($row['DUEDATE']), |
344 | 'id' => $row['BIBNO'], |
345 | ]; |
346 | } |
347 | return $fineLst; |
348 | } catch (PDOException $e) { |
349 | $this->throwAsIlsException($e); |
350 | } |
351 | } |
352 | |
353 | /** |
354 | * Get Patron Holds |
355 | * |
356 | * This is responsible for retrieving all holds by a specific patron. |
357 | * |
358 | * @param array $patron The patron array from patronLogin |
359 | * |
360 | * @throws DateException |
361 | * @throws ILSException |
362 | * @return array Array of the patron's holds on success. |
363 | */ |
364 | public function getMyHolds($patron) |
365 | { |
366 | $sql = $sqlStmt = $row = ''; |
367 | $id = 0; |
368 | $holdLst = []; |
369 | try { |
370 | $id = $patron['id']; |
371 | $sql = 'select reserves.biblionumber as BIBNO, ' . |
372 | 'branches.branchname as BRNAME, ' . |
373 | 'reserves.expirationdate as EXDATE, ' . |
374 | 'reserves.reservedate as RSVDATE from reserves ' . |
375 | 'join branches on reserves.branchcode = branches.branchcode ' . |
376 | 'where reserves.borrowernumber = :id'; |
377 | $sqlStmt = $this->db->prepare($sql); |
378 | $sqlStmt->execute([':id' => $id]); |
379 | foreach ($sqlStmt->fetchAll() as $row) { |
380 | $holdLst[] = [ |
381 | 'id' => $row['BIBNO'], |
382 | 'location' => $row['BRNAME'], |
383 | 'expire' => $this->displayDate($row['EXDATE']), |
384 | 'create' => $this->displayDate($row['RSVDATE']), |
385 | ]; |
386 | } |
387 | } catch (PDOException $e) { |
388 | $this->throwAsIlsException($e); |
389 | } |
390 | return $holdLst; |
391 | } |
392 | |
393 | /** |
394 | * Get Patron Profile |
395 | * |
396 | * This is responsible for retrieving the profile for a specific patron. |
397 | * |
398 | * @param array $patron The patron array |
399 | * |
400 | * @throws ILSException |
401 | * @return array Array of the patron's profile data on success. |
402 | */ |
403 | public function getMyProfile($patron) |
404 | { |
405 | $id = 0; |
406 | $sql = $sqlStmt = $row = ''; |
407 | $profile = []; |
408 | try { |
409 | $id = $patron['id']; |
410 | $sql = 'select address as ADDR1, address2 as ADDR2, zipcode as ZIP, ' . |
411 | 'phone as PHONE, categorycode as GRP from borrowers ' . |
412 | 'where borrowernumber = :id'; |
413 | $sqlStmt = $this->db->prepare($sql); |
414 | $sqlStmt->execute([':id' => $id]); |
415 | $row = $sqlStmt->fetch(); |
416 | if ($row) { |
417 | $profile = [ |
418 | 'firstname' => $patron['firstname'], |
419 | 'lastname' => $patron['lastname'], |
420 | 'address1' => $row['ADDR1'], |
421 | 'address2' => $row['ADDR2'], |
422 | 'zip' => $row['ZIP'], |
423 | 'phone' => $row['PHONE'], |
424 | 'group' => $row['GRP'], |
425 | ]; |
426 | return $profile; |
427 | } |
428 | } catch (PDOException $e) { |
429 | $this->throwAsIlsException($e); |
430 | } |
431 | return null; |
432 | } |
433 | |
434 | /** |
435 | * Get Patron Transactions |
436 | * |
437 | * This is responsible for retrieving all transactions (i.e. checked out items) |
438 | * by a specific patron. |
439 | * |
440 | * @param array $patron The patron array from patronLogin |
441 | * |
442 | * @throws DateException |
443 | * @throws ILSException |
444 | * @return array Array of the patron's transactions on success. |
445 | */ |
446 | public function getMyTransactions($patron) |
447 | { |
448 | $id = 0; |
449 | $transactionLst = []; |
450 | $row = $sql = $sqlStmt = ''; |
451 | try { |
452 | $id = $patron['id']; |
453 | $sql = 'select issues.date_due as DUEDATE, items.biblionumber as ' . |
454 | 'BIBNO, items.barcode BARCODE, issues.renewals as RENEWALS ' . |
455 | 'from issues join items on issues.itemnumber = items.itemnumber ' . |
456 | 'where issues.borrowernumber = :id'; |
457 | $sqlStmt = $this->db->prepare($sql); |
458 | $sqlStmt->execute([':id' => $id]); |
459 | foreach ($sqlStmt->fetchAll() as $row) { |
460 | $transactionLst[] = [ |
461 | 'duedate' => $this->displayDateTime($row['DUEDATE']), |
462 | 'id' => $row['BIBNO'], |
463 | 'barcode' => $row['BARCODE'], |
464 | 'renew' => $row['RENEWALS'], |
465 | ]; |
466 | } |
467 | } catch (PDOException $e) { |
468 | $this->throwAsIlsException($e); |
469 | } |
470 | return $transactionLst; |
471 | } |
472 | |
473 | /** |
474 | * Check whether the patron has any blocks on their account. |
475 | * |
476 | * @param array $patron Patron data from patronLogin |
477 | * |
478 | * @throws ILSException |
479 | * |
480 | * @return mixed A boolean false if no blocks are in place and an array |
481 | * of block reasons if blocks are in place |
482 | */ |
483 | public function getAccountBlocks($patron) |
484 | { |
485 | $blocks = []; |
486 | |
487 | try { |
488 | $id = $patron['id']; |
489 | $sql = 'select type as TYPE, comment as COMMENT ' . |
490 | 'from borrower_debarments ' . |
491 | 'where (expiration is null or expiration >= NOW()) ' . |
492 | 'and borrowernumber = :id'; |
493 | $sqlStmt = $this->db->prepare($sql); |
494 | $sqlStmt->execute([':id' => $id]); |
495 | |
496 | foreach ($sqlStmt->fetchAll() as $row) { |
497 | $block = empty($this->blockTerms[$row['TYPE']]) |
498 | ? [$row['TYPE']] |
499 | : [$this->blockTerms[$row['TYPE']]]; |
500 | |
501 | if ( |
502 | !empty($this->showBlockComments[$row['TYPE']]) |
503 | && !empty($row['COMMENT']) |
504 | ) { |
505 | $block[] = $row['COMMENT']; |
506 | } |
507 | |
508 | $blocks[] = implode(' - ', $block); |
509 | } |
510 | } catch (PDOException $e) { |
511 | $this->throwAsIlsException($e); |
512 | } |
513 | |
514 | return count($blocks) ? $blocks : false; |
515 | } |
516 | |
517 | /** |
518 | * Get Patron Loan History |
519 | * |
520 | * This is responsible for retrieving all historic loans (i.e. items previously |
521 | * checked out and then returned), for a specific patron. |
522 | * |
523 | * @param array $patron The patron array from patronLogin |
524 | * @param array $params Parameters |
525 | * |
526 | * @throws DateException |
527 | * @throws ILSException |
528 | * @return array Array of the patron's transactions on success. |
529 | */ |
530 | public function getMyTransactionHistory($patron, $params) |
531 | { |
532 | $id = 0; |
533 | $historicLoans = []; |
534 | $row = $sql = $sqlStmt = ''; |
535 | try { |
536 | $id = $patron['id']; |
537 | |
538 | // Get total count first |
539 | $sql = 'select count(*) as cnt from old_issues ' . |
540 | 'where old_issues.borrowernumber = :id'; |
541 | $sqlStmt = $this->db->prepare($sql); |
542 | $sqlStmt->execute([':id' => $id]); |
543 | $totalCount = $sqlStmt->fetch()['cnt']; |
544 | |
545 | // Get rows |
546 | $limit = isset($params['limit']) ? (int)$params['limit'] : 50; |
547 | $start = isset($params['page']) |
548 | ? ((int)$params['page'] - 1) * $limit : 0; |
549 | if (isset($params['sort'])) { |
550 | $parts = explode(' ', $params['sort'], 2); |
551 | switch ($parts[0]) { |
552 | case 'return': |
553 | $sort = 'RETURNED'; |
554 | break; |
555 | case 'due': |
556 | $sort = 'DUEDATE'; |
557 | break; |
558 | default: |
559 | $sort = 'ISSUEDATE'; |
560 | break; |
561 | } |
562 | $sort .= isset($parts[1]) && 'asc' === $parts[1] ? ' asc' : ' desc'; |
563 | } else { |
564 | $sort = 'ISSUEDATE desc'; |
565 | } |
566 | $sql = 'select old_issues.issuedate as ISSUEDATE, ' . |
567 | 'old_issues.date_due as DUEDATE, items.biblionumber as ' . |
568 | 'BIBNO, items.barcode BARCODE, old_issues.returndate as RETURNED, ' . |
569 | 'biblio.title as TITLE ' . |
570 | 'from old_issues join items ' . |
571 | 'on old_issues.itemnumber = items.itemnumber ' . |
572 | 'join biblio on items.biblionumber = biblio.biblionumber ' . |
573 | 'where old_issues.borrowernumber = :id ' . |
574 | "order by $sort limit $start,$limit"; |
575 | $sqlStmt = $this->db->prepare($sql); |
576 | |
577 | $sqlStmt->execute([':id' => $id]); |
578 | foreach ($sqlStmt->fetchAll() as $row) { |
579 | $historicLoans[] = [ |
580 | 'title' => $row['TITLE'], |
581 | 'checkoutDate' => $this->displayDateTime($row['ISSUEDATE']), |
582 | 'dueDate' => $this->displayDateTime($row['DUEDATE']), |
583 | 'id' => $row['BIBNO'], |
584 | 'barcode' => $row['BARCODE'], |
585 | 'returnDate' => $this->displayDateTime($row['RETURNED']), |
586 | ]; |
587 | } |
588 | } catch (PDOException $e) { |
589 | $this->throwAsIlsException($e); |
590 | } |
591 | return [ |
592 | 'count' => $totalCount, |
593 | 'transactions' => $historicLoans, |
594 | ]; |
595 | } |
596 | |
597 | /** |
598 | * Get Purchase History |
599 | * |
600 | * This is responsible for retrieving the acquisitions history data for the |
601 | * specific record (usually recently received issues of a serial). |
602 | * |
603 | * @param string $id The record id to retrieve the info for |
604 | * |
605 | * @throws ILSException |
606 | * @return array An array with the acquisitions data on success. |
607 | * |
608 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) |
609 | */ |
610 | public function getPurchaseHistory($id) |
611 | { |
612 | // TODO |
613 | return []; |
614 | } |
615 | |
616 | /** |
617 | * Get Status |
618 | * |
619 | * This is responsible for retrieving the status information of a certain |
620 | * record. |
621 | * |
622 | * @param string $id The record id to retrieve the holdings for |
623 | * |
624 | * @throws ILSException |
625 | * @return mixed On success, an associative array with the following keys: |
626 | * id, availability (boolean), status, location, reserve, callnumber. |
627 | */ |
628 | public function getStatus($id) |
629 | { |
630 | return $this->getHolding($id); |
631 | } |
632 | |
633 | /** |
634 | * Get Statuses |
635 | * |
636 | * This is responsible for retrieving the status information for a |
637 | * collection of records. |
638 | * |
639 | * @param array $idLst The array of record ids to retrieve the status for |
640 | * |
641 | * @throws ILSException |
642 | * @return array An array of getStatus() return values on success. |
643 | */ |
644 | public function getStatuses($idLst) |
645 | { |
646 | $statusLst = []; |
647 | foreach ($idLst as $id) { |
648 | $statusLst[] = $this->getStatus($id); |
649 | } |
650 | return $statusLst; |
651 | } |
652 | |
653 | /** |
654 | * Get suppressed records. |
655 | * |
656 | * NOTE: This function needs to be modified only if Koha has |
657 | * suppressed records in OPAC view |
658 | * |
659 | * @throws ILSException |
660 | * @return array ID numbers of suppressed records in the system. |
661 | */ |
662 | public function getSuppressedRecords() |
663 | { |
664 | // TODO |
665 | return []; |
666 | } |
667 | |
668 | /** |
669 | * Patron Login |
670 | * |
671 | * This is responsible for authenticating a patron against the catalog. |
672 | * |
673 | * @param string $username The patron username |
674 | * @param string $password The patron's password |
675 | * |
676 | * @throws ILSException |
677 | * @return mixed Associative array of patron info on successful login, |
678 | * null on unsuccessful login. |
679 | */ |
680 | public function patronLogin($username, $password) |
681 | { |
682 | $patron = []; |
683 | $row = ''; |
684 | |
685 | $stored_hash = ''; |
686 | try { |
687 | $sql = 'select password from borrowers where userid = :username'; |
688 | $sqlStmt = $this->db->prepare($sql); |
689 | $sqlStmt->execute([':username' => $username]); |
690 | $row = $sqlStmt->fetch(); |
691 | if ($row) { |
692 | $stored_hash = $row['password']; |
693 | } else { |
694 | return null; |
695 | } |
696 | } catch (PDOException $e) { |
697 | $this->throwAsIlsException($e); |
698 | } |
699 | |
700 | if (str_starts_with($stored_hash, '$2a$')) { |
701 | // Newer Koha version that uses bcrypt |
702 | $db_pwd = crypt($password, $stored_hash); |
703 | } else { |
704 | // Koha used to use MD5_BASE64 encoding to save borrowers' passwords, |
705 | // function 'rtrim' is used to discard trailing '=' signs, suitable for |
706 | // pushing into MySQL database |
707 | $db_pwd = rtrim(base64_encode(pack('H*', md5($password))), '='); |
708 | } |
709 | |
710 | $sql = 'select borrowernumber as ID, firstname as FNAME, ' . |
711 | 'surname as LNAME, email as EMAIL from borrowers ' . |
712 | 'where userid = :username'; |
713 | |
714 | $parameters = [':username' => $username]; |
715 | |
716 | if ($this->validatePasswords) { |
717 | $sql .= ' and password = :db_pwd'; |
718 | $parameters[':db_pwd'] = $db_pwd; |
719 | } |
720 | |
721 | try { |
722 | $sqlStmt = $this->db->prepare($sql); |
723 | $sqlStmt->execute($parameters); |
724 | |
725 | $row = $sqlStmt->fetch(); |
726 | if ($row) { |
727 | // NOTE: Here, 'cat_password' => $password is used, password is |
728 | // saved in a clear text as user provided. If 'cat_password' => |
729 | // $db_pwd was used, then password will be saved encrypted as in |
730 | // 'borrowers' table of 'koha' database |
731 | $patron = [ |
732 | 'id' => $row['ID'], |
733 | 'firstname' => $row['FNAME'], |
734 | 'lastname' => $row['LNAME'], |
735 | 'cat_username' => $username, |
736 | 'cat_password' => $password, |
737 | 'email' => $row['EMAIL'], |
738 | 'major' => null, |
739 | 'college' => null, |
740 | ]; |
741 | |
742 | return $patron; |
743 | } |
744 | return null; |
745 | } catch (PDOException $e) { |
746 | $this->throwAsIlsException($e); |
747 | } |
748 | } |
749 | |
750 | /** |
751 | * Convert a database date to a displayable date. |
752 | * |
753 | * @param string $date Date to convert |
754 | * |
755 | * @return string |
756 | */ |
757 | public function displayDate($date) |
758 | { |
759 | if (empty($date)) { |
760 | return ''; |
761 | } elseif (preg_match("/^\d{4}-\d\d-\d\d \d\d:\d\d:\d\d$/", $date) === 1) { |
762 | // YYYY-MM-DD HH:MM:SS |
763 | return $this->dateConverter->convertToDisplayDate('Y-m-d H:i:s', $date); |
764 | } elseif (preg_match("/^\d{4}-\d{2}-\d{2}$/", $date) === 1) { // YYYY-MM-DD |
765 | return $this->dateConverter->convertToDisplayDate('Y-m-d', $date); |
766 | } else { |
767 | error_log("Unexpected date format: $date"); |
768 | return $date; |
769 | } |
770 | } |
771 | |
772 | /** |
773 | * Convert a database datetime to a displayable date and time. |
774 | * |
775 | * @param string $date Datetime to convert |
776 | * |
777 | * @return string |
778 | */ |
779 | public function displayDateTime($date) |
780 | { |
781 | if (empty($date)) { |
782 | return ''; |
783 | } elseif (preg_match("/^\d{4}-\d\d-\d\d \d\d:\d\d:\d\d$/", $date) === 1) { |
784 | // YYYY-MM-DD HH:MM:SS |
785 | return |
786 | $this->dateConverter->convertToDisplayDateAndTime( |
787 | 'Y-m-d H:i:s', |
788 | $date |
789 | ); |
790 | } else { |
791 | error_log("Unexpected date format: $date"); |
792 | return $date; |
793 | } |
794 | } |
795 | |
796 | /** |
797 | * Public Function which retrieves renew, hold and cancel settings from the |
798 | * driver ini file. |
799 | * |
800 | * @param string $function The name of the feature to be checked |
801 | * @param array $params Optional feature-specific parameters (array) |
802 | * |
803 | * @return array An array with key-value pairs. |
804 | * |
805 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) |
806 | */ |
807 | public function getConfig($function, $params = []) |
808 | { |
809 | if ('getMyTransactionHistory' === $function) { |
810 | if (empty($this->config['TransactionHistory']['enabled'])) { |
811 | return false; |
812 | } |
813 | return [ |
814 | 'max_results' => 100, |
815 | 'sort' => [ |
816 | 'checkout desc' => 'sort_checkout_date_desc', |
817 | 'checkout asc' => 'sort_checkout_date_asc', |
818 | 'return desc' => 'sort_return_date_desc', |
819 | 'return asc' => 'sort_return_date_asc', |
820 | 'due desc' => 'sort_due_date_desc', |
821 | 'due asc' => 'sort_due_date_asc', |
822 | ], |
823 | 'default_sort' => 'checkout desc', |
824 | ]; |
825 | } |
826 | return $this->config[$function] ?? false; |
827 | } |
828 | } |