Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 86
0.00% covered (danger)
0.00%
0 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
TitleHolds
0.00% covered (danger)
0.00%
0 / 86
0.00% covered (danger)
0.00%
0 / 7
1406
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
 getHold
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
56
 getHoldings
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 checkOverrideMode
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
56
 driverHold
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
42
 generateHold
0.00% covered (danger)
0.00%
0 / 25
0.00% covered (danger)
0.00%
0 / 1
90
 getHoldDetails
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
12
1<?php
2
3/**
4 * Title Hold Logic Class
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_Logic
25 * @author   Demian Katz <demian.katz@villanova.edu>
26 * @author   Luke O'Sullivan <l.osullivan@swansea.ac.uk>
27 * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
28 * @link     https://vufind.org/wiki/development Wiki
29 */
30
31namespace VuFind\ILS\Logic;
32
33use VuFind\Exception\ILS as ILSException;
34use VuFind\ILS\Connection as ILSConnection;
35
36use function in_array;
37use function is_array;
38use function is_bool;
39
40/**
41 * Title Hold Logic Class
42 *
43 * @category VuFind
44 * @package  ILS_Logic
45 * @author   Demian Katz <demian.katz@villanova.edu>
46 * @author   Luke O'Sullivan <l.osullivan@swansea.ac.uk>
47 * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
48 * @link     https://vufind.org/wiki/development Wiki
49 */
50class TitleHolds
51{
52    /**
53     * ILS authenticator
54     *
55     * @var \VuFind\Auth\ILSAuthenticator
56     */
57    protected $ilsAuth;
58
59    /**
60     * Catalog connection object
61     *
62     * @var ILSConnection
63     */
64    protected $catalog;
65
66    /**
67     * HMAC generator
68     *
69     * @var \VuFind\Crypt\HMAC
70     */
71    protected $hmac;
72
73    /**
74     * VuFind configuration
75     *
76     * @var \Laminas\Config\Config
77     */
78    protected $config;
79
80    /**
81     * Holding locations to hide from display
82     *
83     * @var array
84     */
85    protected $hideHoldings = [];
86
87    /**
88     * Constructor
89     *
90     * @param \VuFind\Auth\ILSAuthenticator $ilsAuth ILS authenticator
91     * @param ILSConnection                 $ils     A catalog connection
92     * @param \VuFind\Crypt\HMAC            $hmac    HMAC generator
93     * @param \Laminas\Config\Config        $config  VuFind configuration
94     */
95    public function __construct(
96        \VuFind\Auth\ILSAuthenticator $ilsAuth,
97        ILSConnection $ils,
98        \VuFind\Crypt\HMAC $hmac,
99        \Laminas\Config\Config $config
100    ) {
101        $this->ilsAuth = $ilsAuth;
102        $this->hmac = $hmac;
103        $this->config = $config;
104
105        if (isset($this->config->Record->hide_holdings)) {
106            foreach ($this->config->Record->hide_holdings as $current) {
107                $this->hideHoldings[] = $current;
108            }
109        }
110
111        $this->catalog = $ils;
112    }
113
114    /**
115     * Public method for getting title level holds
116     *
117     * @param string $id A Bib ID
118     *
119     * @return string|bool URL to place hold, or false if hold option unavailable
120     *
121     * @todo Indicate login failure or ILS connection failure somehow?
122     */
123    public function getHold($id)
124    {
125        // Get Holdings Data
126        if ($this->catalog) {
127            $mode = $this->catalog->getTitleHoldsMode();
128            if ($mode == 'disabled') {
129                return false;
130            } elseif ($mode == 'driver') {
131                try {
132                    $patron = $this->ilsAuth->storedCatalogLogin();
133                    if (!$patron) {
134                        return false;
135                    }
136                    return $this->driverHold($id, $patron);
137                } catch (ILSException $e) {
138                    return false;
139                }
140            } else {
141                try {
142                    $patron = $this->ilsAuth->storedCatalogLogin();
143                } catch (ILSException $e) {
144                    $patron = false;
145                }
146                $mode = $this->checkOverrideMode($id, $mode);
147                return $this->generateHold($id, $mode, $patron);
148            }
149        }
150        return false;
151    }
152
153    /**
154     * Get holdings for a particular record.
155     *
156     * @param string $id ID to retrieve
157     *
158     * @return array
159     */
160    protected function getHoldings($id)
161    {
162        // Cache results in a static array since the same holdings may be requested
163        // multiple times during a run through the class:
164        static $holdings = [];
165
166        if (!isset($holdings[$id])) {
167            $holdings[$id] = $this->catalog->getHolding($id)['holdings'];
168        }
169        return $holdings[$id];
170    }
171
172    /**
173     * Support method for getHold to determine if we should override the configured
174     * holds mode.
175     *
176     * @param string $id   Record ID to check
177     * @param string $mode Current mode
178     *
179     * @return string
180     */
181    protected function checkOverrideMode($id, $mode)
182    {
183        if (
184            isset($this->config->Catalog->allow_holds_override)
185            && $this->config->Catalog->allow_holds_override
186        ) {
187            $holdings = $this->getHoldings($id);
188
189            // For title holds, the most important override feature to handle
190            // is to prevent displaying a link if all items are disabled. We
191            // may eventually want to address other scenarios as well.
192            $allDisabled = true;
193            foreach ($holdings as $holding) {
194                if (
195                    !isset($holding['holdOverride'])
196                    || 'disabled' != $holding['holdOverride']
197                ) {
198                    $allDisabled = false;
199                }
200            }
201            $mode = (true == $allDisabled) ? 'disabled' : $mode;
202        }
203        return $mode;
204    }
205
206    /**
207     * Protected method for driver defined title holds
208     *
209     * @param string $id     A Bib ID
210     * @param array  $patron An Array of patron data
211     *
212     * @return mixed A url on success, boolean false on failure
213     */
214    protected function driverHold($id, $patron)
215    {
216        // Get Hold Details
217        $checkHolds = $this->catalog->checkFunction(
218            'Holds',
219            compact('id', 'patron')
220        );
221
222        if (isset($checkHolds['HMACKeys'])) {
223            $data = ['id' => $id, 'level' => 'title'];
224            $result = $this->catalog->checkRequestIsValid($id, $data, $patron);
225            if (
226                (is_array($result) && $result['valid'])
227                || (is_bool($result) && $result)
228            ) {
229                return $this->getHoldDetails($data, $checkHolds['HMACKeys']);
230            }
231        }
232        return false;
233    }
234
235    /**
236     * Protected method for vufind (i.e. User) defined holds
237     *
238     * @param string $id     A Bib ID
239     * @param string $type   The holds mode to be applied from:
240     * (disabled, always, availability, driver)
241     * @param array  $patron Patron
242     *
243     * @return mixed A url on success, boolean false on failure
244     */
245    protected function generateHold($id, $type, $patron)
246    {
247        $any_available = false;
248        $addlink = false;
249
250        $data = [
251            'id' => $id,
252            'level' => 'title',
253        ];
254
255        // Are holds allows?
256        $checkHolds = $this->catalog->checkFunction(
257            'Holds',
258            compact('id', 'patron')
259        );
260
261        if ($checkHolds != false) {
262            if ($type == 'always') {
263                $addlink = true;
264            } elseif ($type == 'availability') {
265                $holdings = $this->getHoldings($id);
266                foreach ($holdings as $holding) {
267                    if (
268                        $holding['availability']->isAvailable()
269                        && !in_array($holding['location'], $this->hideHoldings)
270                    ) {
271                        $any_available = true;
272                    }
273                }
274                $addlink = !$any_available;
275            }
276
277            if ($addlink) {
278                if ($checkHolds['function'] == 'getHoldLink') {
279                    // Return opac link
280                    return $this->catalog->getHoldLink($id, $data);
281                } else {
282                    // Return non-opac link
283                    return $this->getHoldDetails($data, $checkHolds['HMACKeys']);
284                }
285            }
286        }
287        return false;
288    }
289
290    /**
291     * Get Hold Link
292     *
293     * Supplies the form details required to place a hold
294     *
295     * @param array $data     An array of item data
296     * @param array $HMACKeys An array of keys to hash
297     *
298     * @return array          Details for generating URL
299     */
300    protected function getHoldDetails($data, $HMACKeys)
301    {
302        // Generate HMAC
303        $HMACkey = $this->hmac->generate($HMACKeys, $data);
304
305        // Add Params
306        $queryString = [];
307        foreach ($data as $key => $param) {
308            $needle = in_array($key, $HMACKeys);
309            if ($needle) {
310                $queryString[] = $key . '=' . urlencode($param);
311            }
312        }
313
314        // Add HMAC
315        $queryString[] = 'hashKey=' . urlencode($HMACkey);
316        $queryString = implode('&', $queryString);
317
318        // Build Params
319        return [
320            'action' => 'Hold', 'record' => $data['id'], 'query' => $queryString,
321            'anchor' => '#tabnav',
322        ];
323    }
324}