Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.95% covered (danger)
0.95%
3 / 316
5.26% covered (danger)
5.26%
1 / 19
CRAP
0.00% covered (danger)
0.00%
0 / 1
Evergreen
0.95% covered (danger)
0.95%
3 / 316
5.26% covered (danger)
5.26%
1 / 19
3214.34
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 init
11.76% covered (danger)
11.76%
2 / 17
0.00% covered (danger)
0.00%
0 / 1
9.18
 getStatus
0.00% covered (danger)
0.00%
0 / 30
0.00% covered (danger)
0.00%
0 / 1
42
 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 / 36
0.00% covered (danger)
0.00%
0 / 1
56
 getPurchaseHistory
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 patronLogin
0.00% covered (danger)
0.00%
0 / 24
0.00% covered (danger)
0.00%
0 / 1
30
 getMyTransactions
0.00% covered (danger)
0.00%
0 / 58
0.00% covered (danger)
0.00%
0 / 1
56
 getMyFines
0.00% covered (danger)
0.00%
0 / 35
0.00% covered (danger)
0.00%
0 / 1
12
 getMyHolds
0.00% covered (danger)
0.00%
0 / 37
0.00% covered (danger)
0.00%
0 / 1
12
 getMyProfile
0.00% covered (danger)
0.00%
0 / 28
0.00% covered (danger)
0.00%
0 / 1
30
 getNewItems
0.00% covered (danger)
0.00%
0 / 26
0.00% covered (danger)
0.00%
0 / 1
20
 getFunds
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getSuppressedRecords
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
12
 getDepartments
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getInstructors
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getCourses
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 findReserves
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 formatDate
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2
3/**
4 * Evergreen ILS Driver
5 *
6 * PHP version 8
7 *
8 * Copyright (C) Villanova University 2007.
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   Warren Layton, NRCan Library <warren.layton@gmail.com>
26 * @author   Galen Charlton, Equinox <gmcharlt@equinoxOLI.org>
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
31namespace VuFind\ILS\Driver;
32
33use PDO;
34use PDOException;
35use VuFind\Date\DateException;
36use VuFind\Exception\ILS as ILSException;
37
38use function count;
39
40/**
41 * VuFind Connector for Evergreen
42 *
43 * Written by Warren Layton at the NRCan (Natural Resources Canada)
44 * Library.
45 *
46 * @category VuFind
47 * @package  ILS_Drivers
48 * @author   Warren Layton, NRCan Library <warren.layton@gmail.com>
49 * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
50 * @link     https://vufind.org/wiki/development:plugins:ils_drivers Wiki
51 */
52class Evergreen extends AbstractBase implements \Laminas\Log\LoggerAwareInterface
53{
54    use \VuFind\Log\LoggerAwareTrait;
55
56    /**
57     * Database connection
58     *
59     * @var PDO
60     */
61    protected $db;
62
63    /**
64     * Database name
65     *
66     * @var string
67     */
68    protected $dbName;
69
70    /**
71     * Date converter object
72     *
73     * @var \VuFind\Date\Converter
74     */
75    protected $dateConverter;
76
77    /**
78     * Constructor
79     *
80     * @param \VuFind\Date\Converter $dateConverter Date converter
81     */
82    public function __construct(\VuFind\Date\Converter $dateConverter)
83    {
84        $this->dateConverter = $dateConverter;
85    }
86
87    /**
88     * Evergreen constants
89     */
90    public const EVG_ITEM_STATUS_IN_TRANSIT = '6';
91
92    /**
93     * Initialize the driver.
94     *
95     * Validate configuration and perform all resource-intensive tasks needed to
96     * make the driver active.
97     *
98     * @throws ILSException
99     * @throws PDOException
100     * @return void
101     */
102    public function init()
103    {
104        if (empty($this->config)) {
105            throw new ILSException('Configuration needs to be set.');
106        }
107
108        // Define Database Name
109        $this->dbName = $this->config['Catalog']['database'];
110
111        try {
112            $this->db = new PDO(
113                'pgsql:host='
114                . $this->config['Catalog']['hostname']
115                . ' user='
116                . $this->config['Catalog']['user']
117                . ' dbname='
118                . $this->config['Catalog']['database']
119                . ' password='
120                . $this->config['Catalog']['password']
121                . ' port='
122                . $this->config['Catalog']['port']
123            );
124        } catch (PDOException $e) {
125            throw $e;
126        }
127    }
128
129    /**
130     * Get Status
131     *
132     * This is responsible for retrieving the status information of a certain
133     * record.
134     *
135     * @param string $id The record id to retrieve the holdings for
136     *
137     * @throws ILSException
138     * @return mixed     On success, an associative array with the following keys:
139     * id, availability (boolean), status, location, reserve, callnumber.
140     */
141    public function getStatus($id)
142    {
143        $holding = [];
144
145        // Build SQL Statement
146        $sql = <<<HERE
147            SELECT ccs.name AS status, acn.label AS callnumber, aou.name AS location
148            FROM config.copy_status ccs
149                INNER JOIN asset.copy ac ON ac.status = ccs.id
150                INNER JOIN asset.call_number acn ON acn.id = ac.call_number
151                INNER JOIN actor.org_unit aou ON aou.id = ac.circ_lib
152            WHERE
153                acn.record = ? AND
154                NOT ac.deleted
155            HERE;
156
157        // Execute SQL
158        try {
159            $holding = [];
160            $sqlStmt = $this->db->prepare($sql);
161            $sqlStmt->bindParam(1, $id, PDO::PARAM_INT);
162            $sqlStmt->execute();
163        } catch (PDOException $e) {
164            $this->throwAsIlsException($e);
165        }
166
167        // Build Holdings Array
168        while ($row = $sqlStmt->fetch(PDO::FETCH_ASSOC)) {
169            switch ($row['status']) {
170                case 'Available':
171                    $available = true;
172                    $reserve = false;
173                    break;
174                case 'On holds shelf':
175                    $available = false;
176                    $reserve = true;
177                    break;
178                default:
179                    $available = false;
180                    $reserve = false;
181                    break;
182            }
183
184            $holding[] = [
185                'id' => $id,
186                'availability' => $available,
187                'status' => $row['status'],
188                'location' => $row['location'],
189                'reserve' => $reserve,
190                'callnumber' => $row['callnumber'],
191            ];
192        }
193
194        return $holding;
195    }
196
197    /**
198     * Get Statuses
199     *
200     * This is responsible for retrieving the status information for a
201     * collection of records.
202     *
203     * @param array $idList The array of record ids to retrieve the status for
204     *
205     * @throws ILSException
206     * @return array        An array of getStatus() return values on success.
207     */
208    public function getStatuses($idList)
209    {
210        $status = [];
211        foreach ($idList as $id) {
212            $status[] = $this->getStatus($id);
213        }
214        return $status;
215    }
216
217    /**
218     * Get Holding
219     *
220     * This is responsible for retrieving the holding information of a certain
221     * record.
222     *
223     * @param string $id      The record id to retrieve the holdings for
224     * @param array  $patron  Patron data
225     * @param array  $options Extra options (not currently used)
226     *
227     * @throws DateException
228     * @throws ILSException
229     * @return array         On success, an associative array with the following
230     * keys: id, availability (boolean), status, location, reserve, callnumber,
231     * duedate, number, barcode.
232     *
233     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
234     */
235    public function getHolding($id, array $patron = null, array $options = [])
236    {
237        $holding = [];
238
239        // Build SQL Statement
240        $sql = <<<HERE
241            SELECT ccs.name AS status, acn.label AS callnumber, aou.name AS location,
242                ac.copy_number, ac.barcode,
243                extract (year from circ.due_date) as due_year,
244                extract (month from circ.due_date) as due_month,
245                extract (day from circ.due_date) as due_day
246            FROM config.copy_status ccs
247                INNER JOIN asset.copy ac ON ac.status = ccs.id
248                INNER JOIN asset.call_number acn ON acn.id = ac.call_number
249                INNER JOIN actor.org_unit aou ON aou.id = ac.circ_lib
250                FULL JOIN action.circulation circ ON (
251                    ac.id = circ.target_copy AND circ.checkin_time IS NULL
252                )
253            WHERE
254                acn.record = ? AND
255                NOT ac.deleted
256            HERE;
257
258        // Execute SQL
259        try {
260            $sqlStmt = $this->db->prepare($sql);
261            $sqlStmt->bindParam(1, $id, PDO::PARAM_INT);
262            $sqlStmt->execute();
263        } catch (PDOException $e) {
264            $this->throwAsIlsException($e);
265        }
266
267        // Build Holdings Array
268        while ($row = $sqlStmt->fetch(PDO::FETCH_ASSOC)) {
269            switch ($row['status']) {
270                case 'Available':
271                    $available = true;
272                    $reserve = false;
273                    break;
274                case 'On holds shelf':
275                    // Instead of relying on status = 'On holds shelf',
276                    // I might want to see if:
277                    // action.hold_request.current_copy = asset.copy.id
278                    // and action.hold_request.capture_time is not null
279                    // and I think action.hold_request.fulfillment_time is null
280                    $available = false;
281                    $reserve = true;
282                    break;
283                default:
284                    $available = false;
285                    $reserve = false;
286                    break;
287            }
288
289            if ($row['due_year']) {
290                $due_date = $row['due_year'] . '-' . $row['due_month'] . '-' .
291                            $row['due_day'];
292            } else {
293                $due_date = '';
294            }
295            $holding[] = [
296                'id' => $id,
297                'availability' => $available,
298                'status' => $row['status'],
299                'location' => $row['location'],
300                'reserve' => $reserve,
301                'callnumber' => $row['callnumber'],
302                'duedate' => $due_date,
303                'number' => $row['copy_number'],
304                'barcode' => $row['barcode'],
305            ];
306        }
307
308        return $holding;
309    }
310
311    /**
312     * Get Purchase History
313     *
314     * This is responsible for retrieving the acquisitions history data for the
315     * specific record (usually recently received issues of a serial).
316     *
317     * @param string $id The record id to retrieve the info for
318     *
319     * @throws ILSException
320     * @return array     An array with the acquisitions data on success.
321     *
322     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
323     */
324    public function getPurchaseHistory($id)
325    {
326        // TODO
327        return [];
328    }
329
330    /**
331     * Patron Login
332     *
333     * This is responsible for authenticating a patron against the catalog.
334     *
335     * @param string $barcode The patron username OR barcode number
336     * @param string $passwd  The patron password
337     *
338     * @throws ILSException
339     * @return mixed          Associative array of patron info on successful login,
340     * null on unsuccessful login.
341     */
342    public function patronLogin($barcode, $passwd)
343    {
344        $sql = <<<HERE
345            SELECT usr.id, usr.first_given_name as firstName,
346                usr.family_name as lastName, usr.email, usrname
347            FROM actor.usr usr
348                INNER JOIN actor.card ON usr.card = card.id
349            WHERE card.active = true
350                AND actor.verify_passwd(usr.id, 'main',
351                                       MD5(actor.get_salt(usr.id, 'main') || MD5(?)))
352            HERE;
353        if (is_numeric($barcode)) {
354            // A barcode was supplied as ID
355            $sql .= 'AND card.barcode = ?';
356        } else {
357            // A username was supplied as ID
358            $sql .= 'AND usr.usrname = ?';
359        }
360
361        try {
362            $sqlStmt = $this->db->prepare($sql);
363            $sqlStmt->bindParam(1, $passwd, PDO::PARAM_STR);
364            $sqlStmt->bindParam(2, $barcode, PDO::PARAM_STR);
365            $sqlStmt->execute();
366            $row = $sqlStmt->fetch(PDO::FETCH_ASSOC);
367            if (isset($row['id']) && ($row['id'] != '')) {
368                $return = [];
369                $return['id'] = $row['id'];
370                $return['firstname'] = $row['firstname'];
371                $return['lastname'] = $row['lastname'];
372                $return['cat_username'] = $row['usrname'];
373                $return['cat_password'] = $passwd;
374                $return['email'] = $row['email'];
375                $return['major'] = null;    // Don't know which table this comes from
376                $return['college'] = null;  // Don't know which table this comes from
377                return $return;
378            } else {
379                return null;
380            }
381        } catch (PDOException $e) {
382            $this->throwAsIlsException($e);
383        }
384    }
385
386    /**
387     * Get Patron Transactions
388     *
389     * This is responsible for retrieving all transactions (i.e. checked out items)
390     * by a specific patron.
391     *
392     * @param array $patron The patron array from patronLogin
393     *
394     * @throws DateException
395     * @throws ILSException
396     * @return array        Array of the patron's transactions on success.
397     */
398    public function getMyTransactions($patron)
399    {
400        $transList = [];
401
402        $sql = 'select call_number.record as bib_id, ' .
403               'circulation.due_date as due_date, ' .
404               'circulation.target_copy as item_id, ' .
405               'circulation.renewal_remaining as renewal_remaining, ' .
406               'aou_circ.name as borrowing_location, ' .
407               'aou_own.name as owning_library, ' .
408               'copy.barcode as barcode ' .
409               "from $this->dbName.action.circulation " .
410               "join $this->dbName.asset.copy ON " .
411               ' (circulation.target_copy = copy.id) ' .
412               "join $this->dbName.asset.call_number ON " .
413               '  (copy.call_number = call_number.id) ' .
414               "join $this->dbName.actor.org_unit aou_circ ON " .
415               '  (circulation.circ_lib = aou_circ.id) ' .
416               "join $this->dbName.actor.org_unit aou_own ON " .
417               '  (call_number.owning_lib = aou_own.id) ' .
418               "where circulation.usr = '" . $patron['id'] . "' " .
419               'and circulation.checkin_time is null ' .
420               'and circulation.xact_finish is null';
421
422        try {
423            $sqlStmt = $this->db->prepare($sql);
424            $sqlStmt->execute();
425
426            while ($row = $sqlStmt->fetch(PDO::FETCH_ASSOC)) {
427                $due_date = $this->formatDate($row['due_date']);
428                $_due_time = new \DateTime($row['due_date']);
429                if ($_due_time->format('H:i:s') == '23:59:59') {
430                    $dueTime = ''; // don't display due time for non-hourly loans
431                } else {
432                    $dueTime = $this->dateConverter->convertToDisplayTime(
433                        'Y-m-d H:i',
434                        $row['due_date']
435                    );
436                }
437
438                $today = new \DateTime();
439                $now = time();
440                // since Evergreen normalizes the due time of non-hourly
441                // loans to be 23:59:59, we use a slightly flexible definition
442                // of "due in 24 hours"
443                $end_of_today = strtotime($today->format('Y-m-d 23:59:59'));
444                $dueTimeStamp = strtotime($row['due_date']);
445                $dueStatus = false;
446                if (is_numeric($dueTimeStamp)) {
447                    $_dueTimeLessDay = $dueTimeStamp - (1 * 24 * 60 * 60) - 1;
448                    if ($now > $dueTimeStamp) {
449                        $dueStatus = 'overdue';
450                    } elseif ($end_of_today > $_dueTimeLessDay) {
451                        $dueStatus = 'due';
452                    }
453                }
454
455                $transList[] = [
456                                    'duedate' => $due_date,
457                                    'dueTime' => $dueTime,
458                                    'id' => $row['bib_id'],
459                                    'barcode' => $row['barcode'],
460                                    'item_id' => $row['item_id'],
461                                    'renewLimit' => $row['renewal_remaining'],
462                                    'renewable' => $row['renewal_remaining'] > 1,
463                                    'institution_name' => $row['owning_library'],
464                                    'borrowingLocation' =>
465                                        $row['borrowing_location'],
466                                    'dueStatus' => $dueStatus,
467                               ];
468            }
469        } catch (PDOException $e) {
470            $this->throwAsIlsException($e);
471        }
472        return ['count' => count($transList), 'records' => $transList];
473    }
474
475    /**
476     * Get Patron Fines
477     *
478     * This is responsible for retrieving all fines by a specific patron.
479     *
480     * @param array $patron The patron array from patronLogin
481     *
482     * @throws DateException
483     * @throws ILSException
484     * @return mixed        Array of the patron's fines on success.
485     */
486    public function getMyFines($patron)
487    {
488        $fineList = [];
489
490        $sql = 'select billable_xact_summary.total_owed * 100 as total_owed, ' .
491               'billable_xact_summary.balance_owed * 100 as balance_owed, ' .
492               'billable_xact_summary.last_billing_type, ' .
493               'billable_xact_summary.last_billing_ts, ' .
494               'billable_circulations.create_time as checkout_time, ' .
495               'billable_circulations.due_date, ' .
496               'billable_circulations.target_copy, ' .
497               'call_number.record ' .
498               "from $this->dbName.money.billable_xact_summary " .
499               "LEFT JOIN $this->dbName.action.billable_circulations " .
500               'ON (billable_xact_summary.id = billable_circulations.id ' .
501               ' and billable_circulations.xact_finish is null) ' .
502               "LEFT JOIN $this->dbName.asset.copy ON " .
503               '  (billable_circulations.target_copy = copy.id) ' .
504               "LEFT JOIN $this->dbName.asset.call_number ON " .
505               '  (copy.call_number = call_number.id) ' .
506               "where billable_xact_summary.usr = '" . $patron['id'] . "' " .
507               'and billable_xact_summary.total_owed <> 0 ' .
508               'and billable_xact_summary.xact_finish is null';
509
510        try {
511            $sqlStmt = $this->db->prepare($sql);
512            $sqlStmt->execute();
513
514            while ($row = $sqlStmt->fetch(PDO::FETCH_ASSOC)) {
515                $fineList[] = [
516                    'amount' => $row['total_owed'],
517                    'fine' => $row['last_billing_type'],
518                    'balance' => $row['balance_owed'],
519                    'checkout' => $this->formatDate($row['checkout_time']),
520                    'createdate' => $this->formatDate($row['last_billing_ts']),
521                    'duedate' => $this->formatDate($row['due_date']),
522                    'id' => $row['record'],
523                ];
524            }
525            return $fineList;
526        } catch (PDOException $e) {
527            $this->throwAsIlsException($e);
528        }
529    }
530
531    /**
532     * Get Patron Holds
533     *
534     * This is responsible for retrieving all holds by a specific patron.
535     *
536     * @param array $patron The patron array from patronLogin
537     *
538     * @throws DateException
539     * @throws ILSException
540     * @return array        Array of the patron's holds on success.
541     */
542    public function getMyHolds($patron)
543    {
544        $holdList = [];
545
546        $sql = 'select ahr.hold_type, bib_record, ' .
547               'ahr.id as hold_id, ' .
548               'expire_time, request_time, shelf_time, capture_time, ' .
549               'shelf_time, shelf_expire_time, frozen, thaw_date, ' .
550               'org_unit.name as lib_name, acp.status as copy_status ' .
551               "from $this->dbName.action.hold_request ahr " .
552               "join $this->dbName.actor.org_unit on " .
553               '  (ahr.pickup_lib = org_unit.id) ' .
554               "join $this->dbName.reporter.hold_request_record rhrr on " .
555               '  (rhrr.id = ahr.id) ' .
556               "left join $this->dbName.asset.copy acp on " .
557               '  (acp.id = ahr.current_copy) ' .
558               "where ahr.usr = '" . $patron['id'] . "' " .
559               'and ahr.fulfillment_time is null ' .
560               'and ahr.cancel_time is null';
561
562        try {
563            $sqlStmt = $this->db->prepare($sql);
564            $sqlStmt->execute();
565            while ($row = $sqlStmt->fetch(PDO::FETCH_ASSOC)) {
566                $holdList[] = [
567                    'type' => $row['hold_type'],
568                    'id' => $row['bib_record'],
569                    'reqnum' => $row['hold_id'],
570                    'location' => $row['lib_name'],
571                    'expire' => $this->formatDate($row['expire_time']),
572                    'last_pickup_date' =>
573                        $this->formatDate($row['shelf_expire_time']),
574                    'available' => $row['shelf_time'],
575                    'frozen' => $row['frozen'],
576                    'frozenThrough' => $this->formatDate($row['thaw_date']),
577                    'create' => $this->formatDate($row['request_time']),
578                    'in_transit' =>
579                        $row['copy_status'] == self::EVG_ITEM_STATUS_IN_TRANSIT,
580                ];
581            }
582        } catch (PDOException $e) {
583            $this->throwAsIlsException($e);
584        }
585        return $holdList;
586    }
587
588    /**
589     * Get Patron Profile
590     *
591     * This is responsible for retrieving the profile for a specific patron.
592     *
593     * @param array $patron The patron array
594     *
595     * @throws ILSException
596     * @return array        Array of the patron's profile data on success.
597     */
598    public function getMyProfile($patron)
599    {
600        $sql = <<<HERE
601            SELECT usr.family_name, usr.first_given_name, usr.day_phone,
602                usr.evening_phone, usr.other_phone, aua.street1,
603                aua.street2, aua.post_code, pgt.name AS usrgroup,
604                aua.city, aua.country, usr.expire_date
605            FROM actor.usr
606                FULL JOIN actor.usr_address aua ON aua.id = usr.mailing_address
607                INNER JOIN permission.grp_tree pgt ON pgt.id = usr.profile
608            WHERE usr.active = true
609                 AND usr.id = ?
610            HERE;
611
612        try {
613            $sqlStmt = $this->db->prepare($sql);
614            $sqlStmt->bindParam(1, $patron['id'], PDO::PARAM_INT);
615            $sqlStmt->execute();
616            $row = $sqlStmt->fetch(PDO::FETCH_ASSOC);
617
618            if ($row['day_phone']) {
619                $phone = $row['day_phone'];
620            } elseif ($row['evening_phone']) {
621                $phone = $row['evening_phone'];
622            } else {
623                $phone = $row['other_phone'];
624            }
625
626            if ($row) {
627                $patron = [
628                    'firstname' => $row['first_given_name'],
629                    'lastname' => $row['family_name'],
630                    'address1' => $row['street1'],
631                    'address2' => $row['street2'],
632                    'city' => $row['city'],
633                    'zip' => $row['post_code'],
634                    'country' => $row['country'],
635                    'phone' => $phone,
636                    'group' => $row['usrgroup'],
637                    'expiration_date' => $this->formatDate($row['expire_date']),
638                ];
639                return $patron;
640            }
641        } catch (PDOException $e) {
642            $this->throwAsIlsException($e);
643        }
644        return null;
645    }
646
647    /**
648     * Only one of the following 2 function should be implemented.
649     * Placing a hold directly can be done with placeHold.
650     * Otherwise, getHoldLink will link to Evergreen's page to place
651     * a hold via the ILS.
652     */
653
654    /**
655     * Place Hold
656     *
657     * Attempts to place a hold or recall on a particular item and returns
658     * an array with result details or throws an exception on failure of support
659     * classes
660     *
661     * @param array $holdDetails An array of item and patron data
662     *
663     * @throws ILSException
664     * @return mixed An array of data on the request including
665     * whether or not it was successful and a system message (if available)
666     */
667    //public function placeHold($holdDetails)
668    //{
669    // Need to check asset.copy.status -> config.copy_status.holdable = true
670    // If it is holdable, place hold in action.hold_request:
671    // request_time to now, current_copy to asset.copy.id,
672    // usr to action.usr.id of requesting patron,
673    // phone_notify to phone number, email_notify to t/f
674    // set pickup_lib too?
675
676    /*
677    $sql = "";
678
679    try {
680        $sqlStmt = $this->db->prepare($sql);
681        $sqlStmt->execute();
682    } catch (PDOException $e) {
683        $this->throwAsIlsException($e);
684    }
685    */
686    //}
687
688    /**
689     * Get Hold Link
690     *
691     * The goal for this method is to return a URL to a "place hold" web page on
692     * the ILS OPAC. This is used for ILSs that do not support an API or method
693     * to place Holds.
694     *
695     * @param string $recordId The id of the bib record
696     * @param array  $details  Item details from getHoldings return array
697     *
698     * @return string          URL to ILS's OPAC's place hold screen.
699     */
700    //public function getHoldLink($recordId, $details)
701    //{
702    //}
703
704    /**
705     * Get New Items
706     *
707     * Retrieve the IDs of items recently added to the catalog.
708     *
709     * @param int $page    Page number of results to retrieve (counting starts at 1)
710     * @param int $limit   The size of each page of results to retrieve
711     * @param int $daysOld The maximum age of records to retrieve in days (max. 30)
712     * @param int $fundId  optional fund ID to use for limiting results (use a value
713     * returned by getFunds, or exclude for no limit); note that "fund" may be a
714     * misnomer - if funds are not an appropriate way to limit your new item
715     * results, you can return a different set of values from getFunds. The
716     * important thing is that this parameter supports an ID returned by getFunds,
717     * whatever that may mean.
718     *
719     * @throws ILSException
720     * @return array       Associative array with 'count' and 'results' keys
721     *
722     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
723     */
724    public function getNewItems($page, $limit, $daysOld, $fundId = null)
725    {
726        $items = [];
727
728        $enddate = date('Y-m-d', strtotime('now'));
729        $startdate = date('Y-m-d', strtotime("-$daysOld day"));
730
731        $sql = 'select count(distinct copy.id) as count ' .
732               'from asset.copy ' .
733               "where copy.create_date >= '$startdate" .
734               'and copy.status = 0 ' .
735               "and copy.create_date < '$enddate' LIMIT 50";
736
737        try {
738            $sqlStmt = $this->db->prepare($sql);
739            $sqlStmt->execute();
740            $row = $sqlStmt->fetch(PDO::FETCH_ASSOC);
741            $items['count'] = $row['count'];
742        } catch (PDOException $e) {
743            $this->throwAsIlsException($e);
744        }
745
746        // TODO: implement paging support
747        //$page = ($page) ? $page : 1;
748        //$limit = ($limit) ? $limit : 20;
749        //$startRow = (($page-1)*$limit)+1;
750        //$endRow = ($page*$limit);
751
752        $sql = 'select copy.id, call_number.record from asset.copy ' .
753               'join asset.call_number on (call_number.id = copy.call_number) ' .
754               "where copy.create_date >= '$startdate" .
755               'and copy.status = 0 ' .
756               "and copy.create_date < '$enddate' LIMIT 50";
757
758        try {
759            $sqlStmt = $this->db->prepare($sql);
760            $sqlStmt->execute();
761            while ($row = $sqlStmt->fetch(PDO::FETCH_ASSOC)) {
762                $items['results'][]['id'] = $row['record'];
763            }
764        } catch (PDOException $e) {
765            $this->throwAsIlsException($e);
766        }
767        return $items;
768    }
769
770    /**
771     * Get Funds
772     *
773     * Return a list of funds which may be used to limit the getNewItems list.
774     *
775     * @throws ILSException
776     * @return array An associative array with key = fund ID, value = fund name.
777     */
778    public function getFunds()
779    {
780        $list = [];
781
782        /* TODO:
783        $sql = "";
784
785        try {
786            $sqlStmt = $this->db->prepare($sql);
787            $sqlStmt->execute();
788            while ($row = $sqlStmt->fetch(PDO::FETCH_ASSOC)) {
789                $list[] = $row['name'];
790            }
791        } catch (PDOException $e) {
792            $this->throwAsIlsException($e);
793        }
794        */
795
796        return $list;
797    }
798
799    /**
800     * Get suppressed records.
801     *
802     * @throws ILSException
803     * @return array ID numbers of suppressed records in the system.
804     */
805    public function getSuppressedRecords()
806    {
807        $list = [];
808
809        $sql = 'select copy.id as id ' .
810               "from $this->dbName.asset " .
811               'where copy.opac_visible = false';
812
813        try {
814            $sqlStmt = $this->db->prepare($sql);
815            $sqlStmt->execute();
816            while ($row = $sqlStmt->fetch(PDO::FETCH_ASSOC)) {
817                $list[] = $row['id'];
818            }
819        } catch (PDOException $e) {
820            $this->throwAsIlsException($e);
821        }
822
823        return $list;
824    }
825
826    // *** The functions below are not (yet) applicable to Evergreen ***
827
828    /**
829     * Get Departments
830     *
831     * Obtain a list of departments for use in limiting the reserves list.
832     *
833     * @throws ILSException
834     * @return array An associative array with key = dept. ID, value = dept. name.
835     */
836    public function getDepartments()
837    {
838        // TODO
839        return [];
840    }
841
842    /**
843     * Get Instructors
844     *
845     * Obtain a list of instructors for use in limiting the reserves list.
846     *
847     * @throws ILSException
848     * @return array An associative array with key = ID, value = name.
849     */
850    public function getInstructors()
851    {
852        // TODO
853        return [];
854    }
855
856    /**
857     * Get Courses
858     *
859     * Obtain a list of courses for use in limiting the reserves list.
860     *
861     * @throws ILSException
862     * @return array An associative array with key = ID, value = name.
863     */
864    public function getCourses()
865    {
866        // TODO
867        return [];
868    }
869
870    /**
871     * Find Reserves
872     *
873     * Obtain information on course reserves.
874     *
875     * @param string $course ID from getCourses (empty string to match all)
876     * @param string $inst   ID from getInstructors (empty string to match all)
877     * @param string $dept   ID from getDepartments (empty string to match all)
878     *
879     * @throws ILSException
880     * @return array An array of associative arrays representing reserve items.
881     *
882     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
883     */
884    public function findReserves($course, $inst, $dept)
885    {
886        // TODO
887        return [];
888    }
889
890    /**
891     * Format date
892     *
893     * This formats a date coming from Evergreen for display
894     *
895     * @param string $date The date string to format; may be null
896     *
897     * @throws ILSException
898     * @return string The formatted date
899     */
900    protected function formatDate($date)
901    {
902        if (!$date) {
903            return '';
904        }
905        return $this->dateConverter->convertToDisplayDate('Y-m-d', $date);
906    }
907}