Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 160
0.00% covered (danger)
0.00%
0 / 1
CRAP
0.00% covered (danger)
0.00%
0 / 1
HoldsTrait
0.00% covered (danger)
0.00%
0 / 160
0.00% covered (danger)
0.00%
0 / 1
1892
0.00% covered (danger)
0.00%
0 / 1
 holdAction
0.00% covered (danger)
0.00%
0 / 160
0.00% covered (danger)
0.00%
0 / 1
1892
1<?php
2
3/**
4 * Holds trait (for subclasses of AbstractRecord)
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
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 Site
28 */
29
30namespace VuFind\Controller;
31
32use function count;
33use function in_array;
34use function is_array;
35
36/**
37 * Holds trait (for subclasses of AbstractRecord)
38 *
39 * @category VuFind
40 * @package  Controller
41 * @author   Demian Katz <demian.katz@villanova.edu>
42 * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
43 * @link     https://vufind.org Main Site
44 */
45trait HoldsTrait
46{
47    /**
48     * Action for dealing with holds.
49     *
50     * @return mixed
51     */
52    public function holdAction()
53    {
54        $driver = $this->loadRecord();
55
56        // Stop now if the user does not have valid catalog credentials available:
57        if (!is_array($patron = $this->catalogLogin())) {
58            return $patron;
59        }
60
61        // If we're not supposed to be here, give up now!
62        $catalog = $this->getILS();
63        $checkHolds = $catalog->checkFunction(
64            'Holds',
65            [
66                'id' => $driver->getUniqueID(),
67                'patron' => $patron,
68            ]
69        );
70        if (!$checkHolds) {
71            return $this->redirectToRecord();
72        }
73
74        // Do we have valid information?
75        // Sets $this->logonURL and $this->gatheredDetails
76        $gatheredDetails = $this->holds()->validateRequest($checkHolds['HMACKeys']);
77        if (!$gatheredDetails) {
78            return $this->redirectToRecord();
79        }
80
81        // Block invalid requests:
82        $validRequest = $catalog->checkRequestIsValid(
83            $driver->getUniqueID(),
84            $gatheredDetails,
85            $patron
86        );
87        if ((is_array($validRequest) && !$validRequest['valid']) || !$validRequest) {
88            $this->flashMessenger()->addErrorMessage(
89                is_array($validRequest)
90                    ? $validRequest['status'] : 'hold_error_blocked'
91            );
92            return $this->redirectToRecord('#top');
93        }
94
95        // Send various values to the view so we can build the form:
96        $requestGroups = $catalog->checkCapability(
97            'getRequestGroups',
98            [$driver->getUniqueID(), $patron, $gatheredDetails]
99        ) ? $catalog->getRequestGroups(
100            $driver->getUniqueID(),
101            $patron,
102            $gatheredDetails
103        ) : [];
104        $extraHoldFields = isset($checkHolds['extraHoldFields'])
105            ? explode(':', $checkHolds['extraHoldFields']) : [];
106
107        $requestGroupNeeded = in_array('requestGroup', $extraHoldFields)
108            && !empty($requestGroups)
109            && (empty($gatheredDetails['level'])
110                || ($gatheredDetails['level'] != 'copy'
111                    || count($requestGroups) > 1));
112
113        $pickupDetails = $gatheredDetails;
114        if (
115            !$requestGroupNeeded && !empty($requestGroups)
116            && count($requestGroups) == 1
117        ) {
118            // Request group selection is not required, but we have a single request
119            // group, so make sure pickup locations match with the group
120            $pickupDetails['requestGroupId'] = $requestGroups[0]['id'];
121        }
122        $pickup = $catalog->getPickUpLocations($patron, $pickupDetails);
123
124        // Check that there are pick up locations to choose from if the field is
125        // required:
126        if (in_array('pickUpLocation', $extraHoldFields) && !$pickup) {
127            $this->flashMessenger()
128                ->addErrorMessage('No pickup locations available');
129            return $this->redirectToRecord('#top');
130        }
131
132        $proxiedUsers = [];
133        if (
134            in_array('proxiedUsers', $extraHoldFields)
135            && $catalog->checkCapability(
136                'getProxiedUsers',
137                [$driver->getUniqueID(), $patron, $gatheredDetails]
138            )
139        ) {
140            $proxiedUsers = $catalog->getProxiedUsers($patron);
141        }
142
143        // Process form submissions if necessary:
144        if (null !== $this->params()->fromPost('placeHold')) {
145            // If the form contained a pickup location, request group, start date or
146            // required by date, make sure they are valid:
147            $validGroup = $this->holds()->validateRequestGroupInput(
148                $gatheredDetails,
149                $extraHoldFields,
150                $requestGroups
151            );
152            $validPickup = $validGroup && $this->holds()->validatePickUpInput(
153                $gatheredDetails['pickUpLocation'] ?? null,
154                $extraHoldFields,
155                $pickup
156            );
157            $dateValidationResults = $this->holds()->validateDates(
158                $gatheredDetails['startDate'] ?? null,
159                $gatheredDetails['requiredBy'] ?? null,
160                $extraHoldFields
161            );
162            if (!$validGroup) {
163                $this->flashMessenger()
164                    ->addErrorMessage('hold_invalid_request_group');
165            }
166            if (!$validPickup) {
167                $this->flashMessenger()->addErrorMessage('hold_invalid_pickup');
168            }
169            foreach ($dateValidationResults['errors'] as $msg) {
170                $this->flashMessenger()->addErrorMessage($msg);
171            }
172            if ($validGroup && $validPickup && !$dateValidationResults['errors']) {
173                // If we made it this far, we're ready to place the hold;
174                // if successful, we will redirect and can stop here.
175
176                // Pass start date to the driver only if it's in the future:
177                if (
178                    !empty($gatheredDetails['startDate'])
179                    && $dateValidationResults['startDateTS'] < strtotime('+1 day')
180                ) {
181                    $gatheredDetails['startDate'] = '';
182                    $dateValidationResults['startDateTS'] = 0;
183                }
184
185                // Add patron data and converted dates to submitted data
186                $holdDetails = $gatheredDetails + [
187                    'patron' => $patron,
188                    'startDateTS' => $dateValidationResults['startDateTS'],
189                    'requiredByTS' => $dateValidationResults['requiredByTS'],
190                ];
191
192                // Attempt to place the hold:
193                $function = (string)$checkHolds['function'];
194                $results = $catalog->$function($holdDetails);
195
196                // Success: Go to Display Holds
197                if (isset($results['success']) && $results['success'] == true) {
198                    $msg = [
199                        'html' => true,
200                        'msg' => empty($gatheredDetails['proxiedUser'])
201                            ? 'hold_place_success_html'
202                            : 'proxy_hold_place_success_html',
203                        'tokens' => [
204                            '%%url%%' => $this->url()->fromRoute('holds-list'),
205                        ],
206                    ];
207                    $this->flashMessenger()->addMessage($msg, 'success');
208                    if (!empty($results['warningMessage'])) {
209                        $this->flashMessenger()
210                            ->addWarningMessage($results['warningMessage']);
211                    }
212                    $this->getViewRenderer()->plugin('session')->put('reset_account_status', true);
213                    return $this->redirectToRecord($this->inLightbox() ? '?layout=lightbox' : '');
214                } else {
215                    // Failure: use flash messenger to display messages, stay on
216                    // the current form.
217                    if (isset($results['status'])) {
218                        $this->flashMessenger()
219                            ->addMessage($results['status'], 'error');
220                    }
221                    if (isset($results['sysMessage'])) {
222                        $this->flashMessenger()
223                            ->addMessage($results['sysMessage'], 'error');
224                    }
225                }
226            }
227        }
228
229        // Set default start date to today:
230        $dateConverter = $this->serviceLocator->get(\VuFind\Date\Converter::class);
231        $defaultStartDate = $dateConverter->convertToDisplayDate('U', time());
232
233        // Find and format the default required date:
234        $defaultRequiredTS = $this->holds()->getDefaultRequiredDate(
235            $checkHolds,
236            $catalog,
237            $patron,
238            $gatheredDetails
239        );
240        $defaultRequiredDate = $defaultRequiredTS
241            ? $dateConverter->convertToDisplayDate(
242                'U',
243                $defaultRequiredTS
244            ) : '';
245        try {
246            $defaultPickup
247                = $catalog->getDefaultPickUpLocation($patron, $gatheredDetails);
248        } catch (\Exception $e) {
249            $defaultPickup = false;
250        }
251        try {
252            $defaultRequestGroup = empty($requestGroups)
253                ? false
254                : $catalog->getDefaultRequestGroup($patron, $gatheredDetails);
255        } catch (\Exception $e) {
256            $defaultRequestGroup = false;
257        }
258
259        $config = $this->getConfig();
260        $homeLibrary = ($config->Account->set_home_library ?? true)
261            ? $this->getUser()->getHomeLibrary() : '';
262        // helpText is only for backward compatibility:
263        $helpText = $helpTextHtml = $checkHolds['helpText'];
264
265        $view = $this->createViewModel(
266            compact(
267                'gatheredDetails',
268                'pickup',
269                'defaultPickup',
270                'homeLibrary',
271                'extraHoldFields',
272                'defaultStartDate',
273                'defaultRequiredDate',
274                'requestGroups',
275                'defaultRequestGroup',
276                'requestGroupNeeded',
277                'proxiedUsers',
278                'helpText',
279                'helpTextHtml'
280            )
281        );
282        $view->setTemplate('record/hold');
283        return $view;
284    }
285}