Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 136 |
|
0.00% |
0 / 4 |
CRAP | |
0.00% |
0 / 1 |
Holds | |
0.00% |
0 / 136 |
|
0.00% |
0 / 4 |
1892 | |
0.00% |
0 / 1 |
addCancelDetails | |
0.00% |
0 / 16 |
|
0.00% |
0 / 1 |
42 | |||
cancelHolds | |
0.00% |
0 / 64 |
|
0.00% |
0 / 1 |
182 | |||
validateDates | |
0.00% |
0 / 43 |
|
0.00% |
0 / 1 |
380 | |||
validateFrozenThrough | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
30 |
1 | <?php |
2 | |
3 | /** |
4 | * VuFind Action Helper - Holds Support Methods |
5 | * |
6 | * PHP version 8 |
7 | * |
8 | * Copyright (C) Villanova University 2010. |
9 | * Copyright (C) The National Library of Finland 2021. |
10 | * |
11 | * This program is free software; you can redistribute it and/or modify |
12 | * it under the terms of the GNU General Public License version 2, |
13 | * as published by the Free Software Foundation. |
14 | * |
15 | * This program is distributed in the hope that it will be useful, |
16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
18 | * GNU General Public License for more details. |
19 | * |
20 | * You should have received a copy of the GNU General Public License |
21 | * along with this program; if not, write to the Free Software |
22 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
23 | * |
24 | * @category VuFind |
25 | * @package Controller_Plugins |
26 | * @author Demian Katz <demian.katz@villanova.edu> |
27 | * @author Ere Maijala <ere.maijala@helsinki.fi> |
28 | * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License |
29 | * @link https://vufind.org Main Page |
30 | */ |
31 | |
32 | namespace VuFind\Controller\Plugin; |
33 | |
34 | use VuFind\Date\DateException; |
35 | |
36 | use function in_array; |
37 | |
38 | /** |
39 | * Action helper to perform holds-related actions |
40 | * |
41 | * @category VuFind |
42 | * @package Controller_Plugins |
43 | * @author Demian Katz <demian.katz@villanova.edu> |
44 | * @author Ere Maijala <ere.maijala@helsinki.fi> |
45 | * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License |
46 | * @link https://vufind.org Main Page |
47 | */ |
48 | class Holds extends AbstractRequestBase |
49 | { |
50 | /** |
51 | * Update ILS details with cancellation-specific information, if appropriate. |
52 | * |
53 | * @param \VuFind\ILS\Connection $catalog ILS connection object |
54 | * @param array $ilsDetails Hold details from ILS driver's |
55 | * getMyHolds() method |
56 | * @param array $cancelStatus Cancel settings from ILS driver's |
57 | * checkFunction() method |
58 | * @param array $patron ILS patron |
59 | * |
60 | * @return array $ilsDetails with cancellation info added |
61 | */ |
62 | public function addCancelDetails( |
63 | $catalog, |
64 | $ilsDetails, |
65 | $cancelStatus, |
66 | $patron = [] |
67 | ) { |
68 | // Generate Form Details for cancelling Holds if Cancelling Holds |
69 | // is enabled |
70 | if ($cancelStatus) { |
71 | if ($cancelStatus['function'] == 'getCancelHoldLink') { |
72 | // Build OPAC URL |
73 | $ilsDetails['cancel_link'] |
74 | = $catalog->getCancelHoldLink($ilsDetails, $patron); |
75 | } elseif (isset($ilsDetails['cancel_details'])) { |
76 | // The ILS driver provided cancel details up front. If the |
77 | // details are an empty string (flagging lack of support), we |
78 | // should unset it to prevent confusion; otherwise, we'll leave it |
79 | // as-is. |
80 | if ('' === $ilsDetails['cancel_details']) { |
81 | unset($ilsDetails['cancel_details']); |
82 | } else { |
83 | $this->rememberValidId($ilsDetails['cancel_details']); |
84 | } |
85 | } else { |
86 | // Default case: ILS supports cancel but we need to look up |
87 | // details: |
88 | $cancelDetails |
89 | = $catalog->getCancelHoldDetails($ilsDetails, $patron); |
90 | if ($cancelDetails !== '') { |
91 | $ilsDetails['cancel_details'] = $cancelDetails; |
92 | $this->rememberValidId($ilsDetails['cancel_details']); |
93 | } |
94 | } |
95 | } else { |
96 | // Cancelling holds disabled? Make sure no details get passed back: |
97 | unset($ilsDetails['cancel_link']); |
98 | unset($ilsDetails['cancel_details']); |
99 | } |
100 | |
101 | return $ilsDetails; |
102 | } |
103 | |
104 | /** |
105 | * Process cancellation requests. |
106 | * |
107 | * @param \VuFind\ILS\Connection $catalog ILS connection object |
108 | * @param array $patron Current logged in patron |
109 | * |
110 | * @return array The result of the cancellation, an |
111 | * associative array keyed by item ID (empty if no cancellations performed) |
112 | */ |
113 | public function cancelHolds($catalog, $patron) |
114 | { |
115 | // Retrieve the flashMessenger helper: |
116 | $flashMsg = $this->getController()->flashMessenger(); |
117 | $params = $this->getController()->params(); |
118 | |
119 | // Pick IDs to cancel based on which button was pressed: |
120 | $all = $params->fromPost('cancelAll'); |
121 | $selected = $params->fromPost('cancelSelected'); |
122 | if (!empty($all)) { |
123 | $details = $params->fromPost('cancelAllIDS'); |
124 | } elseif (!empty($selected)) { |
125 | // Include cancelSelectedIDS for backwards-compatibility: |
126 | $details = $params->fromPost('selectedIDS') |
127 | ?? $params->fromPost('cancelSelectedIDS'); |
128 | } else { |
129 | // No button pushed -- no action needed |
130 | return []; |
131 | } |
132 | |
133 | if (!empty($details)) { |
134 | // Confirm? |
135 | if ($params->fromPost('confirm') === '0') { |
136 | if ($params->fromPost('cancelAll') !== null) { |
137 | return $this->getController()->confirm( |
138 | 'hold_cancel_all', |
139 | $this->getController()->url()->fromRoute('holds-list'), |
140 | $this->getController()->url()->fromRoute('holds-list'), |
141 | 'confirm_hold_cancel_all_text', |
142 | [ |
143 | 'cancelAll' => 1, |
144 | 'cancelAllIDS' => $params->fromPost('cancelAllIDS'), |
145 | ] |
146 | ); |
147 | } else { |
148 | return $this->getController()->confirm( |
149 | 'hold_cancel_selected', |
150 | $this->getController()->url()->fromRoute('holds-list'), |
151 | $this->getController()->url()->fromRoute('holds-list'), |
152 | 'confirm_hold_cancel_selected_text', |
153 | [ |
154 | 'cancelSelected' => 1, |
155 | 'cancelSelectedIDS' => |
156 | $params->fromPost('cancelSelectedIDS'), |
157 | ] |
158 | ); |
159 | } |
160 | } |
161 | |
162 | foreach ($details as $info) { |
163 | // If the user input contains a value not found in the session |
164 | // legal list, something has been tampered with -- abort the process. |
165 | if (!in_array($info, $this->getSession()->validIds)) { |
166 | $flashMsg->addErrorMessage('error_inconsistent_parameters'); |
167 | return []; |
168 | } |
169 | } |
170 | |
171 | // Add Patron Data to Submitted Data |
172 | $cancelResults = $catalog->cancelHolds( |
173 | ['details' => $details, 'patron' => $patron] |
174 | ); |
175 | if ($cancelResults == false) { |
176 | $flashMsg->addMessage('hold_cancel_fail', 'error'); |
177 | } else { |
178 | $failed = 0; |
179 | foreach ($cancelResults['items'] ?? [] as $item) { |
180 | if (!$item['success']) { |
181 | ++$failed; |
182 | } |
183 | } |
184 | if ($failed) { |
185 | $msg = $this->getController() |
186 | ->translate( |
187 | 'hold_cancel_fail_items', |
188 | ['%%count%%' => $failed] |
189 | ); |
190 | $flashMsg->addErrorMessage($msg); |
191 | } |
192 | if ($cancelResults['count'] > 0) { |
193 | $msg = $this->getController() |
194 | ->translate( |
195 | 'hold_cancel_success_items', |
196 | ['%%count%%' => $cancelResults['count']] |
197 | ); |
198 | $flashMsg->addSuccessMessage($msg); |
199 | } |
200 | return $cancelResults; |
201 | } |
202 | } else { |
203 | $flashMsg->addMessage('hold_empty_selection', 'error'); |
204 | } |
205 | return []; |
206 | } |
207 | |
208 | /** |
209 | * Check if the user-provided dates are valid. |
210 | * |
211 | * Returns validated dates and/or an array of validation errors if there are |
212 | * problems. |
213 | * |
214 | * @param string $startDate User-specified start date |
215 | * @param string $requiredBy User-specified required-by date |
216 | * @param array $enabledFormFields Hold form fields enabled by |
217 | * configuration/driver |
218 | * |
219 | * @return array |
220 | */ |
221 | public function validateDates( |
222 | ?string $startDate, |
223 | ?string $requiredBy, |
224 | array $enabledFormFields |
225 | ): array { |
226 | $result = [ |
227 | 'startDateTS' => null, |
228 | 'requiredByTS' => null, |
229 | 'errors' => [], |
230 | ]; |
231 | if ( |
232 | !in_array('startDate', $enabledFormFields) |
233 | && !in_array('requiredByDate', $enabledFormFields) |
234 | && !in_array('requiredByDateOptional', $enabledFormFields) |
235 | ) { |
236 | return $result; |
237 | } |
238 | |
239 | if (in_array('startDate', $enabledFormFields)) { |
240 | try { |
241 | $result['startDateTS'] = $startDate |
242 | ? (int)$this->dateConverter->convertFromDisplayDate( |
243 | 'U', |
244 | $startDate |
245 | ) : 0; |
246 | if ($result['startDateTS'] < strtotime('today')) { |
247 | $result['errors'][] = 'hold_start_date_invalid'; |
248 | } |
249 | } catch (DateException $e) { |
250 | $result['errors'][] = 'hold_start_date_invalid'; |
251 | } |
252 | } |
253 | |
254 | if ( |
255 | in_array('requiredByDate', $enabledFormFields) |
256 | || in_array('requiredByDateOptional', $enabledFormFields) |
257 | ) { |
258 | $optional = in_array('requiredByDateOptional', $enabledFormFields); |
259 | try { |
260 | if ($requiredBy) { |
261 | $requiredByDateTime = \DateTime::createFromFormat( |
262 | 'U', |
263 | $this->dateConverter |
264 | ->convertFromDisplayDate('U', $requiredBy) |
265 | ); |
266 | $result['requiredByTS'] = $requiredByDateTime |
267 | ->setTime(23, 59, 59) |
268 | ->getTimestamp(); |
269 | } else { |
270 | $result['requiredByTS'] = 0; |
271 | } |
272 | if ( |
273 | (!$optional || $result['requiredByTS']) |
274 | && $result['requiredByTS'] < strtotime('today') |
275 | ) { |
276 | $result['errors'][] = 'hold_required_by_date_invalid'; |
277 | } |
278 | } catch (DateException $e) { |
279 | $result['errors'][] = 'hold_required_by_date_invalid'; |
280 | } |
281 | } |
282 | |
283 | if ( |
284 | !$result['errors'] |
285 | && in_array('startDate', $enabledFormFields) |
286 | && !empty($result['requiredByTS']) |
287 | && $result['startDateTS'] > $result['requiredByTS'] |
288 | ) { |
289 | $result['errors'][] = 'hold_required_by_date_before_start_date'; |
290 | } |
291 | |
292 | return $result; |
293 | } |
294 | |
295 | /** |
296 | * Check if the user-provided "frozen through" date is valid. |
297 | * |
298 | * Returns validated date and/or an array of validation errors if there are |
299 | * problems. |
300 | * |
301 | * @param string $frozenThrough User-specified "frozen through" date |
302 | * @param array $extraHoldFields Hold form fields enabled by |
303 | * configuration/driver |
304 | * |
305 | * @return array |
306 | */ |
307 | public function validateFrozenThrough( |
308 | ?string $frozenThrough, |
309 | array $extraHoldFields |
310 | ): array { |
311 | $result = [ |
312 | 'frozenThroughTS' => null, |
313 | 'errors' => [], |
314 | ]; |
315 | if (!in_array('frozenThrough', $extraHoldFields) || empty($frozenThrough)) { |
316 | return $result; |
317 | } |
318 | |
319 | try { |
320 | $result['frozenThroughTS'] |
321 | = $this->dateConverter->convertFromDisplayDate('U', $frozenThrough); |
322 | if ($result['frozenThroughTS'] < time()) { |
323 | $result['errors'][] = 'hold_frozen_through_date_invalid'; |
324 | } |
325 | } catch (DateException $e) { |
326 | $result['errors'][] = 'hold_frozen_through_date_invalid'; |
327 | } |
328 | |
329 | return $result; |
330 | } |
331 | } |