868b7320773182a2cc4e469beb3b1f312825b709
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Core / Bootstrap.php
1 <?php
2 namespace TYPO3\CMS\Core\Core;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use Doctrine\Common\Annotations\AnnotationReader;
18 use Doctrine\Common\Annotations\AnnotationRegistry;
19 use Psr\Http\Message\ServerRequestInterface;
20 use Psr\Http\Server\RequestHandlerInterface;
21 use TYPO3\CMS\Core\Http\MiddlewareDispatcher;
22 use TYPO3\CMS\Core\Http\MiddlewareStackResolver;
23 use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
24 use TYPO3\CMS\Core\Utility\GeneralUtility;
25 use TYPO3\CMS\Core\Utility\MathUtility;
26
27 /**
28 * This class encapsulates bootstrap related methods.
29 * It is required directly as the very first thing in entry scripts and
30 * used to define all base things like constants and paths and so on.
31 *
32 * Most methods in this class have dependencies to each other. They can
33 * not be called in arbitrary order. The methods are ordered top down, so
34 * a method at the beginning has lower dependencies than a method further
35 * down. Do not fiddle with the load order in own scripts except you know
36 * exactly what you are doing!
37 */
38 class Bootstrap
39 {
40 /**
41 * @var \TYPO3\CMS\Core\Core\Bootstrap
42 */
43 protected static $instance = null;
44
45 /**
46 * Unique Request ID
47 *
48 * @var string
49 */
50 protected $requestId;
51
52 /**
53 * The application context
54 *
55 * @var \TYPO3\CMS\Core\Core\ApplicationContext
56 */
57 protected $applicationContext;
58
59 /**
60 * @var array List of early instances
61 */
62 protected $earlyInstances = [];
63
64 /**
65 * @var string Path to install tool
66 */
67 protected $installToolPath;
68
69 /**
70 * A list of all registered request handlers, see the Application class / entry points for the registration
71 * @var \TYPO3\CMS\Core\Http\RequestHandlerInterface[]|\TYPO3\CMS\Core\Console\RequestHandlerInterface[]
72 */
73 protected $availableRequestHandlers = [];
74
75 /**
76 * The Response object when using Request/Response logic
77 * @var \Psr\Http\Message\ResponseInterface
78 * @see shutdown()
79 */
80 protected $response;
81
82 /**
83 * @var bool
84 */
85 protected static $usesComposerClassLoading = false;
86
87 /**
88 * Disable direct creation of this object.
89 * Set unique requestId and the application context
90 *
91 * @var string Application context
92 */
93 protected function __construct($applicationContext)
94 {
95 $this->requestId = substr(md5(uniqid('', true)), 0, 13);
96 $this->applicationContext = new ApplicationContext($applicationContext);
97 }
98
99 /**
100 * @return bool
101 */
102 public static function usesComposerClassLoading()
103 {
104 return self::$usesComposerClassLoading;
105 }
106
107 /**
108 * Disable direct cloning of this object.
109 */
110 protected function __clone()
111 {
112 }
113
114 /**
115 * Return 'this' as singleton
116 *
117 * @return Bootstrap
118 * @internal This is not a public API method, do not use in own extensions
119 */
120 public static function getInstance()
121 {
122 if (is_null(static::$instance)) {
123 $applicationContext = getenv('TYPO3_CONTEXT') ?: (getenv('REDIRECT_TYPO3_CONTEXT') ?: 'Production');
124 self::$instance = new static($applicationContext);
125 self::$instance->defineTypo3RequestTypes();
126 }
127 return static::$instance;
128 }
129
130 /**
131 * Gets the request's unique ID
132 *
133 * @return string Unique request ID
134 * @internal This is not a public API method, do not use in own extensions
135 */
136 public function getRequestId()
137 {
138 return $this->requestId;
139 }
140
141 /**
142 * Returns the application context this bootstrap was started in.
143 *
144 * @return \TYPO3\CMS\Core\Core\ApplicationContext The application context encapsulated in an object
145 * @internal This is not a public API method, do not use in own extensions.
146 * Use \TYPO3\CMS\Core\Utility\GeneralUtility::getApplicationContext() instead
147 */
148 public function getApplicationContext()
149 {
150 return $this->applicationContext;
151 }
152
153 /**
154 * Prevent any unwanted output that may corrupt AJAX/compression.
155 * This does not interfere with "die()" or "echo"+"exit()" messages!
156 *
157 * @return Bootstrap
158 * @internal This is not a public API method, do not use in own extensions
159 */
160 public function startOutputBuffering()
161 {
162 ob_start();
163 return $this;
164 }
165
166 /**
167 * Main entry point called at every request usually from Global scope. Checks if everything is correct,
168 * and loads the Configuration.
169 *
170 * Make sure that the baseSetup() is called before and the class loader is present
171 *
172 * @return Bootstrap
173 */
174 public function configure()
175 {
176 $this->startOutputBuffering()
177 ->loadConfigurationAndInitialize()
178 ->loadTypo3LoadedExtAndExtLocalconf(true)
179 ->setFinalCachingFrameworkCacheConfiguration()
180 ->unsetReservedGlobalVariables()
181 ->loadBaseTca();
182 if (empty($GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey'])) {
183 throw new \RuntimeException(
184 'TYPO3 Encryption is empty. $GLOBALS[\'TYPO3_CONF_VARS\'][\'SYS\'][\'encryptionKey\'] needs to be set for TYPO3 to work securely',
185 1502987245
186 );
187 }
188 return $this;
189 }
190
191 /**
192 * Run the base setup that checks server environment, determines paths,
193 * populates base files and sets common configuration.
194 *
195 * Script execution will be aborted if something fails here.
196 *
197 * @param int $entryPointLevel Number of subdirectories where the entry script is located under the document root
198 * @return Bootstrap
199 * @throws \RuntimeException when TYPO3_REQUESTTYPE was not set before, setRequestType() needs to be called before
200 * @internal This is not a public API method, do not use in own extensions
201 */
202 public function baseSetup($entryPointLevel = 0)
203 {
204 if (!defined('TYPO3_REQUESTTYPE')) {
205 throw new \RuntimeException('No Request Type was set, TYPO3 does not know in which context it is run.', 1450561838);
206 }
207 SystemEnvironmentBuilder::run($entryPointLevel);
208 if (!self::$usesComposerClassLoading && ClassLoadingInformation::isClassLoadingInformationAvailable()) {
209 ClassLoadingInformation::registerClassLoadingInformation();
210 }
211 GeneralUtility::presetApplicationContext($this->applicationContext);
212 return $this;
213 }
214
215 /**
216 * Sets the class loader to the bootstrap
217 *
218 * @param \Composer\Autoload\ClassLoader $classLoader an instance of the class loader
219 * @return Bootstrap
220 * @internal This is not a public API method, do not use in own extensions
221 */
222 public function initializeClassLoader($classLoader)
223 {
224 $this->setEarlyInstance(\Composer\Autoload\ClassLoader::class, $classLoader);
225 if (defined('TYPO3_COMPOSER_MODE') && TYPO3_COMPOSER_MODE) {
226 self::$usesComposerClassLoading = true;
227 }
228
229 /** @see initializeAnnotationRegistry */
230 AnnotationRegistry::registerLoader([$classLoader, 'loadClass']);
231
232 /*
233 * All annotations defined by and for Extbase need to be
234 * ignored during their deprecation. Later, their usage may and
235 * should throw an Exception
236 */
237 AnnotationReader::addGlobalIgnoredName('inject');
238 AnnotationReader::addGlobalIgnoredName('transient');
239 AnnotationReader::addGlobalIgnoredName('lazy');
240 AnnotationReader::addGlobalIgnoredName('validate');
241 AnnotationReader::addGlobalIgnoredName('cascade');
242 AnnotationReader::addGlobalIgnoredName('ignorevalidation');
243 AnnotationReader::addGlobalIgnoredName('cli');
244 AnnotationReader::addGlobalIgnoredName('flushesCaches');
245 AnnotationReader::addGlobalIgnoredName('uuid');
246 AnnotationReader::addGlobalIgnoredName('identity');
247
248 // Annotations used in unit tests
249 AnnotationReader::addGlobalIgnoredName('test');
250
251 // Annotations that control the extension scanner
252 AnnotationReader::addGlobalIgnoredName('extensionScannerIgnoreFile');
253 AnnotationReader::addGlobalIgnoredName('extensionScannerIgnoreLine');
254
255 return $this;
256 }
257
258 /**
259 * checks if LocalConfiguration.php or PackageStates.php is missing,
260 * used to see if a redirect to the install tool is needed
261 *
262 * @return bool TRUE when the essential configuration is available, otherwise FALSE
263 * @internal This is not a public API method, do not use in own extensions
264 */
265 public function checkIfEssentialConfigurationExists()
266 {
267 $configurationManager = new \TYPO3\CMS\Core\Configuration\ConfigurationManager;
268 $this->setEarlyInstance(\TYPO3\CMS\Core\Configuration\ConfigurationManager::class, $configurationManager);
269 return file_exists($configurationManager->getLocalConfigurationFileLocation()) && file_exists(PATH_typo3conf . 'PackageStates.php');
270 }
271
272 /**
273 * Redirect to install tool if LocalConfiguration.php is missing.
274 *
275 * @param int $entryPointLevel Number of subdirectories where the entry script is located under the document root
276 * @internal This is not a public API method, do not use in own extensions
277 */
278 public function redirectToInstallTool($entryPointLevel = 0)
279 {
280 $path = TYPO3_mainDir . 'install.php';
281 if ($entryPointLevel > 0) {
282 $path = str_repeat('../', $entryPointLevel) . $path;
283 }
284 header('Location: ' . $path);
285 die;
286 }
287
288 /**
289 * Adds available request handlers usually done via an application from the outside.
290 *
291 * @param string $requestHandler class which implements the request handler interface
292 * @return Bootstrap
293 * @internal This is not a public API method, do not use in own extensions
294 */
295 public function registerRequestHandlerImplementation($requestHandler)
296 {
297 $this->availableRequestHandlers[] = $requestHandler;
298 return $this;
299 }
300
301 /**
302 * Fetches the request handler that suits the best based on the priority and the interface
303 * Be sure to always have the constants that are defined in $this->defineTypo3RequestTypes() are set,
304 * so most RequestHandlers can check if they can handle the request.
305 *
306 * @param \Psr\Http\Message\ServerRequestInterface|\Symfony\Component\Console\Input\InputInterface $request
307 * @return \TYPO3\CMS\Core\Http\RequestHandlerInterface|\TYPO3\CMS\Core\Console\RequestHandlerInterface
308 * @throws \TYPO3\CMS\Core\Exception
309 * @internal This is not a public API method, do not use in own extensions
310 */
311 protected function resolveRequestHandler($request)
312 {
313 $suitableRequestHandlers = [];
314 foreach ($this->availableRequestHandlers as $requestHandlerClassName) {
315 /** @var \TYPO3\CMS\Core\Http\RequestHandlerInterface|\TYPO3\CMS\Core\Console\RequestHandlerInterface $requestHandler */
316 $requestHandler = GeneralUtility::makeInstance($requestHandlerClassName, $this);
317 if ($requestHandler->canHandleRequest($request)) {
318 $priority = $requestHandler->getPriority();
319 if (isset($suitableRequestHandlers[$priority])) {
320 throw new \TYPO3\CMS\Core\Exception('More than one request handler with the same priority can handle the request, but only one handler may be active at a time!', 1176471352);
321 }
322 $suitableRequestHandlers[$priority] = $requestHandler;
323 }
324 }
325 if (empty($suitableRequestHandlers)) {
326 throw new \TYPO3\CMS\Core\Exception('No suitable request handler found.', 1225418233);
327 }
328 ksort($suitableRequestHandlers);
329 return array_pop($suitableRequestHandlers);
330 }
331
332 /**
333 * Builds a Request instance from the current process, and then resolves the request
334 * through the request handlers depending on Frontend, Backend, CLI etc.
335 *
336 * @param \Psr\Http\Message\RequestInterface|\Symfony\Component\Console\Input\InputInterface $request
337 * @param string $middlewareStackName the name of the middleware, usually "frontend" or "backend" for TYPO3 applications
338 * @return Bootstrap
339 * @throws \TYPO3\CMS\Core\Exception
340 * @internal This is not a public API method, do not use in own extensions
341 */
342 public function handleRequest($request, string $middlewareStackName = null)
343 {
344 // Resolve request handler that were registered based on the Application
345 $requestHandler = $this->resolveRequestHandler($request);
346
347 // The application requested a middleware stack, and the request handler is PSR-15 capable.
348 // Fill the middleware dispatcher; enqueue the request handler as kernel for the middleware stack.
349 if ($request instanceof ServerRequestInterface && $requestHandler instanceof RequestHandlerInterface && $middlewareStackName !== null) {
350 $resolver = new MiddlewareStackResolver(
351 $this->getEarlyInstance(\TYPO3\CMS\Core\Package\PackageManager::class),
352 GeneralUtility::makeInstance(\TYPO3\CMS\Core\Service\DependencyOrderingService::class),
353 $this->getEarlyInstance(\TYPO3\CMS\Core\Cache\CacheManager::class)->getCache('cache_core')
354 );
355 $middlewares = $resolver->resolve($middlewareStackName);
356
357 $dispatcher = new MiddlewareDispatcher($requestHandler, $middlewares);
358 $this->response = $dispatcher->handle($request);
359 } else {
360 // Execute the command which returns a Response object or NULL
361 $this->response = $requestHandler->handleRequest($request);
362 }
363
364 return $this;
365 }
366
367 /**
368 * Outputs content if there is a proper Response object.
369 *
370 * @return Bootstrap
371 */
372 protected function sendResponse()
373 {
374 if ($this->response instanceof \Psr\Http\Message\ResponseInterface && !($this->response instanceof \TYPO3\CMS\Core\Http\NullResponse)) {
375 if (!headers_sent()) {
376 // If the response code was not changed by legacy code (still is 200)
377 // then allow the PSR-7 response object to explicitly set it.
378 // Otherwise let legacy code take precedence.
379 // This code path can be deprecated once we expose the response object to third party code
380 if (http_response_code() === 200) {
381 header('HTTP/' . $this->response->getProtocolVersion() . ' ' . $this->response->getStatusCode() . ' ' . $this->response->getReasonPhrase());
382 }
383
384 foreach ($this->response->getHeaders() as $name => $values) {
385 header($name . ': ' . implode(', ', $values));
386 }
387 }
388 echo $this->response->getBody()->__toString();
389 }
390 return $this;
391 }
392
393 /**
394 * Registers the instance of the specified object for an early boot stage.
395 * On finalizing the Object Manager initialization, all those instances will
396 * be transferred to the Object Manager's registry.
397 *
398 * @param string $objectName Object name, as later used by the Object Manager
399 * @param object $instance The instance to register
400 * @internal This is not a public API method, do not use in own extensions
401 */
402 public function setEarlyInstance($objectName, $instance)
403 {
404 $this->earlyInstances[$objectName] = $instance;
405 }
406
407 /**
408 * Returns an instance which was registered earlier through setEarlyInstance()
409 *
410 * @param string $objectName Object name of the registered instance
411 * @return object
412 * @throws \TYPO3\CMS\Core\Exception
413 * @internal This is not a public API method, do not use in own extensions
414 */
415 public function getEarlyInstance($objectName)
416 {
417 if (!isset($this->earlyInstances[$objectName])) {
418 throw new \TYPO3\CMS\Core\Exception('Unknown early instance "' . $objectName . '"', 1365167380);
419 }
420 return $this->earlyInstances[$objectName];
421 }
422
423 /**
424 * Returns all registered early instances indexed by object name
425 *
426 * @return array
427 * @internal This is not a public API method, do not use in own extensions
428 */
429 public function getEarlyInstances()
430 {
431 return $this->earlyInstances;
432 }
433
434 /**
435 * Includes LocalConfiguration.php and sets several
436 * global settings depending on configuration.
437 *
438 * @param bool $allowCaching Whether to allow caching - affects cache_core (autoloader)
439 * @param string $packageManagerClassName Define an alternative package manager implementation (usually for the installer)
440 * @return Bootstrap
441 * @internal This is not a public API method, do not use in own extensions
442 */
443 public function loadConfigurationAndInitialize($allowCaching = true, $packageManagerClassName = \TYPO3\CMS\Core\Package\PackageManager::class)
444 {
445 $this->populateLocalConfiguration()
446 ->initializeErrorHandling();
447 if (!$allowCaching) {
448 $this->disableCoreCache();
449 }
450 $this->initializeCachingFramework()
451 ->initializePackageManagement($packageManagerClassName)
452 ->initializeRuntimeActivatedPackagesFromConfiguration()
453 ->setDefaultTimezone()
454 ->initializeL10nLocales()
455 ->setMemoryLimit();
456 return $this;
457 }
458
459 /**
460 * Initializes the package system and loads the package configuration and settings
461 * provided by the packages.
462 *
463 * @param string $packageManagerClassName Define an alternative package manager implementation (usually for the installer)
464 * @return Bootstrap
465 * @internal This is not a public API method, do not use in own extensions
466 */
467 public function initializePackageManagement($packageManagerClassName)
468 {
469 /** @var \TYPO3\CMS\Core\Package\PackageManager $packageManager */
470 $packageManager = new $packageManagerClassName();
471 $this->setEarlyInstance(\TYPO3\CMS\Core\Package\PackageManager::class, $packageManager);
472 ExtensionManagementUtility::setPackageManager($packageManager);
473 $packageManager->injectCoreCache($this->getEarlyInstance(\TYPO3\CMS\Core\Cache\CacheManager::class)->getCache('cache_core'));
474 $dependencyResolver = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Package\DependencyResolver::class);
475 $dependencyResolver->injectDependencyOrderingService(GeneralUtility::makeInstance(\TYPO3\CMS\Core\Service\DependencyOrderingService::class));
476 $packageManager->injectDependencyResolver($dependencyResolver);
477 $packageManager->initialize();
478 GeneralUtility::setSingletonInstance(\TYPO3\CMS\Core\Package\PackageManager::class, $packageManager);
479 return $this;
480 }
481
482 /**
483 * Activates a package during runtime. This is used in AdditionalConfiguration.php
484 * to enable extensions under conditions.
485 *
486 * @return Bootstrap
487 */
488 protected function initializeRuntimeActivatedPackagesFromConfiguration()
489 {
490 $packages = $GLOBALS['TYPO3_CONF_VARS']['EXT']['runtimeActivatedPackages'] ?? [];
491 if (!empty($packages)) {
492 /** @var \TYPO3\CMS\Core\Package\PackageManager $packageManager */
493 $packageManager = $this->getEarlyInstance(\TYPO3\CMS\Core\Package\PackageManager::class);
494 foreach ($packages as $runtimeAddedPackageKey) {
495 $packageManager->activatePackageDuringRuntime($runtimeAddedPackageKey);
496 }
497 }
498 return $this;
499 }
500
501 /**
502 * Load ext_localconf of extensions
503 *
504 * @param bool $allowCaching
505 * @return Bootstrap
506 * @internal This is not a public API method, do not use in own extensions
507 */
508 public function loadTypo3LoadedExtAndExtLocalconf($allowCaching = true)
509 {
510 ExtensionManagementUtility::loadExtLocalconf($allowCaching);
511 return $this;
512 }
513
514 /**
515 * We need an early instance of the configuration manager.
516 * Since makeInstance relies on the object configuration, we create it here with new instead.
517 *
518 * @return Bootstrap
519 * @internal This is not a public API method, do not use in own extensions
520 */
521 public function populateLocalConfiguration()
522 {
523 try {
524 $configurationManager = $this->getEarlyInstance(\TYPO3\CMS\Core\Configuration\ConfigurationManager::class);
525 } catch (\TYPO3\CMS\Core\Exception $exception) {
526 $configurationManager = new \TYPO3\CMS\Core\Configuration\ConfigurationManager();
527 $this->setEarlyInstance(\TYPO3\CMS\Core\Configuration\ConfigurationManager::class, $configurationManager);
528 }
529 $configurationManager->exportConfiguration();
530 return $this;
531 }
532
533 /**
534 * Set cache_core to null backend, effectively disabling eg. the cache for ext_localconf and PackageManager etc.
535 *
536 * @return \TYPO3\CMS\Core\Core\Bootstrap
537 * @internal This is not a public API method, do not use in own extensions
538 */
539 public function disableCoreCache()
540 {
541 $GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']['cache_core']['backend']
542 = \TYPO3\CMS\Core\Cache\Backend\NullBackend::class;
543 unset($GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']['cache_core']['options']);
544 return $this;
545 }
546
547 /**
548 * Initialize caching framework, and re-initializes it (e.g. in the install tool) by recreating the instances
549 * again despite the Singleton instance
550 *
551 * @return Bootstrap
552 * @internal This is not a public API method, do not use in own extensions
553 */
554 public function initializeCachingFramework()
555 {
556 $cacheManager = new \TYPO3\CMS\Core\Cache\CacheManager();
557 $cacheManager->setCacheConfigurations($GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']);
558 GeneralUtility::setSingletonInstance(\TYPO3\CMS\Core\Cache\CacheManager::class, $cacheManager);
559 $this->setEarlyInstance(\TYPO3\CMS\Core\Cache\CacheManager::class, $cacheManager);
560 return $this;
561 }
562
563 /**
564 * Set default timezone
565 *
566 * @return Bootstrap
567 */
568 protected function setDefaultTimezone()
569 {
570 $timeZone = $GLOBALS['TYPO3_CONF_VARS']['SYS']['phpTimeZone'];
571 if (empty($timeZone)) {
572 // Time zone from the server environment (TZ env or OS query)
573 $defaultTimeZone = @date_default_timezone_get();
574 if ($defaultTimeZone !== '') {
575 $timeZone = $defaultTimeZone;
576 } else {
577 $timeZone = 'UTC';
578 }
579 }
580 // Set default to avoid E_WARNINGs with PHP > 5.3
581 date_default_timezone_set($timeZone);
582 return $this;
583 }
584
585 /**
586 * Initialize the locales handled by TYPO3
587 *
588 * @return Bootstrap
589 */
590 protected function initializeL10nLocales()
591 {
592 \TYPO3\CMS\Core\Localization\Locales::initialize();
593 return $this;
594 }
595
596 /**
597 * Configure and set up exception and error handling
598 *
599 * @return Bootstrap
600 * @throws \RuntimeException
601 */
602 protected function initializeErrorHandling()
603 {
604 $productionExceptionHandlerClassName = $GLOBALS['TYPO3_CONF_VARS']['SYS']['productionExceptionHandler'];
605 $debugExceptionHandlerClassName = $GLOBALS['TYPO3_CONF_VARS']['SYS']['debugExceptionHandler'];
606
607 $errorHandlerClassName = $GLOBALS['TYPO3_CONF_VARS']['SYS']['errorHandler'];
608 $errorHandlerErrors = $GLOBALS['TYPO3_CONF_VARS']['SYS']['errorHandlerErrors'];
609 $exceptionalErrors = $GLOBALS['TYPO3_CONF_VARS']['SYS']['exceptionalErrors'];
610
611 $displayErrorsSetting = (int)$GLOBALS['TYPO3_CONF_VARS']['SYS']['displayErrors'];
612 switch ($displayErrorsSetting) {
613 case -1:
614 $ipMatchesDevelopmentSystem = GeneralUtility::cmpIP(GeneralUtility::getIndpEnv('REMOTE_ADDR'), $GLOBALS['TYPO3_CONF_VARS']['SYS']['devIPmask']);
615 $exceptionHandlerClassName = $ipMatchesDevelopmentSystem ? $debugExceptionHandlerClassName : $productionExceptionHandlerClassName;
616 $displayErrors = $ipMatchesDevelopmentSystem ? 1 : 0;
617 $exceptionalErrors = $ipMatchesDevelopmentSystem ? $exceptionalErrors : 0;
618 break;
619 case 0:
620 $exceptionHandlerClassName = $productionExceptionHandlerClassName;
621 $displayErrors = 0;
622 break;
623 case 1:
624 $exceptionHandlerClassName = $debugExceptionHandlerClassName;
625 $displayErrors = 1;
626 break;
627 default:
628 // Throw exception if an invalid option is set.
629 throw new \RuntimeException(
630 'The option $TYPO3_CONF_VARS[SYS][displayErrors] is not set to "-1", "0" or "1".',
631 1476046290
632 );
633 }
634 @ini_set('display_errors', $displayErrors);
635
636 if (!empty($errorHandlerClassName)) {
637 // Register an error handler for the given errorHandlerError
638 $errorHandler = GeneralUtility::makeInstance($errorHandlerClassName, $errorHandlerErrors);
639 $errorHandler->setExceptionalErrors($exceptionalErrors);
640 if (is_callable([$errorHandler, 'setDebugMode'])) {
641 $errorHandler->setDebugMode($displayErrors === 1);
642 }
643 }
644 if (!empty($exceptionHandlerClassName)) {
645 // Registering the exception handler is done in the constructor
646 GeneralUtility::makeInstance($exceptionHandlerClassName);
647 }
648 return $this;
649 }
650
651 /**
652 * Set PHP memory limit depending on value of
653 * $GLOBALS['TYPO3_CONF_VARS']['SYS']['setMemoryLimit']
654 *
655 * @return Bootstrap
656 */
657 protected function setMemoryLimit()
658 {
659 if ((int)$GLOBALS['TYPO3_CONF_VARS']['SYS']['setMemoryLimit'] > 16) {
660 @ini_set('memory_limit', ((int)$GLOBALS['TYPO3_CONF_VARS']['SYS']['setMemoryLimit'] . 'm'));
661 }
662 return $this;
663 }
664
665 /**
666 * Define TYPO3_REQUESTTYPE* constants that can be used for developers to see if any context has been hit
667 * also see setRequestType(). Is done at the very beginning so these parameters are always available.
668 *
669 * @return Bootstrap
670 */
671 protected function defineTypo3RequestTypes()
672 {
673 define('TYPO3_REQUESTTYPE_FE', 1);
674 define('TYPO3_REQUESTTYPE_BE', 2);
675 define('TYPO3_REQUESTTYPE_CLI', 4);
676 define('TYPO3_REQUESTTYPE_AJAX', 8);
677 define('TYPO3_REQUESTTYPE_INSTALL', 16);
678 }
679
680 /**
681 * Defines the TYPO3_REQUESTTYPE constant so the environment knows which context the request is running.
682 *
683 * @throws \RuntimeException if the method was already called during a request
684 * @return Bootstrap
685 */
686 public function setRequestType($requestType)
687 {
688 if (defined('TYPO3_REQUESTTYPE')) {
689 throw new \RuntimeException('TYPO3_REQUESTTYPE has already been set, cannot be called multiple times', 1450561878);
690 }
691 define('TYPO3_REQUESTTYPE', $requestType);
692 return $this;
693 }
694
695 /**
696 * Extensions may register new caches, so we set the
697 * global cache array to the manager again at this point
698 *
699 * @return Bootstrap
700 * @internal This is not a public API method, do not use in own extensions
701 */
702 public function setFinalCachingFrameworkCacheConfiguration()
703 {
704 $this->getEarlyInstance(\TYPO3\CMS\Core\Cache\CacheManager::class)->setCacheConfigurations($GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']);
705 return $this;
706 }
707
708 /**
709 * Unsetting reserved global variables:
710 * Those are set in "ext:core/ext_tables.php" file:
711 *
712 * @return Bootstrap
713 * @internal This is not a public API method, do not use in own extensions
714 */
715 public function unsetReservedGlobalVariables()
716 {
717 unset($GLOBALS['PAGES_TYPES']);
718 unset($GLOBALS['TCA']);
719 unset($GLOBALS['TBE_MODULES']);
720 unset($GLOBALS['TBE_STYLES']);
721 unset($GLOBALS['BE_USER']);
722 // Those set otherwise:
723 unset($GLOBALS['TBE_MODULES_EXT']);
724 unset($GLOBALS['TCA_DESCR']);
725 unset($GLOBALS['LOCAL_LANG']);
726 return $this;
727 }
728
729 /**
730 * Check adminOnly configuration variable and redirects
731 * to an URL in file typo3conf/LOCK_BACKEND or exit the script
732 *
733 * @throws \RuntimeException
734 * @param bool $forceProceeding if this option is set, the bootstrap will proceed even if the user is logged in (usually only needed for special AJAX cases, see AjaxRequestHandler)
735 * @return Bootstrap
736 * @internal This is not a public API method, do not use in own extensions
737 */
738 public function checkLockedBackendAndRedirectOrDie($forceProceeding = false)
739 {
740 if ($GLOBALS['TYPO3_CONF_VARS']['BE']['adminOnly'] < 0) {
741 throw new \RuntimeException('TYPO3 Backend locked: Backend and Install Tool are locked for maintenance. [BE][adminOnly] is set to "' . (int)$GLOBALS['TYPO3_CONF_VARS']['BE']['adminOnly'] . '".', 1294586847);
742 }
743 if (@is_file(PATH_typo3conf . 'LOCK_BACKEND') && $forceProceeding === false) {
744 $fileContent = file_get_contents(PATH_typo3conf . 'LOCK_BACKEND');
745 if ($fileContent) {
746 header('Location: ' . $fileContent);
747 } else {
748 throw new \RuntimeException('TYPO3 Backend locked: Browser backend is locked for maintenance. Remove lock by removing the file "typo3conf/LOCK_BACKEND" or use CLI-scripts.', 1294586848);
749 }
750 die;
751 }
752 return $this;
753 }
754
755 /**
756 * Compare client IP with IPmaskList and exit the script run
757 * if the client is not allowed to access the backend
758 *
759 * @return Bootstrap
760 * @internal This is not a public API method, do not use in own extensions
761 * @throws \RuntimeException
762 */
763 public function checkBackendIpOrDie()
764 {
765 if (trim($GLOBALS['TYPO3_CONF_VARS']['BE']['IPmaskList'])) {
766 if (!GeneralUtility::cmpIP(GeneralUtility::getIndpEnv('REMOTE_ADDR'), $GLOBALS['TYPO3_CONF_VARS']['BE']['IPmaskList'])) {
767 throw new \RuntimeException('TYPO3 Backend access denied: The IP address of your client does not match the list of allowed IP addresses.', 1389265900);
768 }
769 }
770 return $this;
771 }
772
773 /**
774 * Check lockSSL configuration variable and redirect
775 * to https version of the backend if needed
776 *
777 * @return Bootstrap
778 * @internal This is not a public API method, do not use in own extensions
779 * @throws \RuntimeException
780 */
781 public function checkSslBackendAndRedirectIfNeeded()
782 {
783 if ((bool)$GLOBALS['TYPO3_CONF_VARS']['BE']['lockSSL'] && !GeneralUtility::getIndpEnv('TYPO3_SSL')) {
784 if ((int)$GLOBALS['TYPO3_CONF_VARS']['BE']['lockSSLPort']) {
785 $sslPortSuffix = ':' . (int)$GLOBALS['TYPO3_CONF_VARS']['BE']['lockSSLPort'];
786 } else {
787 $sslPortSuffix = '';
788 }
789 list(, $url) = explode('://', GeneralUtility::getIndpEnv('TYPO3_SITE_URL') . TYPO3_mainDir, 2);
790 list($server, $address) = explode('/', $url, 2);
791 header('Location: https://' . $server . $sslPortSuffix . '/' . $address);
792 die;
793 }
794 return $this;
795 }
796
797 /**
798 * Load $TCA
799 *
800 * This will mainly set up $TCA through extMgm API.
801 *
802 * @param bool $allowCaching True, if loading TCA from cache is allowed
803 * @return Bootstrap
804 * @internal This is not a public API method, do not use in own extensions
805 */
806 public function loadBaseTca(bool $allowCaching = true): Bootstrap
807 {
808 ExtensionManagementUtility::loadBaseTca($allowCaching);
809 return $this;
810 }
811
812 /**
813 * Load ext_tables and friends.
814 *
815 * This will mainly load and execute ext_tables.php files of loaded extensions
816 * or the according cache file if exists.
817 *
818 * @param bool $allowCaching True, if reading compiled ext_tables file from cache is allowed
819 * @return Bootstrap
820 * @internal This is not a public API method, do not use in own extensions
821 */
822 public function loadExtTables(bool $allowCaching = true): Bootstrap
823 {
824 ExtensionManagementUtility::loadExtTables($allowCaching);
825 $this->runExtTablesPostProcessingHooks();
826 return $this;
827 }
828
829 /**
830 * Check for registered ext tables hooks and run them
831 *
832 * @throws \UnexpectedValueException
833 */
834 protected function runExtTablesPostProcessingHooks()
835 {
836 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['GLOBAL']['extTablesInclusion-PostProcessing'] ?? [] as $className) {
837 /** @var $hookObject \TYPO3\CMS\Core\Database\TableConfigurationPostProcessingHookInterface */
838 $hookObject = GeneralUtility::makeInstance($className);
839 if (!$hookObject instanceof \TYPO3\CMS\Core\Database\TableConfigurationPostProcessingHookInterface) {
840 throw new \UnexpectedValueException(
841 '$hookObject "' . $className . '" must implement interface TYPO3\\CMS\\Core\\Database\\TableConfigurationPostProcessingHookInterface',
842 1320585902
843 );
844 }
845 $hookObject->processData();
846 }
847 }
848
849 /**
850 * Initialize the Routing for the TYPO3 Backend
851 * Loads all routes registered inside all packages and stores them inside the Router
852 *
853 * @return Bootstrap
854 * @internal This is not a public API method, do not use in own extensions
855 */
856 public function initializeBackendRouter()
857 {
858 // See if the Routes.php from all active packages have been built together already
859 $cacheIdentifier = 'BackendRoutesFromPackages_' . sha1((TYPO3_version . PATH_site . 'BackendRoutesFromPackages'));
860
861 /** @var $codeCache \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface */
862 $codeCache = $this->getEarlyInstance(\TYPO3\CMS\Core\Cache\CacheManager::class)->getCache('cache_core');
863 $routesFromPackages = [];
864 if ($codeCache->has($cacheIdentifier)) {
865 // substr is necessary, because the php frontend wraps php code around the cache value
866 $routesFromPackages = unserialize(substr($codeCache->get($cacheIdentifier), 6, -2));
867 } else {
868 // Loop over all packages and check for a Configuration/Backend/Routes.php file
869 $packageManager = $this->getEarlyInstance(\TYPO3\CMS\Core\Package\PackageManager::class);
870 $packages = $packageManager->getActivePackages();
871 foreach ($packages as $package) {
872 $routesFileNameForPackage = $package->getPackagePath() . 'Configuration/Backend/Routes.php';
873 if (file_exists($routesFileNameForPackage)) {
874 $definedRoutesInPackage = require $routesFileNameForPackage;
875 if (is_array($definedRoutesInPackage)) {
876 $routesFromPackages = array_merge($routesFromPackages, $definedRoutesInPackage);
877 }
878 }
879 $routesFileNameForPackage = $package->getPackagePath() . 'Configuration/Backend/AjaxRoutes.php';
880 if (file_exists($routesFileNameForPackage)) {
881 $definedRoutesInPackage = require $routesFileNameForPackage;
882 if (is_array($definedRoutesInPackage)) {
883 foreach ($definedRoutesInPackage as $routeIdentifier => $routeOptions) {
884 // prefix the route with "ajax_" as "namespace"
885 $routeOptions['path'] = '/ajax' . $routeOptions['path'];
886 $routesFromPackages['ajax_' . $routeIdentifier] = $routeOptions;
887 $routesFromPackages['ajax_' . $routeIdentifier]['ajax'] = true;
888 }
889 }
890 }
891 }
892 // Store the data from all packages in the cache
893 $codeCache->set($cacheIdentifier, serialize($routesFromPackages));
894 }
895
896 // Build Route objects from the data
897 $router = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\Router::class);
898 foreach ($routesFromPackages as $name => $options) {
899 $path = $options['path'];
900 unset($options['path']);
901 $route = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\Route::class, $path, $options);
902 $router->addRoute($name, $route);
903 }
904 return $this;
905 }
906
907 /**
908 * Initialize backend user object in globals
909 *
910 * @param string $className usually \TYPO3\CMS\Core\Authentication\BackendUserAuthentication::class but can be used for CLI
911 * @return Bootstrap
912 * @internal This is not a public API method, do not use in own extensions
913 */
914 public function initializeBackendUser($className = \TYPO3\CMS\Core\Authentication\BackendUserAuthentication::class)
915 {
916 /** @var $backendUser \TYPO3\CMS\Core\Authentication\BackendUserAuthentication */
917 $backendUser = GeneralUtility::makeInstance($className);
918 // The global must be available very early, because methods below
919 // might trigger code which relies on it. See: #45625
920 $GLOBALS['BE_USER'] = $backendUser;
921 $backendUser->start();
922 return $this;
923 }
924
925 /**
926 * Initializes and ensures authenticated access
927 *
928 * @internal This is not a public API method, do not use in own extensions
929 * @param bool $proceedIfNoUserIsLoggedIn if set to TRUE, no forced redirect to the login page will be done
930 * @return \TYPO3\CMS\Core\Core\Bootstrap
931 */
932 public function initializeBackendAuthentication($proceedIfNoUserIsLoggedIn = false)
933 {
934 $GLOBALS['BE_USER']->backendCheckLogin($proceedIfNoUserIsLoggedIn);
935 return $this;
936 }
937
938 /**
939 * Initialize language object
940 *
941 * @return Bootstrap
942 * @internal This is not a public API method, do not use in own extensions
943 */
944 public function initializeLanguageObject()
945 {
946 /** @var $GLOBALS['LANG'] \TYPO3\CMS\Core\Localization\LanguageService */
947 $GLOBALS['LANG'] = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Localization\LanguageService::class);
948 $GLOBALS['LANG']->init($GLOBALS['BE_USER']->uc['lang']);
949 return $this;
950 }
951
952 /**
953 * Throw away all output that may have happened during bootstrapping by weird extensions
954 *
955 * @return Bootstrap
956 * @internal This is not a public API method, do not use in own extensions
957 */
958 public function endOutputBufferingAndCleanPreviousOutput()
959 {
960 ob_clean();
961 return $this;
962 }
963
964 /**
965 * Initialize output compression if configured
966 *
967 * @return Bootstrap
968 * @internal This is not a public API method, do not use in own extensions
969 */
970 public function initializeOutputCompression()
971 {
972 if (extension_loaded('zlib') && $GLOBALS['TYPO3_CONF_VARS']['BE']['compressionLevel']) {
973 if (MathUtility::canBeInterpretedAsInteger($GLOBALS['TYPO3_CONF_VARS']['BE']['compressionLevel'])) {
974 @ini_set('zlib.output_compression_level', $GLOBALS['TYPO3_CONF_VARS']['BE']['compressionLevel']);
975 }
976 ob_start('ob_gzhandler');
977 }
978 return $this;
979 }
980
981 /**
982 * Send HTTP headers if configured
983 *
984 * @return Bootstrap
985 * @internal This is not a public API method, do not use in own extensions
986 */
987 public function sendHttpHeaders()
988 {
989 array_map('header', $GLOBALS['TYPO3_CONF_VARS']['BE']['HTTP']['Response']['Headers'] ?? []);
990 return $this;
991 }
992
993 /**
994 * Things that should be performed to shut down the framework.
995 * This method is called in all important scripts for a clean
996 * shut down of the system.
997 *
998 * @return Bootstrap
999 * @internal This is not a public API method, do not use in own extensions
1000 */
1001 public function shutdown()
1002 {
1003 $this->sendResponse();
1004 return $this;
1005 }
1006
1007 /**
1008 * Provides an instance of "template" for backend-modules to
1009 * work with.
1010 *
1011 * @return Bootstrap
1012 * @internal This is not a public API method, do not use in own extensions
1013 */
1014 public function initializeBackendTemplate()
1015 {
1016 $GLOBALS['TBE_TEMPLATE'] = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Template\DocumentTemplate::class);
1017 return $this;
1018 }
1019 }