Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
11.06% |
25 / 226 |
|
14.29% |
6 / 42 |
CRAP | |
0.00% |
0 / 1 |
AbstractBase | |
11.06% |
25 / 226 |
|
14.29% |
6 / 42 |
7566.41 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
validateAccessPermission | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
12 | |||
getAccessPermission | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
setAccessPermission | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
6 | |||
getRequest | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
attachDefaultListeners | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
6 | |||
createViewModel | |
72.73% |
8 / 11 |
|
0.00% |
0 / 1 |
3.18 | |||
createEmailViewModel | |
0.00% |
0 / 32 |
|
0.00% |
0 / 1 |
342 | |||
getAuthManager | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getAuthorizationService | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
getILSAuthenticator | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getUser | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getViewRenderer | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
forceLogin | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
20 | |||
catalogLogin | |
0.00% |
0 / 38 |
|
0.00% |
0 / 1 |
272 | |||
getConfig | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
getILS | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getRecordLoader | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getRecordCache | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getRecordRouter | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getTable | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
getDbService | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
getServerUrl | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
forwardTo | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
formWasSubmitted | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
30 | |||
confirm | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
2 | |||
disableSessionWrites | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getSearchMemory | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
commentsEnabled | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
listsEnabled | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
tagsEnabled | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
setFollowupUrlToReferer | |
0.00% |
0 / 19 |
|
0.00% |
0 / 1 |
56 | |||
normalizeUrlForComparison | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
hasFollowupUrl | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getAndClearFollowupUrl | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
42 | |||
clearFollowupUrl | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
getRecordTabManager | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
inLightbox | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
2 | |||
getILSLoginMethod | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
2 | |||
getILSLoginSettings | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
12 | |||
getRefreshResponse | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
isLocalUrl | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | |
3 | /** |
4 | * VuFind controller base class (defines some methods that can be shared by other |
5 | * controllers). |
6 | * |
7 | * PHP version 8 |
8 | * |
9 | * Copyright (C) Villanova University 2010. |
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 | * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License |
28 | * @link https://vufind.org/wiki/development:plugins:controllers Wiki |
29 | */ |
30 | |
31 | namespace VuFind\Controller; |
32 | |
33 | use Laminas\Mvc\Controller\AbstractActionController; |
34 | use Laminas\Mvc\MvcEvent; |
35 | use Laminas\Mvc\Plugin\FlashMessenger\FlashMessenger; |
36 | use Laminas\ServiceManager\ServiceLocatorInterface; |
37 | use Laminas\Uri\Http; |
38 | use Laminas\View\Model\ViewModel; |
39 | use VuFind\Controller\Feature\AccessPermissionInterface; |
40 | use VuFind\Db\Entity\UserEntityInterface; |
41 | use VuFind\Exception\Auth as AuthException; |
42 | use VuFind\Exception\ILS as ILSException; |
43 | use VuFind\Http\PhpEnvironment\Request as HttpRequest; |
44 | use VuFind\I18n\Translator\TranslatorAwareInterface; |
45 | use VuFind\I18n\Translator\TranslatorAwareTrait; |
46 | |
47 | use function intval; |
48 | use function is_object; |
49 | |
50 | /** |
51 | * VuFind controller base class (defines some methods that can be shared by other |
52 | * controllers). |
53 | * |
54 | * @category VuFind |
55 | * @package Controller |
56 | * @author Chris Hallberg <challber@villanova.edu> |
57 | * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License |
58 | * @link https://vufind.org/wiki/development:plugins:controllers Wiki |
59 | * |
60 | * @method Plugin\Captcha captcha() Captcha plugin |
61 | * @method Plugin\DbUpgrade dbUpgrade() DbUpgrade plugin |
62 | * @method FlashMessenger flashMessenger() FlashMessenger plugin |
63 | * @method Plugin\Followup followup() Followup plugin |
64 | * @method Plugin\Holds holds() Holds plugin |
65 | * @method Plugin\ILLRequests ILLRequests() ILLRequests plugin |
66 | * @method Plugin\IlsRecords ilsRecords() IlsRecords plugin |
67 | * @method Plugin\NewItems newItems() NewItems plugin |
68 | * @method Plugin\Permission permission() Permission plugin |
69 | * @method Plugin\Renewals renewals() Renewals plugin |
70 | * @method Plugin\Reserves reserves() Reserves plugin |
71 | * @method Plugin\ResultScroller resultScroller() ResultScroller plugin |
72 | * @method Plugin\StorageRetrievalRequests storageRetrievalRequests() |
73 | * StorageRetrievalRequests plugin |
74 | * |
75 | * @SuppressWarnings(PHPMD.NumberOfChildren) |
76 | */ |
77 | class AbstractBase extends AbstractActionController implements AccessPermissionInterface, TranslatorAwareInterface |
78 | { |
79 | use TranslatorAwareTrait; |
80 | |
81 | /** |
82 | * Permission that must be granted to access this module (false for no |
83 | * restriction, null to use configured default (which is usually the same |
84 | * as false)). |
85 | * |
86 | * @var string|bool|null |
87 | */ |
88 | protected $accessPermission = null; |
89 | |
90 | /** |
91 | * Behavior when access is denied (used unless overridden through |
92 | * permissionBehavior.ini). Valid values are 'promptLogin' and 'exception'. |
93 | * Leave at null to use the defaultDeniedControllerBehavior set in |
94 | * permissionBehavior.ini (normally 'promptLogin' unless changed). |
95 | * |
96 | * @var string |
97 | */ |
98 | protected $accessDeniedBehavior = null; |
99 | |
100 | /** |
101 | * Service manager |
102 | * |
103 | * @var ServiceLocatorInterface |
104 | */ |
105 | protected $serviceLocator; |
106 | |
107 | /** |
108 | * Constructor |
109 | * |
110 | * @param ServiceLocatorInterface $sm Service locator |
111 | */ |
112 | public function __construct(ServiceLocatorInterface $sm) |
113 | { |
114 | $this->serviceLocator = $sm; |
115 | } |
116 | |
117 | /** |
118 | * Use preDispatch event to block access when appropriate. |
119 | * |
120 | * @param MvcEvent $e Event object |
121 | * |
122 | * @return void |
123 | */ |
124 | public function validateAccessPermission(MvcEvent $e) |
125 | { |
126 | // If there is an access permission set for this controller, pass it |
127 | // through the permission helper, and if the helper returns a custom |
128 | // response, use that instead of the normal behavior. |
129 | if ($this->accessPermission) { |
130 | $response = $this->permission() |
131 | ->check($this->accessPermission, $this->accessDeniedBehavior); |
132 | if (is_object($response)) { |
133 | $e->setResponse($response); |
134 | } |
135 | } |
136 | } |
137 | |
138 | /** |
139 | * Getter for access permission (string for required permission name, false |
140 | * for no permission required, null to use default permission). |
141 | * |
142 | * @return string|bool|null |
143 | */ |
144 | public function getAccessPermission() |
145 | { |
146 | return $this->accessPermission; |
147 | } |
148 | |
149 | /** |
150 | * Getter for access permission. |
151 | * |
152 | * @param string|false $ap Permission to require for access to the controller (false |
153 | * for no requirement) |
154 | * |
155 | * @return void |
156 | */ |
157 | public function setAccessPermission($ap) |
158 | { |
159 | $this->accessPermission = empty($ap) ? false : $ap; |
160 | } |
161 | |
162 | /** |
163 | * Get request object |
164 | * |
165 | * @return HttpRequest |
166 | */ |
167 | public function getRequest() |
168 | { |
169 | if (!$this->request) { |
170 | $this->request = new HttpRequest(); |
171 | } |
172 | |
173 | return $this->request; |
174 | } |
175 | |
176 | /** |
177 | * Register the default events for this controller |
178 | * |
179 | * @return void |
180 | */ |
181 | protected function attachDefaultListeners() |
182 | { |
183 | parent::attachDefaultListeners(); |
184 | |
185 | // Attach preDispatch event if we need to check permissions. |
186 | if ($this->accessPermission) { |
187 | $events = $this->getEventManager(); |
188 | $events->attach( |
189 | MvcEvent::EVENT_DISPATCH, |
190 | [$this, 'validateAccessPermission'], |
191 | 1000 |
192 | ); |
193 | } |
194 | } |
195 | |
196 | /** |
197 | * Create a new ViewModel. |
198 | * |
199 | * @param array $params Parameters to pass to ViewModel constructor. |
200 | * |
201 | * @return ViewModel |
202 | */ |
203 | protected function createViewModel($params = null) |
204 | { |
205 | if ($this->inLightbox()) { |
206 | $this->layout()->setTemplate('layout/lightbox'); |
207 | $params['inLightbox'] = true; |
208 | } |
209 | $lightboxParentUrl = new Http($this->getServerUrl()); |
210 | $query = $lightboxParentUrl->getQueryAsArray(); |
211 | unset($query['lightboxChild']); |
212 | $lightboxParentUrl->setQuery($query); |
213 | $this->layout()->lightboxParent = $lightboxParentUrl->toString(); |
214 | if ($lightboxChild = $this->getRequest()->getQuery('lightboxChild')) { |
215 | $this->layout()->lightboxChild = $lightboxChild; |
216 | } |
217 | return new ViewModel($params); |
218 | } |
219 | |
220 | /** |
221 | * Create a new ViewModel to use as an email form. |
222 | * |
223 | * @param array $params Parameters to pass to ViewModel constructor. |
224 | * @param string $defaultSubject Default subject line to use. |
225 | * |
226 | * @return ViewModel |
227 | */ |
228 | protected function createEmailViewModel($params = null, $defaultSubject = null) |
229 | { |
230 | // Build view: |
231 | $view = $this->createViewModel($params); |
232 | |
233 | // Load configuration and current user for convenience: |
234 | $config = $this->getConfig(); |
235 | $view->disableFrom |
236 | = (isset($config->Mail->disable_from) && $config->Mail->disable_from); |
237 | $view->editableSubject = isset($config->Mail->user_editable_subjects) |
238 | && $config->Mail->user_editable_subjects; |
239 | $view->maxRecipients = isset($config->Mail->maximum_recipients) |
240 | ? intval($config->Mail->maximum_recipients) : 1; |
241 | $user = $this->getUser(); |
242 | |
243 | // Send parameters back to view so form can be re-populated: |
244 | if ($this->getRequest()->isPost()) { |
245 | $view->to = $this->params()->fromPost('to'); |
246 | if (!$view->disableFrom) { |
247 | $view->from = $this->params()->fromPost('from'); |
248 | } |
249 | if ($view->editableSubject) { |
250 | $view->subject = $this->params()->fromPost('subject'); |
251 | } |
252 | $view->message = $this->params()->fromPost('message'); |
253 | } |
254 | |
255 | // Set default values if applicable: |
256 | if (empty($view->to) && $user && ($config->Mail->user_email_in_to ?? false)) { |
257 | $view->to = $user->getEmail(); |
258 | } |
259 | if (empty($view->from)) { |
260 | if ($user && ($config->Mail->user_email_in_from ?? false)) { |
261 | $view->userEmailInFrom = true; |
262 | $view->from = $user->getEmail(); |
263 | } elseif ($config->Mail->default_from ?? false) { |
264 | $view->from = $config->Mail->default_from; |
265 | } |
266 | } |
267 | if (empty($view->subject)) { |
268 | $view->subject = $defaultSubject; |
269 | } |
270 | |
271 | // Fail if we're missing a from and the form element is disabled: |
272 | if ($view->disableFrom) { |
273 | if (empty($view->from)) { |
274 | $view->from = $config->Site->email; |
275 | } |
276 | if (empty($view->from)) { |
277 | throw new \Exception('Unable to determine email from address'); |
278 | } |
279 | } |
280 | |
281 | return $view; |
282 | } |
283 | |
284 | /** |
285 | * Get the account manager object. |
286 | * |
287 | * @return \VuFind\Auth\Manager |
288 | */ |
289 | protected function getAuthManager() |
290 | { |
291 | return $this->serviceLocator->get(\VuFind\Auth\Manager::class); |
292 | } |
293 | |
294 | /** |
295 | * Get the authorization service (note that we're doing this on-demand |
296 | * rather than through injection with the AuthorizationServiceAwareInterface |
297 | * to minimize expensive initialization when authorization is not needed. |
298 | * |
299 | * @return \LmcRbacMvc\Service\AuthorizationService |
300 | */ |
301 | protected function getAuthorizationService() |
302 | { |
303 | return $this->serviceLocator |
304 | ->get(\LmcRbacMvc\Service\AuthorizationService::class); |
305 | } |
306 | |
307 | /** |
308 | * Get the ILS authenticator. |
309 | * |
310 | * @return \VuFind\Auth\ILSAuthenticator |
311 | */ |
312 | protected function getILSAuthenticator() |
313 | { |
314 | return $this->serviceLocator->get(\VuFind\Auth\ILSAuthenticator::class); |
315 | } |
316 | |
317 | /** |
318 | * Get the user object if logged in, false otherwise. |
319 | * |
320 | * @return ?UserEntityInterface |
321 | */ |
322 | protected function getUser(): ?UserEntityInterface |
323 | { |
324 | return $this->getAuthManager()->getUserObject(); |
325 | } |
326 | |
327 | /** |
328 | * Get the view renderer |
329 | * |
330 | * @return \Laminas\View\Renderer\RendererInterface |
331 | */ |
332 | protected function getViewRenderer() |
333 | { |
334 | return $this->serviceLocator->get('ViewRenderer'); |
335 | } |
336 | |
337 | /** |
338 | * Redirect the user to the login screen. |
339 | * |
340 | * @param string $msg Flash message to display on login screen |
341 | * @param array $extras Associative array of extra fields to store |
342 | * @param bool $forward True to forward, false to redirect |
343 | * |
344 | * @return mixed |
345 | */ |
346 | public function forceLogin($msg = null, $extras = [], $forward = true) |
347 | { |
348 | // Set default message if necessary. |
349 | if (null === $msg) { |
350 | $msg = 'You must be logged in first'; |
351 | } |
352 | |
353 | // store parent url of lightboxes |
354 | $extras['lightboxParent'] = $this->getRequest()->getQuery('lightboxParent'); |
355 | |
356 | // Store the current URL as a login followup action |
357 | $this->followup()->store($extras); |
358 | if (!empty($msg)) { |
359 | $this->flashMessenger()->addMessage($msg, 'error'); |
360 | } |
361 | |
362 | // Set a flag indicating that we are forcing login: |
363 | $this->getRequest()->getPost()->set('forcingLogin', true); |
364 | |
365 | if ($forward) { |
366 | return $this->forwardTo('MyResearch', 'Login'); |
367 | } |
368 | return $this->redirect()->toRoute('myresearch-home'); |
369 | } |
370 | |
371 | /** |
372 | * Does the user have catalog credentials available? Returns associative array |
373 | * of patron data if so, otherwise forwards to appropriate login prompt and |
374 | * returns false. If there is an ILS exception, a flash message is added and |
375 | * a newly created ViewModel is returned. |
376 | * |
377 | * @return bool|array|ViewModel |
378 | */ |
379 | protected function catalogLogin() |
380 | { |
381 | // First make sure user is logged in to VuFind: |
382 | $account = $this->getAuthManager(); |
383 | if (!$account->getIdentity()) { |
384 | return $this->forceLogin(); |
385 | } |
386 | |
387 | // Now check if the user has provided credentials with which to log in: |
388 | $ilsAuth = $this->getILSAuthenticator(); |
389 | $patron = null; |
390 | if ( |
391 | ($username = $this->params()->fromPost('cat_username', false)) |
392 | && ($password = $this->params()->fromPost('cat_password', false)) |
393 | ) { |
394 | // If somebody is POSTing credentials but that logic is disabled, we |
395 | // should throw an exception! |
396 | if (!$account->allowsUserIlsLogin()) { |
397 | throw new \Exception('Unexpected ILS credential submission.'); |
398 | } |
399 | // Check for multiple ILS target selection |
400 | $target = $this->params()->fromPost('target', false); |
401 | if ($target) { |
402 | $username = "$target.$username"; |
403 | } |
404 | try { |
405 | if ('email' === $this->getILSLoginMethod($target)) { |
406 | $routeMatch = $this->getEvent()->getRouteMatch(); |
407 | $routeName = $routeMatch ? $routeMatch->getMatchedRouteName() |
408 | : 'myresearch-profile'; |
409 | $routeParams = $routeMatch ? $routeMatch->getParams() : []; |
410 | $ilsAuth->sendEmailLoginLink($username, $routeName, $routeParams, ['catalogLogin' => 'true']); |
411 | $this->flashMessenger() |
412 | ->addSuccessMessage('email_login_link_sent'); |
413 | } else { |
414 | $patron = $ilsAuth->newCatalogLogin($username, $password); |
415 | |
416 | // If login failed, store a warning message: |
417 | if (!$patron) { |
418 | $this->flashMessenger() |
419 | ->addErrorMessage('Invalid Patron Login'); |
420 | } |
421 | } |
422 | } catch (ILSException $e) { |
423 | $this->flashMessenger()->addErrorMessage('ils_connection_failed'); |
424 | } |
425 | } elseif ( |
426 | 'ILS' === $this->params()->fromQuery('auth_method', false) |
427 | && ($hash = $this->params()->fromQuery('hash', false)) |
428 | ) { |
429 | try { |
430 | $patron = $ilsAuth->processEmailLoginHash($hash); |
431 | } catch (AuthException $e) { |
432 | $this->flashMessenger()->addErrorMessage($e->getMessage()); |
433 | } |
434 | } else { |
435 | try { |
436 | // If no credentials were provided, try the stored values: |
437 | $patron = $ilsAuth->storedCatalogLogin(); |
438 | } catch (ILSException $e) { |
439 | $this->flashMessenger()->addErrorMessage('ils_connection_failed'); |
440 | return $this->createViewModel(); |
441 | } |
442 | } |
443 | |
444 | // If catalog login failed, send the user to the right page: |
445 | if (!$patron) { |
446 | return $this->forwardTo('MyResearch', 'CatalogLogin'); |
447 | } |
448 | |
449 | // Send value (either false or patron array) back to caller: |
450 | return $patron; |
451 | } |
452 | |
453 | /** |
454 | * Get a VuFind configuration. |
455 | * |
456 | * @param string $id Configuration identifier (default = main VuFind config) |
457 | * |
458 | * @return \Laminas\Config\Config |
459 | */ |
460 | public function getConfig($id = 'config') |
461 | { |
462 | return $this->serviceLocator->get(\VuFind\Config\PluginManager::class) |
463 | ->get($id); |
464 | } |
465 | |
466 | /** |
467 | * Get the ILS connection. |
468 | * |
469 | * @return \VuFind\ILS\Connection |
470 | */ |
471 | public function getILS() |
472 | { |
473 | return $this->serviceLocator->get(\VuFind\ILS\Connection::class); |
474 | } |
475 | |
476 | /** |
477 | * Get the record loader |
478 | * |
479 | * @return \VuFind\Record\Loader |
480 | */ |
481 | public function getRecordLoader() |
482 | { |
483 | return $this->serviceLocator->get(\VuFind\Record\Loader::class); |
484 | } |
485 | |
486 | /** |
487 | * Get the record cache |
488 | * |
489 | * @return \VuFind\Record\Cache |
490 | */ |
491 | public function getRecordCache() |
492 | { |
493 | return $this->serviceLocator->get(\VuFind\Record\Cache::class); |
494 | } |
495 | |
496 | /** |
497 | * Get the record router. |
498 | * |
499 | * @return \VuFind\Record\Router |
500 | */ |
501 | public function getRecordRouter() |
502 | { |
503 | return $this->serviceLocator->get(\VuFind\Record\Router::class); |
504 | } |
505 | |
506 | /** |
507 | * Get a database table object. |
508 | * |
509 | * @param string $table Name of table to retrieve |
510 | * |
511 | * @return \VuFind\Db\Table\Gateway |
512 | */ |
513 | public function getTable($table) |
514 | { |
515 | return $this->serviceLocator->get(\VuFind\Db\Table\PluginManager::class) |
516 | ->get($table); |
517 | } |
518 | |
519 | /** |
520 | * Get a database service object. |
521 | * |
522 | * @param class-string<T> $name Name of service to retrieve |
523 | * |
524 | * @template T |
525 | * |
526 | * @return T |
527 | */ |
528 | public function getDbService(string $name): \VuFind\Db\Service\DbServiceInterface |
529 | { |
530 | return $this->serviceLocator->get(\VuFind\Db\Service\PluginManager::class) |
531 | ->get($name); |
532 | } |
533 | |
534 | /** |
535 | * Get the full URL to one of VuFind's routes. |
536 | * |
537 | * @param bool|string $route Boolean true for current URL, otherwise name of |
538 | * route to render as URL |
539 | * |
540 | * @return string |
541 | */ |
542 | public function getServerUrl($route = true) |
543 | { |
544 | $serverHelper = $this->getViewRenderer()->plugin('serverurl'); |
545 | return $serverHelper( |
546 | $route === true ? true : $this->url()->fromRoute($route) |
547 | ); |
548 | } |
549 | |
550 | /** |
551 | * Convenience method to make invocation of forward() helper less verbose. |
552 | * |
553 | * @param string $controller Controller to invoke |
554 | * @param string $action Action to invoke |
555 | * @param array $params Extra parameters for the RouteMatch object (no |
556 | * need to provide action here, since $action takes care of that) |
557 | * |
558 | * @return mixed |
559 | */ |
560 | public function forwardTo($controller, $action, $params = []) |
561 | { |
562 | // Inject action into the RouteMatch parameters |
563 | $params['action'] = $action; |
564 | |
565 | // Dispatch the requested controller/action: |
566 | return $this->forward()->dispatch($controller, $params); |
567 | } |
568 | |
569 | /** |
570 | * Check to see if a form was submitted from its post value |
571 | * Also validate the Captcha, if it's activated |
572 | * |
573 | * @param string|string[]|null $submitElements Name of the post field(s) to check |
574 | * to indicate a form submission (or null for default) |
575 | * @param bool $useCaptcha Are we using captcha in this situation? |
576 | * |
577 | * @return bool |
578 | */ |
579 | protected function formWasSubmitted( |
580 | $submitElements = null, |
581 | $useCaptcha = false |
582 | ) { |
583 | $buttonFound = false; |
584 | // Use of 'submit' as an input name was deprecated in release 10.0, but the |
585 | // check is retained for backward compatibility with custom templates. |
586 | $defaultSubmitElements = ['submitButton', 'submit']; |
587 | foreach ((array)($submitElements ?? $defaultSubmitElements) as $submitElement) { |
588 | if ($this->params()->fromPost($submitElement, false)) { |
589 | $buttonFound = true; |
590 | break; |
591 | } |
592 | } |
593 | // Fail if all expected submission elements were missing from the POST or |
594 | // if the form was submitted but expected CAPTCHA does not validate. |
595 | return $buttonFound && (!$useCaptcha || $this->captcha()->verify()); |
596 | } |
597 | |
598 | /** |
599 | * Confirm an action. |
600 | * |
601 | * @param string $title Title of confirm dialog |
602 | * @param string $yesTarget Form target for "confirm" action |
603 | * @param string $noTarget Form target for "cancel" action |
604 | * @param string|array $messages Info messages for confirm dialog |
605 | * @param array $extras Extra details to include in form |
606 | * |
607 | * @return mixed |
608 | */ |
609 | public function confirm( |
610 | $title, |
611 | $yesTarget, |
612 | $noTarget, |
613 | $messages = [], |
614 | $extras = [] |
615 | ) { |
616 | return $this->forwardTo( |
617 | 'Confirm', |
618 | 'Confirm', |
619 | [ |
620 | 'data' => [ |
621 | 'title' => $title, |
622 | 'confirm' => $yesTarget, |
623 | 'cancel' => $noTarget, |
624 | 'messages' => (array)$messages, |
625 | 'extras' => $extras, |
626 | ], |
627 | ] |
628 | ); |
629 | } |
630 | |
631 | /** |
632 | * Prevent session writes -- this is designed to be called prior to time- |
633 | * consuming AJAX operations to help reduce the odds of a timing-related bug |
634 | * that causes the wrong version of session data to be written to disk (see |
635 | * VUFIND-716 for more details). |
636 | * |
637 | * @return void |
638 | */ |
639 | protected function disableSessionWrites() |
640 | { |
641 | $this->serviceLocator->get(\VuFind\Session\Settings::class)->disableWrite(); |
642 | } |
643 | |
644 | /** |
645 | * Get the search memory |
646 | * |
647 | * @return \VuFind\Search\Memory |
648 | */ |
649 | public function getSearchMemory() |
650 | { |
651 | return $this->serviceLocator->get(\VuFind\Search\Memory::class); |
652 | } |
653 | |
654 | /** |
655 | * Are comments enabled? |
656 | * |
657 | * @return bool |
658 | */ |
659 | protected function commentsEnabled() |
660 | { |
661 | $check = $this->serviceLocator |
662 | ->get(\VuFind\Config\AccountCapabilities::class); |
663 | return $check->getCommentSetting() !== 'disabled'; |
664 | } |
665 | |
666 | /** |
667 | * Are lists enabled? |
668 | * |
669 | * @return bool |
670 | */ |
671 | protected function listsEnabled() |
672 | { |
673 | $check = $this->serviceLocator |
674 | ->get(\VuFind\Config\AccountCapabilities::class); |
675 | return $check->getListSetting() !== 'disabled'; |
676 | } |
677 | |
678 | /** |
679 | * Are tags enabled? |
680 | * |
681 | * @return bool |
682 | */ |
683 | protected function tagsEnabled() |
684 | { |
685 | $check = $this->serviceLocator |
686 | ->get(\VuFind\Config\AccountCapabilities::class); |
687 | return $check->getTagSetting() !== 'disabled'; |
688 | } |
689 | |
690 | /** |
691 | * Store a referer (if appropriate) to keep post-login redirect pointing |
692 | * to an appropriate location. This is used when the user clicks the |
693 | * log in link from an arbitrary page or when a password is mistyped; |
694 | * separate logic is used for storing followup information when VuFind |
695 | * forces the user to log in from another context. |
696 | * |
697 | * @param bool $allowCurrentUrl Whether the current URL is valid for followup |
698 | * @param array $extras Extra data for the followup |
699 | * |
700 | * @return void |
701 | */ |
702 | protected function setFollowupUrlToReferer(bool $allowCurrentUrl = true, array $extras = []) |
703 | { |
704 | // lbreferer is the stored current url of the lightbox |
705 | // which overrides the url from the server request when present |
706 | $referer = $this->getRequest()->getQuery()->get( |
707 | 'lbreferer', |
708 | $this->getRequest()->getServer()->get('HTTP_REFERER', null) |
709 | ); |
710 | // Get the referer -- if it's empty, there's nothing to store! Also, |
711 | // if the referer lives outside of VuFind, don't store it! We only |
712 | // want internal post-login redirects. |
713 | if (empty($referer) || !$this->isLocalUrl($referer)) { |
714 | return; |
715 | } |
716 | // If the referer is the MyResearch/Home action, it probably means |
717 | // that the user is repeatedly mistyping their password. We should |
718 | // ignore this and instead rely on any previously stored referer. |
719 | $refererNorm = $this->normalizeUrlForComparison($referer); |
720 | $myResearchHomeUrl = $this->getServerUrl('myresearch-home'); |
721 | $mrhuNorm = $this->normalizeUrlForComparison($myResearchHomeUrl); |
722 | if ($mrhuNorm === $refererNorm) { |
723 | return; |
724 | } |
725 | |
726 | // If the referer is the MyResearch/UserLogin action, it probably means |
727 | // that the user is repeatedly mistyping their password. We should |
728 | // ignore this and instead rely on any previously stored referer. |
729 | $myUserLogin = $this->getServerUrl('myresearch-userlogin'); |
730 | $mulNorm = $this->normalizeUrlForComparison($myUserLogin); |
731 | if (str_starts_with($refererNorm, $mulNorm)) { |
732 | return; |
733 | } |
734 | |
735 | // Check that the referer is not current URL if not allowed: |
736 | if (!$allowCurrentUrl && $this->getRequest()->getUriString() === $referer) { |
737 | return; |
738 | } |
739 | |
740 | // Clear previously stored lightboxParent. |
741 | $this->followup()->clear('lightboxParent'); |
742 | |
743 | // If we got this far, we want to store the referer: |
744 | $this->followup()->store($extras, $referer); |
745 | } |
746 | |
747 | /** |
748 | * Normalize the referer URL so that inconsistencies in protocol and trailing |
749 | * slashes do not break comparisons. |
750 | * |
751 | * @param string $url URL to normalize |
752 | * |
753 | * @return string |
754 | */ |
755 | protected function normalizeUrlForComparison($url) |
756 | { |
757 | $parts = explode('://', $url, 2); |
758 | return trim(end($parts), '/'); |
759 | } |
760 | |
761 | /** |
762 | * Checks if a followup url is set |
763 | * |
764 | * @return bool |
765 | */ |
766 | protected function hasFollowupUrl() |
767 | { |
768 | return null !== $this->followup()->retrieve('url'); |
769 | } |
770 | |
771 | /** |
772 | * Retrieve a referer to keep post-login redirect pointing |
773 | * to an appropriate location. |
774 | * Unset the followup before returning. |
775 | * |
776 | * @param bool $checkRedirect Whether the query should be checked for param 'redirect' |
777 | * |
778 | * @return string |
779 | */ |
780 | protected function getAndClearFollowupUrl($checkRedirect = false) |
781 | { |
782 | if ($url = $this->followup()->retrieveAndClear('url')) { |
783 | $lightboxParent = $this->followup()->retrieveAndClear('lightboxParent'); |
784 | // If a user clicks on the "Your Account" link, we want to be sure |
785 | // they get to their account rather than being redirected to an old |
786 | // followup URL. We'll use a redirect=0 GET flag to indicate this: |
787 | if (!$checkRedirect || $this->params()->fromQuery('redirect', true)) { |
788 | if (null !== $lightboxParent && !$this->inLightbox()) { |
789 | $parentUrl = new \Laminas\Uri\Uri($lightboxParent); |
790 | $params = $parentUrl->getQueryAsArray(); |
791 | $params['lightboxChild'] = $url; |
792 | $parentUrl->setQuery($params); |
793 | return $parentUrl; |
794 | } |
795 | return $url; |
796 | } |
797 | } |
798 | return null; |
799 | } |
800 | |
801 | /** |
802 | * Sometimes we need to unset the followup to trigger default behaviors |
803 | * |
804 | * @return void |
805 | */ |
806 | protected function clearFollowupUrl() |
807 | { |
808 | $this->followup()->clear('isReferrer'); |
809 | $this->followup()->clear('lightboxParent'); |
810 | $this->followup()->clear('url'); |
811 | } |
812 | |
813 | /** |
814 | * Get the tab configuration for this controller. |
815 | * |
816 | * @return \VuFind\RecordTab\TabManager |
817 | */ |
818 | protected function getRecordTabManager() |
819 | { |
820 | return $this->serviceLocator->get(\VuFind\RecordTab\TabManager::class); |
821 | } |
822 | |
823 | /** |
824 | * Are we currently in a lightbox context? |
825 | * |
826 | * @return bool |
827 | */ |
828 | protected function inLightbox() |
829 | { |
830 | return |
831 | $this->params()->fromPost( |
832 | 'layout', |
833 | $this->params()->fromQuery('layout', false) |
834 | ) === 'lightbox' |
835 | || 'layout/lightbox' == $this->layout()->getTemplate(); |
836 | } |
837 | |
838 | /** |
839 | * What login method does the ILS use (password, email, vufind) |
840 | * |
841 | * @param string $target Login target (MultiILS only) |
842 | * |
843 | * @return string |
844 | */ |
845 | protected function getILSLoginMethod($target = '') |
846 | { |
847 | $config = $this->getILS()->checkFunction( |
848 | 'patronLogin', |
849 | ['patron' => ['cat_username' => "$target.login"]] |
850 | ); |
851 | return $config['loginMethod'] ?? 'password'; |
852 | } |
853 | |
854 | /** |
855 | * Get settings required for displaying the catalog login form |
856 | * |
857 | * @return array |
858 | */ |
859 | protected function getILSLoginSettings() |
860 | { |
861 | $targets = null; |
862 | $defaultTarget = null; |
863 | $loginMethod = null; |
864 | $loginMethods = []; |
865 | // Connect to the ILS and check if multiple target support is available: |
866 | $catalog = $this->getILS(); |
867 | if ($catalog->checkCapability('getLoginDrivers')) { |
868 | $targets = $catalog->getLoginDrivers(); |
869 | $defaultTarget = $catalog->getDefaultLoginDriver(); |
870 | foreach ($targets as $t) { |
871 | $loginMethods[$t] = $this->getILSLoginMethod($t); |
872 | } |
873 | } else { |
874 | $loginMethod = $this->getILSLoginMethod(); |
875 | } |
876 | return compact('targets', 'defaultTarget', 'loginMethod', 'loginMethods'); |
877 | } |
878 | |
879 | /** |
880 | * Construct an HTTP 205 (refresh) response. Useful for reporting success |
881 | * in the lightbox without actually rendering content. |
882 | * |
883 | * @return \Laminas\Http\Response |
884 | */ |
885 | protected function getRefreshResponse() |
886 | { |
887 | $response = $this->getResponse(); |
888 | $response->setStatusCode(205); |
889 | return $response; |
890 | } |
891 | |
892 | /** |
893 | * Is the provided URL local to this instance? |
894 | * |
895 | * @param string $url URL to check |
896 | * |
897 | * @return bool |
898 | */ |
899 | protected function isLocalUrl(string $url): bool |
900 | { |
901 | $baseUrlNorm = $this->normalizeUrlForComparison($this->getServerUrl('home')); |
902 | return str_starts_with($this->normalizeUrlForComparison($url), $baseUrlNorm); |
903 | } |
904 | } |