Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 240 |
|
0.00% |
0 / 12 |
CRAP | |
0.00% |
0 / 1 |
CartController | |
0.00% |
0 / 240 |
|
0.00% |
0 / 12 |
6006 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
getCart | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getCartActionFromRequest | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
30 | |||
searchresultsbulkAction | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
12 | |||
processorAction | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
homeAction | |
0.00% |
0 / 23 |
|
0.00% |
0 / 1 |
72 | |||
myresearchbulkAction | |
0.00% |
0 / 20 |
|
0.00% |
0 / 1 |
72 | |||
emailAction | |
0.00% |
0 / 51 |
|
0.00% |
0 / 1 |
306 | |||
printcartAction | |
0.00% |
0 / 16 |
|
0.00% |
0 / 1 |
20 | |||
exportAction | |
0.00% |
0 / 49 |
|
0.00% |
0 / 1 |
156 | |||
doexportAction | |
0.00% |
0 / 16 |
|
0.00% |
0 / 1 |
30 | |||
saveAction | |
0.00% |
0 / 44 |
|
0.00% |
0 / 1 |
156 |
1 | <?php |
2 | |
3 | /** |
4 | * Book Bag / Bulk Action Controller |
5 | * |
6 | * PHP version 8 |
7 | * |
8 | * Copyright (C) Villanova University 2010. |
9 | * |
10 | * This program is free software; you can redistribute it and/or modify |
11 | * it under the terms of the GNU General Public License version 2, |
12 | * as published by the Free Software Foundation. |
13 | * |
14 | * This program is distributed in the hope that it will be useful, |
15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
17 | * GNU General Public License for more details. |
18 | * |
19 | * You should have received a copy of the GNU General Public License |
20 | * along with this program; if not, write to the Free Software |
21 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
22 | * |
23 | * @category VuFind |
24 | * @package Controller |
25 | * @author Demian Katz <demian.katz@villanova.edu> |
26 | * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License |
27 | * @link https://vufind.org Main Site |
28 | */ |
29 | |
30 | namespace VuFind\Controller; |
31 | |
32 | use Laminas\ServiceManager\ServiceLocatorInterface; |
33 | use Laminas\Session\Container; |
34 | use VuFind\Controller\Feature\ListItemSelectionTrait; |
35 | use VuFind\Db\Service\UserListServiceInterface; |
36 | use VuFind\Exception\Forbidden as ForbiddenException; |
37 | use VuFind\Exception\Mail as MailException; |
38 | use VuFind\Favorites\FavoritesService; |
39 | |
40 | use function count; |
41 | use function is_array; |
42 | use function strlen; |
43 | |
44 | /** |
45 | * Book Bag / Bulk Action Controller |
46 | * |
47 | * @category VuFind |
48 | * @package Controller |
49 | * @author Demian Katz <demian.katz@villanova.edu> |
50 | * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License |
51 | * @link https://vufind.org Main Site |
52 | */ |
53 | class CartController extends AbstractBase |
54 | { |
55 | use Feature\BulkActionControllerTrait; |
56 | use ListItemSelectionTrait; |
57 | |
58 | /** |
59 | * Session container |
60 | * |
61 | * @var \Laminas\Session\Container |
62 | */ |
63 | protected $session; |
64 | |
65 | /** |
66 | * Configuration loader |
67 | * |
68 | * @var \VuFind\Config\PluginManager |
69 | */ |
70 | protected $configLoader; |
71 | |
72 | /** |
73 | * Export support class |
74 | * |
75 | * @var \VuFind\Export |
76 | */ |
77 | protected $export; |
78 | |
79 | /** |
80 | * Constructor |
81 | * |
82 | * @param ServiceLocatorInterface $sm Service manager |
83 | * @param Container $container Session container |
84 | * @param \VuFind\Config\PluginManager $configLoader Configuration loader |
85 | * @param \VuFind\Export $export Export support class |
86 | */ |
87 | public function __construct( |
88 | ServiceLocatorInterface $sm, |
89 | Container $container, |
90 | \VuFind\Config\PluginManager $configLoader, |
91 | \VuFind\Export $export |
92 | ) { |
93 | parent::__construct($sm); |
94 | $this->session = $container; |
95 | $this->configLoader = $configLoader; |
96 | $this->export = $export; |
97 | } |
98 | |
99 | /** |
100 | * Get the cart object. |
101 | * |
102 | * @return \VuFind\Cart |
103 | */ |
104 | protected function getCart() |
105 | { |
106 | return $this->serviceLocator->get(\VuFind\Cart::class); |
107 | } |
108 | |
109 | /** |
110 | * Figure out an action from the request.... |
111 | * |
112 | * @param string $default Default action if none can be determined. |
113 | * |
114 | * @return string |
115 | */ |
116 | protected function getCartActionFromRequest($default = 'Home') |
117 | { |
118 | if (strlen($this->params()->fromPost('email', '')) > 0) { |
119 | return 'Email'; |
120 | } elseif (strlen($this->params()->fromPost('print', '')) > 0) { |
121 | return 'PrintCart'; |
122 | } elseif (strlen($this->params()->fromPost('saveCart', '')) > 0) { |
123 | return 'Save'; |
124 | } elseif (strlen($this->params()->fromPost('export', '')) > 0) { |
125 | return 'Export'; |
126 | } |
127 | // Check if the user is in the midst of a login process; if not, |
128 | // use the provided default. |
129 | return $this->followup()->retrieveAndClear('cartAction', $default); |
130 | } |
131 | |
132 | /** |
133 | * Process requests for bulk actions from search results. |
134 | * |
135 | * @return mixed |
136 | */ |
137 | public function searchresultsbulkAction() |
138 | { |
139 | // We came in from a search, so let's remember that context so we can |
140 | // return to it later. However, if we came in from a previous instance |
141 | // of this action (for example, because of a login screen), or if we |
142 | // have an external site in the referer, we should ignore that! |
143 | $referer = $this->getRequest()->getServer()->get('HTTP_REFERER'); |
144 | $bulk = $this->url()->fromRoute('cart-searchresultsbulk'); |
145 | if ($this->isLocalUrl($referer) && !str_ends_with($referer, $bulk)) { |
146 | $this->session->url = $referer; |
147 | } |
148 | |
149 | // Now forward to the requested action: |
150 | return $this->forwardTo('Cart', $this->getCartActionFromRequest()); |
151 | } |
152 | |
153 | /** |
154 | * Process requests for main cart. |
155 | * |
156 | * @return mixed |
157 | */ |
158 | public function processorAction() |
159 | { |
160 | // We came in from the cart -- let's remember this so we can redirect there |
161 | // when we're done: |
162 | $this->session->url = $this->url()->fromRoute('cart-home'); |
163 | |
164 | // Now forward to the requested action: |
165 | return $this->forwardTo('Cart', $this->getCartActionFromRequest()); |
166 | } |
167 | |
168 | /** |
169 | * Display cart contents. |
170 | * |
171 | * @return mixed |
172 | */ |
173 | public function homeAction() |
174 | { |
175 | // Bail out if cart is disabled. |
176 | if (!$this->getCart()->isActive()) { |
177 | return $this->redirect()->toRoute('home'); |
178 | } |
179 | |
180 | // If a user is coming directly to the cart, we should clear out any |
181 | // existing context information to prevent weird, unexpected workflows |
182 | // caused by unusual user behavior. |
183 | $this->followup()->retrieveAndClear('cartAction'); |
184 | $this->followup()->retrieveAndClear('cartIds'); |
185 | |
186 | $ids = $this->getSelectedIds(); |
187 | |
188 | // Add items if necessary: |
189 | if (strlen($this->params()->fromPost('empty', '')) > 0) { |
190 | $this->getCart()->emptyCart(); |
191 | } elseif (strlen($this->params()->fromPost('delete', '')) > 0) { |
192 | if (empty($ids)) { |
193 | return $this->redirectToSource('error', 'bulk_noitems_advice'); |
194 | } else { |
195 | $this->getCart()->removeItems($ids); |
196 | } |
197 | } elseif (strlen($this->params()->fromPost('add', '')) > 0) { |
198 | if (empty($ids)) { |
199 | return $this->redirectToSource('error', 'bulk_noitems_advice'); |
200 | } else { |
201 | $addItems = $this->getCart()->addItems($ids); |
202 | if (!$addItems['success']) { |
203 | $msg = $this->translate('bookbag_full_msg') . '. ' |
204 | . $addItems['notAdded'] . ' ' |
205 | . $this->translate('items_already_in_bookbag') . '.'; |
206 | $this->flashMessenger()->addMessage($msg, 'info'); |
207 | } |
208 | } |
209 | } |
210 | // Using the cart/cart template for the cart/home action is a legacy of |
211 | // an earlier controller design; we may want to rename the template for |
212 | // clarity, but right now we are retaining the old template name for |
213 | // backward compatibility. |
214 | $view = $this->createViewModel(); |
215 | $view->setTemplate('cart/cart'); |
216 | return $view; |
217 | } |
218 | |
219 | /** |
220 | * Process bulk actions from the MyResearch area; most of this is only necessary |
221 | * when Javascript is disabled. |
222 | * |
223 | * @return mixed |
224 | */ |
225 | public function myresearchbulkAction() |
226 | { |
227 | // We came in from the MyResearch section -- let's remember which list (if |
228 | // any) we came from so we can redirect there when we're done: |
229 | $listID = $this->params()->fromPost('listID'); |
230 | $this->session->url = empty($listID) |
231 | ? $this->url()->fromRoute('myresearch-favorites') |
232 | : $this->url()->fromRoute('userList', ['id' => $listID]); |
233 | |
234 | // Now forward to the requested controller/action: |
235 | $controller = 'Cart'; // assume Cart unless overridden below. |
236 | if (strlen($this->params()->fromPost('email', '')) > 0) { |
237 | $action = 'Email'; |
238 | } elseif (strlen($this->params()->fromPost('print', '')) > 0) { |
239 | $action = 'PrintCart'; |
240 | } elseif (strlen($this->params()->fromPost('delete', '')) > 0) { |
241 | $controller = 'MyResearch'; |
242 | $action = 'Delete'; |
243 | } elseif (strlen($this->params()->fromPost('add', '')) > 0) { |
244 | $action = 'Home'; |
245 | } elseif (strlen($this->params()->fromPost('export', '')) > 0) { |
246 | $action = 'Export'; |
247 | } else { |
248 | $action = $this->followup()->retrieveAndClear('cartAction', null); |
249 | if (empty($action)) { |
250 | throw new \Exception('Unrecognized bulk action.'); |
251 | } |
252 | } |
253 | return $this->forwardTo($controller, $action); |
254 | } |
255 | |
256 | /** |
257 | * Email a batch of records. |
258 | * |
259 | * @return mixed |
260 | */ |
261 | public function emailAction() |
262 | { |
263 | // Retrieve ID list: |
264 | $ids = $this->getSelectedIds(); |
265 | |
266 | // Retrieve follow-up information if necessary: |
267 | if (!is_array($ids) || empty($ids)) { |
268 | $ids = $this->followup()->retrieveAndClear('cartIds') ?? []; |
269 | } |
270 | $actionLimit = $this->getBulkActionLimit('email'); |
271 | if (!is_array($ids) || empty($ids)) { |
272 | if ($redirect = $this->redirectToSource('error', 'bulk_noitems_advice')) { |
273 | return $redirect; |
274 | } |
275 | $submitDisabled = true; |
276 | } elseif (count($ids) > $actionLimit) { |
277 | $errorMsg = $this->translate( |
278 | 'bulk_limit_exceeded', |
279 | ['%%count%%' => count($ids), '%%limit%%' => $actionLimit], |
280 | ); |
281 | if ($redirect = $this->redirectToSource('error', $errorMsg)) { |
282 | return $redirect; |
283 | } |
284 | $submitDisabled = true; |
285 | } |
286 | |
287 | // Force login if necessary: |
288 | $config = $this->getConfig(); |
289 | if ( |
290 | (!isset($config->Mail->require_login) || $config->Mail->require_login) |
291 | && !$this->getUser() |
292 | ) { |
293 | return $this->forceLogin( |
294 | null, |
295 | ['cartIds' => $ids, 'cartAction' => 'Email'] |
296 | ); |
297 | } |
298 | |
299 | $view = $this->createEmailViewModel( |
300 | null, |
301 | $this->translate('bulk_email_title') |
302 | ); |
303 | $view->records = $this->getRecordLoader()->loadBatch($ids); |
304 | // Set up Captcha |
305 | $view->useCaptcha = $this->captcha()->active('email'); |
306 | |
307 | // Process form submission: |
308 | if (!($submitDisabled ?? false) && $this->formWasSubmitted(useCaptcha: $view->useCaptcha)) { |
309 | // Build the URL to share: |
310 | $params = []; |
311 | foreach ($ids as $current) { |
312 | $params[] = urlencode('id[]') . '=' . urlencode($current); |
313 | } |
314 | $url = $this->getServerUrl('records-home') . '?' . implode('&', $params); |
315 | |
316 | // Attempt to send the email and show an appropriate flash message: |
317 | try { |
318 | // If we got this far, we're ready to send the email: |
319 | $mailer = $this->serviceLocator->get(\VuFind\Mailer\Mailer::class); |
320 | $mailer->setMaxRecipients($view->maxRecipients); |
321 | $cc = $this->params()->fromPost('ccself') && $view->from != $view->to |
322 | ? $view->from : null; |
323 | $mailer->sendLink( |
324 | $view->to, |
325 | $view->from, |
326 | $view->message, |
327 | $url, |
328 | $this->getViewRenderer(), |
329 | $view->subject, |
330 | $cc |
331 | ); |
332 | return $this->redirectToSource('success', 'bulk_email_success', true); |
333 | } catch (MailException $e) { |
334 | $this->flashMessenger()->addMessage($e->getDisplayMessage(), 'error'); |
335 | } |
336 | } |
337 | |
338 | return $view; |
339 | } |
340 | |
341 | /** |
342 | * Print a batch of records. |
343 | * |
344 | * @return mixed |
345 | */ |
346 | public function printcartAction() |
347 | { |
348 | $ids = $this->getSelectedIds(); |
349 | if (!is_array($ids) || empty($ids)) { |
350 | return $this->redirectToSource('error', 'bulk_noitems_advice'); |
351 | } |
352 | |
353 | // Check if id limit is exceeded |
354 | $actionLimit = $this->getBulkActionLimit('print'); |
355 | if (count($ids) > $actionLimit) { |
356 | $errorMsg = $this->translate( |
357 | 'bulk_limit_exceeded', |
358 | ['%%count%%' => count($ids), '%%limit%%' => $actionLimit], |
359 | ); |
360 | return $this->redirectToSource('error', $errorMsg); |
361 | } |
362 | |
363 | $callback = function ($i) { |
364 | return 'id[]=' . urlencode($i); |
365 | }; |
366 | $query = '?print=true&' . implode('&', array_map($callback, $ids)); |
367 | $url = $this->url()->fromRoute('records-home') . $query; |
368 | return $this->redirect()->toUrl($url); |
369 | } |
370 | |
371 | /** |
372 | * Set up export of a batch of records. |
373 | * |
374 | * @return mixed |
375 | */ |
376 | public function exportAction() |
377 | { |
378 | // Get the desired ID list: |
379 | $ids = $this->getSelectedIds(); |
380 | |
381 | // Get export tools: |
382 | $export = $this->export; |
383 | |
384 | // Get id limit |
385 | $format = $this->params()->fromPost('format'); |
386 | $actionLimit = $format ? $this->getExportActionLimit($format) : $this->getBulkActionLimit('export'); |
387 | |
388 | if (!is_array($ids) || empty($ids)) { |
389 | if ($redirect = $this->redirectToSource('error', 'bulk_noitems_advice')) { |
390 | return $redirect; |
391 | } |
392 | } elseif (count($ids) > $actionLimit) { |
393 | $errorMsg = $this->translate( |
394 | 'bulk_limit_exceeded', |
395 | ['%%count%%' => count($ids), '%%limit%%' => $actionLimit], |
396 | ); |
397 | if ($redirect = $this->redirectToSource('error', $errorMsg)) { |
398 | return $redirect; |
399 | } |
400 | } elseif ($this->formWasSubmitted()) { |
401 | $url = $export->getBulkUrl($this->getViewRenderer(), $format, $ids); |
402 | if ($export->needsRedirect($format)) { |
403 | return $this->redirect()->toUrl($url); |
404 | } |
405 | $exportType = $export->getBulkExportType($format); |
406 | $params = [ |
407 | 'exportType' => $exportType, |
408 | 'format' => $format, |
409 | ]; |
410 | if ('post' === $exportType) { |
411 | $records = $this->getRecordLoader()->loadBatch($ids); |
412 | $recordHelper = $this->getViewRenderer()->plugin('record'); |
413 | $parts = []; |
414 | foreach ($records as $record) { |
415 | $parts[] = $recordHelper($record)->getExport($format); |
416 | } |
417 | |
418 | $params['postField'] = $export->getPostField($format); |
419 | $params['postData'] = $export->processGroup($format, $parts); |
420 | $params['targetWindow'] = $export->getTargetWindow($format); |
421 | $params['url'] = $export->getRedirectUrl($format, ''); |
422 | } else { |
423 | $params['url'] = $url; |
424 | } |
425 | $msg = [ |
426 | 'translate' => false, 'html' => true, |
427 | 'msg' => $this->getViewRenderer()->render( |
428 | 'cart/export-success.phtml', |
429 | $params |
430 | ), |
431 | ]; |
432 | return $this->redirectToSource('success', $msg, true); |
433 | } |
434 | |
435 | // Load the records: |
436 | $view = $this->createViewModel(); |
437 | $view->records = $this->getRecordLoader()->loadBatch($ids); |
438 | |
439 | // Assign the list of legal export options. We'll filter them down based |
440 | // on what the selected records actually support. |
441 | $view->exportOptions = $export->getFormatsForRecords($view->records); |
442 | |
443 | // No legal export options? Display a warning: |
444 | if (empty($view->exportOptions)) { |
445 | $this->flashMessenger() |
446 | ->addMessage('bulk_export_not_supported', 'error'); |
447 | } |
448 | return $view; |
449 | } |
450 | |
451 | /** |
452 | * Actually perform the export operation. |
453 | * |
454 | * @return mixed |
455 | */ |
456 | public function doexportAction() |
457 | { |
458 | // We use abbreviated parameters here to keep the URL short (there may |
459 | // be a long list of IDs, and we don't want to run out of room): |
460 | $ids = $this->params()->fromQuery('i', []); |
461 | $format = $this->params()->fromQuery('f'); |
462 | |
463 | // Make sure we have IDs to export: |
464 | if (!is_array($ids) || empty($ids)) { |
465 | return $this->redirectToSource('error', 'bulk_noitems_advice'); |
466 | } |
467 | |
468 | // Check if id limit is exceeded |
469 | $actionLimit = $this->getExportActionLimit($format); |
470 | if (count($ids) > $actionLimit) { |
471 | return $this->redirectToSource('error', 'bulk_limit_exceeded'); |
472 | } |
473 | |
474 | // Send appropriate HTTP headers for requested format: |
475 | $response = $this->getResponse(); |
476 | $response->getHeaders()->addHeaders($this->export->getHeaders($format)); |
477 | |
478 | // Actually export the records |
479 | $records = $this->getRecordLoader()->loadBatch($ids); |
480 | $recordHelper = $this->getViewRenderer()->plugin('record'); |
481 | $parts = []; |
482 | foreach ($records as $record) { |
483 | $parts[] = $recordHelper($record)->getExport($format); |
484 | } |
485 | |
486 | // Process and display the exported records |
487 | $response->setContent($this->export->processGroup($format, $parts)); |
488 | return $response; |
489 | } |
490 | |
491 | /** |
492 | * Save a batch of records. |
493 | * |
494 | * @return mixed |
495 | */ |
496 | public function saveAction() |
497 | { |
498 | // Fail if lists are disabled: |
499 | if (!$this->listsEnabled()) { |
500 | throw new ForbiddenException('Lists disabled'); |
501 | } |
502 | |
503 | // Load record information first (no need to prompt for login if we just |
504 | // need to display a "no records" error message): |
505 | $ids = $this->getSelectedIds(); |
506 | if (!is_array($ids) || empty($ids)) { |
507 | $ids = $this->followup()->retrieveAndClear('cartIds') ?? []; |
508 | } |
509 | $actionLimit = $this->getBulkActionLimit('saveCart'); |
510 | if (!is_array($ids) || empty($ids)) { |
511 | if ($redirect = $this->redirectToSource('error', 'bulk_noitems_advice')) { |
512 | return $redirect; |
513 | } |
514 | $submitDisabled = true; |
515 | } elseif (count($ids) > $actionLimit) { |
516 | $errorMsg = $this->translate( |
517 | 'bulk_limit_exceeded', |
518 | ['%%count%%' => count($ids), '%%limit%%' => $actionLimit], |
519 | ); |
520 | if ($redirect = $this->redirectToSource('error', $errorMsg)) { |
521 | return $redirect; |
522 | } |
523 | $submitDisabled = true; |
524 | } |
525 | |
526 | // Make sure user is logged in: |
527 | if (!($user = $this->getUser())) { |
528 | return $this->forceLogin( |
529 | null, |
530 | ['cartIds' => $ids, 'cartAction' => 'Save'] |
531 | ); |
532 | } |
533 | |
534 | // Process submission if necessary: |
535 | if (!($submitDisabled ?? false) && $this->formWasSubmitted()) { |
536 | $results = $this->serviceLocator->get(FavoritesService::class) |
537 | ->saveRecordsToFavorites($this->getRequest()->getPost()->toArray(), $user); |
538 | $listUrl = $this->url()->fromRoute( |
539 | 'userList', |
540 | ['id' => $results['listId']] |
541 | ); |
542 | $message = [ |
543 | 'html' => true, |
544 | 'msg' => $this->translate('bulk_save_success') . '. ' |
545 | . '<a href="' . $listUrl . '" class="gotolist">' |
546 | . $this->translate('go_to_list') . '</a>.', |
547 | ]; |
548 | $this->flashMessenger()->addMessage($message, 'success'); |
549 | return $this->redirect()->toUrl($listUrl); |
550 | } |
551 | |
552 | // Pass record and list information to view: |
553 | return $this->createViewModel( |
554 | [ |
555 | 'records' => $this->getRecordLoader()->loadBatch($ids), |
556 | 'lists' => $this->getDbService(UserListServiceInterface::class)->getUserListsByUser($user), |
557 | ] |
558 | ); |
559 | } |
560 | } |