Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
3.64% |
42 / 1154 |
|
1.28% |
1 / 78 |
CRAP | |
0.00% |
0 / 1 |
Demo | |
3.64% |
42 / 1154 |
|
1.28% |
1 / 78 |
80826.62 | |
0.00% |
0 / 1 |
__construct | |
83.33% |
5 / 6 |
|
0.00% |
0 / 1 |
2.02 | |||
init | |
56.25% |
9 / 16 |
|
0.00% |
0 / 1 |
11.10 | |||
isFailing | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
getFakeLoc | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
6 | |||
getFakeServices | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
42 | |||
getFakeStatus | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
42 | |||
getFakeCallNum | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
getFakeCallNumPrefix | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
6 | |||
getRandomBibId | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
getRandomBibIdAndTitle | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
6 | |||
getRecordSource | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
checkIntermittentFailure | |
50.00% |
1 / 2 |
|
0.00% |
0 / 1 |
2.50 | |||
checkRenewBlock | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getRequestBlocks | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
6 | |||
getAccountBlocks | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
6 | |||
getRandomHolding | |
0.00% |
0 / 53 |
|
0.00% |
0 / 1 |
210 | |||
getRandomItemIdentifier | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
20 | |||
createRequestList | |
0.00% |
0 / 65 |
|
0.00% |
0 / 1 |
306 | |||
getStatus | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getSuppressedRecords | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getSession | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
20 | |||
getSimulatedStatus | |
0.00% |
0 / 23 |
|
0.00% |
0 / 1 |
56 | |||
setStatus | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
30 | |||
getStatuses | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
6 | |||
getHolding | |
0.00% |
0 / 63 |
|
0.00% |
0 / 1 |
156 | |||
getPurchaseHistory | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
6 | |||
patronLogin | |
71.43% |
15 / 21 |
|
0.00% |
0 / 1 |
5.58 | |||
getMyProfile | |
0.00% |
0 / 19 |
|
0.00% |
0 / 1 |
2 | |||
getMyFines | |
0.00% |
0 / 35 |
|
0.00% |
0 / 1 |
90 | |||
getMyHolds | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
6 | |||
getMyStorageRetrievalRequests | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
6 | |||
getMyILLRequests | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
6 | |||
getTransactionList | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
calculateDueStatus | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
12 | |||
getRandomTransactionList | |
0.00% |
0 / 44 |
|
0.00% |
0 / 1 |
72 | |||
getMyTransactions | |
0.00% |
0 / 25 |
|
0.00% |
0 / 1 |
90 | |||
getHistoricTransactionList | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
getRandomHistoricTransactionList | |
0.00% |
0 / 34 |
|
0.00% |
0 / 1 |
20 | |||
getMyTransactionHistory | |
0.00% |
0 / 38 |
|
0.00% |
0 / 1 |
132 | |||
purgeTransactionHistory | |
0.00% |
0 / 16 |
|
0.00% |
0 / 1 |
6 | |||
getPickUpLocations | |
0.00% |
0 / 16 |
|
0.00% |
0 / 1 |
12 | |||
getHoldDefaultRequiredDate | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
getDefaultPickUpLocation | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
getDefaultRequestGroup | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
6 | |||
getRequestGroups | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
2 | |||
getFunds | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
getDepartments | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
getInstructors | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
getCourses | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
getRandomBibIds | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
20 | |||
getNewItems | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
6 | |||
getCourseId | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
6 | |||
getDepartmentId | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
6 | |||
getInstructorId | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
6 | |||
findReserves | |
0.00% |
0 / 16 |
|
0.00% |
0 / 1 |
20 | |||
cancelHolds | |
0.00% |
0 / 23 |
|
0.00% |
0 / 1 |
20 | |||
updateHolds | |
0.00% |
0 / 24 |
|
0.00% |
0 / 1 |
90 | |||
cancelStorageRetrievalRequests | |
0.00% |
0 / 23 |
|
0.00% |
0 / 1 |
20 | |||
getCancelStorageRetrievalRequestDetails | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
renewMyItems | |
0.00% |
0 / 42 |
|
0.00% |
0 / 1 |
30 | |||
getRenewDetails | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
checkRequestIsValid | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
12 | |||
placeHold | |
0.00% |
0 / 61 |
|
0.00% |
0 / 1 |
110 | |||
checkStorageRetrievalRequestIsValid | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
20 | |||
placeStorageRetrievalRequest | |
0.00% |
0 / 61 |
|
0.00% |
0 / 1 |
156 | |||
checkILLRequestIsValid | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
20 | |||
placeILLRequest | |
0.00% |
0 / 67 |
|
0.00% |
0 / 1 |
182 | |||
getILLPickupLibraries | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
getILLPickupLocations | |
0.00% |
0 / 28 |
|
0.00% |
0 / 1 |
12 | |||
cancelILLRequests | |
0.00% |
0 / 23 |
|
0.00% |
0 / 1 |
20 | |||
getCancelILLRequestDetails | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
changePassword | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
6 | |||
getConfig | |
11.27% |
8 / 71 |
|
0.00% |
0 / 1 |
150.93 | |||
getRecentlyReturnedBibs | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
2 | |||
getTrendingBibs | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getProxiedUsers | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getProxyingUsers | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getUrlsForRecord | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
6 |
1 | <?php |
2 | |
3 | /** |
4 | * Advanced Dummy ILS Driver -- Returns sample values based on Solr index. |
5 | * |
6 | * Note that some sample values (holds, transactions, fines) are stored in |
7 | * the session. You can log out and log back in to get a different set of |
8 | * values. |
9 | * |
10 | * PHP version 8 |
11 | * |
12 | * Copyright (C) Villanova University 2007, 2022. |
13 | * Copyright (C) The National Library of Finland 2014. |
14 | * |
15 | * This program is free software; you can redistribute it and/or modify |
16 | * it under the terms of the GNU General Public License version 2, |
17 | * as published by the Free Software Foundation. |
18 | * |
19 | * This program is distributed in the hope that it will be useful, |
20 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
21 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
22 | * GNU General Public License for more details. |
23 | * |
24 | * You should have received a copy of the GNU General Public License |
25 | * along with this program; if not, write to the Free Software |
26 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
27 | * |
28 | * @category VuFind |
29 | * @package ILS_Drivers |
30 | * @author Greg Pendlebury <vufind-tech@lists.sourceforge.net> |
31 | * @author Ere Maijala <ere.maijala@helsinki.fi> |
32 | * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License |
33 | * @link https://vufind.org/wiki/development:plugins:ils_drivers Wiki |
34 | */ |
35 | |
36 | namespace VuFind\ILS\Driver; |
37 | |
38 | use ArrayObject; |
39 | use Laminas\Http\Request as HttpRequest; |
40 | use Laminas\Session\Container as SessionContainer; |
41 | use VuFind\Date\DateException; |
42 | use VuFind\Exception\ILS as ILSException; |
43 | use VuFind\ILS\Logic\AvailabilityStatus; |
44 | use VuFind\ILS\Logic\AvailabilityStatusInterface; |
45 | use VuFindSearch\Command\RandomCommand; |
46 | use VuFindSearch\Query\Query; |
47 | use VuFindSearch\Service as SearchService; |
48 | |
49 | use function array_key_exists; |
50 | use function array_slice; |
51 | use function count; |
52 | use function in_array; |
53 | use function is_callable; |
54 | use function strlen; |
55 | |
56 | /** |
57 | * Advanced Dummy ILS Driver -- Returns sample values based on Solr index. |
58 | * |
59 | * @category VuFind |
60 | * @package ILS_Drivers |
61 | * @author Greg Pendlebury <vufind-tech@lists.sourceforge.net> |
62 | * @author Ere Maijala <ere.maijala@helsinki.fi> |
63 | * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License |
64 | * @link https://vufind.org/wiki/development:plugins:ils_drivers Wiki |
65 | */ |
66 | class Demo extends AbstractBase implements \VuFind\I18n\HasSorterInterface |
67 | { |
68 | use \VuFind\I18n\HasSorterTrait; |
69 | |
70 | /** |
71 | * Catalog ID used to distinquish between multiple Demo driver instances with the |
72 | * MultiBackend driver |
73 | * |
74 | * @var string |
75 | */ |
76 | protected $catalogId = 'demo'; |
77 | |
78 | /** |
79 | * Connection used when getting random bib ids from Solr |
80 | * |
81 | * @var SearchService |
82 | */ |
83 | protected $searchService; |
84 | |
85 | /** |
86 | * Total count of records in the Solr index (used for random bib lookup) |
87 | * |
88 | * @var int |
89 | */ |
90 | protected $totalRecords; |
91 | |
92 | /** |
93 | * Container for storing persistent simulated ILS data. |
94 | * |
95 | * @var SessionContainer[] |
96 | */ |
97 | protected $session = []; |
98 | |
99 | /** |
100 | * Factory function for constructing the SessionContainer. |
101 | * |
102 | * @var callable |
103 | */ |
104 | protected $sessionFactory; |
105 | |
106 | /** |
107 | * HTTP Request object (if available). |
108 | * |
109 | * @var ?HttpRequest |
110 | */ |
111 | protected $request; |
112 | |
113 | /** |
114 | * Should we return bib IDs in MyResearch responses? |
115 | * |
116 | * @var bool |
117 | */ |
118 | protected $idsInMyResearch = true; |
119 | |
120 | /** |
121 | * Should we support Storage Retrieval Requests? |
122 | * |
123 | * @var bool |
124 | */ |
125 | protected $storageRetrievalRequests = true; |
126 | |
127 | /** |
128 | * Should we support ILLRequests? |
129 | * |
130 | * @var bool |
131 | */ |
132 | protected $ILLRequests = true; |
133 | |
134 | /** |
135 | * Date converter object |
136 | * |
137 | * @var \VuFind\Date\Converter |
138 | */ |
139 | protected $dateConverter; |
140 | |
141 | /** |
142 | * Failure probability settings |
143 | * |
144 | * @var array |
145 | */ |
146 | protected $failureProbabilities = []; |
147 | |
148 | /** |
149 | * Courses for use in course reserves. |
150 | * |
151 | * @var array |
152 | */ |
153 | protected $courses = ['Course A', 'Course B', 'Course C']; |
154 | |
155 | /** |
156 | * Departments for use in course reserves. |
157 | * |
158 | * @var array |
159 | */ |
160 | protected $departments = ['Dept. A', 'Dept. B', 'Dept. C']; |
161 | |
162 | /** |
163 | * Instructors for use in course reserves. |
164 | * |
165 | * @var array |
166 | */ |
167 | protected $instructors = ['Instructor A', 'Instructor B', 'Instructor C']; |
168 | |
169 | /** |
170 | * Item and pick up locations |
171 | * |
172 | * @var array |
173 | */ |
174 | protected $locations = [ |
175 | [ |
176 | 'locationID' => 'A', |
177 | 'locationDisplay' => 'Campus A', |
178 | ], |
179 | [ |
180 | 'locationID' => 'B', |
181 | 'locationDisplay' => 'Campus B', |
182 | ], |
183 | [ |
184 | 'locationID' => 'C', |
185 | 'locationDisplay' => 'Campus C', |
186 | ], |
187 | ]; |
188 | |
189 | /** |
190 | * Default pickup location |
191 | * |
192 | * @var string |
193 | */ |
194 | protected $defaultPickUpLocation; |
195 | |
196 | /** |
197 | * Constructor |
198 | * |
199 | * @param \VuFind\Date\Converter $dateConverter Date converter object |
200 | * @param SearchService $ss Search service |
201 | * @param callable $sessionFactory Factory function returning |
202 | * SessionContainer object for fake data to simulate consistency and reduce Solr |
203 | * hits |
204 | * @param HttpRequest $request HTTP request object (optional) |
205 | */ |
206 | public function __construct( |
207 | \VuFind\Date\Converter $dateConverter, |
208 | SearchService $ss, |
209 | $sessionFactory, |
210 | HttpRequest $request = null |
211 | ) { |
212 | $this->dateConverter = $dateConverter; |
213 | $this->searchService = $ss; |
214 | if (!is_callable($sessionFactory)) { |
215 | throw new \Exception('Invalid session factory passed to constructor.'); |
216 | } |
217 | $this->sessionFactory = $sessionFactory; |
218 | $this->request = $request; |
219 | } |
220 | |
221 | /** |
222 | * Initialize the driver. |
223 | * |
224 | * Validate configuration and perform all resource-intensive tasks needed to |
225 | * make the driver active. |
226 | * |
227 | * @throws ILSException |
228 | * @return void |
229 | */ |
230 | public function init() |
231 | { |
232 | if (isset($this->config['Catalog']['id'])) { |
233 | $this->catalogId = $this->config['Catalog']['id']; |
234 | } |
235 | if (isset($this->config['Catalog']['idsInMyResearch'])) { |
236 | $this->idsInMyResearch = $this->config['Catalog']['idsInMyResearch']; |
237 | } |
238 | if (isset($this->config['Catalog']['storageRetrievalRequests'])) { |
239 | $this->storageRetrievalRequests |
240 | = $this->config['Catalog']['storageRetrievalRequests']; |
241 | } |
242 | if (isset($this->config['Catalog']['ILLRequests'])) { |
243 | $this->ILLRequests = $this->config['Catalog']['ILLRequests']; |
244 | } |
245 | if (isset($this->config['Failure_Probabilities'])) { |
246 | $this->failureProbabilities = $this->config['Failure_Probabilities']; |
247 | } |
248 | $this->defaultPickUpLocation |
249 | = $this->config['Holds']['defaultPickUpLocation'] ?? ''; |
250 | if ($this->defaultPickUpLocation === 'user-selected') { |
251 | $this->defaultPickUpLocation = false; |
252 | } |
253 | $this->checkIntermittentFailure(); |
254 | } |
255 | |
256 | /** |
257 | * Check for a simulated failure. Returns true for failure, false for |
258 | * success. |
259 | * |
260 | * @param string $method Name of method that might fail |
261 | * @param int $default Default probability (if config is empty) |
262 | * |
263 | * @return bool |
264 | */ |
265 | protected function isFailing($method, $default = 0) |
266 | { |
267 | // Method may come in like Class::Method, we just want the Method part |
268 | $parts = explode('::', $method); |
269 | $key = array_pop($parts); |
270 | $probability = $this->failureProbabilities[$key] ?? $default; |
271 | return rand(1, 100) <= $probability; |
272 | } |
273 | |
274 | /** |
275 | * Generate a fake location name. |
276 | * |
277 | * @param bool $returnText If true, return location text; if false, return ID |
278 | * |
279 | * @return string |
280 | */ |
281 | protected function getFakeLoc($returnText = true) |
282 | { |
283 | $locations = $this->locations; |
284 | $loc = rand() % count($locations); |
285 | return $returnText |
286 | ? $locations[$loc]['locationDisplay'] |
287 | : $locations[$loc]['locationID']; |
288 | } |
289 | |
290 | /** |
291 | * Generate fake services. |
292 | * |
293 | * @return array |
294 | */ |
295 | protected function getFakeServices() |
296 | { |
297 | // Load service configuration; return empty array if no services defined. |
298 | $services = isset($this->config['Records']['services']) |
299 | ? (array)$this->config['Records']['services'] |
300 | : []; |
301 | if (empty($services)) { |
302 | return []; |
303 | } |
304 | |
305 | // Make it more likely we have a single service than many: |
306 | $count = rand(1, 5) == 1 ? rand(1, count($services)) : 1; |
307 | $keys = (array)array_rand($services, $count); |
308 | $fakeServices = []; |
309 | |
310 | foreach ($keys as $key) { |
311 | if ($key !== null) { |
312 | $fakeServices[] = $services[$key]; |
313 | } |
314 | } |
315 | |
316 | return $fakeServices; |
317 | } |
318 | |
319 | /** |
320 | * Generate a fake status message. |
321 | * |
322 | * @return string |
323 | */ |
324 | protected function getFakeStatus() |
325 | { |
326 | $loc = rand() % 10; |
327 | switch ($loc) { |
328 | case 10: |
329 | return 'Missing'; |
330 | case 9: |
331 | return 'On Order'; |
332 | case 8: |
333 | return 'Invoiced'; |
334 | case 7: |
335 | return 'Uncertain'; |
336 | default: |
337 | return 'Available'; |
338 | } |
339 | } |
340 | |
341 | /** |
342 | * Generate a fake call number. |
343 | * |
344 | * @return string |
345 | */ |
346 | protected function getFakeCallNum() |
347 | { |
348 | $codes = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; |
349 | $a = $codes[rand() % strlen($codes)]; |
350 | $b = rand() % 899 + 100; |
351 | $c = rand() % 9999; |
352 | return $a . $b . '.' . $c; |
353 | } |
354 | |
355 | /** |
356 | * Generate a fake call number prefix sometimes. |
357 | * |
358 | * @return string |
359 | */ |
360 | protected function getFakeCallNumPrefix() |
361 | { |
362 | $codes = '0123456789'; |
363 | $prefix = substr(str_shuffle($codes), 1, rand(0, 1)); |
364 | if (!empty($prefix)) { |
365 | return 'Prefix: ' . $prefix; |
366 | } |
367 | return ''; |
368 | } |
369 | |
370 | /** |
371 | * Get a random ID from the Solr index. |
372 | * |
373 | * @return string |
374 | */ |
375 | protected function getRandomBibId() |
376 | { |
377 | [$id] = $this->getRandomBibIdAndTitle(); |
378 | return $id; |
379 | } |
380 | |
381 | /** |
382 | * Get a random ID and title from the Solr index. |
383 | * |
384 | * @return array [id, title] |
385 | */ |
386 | protected function getRandomBibIdAndTitle() |
387 | { |
388 | $source = $this->getRecordSource(); |
389 | $query = $this->config['Records']['query'] ?? '*:*'; |
390 | $command = new RandomCommand($source, new Query($query), 1); |
391 | $result = $this->searchService->invoke($command)->getResult(); |
392 | if (count($result) === 0) { |
393 | throw new \Exception("Problem retrieving random record from $source."); |
394 | } |
395 | $record = current($result->getRecords()); |
396 | return [$record->getUniqueId(), $record->getTitle()]; |
397 | } |
398 | |
399 | /** |
400 | * Get the name of the search backend providing records. |
401 | * |
402 | * @return string |
403 | */ |
404 | protected function getRecordSource() |
405 | { |
406 | return $this->config['Records']['source'] ?? DEFAULT_SEARCH_BACKEND; |
407 | } |
408 | |
409 | /** |
410 | * Should we simulate a system failure? |
411 | * |
412 | * @return void |
413 | * @throws ILSException |
414 | */ |
415 | protected function checkIntermittentFailure() |
416 | { |
417 | if ($this->isFailing(__METHOD__, 0)) { |
418 | throw new ILSException('Simulating low-level system failure'); |
419 | } |
420 | } |
421 | |
422 | /** |
423 | * Are renewals blocked? |
424 | * |
425 | * @return bool |
426 | */ |
427 | protected function checkRenewBlock() |
428 | { |
429 | return $this->isFailing(__METHOD__, 25); |
430 | } |
431 | |
432 | /** |
433 | * Check whether the patron is blocked from placing requests (holds/ILL/SRR). |
434 | * |
435 | * @param array $patron Patron data from patronLogin(). |
436 | * |
437 | * @return mixed A boolean false if no blocks are in place and an array |
438 | * of block reasons if blocks are in place |
439 | * |
440 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) |
441 | */ |
442 | public function getRequestBlocks($patron) |
443 | { |
444 | return $this->isFailing(__METHOD__, 10) |
445 | ? ['simulated request block'] : false; |
446 | } |
447 | |
448 | /** |
449 | * Check whether the patron has any blocks on their account. |
450 | * |
451 | * @param array $patron Patron data from patronLogin(). |
452 | * |
453 | * @return mixed A boolean false if no blocks are in place and an array |
454 | * of block reasons if blocks are in place |
455 | * |
456 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) |
457 | */ |
458 | public function getAccountBlocks($patron) |
459 | { |
460 | return $this->isFailing(__METHOD__, 10) |
461 | ? ['simulated account block'] : false; |
462 | } |
463 | |
464 | /** |
465 | * Generates a random, fake holding array |
466 | * |
467 | * @param string $id set id |
468 | * @param string $number set number for multiple items |
469 | * @param array $patron Patron data |
470 | * |
471 | * @return array |
472 | */ |
473 | protected function getRandomHolding($id, $number, array $patron = null) |
474 | { |
475 | $status = $this->getFakeStatus(); |
476 | $location = $this->getFakeLoc(); |
477 | $locationhref = ($location === 'Campus A') ? 'http://campus-a' : false; |
478 | switch ($status) { |
479 | case 'Uncertain': |
480 | $availability = AvailabilityStatusInterface::STATUS_UNCERTAIN; |
481 | break; |
482 | case 'Available': |
483 | if (rand(1, 2) === 1) { |
484 | // Legacy boolean value |
485 | $availability = true; |
486 | } else { |
487 | $availability = AvailabilityStatusInterface::STATUS_AVAILABLE; |
488 | $status = 'Item in Library'; |
489 | } |
490 | break; |
491 | default: |
492 | if (rand(1, 2) === 1) { |
493 | // Legacy boolean value |
494 | $availability = false; |
495 | } else { |
496 | $availability = AvailabilityStatusInterface::STATUS_UNAVAILABLE; |
497 | } |
498 | break; |
499 | } |
500 | $result = [ |
501 | 'id' => $id, |
502 | 'source' => $this->getRecordSource(), |
503 | 'item_id' => $number, |
504 | 'number' => $number, |
505 | 'barcode' => sprintf('%08d', rand() % 50000), |
506 | 'availability' => $availability, |
507 | 'status' => $status, |
508 | 'location' => $location, |
509 | 'locationhref' => $locationhref, |
510 | 'reserve' => rand(1, 4) === 1 ? 'Y' : 'N', |
511 | 'callnumber' => $this->getFakeCallNum(), |
512 | 'callnumber_prefix' => $this->getFakeCallNumPrefix(), |
513 | 'duedate' => '', |
514 | 'is_holdable' => true, |
515 | 'addLink' => $patron ? true : false, |
516 | 'level' => 'copy', |
517 | 'storageRetrievalRequest' => 'auto', |
518 | 'addStorageRetrievalRequestLink' => $patron ? 'check' : false, |
519 | 'ILLRequest' => 'auto', |
520 | 'addILLRequestLink' => $patron ? 'check' : false, |
521 | 'services' => $status == 'Available' ? $this->getFakeServices() : [], |
522 | ]; |
523 | |
524 | switch (rand(1, 5)) { |
525 | case 1: |
526 | $result['location'] = 'Digital copy available'; |
527 | $result['locationhref'] = 'http://digital'; |
528 | $result['__electronic__'] = true; |
529 | $result['availability'] = true; |
530 | $result['status'] = ''; |
531 | break; |
532 | case 2: |
533 | $result['location'] = 'Electronic Journals'; |
534 | $result['locationhref'] = 'http://electronic'; |
535 | $result['__electronic__'] = true; |
536 | $result['availability'] = true; |
537 | $result['status'] = 'Available from ' . rand(2010, 2019); |
538 | } |
539 | |
540 | return $result; |
541 | } |
542 | |
543 | /** |
544 | * Generate an associative array containing some sort of ID (for cover |
545 | * generation). |
546 | * |
547 | * @return array |
548 | */ |
549 | protected function getRandomItemIdentifier() |
550 | { |
551 | switch (rand(1, 4)) { |
552 | case 1: |
553 | return ['isbn' => '1558612742']; |
554 | case 2: |
555 | return ['oclc' => '55114477']; |
556 | case 3: |
557 | return ['issn' => '1133-0686']; |
558 | } |
559 | return ['upc' => '733961100525']; |
560 | } |
561 | |
562 | /** |
563 | * Generate a list of holds, storage retrieval requests or ILL requests. |
564 | * |
565 | * @param string $requestType Request type (Holds, StorageRetrievalRequests or |
566 | * ILLRequests) |
567 | * |
568 | * @return ArrayObject List of requests |
569 | */ |
570 | protected function createRequestList($requestType) |
571 | { |
572 | // How many items are there? %10 - 1 = 10% chance of none, |
573 | // 90% of 1-9 (give or take some odd maths) |
574 | $items = rand() % 10 - 1; |
575 | |
576 | $requestGroups = $this->getRequestGroups(null, null); |
577 | |
578 | $list = new ArrayObject(); |
579 | for ($i = 0; $i < $items; $i++) { |
580 | $location = $this->getFakeLoc(false); |
581 | $randDays = rand() % 10; |
582 | $currentItem = [ |
583 | 'location' => $location, |
584 | 'create' => $this->dateConverter->convertToDisplayDate( |
585 | 'U', |
586 | strtotime("now - {$randDays} days") |
587 | ), |
588 | 'expire' => $this->dateConverter->convertToDisplayDate( |
589 | 'U', |
590 | strtotime('now + 30 days') |
591 | ), |
592 | 'item_id' => $i, |
593 | 'reqnum' => $i, |
594 | ]; |
595 | // Inject a random identifier of some sort: |
596 | $currentItem += $this->getRandomItemIdentifier(); |
597 | if ($i == 2 || rand() % 5 == 1) { |
598 | // Mimic an ILL request |
599 | $currentItem['id'] = "ill_request_$i"; |
600 | $currentItem['title'] = "ILL Hold Title $i"; |
601 | $currentItem['institution_id'] = 'ill_institution'; |
602 | $currentItem['institution_name'] = 'ILL Library'; |
603 | $currentItem['institution_dbkey'] = 'ill_institution'; |
604 | } else { |
605 | if ($this->idsInMyResearch) { |
606 | [$currentItem['id'], $currentItem['title']] |
607 | = $this->getRandomBibIdAndtitle(); |
608 | $currentItem['source'] = $this->getRecordSource(); |
609 | } else { |
610 | $currentItem['title'] = 'Demo Title ' . $i; |
611 | } |
612 | } |
613 | |
614 | if ($requestType == 'Holds') { |
615 | $pos = rand() % 5; |
616 | if ($pos > 1) { |
617 | $currentItem['position'] = $pos; |
618 | $currentItem['available'] = false; |
619 | $currentItem['in_transit'] = (rand() % 2) === 1; |
620 | } else { |
621 | $currentItem['available'] = true; |
622 | $currentItem['in_transit'] = false; |
623 | if (rand() % 3 != 1) { |
624 | $lastDate = strtotime('now + 3 days'); |
625 | $currentItem['last_pickup_date'] = $this->dateConverter |
626 | ->convertToDisplayDate('U', $lastDate); |
627 | } |
628 | } |
629 | $pos = rand(0, count($requestGroups) - 1); |
630 | $currentItem['requestGroup'] = $requestGroups[$pos]['name']; |
631 | $currentItem['cancel_details'] = $currentItem['updateDetails'] |
632 | = (!$currentItem['available'] && !$currentItem['in_transit']) |
633 | ? $currentItem['reqnum'] : ''; |
634 | if (rand(0, 3) === 1) { |
635 | $currentItem['proxiedBy'] = 'Fictional Proxy User'; |
636 | } |
637 | } else { |
638 | $status = rand() % 5; |
639 | $currentItem['available'] = $status == 1; |
640 | $currentItem['canceled'] = $status == 2; |
641 | $currentItem['processed'] = ($status == 1 || rand(1, 3) == 3) |
642 | ? $this->dateConverter->convertToDisplayDate('U', time()) |
643 | : ''; |
644 | if ($requestType == 'ILLRequests') { |
645 | $transit = rand() % 2; |
646 | if ( |
647 | !$currentItem['available'] |
648 | && !$currentItem['canceled'] |
649 | && $transit == 1 |
650 | ) { |
651 | $currentItem['in_transit'] = $location; |
652 | } else { |
653 | $currentItem['in_transit'] = false; |
654 | } |
655 | } |
656 | } |
657 | |
658 | $list->append($currentItem); |
659 | } |
660 | return $list; |
661 | } |
662 | |
663 | /** |
664 | * Get Status |
665 | * |
666 | * This is responsible for retrieving the status information of a certain |
667 | * record. |
668 | * |
669 | * @param string $id The record id to retrieve the holdings for |
670 | * |
671 | * @return mixed On success, an associative array with the following keys: |
672 | * id, availability (boolean), status, location, reserve, callnumber. |
673 | */ |
674 | public function getStatus($id) |
675 | { |
676 | return $this->getSimulatedStatus($id); |
677 | } |
678 | |
679 | /** |
680 | * Get suppressed records. |
681 | * |
682 | * @return array ID numbers of suppressed records in the system. |
683 | */ |
684 | public function getSuppressedRecords() |
685 | { |
686 | return $this->config['Records']['suppressed'] ?? []; |
687 | } |
688 | |
689 | /** |
690 | * Get the session container (constructing it on demand if not already present) |
691 | * |
692 | * @param string $patron ID of current patron |
693 | * |
694 | * @return SessionContainer |
695 | */ |
696 | protected function getSession($patron = null) |
697 | { |
698 | $sessionKey = md5($this->catalogId . '/' . ($patron ?? 'default')); |
699 | |
700 | // SessionContainer not defined yet? Build it now: |
701 | if (!isset($this->session[$sessionKey])) { |
702 | $this->session[$sessionKey] = ($this->sessionFactory)($sessionKey); |
703 | } |
704 | $result = $this->session[$sessionKey]; |
705 | // Special case: check for clear_demo request parameter to reset: |
706 | if ($this->request && $this->request->getQuery('clear_demo')) { |
707 | $result->exchangeArray([]); |
708 | } |
709 | |
710 | return $result; |
711 | } |
712 | |
713 | /** |
714 | * Get Simulated Status (support method for getStatus/getHolding) |
715 | * |
716 | * This is responsible for retrieving the status information of a certain |
717 | * record. |
718 | * |
719 | * @param string $id The record id to retrieve the holdings for |
720 | * @param array $patron Patron data |
721 | * |
722 | * @return mixed On success, an associative array with the following keys: |
723 | * id, availability (boolean), status, location, reserve, callnumber. |
724 | * |
725 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) |
726 | */ |
727 | protected function getSimulatedStatus($id, array $patron = null) |
728 | { |
729 | $id = (string)$id; |
730 | |
731 | if ($json = $this->config['StaticHoldings'][$id] ?? null) { |
732 | foreach (json_decode($json, true) as $i => $status) { |
733 | if ($status['use_status_class'] ?? false) { |
734 | $availability = $status['availability'] ?? false; |
735 | if ($status['use_unknown_message'] ?? false) { |
736 | $availability = AvailabilityStatusInterface::STATUS_UNKNOWN; |
737 | } |
738 | $status['availability'] = new AvailabilityStatus( |
739 | $availability, |
740 | $status['status'] ?? '', |
741 | $status['extraStatusInformation'] ?? [] |
742 | ); |
743 | unset($status['status']); |
744 | unset($status['use_unknown_message']); |
745 | } |
746 | $this->setStatus($id, $status, $i > 0, $patron); |
747 | } |
748 | } |
749 | |
750 | // Do we have a fake status persisted in the session? |
751 | $session = $this->getSession($patron['id'] ?? null); |
752 | if (isset($session->statuses[$id])) { |
753 | return $session->statuses[$id]; |
754 | } |
755 | |
756 | // Create fake entries for a random number of items |
757 | $holding = []; |
758 | $records = rand() % 15; |
759 | for ($i = 1; $i <= $records; $i++) { |
760 | $holding[] = $this->setStatus($id, [], true, $patron); |
761 | } |
762 | return $holding; |
763 | } |
764 | |
765 | /** |
766 | * Set Status |
767 | * |
768 | * @param string $id id for record |
769 | * @param array $holding associative array with options to specify |
770 | * number, barcode, availability, status, location, |
771 | * reserve, callnumber, duedate, is_holdable, and addLink |
772 | * @param bool $append add another record or replace current record |
773 | * @param array $patron Patron data |
774 | * |
775 | * @return array |
776 | */ |
777 | protected function setStatus(string $id, $holding = [], $append = true, $patron = null) |
778 | { |
779 | $session = $this->getSession($patron['id'] ?? null); |
780 | $i = isset($session->statuses[$id]) |
781 | ? count($session->statuses[$id]) + 1 : 1; |
782 | $holding = array_merge($this->getRandomHolding($id, $i, $patron), $holding); |
783 | |
784 | // if statuses is already stored |
785 | if ($session->statuses) { |
786 | // and this id is part of it |
787 | if ($append && isset($session->statuses[$id])) { |
788 | // add to the array |
789 | $session->statuses[$id][] = $holding; |
790 | } else { |
791 | // if we're over-writing or if there's nothing stored for this id |
792 | $session->statuses[$id] = [$holding]; |
793 | } |
794 | } else { |
795 | // brand new status storage! |
796 | $session->statuses = [$id => [$holding]]; |
797 | } |
798 | return $holding; |
799 | } |
800 | |
801 | /** |
802 | * Get Statuses |
803 | * |
804 | * This is responsible for retrieving the status information for a |
805 | * collection of records. |
806 | * |
807 | * @param array $ids The array of record ids to retrieve the status for |
808 | * |
809 | * @return array An array of getStatus() return values on success. |
810 | */ |
811 | public function getStatuses($ids) |
812 | { |
813 | $this->checkIntermittentFailure(); |
814 | |
815 | if ($this->isFailing(__METHOD__, 0)) { |
816 | return array_map( |
817 | function ($id) { |
818 | return [ |
819 | [ |
820 | 'id' => $id, |
821 | 'error' => 'Simulated failure', |
822 | ], |
823 | ]; |
824 | }, |
825 | $ids |
826 | ); |
827 | } |
828 | |
829 | return array_map([$this, 'getStatus'], $ids); |
830 | } |
831 | |
832 | /** |
833 | * Get Holding |
834 | * |
835 | * This is responsible for retrieving the holding information of a certain |
836 | * record. |
837 | * |
838 | * @param string $id The record id to retrieve the holdings for |
839 | * @param array $patron Patron data |
840 | * @param array $options Extra options |
841 | * |
842 | * @return array On success, an associative array with the following keys: |
843 | * id, availability (boolean), status, location, reserve, callnumber, |
844 | * duedate, number, barcode. |
845 | */ |
846 | public function getHolding($id, array $patron = null, array $options = []) |
847 | { |
848 | $this->checkIntermittentFailure(); |
849 | |
850 | if ($this->isFailing(__METHOD__, 0)) { |
851 | return [ |
852 | 'id' => $id, |
853 | 'error' => 'Simulated failure', |
854 | ]; |
855 | } |
856 | |
857 | // Get basic status info: |
858 | $status = $this->getSimulatedStatus($id, $patron); |
859 | |
860 | $issue = 1; |
861 | // Add notes and summary: |
862 | foreach (array_keys($status) as $i) { |
863 | $itemNum = $i + 1; |
864 | $noteCount = rand(1, 3); |
865 | $status[$i]['holdings_notes'] = []; |
866 | $status[$i]['item_notes'] = []; |
867 | for ($j = 1; $j <= $noteCount; $j++) { |
868 | $status[$i]['holdings_notes'][] = "Item $itemNum holdings note $j" |
869 | . ($j === 1 ? ' https://vufind.org/?f=1&b=2#sample_link' : ''); |
870 | $status[$i]['item_notes'][] = "Item $itemNum note $j"; |
871 | } |
872 | $summCount = rand(1, 3); |
873 | $status[$i]['summary'] = []; |
874 | for ($j = 1; $j <= $summCount; $j++) { |
875 | $status[$i]['summary'][] = "Item $itemNum summary $j"; |
876 | } |
877 | $volume = intdiv($issue, 4) + 1; |
878 | $seriesIssue = $issue % 4; |
879 | $issue = $issue + 1; |
880 | $status[$i]['enumchron'] = "volume $volume, issue $seriesIssue"; |
881 | if (rand(1, 100) <= ($this->config['Holdings']['boundWithProbability'] ?? 25)) { |
882 | $status[$i]['bound_with_records'] = []; |
883 | $boundWithCount = 3; |
884 | for ($j = 0; $j < $boundWithCount; $j++) { |
885 | $randomRecord = array_combine(['bibId', 'title'], $this->getRandomBibIdAndTitle()); |
886 | $status[$i]['bound_with_records'][] = $randomRecord; |
887 | } |
888 | $boundWithIndex = rand(0, $boundWithCount + 1); |
889 | array_splice($status[$i]['bound_with_records'], $boundWithIndex, 0, [ |
890 | [ |
891 | 'title' => 'The Title on This Page', |
892 | 'bibId' => $id, |
893 | ], |
894 | ]); |
895 | } |
896 | } |
897 | |
898 | // Filter out electronic holdings from the normal holdings list: |
899 | $status = array_filter( |
900 | $status, |
901 | function ($a) { |
902 | return !($a['__electronic__'] ?? false); |
903 | } |
904 | ); |
905 | |
906 | // Slice out a chunk if pagination is enabled. |
907 | $slice = null; |
908 | if ($options['itemLimit'] ?? null) { |
909 | // For sensible pagination, we need to sort by location: |
910 | $callback = function ($a, $b) { |
911 | return $this->getSorter()->compare($a['location'], $b['location']); |
912 | }; |
913 | usort($status, $callback); |
914 | $slice = array_slice( |
915 | $status, |
916 | $options['offset'] ?? 0, |
917 | $options['itemLimit'] |
918 | ); |
919 | } |
920 | |
921 | // Electronic holdings: |
922 | $statuses = $this->getStatus($id); |
923 | $electronic = []; |
924 | foreach ($statuses as $item) { |
925 | if ($item['__electronic__'] ?? false) { |
926 | // Don't expose internal __electronic__ flag upstream: |
927 | unset($item['__electronic__']); |
928 | $electronic[] = $item; |
929 | } |
930 | } |
931 | |
932 | // Send back final value: |
933 | return [ |
934 | 'total' => count($status), |
935 | 'holdings' => $slice ?: $status, |
936 | 'electronic_holdings' => $electronic, |
937 | ]; |
938 | } |
939 | |
940 | /** |
941 | * Get Purchase History |
942 | * |
943 | * This is responsible for retrieving the acquisitions history data for the |
944 | * specific record (usually recently received issues of a serial). |
945 | * |
946 | * @param string $id The record id to retrieve the info for |
947 | * |
948 | * @return array An array with the acquisitions data on success. |
949 | */ |
950 | public function getPurchaseHistory($id) |
951 | { |
952 | $this->checkIntermittentFailure(); |
953 | $issues = rand(0, 3); |
954 | $retval = []; |
955 | for ($i = 0; $i < $issues; $i++) { |
956 | $retval[] = ['issue' => 'issue ' . ($i + 1)]; |
957 | } |
958 | return $retval; |
959 | } |
960 | |
961 | /** |
962 | * Patron Login |
963 | * |
964 | * This is responsible for authenticating a patron against the catalog. |
965 | * |
966 | * @param string $username The patron username |
967 | * @param string $password The patron password |
968 | * |
969 | * @throws ILSException |
970 | * @return mixed Associative array of patron info on successful login, |
971 | * null on unsuccessful login. |
972 | */ |
973 | public function patronLogin($username, $password) |
974 | { |
975 | $this->checkIntermittentFailure(); |
976 | |
977 | $user = [ |
978 | 'id' => trim($username), |
979 | 'firstname' => 'Lib', |
980 | 'lastname' => 'Rarian', |
981 | 'cat_username' => trim($username), |
982 | 'cat_password' => trim($password), |
983 | 'email' => 'Lib.Rarian@library.not', |
984 | 'major' => null, |
985 | 'college' => null, |
986 | ]; |
987 | |
988 | $loginMethod = $this->config['Catalog']['loginMethod'] ?? 'password'; |
989 | if ('email' === $loginMethod) { |
990 | $user['email'] = $username; |
991 | $user['cat_password'] = ''; |
992 | return $user; |
993 | } |
994 | |
995 | if (isset($this->config['Users'])) { |
996 | if ( |
997 | !isset($this->config['Users'][$username]) |
998 | || $password !== $this->config['Users'][$username] |
999 | ) { |
1000 | return null; |
1001 | } |
1002 | } |
1003 | |
1004 | return $user; |
1005 | } |
1006 | |
1007 | /** |
1008 | * Get Patron Profile |
1009 | * |
1010 | * This is responsible for retrieving the profile for a specific patron. |
1011 | * |
1012 | * @param array $patron The patron array |
1013 | * |
1014 | * @return array Array of the patron's profile data on success. |
1015 | */ |
1016 | public function getMyProfile($patron) |
1017 | { |
1018 | $this->checkIntermittentFailure(); |
1019 | $age = rand(13, 113); |
1020 | $birthDate = new \DateTime(); |
1021 | $birthDate->sub(new \DateInterval("P{$age}Y")); |
1022 | $patron = [ |
1023 | 'firstname' => 'Lib-' . $patron['cat_username'], |
1024 | 'lastname' => 'Rarian', |
1025 | 'address1' => 'Somewhere...', |
1026 | 'address2' => 'Over the Rainbow', |
1027 | 'zip' => '12345', |
1028 | 'city' => 'City', |
1029 | 'country' => 'Country', |
1030 | 'phone' => '1900 CALL ME', |
1031 | 'mobile_phone' => '1234567890', |
1032 | 'group' => 'Library Staff', |
1033 | 'expiration_date' => 'Someday', |
1034 | 'birthdate' => $birthDate->format('Y-m-d'), |
1035 | ]; |
1036 | return $patron; |
1037 | } |
1038 | |
1039 | /** |
1040 | * Get Patron Fines |
1041 | * |
1042 | * This is responsible for retrieving all fines by a specific patron. |
1043 | * |
1044 | * @param array $patron The patron array from patronLogin |
1045 | * |
1046 | * @return mixed Array of the patron's fines on success. |
1047 | * |
1048 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) |
1049 | */ |
1050 | public function getMyFines($patron) |
1051 | { |
1052 | $this->checkIntermittentFailure(); |
1053 | $session = $this->getSession($patron['id'] ?? null); |
1054 | if (!isset($session->fines)) { |
1055 | // How many items are there? %20 - 2 = 10% chance of none, |
1056 | // 90% of 1-18 (give or take some odd maths) |
1057 | $fines = rand() % 20 - 2; |
1058 | |
1059 | $fineList = []; |
1060 | for ($i = 0; $i < $fines; $i++) { |
1061 | // How many days overdue is the item? |
1062 | $day_overdue = rand() % 30 + 5; |
1063 | // Calculate checkout date: |
1064 | $checkout = strtotime('now - ' . ($day_overdue + 14) . ' days'); |
1065 | // 1 in 10 chance of this being a "Manual Fee": |
1066 | if (rand(1, 10) === 1) { |
1067 | $fine = 2.50; |
1068 | $type = 'Manual Fee'; |
1069 | } else { |
1070 | // 50c a day fine |
1071 | $fine = $day_overdue * 0.50; |
1072 | // After 20 days it becomes 'Long Overdue' |
1073 | $type = $day_overdue > 20 ? 'Long Overdue' : 'Overdue'; |
1074 | } |
1075 | |
1076 | $fineList[] = [ |
1077 | 'amount' => $fine * 100, |
1078 | 'checkout' => $this->dateConverter |
1079 | ->convertToDisplayDate('U', $checkout), |
1080 | 'createdate' => $this->dateConverter |
1081 | ->convertToDisplayDate('U', time()), |
1082 | 'fine' => $type, |
1083 | // Additional description for long overdue fines: |
1084 | 'description' => 'Manual Fee' === $type ? 'Interlibrary loan request fee' : '', |
1085 | // 50% chance they've paid half of it |
1086 | 'balance' => (rand() % 100 > 49 ? $fine / 2 : $fine) * 100, |
1087 | 'duedate' => $this->dateConverter->convertToDisplayDate( |
1088 | 'U', |
1089 | strtotime("now - $day_overdue days") |
1090 | ), |
1091 | ]; |
1092 | // Some fines will have no id or title: |
1093 | if (rand() % 3 != 1) { |
1094 | if ($this->idsInMyResearch) { |
1095 | [$fineList[$i]['id'], $fineList[$i]['title']] |
1096 | = $this->getRandomBibIdAndTitle(); |
1097 | $fineList[$i]['source'] = $this->getRecordSource(); |
1098 | } else { |
1099 | $fineList[$i]['title'] = 'Demo Title ' . $i; |
1100 | } |
1101 | } |
1102 | } |
1103 | $session->fines = $fineList; |
1104 | } |
1105 | return $session->fines; |
1106 | } |
1107 | |
1108 | /** |
1109 | * Get Patron Holds |
1110 | * |
1111 | * This is responsible for retrieving all holds by a specific patron. |
1112 | * |
1113 | * @param array $patron The patron array from patronLogin |
1114 | * |
1115 | * @return mixed Array of the patron's holds on success. |
1116 | * |
1117 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) |
1118 | */ |
1119 | public function getMyHolds($patron) |
1120 | { |
1121 | $this->checkIntermittentFailure(); |
1122 | $session = $this->getSession($patron['id'] ?? null); |
1123 | if (!isset($session->holds)) { |
1124 | $session->holds = $this->createRequestList('Holds'); |
1125 | } |
1126 | return $session->holds; |
1127 | } |
1128 | |
1129 | /** |
1130 | * Get Patron Storage Retrieval Requests |
1131 | * |
1132 | * This is responsible for retrieving all call slips by a specific patron. |
1133 | * |
1134 | * @param array $patron The patron array from patronLogin |
1135 | * |
1136 | * @return mixed Array of the patron's holds |
1137 | * |
1138 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) |
1139 | */ |
1140 | public function getMyStorageRetrievalRequests($patron) |
1141 | { |
1142 | $this->checkIntermittentFailure(); |
1143 | $session = $this->getSession($patron['id'] ?? null); |
1144 | if (!isset($session->storageRetrievalRequests)) { |
1145 | $session->storageRetrievalRequests |
1146 | = $this->createRequestList('StorageRetrievalRequests'); |
1147 | } |
1148 | return $session->storageRetrievalRequests; |
1149 | } |
1150 | |
1151 | /** |
1152 | * Get Patron ILL Requests |
1153 | * |
1154 | * This is responsible for retrieving all ILL requests by a specific patron. |
1155 | * |
1156 | * @param array $patron The patron array from patronLogin |
1157 | * |
1158 | * @return mixed Array of the patron's ILL requests |
1159 | * |
1160 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) |
1161 | */ |
1162 | public function getMyILLRequests($patron) |
1163 | { |
1164 | $this->checkIntermittentFailure(); |
1165 | $session = $this->getSession($patron['id'] ?? null); |
1166 | if (!isset($session->ILLRequests)) { |
1167 | $session->ILLRequests = $this->createRequestList('ILLRequests'); |
1168 | } |
1169 | return $session->ILLRequests; |
1170 | } |
1171 | |
1172 | /** |
1173 | * Construct a transaction list for getMyTransactions; may be random or |
1174 | * pre-set depending on Demo.ini settings. |
1175 | * |
1176 | * @return array |
1177 | */ |
1178 | protected function getTransactionList() |
1179 | { |
1180 | $this->checkIntermittentFailure(); |
1181 | // If Demo.ini includes a fixed set of transactions, load those; otherwise |
1182 | // build some random ones. |
1183 | return isset($this->config['Records']['transactions']) |
1184 | ? json_decode($this->config['Records']['transactions'], true) |
1185 | : $this->getRandomTransactionList(); |
1186 | } |
1187 | |
1188 | /** |
1189 | * Calculate the due status for a due date. |
1190 | * |
1191 | * @param int $due Due date as Unix timestamp |
1192 | * |
1193 | * @return string |
1194 | */ |
1195 | protected function calculateDueStatus($due) |
1196 | { |
1197 | $dueRelative = $due - time(); |
1198 | if ($dueRelative < 0) { |
1199 | return 'overdue'; |
1200 | } elseif ($dueRelative < 24 * 60 * 60) { |
1201 | return 'due'; |
1202 | } |
1203 | return false; |
1204 | } |
1205 | |
1206 | /** |
1207 | * Construct a random set of transactions for getMyTransactions(). |
1208 | * |
1209 | * @return array |
1210 | */ |
1211 | protected function getRandomTransactionList() |
1212 | { |
1213 | // How many items are there? %10 - 1 = 10% chance of none, |
1214 | // 90% of 1-9 (give or take some odd maths) |
1215 | $trans = rand() % 10 - 1; |
1216 | |
1217 | $transList = []; |
1218 | for ($i = 0; $i < $trans; $i++) { |
1219 | // When is it due? +/- up to 15 days |
1220 | $due_relative = rand() % 30 - 15; |
1221 | // Due date |
1222 | $rawDueDate = strtotime( |
1223 | 'now ' . ($due_relative >= 0 ? '+' : '') . $due_relative . ' days' |
1224 | ); |
1225 | |
1226 | // Times renewed : 0,0,0,0,0,1,2,3,4,5 |
1227 | $renew = rand() % 10 - 5; |
1228 | if ($renew < 0) { |
1229 | $renew = 0; |
1230 | } |
1231 | |
1232 | // Renewal limit |
1233 | $renewLimit = $renew + rand() % 3; |
1234 | |
1235 | // Pending requests : 0,0,0,0,0,1,2,3,4,5 |
1236 | $req = rand() % 10 - 5; |
1237 | if ($req < 0) { |
1238 | $req = 0; |
1239 | } |
1240 | |
1241 | // Create a generic transaction: |
1242 | $transList[] = $this->getRandomItemIdentifier() + [ |
1243 | // maintain separate display vs. raw due dates (the raw |
1244 | // one is used for renewals, in case the user display |
1245 | // format is incompatible with date math). |
1246 | 'duedate' => $this->dateConverter->convertToDisplayDate( |
1247 | 'U', |
1248 | $rawDueDate |
1249 | ), |
1250 | 'rawduedate' => $rawDueDate, |
1251 | 'dueStatus' => $this->calculateDueStatus($rawDueDate), |
1252 | 'barcode' => sprintf('%08d', rand() % 50000), |
1253 | 'renew' => $renew, |
1254 | 'renewLimit' => $renewLimit, |
1255 | 'request' => $req, |
1256 | 'item_id' => $i, |
1257 | 'renewable' => $renew < $renewLimit, |
1258 | ]; |
1259 | if ($i == 2 || rand() % 5 == 1) { |
1260 | // Mimic an ILL loan |
1261 | $transList[$i] += [ |
1262 | 'id' => "ill_institution_$i", |
1263 | 'title' => "ILL Loan Title $i", |
1264 | 'institution_id' => 'ill_institution', |
1265 | 'institution_name' => 'ILL Library', |
1266 | 'institution_dbkey' => 'ill_institution', |
1267 | 'borrowingLocation' => 'ILL Service Desk', |
1268 | ]; |
1269 | } else { |
1270 | $transList[$i]['borrowingLocation'] = $this->getFakeLoc(); |
1271 | if ($this->idsInMyResearch) { |
1272 | [$transList[$i]['id'], $transList[$i]['title']] |
1273 | = $this->getRandomBibIdAndTitle(); |
1274 | $transList[$i]['source'] = $this->getRecordSource(); |
1275 | } else { |
1276 | $transList[$i]['title'] = 'Demo Title ' . $i; |
1277 | } |
1278 | } |
1279 | } |
1280 | return $transList; |
1281 | } |
1282 | |
1283 | /** |
1284 | * Get Patron Transactions |
1285 | * |
1286 | * This is responsible for retrieving all transactions (i.e. checked out items) |
1287 | * by a specific patron. |
1288 | * |
1289 | * @param array $patron The patron array from patronLogin |
1290 | * @param array $params Parameters |
1291 | * |
1292 | * @return mixed Array of the patron's transactions on success. |
1293 | * |
1294 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) |
1295 | */ |
1296 | public function getMyTransactions($patron, $params = []) |
1297 | { |
1298 | $this->checkIntermittentFailure(); |
1299 | $session = $this->getSession($patron['id'] ?? null); |
1300 | if (!isset($session->transactions)) { |
1301 | $session->transactions = $this->getTransactionList(); |
1302 | } |
1303 | // Order |
1304 | $transactions = $session->transactions; |
1305 | if (!empty($params['sort'])) { |
1306 | $sort = explode( |
1307 | ' ', |
1308 | !empty($params['sort']) ? $params['sort'] : 'date_due desc', |
1309 | 2 |
1310 | ); |
1311 | |
1312 | $descending = isset($sort[1]) && 'desc' === $sort[1]; |
1313 | |
1314 | usort( |
1315 | $transactions, |
1316 | function ($a, $b) use ($sort, $descending) { |
1317 | if ('title' === $sort[0]) { |
1318 | $cmp = $this->getSorter()->compare( |
1319 | $a['title'] ?? '', |
1320 | $b['title'] ?? '' |
1321 | ); |
1322 | } else { |
1323 | $cmp = $a['rawduedate'] - $b['rawduedate']; |
1324 | } |
1325 | return $descending ? -$cmp : $cmp; |
1326 | } |
1327 | ); |
1328 | } |
1329 | |
1330 | if (isset($params['limit'])) { |
1331 | $limit = $params['limit'] ?? 50; |
1332 | $offset = isset($params['page']) ? ($params['page'] - 1) * $limit : 0; |
1333 | $transactions = array_slice($transactions, $offset, $limit); |
1334 | } |
1335 | |
1336 | return [ |
1337 | 'count' => count($session->transactions), |
1338 | 'records' => $transactions, |
1339 | ]; |
1340 | } |
1341 | |
1342 | /** |
1343 | * Construct a historic transaction list for getMyTransactionHistory; may be |
1344 | * random or pre-set depending on Demo.ini settings. |
1345 | * |
1346 | * @return array |
1347 | */ |
1348 | protected function getHistoricTransactionList() |
1349 | { |
1350 | $this->checkIntermittentFailure(); |
1351 | // If Demo.ini includes a fixed set of transactions, load those; otherwise |
1352 | // build some random ones. |
1353 | return isset($this->config['Records']['historicTransactions']) |
1354 | ? json_decode($this->config['Records']['historicTransactions'], true) |
1355 | : $this->getRandomHistoricTransactionList(); |
1356 | } |
1357 | |
1358 | /** |
1359 | * Construct a random set of transactions for getMyTransactionHistory(). |
1360 | * |
1361 | * @return array |
1362 | */ |
1363 | protected function getRandomHistoricTransactionList() |
1364 | { |
1365 | // How many items are there? %10 - 1 = 10% chance of none, |
1366 | // 90% of 1-150 (give or take some odd maths) |
1367 | $trans = rand() % 10 - 1 > 0 ? rand() % 15 : 0; |
1368 | |
1369 | $transList = []; |
1370 | for ($i = 0; $i < $trans; $i++) { |
1371 | // Checkout date |
1372 | $relative = rand() % 300; |
1373 | $checkoutDate = strtotime("now -$relative days"); |
1374 | // Due date (7-30 days from checkout) |
1375 | $dueDate = $checkoutDate + 60 * 60 * 24 * (rand() % 23 + 7); |
1376 | // Return date (1-40 days from checkout and < now) |
1377 | $returnDate = min( |
1378 | [$checkoutDate + 60 * 60 * 24 * (rand() % 39 + 1), time()] |
1379 | ); |
1380 | |
1381 | // Create a generic transaction: |
1382 | $transList[] = $this->getRandomItemIdentifier() + [ |
1383 | 'checkoutDate' => $this->dateConverter->convertToDisplayDate( |
1384 | 'U', |
1385 | $checkoutDate |
1386 | ), |
1387 | 'dueDate' => $this->dateConverter->convertToDisplayDate( |
1388 | 'U', |
1389 | $dueDate |
1390 | ), |
1391 | 'returnDate' => $this->dateConverter->convertToDisplayDate( |
1392 | 'U', |
1393 | $returnDate |
1394 | ), |
1395 | // Raw dates for sorting |
1396 | '_checkoutDate' => $checkoutDate, |
1397 | '_dueDate' => $dueDate, |
1398 | '_returnDate' => $returnDate, |
1399 | 'barcode' => sprintf('%08d', rand() % 50000), |
1400 | 'row_id' => $i, |
1401 | ]; |
1402 | if ($this->idsInMyResearch) { |
1403 | [$transList[$i]['id'], $transList[$i]['title']] |
1404 | = $this->getRandomBibIdAndTitle(); |
1405 | $transList[$i]['source'] = $this->getRecordSource(); |
1406 | } else { |
1407 | $transList[$i]['title'] = 'Demo Title ' . $i; |
1408 | } |
1409 | } |
1410 | return $transList; |
1411 | } |
1412 | |
1413 | /** |
1414 | * Get Patron Loan History |
1415 | * |
1416 | * This is responsible for retrieving all historic transactions for a specific |
1417 | * patron. |
1418 | * |
1419 | * @param array $patron The patron array from patronLogin |
1420 | * @param array $params Parameters |
1421 | * |
1422 | * @return mixed Array of the patron's historic transactions on success. |
1423 | * |
1424 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) |
1425 | */ |
1426 | public function getMyTransactionHistory($patron, $params) |
1427 | { |
1428 | $this->checkIntermittentFailure(); |
1429 | $session = $this->getSession($patron['id'] ?? null); |
1430 | if (!isset($session->historicLoans)) { |
1431 | $session->historicLoans = $this->getHistoricTransactionList(); |
1432 | } |
1433 | |
1434 | // Sort and splice the list |
1435 | $historicLoans = $session->historicLoans; |
1436 | if (isset($params['sort'])) { |
1437 | switch ($params['sort']) { |
1438 | case 'checkout asc': |
1439 | $sorter = function ($a, $b) { |
1440 | return strcmp($a['_checkoutDate'], $b['_checkoutDate']); |
1441 | }; |
1442 | break; |
1443 | case 'return desc': |
1444 | $sorter = function ($a, $b) { |
1445 | return strcmp($b['_returnDate'], $a['_returnDate']); |
1446 | }; |
1447 | break; |
1448 | case 'return asc': |
1449 | $sorter = function ($a, $b) { |
1450 | return strcmp($a['_returnDate'], $b['_returnDate']); |
1451 | }; |
1452 | break; |
1453 | case 'due desc': |
1454 | $sorter = function ($a, $b) { |
1455 | return strcmp($b['_dueDate'], $a['_dueDate']); |
1456 | }; |
1457 | break; |
1458 | case 'due asc': |
1459 | $sorter = function ($a, $b) { |
1460 | return strcmp($a['_dueDate'], $b['_dueDate']); |
1461 | }; |
1462 | break; |
1463 | default: |
1464 | $sorter = function ($a, $b) { |
1465 | return strcmp($b['_checkoutDate'], $a['_checkoutDate']); |
1466 | }; |
1467 | break; |
1468 | } |
1469 | |
1470 | usort($historicLoans, $sorter); |
1471 | } |
1472 | |
1473 | $limit = isset($params['limit']) ? (int)$params['limit'] : 50; |
1474 | $start = isset($params['page']) |
1475 | ? ((int)$params['page'] - 1) * $limit : 0; |
1476 | |
1477 | $historicLoans = array_splice($historicLoans, $start, $limit); |
1478 | |
1479 | return [ |
1480 | 'count' => count($session->historicLoans), |
1481 | 'transactions' => $historicLoans, |
1482 | ]; |
1483 | } |
1484 | |
1485 | /** |
1486 | * Purge Patron Transaction History |
1487 | * |
1488 | * @param array $patron The patron array from patronLogin |
1489 | * @param ?array $ids IDs to purge, or null for all |
1490 | * |
1491 | * @throws ILSException |
1492 | * @return array Associative array of the results |
1493 | */ |
1494 | public function purgeTransactionHistory(array $patron, ?array $ids): array |
1495 | { |
1496 | $this->checkIntermittentFailure(); |
1497 | $session = $this->getSession($patron['id'] ?? null); |
1498 | if (null === $ids) { |
1499 | $session->historicLoans = []; |
1500 | $status = 'loan_history_all_purged'; |
1501 | } else { |
1502 | $session->historicLoans = array_filter( |
1503 | $session->historicLoans ?? [], |
1504 | function ($loan) use ($ids) { |
1505 | return !in_array($loan['row_id'], $ids); |
1506 | } |
1507 | ); |
1508 | $status = 'loan_history_selected_purged'; |
1509 | } |
1510 | return [ |
1511 | 'success' => true, |
1512 | 'status' => $status, |
1513 | 'sys_message' => '', |
1514 | ]; |
1515 | } |
1516 | |
1517 | /** |
1518 | * Get Pick Up Locations |
1519 | * |
1520 | * This is responsible get a list of valid library locations for holds / recall |
1521 | * retrieval |
1522 | * |
1523 | * @param array $patron Patron information returned by the patronLogin |
1524 | * method. |
1525 | * @param array $holdDetails Optional array, only passed in when getting a list |
1526 | * in the context of placing or editing a hold. When placing a hold, it contains |
1527 | * most of the same values passed to placeHold, minus the patron data. When |
1528 | * editing a hold it contains all the hold information returned by getMyHolds. |
1529 | * May be used to limit the pickup options or may be ignored. The driver must |
1530 | * not add new options to the return array based on this data or other areas of |
1531 | * VuFind may behave incorrectly. |
1532 | * |
1533 | * @return array An array of associative arrays with locationID and |
1534 | * locationDisplay keys |
1535 | * |
1536 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) |
1537 | */ |
1538 | public function getPickUpLocations($patron = false, $holdDetails = null) |
1539 | { |
1540 | $this->checkIntermittentFailure(); |
1541 | $result = $this->locations; |
1542 | if (($holdDetails['reqnum'] ?? '') == 1) { |
1543 | $result[] = [ |
1544 | 'locationID' => 'D', |
1545 | 'locationDisplay' => 'Campus D', |
1546 | ]; |
1547 | } |
1548 | |
1549 | if (isset($this->config['Holds']['excludePickupLocations'])) { |
1550 | $excluded |
1551 | = explode(':', $this->config['Holds']['excludePickupLocations']); |
1552 | $result = array_filter( |
1553 | $result, |
1554 | function ($loc) use ($excluded) { |
1555 | return !in_array($loc['locationID'], $excluded); |
1556 | } |
1557 | ); |
1558 | } |
1559 | |
1560 | return $result; |
1561 | } |
1562 | |
1563 | /** |
1564 | * Get Default "Hold Required By" Date (as Unix timestamp) or null if unsupported |
1565 | * |
1566 | * @param array $patron Patron information returned by the patronLogin method. |
1567 | * @param array $holdInfo Contains most of the same values passed to |
1568 | * placeHold, minus the patron data. |
1569 | * |
1570 | * @return int|null |
1571 | * |
1572 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) |
1573 | */ |
1574 | public function getHoldDefaultRequiredDate($patron, $holdInfo) |
1575 | { |
1576 | $this->checkIntermittentFailure(); |
1577 | // 5 years in the future (but similate intermittent failure): |
1578 | return !$this->isFailing(__METHOD__, 50) |
1579 | ? mktime(0, 0, 0, date('m'), date('d'), date('Y') + 5) : null; |
1580 | } |
1581 | |
1582 | /** |
1583 | * Get Default Pick Up Location |
1584 | * |
1585 | * Returns the default pick up location set in HorizonXMLAPI.ini |
1586 | * |
1587 | * @param array $patron Patron information returned by the patronLogin |
1588 | * method. |
1589 | * @param array $holdDetails Optional array, only passed in when getting a list |
1590 | * in the context of placing a hold; contains most of the same values passed to |
1591 | * placeHold, minus the patron data. May be used to limit the pickup options |
1592 | * or may be ignored. |
1593 | * |
1594 | * @return false|string The default pickup location for the patron or false |
1595 | * if the user has to choose. |
1596 | * |
1597 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) |
1598 | */ |
1599 | public function getDefaultPickUpLocation($patron = false, $holdDetails = null) |
1600 | { |
1601 | $this->checkIntermittentFailure(); |
1602 | return $this->defaultPickUpLocation; |
1603 | } |
1604 | |
1605 | /** |
1606 | * Get Default Request Group |
1607 | * |
1608 | * Returns the default request group |
1609 | * |
1610 | * @param array $patron Patron information returned by the patronLogin |
1611 | * method. |
1612 | * @param array $holdDetails Optional array, only passed in when getting a list |
1613 | * in the context of placing a hold; contains most of the same values passed to |
1614 | * placeHold, minus the patron data. May be used to limit the request group |
1615 | * options or may be ignored. |
1616 | * |
1617 | * @return false|string The default request group for the patron. |
1618 | * |
1619 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) |
1620 | */ |
1621 | public function getDefaultRequestGroup($patron = false, $holdDetails = null) |
1622 | { |
1623 | $this->checkIntermittentFailure(); |
1624 | if ($this->isFailing(__METHOD__, 50)) { |
1625 | return false; |
1626 | } |
1627 | $requestGroups = $this->getRequestGroups(0, 0); |
1628 | return $requestGroups[0]['id']; |
1629 | } |
1630 | |
1631 | /** |
1632 | * Get request groups |
1633 | * |
1634 | * @param int $bibId BIB ID |
1635 | * @param array $patron Patron information returned by the patronLogin |
1636 | * method. |
1637 | * @param array $holdDetails Optional array, only passed in when getting a list |
1638 | * in the context of placing a hold; contains most of the same values passed to |
1639 | * placeHold, minus the patron data. May be used to limit the request group |
1640 | * options or may be ignored. |
1641 | * |
1642 | * @return array False if request groups not in use or an array of |
1643 | * associative arrays with id and name keys |
1644 | * |
1645 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) |
1646 | */ |
1647 | public function getRequestGroups( |
1648 | $bibId = null, |
1649 | $patron = null, |
1650 | $holdDetails = null |
1651 | ) { |
1652 | $this->checkIntermittentFailure(); |
1653 | return [ |
1654 | [ |
1655 | 'id' => 1, |
1656 | 'name' => 'Main Library', |
1657 | ], |
1658 | [ |
1659 | 'id' => 2, |
1660 | 'name' => 'Branch Library', |
1661 | ], |
1662 | ]; |
1663 | } |
1664 | |
1665 | /** |
1666 | * Get Funds |
1667 | * |
1668 | * Return a list of funds which may be used to limit the getNewItems list. |
1669 | * |
1670 | * @return array An associative array with key = fund ID, value = fund name. |
1671 | */ |
1672 | public function getFunds() |
1673 | { |
1674 | $this->checkIntermittentFailure(); |
1675 | return ['Fund A', 'Fund B', 'Fund C']; |
1676 | } |
1677 | |
1678 | /** |
1679 | * Get Departments |
1680 | * |
1681 | * Obtain a list of departments for use in limiting the reserves list. |
1682 | * |
1683 | * @return array An associative array with key = dept. ID, value = dept. name. |
1684 | */ |
1685 | public function getDepartments() |
1686 | { |
1687 | $this->checkIntermittentFailure(); |
1688 | return $this->departments; |
1689 | } |
1690 | |
1691 | /** |
1692 | * Get Instructors |
1693 | * |
1694 | * Obtain a list of instructors for use in limiting the reserves list. |
1695 | * |
1696 | * @return array An associative array with key = ID, value = name. |
1697 | */ |
1698 | public function getInstructors() |
1699 | { |
1700 | $this->checkIntermittentFailure(); |
1701 | return $this->instructors; |
1702 | } |
1703 | |
1704 | /** |
1705 | * Get Courses |
1706 | * |
1707 | * Obtain a list of courses for use in limiting the reserves list. |
1708 | * |
1709 | * @return array An associative array with key = ID, value = name. |
1710 | */ |
1711 | public function getCourses() |
1712 | { |
1713 | $this->checkIntermittentFailure(); |
1714 | return $this->courses; |
1715 | } |
1716 | |
1717 | /** |
1718 | * Get a set of random bib IDs |
1719 | * |
1720 | * @param int $limit Maximum number of IDs to return (max 30) |
1721 | * |
1722 | * @return string[] |
1723 | */ |
1724 | protected function getRandomBibIds($limit): array |
1725 | { |
1726 | $count = rand(0, $limit > 30 ? 30 : $limit); |
1727 | $results = []; |
1728 | for ($x = 0; $x < $count; $x++) { |
1729 | $randomId = $this->getRandomBibId(); |
1730 | |
1731 | // avoid duplicate entries in array: |
1732 | if (!in_array($randomId, $results)) { |
1733 | $results[] = $randomId; |
1734 | } |
1735 | } |
1736 | return $results; |
1737 | } |
1738 | |
1739 | /** |
1740 | * Get New Items |
1741 | * |
1742 | * Retrieve the IDs of items recently added to the catalog. |
1743 | * |
1744 | * @param int $page Page number of results to retrieve (counting starts at 1) |
1745 | * @param int $limit The size of each page of results to retrieve |
1746 | * @param int $daysOld The maximum age of records to retrieve in days (max. 30) |
1747 | * @param int $fundId optional fund ID to use for limiting results (use a value |
1748 | * returned by getFunds, or exclude for no limit); note that "fund" may be a |
1749 | * misnomer - if funds are not an appropriate way to limit your new item |
1750 | * results, you can return a different set of values from getFunds. The |
1751 | * important thing is that this parameter supports an ID returned by getFunds, |
1752 | * whatever that may mean. |
1753 | * |
1754 | * @return array Associative array with 'count' and 'results' keys |
1755 | * |
1756 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) |
1757 | */ |
1758 | public function getNewItems($page, $limit, $daysOld, $fundId = null) |
1759 | { |
1760 | $this->checkIntermittentFailure(); |
1761 | // Pick a random number of results to return -- don't exceed limit or 30, |
1762 | // whichever is smaller (this can be pretty slow due to the random ID code). |
1763 | $results = $this->config['Records']['new_items'] |
1764 | ?? $this->getRandomBibIds(30); |
1765 | $retVal = ['count' => count($results), 'results' => []]; |
1766 | foreach ($results as $result) { |
1767 | $retVal['results'][] = ['id' => $result]; |
1768 | } |
1769 | return $retVal; |
1770 | } |
1771 | |
1772 | /** |
1773 | * Determine a course ID for findReserves. |
1774 | * |
1775 | * @param string $course Course ID (or empty for a random choice) |
1776 | * |
1777 | * @return string |
1778 | */ |
1779 | protected function getCourseId(string $course = ''): string |
1780 | { |
1781 | return empty($course) ? (string)rand(0, count($this->courses) - 1) : $course; |
1782 | } |
1783 | |
1784 | /** |
1785 | * Determine a department ID for findReserves. |
1786 | * |
1787 | * @param string $dept Department ID (or empty for a random choice) |
1788 | * |
1789 | * @return string |
1790 | */ |
1791 | protected function getDepartmentId(string $dept = ''): string |
1792 | { |
1793 | return empty($dept) ? (string)rand(0, count($this->departments) - 1) : $dept; |
1794 | } |
1795 | |
1796 | /** |
1797 | * Determine an instructor ID for findReserves. |
1798 | * |
1799 | * @param string $inst Instructor ID (or empty for a random choice) |
1800 | * |
1801 | * @return string |
1802 | */ |
1803 | protected function getInstructorId(string $inst = ''): string |
1804 | { |
1805 | return empty($inst) ? (string)rand(0, count($this->instructors) - 1) : $inst; |
1806 | } |
1807 | |
1808 | /** |
1809 | * Find Reserves |
1810 | * |
1811 | * Obtain information on course reserves. |
1812 | * |
1813 | * @param string $course ID from getCourses (empty string to match all) |
1814 | * @param string $inst ID from getInstructors (empty string to match all) |
1815 | * @param string $dept ID from getDepartments (empty string to match all) |
1816 | * |
1817 | * @return mixed An array of associative arrays representing reserve items. |
1818 | * |
1819 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) |
1820 | */ |
1821 | public function findReserves($course, $inst, $dept) |
1822 | { |
1823 | $this->checkIntermittentFailure(); |
1824 | // Pick a random number of results to return -- don't exceed 30. |
1825 | $count = rand(0, 30); |
1826 | $results = []; |
1827 | for ($x = 0; $x < $count; $x++) { |
1828 | $randomId = $this->getRandomBibId(); |
1829 | |
1830 | // avoid duplicate entries in array: |
1831 | if (!in_array($randomId, $results)) { |
1832 | $results[] = $randomId; |
1833 | } |
1834 | } |
1835 | |
1836 | $retVal = []; |
1837 | foreach ($results as $current) { |
1838 | $retVal[] = [ |
1839 | 'BIB_ID' => $current, |
1840 | 'INSTRUCTOR_ID' => $this->getInstructorId($inst), |
1841 | 'COURSE_ID' => $this->getCourseId($course), |
1842 | 'DEPARTMENT_ID' => $this->getDepartmentId($dept), |
1843 | ]; |
1844 | } |
1845 | return $retVal; |
1846 | } |
1847 | |
1848 | /** |
1849 | * Cancel Holds |
1850 | * |
1851 | * Attempts to Cancel a hold or recall on a particular item. |
1852 | * |
1853 | * @param array $cancelDetails An array of item and patron data |
1854 | * |
1855 | * @return array An array of data on each request including |
1856 | * whether or not it was successful and a system message (if available) |
1857 | */ |
1858 | public function cancelHolds($cancelDetails) |
1859 | { |
1860 | $this->checkIntermittentFailure(); |
1861 | // Rewrite the holds in the session, removing those the user wants to |
1862 | // cancel. |
1863 | $newHolds = new ArrayObject(); |
1864 | $retVal = ['count' => 0, 'items' => []]; |
1865 | $session = $this->getSession($cancelDetails['patron']['id'] ?? null); |
1866 | foreach ($session->holds as $current) { |
1867 | if (!in_array($current['reqnum'], $cancelDetails['details'])) { |
1868 | $newHolds->append($current); |
1869 | } else { |
1870 | if (!$this->isFailing(__METHOD__, 50)) { |
1871 | $retVal['count']++; |
1872 | $retVal['items'][$current['item_id']] = [ |
1873 | 'success' => true, |
1874 | 'status' => 'hold_cancel_success', |
1875 | ]; |
1876 | } else { |
1877 | $newHolds->append($current); |
1878 | $retVal['items'][$current['item_id']] = [ |
1879 | 'success' => false, |
1880 | 'status' => 'hold_cancel_fail', |
1881 | 'sysMessage' => |
1882 | 'Demonstrating failure; keep trying and ' . |
1883 | 'it will work eventually.', |
1884 | ]; |
1885 | } |
1886 | } |
1887 | } |
1888 | |
1889 | $session->holds = $newHolds; |
1890 | return $retVal; |
1891 | } |
1892 | |
1893 | /** |
1894 | * Update holds |
1895 | * |
1896 | * This is responsible for changing the status of hold requests |
1897 | * |
1898 | * @param array $holdsDetails The details identifying the holds |
1899 | * @param array $fields An associative array of fields to be updated |
1900 | * @param array $patron Patron array |
1901 | * |
1902 | * @return array Associative array of the results |
1903 | */ |
1904 | public function updateHolds( |
1905 | array $holdsDetails, |
1906 | array $fields, |
1907 | array $patron |
1908 | ): array { |
1909 | $results = []; |
1910 | $session = $this->getSession($patron['id']); |
1911 | foreach ($session->holds as &$currentHold) { |
1912 | if ( |
1913 | !isset($currentHold['updateDetails']) |
1914 | || !in_array($currentHold['updateDetails'], $holdsDetails) |
1915 | ) { |
1916 | continue; |
1917 | } |
1918 | if ($this->isFailing(__METHOD__, 25)) { |
1919 | $results[$currentHold['reqnum']]['success'] = false; |
1920 | $results[$currentHold['reqnum']]['status'] |
1921 | = 'Simulated error; try again and it will work eventually.'; |
1922 | continue; |
1923 | } |
1924 | if (array_key_exists('frozen', $fields)) { |
1925 | if ($fields['frozen']) { |
1926 | $currentHold['frozen'] = true; |
1927 | if (isset($fields['frozenThrough'])) { |
1928 | $currentHold['frozenThrough'] = $this->dateConverter |
1929 | ->convertToDisplayDate('U', $fields['frozenThroughTS']); |
1930 | } else { |
1931 | $currentHold['frozenThrough'] = ''; |
1932 | } |
1933 | } else { |
1934 | $currentHold['frozen'] = false; |
1935 | $currentHold['frozenThrough'] = ''; |
1936 | } |
1937 | } |
1938 | if (isset($fields['pickUpLocation'])) { |
1939 | $currentHold['location'] = $fields['pickUpLocation']; |
1940 | } |
1941 | $results[$currentHold['reqnum']]['success'] = true; |
1942 | } |
1943 | |
1944 | return $results; |
1945 | } |
1946 | |
1947 | /** |
1948 | * Cancel Storage Retrieval Request |
1949 | * |
1950 | * Attempts to Cancel a Storage Retrieval Request on a particular item. The |
1951 | * data in $cancelDetails['details'] is determined by |
1952 | * getCancelStorageRetrievalRequestDetails(). |
1953 | * |
1954 | * @param array $cancelDetails An array of item and patron data |
1955 | * |
1956 | * @return array An array of data on each request including |
1957 | * whether or not it was successful and a system message (if available) |
1958 | */ |
1959 | public function cancelStorageRetrievalRequests($cancelDetails) |
1960 | { |
1961 | $this->checkIntermittentFailure(); |
1962 | // Rewrite the items in the session, removing those the user wants to |
1963 | // cancel. |
1964 | $newRequests = new ArrayObject(); |
1965 | $retVal = ['count' => 0, 'items' => []]; |
1966 | $session = $this->getSession($cancelDetails['patron']['id'] ?? null); |
1967 | foreach ($session->storageRetrievalRequests as $current) { |
1968 | if (!in_array($current['reqnum'], $cancelDetails['details'])) { |
1969 | $newRequests->append($current); |
1970 | } else { |
1971 | if (!$this->isFailing(__METHOD__, 50)) { |
1972 | $retVal['count']++; |
1973 | $retVal['items'][$current['item_id']] = [ |
1974 | 'success' => true, |
1975 | 'status' => 'storage_retrieval_request_cancel_success', |
1976 | ]; |
1977 | } else { |
1978 | $newRequests->append($current); |
1979 | $retVal['items'][$current['item_id']] = [ |
1980 | 'success' => false, |
1981 | 'status' => 'storage_retrieval_request_cancel_fail', |
1982 | 'sysMessage' => |
1983 | 'Demonstrating failure; keep trying and ' . |
1984 | 'it will work eventually.', |
1985 | ]; |
1986 | } |
1987 | } |
1988 | } |
1989 | |
1990 | $session->storageRetrievalRequests = $newRequests; |
1991 | return $retVal; |
1992 | } |
1993 | |
1994 | /** |
1995 | * Get Cancel Storage Retrieval Request Details |
1996 | * |
1997 | * In order to cancel a hold, Voyager requires the patron details an item ID |
1998 | * and a recall ID. This function returns the item id and recall id as a string |
1999 | * separated by a pipe, which is then submitted as form data in Hold.php. This |
2000 | * value is then extracted by the CancelHolds function. |
2001 | * |
2002 | * @param array $request An array of request data |
2003 | * @param array $patron Patron information from patronLogin |
2004 | * |
2005 | * @return string Data for use in a form field |
2006 | * |
2007 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) |
2008 | */ |
2009 | public function getCancelStorageRetrievalRequestDetails($request, $patron) |
2010 | { |
2011 | return $request['reqnum']; |
2012 | } |
2013 | |
2014 | /** |
2015 | * Renew My Items |
2016 | * |
2017 | * Function for attempting to renew a patron's items. The data in |
2018 | * $renewDetails['details'] is determined by getRenewDetails(). |
2019 | * |
2020 | * @param array $renewDetails An array of data required for renewing items |
2021 | * including the Patron ID and an array of renewal IDS |
2022 | * |
2023 | * @return array An array of renewal information keyed by item ID |
2024 | */ |
2025 | public function renewMyItems($renewDetails) |
2026 | { |
2027 | $this->checkIntermittentFailure(); |
2028 | // Simulate an account block at random. |
2029 | if ($this->checkRenewBlock()) { |
2030 | return [ |
2031 | 'blocks' => [ |
2032 | 'Simulated account block; try again and it will work eventually.', |
2033 | ], |
2034 | 'details' => [], |
2035 | ]; |
2036 | } |
2037 | |
2038 | // Set up successful return value. |
2039 | $finalResult = ['blocks' => false, 'details' => []]; |
2040 | |
2041 | // Grab transactions from session so we can modify them: |
2042 | $session = $this->getSession($renewDetails['patron']['id'] ?? null); |
2043 | $transactions = $session->transactions; |
2044 | foreach ($transactions as $i => $current) { |
2045 | // Only renew requested items: |
2046 | if (in_array($current['item_id'], $renewDetails['details'])) { |
2047 | if (!$this->isFailing(__METHOD__, 50)) { |
2048 | $transactions[$i]['rawduedate'] += 21 * 24 * 60 * 60; |
2049 | $transactions[$i]['dueStatus'] |
2050 | = $this->calculateDueStatus($transactions[$i]['rawduedate']); |
2051 | $transactions[$i]['duedate'] |
2052 | = $this->dateConverter->convertToDisplayDate( |
2053 | 'U', |
2054 | $transactions[$i]['rawduedate'] |
2055 | ); |
2056 | $transactions[$i]['renew'] = $transactions[$i]['renew'] + 1; |
2057 | $transactions[$i]['renewable'] |
2058 | = $transactions[$i]['renew'] |
2059 | < $transactions[$i]['renewLimit']; |
2060 | |
2061 | $finalResult['details'][$current['item_id']] = [ |
2062 | 'success' => true, |
2063 | 'new_date' => $transactions[$i]['duedate'], |
2064 | 'new_time' => '', |
2065 | 'item_id' => $current['item_id'], |
2066 | ]; |
2067 | } else { |
2068 | $finalResult['details'][$current['item_id']] = [ |
2069 | 'success' => false, |
2070 | 'new_date' => false, |
2071 | 'item_id' => $current['item_id'], |
2072 | 'sysMessage' => |
2073 | 'Demonstrating failure; keep trying and ' . |
2074 | 'it will work eventually.', |
2075 | ]; |
2076 | } |
2077 | } |
2078 | } |
2079 | |
2080 | // Write modified transactions back to session; in-place changes do not |
2081 | // work due to ArrayObject eccentricities: |
2082 | $session->transactions = $transactions; |
2083 | |
2084 | return $finalResult; |
2085 | } |
2086 | |
2087 | /** |
2088 | * Get Renew Details |
2089 | * |
2090 | * In order to renew an item, Voyager requires the patron details and an item |
2091 | * id. This function returns the item id as a string which is then used |
2092 | * as submitted form data in checkedOut.php. This value is then extracted by |
2093 | * the RenewMyItems function. |
2094 | * |
2095 | * @param array $checkOutDetails An array of item data |
2096 | * |
2097 | * @return string Data for use in a form field |
2098 | */ |
2099 | public function getRenewDetails($checkOutDetails) |
2100 | { |
2101 | return $checkOutDetails['item_id']; |
2102 | } |
2103 | |
2104 | /** |
2105 | * Check if hold or recall available |
2106 | * |
2107 | * This is responsible for determining if an item is requestable |
2108 | * |
2109 | * @param string $id The Bib ID |
2110 | * @param array $data An Array of item data |
2111 | * @param array $patron An array of patron data |
2112 | * |
2113 | * @return mixed An array of data on the request including |
2114 | * whether or not it is valid and a status message. Alternatively a boolean |
2115 | * true if request is valid, false if not. |
2116 | * |
2117 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) |
2118 | */ |
2119 | public function checkRequestIsValid($id, $data, $patron) |
2120 | { |
2121 | $this->checkIntermittentFailure(); |
2122 | if ($this->isFailing(__METHOD__, 10)) { |
2123 | return [ |
2124 | 'valid' => false, |
2125 | 'status' => rand() % 3 != 0 |
2126 | ? 'hold_error_blocked' : 'Demonstrating a custom failure', |
2127 | ]; |
2128 | } |
2129 | return [ |
2130 | 'valid' => true, |
2131 | 'status' => 'request_place_text', |
2132 | ]; |
2133 | } |
2134 | |
2135 | /** |
2136 | * Place Hold |
2137 | * |
2138 | * Attempts to place a hold or recall on a particular item and returns |
2139 | * an array with result details. |
2140 | * |
2141 | * @param array $holdDetails An array of item and patron data |
2142 | * |
2143 | * @return mixed An array of data on the request including |
2144 | * whether or not it was successful and a system message (if available) |
2145 | */ |
2146 | public function placeHold($holdDetails) |
2147 | { |
2148 | $this->checkIntermittentFailure(); |
2149 | // Simulate failure: |
2150 | if ($this->isFailing(__METHOD__, 50)) { |
2151 | return [ |
2152 | 'success' => false, |
2153 | 'sysMessage' => |
2154 | 'Demonstrating failure; keep trying and ' . |
2155 | 'it will work eventually.', |
2156 | ]; |
2157 | } |
2158 | |
2159 | $session = $this->getSession($holdDetails['patron']['id'] ?? null); |
2160 | if (!isset($session->holds)) { |
2161 | $session->holds = new ArrayObject(); |
2162 | } |
2163 | $lastHold = count($session->holds) - 1; |
2164 | $nextId = $lastHold >= 0 |
2165 | ? $session->holds[$lastHold]['item_id'] + 1 : 0; |
2166 | |
2167 | // Figure out appropriate expiration date: |
2168 | $expire = !empty($holdDetails['requiredByTS']) |
2169 | ? $this->dateConverter->convertToDisplayDate( |
2170 | 'Y-m-d', |
2171 | gmdate('Y-m-d', $holdDetails['requiredByTS']) |
2172 | ) : null; |
2173 | |
2174 | $requestGroup = ''; |
2175 | foreach ($this->getRequestGroups(null, null) as $group) { |
2176 | if ( |
2177 | isset($holdDetails['requestGroupId']) |
2178 | && $group['id'] == $holdDetails['requestGroupId'] |
2179 | ) { |
2180 | $requestGroup = $group['name']; |
2181 | break; |
2182 | } |
2183 | } |
2184 | if ($holdDetails['startDateTS']) { |
2185 | // Suspend until the previous day: |
2186 | $frozen = true; |
2187 | $frozenThrough = $this->dateConverter->convertToDisplayDate( |
2188 | 'U', |
2189 | \DateTime::createFromFormat( |
2190 | 'U', |
2191 | $holdDetails['startDateTS'] |
2192 | )->modify('-1 DAY')->getTimestamp() |
2193 | ); |
2194 | } else { |
2195 | $frozen = false; |
2196 | $frozenThrough = ''; |
2197 | } |
2198 | $reqNum = sprintf('%06d', $nextId); |
2199 | $proxiedFor = null; |
2200 | if (!empty($holdDetails['proxiedUser'])) { |
2201 | $proxies = $this->getProxiedUsers($holdDetails['patron']); |
2202 | $proxiedFor = $proxies[$holdDetails['proxiedUser']]; |
2203 | } |
2204 | $session->holds->append( |
2205 | [ |
2206 | 'id' => $holdDetails['id'], |
2207 | 'source' => $this->getRecordSource(), |
2208 | 'location' => $holdDetails['pickUpLocation'], |
2209 | 'expire' => $expire, |
2210 | 'create' => |
2211 | $this->dateConverter->convertToDisplayDate('U', time()), |
2212 | 'reqnum' => $reqNum, |
2213 | 'item_id' => $nextId, |
2214 | 'volume' => '', |
2215 | 'processed' => '', |
2216 | 'requestGroup' => $requestGroup, |
2217 | 'frozen' => $frozen, |
2218 | 'frozenThrough' => $frozenThrough, |
2219 | 'updateDetails' => $reqNum, |
2220 | 'cancel_details' => $reqNum, |
2221 | 'proxiedFor' => $proxiedFor, |
2222 | ] |
2223 | ); |
2224 | |
2225 | return ['success' => true]; |
2226 | } |
2227 | |
2228 | /** |
2229 | * Check if storage retrieval request available |
2230 | * |
2231 | * This is responsible for determining if an item is requestable |
2232 | * |
2233 | * @param string $id The Bib ID |
2234 | * @param array $data An Array of item data |
2235 | * @param array $patron An array of patron data |
2236 | * |
2237 | * @return mixed An array of data on the request including |
2238 | * whether or not it is valid and a status message. Alternatively a boolean |
2239 | * true if request is valid, false if not. |
2240 | * |
2241 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) |
2242 | */ |
2243 | public function checkStorageRetrievalRequestIsValid($id, $data, $patron) |
2244 | { |
2245 | $this->checkIntermittentFailure(); |
2246 | if (!$this->storageRetrievalRequests || $this->isFailing(__METHOD__, 10)) { |
2247 | return [ |
2248 | 'valid' => false, |
2249 | 'status' => rand() % 3 != 0 |
2250 | ? 'storage_retrieval_request_error_blocked' |
2251 | : 'Demonstrating a custom failure', |
2252 | ]; |
2253 | } |
2254 | return [ |
2255 | 'valid' => true, |
2256 | 'status' => 'storage_retrieval_request_place_text', |
2257 | ]; |
2258 | } |
2259 | |
2260 | /** |
2261 | * Place a Storage Retrieval Request |
2262 | * |
2263 | * Attempts to place a request on a particular item and returns |
2264 | * an array with result details. |
2265 | * |
2266 | * @param array $details An array of item and patron data |
2267 | * |
2268 | * @return mixed An array of data on the request including |
2269 | * whether or not it was successful and a system message (if available) |
2270 | */ |
2271 | public function placeStorageRetrievalRequest($details) |
2272 | { |
2273 | $this->checkIntermittentFailure(); |
2274 | if (!$this->storageRetrievalRequests) { |
2275 | return [ |
2276 | 'success' => false, |
2277 | 'sysMessage' => 'Storage Retrieval Requests are disabled.', |
2278 | ]; |
2279 | } |
2280 | |
2281 | // Make sure pickup location is valid |
2282 | $pickUpLocation = $details['pickUpLocation'] ?? null; |
2283 | $validLocations = array_column($this->getPickUpLocations(), 'locationID'); |
2284 | if ( |
2285 | null !== $pickUpLocation |
2286 | && !in_array($pickUpLocation, $validLocations) |
2287 | ) { |
2288 | return [ |
2289 | 'success' => false, |
2290 | 'sysMessage' => 'storage_retrieval_request_invalid_pickup', |
2291 | ]; |
2292 | } |
2293 | |
2294 | // Simulate failure: |
2295 | if ($this->isFailing(__METHOD__, 50)) { |
2296 | return [ |
2297 | 'success' => false, |
2298 | 'sysMessage' => |
2299 | 'Demonstrating failure; keep trying and ' . |
2300 | 'it will work eventually.', |
2301 | ]; |
2302 | } |
2303 | |
2304 | $session = $this->getSession($details['patron']['id'] ?? null); |
2305 | if (!isset($session->storageRetrievalRequests)) { |
2306 | $session->storageRetrievalRequests = new ArrayObject(); |
2307 | } |
2308 | $lastRequest = count($session->storageRetrievalRequests) - 1; |
2309 | $nextId = $lastRequest >= 0 |
2310 | ? $session->storageRetrievalRequests[$lastRequest]['item_id'] + 1 |
2311 | : 0; |
2312 | |
2313 | // Figure out appropriate expiration date: |
2314 | if ( |
2315 | !isset($details['requiredBy']) |
2316 | || empty($details['requiredBy']) |
2317 | ) { |
2318 | $expire = strtotime('now + 30 days'); |
2319 | } else { |
2320 | try { |
2321 | $expire = $this->dateConverter->convertFromDisplayDate( |
2322 | 'U', |
2323 | $details['requiredBy'] |
2324 | ); |
2325 | } catch (DateException $e) { |
2326 | // Expiration date is invalid |
2327 | return [ |
2328 | 'success' => false, |
2329 | 'sysMessage' => 'storage_retrieval_request_date_invalid', |
2330 | ]; |
2331 | } |
2332 | } |
2333 | if ($expire <= time()) { |
2334 | return [ |
2335 | 'success' => false, |
2336 | 'sysMessage' => 'storage_retrieval_request_date_past', |
2337 | ]; |
2338 | } |
2339 | |
2340 | $session->storageRetrievalRequests->append( |
2341 | [ |
2342 | 'id' => $details['id'], |
2343 | 'source' => $this->getRecordSource(), |
2344 | 'location' => $details['pickUpLocation'], |
2345 | 'expire' => |
2346 | $this->dateConverter->convertToDisplayDate('U', $expire), |
2347 | 'create' => |
2348 | $this->dateConverter->convertToDisplayDate('U', time()), |
2349 | 'processed' => rand() % 3 == 0 |
2350 | ? $this->dateConverter->convertToDisplayDate('U', $expire) : '', |
2351 | 'reqnum' => sprintf('%06d', $nextId), |
2352 | 'item_id' => $nextId, |
2353 | ] |
2354 | ); |
2355 | |
2356 | return ['success' => true]; |
2357 | } |
2358 | |
2359 | /** |
2360 | * Check if ILL request available |
2361 | * |
2362 | * This is responsible for determining if an item is requestable |
2363 | * |
2364 | * @param string $id The Bib ID |
2365 | * @param array $data An Array of item data |
2366 | * @param array $patron An array of patron data |
2367 | * |
2368 | * @return mixed An array of data on the request including |
2369 | * whether or not it is valid and a status message. Alternatively a boolean |
2370 | * true if request is valid, false if not. |
2371 | * |
2372 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) |
2373 | */ |
2374 | public function checkILLRequestIsValid($id, $data, $patron) |
2375 | { |
2376 | $this->checkIntermittentFailure(); |
2377 | if (!$this->ILLRequests || $this->isFailing(__METHOD__, 10)) { |
2378 | return [ |
2379 | 'valid' => false, |
2380 | 'status' => rand() % 3 != 0 |
2381 | ? 'ill_request_error_blocked' : 'Demonstrating a custom failure', |
2382 | ]; |
2383 | } |
2384 | return [ |
2385 | 'valid' => true, |
2386 | 'status' => 'ill_request_place_text', |
2387 | ]; |
2388 | } |
2389 | |
2390 | /** |
2391 | * Place ILL Request |
2392 | * |
2393 | * Attempts to place an ILL request on a particular item and returns |
2394 | * an array with result details |
2395 | * |
2396 | * @param array $details An array of item and patron data |
2397 | * |
2398 | * @return mixed An array of data on the request including |
2399 | * whether or not it was successful and a system message (if available) |
2400 | */ |
2401 | public function placeILLRequest($details) |
2402 | { |
2403 | $this->checkIntermittentFailure(); |
2404 | if (!$this->ILLRequests) { |
2405 | return [ |
2406 | 'success' => false, |
2407 | 'sysMessage' => 'ILL requests are disabled.', |
2408 | ]; |
2409 | } |
2410 | // Simulate failure: |
2411 | if ($this->isFailing(__METHOD__, 50)) { |
2412 | return [ |
2413 | 'success' => false, |
2414 | 'sysMessage' => |
2415 | 'Demonstrating failure; keep trying and ' . |
2416 | 'it will work eventually.', |
2417 | ]; |
2418 | } |
2419 | |
2420 | $session = $this->getSession($details['patron']['id'] ?? null); |
2421 | if (!isset($session->ILLRequests)) { |
2422 | $session->ILLRequests = new ArrayObject(); |
2423 | } |
2424 | $lastRequest = count($session->ILLRequests) - 1; |
2425 | $nextId = $lastRequest >= 0 |
2426 | ? $session->ILLRequests[$lastRequest]['item_id'] + 1 |
2427 | : 0; |
2428 | |
2429 | // Figure out appropriate expiration date: |
2430 | if ( |
2431 | !isset($details['requiredBy']) |
2432 | || empty($details['requiredBy']) |
2433 | ) { |
2434 | $expire = strtotime('now + 30 days'); |
2435 | } else { |
2436 | try { |
2437 | $expire = $this->dateConverter->convertFromDisplayDate( |
2438 | 'U', |
2439 | $details['requiredBy'] |
2440 | ); |
2441 | } catch (DateException $e) { |
2442 | // Expiration Date is invalid |
2443 | return [ |
2444 | 'success' => false, |
2445 | 'sysMessage' => 'ill_request_date_invalid', |
2446 | ]; |
2447 | } |
2448 | } |
2449 | if ($expire <= time()) { |
2450 | return [ |
2451 | 'success' => false, |
2452 | 'sysMessage' => 'ill_request_date_past', |
2453 | ]; |
2454 | } |
2455 | |
2456 | // Verify pickup library and location |
2457 | $pickupLocation = ''; |
2458 | $pickupLocations = $this->getILLPickupLocations( |
2459 | $details['id'], |
2460 | $details['pickUpLibrary'], |
2461 | $details['patron'] |
2462 | ); |
2463 | foreach ($pickupLocations as $location) { |
2464 | if ($location['id'] == $details['pickUpLibraryLocation']) { |
2465 | $pickupLocation = $location['name']; |
2466 | break; |
2467 | } |
2468 | } |
2469 | if (!$pickupLocation) { |
2470 | return [ |
2471 | 'success' => false, |
2472 | 'sysMessage' => 'ill_request_place_fail_missing', |
2473 | ]; |
2474 | } |
2475 | |
2476 | $session->ILLRequests->append( |
2477 | [ |
2478 | 'id' => $details['id'], |
2479 | 'source' => $this->getRecordSource(), |
2480 | 'location' => $pickupLocation, |
2481 | 'expire' => |
2482 | $this->dateConverter->convertToDisplayDate('U', $expire), |
2483 | 'create' => |
2484 | $this->dateConverter->convertToDisplayDate('U', time()), |
2485 | 'processed' => rand() % 3 == 0 |
2486 | ? $this->dateConverter->convertToDisplayDate('U', $expire) : '', |
2487 | 'reqnum' => sprintf('%06d', $nextId), |
2488 | 'item_id' => $nextId, |
2489 | ] |
2490 | ); |
2491 | |
2492 | return ['success' => true]; |
2493 | } |
2494 | |
2495 | /** |
2496 | * Get ILL Pickup Libraries |
2497 | * |
2498 | * This is responsible for getting information on the possible pickup libraries |
2499 | * |
2500 | * @param string $id Record ID |
2501 | * @param array $patron Patron |
2502 | * |
2503 | * @return bool|array False if request not allowed, or an array of associative |
2504 | * arrays with libraries. |
2505 | * |
2506 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) |
2507 | */ |
2508 | public function getILLPickupLibraries($id, $patron) |
2509 | { |
2510 | $this->checkIntermittentFailure(); |
2511 | if (!$this->ILLRequests) { |
2512 | return false; |
2513 | } |
2514 | |
2515 | $details = [ |
2516 | [ |
2517 | 'id' => 1, |
2518 | 'name' => 'Main Library', |
2519 | 'isDefault' => true, |
2520 | ], |
2521 | [ |
2522 | 'id' => 2, |
2523 | 'name' => 'Branch Library', |
2524 | 'isDefault' => false, |
2525 | ], |
2526 | ]; |
2527 | |
2528 | return $details; |
2529 | } |
2530 | |
2531 | /** |
2532 | * Get ILL Pickup Locations |
2533 | * |
2534 | * This is responsible for getting a list of possible pickup locations for a |
2535 | * library |
2536 | * |
2537 | * @param string $id Record ID |
2538 | * @param string $pickupLib Pickup library ID |
2539 | * @param array $patron Patron |
2540 | * |
2541 | * @return bool|array False if request not allowed, or an array of locations. |
2542 | * |
2543 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) |
2544 | */ |
2545 | public function getILLPickupLocations($id, $pickupLib, $patron) |
2546 | { |
2547 | $this->checkIntermittentFailure(); |
2548 | switch ($pickupLib) { |
2549 | case 1: |
2550 | return [ |
2551 | [ |
2552 | 'id' => 1, |
2553 | 'name' => 'Circulation Desk', |
2554 | 'isDefault' => true, |
2555 | ], |
2556 | [ |
2557 | 'id' => 2, |
2558 | 'name' => 'Reference Desk', |
2559 | 'isDefault' => false, |
2560 | ], |
2561 | ]; |
2562 | case 2: |
2563 | return [ |
2564 | [ |
2565 | 'id' => 3, |
2566 | 'name' => 'Main Desk', |
2567 | 'isDefault' => false, |
2568 | ], |
2569 | [ |
2570 | 'id' => 4, |
2571 | 'name' => 'Library Bus', |
2572 | 'isDefault' => true, |
2573 | ], |
2574 | ]; |
2575 | } |
2576 | return []; |
2577 | } |
2578 | |
2579 | /** |
2580 | * Cancel ILL Request |
2581 | * |
2582 | * Attempts to Cancel an ILL request on a particular item. The |
2583 | * data in $cancelDetails['details'] is determined by |
2584 | * getCancelILLRequestDetails(). |
2585 | * |
2586 | * @param array $cancelDetails An array of item and patron data |
2587 | * |
2588 | * @return array An array of data on each request including |
2589 | * whether or not it was successful and a system message (if available) |
2590 | */ |
2591 | public function cancelILLRequests($cancelDetails) |
2592 | { |
2593 | $this->checkIntermittentFailure(); |
2594 | // Rewrite the items in the session, removing those the user wants to |
2595 | // cancel. |
2596 | $newRequests = new ArrayObject(); |
2597 | $retVal = ['count' => 0, 'items' => []]; |
2598 | $session = $this->getSession($cancelDetails['patron']['id'] ?? null); |
2599 | foreach ($session->ILLRequests as $current) { |
2600 | if (!in_array($current['reqnum'], $cancelDetails['details'])) { |
2601 | $newRequests->append($current); |
2602 | } else { |
2603 | if (!$this->isFailing(__METHOD__, 50)) { |
2604 | $retVal['count']++; |
2605 | $retVal['items'][$current['item_id']] = [ |
2606 | 'success' => true, |
2607 | 'status' => 'ill_request_cancel_success', |
2608 | ]; |
2609 | } else { |
2610 | $newRequests->append($current); |
2611 | $retVal['items'][$current['item_id']] = [ |
2612 | 'success' => false, |
2613 | 'status' => 'ill_request_cancel_fail', |
2614 | 'sysMessage' => |
2615 | 'Demonstrating failure; keep trying and ' . |
2616 | 'it will work eventually.', |
2617 | ]; |
2618 | } |
2619 | } |
2620 | } |
2621 | |
2622 | $session->ILLRequests = $newRequests; |
2623 | return $retVal; |
2624 | } |
2625 | |
2626 | /** |
2627 | * Get Cancel ILL Request Details |
2628 | * |
2629 | * @param array $request An array of request data |
2630 | * @param array $patron Patron information from patronLogin |
2631 | * |
2632 | * @return string Data for use in a form field |
2633 | * |
2634 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) |
2635 | */ |
2636 | public function getCancelILLRequestDetails($request, $patron) |
2637 | { |
2638 | return $request['reqnum']; |
2639 | } |
2640 | |
2641 | /** |
2642 | * Change Password |
2643 | * |
2644 | * Attempts to change patron password (PIN code) |
2645 | * |
2646 | * @param array $details An array of patron id and old and new password: |
2647 | * |
2648 | * 'patron' The patron array from patronLogin |
2649 | * 'oldPassword' Old password |
2650 | * 'newPassword' New password |
2651 | * |
2652 | * @return array An array of data on the request including |
2653 | * whether or not it was successful and a system message (if available) |
2654 | * |
2655 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) |
2656 | */ |
2657 | public function changePassword($details) |
2658 | { |
2659 | $this->checkIntermittentFailure(); |
2660 | if (!$this->isFailing(__METHOD__, 33)) { |
2661 | return ['success' => true, 'status' => 'change_password_ok']; |
2662 | } |
2663 | return [ |
2664 | 'success' => false, |
2665 | 'status' => 'An error has occurred', |
2666 | 'sysMessage' => |
2667 | 'Demonstrating failure; keep trying and it will work eventually.', |
2668 | ]; |
2669 | } |
2670 | |
2671 | /** |
2672 | * Public Function which specifies renew, hold and cancel settings. |
2673 | * |
2674 | * @param string $function The name of the feature to be checked |
2675 | * @param array $params Optional feature-specific parameters (array) |
2676 | * |
2677 | * @return array An array with key-value pairs. |
2678 | * |
2679 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) |
2680 | */ |
2681 | public function getConfig($function, $params = []) |
2682 | { |
2683 | $this->checkIntermittentFailure(); |
2684 | if ($function == 'Holds') { |
2685 | return $this->config['Holds'] |
2686 | ?? [ |
2687 | 'HMACKeys' => 'id:item_id:level', |
2688 | 'extraHoldFields' => |
2689 | 'comments:requestGroup:pickUpLocation:requiredByDate', |
2690 | 'defaultRequiredDate' => 'driver:0:2:0', |
2691 | ]; |
2692 | } |
2693 | if ($function == 'Holdings') { |
2694 | return [ |
2695 | 'itemLimit' => $this->config['Holdings']['itemLimit'] ?? null, |
2696 | ]; |
2697 | } |
2698 | if ( |
2699 | $function == 'StorageRetrievalRequests' |
2700 | && $this->storageRetrievalRequests |
2701 | ) { |
2702 | return [ |
2703 | 'HMACKeys' => 'id', |
2704 | 'extraFields' => 'comments:pickUpLocation:requiredByDate:item-issue', |
2705 | 'helpText' => 'This is a storage retrieval request help text' |
2706 | . ' with some <span style="color: red">styling</span>.', |
2707 | ]; |
2708 | } |
2709 | if ($function == 'ILLRequests' && $this->ILLRequests) { |
2710 | return [ |
2711 | 'enabled' => true, |
2712 | 'HMACKeys' => 'number', |
2713 | 'extraFields' => |
2714 | 'comments:pickUpLibrary:pickUpLibraryLocation:requiredByDate', |
2715 | 'defaultRequiredDate' => '0:1:0', |
2716 | 'helpText' => 'This is an ILL request help text' |
2717 | . ' with some <span style="color: red">styling</span>.', |
2718 | ]; |
2719 | } |
2720 | if ($function == 'changePassword') { |
2721 | return $this->config['changePassword'] |
2722 | ?? ['minLength' => 4, 'maxLength' => 20]; |
2723 | } |
2724 | if ($function == 'getMyTransactionHistory') { |
2725 | if (empty($this->config['TransactionHistory']['enabled'])) { |
2726 | return false; |
2727 | } |
2728 | $config = [ |
2729 | 'sort' => [ |
2730 | 'checkout desc' => 'sort_checkout_date_desc', |
2731 | 'checkout asc' => 'sort_checkout_date_asc', |
2732 | 'return desc' => 'sort_return_date_desc', |
2733 | 'return asc' => 'sort_return_date_asc', |
2734 | 'due desc' => 'sort_due_date_desc', |
2735 | 'due asc' => 'sort_due_date_asc', |
2736 | ], |
2737 | 'default_sort' => 'checkout desc', |
2738 | 'purge_all' => $this->config['TransactionHistory']['purgeAll'] ?? true, |
2739 | 'purge_selected' => $this->config['TransactionHistory']['purgeSelected'] ?? true, |
2740 | ]; |
2741 | if ($this->config['Loans']['paging'] ?? false) { |
2742 | $config['max_results'] |
2743 | = $this->config['Loans']['max_page_size'] ?? 100; |
2744 | } |
2745 | return $config; |
2746 | } |
2747 | if ('getMyTransactions' === $function) { |
2748 | if (empty($this->config['Loans']['paging'])) { |
2749 | return []; |
2750 | } |
2751 | return [ |
2752 | 'max_results' => $this->config['Loans']['max_page_size'] ?? 100, |
2753 | 'sort' => [ |
2754 | 'due desc' => 'sort_due_date_desc', |
2755 | 'due asc' => 'sort_due_date_asc', |
2756 | 'title asc' => 'sort_title', |
2757 | ], |
2758 | 'default_sort' => 'due asc', |
2759 | ]; |
2760 | } |
2761 | if ($function == 'patronLogin') { |
2762 | return [ |
2763 | 'loginMethod' |
2764 | => $this->config['Catalog']['loginMethod'] ?? 'password', |
2765 | ]; |
2766 | } |
2767 | |
2768 | return []; |
2769 | } |
2770 | |
2771 | /** |
2772 | * Get bib records for recently returned items. |
2773 | * |
2774 | * @param int $limit Maximum number of records to retrieve (default = 30) |
2775 | * @param int $maxage The maximum number of days to consider "recently |
2776 | * returned." |
2777 | * @param array $patron Patron Data |
2778 | * |
2779 | * @return array |
2780 | * |
2781 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) |
2782 | */ |
2783 | public function getRecentlyReturnedBibs( |
2784 | $limit = 30, |
2785 | $maxage = 30, |
2786 | $patron = null |
2787 | ) { |
2788 | $this->checkIntermittentFailure(); |
2789 | |
2790 | $results = $this->config['Records']['recently_returned'] |
2791 | ?? $this->getRandomBibIds($limit); |
2792 | $mapper = function ($id) { |
2793 | return ['id' => $id]; |
2794 | }; |
2795 | return array_map($mapper, $results); |
2796 | } |
2797 | |
2798 | /** |
2799 | * Get bib records for "trending" items (recently returned with high usage). |
2800 | * |
2801 | * @param int $limit Maximum number of records to retrieve (default = 30) |
2802 | * @param int $maxage The maximum number of days' worth of data to examine. |
2803 | * @param array $patron Patron Data |
2804 | * |
2805 | * @return array |
2806 | */ |
2807 | public function getTrendingBibs($limit = 30, $maxage = 30, $patron = null) |
2808 | { |
2809 | // This is similar to getRecentlyReturnedBibs for demo purposes. |
2810 | return $this->getRecentlyReturnedBibs($limit, $maxage, $patron); |
2811 | } |
2812 | |
2813 | /** |
2814 | * Get list of users for whom the provided patron is a proxy. |
2815 | * |
2816 | * @param array $patron The patron array with username and password |
2817 | * |
2818 | * @return array |
2819 | * |
2820 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) |
2821 | */ |
2822 | public function getProxiedUsers(array $patron): array |
2823 | { |
2824 | return $this->config['ProxiedUsers'] ?? []; |
2825 | } |
2826 | |
2827 | /** |
2828 | * Get list of users who act as proxies for the provided patron. |
2829 | * |
2830 | * @param array $patron The patron array with username and password |
2831 | * |
2832 | * @return array |
2833 | * |
2834 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) |
2835 | */ |
2836 | public function getProxyingUsers(array $patron): array |
2837 | { |
2838 | return $this->config['ProxyingUsers'] ?? []; |
2839 | } |
2840 | |
2841 | /** |
2842 | * Provide an array of URL data (in the same format returned by the record |
2843 | * driver's getURLs method) for the specified bibliographic record. |
2844 | * |
2845 | * @param string $id Bibliographic record ID |
2846 | * |
2847 | * @return array |
2848 | */ |
2849 | public function getUrlsForRecord(string $id): array |
2850 | { |
2851 | $links = []; |
2852 | if ($this->config['RecordLinks']['fakeOpacLink'] ?? false) { |
2853 | $links[] = [ |
2854 | 'url' => 'http://localhost/my-fake-ils?id=' . urlencode($id), |
2855 | 'desc' => 'View in OPAC (fake demo link)', |
2856 | ]; |
2857 | } |
2858 | return $links; |
2859 | } |
2860 | } |