Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 169
0.00% covered (danger)
0.00%
0 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
LibraryCardsController
0.00% covered (danger)
0.00%
0 / 169
0.00% covered (danger)
0.00%
0 / 9
2162
0.00% covered (danger)
0.00%
0 / 1
 homeAction
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
6
 editCardAction
0.00% covered (danger)
0.00%
0 / 31
0.00% covered (danger)
0.00%
0 / 1
110
 deleteCardAction
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
12
 adjustCardRedirectUrl
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 selectCardAction
0.00% covered (danger)
0.00%
0 / 22
0.00% covered (danger)
0.00%
0 / 1
42
 connectCardLoginAction
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
12
 connectCardAction
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
 processEditLibraryCard
0.00% covered (danger)
0.00%
0 / 55
0.00% covered (danger)
0.00%
0 / 1
240
 processEmailLink
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
12
1<?php
2
3/**
4 * LibraryCards Controller
5 *
6 * PHP version 8
7 *
8 * Copyright (C) Villanova University 2010.
9 * Copyright (C) The National Library of Finland 2015-2019.
10 *
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License version 2,
13 * as published by the Free Software Foundation.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
23 *
24 * @category VuFind
25 * @package  Controller
26 * @author   Demian Katz <demian.katz@villanova.edu>
27 * @author   Ere Maijala <ere.maijala@helsinki.fi>
28 * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
29 * @link     https://vufind.org Main Site
30 */
31
32namespace VuFind\Controller;
33
34use VuFind\Db\Entity\UserEntityInterface;
35use VuFind\Db\Service\UserCardServiceInterface;
36use VuFind\Exception\ILS as ILSException;
37
38/**
39 * Controller for the library card functionality.
40 *
41 * @category VuFind
42 * @package  Controller
43 * @author   Demian Katz <demian.katz@villanova.edu>
44 * @author   Ere Maijala <ere.maijala@helsinki.fi>
45 * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
46 * @link     https://vufind.org Main Site
47 */
48class LibraryCardsController extends AbstractBase
49{
50    /**
51     * Send user's library cards to the view
52     *
53     * @return mixed
54     */
55    public function homeAction()
56    {
57        if (!($user = $this->getUser())) {
58            return $this->forceLogin();
59        }
60
61        // Connect to the ILS for login drivers:
62        $catalog = $this->getILS();
63        $cardService = $this->getDbService(UserCardServiceInterface::class);
64
65        return $this->createViewModel(
66            [
67                'libraryCards' => $cardService->getLibraryCards($user),
68                'multipleTargets' => $catalog->checkCapability('getLoginDrivers'),
69                'allowConnectingCards' => $this->getAuthManager()
70                    ->supportsConnectingLibraryCard(),
71            ]
72        );
73    }
74
75    /**
76     * Send user's library card to the edit view
77     *
78     * @return mixed
79     */
80    public function editCardAction()
81    {
82        // User must be logged in to edit library cards:
83        if (!($user = $this->getUser())) {
84            return $this->forceLogin();
85        }
86
87        // Process email authentication:
88        if (
89            $this->params()->fromQuery('auth_method') === 'Email'
90            && ($hash = $this->params()->fromQuery('hash'))
91        ) {
92            return $this->processEmailLink($user, $hash);
93        }
94
95        // Process form submission:
96        if ($this->formWasSubmitted()) {
97            if ($redirect = $this->processEditLibraryCard($user)) {
98                return $redirect;
99            }
100        }
101
102        $id = $this->params()->fromRoute('id', $this->params()->fromQuery('id'));
103        $cardService = $this->getDbService(UserCardServiceInterface::class);
104        $card = $cardService->getOrCreateLibraryCard($user, $id == 'NEW' ? null : $id);
105
106        $target = null;
107        $username = $card->getCatUsername();
108
109        $loginSettings = $this->getILSLoginSettings();
110        // Split target and username if multiple login targets are available:
111        if ($loginSettings['targets'] && strstr($username, '.')) {
112            [$target, $username] = explode('.', $username, 2);
113        }
114
115        $cardName = $this->params()->fromPost('card_name', $card->getCardName());
116        $username = $this->params()->fromPost('username', $username);
117        $target = $this->params()->fromPost('target', $target);
118
119        // Send the card to the view:
120        return $this->createViewModel(
121            [
122                'card' => $card,
123                'cardName' => $cardName,
124                'target' => $target ?: $loginSettings['defaultTarget'],
125                'username' => $username,
126                'targets' => $loginSettings['targets'],
127                'defaultTarget' => $loginSettings['defaultTarget'],
128                'loginMethod' => $loginSettings['loginMethod'],
129                'loginMethods' => $loginSettings['loginMethods'],
130            ]
131        );
132    }
133
134    /**
135     * Creates a confirmation box to delete or not delete the current list
136     *
137     * @return mixed
138     */
139    public function deleteCardAction()
140    {
141        // User must be logged in to edit library cards:
142        if (!($user = $this->getUser())) {
143            return $this->forceLogin();
144        }
145
146        // Get requested library card ID:
147        $cardID = $this->params()
148            ->fromPost('cardID', $this->params()->fromQuery('cardID'));
149
150        // Have we confirmed this?
151        $confirm = $this->params()->fromPost(
152            'confirm',
153            $this->params()->fromQuery('confirm')
154        );
155        if ($confirm) {
156            $this->getDbService(UserCardServiceInterface::class)->deleteLibraryCard($user, $cardID);
157
158            // Success Message
159            $this->flashMessenger()->addMessage('Library Card Deleted', 'success');
160            // Redirect to MyResearch library cards
161            return $this->redirect()->toRoute('librarycards-home');
162        }
163
164        // If we got this far, we must display a confirmation message:
165        return $this->confirm(
166            'confirm_delete_library_card_brief',
167            $this->url()->fromRoute('librarycards-deletecard'),
168            $this->url()->fromRoute('librarycards-home'),
169            'confirm_delete_library_card_text',
170            ['cardID' => $cardID]
171        );
172    }
173
174    /**
175     * When redirecting after selecting a library card, adjust the URL to make
176     * sure it will work correctly.
177     *
178     * @param string $url URL to adjust
179     *
180     * @return string
181     */
182    protected function adjustCardRedirectUrl($url)
183    {
184        // If there is pagination in the URL, reset it to page 1, since the
185        // new card may have a different number of pages of data:
186        return preg_replace('/([&?]page)=[0-9]+/', '$1=1', $url);
187    }
188
189    /**
190     * Activates a library card
191     *
192     * @return \Laminas\Http\Response
193     */
194    public function selectCardAction()
195    {
196        if (!($user = $this->getUser())) {
197            return $this->forceLogin();
198        }
199
200        $cardID = $this->params()->fromQuery('cardID');
201        if (null === $cardID) {
202            return $this->redirect()->toRoute('myresearch-home');
203        }
204        $cardService = $this->getDbService(UserCardServiceInterface::class);
205        $cardService->activateLibraryCard($user, $cardID);
206
207        // Connect to the ILS and check that the credentials are correct:
208        try {
209            $catalog = $this->getILS();
210            $patron = $catalog->patronLogin(
211                $user->getCatUsername(),
212                $this->getILSAuthenticator()->getCatPasswordForUser($user)
213            );
214            if (!$patron) {
215                $this->flashMessenger()
216                    ->addMessage('authentication_error_invalid', 'error');
217            }
218        } catch (ILSException $e) {
219            $this->flashMessenger()
220                ->addMessage('authentication_error_technical', 'error');
221        }
222
223        $this->setFollowupUrlToReferer(false);
224        if ($url = $this->getAndClearFollowupUrl()) {
225            return $this->redirect()->toUrl($this->adjustCardRedirectUrl($url));
226        }
227        return $this->redirect()->toRoute('myresearch-home');
228    }
229
230    /**
231     * Redirects to authentication to connect a new library card
232     *
233     * @return \Laminas\Http\Response
234     */
235    public function connectCardLoginAction()
236    {
237        if (!($user = $this->getUser())) {
238            return $this->forceLogin();
239        }
240        $url = $this->getServerUrl('librarycards-connectcard');
241        $redirectUrl = $this->getAuthManager()->getSessionInitiator($url);
242        if (!$redirectUrl) {
243            $this->flashMessenger()
244                ->addMessage('authentication_error_technical', 'error');
245            return $this->redirect()->toRoute('librarycards-home');
246        }
247        return $this->redirect()->toUrl($redirectUrl);
248    }
249
250    /**
251     * Connects a new library card for authenticated user
252     *
253     * @return \Laminas\Http\Response
254     */
255    public function connectCardAction()
256    {
257        if (!($user = $this->getUser())) {
258            return $this->forceLogin();
259        }
260        try {
261            $this->getAuthManager()->connectLibraryCard($this->getRequest(), $user);
262        } catch (\Exception $ex) {
263            $this->flashMessenger()->setNamespace('error')
264                ->addMessage($ex->getMessage());
265        }
266        return $this->redirect()->toRoute('librarycards-home');
267    }
268
269    /**
270     * Process the "edit library card" submission.
271     *
272     * @param UserEntityInterface $user Logged in user
273     *
274     * @return object|bool        Response object if redirect is
275     * needed, false if form needs to be redisplayed.
276     */
277    protected function processEditLibraryCard($user)
278    {
279        $cardName = $this->params()->fromPost('card_name', '');
280        $target = $this->params()->fromPost('target', '');
281        $username = $this->params()->fromPost('username', '');
282        $password = $this->params()->fromPost('password', '');
283        $id = $this->params()->fromRoute('id', $this->params()->fromQuery('id'));
284
285        if (!$username) {
286            $this->flashMessenger()
287                ->addMessage('authentication_error_blank', 'error');
288            return false;
289        }
290
291        if ($target) {
292            $username = "$target.$username";
293        }
294
295        // Check the credentials if the username is changed or a new password is
296        // entered:
297        $cardService = $this->getDbService(UserCardServiceInterface::class);
298        $card = $cardService->getOrCreateLibraryCard($user, $id == 'NEW' ? null : $id);
299        if ($card->getCatUsername() !== $username || trim($password)) {
300            // Connect to the ILS and check that the credentials are correct:
301            $loginMethod = $this->getILSLoginMethod($target);
302            if (
303                'password' === $loginMethod
304                && !$this->getAuthManager()->allowsUserIlsLogin()
305            ) {
306                throw new \Exception(
307                    'Illegal configuration: '
308                    . 'password-based library cards and disabled user login'
309                );
310            }
311            $catalog = $this->getILS();
312            try {
313                $patron = $catalog->patronLogin($username, $password);
314            } catch (ILSException $e) {
315                $this->flashMessenger()->addErrorMessage('ils_connection_failed');
316                return false;
317            }
318            if ('password' === $loginMethod && !$patron) {
319                $this->flashMessenger()
320                    ->addMessage('authentication_error_invalid', 'error');
321                return false;
322            }
323            if ('email' === $loginMethod) {
324                if ($patron) {
325                    $info = $patron;
326                    $info['cardID'] = $id;
327                    $info['cardName'] = $cardName;
328                    $emailAuthenticator = $this->getService(\VuFind\Auth\EmailAuthenticator::class);
329                    $emailAuthenticator->sendAuthenticationLink(
330                        $info['email'],
331                        $info,
332                        ['auth_method' => 'Email'],
333                        'editLibraryCard'
334                    );
335                }
336                // Don't reveal the result
337                $this->flashMessenger()->addSuccessMessage('email_login_link_sent');
338                return $this->redirect()->toRoute('librarycards-home');
339            }
340        }
341
342        try {
343            $cardService->persistLibraryCardData(
344                $user,
345                $id == 'NEW' ? null : $id,
346                $cardName,
347                $username,
348                $password
349            );
350        } catch (\VuFind\Exception\LibraryCard $e) {
351            $this->flashMessenger()->addMessage($e->getMessage(), 'error');
352            return false;
353        }
354
355        return $this->redirect()->toRoute('librarycards-home');
356    }
357
358    /**
359     * Process library card addition via an email link
360     *
361     * @param UserEntityInterface $user User object
362     * @param string              $hash Hash
363     *
364     * @return \Laminas\Http\Response Response object
365     */
366    protected function processEmailLink($user, $hash)
367    {
368        $emailAuthenticator = $this->getService(\VuFind\Auth\EmailAuthenticator::class);
369        try {
370            $info = $emailAuthenticator->authenticate($hash);
371            $cardService = $this->getDbService(UserCardServiceInterface::class);
372            $cardService->persistLibraryCardData(
373                $user,
374                'NEW' === $info['cardID'] ? null : $info['cardID'],
375                $info['cardName'],
376                $info['cat_username'],
377                ' '
378            );
379        } catch (\VuFind\Exception\Auth | \VuFind\Exception\LibraryCard $e) {
380            $this->flashMessenger()->addErrorMessage($e->getMessage());
381        }
382
383        return $this->redirect()->toRoute('librarycards-home');
384    }
385}