Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 130 |
|
0.00% |
0 / 17 |
CRAP | |
0.00% |
0 / 1 |
Bootstrapper | |
0.00% |
0 / 130 |
|
0.00% |
0 / 17 |
1482 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
2 | |||
bootstrap | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
12 | |||
getDbService | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
initTestMode | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
initSystemStatus | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
12 | |||
initTimeZone | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
initContext | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
12 | |||
initViewModel | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
2 | |||
initUserLanguage | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
12 | |||
initTheme | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
2 | |||
initLoginTokenManager | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
2 | |||
initExceptionBasedHttpStatuses | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
20 | |||
initSearch | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
2 | |||
initErrorLogging | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
30 | |||
initRenderErrorEvent | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
2 | |||
initContentSecurityPolicy | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
12 | |||
initRateLimiter | |
0.00% |
0 / 15 |
|
0.00% |
0 / 1 |
20 |
1 | <?php |
2 | |
3 | /** |
4 | * VuFind Bootstrapper |
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 Bootstrap |
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; |
31 | |
32 | use Laminas\Mvc\MvcEvent; |
33 | use Laminas\Router\Http\RouteMatch; |
34 | use Psr\Container\ContainerInterface; |
35 | use VuFind\I18n\Locale\LocaleSettings; |
36 | |
37 | /** |
38 | * VuFind Bootstrapper |
39 | * |
40 | * @category VuFind |
41 | * @package Bootstrap |
42 | * @author Demian Katz <demian.katz@villanova.edu> |
43 | * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License |
44 | * @link https://vufind.org Main Site |
45 | */ |
46 | class Bootstrapper |
47 | { |
48 | /** |
49 | * Main VuFind configuration |
50 | * |
51 | * @var \Laminas\Config\Config |
52 | */ |
53 | protected $config; |
54 | |
55 | /** |
56 | * Service manager |
57 | * |
58 | * @var ContainerInterface |
59 | */ |
60 | protected $container; |
61 | |
62 | /** |
63 | * Current MVC event |
64 | * |
65 | * @var MvcEvent |
66 | */ |
67 | protected $event; |
68 | |
69 | /** |
70 | * Event manager |
71 | * |
72 | * @var \Laminas\EventManager\EventManagerInterface |
73 | */ |
74 | protected $events; |
75 | |
76 | /** |
77 | * Constructor |
78 | * |
79 | * @param MvcEvent $event Laminas MVC Event object |
80 | */ |
81 | public function __construct(MvcEvent $event) |
82 | { |
83 | $this->event = $event; |
84 | $app = $event->getApplication(); |
85 | $this->events = $app->getEventManager(); |
86 | $this->container = $app->getServiceManager(); |
87 | $this->config = $this->container->get(\VuFind\Config\PluginManager::class) |
88 | ->get('config'); |
89 | } |
90 | |
91 | /** |
92 | * Bootstrap all necessary resources. |
93 | * |
94 | * @return void |
95 | */ |
96 | public function bootstrap(): void |
97 | { |
98 | // automatically call all methods starting with "init": |
99 | $methods = get_class_methods($this); |
100 | foreach ($methods as $method) { |
101 | if (str_starts_with($method, 'init')) { |
102 | $this->$method(); |
103 | } |
104 | } |
105 | } |
106 | |
107 | /** |
108 | * Get a database service object. |
109 | * |
110 | * @param class-string<T> $name Name of service to retrieve |
111 | * |
112 | * @template T |
113 | * |
114 | * @return T |
115 | */ |
116 | public function getDbService(string $name): \VuFind\Db\Service\DbServiceInterface |
117 | { |
118 | return $this->container->get(\VuFind\Db\Service\PluginManager::class)->get($name); |
119 | } |
120 | |
121 | /** |
122 | * Set up cookie to flag test mode. |
123 | * |
124 | * @return void |
125 | */ |
126 | protected function initTestMode(): void |
127 | { |
128 | // If we're in test mode (as determined by the config.ini property installed |
129 | // by the build.xml startup process), set a cookie so the front-end code can |
130 | // act accordingly. (This is needed to work around a problem where opening |
131 | // print dialogs during testing stalls the automated test process). |
132 | if ($this->config->System->runningTestSuite ?? false) { |
133 | $cm = $this->container->get(\VuFind\Cookie\CookieManager::class); |
134 | $cm->set('VuFindTestSuiteRunning', '1', 0, false); |
135 | } |
136 | } |
137 | |
138 | /** |
139 | * If the system is offline, set up a handler to override the routing output. |
140 | * |
141 | * @return void |
142 | */ |
143 | protected function initSystemStatus(): void |
144 | { |
145 | // If the system is unavailable and we're not in the console, forward to the |
146 | // unavailable page. |
147 | if (PHP_SAPI !== 'cli' && !($this->config->System->available ?? true)) { |
148 | $callback = function ($e) { |
149 | $routeMatch = new RouteMatch( |
150 | ['controller' => 'Error', 'action' => 'Unavailable'], |
151 | 1 |
152 | ); |
153 | $routeMatch->setMatchedRouteName('error-unavailable'); |
154 | $e->setRouteMatch($routeMatch); |
155 | }; |
156 | $this->events->attach('route', $callback); |
157 | } |
158 | } |
159 | |
160 | /** |
161 | * Initializes timezone value |
162 | * |
163 | * @return void |
164 | */ |
165 | protected function initTimeZone(): void |
166 | { |
167 | date_default_timezone_set($this->config->Site->timezone); |
168 | } |
169 | |
170 | /** |
171 | * Set view variables representing the current context. |
172 | * |
173 | * @return void |
174 | */ |
175 | protected function initContext(): void |
176 | { |
177 | $callback = function ($event) { |
178 | if (PHP_SAPI !== 'cli') { |
179 | $viewModel = $this->container->get('ViewManager')->getViewModel(); |
180 | |
181 | // Grab the template name from the first child -- we can use this to |
182 | // figure out the current template context. |
183 | $children = $viewModel->getChildren(); |
184 | if (!empty($children)) { |
185 | $parts = explode('/', $children[0]->getTemplate()); |
186 | $viewModel->setVariable('templateDir', $parts[0]); |
187 | $viewModel->setVariable( |
188 | 'templateName', |
189 | $parts[1] ?? null |
190 | ); |
191 | } |
192 | } |
193 | }; |
194 | $this->events->attach('dispatch', $callback); |
195 | } |
196 | |
197 | /** |
198 | * Set up the initial view model. |
199 | * |
200 | * @return void |
201 | */ |
202 | protected function initViewModel(): void |
203 | { |
204 | $settings = $this->container->get(LocaleSettings::class); |
205 | $locale = $settings->getUserLocale(); |
206 | $viewModel = $this->container->get('HttpViewManager')->getViewModel(); |
207 | $viewModel->setVariable('userLang', $locale); |
208 | $viewModel->setVariable('allLangs', $settings->getEnabledLocales()); |
209 | $viewModel->setVariable('rtl', $settings->isRightToLeftLocale($locale)); |
210 | } |
211 | |
212 | /** |
213 | * Update language in user account, as needed. |
214 | * |
215 | * @return void |
216 | */ |
217 | protected function initUserLanguage(): void |
218 | { |
219 | $callback = function ($event) { |
220 | // Store last selected language in user account, if applicable: |
221 | $settings = $this->container->get(LocaleSettings::class); |
222 | $language = $settings->getUserLocale(); |
223 | $authManager = $this->container->get(\VuFind\Auth\Manager::class); |
224 | if ( |
225 | ($user = $authManager->getUserObject()) |
226 | && $user->getLastLanguage() != $language |
227 | ) { |
228 | $user->setLastLanguage($language); |
229 | $this->getDbService(\VuFind\Db\Service\UserServiceInterface::class)->persistEntity($user); |
230 | } |
231 | }; |
232 | $this->events->attach('dispatch.error', $callback); |
233 | $this->events->attach('dispatch', $callback); |
234 | } |
235 | |
236 | /** |
237 | * Set up theme handling. |
238 | * |
239 | * @return void |
240 | */ |
241 | protected function initTheme(): void |
242 | { |
243 | // Attach remaining theme configuration to the dispatch event at high |
244 | // priority (TODO: use priority constant once defined by framework): |
245 | $config = $this->config->Site; |
246 | $callback = function ($event) use ($config) { |
247 | $theme = new \VuFindTheme\Initializer($config, $event); |
248 | $theme->init(); |
249 | }; |
250 | $this->events->attach('dispatch.error', $callback, 9000); |
251 | $this->events->attach('dispatch', $callback, 9000); |
252 | } |
253 | |
254 | /** |
255 | * The login token manager needs to be informed after the theme has been initialized, |
256 | * so that it can send warning emails if necessary. |
257 | * |
258 | * @return void |
259 | */ |
260 | protected function initLoginTokenManager(): void |
261 | { |
262 | $dispatchCallback = function () { |
263 | $this->container->get(\VuFind\Auth\LoginTokenManager::class)->themeIsReady(); |
264 | }; |
265 | $finishCallback = function () { |
266 | $this->container->get(\VuFind\Auth\LoginTokenManager::class)->requestIsFinished(); |
267 | }; |
268 | $this->events->attach('dispatch.error', $dispatchCallback, 8000); |
269 | $this->events->attach('dispatch', $dispatchCallback, 8000); |
270 | $this->events->attach('finish', $finishCallback, 8000); |
271 | } |
272 | |
273 | /** |
274 | * Set up custom HTTP status based on exception information. |
275 | * |
276 | * @return void |
277 | */ |
278 | protected function initExceptionBasedHttpStatuses(): void |
279 | { |
280 | // HTTP statuses not needed in console mode: |
281 | if (PHP_SAPI == 'cli') { |
282 | return; |
283 | } |
284 | |
285 | $callback = function ($e) { |
286 | $exception = $e->getParam('exception'); |
287 | if ($exception instanceof \VuFind\Exception\HttpStatusInterface) { |
288 | $response = $e->getResponse(); |
289 | if (!$response) { |
290 | $response = new \Laminas\Http\Response(); |
291 | $e->setResponse($response); |
292 | } |
293 | $response->setStatusCode($exception->getHttpStatus()); |
294 | } |
295 | }; |
296 | $this->events->attach('dispatch.error', $callback); |
297 | } |
298 | |
299 | /** |
300 | * Set up search subsystem. |
301 | * |
302 | * @return void |
303 | */ |
304 | protected function initSearch(): void |
305 | { |
306 | $bm = $this->container->get(\VuFind\Search\BackendManager::class); |
307 | $events = $this->container->get('SharedEventManager'); |
308 | $events->attach( |
309 | \VuFindSearch\Service::class, |
310 | \VuFindSearch\Service::EVENT_RESOLVE, |
311 | [$bm, 'onResolve'] |
312 | ); |
313 | } |
314 | |
315 | /** |
316 | * Set up logging. |
317 | * |
318 | * @return void |
319 | */ |
320 | protected function initErrorLogging(): void |
321 | { |
322 | $callback = function ($event) { |
323 | if ($this->container->has(\VuFind\Log\Logger::class)) { |
324 | $log = $this->container->get(\VuFind\Log\Logger::class); |
325 | if ($log instanceof \VuFind\Log\ExtendedLoggerInterface) { |
326 | $exception = $event->getParam('exception'); |
327 | // Console request does not include server, |
328 | // so use a dummy in that case. |
329 | $server = (PHP_SAPI == 'cli') |
330 | ? new \Laminas\Stdlib\Parameters(['env' => 'console']) |
331 | : $event->getRequest()->getServer(); |
332 | if (!empty($exception)) { |
333 | $log->logException($exception, $server); |
334 | } |
335 | } |
336 | } |
337 | }; |
338 | $this->events->attach('dispatch.error', $callback); |
339 | $this->events->attach('render.error', $callback); |
340 | } |
341 | |
342 | /** |
343 | * Set up handling for rendering problems. |
344 | * |
345 | * @return void |
346 | */ |
347 | protected function initRenderErrorEvent(): void |
348 | { |
349 | // When a render.error is triggered, as a high priority, set a flag in the |
350 | // layout that can be used to suppress actions in the layout templates that |
351 | // might trigger exceptions -- this will greatly increase the odds of showing |
352 | // a user-friendly message instead of a fatal error. |
353 | $callback = function ($event) { |
354 | $viewModel = $this->container->get('ViewManager')->getViewModel(); |
355 | $viewModel->renderingError = true; |
356 | }; |
357 | $this->events->attach('render.error', $callback, 10000); |
358 | } |
359 | |
360 | /** |
361 | * Set up content security policy |
362 | * |
363 | * @return void |
364 | */ |
365 | protected function initContentSecurityPolicy(): void |
366 | { |
367 | if (PHP_SAPI === 'cli') { |
368 | return; |
369 | } |
370 | $headers = $this->event->getResponse()->getHeaders(); |
371 | $cspHeaderGenerator = $this->container |
372 | ->get(\VuFind\Security\CspHeaderGenerator::class); |
373 | foreach ($cspHeaderGenerator->getHeaders() as $cspHeader) { |
374 | $headers->addHeader($cspHeader); |
375 | } |
376 | } |
377 | |
378 | /** |
379 | * Set up rate limiter |
380 | * |
381 | * @return void |
382 | */ |
383 | protected function initRateLimiter(): void |
384 | { |
385 | if (PHP_SAPI === 'cli') { |
386 | return; |
387 | } |
388 | $callback = function ($event) { |
389 | // Create rate limiter manager here so that we don't e.g. initialize the session too early: |
390 | $rateLimiterManager = $this->container->get(\VuFind\RateLimiter\RateLimiterManager::class); |
391 | if (!$rateLimiterManager->isEnabled()) { |
392 | return; |
393 | } |
394 | $result = $rateLimiterManager->check($event); |
395 | if (!$result['allow']) { |
396 | $response = $event->getResponse(); |
397 | $response->setStatusCode(429); |
398 | $response->setContent($result['message']); |
399 | $event->stopPropagation(true); |
400 | return $response; |
401 | } |
402 | }; |
403 | $this->events->attach('dispatch', $callback, 11000); |
404 | } |
405 | } |