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