Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
17.95% covered (danger)
17.95%
14 / 78
38.46% covered (danger)
38.46%
5 / 13
CRAP
0.00% covered (danger)
0.00%
0 / 1
AbstractRequestBase
17.95% covered (danger)
17.95%
14 / 78
38.46% covered (danger)
38.46%
5 / 13
597.66
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 getSession
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 resetValidation
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 rememberValidId
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 validateIds
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 validateRequest
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
12
 validatePickUpInput
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 validatePickUpLocation
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
 validateRequestGroupInput
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
20
 validateRequestGroup
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
 getDefaultRequiredDate
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
72
 getDateFromArray
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
6
 getValidIds
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3/**
4 * VuFind Action Helper - Requests Support Methods
5 *
6 * PHP version 8
7 *
8 * Copyright (C) Villanova 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  Controller_Plugins
25 * @author   Demian Katz <demian.katz@villanova.edu>
26 * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
27 * @link     https://vufind.org Main Page
28 */
29
30namespace VuFind\Controller\Plugin;
31
32use Laminas\Mvc\Controller\Plugin\AbstractPlugin;
33use Laminas\Session\Container;
34use Laminas\Session\SessionManager;
35use VuFind\Crypt\HMAC;
36use VuFind\Date\Converter as DateConverter;
37use VuFind\ILS\Connection;
38
39use function count;
40use function get_class;
41use function in_array;
42
43/**
44 * Action helper base class to perform request-related actions
45 *
46 * @category VuFind
47 * @package  Controller_Plugins
48 * @author   Demian Katz <demian.katz@villanova.edu>
49 * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
50 * @link     https://vufind.org Main Page
51 */
52abstract class AbstractRequestBase extends AbstractPlugin
53{
54    /**
55     * Session data
56     *
57     * @var Container
58     */
59    protected $session;
60
61    /**
62     * Session manager
63     *
64     * @var SessionManager
65     */
66    protected $sessionManager;
67
68    /**
69     * HMAC generator
70     *
71     * @var HMAC
72     */
73    protected $hmac;
74
75    /**
76     * Date converter
77     *
78     * @var DateConverter
79     */
80    protected $dateConverter;
81
82    /**
83     * Constructor
84     *
85     * @param HMAC           $hmac           HMAC generator
86     * @param SessionManager $sessionManager Session manager
87     * @param DateConverter  $dateConverter  Date converter
88     */
89    public function __construct(
90        HMAC $hmac,
91        SessionManager $sessionManager,
92        DateConverter $dateConverter
93    ) {
94        $this->hmac = $hmac;
95        $this->sessionManager = $sessionManager;
96        $this->dateConverter = $dateConverter;
97    }
98
99    /**
100     * Grab the Container object for storing helper-specific session
101     * data.
102     *
103     * @return Container
104     */
105    protected function getSession()
106    {
107        if (!isset($this->session)) {
108            $this->session = new Container(
109                get_class($this) . '_Helper',
110                $this->sessionManager
111            );
112        }
113        return $this->session;
114    }
115
116    /**
117     * Reset the array of valid IDs in the session (used for form submission
118     * validation)
119     *
120     * @return void
121     */
122    public function resetValidation()
123    {
124        $this->getSession()->validIds = [];
125    }
126
127    /**
128     * Add an ID to the validation array.
129     *
130     * @param string $id ID to remember
131     *
132     * @return void
133     */
134    public function rememberValidId($id)
135    {
136        // The session container doesn't allow modification of entries (as of
137        // 2012, anyway), so we have to do this in a roundabout way.
138        // TODO: investigate whether this limitation has been lifted.
139        $existingArray = $this->getSession()->validIds;
140        $existingArray[] = $id;
141        $this->getSession()->validIds = $existingArray;
142    }
143
144    /**
145     * Validate supplied IDs against remembered IDs. Returns true if all supplied
146     * IDs are remembered, otherwise returns false.
147     *
148     * @param array $ids IDs to validate
149     *
150     * @return bool
151     */
152    public function validateIds($ids): bool
153    {
154        return !(bool)array_diff($ids, $this->getValidIds());
155    }
156
157    /**
158     * Method for validating contents of a request; returns an array of
159     * collected details if request is valid, otherwise returns false.
160     *
161     * @param array $linkData An array of keys to check
162     *
163     * @return bool|array
164     */
165    public function validateRequest($linkData)
166    {
167        $controller = $this->getController();
168        $params = $controller->params();
169
170        $keyValueArray = [];
171        foreach ($linkData as $details) {
172            // We expect most parameters to come via query, but some (mainly ID) may
173            // be in the route:
174            $keyValueArray[$details]
175                = $params->fromQuery($details, $params->fromRoute($details));
176        }
177        $hashKey = $this->hmac->generate($linkData, $keyValueArray);
178
179        if ($params->fromQuery('hashKey') != $hashKey) {
180            return false;
181        }
182
183        // Initialize gatheredDetails with any POST values we find; this will
184        // allow us to repopulate the form with user-entered values if there
185        // is an error. However, it is important that we load the POST data
186        // FIRST and then override it with GET values in order to ensure that
187        // the user doesn't bypass the hashkey verification by manipulating POST
188        // values.
189        $gatheredDetails = $params->fromPost('gatheredDetails', []);
190
191        // Make sure the bib ID is included, even if it's not loaded as part of
192        // the validation loop below.
193        $gatheredDetails['id'] = $params->fromRoute('id', $params->fromQuery('id'));
194
195        // Get Values Passed from holdings.php
196        $gatheredDetails = array_merge($gatheredDetails, $keyValueArray);
197
198        return $gatheredDetails;
199    }
200
201    /**
202     * Check if the user-provided pickup location is valid.
203     *
204     * @param string $pickup          User-specified pickup location
205     * @param array  $extraHoldFields Hold form fields enabled by
206     * configuration/driver
207     * @param array  $pickUpLibs      Pickup library list from driver
208     *
209     * @return bool
210     */
211    public function validatePickUpInput($pickup, $extraHoldFields, $pickUpLibs)
212    {
213        // Not having to care for pickUpLocation is equivalent to having a valid one.
214        if (!in_array('pickUpLocation', $extraHoldFields)) {
215            return true;
216        }
217
218        // Check the valid pickup locations for a match against user input:
219        return $this->validatePickUpLocation($pickup, $pickUpLibs);
220    }
221
222    /**
223     * Check if the provided pickup location is valid.
224     *
225     * @param string $location   Location to check
226     * @param array  $pickUpLibs Pickup locations list from driver
227     *
228     * @return bool
229     */
230    public function validatePickUpLocation($location, $pickUpLibs)
231    {
232        foreach ($pickUpLibs as $lib) {
233            if ($location == $lib['locationID']) {
234                return true;
235            }
236        }
237
238        // If we got this far, something is wrong!
239        return false;
240    }
241
242    /**
243     * Check if the user-provided request group is valid.
244     *
245     * @param array $gatheredDetails User hold parameters
246     * @param array $extraHoldFields Form fields enabled by configuration/driver
247     * @param array $requestGroups   Request group list from driver
248     *
249     * @return bool
250     */
251    public function validateRequestGroupInput(
252        $gatheredDetails,
253        $extraHoldFields,
254        $requestGroups
255    ) {
256        // Not having to care for requestGroup is equivalent to having a valid one.
257        if (!in_array('requestGroup', $extraHoldFields)) {
258            return true;
259        }
260        if (
261            !isset($gatheredDetails['level'])
262            || $gatheredDetails['level'] !== 'title'
263        ) {
264            return true;
265        }
266
267        // Check the valid request groups for a match against user input:
268        return $this->validateRequestGroup(
269            $gatheredDetails['requestGroupId'],
270            $requestGroups
271        );
272    }
273
274    /**
275     * Check if the provided request group is valid.
276     *
277     * @param string $requestGroupId Id of the request group to check
278     * @param array  $requestGroups  Request group list from driver
279     *
280     * @return bool
281     */
282    public function validateRequestGroup($requestGroupId, $requestGroups)
283    {
284        foreach ($requestGroups as $group) {
285            if ($requestGroupId == $group['id']) {
286                return true;
287            }
288        }
289
290        // If we got this far, something is wrong!
291        return false;
292    }
293
294    /**
295     * Getting a default required date based on hold settings.
296     *
297     * @param array      $checkHolds Hold settings returned by the ILS driver's
298     * checkFunction method.
299     * @param Connection $catalog    ILS connection (optional)
300     * @param array      $patron     Patron details (optional)
301     * @param array      $holdInfo   Hold details (optional)
302     *
303     * @return int A timestamp representing the default required date
304     */
305    public function getDefaultRequiredDate(
306        $checkHolds,
307        $catalog = null,
308        $patron = null,
309        $holdInfo = null
310    ) {
311        // Load config:
312        $dateArray = isset($checkHolds['defaultRequiredDate'])
313             ? explode(':', $checkHolds['defaultRequiredDate'])
314             : [0, 1, 0];
315
316        // Process special "driver" prefix and adjust default date
317        // settings accordingly:
318        if ($dateArray[0] == 'driver') {
319            $useDriver = true;
320            array_shift($dateArray);
321            if (count($dateArray) < 3) {
322                $dateArray = [0, 1, 0];
323            }
324        } else {
325            $useDriver = false;
326        }
327
328        // If the driver setting is active, try it out:
329        if ($useDriver && $catalog) {
330            $check = $catalog->checkCapability(
331                'getHoldDefaultRequiredDate',
332                [$patron, $holdInfo]
333            );
334            if ($check) {
335                $result = $catalog->getHoldDefaultRequiredDate($patron, $holdInfo);
336                if (!empty($result)) {
337                    return $result;
338                }
339            }
340        }
341
342        // If the driver setting is off or the driver didn't work, use the
343        // standard relative date mechanism:
344        return $this->getDateFromArray($dateArray);
345    }
346
347    /**
348     * Support method for getDefaultRequiredDate() -- generate a date based
349     * on a days/months/years offset array.
350     *
351     * @param array $dateArray 3-element array containing day/month/year offsets
352     *
353     * @return int A timestamp representing the default required date
354     */
355    protected function getDateFromArray($dateArray)
356    {
357        if (!isset($dateArray[2])) {
358            return 0;
359        }
360        [$d, $m, $y] = $dateArray;
361        return mktime(
362            0,
363            0,
364            0,
365            date('m') + $m,
366            date('d') + $d,
367            date('Y') + $y
368        );
369    }
370
371    /**
372     * Get remembered valid IDs
373     *
374     * @return array
375     */
376    protected function getValidIds(): array
377    {
378        return $this->getSession()->validIds ?? [];
379    }
380}