Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 279
0.00% covered (danger)
0.00%
0 / 16
CRAP
0.00% covered (danger)
0.00%
0 / 1
OverdriveController
0.00% covered (danger)
0.00%
0 / 279
0.00% covered (danger)
0.00%
0 / 16
2862
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 mycontentAction
0.00% covered (danger)
0.00%
0 / 82
0.00% covered (danger)
0.00%
0 / 1
132
 getStatusAction
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
2
 holdAction
0.00% covered (danger)
0.00%
0 / 66
0.00% covered (danger)
0.00%
0 / 1
72
 getHoldConfirmRes
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
12
 getConfirmCheckoutRes
0.00% covered (danger)
0.00%
0 / 30
0.00% covered (danger)
0.00%
0 / 1
42
 getCheckoutRes
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
6
 getPlaceHoldRes
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
6
 getEditHoldEmailConfRes
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 getEditHoldEmailRes
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 getReturnTitleConfirmResult
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
6
 getSuspendHoldRes
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
20
 getEditSuspendedRes
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
20
 getCancelHoldRes
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 getReturnTitleRes
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 getDownloadTitleRes
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2
3/**
4 * Overdrive Controller
5 *
6 * PHP version 8
7 *
8 * @category VuFind
9 * @package  Controller
10 * @author   Brent Palmer <brent-palmer@ipcl.org>
11 * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
12 * @link     https://vufind.org Main Site
13 */
14
15namespace VuFind\Controller;
16
17use Laminas\Log\LoggerAwareInterface;
18use Laminas\ServiceManager\ServiceLocatorInterface;
19use VuFind\DigitalContent\OverdriveConnector;
20
21use function is_array;
22
23/**
24 * Overdrive Controller supports actions for Overdrive Integration
25 *
26 * @category VuFind
27 * @package  Controller
28 * @author   Brent Palmer <brent-palmer@ipcl.org>
29 * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
30 * @link     https://vufind.org/wiki/development:plugins:controllers Wiki
31 */
32class OverdriveController extends AbstractBase implements LoggerAwareInterface
33{
34    use \VuFind\Log\LoggerAwareTrait {
35        logError as error;
36    }
37
38    /**
39     * Overdrive Connector
40     *
41     * @var OverdriveConnector $connector Overdrive Connector
42     */
43    protected $connector;
44
45    /**
46     * Constructor
47     *
48     * @param ServiceLocatorInterface $sm Service locator
49     */
50    public function __construct(ServiceLocatorInterface $sm)
51    {
52        $this->setLogger($sm->get(\VuFind\Log\Logger::class));
53        $this->connector
54            = $sm->get(\VuFind\DigitalContent\OverdriveConnector::class);
55        parent::__construct($sm);
56    }
57
58    /**
59     * My Content Action
60     * Prepares the view for the Overdrive MyContent template.
61     *
62     * @return array|bool|\Laminas\View\Model\ViewModel
63     */
64    public function mycontentAction()
65    {
66        $this->debug('ODC mycontent action');
67        // Force login
68        if (!is_array($patron = $this->catalogLogin())) {
69            return $patron;
70        }
71        $holds = [];
72        $checkouts = [];
73        $checkoutsUnavailable = false;
74        $holdsUnavailable = false;
75
76        // Check on this patrons's access to Overdrive
77        $odAccessResult = $this->connector->getAccess();
78
79        if (!($odAccessResult->status ?? false)) {
80            $this->debug('result:' . print_r($odAccessResult, true));
81            $this->flashMessenger()->addErrorMessage(
82                $this->translate(
83                    $odAccessResult->code ?? 'An error has occurred',
84                    ['%%message%%' => $odAccessResult->msg ?? '']
85                )
86            );
87            $checkoutsUnavailable = true;
88            $holdsUnavailable = true;
89        } else {
90            // Get the current Overdrive checkouts
91            // for this user and add to our array of IDS
92            $checkoutResults = $this->connector->getCheckouts(true);
93            if (!($checkoutResults->status ?? false)) {
94                $this->flashMessenger()->addMessage(
95                    $checkoutResults->code ?? 'An error has occurred',
96                    'error'
97                );
98                $checkoutsUnavailable = true;
99            } else {
100                foreach ($checkoutResults->data as $checkout) {
101                    $mycheckout = compact('checkout');
102
103                    if ($checkout->metadata->mediaType == 'Magazine') {
104                        $mycheckout['checkout']->isMagazine = true;
105                        $this->debug("loading magazine metadata for {$checkout->reserveId}");
106                        $idToLoad = strtolower($checkout->metadata->parentMagazineReferenceId);
107                        $this->debug("loading magazine parent with id: $idToLoad instead");
108                    } else {
109                        $mycheckout['checkout']->isMagazine = false;
110                        $idToLoad = strtolower($checkout->reserveId);
111                    }
112
113                    try {
114                        $this->debug("loading checkout using: $idToLoad");
115                        $mycheckout['record']
116                            = $this->serviceLocator->get(\VuFind\Record\Loader::class)
117                            ->load($idToLoad);
118                        $checkouts[] = $mycheckout;
119                    } catch (\VuFind\Exception\RecordMissing $e) {
120                        $this->debug("missing record in index: $idToLoad");
121                        // checkout is missing from Solr
122                        $this->flashMessenger()->addMessage(
123                            'One or more checkouts could not be displayed properly: ' .
124                            $e->getMessage(),
125                            'error'
126                        );
127                        // get metadata from overdrive.
128                        $meta = $this->connector->getMetadata([strtolower($checkout->reserveId)]);
129                        $mycheckout['metadata'] = $meta[strtolower($checkout->reserveId)];
130                        $checkouts[] = $mycheckout;
131                    }
132                }
133            }
134            // Get the current Overdrive holds for this user and add to
135            // our array of IDS
136            $holdsResults = $this->connector->getHolds(true);
137            if (
138                !($holdsResults->status ?? false)
139                && ($checkoutResults->status ?? false) // avoid double errors
140            ) {
141                $this->flashMessenger()->addMessage(
142                    $holdsResults->code ?? 'An error has occurred',
143                    'error'
144                );
145                $holdsUnavailable = true;
146            } else {
147                foreach ($holdsResults->data as $hold) {
148                    $myhold['hold'] = $hold;
149                    try {
150                        $myhold['record']
151                            = $this->serviceLocator->get(\VuFind\Record\Loader::class)
152                            ->load(strtolower($hold->reserveId));
153                        $holds[] = $myhold;
154                    } catch (\VuFind\Exception\RecordMissing $e) {
155                        // hold is missing from Solr
156                        $this->flashMessenger()->addMessage(
157                            'One or more holds could not be displayed properly: ' .
158                            $e->getMessage(),
159                            'error'
160                        );
161
162                        // get metadata from overdrive.
163                        $meta = $this->connector->getMetadata([$hold->reserveId]);
164                        $myhold['metadata'] = $meta[$hold->reserveId];
165                        $holds[] = $myhold;
166                    }
167                }
168            }
169        }
170        $view = $this->createViewModel(
171            compact(
172                'checkoutsUnavailable',
173                'holdsUnavailable',
174                'checkouts',
175                'holds'
176            )
177        );
178
179        $view->setTemplate('myresearch/odmycontent');
180        return $view;
181    }
182
183    /**
184     * Get Status Action
185     * Supports the ajax getStatus calls
186     *
187     * @return array|bool|\Laminas\View\Model\ViewModel
188     */
189    public function getStatusAction()
190    {
191        $this->debug('ODC getStatus action');
192        $ids = $this->params()->fromPost(
193            'id',
194            $this->params()->fromQuery('id', [])
195        );
196        $result = $this->connector->getAvailabilityBulk($ids);
197        $view = $this->createViewModel(compact('ids', 'result'));
198        $view->setTemplate('RecordDriver/SolrOverdrive/status-full');
199        $this->layout()->setTemplate('layout/lightbox');
200        return $view;
201    }
202
203    /**
204     * Hold Action
205     *
206     * Hold Action handles all of the actions involving
207     * Overdrive content including checkout, hold, cancel hold etc.
208     *
209     * @return array|bool|\Laminas\View\Model\ViewModel
210     * @todo   Deal with situation that an unlogged in user requests
211     *     an action but the action is no longer valid since they
212     *     already have the content on hold/checked out or do not have access
213     */
214    public function holdAction()
215    {
216        $this->debug('ODC Hold action');
217
218        if (!is_array($patron = $this->catalogLogin())) {
219            return $patron;
220        }
221        $od_id = $this->params()->fromQuery('od_id');
222        $rec_id = $this->params()->fromQuery('rec_id');
223        $action = $this->params()->fromQuery('action');
224        $edition = $this->params()->fromPost(
225            'edition',
226            $this->params()->fromQuery('edition', false)
227        );
228        $holdEmail = '';
229
230        // Action comes in through the form
231        if (null !== $this->params()->fromPost('doAction')) {
232            $action = $this->params()->fromPost('doAction');
233        }
234
235        // Place hold action comes in through the form
236        if (null !== $this->params()->fromPost('getTitleFormat')) {
237            $format = $this->params()->fromPost('getTitleFormat');
238        }
239
240        $format = $this->params()->fromQuery('getTitleFormat');
241
242        $this->debug("ODRC od_id=$od_id rec_id=$rec_id action=$action");
243        // Load the Record Driver. Should be a SolrOverdrive driver.
244        $driver = $this->serviceLocator->get(\VuFind\Record\Loader::class)->load(
245            $rec_id
246        );
247
248        $formats = $driver->getDigitalFormats();
249        $title = $driver->getTitle();
250        $cover = $driver->getThumbnail('small');
251        $listAuthors = $driver->getPrimaryAuthors();
252        $result = null;
253        $actionTitleCode = '';
254
255        if (!$action) {
256            // Double check the availability in case it has changed since the page
257            // was loaded.
258            $avail = $driver->getOverdriveAvailability();
259            $action = ($avail->copiesAvailable > 0) ? 'checkoutConfirm' : 'holdConfirm';
260        }
261        $actions = [
262            'checkoutConfirm' => ['titleCode' => 'od_checkout', 'resMeth' => 'getConfirmCheckoutRes'],
263            'holdConfirm' => ['titleCode' => 'od_hold', 'resMeth' => 'getHoldConfirmRes'],
264            'cancelHoldConfirm' => ['titleCode' => 'od_cancel_hold', 'resMeth' => null],
265            'suspHoldConfirm' => ['titleCode' => 'od_susp_hold', 'resMeth' => null],
266            'editHoldConfirm' => ['titleCode' => 'od_susp_hold_edit', 'resMeth' => null],
267            'editHoldEmailConfirm' => ['titleCode' => 'od_edit_hold_email', 'resMeth' => 'getEditHoldEmailConfRes'],
268            'returnTitleConfirm' => ['titleCode' => 'od_early_return', 'resMeth' => 'getReturnTitleConfirmResult'],
269            'doCheckout' => ['titleCode' => 'od_checkout', 'resMeth' => 'getCheckoutRes'],
270            'placeHold' => ['titleCode' => 'od_hold', 'resMeth' => 'getPlaceHoldRes'],
271            'editHoldEmail' => ['titleCode' => 'od_hold', 'resMeth' => 'getEditHoldEmailRes'],
272            'suspendHold' => ['titleCode' => 'od_susp_hold', 'resMeth' => 'getSuspendHoldRes'],
273            'editSuspendedHold' => ['titleCode' => 'od_susp_hold_edit', 'resMeth' => 'getEditSuspendedRes'],
274            'cancelHold' => ['titleCode' => 'od_cancel_hold', 'resMeth' => 'getCancelHoldRes'],
275            'returnTitle' => ['titleCode' => 'od_early_return', 'resMeth' => 'getReturnTitleRes'],
276            'getTitle' => ['titleCode' => 'od_get_title', 'resMeth' => 'getDownloadTitleRes'],
277        ];
278
279        if (isset($actions[$action])) {
280            $actionTitleCode = $actions[$action]['titleCode'];
281            $result = $actions[$action]['resMeth'] ? $this->{$actions[$action]['resMeth']}() : false;
282        } else {
283            $this->logWarning("overdrive action not defined: $action");
284        }
285
286        $view = $this->createViewModel(
287            compact(
288                'od_id',
289                'rec_id',
290                'action',
291                'result',
292                'formats',
293                'cover',
294                'title',
295                'actionTitleCode',
296                'listAuthors',
297                'edition'
298            )
299        );
300
301        $view->setTemplate('RecordDriver/SolrOverdrive/hold');
302        return $view;
303    }
304
305    /**
306     * Hold Confirm Result
307     *
308     * Get result of the action
309     *
310     * @return obj Result Object
311     */
312    public function getHoldConfirmRes()
313    {
314        $od_id = $this->params()->fromQuery('od_id');
315        $result = $this->connector->getResultObject();
316        // Check to make sure they don't already have this checked out.
317        // Shouldn't need to refresh.
318        if ($checkout = $this->connector->getCheckout($od_id, true)) {
319            $result->status = false;
320            $result->data = (object)[];
321            $result->data->checkout = $checkout;
322            $result->code = 'OD_CODE_ALREADY_CHECKED_OUT';
323            $this->debug("title already checked out: $od_id");
324        } elseif ($hold = $this->connector->getHold($od_id, true)) {
325            $result->status = false;
326            $result->data = (object)[];
327            $result->data->hold = $hold;
328            $result->code = 'OD_CODE_ALREADY_ON_HOLD';
329            $this->debug("title already on hold: $od_id");
330        } else {
331            $result->status = true;
332        }
333        return $result;
334    }
335
336    /**
337     * Confirm Checkout Result
338     *
339     * Get result of the action
340     *
341     * @return obj Result Object
342     */
343    public function getConfirmCheckoutRes()
344    {
345        $od_id = $this->params()->fromQuery('od_id');
346        $rec_id = $this->params()->fromQuery('rec_id');
347        // Load the Record Driver. Should be a SolrOverdrive driver.
348        $driver = $this->serviceLocator->get(\VuFind\Record\Loader::class)->load(
349            $rec_id
350        );
351        $formats = $driver->getDigitalFormats();
352        // Looks like this is a magazine...
353        if (current($formats)->id == 'magazine-overdrive') {
354            $isMagazine = true;
355            $result = $this->connector->getMagazineIssues($od_id, true);
356            if ($result->status) {
357                $issues = $result->data->products;
358                $result->data->isMagazine = true;
359            } else {
360                $this->debug("couldn't get issues for checkout");
361                $result->status = false;
362                $result->code = 'OD_CODE_NOMAGISSUES';
363                $result->msg = 'No magazine issue available.';
364            }
365        } else {
366            $result = $this->connector->getResultObject();
367            $result->data = (object)['isMagazine' => false];
368            // Check to make sure they don't already have this checked out
369            // shouldn't need to refresh.
370            if ($checkout = $this->connector->getCheckout($od_id, false)) {
371                $result->status = false;
372                $result->data->checkout = $checkout;
373                $result->code = 'OD_CODE_ALREADY_CHECKED_OUT';
374            } elseif ($hold = $this->connector->getHold($od_id, false)) {
375                if ($hold->holdReadyForCheckout) {
376                    $result->status = true;
377                } else {
378                    $result->status = false;
379                    $result->data->hold = $hold;
380                    $result->code = 'OD_CODE_ALREADY_ON_HOLD';
381                }
382            } else {
383                $result->status = true;
384            }
385        }
386        return $result;
387    }
388
389    /**
390     * Checkout Result
391     *
392     * Get result of the action
393     *
394     * @return obj Result Object
395     */
396    public function getCheckoutRes()
397    {
398        $od_id = $this->params()->fromQuery('od_id');
399        $edition = $this->params()->fromPost(
400            'edition',
401            $this->params()->fromQuery('edition', false)
402        );
403        if ($edition) {
404            $od_id = $edition;
405        }
406        $result = $this->connector->doOverdriveCheckout($od_id);
407        return $result;
408    }
409
410    /**
411     * Place Hold Result
412     *
413     * Get result of the action
414     *
415     * @return obj Result Object
416     */
417    public function getPlaceHoldRes()
418    {
419        $od_id = $this->params()->fromQuery('od_id');
420        $email = $this->params()->fromPost('email');
421        $result = $this->connector->placeOverDriveHold($od_id, $email);
422        if ($result->status) {
423            $result->code = 'od_hold_place_success';
424            $result->codeParams = ['%%holdListPosition%%' => $result->data->holdListPosition];
425        } else {
426            $result->code = 'od_hold_place_failure';
427        }
428        return $result;
429    }
430
431    /**
432     * Edit Hold Email Confirm Result
433     *
434     * Get result of the action
435     *
436     * @return obj Result Object
437     */
438    public function getEditHoldEmailConfRes()
439    {
440        $od_id = $this->params()->fromQuery('od_id');
441        $hold = $this->connector->getHold($od_id, true);
442        $result = $this->connector->getResultObject(true);
443        $result->data = (object)[];
444        $result->data->hold = $hold;
445        return $result;
446    }
447
448    /**
449     * Edit Hold Email Result
450     *
451     * Get result of the action
452     *
453     * @return obj Result Object
454     */
455    public function getEditHoldEmailRes()
456    {
457        $od_id = $this->params()->fromQuery('od_id');
458        $email = $this->params()->fromPost('email');
459        $result = $this->connector->updateOverDriveHold($od_id, $email);
460        $result->code = $result->status ? 'od_hold_update_success' : 'od_hold_update_failure';
461        return $result;
462    }
463
464    /**
465     * Return Title Confirmation Result
466     *
467     * Get result of the action
468     *
469     * @return obj Result Object
470     */
471    public function getReturnTitleConfirmResult()
472    {
473        $result = $this->connector->getResultObject();
474        $rec_id = $this->params()->fromQuery('rec_id');
475        // Load the SolrOverdrive driver.
476        $driver = $this->serviceLocator->get(\VuFind\Record\Loader::class)->load(
477            $rec_id
478        );
479        $formats = $driver->getDigitalFormats();
480        $result->data = (current($formats)->id == 'magazine-overdrive') ?: false;
481        return $result;
482    }
483
484    /**
485     * Suspend Hold Result
486     *
487     * Get result of the action
488     *
489     * @return obj Result Object
490     */
491    public function getSuspendHoldRes()
492    {
493        $od_id = $this->params()->fromQuery('od_id');
494        $suspendValue = $this->params()->fromPost('suspendValue');
495        $hold = $this->connector->getHold($od_id, false);
496        $holdEmail = $hold->emailAddress;
497        $suspensionType = $suspendValue == -1 ? 'indefinite' : 'limited';
498        $result = $this->connector->suspendHold($od_id, $holdEmail, $suspensionType, $suspendValue);
499
500        if ($result->status) {
501            if ($suspensionType == 'indefinite') {
502                $result->code = 'od_hold_susp_indef';
503            } else {
504                $result->code = 'od_hold_redelivery';
505                $result->codeParams = ['%%days%%' => $result->data->holdSuspension->numberOfDays];
506            }
507        } else {
508            $result->code = 'od_hold_update_failure';
509        }
510        return $result;
511    }
512
513    /**
514     * Edit Suspended Hold Result
515     *
516     * Get result of the action
517     *
518     * @return obj Result Object
519     */
520    public function getEditSuspendedRes()
521    {
522        $od_id = $this->params()->fromQuery('od_id');
523        $suspendValue = $this->params()->fromPost('suspendValue');
524        if ($suspendValue == 0) {
525            $result = $this->connector->deleteHoldSuspension($od_id);
526        } else {
527            $hold = $this->connector->getHold($od_id, false);
528            $holdEmail = $hold->emailAddress;
529            $suspensionType = $suspendValue == -1 ? 'indefinite' : 'limited';
530            $result = $this->connector->editSuspendedHold($od_id, $holdEmail, $suspensionType, $suspendValue);
531        }
532        $result->code = $result->status ? 'od_hold_update_success' : 'od_hold_update_failure';
533        return $result;
534    }
535
536    /**
537     * Cancel Hold Result
538     *
539     * Get result of the action
540     *
541     * @return obj Result Object
542     */
543    public function getCancelHoldRes()
544    {
545        $od_id = $this->params()->fromQuery('od_id');
546        $result = $this->connector->cancelHold($od_id);
547        $result->code = $result->status ? 'od_hold_cancel_success' : 'od_hold_cancel_failure';
548        return $result;
549    }
550
551    /**
552     * Return Title Result
553     *
554     * Get result of the action
555     *
556     * @return obj Result Object
557     */
558    public function getReturnTitleRes()
559    {
560        $od_id = $this->params()->fromQuery('od_id');
561        $result = $this->connector->returnResource($od_id);
562        $result->code = $result->status ? 'od_return_success' : 'od_return_failure';
563        return $result;
564    }
565
566    /**
567     * Download Title Result
568     *
569     * Get result of the action
570     *
571     * @return obj Result Object
572     */
573    public function getDownloadTitleRes()
574    {
575        $od_id = $this->params()->fromQuery('od_id');
576        $result = $this->connector->getDownloadRedirect($od_id);
577        $result->code = $result->status ? '' : 'od_gettitle_failure';
578        return $result;
579    }
580}