Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 99
0.00% covered (danger)
0.00%
0 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
CheckoutsController
0.00% covered (danger)
0.00%
0 / 99
0.00% covered (danger)
0.00%
0 / 7
756
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
 historyAction
0.00% covered (danger)
0.00%
0 / 57
0.00% covered (danger)
0.00%
0 / 1
156
 purgeHistoryAction
0.00% covered (danger)
0.00%
0 / 31
0.00% covered (danger)
0.00%
0 / 1
90
 getRowIdContainer
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 resetValidRowIds
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 rememberValidRowId
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 validateRowIds
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3/**
4 * Checkouts Controller
5 *
6 * PHP version 8
7 *
8 * Copyright (C) Villanova University 2010.
9 * Copyright (C) The National Library of Finland 2023.
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 Laminas\ServiceManager\ServiceLocatorInterface;
35use Laminas\Session\SessionManager;
36use VuFind\ILS\PaginationHelper;
37use VuFind\Validator\CsrfInterface;
38
39use function is_array;
40
41/**
42 * Controller for the user checkouts area.
43 *
44 * @category VuFind
45 * @package  Controller
46 * @author   Demian Katz <demian.katz@villanova.edu>
47 * @author   Ere Maijala <ere.maijala@helsinki.fi>
48 * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
49 * @link     https://vufind.org Main Site
50 */
51class CheckoutsController extends AbstractBase
52{
53    use Feature\CatchIlsExceptionsTrait;
54
55    /**
56     * CSRF validator
57     *
58     * @var CsrfInterface
59     */
60    protected $csrf;
61
62    /**
63     * Session manager
64     *
65     * @var SessionManager
66     */
67    protected $sessionManager;
68
69    /**
70     * Session container
71     *
72     * @var \Laminas\Session\Container
73     */
74    protected $sessionContainer = null;
75
76    /**
77     * Pagination helper
78     *
79     * @var PaginationHelper
80     */
81    protected $paginationHelper;
82
83    /**
84     * Constructor
85     *
86     * @param ServiceLocatorInterface $sm      Service locator
87     * @param CsrfInterface           $csrf    CSRF validator
88     * @param SessionManager          $sessMgr Session manager
89     */
90    public function __construct(
91        ServiceLocatorInterface $sm,
92        CsrfInterface $csrf,
93        SessionManager $sessMgr
94    ) {
95        parent::__construct($sm);
96        $this->csrf = $csrf;
97        $this->sessionManager = $sessMgr;
98        $this->paginationHelper = new PaginationHelper();
99    }
100
101    /**
102     * Send loan history to view
103     *
104     * @return mixed
105     */
106    public function historyAction()
107    {
108        $this->resetValidRowIds();
109
110        // Stop now if the user does not have valid catalog credentials available:
111        if (!is_array($patron = $this->catalogLogin())) {
112            return $patron;
113        }
114
115        // Connect to the ILS:
116        $catalog = $this->getILS();
117
118        // Check function config
119        $functionConfig = $catalog->checkFunction(
120            'getMyTransactionHistory',
121            $patron
122        );
123        if (false === $functionConfig) {
124            $this->flashMessenger()->addErrorMessage('ils_action_unavailable');
125            return $this->createViewModel();
126        }
127        $purgeSelectedAllowed = !empty($functionConfig['purge_selected']);
128        $purgeAllAllowed = !empty($functionConfig['purge_all']);
129
130        // Get paging setup:
131        $config = $this->getConfig();
132        $pageOptions = $this->paginationHelper->getOptions(
133            (int)$this->params()->fromQuery('page', 1),
134            $this->params()->fromQuery('sort'),
135            $config->Catalog->historic_loan_page_size ?? 50,
136            $functionConfig
137        );
138
139        // Get checked out item details:
140        $result
141            = $catalog->getMyTransactionHistory($patron, $pageOptions['ilsParams']);
142
143        if (isset($result['success']) && !$result['success']) {
144            $this->flashMessenger()->addErrorMessage($result['status']);
145            return $this->createViewModel();
146        }
147
148        $paginator = $this->paginationHelper->getPaginator(
149            $pageOptions,
150            $result['count'],
151            $result['transactions']
152        );
153        if ($paginator) {
154            $pageStart = $paginator->getAbsoluteItemNumber(1) - 1;
155            $pageEnd = $paginator->getAbsoluteItemNumber($pageOptions['limit']) - 1;
156        } else {
157            $pageStart = 0;
158            $pageEnd = $result['count'];
159        }
160
161        $driversNeeded = $hiddenTransactions = [];
162        foreach ($result['transactions'] as $i => $current) {
163            // Build record drivers (only for the current visible page):
164            if ($pageOptions['ilsPaging'] || ($i >= $pageStart && $i <= $pageEnd)) {
165                $driversNeeded[] = $current;
166            } else {
167                $hiddenTransactions[] = $current;
168            }
169            if ($purgeSelectedAllowed && isset($current['row_id'])) {
170                $this->rememberValidRowId($current['row_id']);
171            }
172        }
173
174        $transactions = $this->ilsRecords()->getDrivers($driversNeeded);
175        $sortList = $pageOptions['sortList'];
176        $params = $pageOptions['ilsParams'];
177        return $this->createViewModel(
178            compact(
179                'transactions',
180                'paginator',
181                'params',
182                'hiddenTransactions',
183                'sortList',
184                'functionConfig',
185                'purgeAllAllowed',
186                'purgeSelectedAllowed'
187            )
188        );
189    }
190
191    /**
192     * Purge loans from loan history
193     *
194     * @return mixed
195     */
196    public function purgeHistoryAction()
197    {
198        $this->ilsExceptionResponse = $redirectResponse
199            = $this->redirect()->toRoute('checkouts-history');
200
201        // Stop now if the user does not have valid catalog credentials available:
202        if (!is_array($patron = $this->catalogLogin())) {
203            return $patron;
204        }
205
206        $purgeSelected = $this->formWasSubmitted('purgeSelected', false);
207        $purgeAll = $this->formWasSubmitted('purgeAll', false);
208        if ($purgeSelected || $purgeAll) {
209            $csrfToken = $this->getRequest()->getPost()->get('csrf');
210            if (!$this->csrf->isValid($csrfToken)) {
211                $this->flashMessenger()
212                    ->addErrorMessage('error_inconsistent_parameters');
213                return $redirectResponse;
214            }
215            // After successful token verification, clear list to shrink session:
216            $this->csrf->trimTokenList(0);
217            $catalog = $this->getILS();
218            if ($purgeAll) {
219                $result = $catalog->purgeTransactionHistory($patron, null);
220            } else {
221                $ids = $this->getRequest()->getPost()->get('purgeSelectedIDs', []);
222                if (!$ids) {
223                    $this->flashMessenger()
224                        ->addErrorMessage('no_items_selected');
225                    return $redirectResponse;
226                }
227                if (!$this->validateRowIds($ids)) {
228                    $this->flashMessenger()
229                        ->addErrorMessage('error_inconsistent_parameters');
230                    return $redirectResponse;
231                }
232                $result = $catalog->purgeTransactionHistory($patron, $ids);
233            }
234            $this->flashMessenger()->addMessage(
235                $result['status'],
236                $result['success'] ? 'success' : 'error'
237            );
238        }
239        return $redirectResponse;
240    }
241
242    /**
243     * Return a session container for validating selected row ids.
244     *
245     * @return \Laminas\Session\Container
246     */
247    protected function getRowIdContainer()
248    {
249        if (null === $this->sessionContainer) {
250            $this->sessionContainer
251                = new \Laminas\Session\Container('row_ids', $this->sessionManager);
252        }
253        return $this->sessionContainer;
254    }
255
256    /**
257     * Reset the array of valid IDs in the session (used for form submission
258     * validation)
259     *
260     * @return void
261     */
262    protected function resetValidRowIds(): void
263    {
264        $this->getRowIdContainer()->validIds = [];
265    }
266
267    /**
268     * Add an ID to the validation array.
269     *
270     * @param string $id ID to remember
271     *
272     * @return void
273     */
274    protected function rememberValidRowId($id): void
275    {
276        $this->getRowIdContainer()->validIds[] = $id;
277    }
278
279    /**
280     * Validate supplied IDs against remembered IDs. Returns true if all supplied
281     * IDs are remembered, otherwise returns false.
282     *
283     * @param array $ids IDs to validate
284     *
285     * @return bool
286     */
287    public function validateRowIds(array $ids): bool
288    {
289        return !(bool)array_diff($ids, $this->getRowIdContainer()->validIds ?? []);
290    }
291}