Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.49% covered (danger)
0.49%
2 / 409
0.00% covered (danger)
0.00%
0 / 20
CRAP
0.00% covered (danger)
0.00%
0 / 1
Amicus
0.49% covered (danger)
0.49%
2 / 409
0.00% covered (danger)
0.00%
0 / 20
7204.53
0.00% covered (danger)
0.00%
0 / 1
 init
9.09% covered (danger)
9.09%
2 / 22
0.00% covered (danger)
0.00%
0 / 1
9.76
 pickStatus
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
42
 determineAvailability
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
30
 sacaStatus
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
20
 sacaFecha
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
20
 sacaReservas
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
12
 getStatus
0.00% covered (danger)
0.00%
0 / 79
0.00% covered (danger)
0.00%
0 / 1
240
 getStatuses
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 getHolding
0.00% covered (danger)
0.00%
0 / 40
0.00% covered (danger)
0.00%
0 / 1
56
 getPurchaseHistory
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
12
 patronLogin
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
20
 getMyTransactions
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
12
 getMyFines
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
12
 getMyHolds
0.00% covered (danger)
0.00%
0 / 20
0.00% covered (danger)
0.00%
0 / 1
12
 getMyProfile
0.00% covered (danger)
0.00%
0 / 33
0.00% covered (danger)
0.00%
0 / 1
12
 getHoldLink
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getNewItems
0.00% covered (danger)
0.00%
0 / 37
0.00% covered (danger)
0.00%
0 / 1
56
 getFunds
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
12
 findReserves
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 1
12
 getSuppressedRecords
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
12
1<?php
2
3/**
4 * Amicus ILS Driver
5 *
6 * PHP version 8
7 *
8 * Copyright (C) Scanbit 2011.
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   Josu Moreno <jmoreno@scanbit.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
30namespace VuFind\ILS\Driver;
31
32use PDO;
33use PDOException;
34use VuFind\Date\DateException;
35use VuFind\Exception\ILS as ILSException;
36use VuFind\I18n\Translator\TranslatorAwareInterface;
37
38use function count;
39use function in_array;
40
41/**
42 * Amicus ILS Driver
43 *
44 * @category VuFind
45 * @package  ILS_Drivers
46 * @author   Josu Moreno <jmoreno@scanbit.net>
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 */
50class Amicus extends AbstractBase implements TranslatorAwareInterface
51{
52    use \VuFind\I18n\Translator\TranslatorAwareTrait;
53
54    /**
55     * Database connection
56     *
57     * @var PDO
58     */
59    protected $db;
60
61    /**
62     * Stored status rankings from the database; initialized to false but populated
63     * by the pickStatus() method.
64     *
65     * @var array|bool
66     */
67    protected $statusRankings = false;
68
69    /**
70     * Initialize the driver.
71     *
72     * Validate configuration and perform all resource-intensive tasks needed to
73     * make the driver active.
74     *
75     * @throws ILSException
76     * @return void
77     */
78    public function init()
79    {
80        if (empty($this->config)) {
81            throw new ILSException('Configuration needs to be set.');
82        }
83
84        // Define Database Name
85        $tns = '(DESCRIPTION=' .
86                 '(ADDRESS_LIST=' .
87                   '(ADDRESS=' .
88                     '(PROTOCOL=TCP)' .
89                     '(HOST=' . $this->config['Catalog']['host'] . ')' .
90                     '(PORT=' . $this->config['Catalog']['port'] . ')' .
91                   ')' .
92                 ')' .
93                 '(CONNECT_DATA=' .
94                   '(SERVICE_NAME=' . $this->config['Catalog']['service'] . ')' .
95                 ')' .
96               ')';
97        try {
98            $this->db = new PDO(
99                "oci:dbname=$tns",
100                $this->config['Catalog']['user'],
101                $this->config['Catalog']['password']
102            );
103            $this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
104        } catch (PDOException $e) {
105            throw $e;
106        }
107    }
108
109    /**
110     * Support method to pick which status message to display when multiple
111     * options are present.
112     *
113     * @param array $statusArray Array of status messages to choose from.
114     *
115     * @return string            The best status message to display.
116     */
117    protected function pickStatus($statusArray)
118    {
119        // This array controls the rankings of possible status messages. The lower
120        // the ID in the ITEM_STATUS_TYPE table, the higher the priority of the
121        // message. We only need to load it once -- after that, it's cached in the
122        // driver.
123        if ($this->statusRankings == false) {
124            // Execute SQL
125            $sql = 'SELECT * FROM T_HLDG_STUS_TYP';
126            try {
127                $sqlStmt = $this->db->prepare($sql);
128                $sqlStmt->execute();
129            } catch (PDOException $e) {
130                $this->throwAsIlsException($e);
131            }
132
133            // Read results
134            while ($row = $sqlStmt->fetch(PDO::FETCH_ASSOC)) {
135                $this->statusRankings[$row['TBL_LNG_FR_TXT']] = $row['TBL_VLU_CDE'];
136            }
137        }
138
139        // Pick the first entry by default, then see if we can find a better match:
140        $status = $statusArray[0];
141        $rank = $this->statusRankings[$status];
142        for ($x = 1; $x < count($statusArray); $x++) {
143            if ($this->statusRankings[$statusArray[$x]] < $rank) {
144                $status = $statusArray[$x];
145            }
146        }
147        return $status;
148    }
149
150    /**
151     * Support method to take an array of status strings and determine
152     * whether or not this indicates an available item. Returns an array with
153     * two keys: 'available', the boolean availability status, and 'otherStatuses',
154     * every status code found other than "Not Charged" - for use with _pickStatus().
155     *
156     * @param array $statusArray The status codes to analyze.
157     *
158     * @return array             Availability and other status information.
159     */
160    protected function determineAvailability($statusArray)
161    {
162        // It's possible for a record to have multiple status codes. We
163        // need to loop through in search of the "Not Charged" (i.e. on
164        // shelf) status, collecting any other statuses we find along the
165        // way...
166        $notCharged = false;
167        $otherStatuses = [];
168        foreach ($statusArray as $status) {
169            switch ($status) {
170                case 'Disponible':
171                    $notCharged = true;
172                    break;
173                default:
174                    $otherStatuses[] = $status;
175                    break;
176            }
177        }
178
179        // If we found other statuses or if we failed to find "Not Charged,"
180        // the item is not available!
181        $available = (count($otherStatuses) == 0 && $notCharged);
182        return ['available' => $available, 'otherStatuses' => $otherStatuses];
183    }
184
185    /**
186     * Function that returns the number or on loan items for a given copy number.
187     * If there is no on loan items it returns 0.
188     * Used in getHolding and getStatus functions
189     *
190     * @param int $copyId The copy id number to check.
191     *
192     * @return int        Number of on loan items.
193     */
194    protected function sacaStatus($copyId)
195    {
196        $circulacion = 'SELECT COUNT(*) AS PRESTADO ' .
197            'FROM CIRT_ITM ' .
198            "WHERE CPY_ID_NBR = '$copyId'";
199
200        //$holds = "SELECT COUNT(*) AS PRESTADO FROM CIRTN_HLD " .
201        //"WHERE CPY_ID_NBR = '$copyId'";
202
203        $prestados = 0;
204        try {
205            $sqlStmt = $this->db->prepare($circulacion);
206            $sqlStmt->execute();
207            //$sqlStmt2 = $this->db->prepare($holds);
208            //$sqlStmt2->execute();
209        } catch (PDOException $e) {
210            $this->throwAsIlsException($e);
211        }
212        while ($row = $sqlStmt->fetch(PDO::FETCH_ASSOC)) {
213            $prestados = $row['PRESTADO'];
214            if ($row['PRESTADO'] == 0) {
215                $prestados = 'Disponible';
216            } else {
217                $prestados = 'No disponible';
218            }
219        }
220        return $prestados;
221    }
222
223    /**
224     * Function that returns the due date or a special message.
225     * If the difference is greater than 50 days it will return one special message
226     * If not it returns the due date
227     *
228     * @param int $copyId The copy id number to check.
229     *
230     * @return string     String with special message or due date.
231     */
232    protected function sacaFecha($copyId)
233    {
234        $circulacion = "SELECT to_char(CIRT_ITM_DUE_DTE,'dd-mm-yyyy') AS FECHADEV, "
235            . 'ROUND(CIRT_ITM_DUE_DTE - SYSDATE) AS DIFERENCIA '
236            . 'FROM CIRT_ITM '
237            . "WHERE CPY_ID_NBR = '$copyId'";
238        $fecha = 0;
239        $diferencia = 0;
240        try {
241            $sqlStmt = $this->db->prepare($circulacion);
242            $sqlStmt->execute();
243        } catch (PDOException $e) {
244            $this->throwAsIlsException($e);
245        }
246        while ($row = $sqlStmt->fetch(PDO::FETCH_ASSOC)) {
247            $diferencia = $row['DIFERENCIA'];
248            if ($diferencia > 50) {
249                $fecha = 'SIN DETERMINAR';
250            } else {
251                $fecha = $row['FECHADEV'];
252            }
253        }
254        return $fecha;
255    }
256
257    /**
258     * Function that returns the numbers of holds for a copy id number given.
259     * If there is no holds it returns 0.
260     *
261     * @param int $holdingId The copy id number to check.
262     *
263     * @return int           Integer with the number of holds.
264     */
265    protected function sacaReservas($holdingId)
266    {
267        $reservas = 'SELECT COUNT(*) as reservados ' .
268                    'FROM CIRTN_HLD ' .
269                    "WHERE CPY_ID_NBR = '$holdingId'";
270
271        $reservados = 0;
272        try {
273            $sqlStmt = $this->db->prepare($reservas);
274            $sqlStmt->execute();
275        } catch (PDOException $e) {
276            $this->throwAsIlsException($e);
277        }
278        while ($row = $sqlStmt->fetch(PDO::FETCH_ASSOC)) {
279            $reservados = $row['RESERVADOS'];
280        }
281        return $reservados;
282    }
283
284    /**
285     * Get Status
286     *
287     * This is responsible for retrieving the status information of a certain
288     * record.
289     *
290     * @param string $id The record id to retrieve the holdings for
291     *
292     * @throws ILSException
293     * @return mixed     On success, an associative array with the following keys:
294     * id, availability (boolean), status, location, reserve, callnumber.
295     */
296    public function getStatus($id)
297    {
298        // There are two possible queries we can use to obtain status information.
299        // The first (and most common) obtains information from a combination of
300        // items and holdings records. The second (a rare case) obtains
301        // information from the holdings record when no items are available.
302
303        $items = 'select BIB_ITM_NBR, ILL_CDE as ON_RESERVE, ' .
304            'T_LCTN_NME_BUO.TBL_LNG_ENG_TXT ' .
305            'as LOCATION, SHLF_LIST_SRT_FORM as CALLNUMBER, CPY_ID_NBR as ' .
306            'CPY_ID_NBR ' .
307            'from CPY_ID, SHLF_LIST, T_LCTN_NME_BUO ' .
308            'where CPY_ID.SHLF_LIST_KEY_NBR = SHLF_LIST.SHLF_LIST_KEY_NBR ' .
309            'and CPY_ID.LCTN_NME_CDE = T_LCTN_NME_BUO.TBL_VLU_CDE ' .
310            "and CPY_ID.BIB_ITM_NBR = '$id'";
311
312        $multipleLoc = 'SELECT COUNT(DISTINCT(SHLF_LIST_KEY_NBR)) AS multiple ' .
313                 'FROM CPY_ID ' .
314                 "WHERE CPY_ID.BIB_ITM_NBR = '$id'";
315
316        try {
317            $sqlStmt = $this->db->prepare($multipleLoc);
318            $sqlStmt->execute();
319        } catch (PDOException $e) {
320            $this->throwAsIlsException($e);
321        }
322
323        $multiple = '';
324        // Read results
325        while ($row = $sqlStmt->fetch(PDO::FETCH_ASSOC)) {
326            $multiple = $row['MULTIPLE'];
327        }
328        $prestados = 0;
329        $reservados = 0;
330        $possibleQueries = [$items];
331
332        // Loop through the possible queries and try each in turn -- the first one
333        // that yields results will cause the function to return.
334        foreach ($possibleQueries as $sql) {
335            // Execute SQL
336
337            try {
338                $sqlStmt = $this->db->prepare($sql);
339                $sqlStmt->execute();
340            } catch (PDOException $e) {
341                $this->throwAsIlsException($e);
342            }
343
344            // Build Array of Item Information
345            $data = [];
346            while ($row = $sqlStmt->fetch(PDO::FETCH_ASSOC)) {
347                $prestados = $this->sacaStatus($row['CPY_ID_NBR']);
348                $reservados = $this->sacaReservas($row['CPY_ID_NBR']);
349                if (!isset($data[$row['BIB_ITM_NBR']])) {
350                    if ($multiple != 1) {
351                        $multiple = $this->translate('Multiple Locations');
352                        $textoLoc = $this->translate('Multiple');
353                        $textoSign = $this->translate('Multiple Locations');
354                        $data[$row['BIB_ITM_NBR']] = [
355                            'id' => $id,
356                            'status' => $prestados,
357                            'status_array' => [$prestados],
358                            'location' => $textoLoc,
359                            'reserve' => $reservados,
360                            'callnumber' => $textoSign,
361                        ];
362                    } else {
363                        $multiple = $row['LOCATION'];
364                        if ($multiple == 'Deposito2') {
365                            $multiple = 'Depósito2';
366                        }
367                        if ($multiple == 'Deposito') {
368                            $multiple = 'Depósito';
369                        }
370                        $data[$row['BIB_ITM_NBR']] = [
371                            'id' => $id,
372                            'status' => $prestados,
373                            'status_array' => [$prestados],
374                            'location' => $multiple,
375                            'reserve' => $reservados,
376                            'callnumber' => $row['CALLNUMBER'],
377                        ];
378                    }
379                } else {
380                    $status_array = & $data[$row['BIB_ITM_NBR']]['status_array'];
381                    if (!in_array($prestados, $status_array)) {
382                        $status_array[] = $prestados;
383                    }
384                }
385            }
386            // If we found any information, break out of the foreach loop;
387            // we don't need to try any more queries.
388            if (count($data) == 0) {
389                $data[$id] = [
390                    'id' => $id,
391                    'status' => $prestados,
392                    'status_array' => [$prestados],
393                    'location' => $this->translate('No copies'),
394                    'reserve' => $reservados,
395                    'callnumber' => $this->translate('No copies'),
396                ];
397                break;
398            }
399            if (count($data) > 0) {
400                break;
401            }
402        }
403        // Process the raw data into final status information:
404        $status = [];
405        foreach ($data as $current) {
406            // Get availability/status info based on the array of status codes:
407            $availability = $this->determineAvailability($current['status_array']);
408            // If we found other statuses, we should override the display value
409            // appropriately:
410            if (count($availability['otherStatuses']) > 0) {
411                $current['status']
412                    = $this->pickStatus($availability['otherStatuses']);
413            }
414            $current['availability'] = $availability['available'];
415            $status[] = $current;
416        }
417        return $status;
418    }
419
420    /**
421     * Get Statuses
422     *
423     * This is responsible for retrieving the status information for a
424     * collection of records.
425     *
426     * @param array $idList The array of record ids to retrieve the status for
427     *
428     * @throws ILSException
429     * @return array        An array of getStatus() return values on success.
430     */
431    public function getStatuses($idList)
432    {
433        $status = [];
434
435        foreach ($idList as $id) {
436            $status[] = $this->getStatus($id);
437        }
438        return $status;
439    }
440
441    /**
442     * Get Holding
443     *
444     * This is responsible for retrieving the holding information of a certain
445     * record.
446     *
447     * @param string $id      The record id to retrieve the holdings for
448     * @param array  $patron  Patron data
449     * @param array  $options Extra options (not currently used)
450     *
451     * @throws DateException
452     * @throws ILSException
453     * @return array         On success, an associative array with the following
454     * keys: id, availability (boolean), status, location, reserve, callnumber,
455     * duedate, number, barcode.
456     *
457     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
458     */
459    public function getHolding($id, array $patron = null, array $options = [])
460    {
461        $items = 'select CPY_ID.BRCDE_NBR, CPY_ID.BIB_ITM_NBR, ' .
462            'T_LCTN_NME_BUO.TBL_LNG_ENG_TXT ' .
463            'as LOCATION, SHLF_LIST_SRT_FORM as CALLNUMBER, CPY_ID.CPY_ID_NBR as ' .
464            'CPY_ID_NBR ' .
465            'from CPY_ID, SHLF_LIST, T_LCTN_NME_BUO ' .
466            'where CPY_ID.SHLF_LIST_KEY_NBR = SHLF_LIST.SHLF_LIST_KEY_NBR ' .
467            'AND CPY_ID.LCTN_NME_CDE = T_LCTN_NME_BUO.TBL_VLU_CDE ' .
468            "and CPY_ID.BIB_ITM_NBR = '$id" .
469            'order by SHLF_LIST_SRT_FORM ASC, CPY_ID.CPY_ID_NBR ASC';
470
471        $possibleQueries = [$items];
472
473        // Loop through the possible queries and try each in turn -- the first one
474        // that yields results will cause us to break out of the loop.
475        foreach ($possibleQueries as $sql) {
476            // Execute SQL
477            try {
478                $sqlStmt = $this->db->prepare($sql);
479                $sqlStmt->execute();
480            } catch (PDOException $e) {
481                $this->throwAsIlsException($e);
482            }
483
484            // Build Holdings Array
485            $data = [];
486            while ($row = $sqlStmt->fetch(PDO::FETCH_ASSOC)) {
487                // Determine Location
488                $loc = $row['LOCATION'];
489                if ($loc == 'Deposito2') {
490                    $loc = 'Depósito2';
491                }
492                if ($loc == 'Deposito') {
493                    $loc = 'Depósito';
494                }
495
496                $status = $this->sacaStatus($row['CPY_ID_NBR']);
497                $availability = $this->determineAvailability([$status]);
498                $currentItem = [
499                    'id' => $id,
500                    'availability' => $availability['available'],
501                    'status' => $status,
502                    'location' => $loc,
503                    // TODO: make this smarter if you use course reserves:
504                    'reserve' => 'N',
505                    'callnumber' => $row['CALLNUMBER'],
506                    'duedate' => $this->sacaFecha($row['CPY_ID_NBR']),
507                    // TODO: fill this in if you want "recently returned" support:
508                    'returnDate' => false,
509                    'number' => count($data) + 1,
510                    'item_id' => $row['CPY_ID_NBR'],
511                    'barcode' => $row['BRCDE_NBR'],
512                ];
513                $data[] = $currentItem;
514            }
515            // If we found data, we can leave the foreach loop -- we don't need to
516            // try any more queries.
517            if (count($data) > 0) {
518                break;
519            }
520        }
521        return $data;
522    }
523
524    /**
525     * Get Purchase History
526     *
527     * This is responsible for retrieving the acquisitions history data for the
528     * specific record (usually recently received issues of a serial).
529     *
530     * @param string $id The record id to retrieve the info for
531     *
532     * @throws ILSException
533     * @return array     An array with the acquisitions data on success.
534     */
535    public function getPurchaseHistory($id)
536    {
537        $sql = "select REPLACE(REPLACE(CPY_STMT_TXT,'a',''),'Fondos: ','') as " .
538            'ENUMCHRON ' .
539            'from CPY_ID ' .
540            "WHERE CPY_ID.BIB_ITM_NBR = '$id" .
541            'order by CPY_ID.SHLF_LIST_KEY_NBR ASC, CPY_ID.CPY_ID_NBR ASC';
542        $data = [];
543        try {
544            $sqlStmt = $this->db->prepare($sql);
545            $sqlStmt->execute();
546            while ($row = $sqlStmt->fetch(PDO::FETCH_ASSOC)) {
547                $data[] = ['issue' => $row['ENUMCHRON']];
548            }
549        } catch (PDOException $e) {
550            $this->throwAsIlsException($e);
551        }
552        return $data;
553    }
554
555    /**
556     * Patron Login
557     *
558     * This is responsible for authenticating a patron against the catalog.
559     *
560     * @param string $barcode The patron username
561     * @param string $lname   The patron's password
562     *
563     * @throws ILSException
564     * @return mixed          Associative array of patron info on successful login,
565     * null on unsuccessful login.
566     */
567    public function patronLogin($barcode, $lname)
568    {
569        $sql = 'SELECT LOGIN , PASSWORD AS FIRST_NAME ' .
570               'FROM LV_USER ' .
571               "WHERE PASSWORD = '$lname' AND LOGIN = '$barcode'";
572
573        try {
574            $sqlStmt = $this->db->prepare($sql);
575            $sqlStmt->execute();
576            $row = $sqlStmt->fetch(PDO::FETCH_ASSOC);
577            if (isset($row['LOGIN']) && ($row['LOGIN'] != '')) {
578                return [
579                    'id' => $row['LOGIN'],
580                    'firstname' => $row['FIRST_NAME'],
581                    'lastname' => $lname,
582                    'cat_username' => $barcode,
583                    'cat_password' => $lname,
584                    // There's supposed to be a getPatronEmailAddress stored
585                    // procedure in Oracle, but I couldn't get it to work here;
586                    // might be worth investigating further if needed later.
587                    'email' => null,
588                    'major' => null,
589                    'college' => null];
590            } else {
591                return null;
592            }
593        } catch (PDOException $e) {
594            $this->throwAsIlsException($e);
595        }
596    }
597
598    /**
599     * Get Patron Transactions
600     *
601     * This is responsible for retrieving all transactions (i.e. checked out items)
602     * by a specific patron.
603     *
604     * @param array $patron The patron array from patronLogin
605     *
606     * @throws DateException
607     * @throws ILSException
608     * @return array        Array of the patron's transactions on success.
609     */
610    public function getMyTransactions($patron)
611    {
612        $transList = [];
613
614        $sql = "SELECT TO_CHAR(CIRT_ITM.CIRT_ITM_DUE_DTE,'DD/MM/YYYY') " .
615            'AS DUEDATE, CIRT_ITM.BIB_ITM_NBR AS BIB_ID ' .
616            'FROM LV_USER, CIRT_ITM ' .
617            'WHERE LV_USER.PRSN_NBR = CIRT_ITM.PRSN_NBR ' .
618            "AND LV_USER.LOGIN = '" . $patron['id'] . "'";
619        try {
620            $sqlStmt = $this->db->prepare($sql);
621            $sqlStmt->execute();
622            while ($row = $sqlStmt->fetch(PDO::FETCH_ASSOC)) {
623                $transList[] = ['duedate' => $row['DUEDATE'],
624                                     'id' => $row['BIB_ID']];
625            }
626        } catch (PDOException $e) {
627            $this->throwAsIlsException($e);
628        }
629        return $transList;
630    }
631
632    /**
633     * Get Patron Fines
634     *
635     * This is responsible for retrieving all fines by a specific patron.
636     *
637     * @param array $patron The patron array from patronLogin
638     *
639     * @throws DateException
640     * @throws ILSException
641     * @return mixed        Array of the patron's fines on success.
642     */
643    public function getMyFines($patron)
644    {
645        $fineList = [];
646
647        $sql = "SELECT UNIQUE TO_CHAR(CIRT_ITM.CIRT_ITM_CHRG_OUT_DTE,'DD/MM/YYYY') "
648            . 'AS ORIG_CHARGE_DATE, '
649            . "TO_CHAR(CIRT_ITM.CIRT_ITM_DUE_DTE,'DD/MM/YYYY')  AS DUE_DATE, "
650            . 'CIRT_ITM.BIB_ITM_NBR AS BIB_ID '
651            . 'FROM CIRT_ITM, LV_USER '
652            . 'WHERE CIRT_ITM.PRSN_NBR = LV_USER.PRSN_NBR '
653            . 'AND CIRT_ITM_DUE_DTE < SYSDATE '
654            . "AND  LV_USER.LOGIN='" . $patron['id'] . "'";
655        try {
656            $sqlStmt = $this->db->prepare($sql);
657            $sqlStmt->execute();
658            while ($row = $sqlStmt->fetch(PDO::FETCH_ASSOC)) {
659                $fineList[] = ['checkout' => $row['ORIG_CHARGE_DATE'],
660                                    'duedate' => $row['DUE_DATE'],
661                                    'id' => $row['BIB_ID']];
662            }
663            return $fineList;
664        } catch (PDOException $e) {
665            $this->throwAsIlsException($e);
666        }
667    }
668
669    /**
670     * Get Patron Holds
671     *
672     * This is responsible for retrieving all holds by a specific patron.
673     *
674     * @param array $patron The patron array from patronLogin
675     *
676     * @throws DateException
677     * @throws ILSException
678     * @return array        Array of the patron's holds on success.
679     */
680    public function getMyHolds($patron)
681    {
682        $holdList = [];
683
684        $sql = 'SELECT CIRTN_HLD.BIB_ITM_NBR AS BIB_ID, ' .
685            'CIRTN_HLD.CIRTN_HLD_LCTN_ORG_NBR AS PICKUP_LOCATION, ' .
686            'CIRTN_HLD.CIRTN_HLD_TYP_CDE AS  HOLD_RECALL_TYPE, ' .
687            "TO_CHAR(CIRTN_HLD.TME_HLD_END_DTE,'DD/MM/YYYY') AS EXPIRE_DATE, " .
688            "TO_CHAR(CIRTN_HLD.CIRTN_HLD_CRTE_DTE,'DD/MM/YYYY') AS " .
689            'CREATE_DATE FROM CIRTN_HLD, LV_USER ' .
690            'WHERE CIRTN_HLD.PRSN_NBR = LV_USER.PRSN_NBR ' .
691            "AND LV_USER.LOGIN = '" . $patron['id'] . "'";
692        try {
693            $sqlStmt = $this->db->prepare($sql);
694            $sqlStmt->execute();
695            while ($row = $sqlStmt->fetch(PDO::FETCH_ASSOC)) {
696                $holdList[] = ['type' => $row['HOLD_RECALL_TYPE'],
697                                    'id' => $row['BIB_ID'],
698                                    'location' => $row['PICKUP_LOCATION'],
699                                    'expire' => $row['EXPIRE_DATE'],
700                                    'create' => $row['CREATE_DATE']];
701            }
702        } catch (PDOException $e) {
703            $this->throwAsIlsException($e);
704        }
705        return $holdList;
706    }
707
708    /**
709     * Get Patron Profile
710     *
711     * This is responsible for retrieving the profile for a specific patron.
712     *
713     * @param array $patron The patron array
714     *
715     * @throws ILSException
716     * @return array        Array of the patron's profile data on success.
717     */
718    public function getMyProfile($patron)
719    {
720        $sql = 'SELECT DISTINCT  PRSN.PRSN_NBR AS UNO, (SELECT p1.PRSN_CMCTN_NBR ' .
721            'FROM PRSN_CMCTN p1 ' .
722            'WHERE p1.prsn_nbr = PRSN.prsn_nbr AND  PRSN_CMCTN_TYP_CDE = 7) tfno, ' .
723            '(SELECT p1.PRSN_CMCTN_NBR  FROM PRSN_CMCTN p1 WHERE p1.prsn_nbr = ' .
724            'PRSN.prsn_nbr AND  ' .
725            'PRSN_CMCTN_TYP_CDE = 1) email, ' .
726            'PRSN.PRSN_SRNME_SRT_FORM AS  LAST_NAME, PRSN.PRSN_1ST_NME_SRT_FORM ' .
727            'AS FIRST_NAME, ' .
728            "CONCAT(PSTL_ADR_ST_NME,CONCAT(' ',CONCAT(PSTL_ADR_ST_NBR,CONCAT(' ', " .
729            "CONCAT(PSTL_ADR_FLR_NBR,CONCAT(' ',PSTL_ADR_RM_NBR)))))) " .
730            'AS ADDRESS_LINE1, PRSN_PSTL_ADR.PSTL_ADR_CTY_NME ' .
731            'AS ADDRESS_LINE2, PRSN_PSTL_ADR.PSTL_ADR_PSTL_CDE AS ZIP_POSTAL ' .
732            'FROM PRSN, PRSN_CMCTN, PRSN_PSTL_ADR, LV_USER ' .
733            'WHERE   PRSN_CMCTN.PRSN_nbr = PRSN.PRSN_NBR (+) ' .
734            'AND PRSN.PRSN_NBR = PRSN_PSTL_ADR.PRSN_NBR (+) ' .
735            'AND LV_USER.PRSN_NBR = PRSN.PRSN_NBR ' .
736            "AND LV_USER.LOGIN = UPPER('" . $patron['id'] . "')";
737
738        try {
739            $sqlStmt = $this->db->prepare($sql);
740            $sqlStmt->execute();
741            $row = $sqlStmt->fetch(PDO::FETCH_ASSOC);
742            if ($row) {
743                $patron = ['firstname' => $row['FIRST_NAME'],
744                                'lastname' => $row['LAST_NAME'],
745                                'address1' => $row['ADDRESS_LINE1'],
746                                'address2' => $row['ADDRESS_LINE2'],
747                                'zip' => $row['ZIP_POSTAL'],
748                                'phone' => $row['TFNO'],
749                                'email' => $row['EMAIL'],
750                                'group' => $row['PATRON_GROUP_NAME']];
751                return $patron;
752            }
753        } catch (PDOException $e) {
754            $this->throwAsIlsException($e);
755        }
756        return null;
757    }
758
759    /**
760     * Get Hold Link
761     *
762     * The goal for this method is to return a URL to a "place hold" web page on
763     * the ILS OPAC. This is used for ILSs that do not support an API or method
764     * to place Holds.
765     *
766     * @param string $recordId The id of the bib record
767     * @param array  $details  Item details from getHoldings return array
768     *
769     * @return string          URL to ILS's OPAC's place hold screen.
770     *
771     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
772     */
773    public function getHoldLink($recordId, $details)
774    {
775        return $this->config['Catalog']['hold'] . $recordId;
776    }
777
778    /**
779     * Get New Items
780     *
781     * Retrieve the IDs of items recently added to the catalog.
782     *
783     * @param int $page    Page number of results to retrieve (counting starts at 1)
784     * @param int $limit   The size of each page of results to retrieve
785     * @param int $daysOld The maximum age of records to retrieve in days (max. 30)
786     * @param int $fundId  optional fund ID to use for limiting results (use a value
787     * returned by getFunds, or exclude for no limit); note that "fund" may be a
788     * misnomer - if funds are not an appropriate way to limit your new item
789     * results, you can return a different set of values from getFunds. The
790     * important thing is that this parameter supports an ID returned by getFunds,
791     * whatever that may mean.
792     *
793     * @throws ILSException
794     * @return array       Associative array with 'count' and 'results' keys
795     *
796     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
797     */
798    public function getNewItems($page, $limit, $daysOld, $fundId = null)
799    {
800        $items = [];
801
802        // Prevent unnecessary load on voyager
803        if ($daysOld > 30) {
804            $daysOld = 30;
805        }
806
807        $enddate = date('d-m-Y', strtotime('now'));
808        $startdate = date('d-m-Y', strtotime("-$daysOld day"));
809
810        $sql = 'select count(distinct BIB_ITM_NBR) as count ' .
811               'from CPY_ID ' .
812               "where CPY_ID.CRTN_DTE >= to_date('$startdate', 'dd-mm-yyyy') " .
813               "and CPY_ID.CRTN_DTE < to_date('$enddate', 'dd-mm-yyyy')";
814        try {
815            $sqlStmt = $this->db->prepare($sql);
816            $sqlStmt->execute();
817            $row = $sqlStmt->fetch(PDO::FETCH_ASSOC);
818            $items['count'] = $row['COUNT'];
819        } catch (PDOException $e) {
820            $this->throwAsIlsException($e);
821        }
822
823        $page = ($page) ? $page : 1;
824        $limit = ($limit) ? $limit : 20;
825        $startRow = (($page - 1) * $limit) + 1;
826        $endRow = ($page * $limit);
827        $sql = 'select * from ' .
828               '(select a.*, rownum rnum from ' .
829               '(select  CPY_ID.BIB_ITM_NBR  as BIB_ID, CPY_ID.CRTN_DTE ' .
830               'as CREATE_DATE ' .
831               'from CPY_ID ' .
832               "where CPY_ID.CRTN_DTE >= to_date('$startdate', 'dd-mm-yyyy') " .
833               "and CPY_ID.CRTN_DTE < to_date('$enddate', 'dd-mm-yyyy') " .
834               'group by CPY_ID.BIB_ITM_NBR, CPY_ID.CRTN_DTE ' .
835               'order by CPY_ID.CRTN_DTE desc) a ' .
836               "where rownum <= $endRow" .
837               "where rnum >= $startRow";
838        try {
839            $sqlStmt = $this->db->prepare($sql);
840            $sqlStmt->execute();
841            while ($row = $sqlStmt->fetch(PDO::FETCH_ASSOC)) {
842                $items['results'][]['id'] = $row['BIB_ID'];
843            }
844        } catch (PDOException $e) {
845            $this->throwAsIlsException($e);
846        }
847        return $items;
848    }
849
850    /**
851     * Get Funds
852     *
853     * Return a list of funds which may be used to limit the getNewItems list.
854     *
855     * @throws ILSException
856     * @return array An associative array with key = fund ID, value = fund name.
857     */
858    public function getFunds()
859    {
860        $list = [];
861
862        $sql = 'select distinct * from ' .
863               '(select initcap(lower(FUND.FUND_NME)) as name from FUND) ' .
864               'order by name';
865        try {
866            $sqlStmt = $this->db->prepare($sql);
867            $sqlStmt->execute();
868            while ($row = $sqlStmt->fetch(PDO::FETCH_ASSOC)) {
869                $list[] = $row['NAME'];
870            }
871        } catch (PDOException $e) {
872            $this->throwAsIlsException($e);
873        }
874
875        return $list;
876    }
877
878    /**
879     * Find Reserves
880     *
881     * Obtain information on course reserves.
882     *
883     * @param string $course ID from getCourses (empty string to match all)
884     * @param string $inst   ID from getInstructors (empty string to match all)
885     * @param string $dept   ID from getDepartments (empty string to match all)
886     *
887     * @throws ILSException
888     * @return array An array of associative arrays representing reserve items.
889     *
890     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
891     */
892    public function findReserves($course, $inst, $dept)
893    {
894        $recordList = [];
895
896        $dept = str_replace("'", '', $dept);
897        $dept = str_replace('"', '', $dept);
898        $dept = str_replace(':', '', $dept);
899        $dept = str_replace(',', '', $dept);
900        $dept = str_replace('.', '', $dept);
901        $dept = str_replace(';', '', $dept);
902        $dept = str_replace('*', '%', $dept);
903
904        $sql = 'select distinct(BIB_ITM_NBR) as BIB_ID ' .
905               'FROM CPY_ID, SHLF_LIST ' .
906               'WHERE CPY_ID.SHLF_LIST_KEY_NBR = SHLF_LIST.SHLF_LIST_KEY_NBR ' .
907               'AND UPPER(SUBSTR(SHLF_LIST.SHLF_LIST_STRNG_TEXT,3,20)) LIKE ' .
908               "UPPER('" . $dept . "%') " .
909               'AND ROWNUM <= 1000';
910
911        try {
912            $sqlStmt = $this->db->prepare($sql);
913            $sqlStmt->execute();
914            while ($row = $sqlStmt->fetch(PDO::FETCH_ASSOC)) {
915                $recordList[] = $row;
916            }
917        } catch (PDOException $e) {
918            $this->throwAsIlsException($e);
919        }
920
921        return $recordList;
922    }
923
924    /*
925    function findReserves($course, $inst, $dept)
926    {
927        $recordList = array();
928
929        $reserve_subset = "";
930
931        $reserve_subset = "(".$reserve_subset.")";
932        $sql = "select SHLF_LIST_SRT_FORM as DISPLAY_CALL_NO, CPY_ID.BIB_ITM_NBR " .
933            "as BIB_ID, NME_MAIN_ENTRY_STRNG_TXT as AUTHOR, TTL_HDG_MAIN_STRNG_TXT" .
934            " as TITLE, BIB_NTE_IPRNT_STRNG_TXT as PUBLISHER, ITM_DTE_1_DSC as " .
935            "PUBLISHER_DATE " .
936            "from CIRTN_HLD, S_CACHE_BIB_ITM_DSPLY, SHLF_LIST, CPY_ID " .
937            "where S_CACHE_BIB_ITM_DSPLY.BIB_ITM_NBR = CIRTN_HLD.BIB_ITM_NBR and " .
938            "CIRTN_HLD.CPY_ID_NBR = CPY_ID.CPY_ID_NBR and " .
939            "CPY_ID.SHLF_LIST_KEY_NBR = SHLF_LIST.SHLF_LIST_KEY_NBR";
940
941
942        try {
943            $sqlStmt = $this->db->prepare($sql);
944            $sqlStmt->execute();
945            while ($row = $sqlStmt->fetch(PDO::FETCH_ASSOC)) {
946                $recordList[] = $row;
947            }
948        } catch (PDOException $e) {
949            $this->throwAsIlsException($e);
950        }
951
952        return $recordList;
953    }
954    */
955
956    /**
957     * Get suppressed records.
958     *
959     * @throws ILSException
960     * @return array ID numbers of suppressed records in the system.
961     */
962    public function getSuppressedRecords()
963    {
964        $list = [];
965        $sql = 'SELECT BIB_AUT_ITM_NBR as BIB_ID ' .
966            'FROM CTLGG_TRSTN_ACTVT_LOG ' .
967            'WHERE STATS_TRSTN_TYP_CDE = 4 ' .
968            'AND trstn_log_tmest >= SYSDATE -30';
969        try {
970            $sqlStmt = $this->db->prepare($sql);
971            $sqlStmt->execute();
972            while ($row = $sqlStmt->fetch(PDO::FETCH_ASSOC)) {
973                $list[] = $row['BIB_ID'];
974            }
975        } catch (PDOException $e) {
976            $this->throwAsIlsException($e);
977        }
978
979        return $list;
980    }
981}