TYPO3 v15 dev-main snapshot ()

This commit is contained in:
2026-08-10 22:31:09 +02:00
commit af8cc155b5
6818 changed files with 642608 additions and 0 deletions
+135
View File
@@ -0,0 +1,135 @@
<?php
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
namespace TYPO3\CMS\Core\Core;
use TYPO3\CMS\Core\Exception;
/**
* The TYPO3 Context object.
*
* A TYPO3 Application context is something like "Production", "Development",
* "Production/StagingSystem", and is set using the TYPO3_CONTEXT environment variable.
*
* A context can contain arbitrary sub-contexts, which are delimited with slash
* ("Production/StagingSystem", "Production/Staging/Server1"). The top-level
* contexts, however, must be one of "Testing", "Development" and "Production".
*
* Mainly, you will use $context->isProduction(), $context->isTesting() and
* $context->isDevelopment() inside your custom code.
*
* ATTENTION: The Testing context is only used internally when executing TYPO3 Core tests. It must not be used otherwise.
*
* This class is derived from the TYPO3 Flow framework.
* Credits go to the respective authors.
*/
class ApplicationContext
{
/**
* The (internal) context string; could be something like "Development" or "Development/MyLocalMacBook"
*
* @var string
*/
protected $contextString;
/**
* The root context; must be one of "Development", "Testing" or "Production"
*
* @var string
*/
protected $rootContextString;
/**
* The parent context, or NULL if there is no parent context
*
* @var \TYPO3\CMS\Core\Core\ApplicationContext|null
*/
protected $parentContext;
/**
* Initialize the context object.
*
* @param string $contextString
* @throws Exception if the parent context is none of "Development", "Production" or "Testing"
*/
public function __construct($contextString)
{
if (!str_contains($contextString, '/')) {
$this->rootContextString = $contextString;
$this->parentContext = null;
} else {
$contextStringParts = explode('/', $contextString);
$this->rootContextString = $contextStringParts[0];
array_pop($contextStringParts);
$this->parentContext = new self(implode('/', $contextStringParts));
}
if (!in_array($this->rootContextString, ['Development', 'Production', 'Testing'], true)) {
throw new Exception('The given context "' . $contextString . '" was not valid. Only allowed are Development, Production and Testing, including their sub-contexts', 1335436551);
}
$this->contextString = $contextString;
}
/**
* Returns the full context string, for example "Development", or "Production/LiveSystem"
*
* @return string
*/
public function __toString()
{
return $this->contextString;
}
/**
* Returns TRUE if this context is the Development context or a sub-context of it
*
* @return bool
*/
public function isDevelopment()
{
return $this->rootContextString === 'Development';
}
/**
* Returns TRUE if this context is the Production context or a sub-context of it
*
* @return bool
*/
public function isProduction()
{
return $this->rootContextString === 'Production';
}
/**
* Returns TRUE if this context is the Testing context or a sub-context of it
*
* @return bool
*/
public function isTesting()
{
return $this->rootContextString === 'Testing';
}
/**
* Returns the parent context object, if any
*
* @return \TYPO3\CMS\Core\Core\ApplicationContext|null the parent context or NULL, if there is none
*/
public function getParent()
{
return $this->parentContext;
}
}
+32
View File
@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
namespace TYPO3\CMS\Core\Core;
/**
* The base ApplicationInterface which
* is used for all Entry Points for TYPO3, may it be
* Frontend, Backend, Install Tool or Command Line.
* @internal only to be meant for internal Application-level purposes, not part of TYPO3 Core API.
*/
interface ApplicationInterface
{
/**
* Starting point
*/
public function run();
}
+163
View File
@@ -0,0 +1,163 @@
<?php
declare(strict_types=1);
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
namespace TYPO3\CMS\Core\Core;
use Psr\Container\ContainerInterface;
use Psr\EventDispatcher\EventDispatcherInterface;
use TYPO3\CMS\Core\Configuration\Extension\ExtLocalconfFactory;
use TYPO3\CMS\Core\Configuration\Tca\TcaFactory;
use TYPO3\CMS\Core\Core\Event\BootCompletedEvent;
use TYPO3\CMS\Core\DependencyInjection\ContainerBuilder;
use TYPO3\CMS\Core\Package\PackageManager;
use TYPO3\CMS\Core\Schema\TcaSchemaFactory;
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* @internal This is NOT an API class, it is for internal use in TYPO3 core only.
*/
class BootService
{
private ContainerBuilder $containerBuilder;
private ContainerInterface $failsafeContainer;
private ?ContainerInterface $container = null;
public function __construct(ContainerBuilder $containerBuilder, ContainerInterface $failsafeContainer)
{
$this->containerBuilder = $containerBuilder;
$this->failsafeContainer = $failsafeContainer;
}
public function getContainer(bool $allowCaching = true): ContainerInterface
{
return $this->container ?? $this->prepareContainer($allowCaching);
}
private function prepareContainer(bool $allowCaching = true): ContainerInterface
{
$packageManager = $this->failsafeContainer->get(PackageManager::class);
$dependencyInjectionContainerCache = $this->failsafeContainer->get('cache.di');
$failsafe = false;
// Build a non-failsafe container which is required for loading ext_localconf
$this->container = $this->containerBuilder->createDependencyInjectionContainer($packageManager, $dependencyInjectionContainerCache, $failsafe);
$this->container->set('_early.boot-service', $this);
if ($allowCaching) {
$this->container->get('boot.state')->cacheDisabled = false;
$coreCache = Bootstrap::createCache('core');
// Core cache is initialized with a NullBackend in failsafe mode.
// Replace it with a new cache that uses the real backend.
$this->container->set('_early.cache.core', $coreCache);
if (!Environment::isComposerMode()) {
$this->container->get(PackageManager::class)->setPackageCache(Bootstrap::createPackageCache($coreCache));
}
}
return $this->container;
}
/**
* Switch global context to a new context, or revert
* to the original booting container if no container
* is specified
*/
public function makeCurrent(?ContainerInterface $container = null, array $backup = []): array
{
$container = $container ?? $backup['container'] ?? $this->failsafeContainer;
$newBackup = [
'singletonInstances' => GeneralUtility::getSingletonInstances(),
'container' => GeneralUtility::getContainer(),
];
GeneralUtility::purgeInstances();
// Set global state to the non-failsafe container and it's instances
GeneralUtility::setContainer($container);
ExtensionManagementUtility::setPackageManager($container->get(PackageManager::class));
$backupSingletonInstances = $backup['singletonInstances'] ?? [];
foreach ($backupSingletonInstances as $className => $instance) {
GeneralUtility::setSingletonInstance($className, $instance);
}
return $newBackup;
}
/**
* Bootstrap a non-failsafe container and load ext_localconf.phps of all extensions.
*
* Use by actions like the database analyzer and the upgrade wizards which
* need additional bootstrap actions performed.
*
* Those actions can potentially fatal if some old extension is loaded that triggers
* a fatal in ext_localconf code! Use only if really needed.
*/
public function loadExtLocalconfDatabase(bool $resetContainer = false, bool $allowCaching = true): ContainerInterface
{
$container = $this->getContainer($allowCaching);
$backup = $this->makeCurrent($container);
$beUserBackup = $GLOBALS['BE_USER'] ?? null;
$container->get('boot.state')->complete = false;
$eventDispatcher = $container->get(EventDispatcherInterface::class);
$tcaFactory = $container->get(TcaFactory::class);
if ($allowCaching) {
$container->get(ExtLocalconfFactory::class)->load();
} else {
$container->get(ExtLocalconfFactory::class)->loadUncached();
}
$GLOBALS['BE_USER'] = $beUserBackup;
if ($allowCaching) {
$GLOBALS['TCA'] = $tcaFactory->get();
} else {
$GLOBALS['TCA'] = $tcaFactory->create();
}
$container->get('boot.state')->complete = true;
if ($allowCaching) {
$container->get(TcaSchemaFactory::class)->load($GLOBALS['TCA']);
} else {
$container->get(TcaSchemaFactory::class)->rebuild($GLOBALS['TCA']);
}
$eventDispatcher->dispatch(new BootCompletedEvent($allowCaching));
if ($resetContainer) {
$this->makeCurrent(null, $backup);
}
return $container;
}
public function resetGlobalContainer(): void
{
$this->makeCurrent(null, []);
}
public function getFailsafeContainer(): ContainerInterface
{
return $this->failsafeContainer;
}
public function unsetInternalContainerInstance(): void
{
$this->container = null;
}
}
+407
View File
@@ -0,0 +1,407 @@
<?php
declare(strict_types=1);
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
namespace TYPO3\CMS\Core\Core;
use Composer\Autoload\ClassLoader;
use Composer\InstalledVersions;
use Psr\Container\ContainerInterface;
use Psr\EventDispatcher\EventDispatcherInterface;
use Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
use TYPO3\CMS\Core\Cache\Backend\BackendInterface;
use TYPO3\CMS\Core\Cache\Backend\NullBackend;
use TYPO3\CMS\Core\Cache\Backend\Typo3DatabaseBackend;
use TYPO3\CMS\Core\Cache\Exception\InvalidBackendException;
use TYPO3\CMS\Core\Cache\Exception\InvalidCacheException;
use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
use TYPO3\CMS\Core\Cache\Frontend\PhpFrontend;
use TYPO3\CMS\Core\Cache\Frontend\VariableFrontend;
use TYPO3\CMS\Core\Configuration\ConfigurationManager;
use TYPO3\CMS\Core\Configuration\Extension\ExtLocalconfFactory;
use TYPO3\CMS\Core\Configuration\Tca\TcaFactory;
use TYPO3\CMS\Core\Core\Event\BootCompletedEvent;
use TYPO3\CMS\Core\DependencyInjection\Cache\ContainerBackend;
use TYPO3\CMS\Core\DependencyInjection\ContainerBuilder;
use TYPO3\CMS\Core\Http\NormalizedParams;
use TYPO3\CMS\Core\Log\LogManager;
use TYPO3\CMS\Core\Package\Cache\ComposerPackageArtifact;
use TYPO3\CMS\Core\Package\Cache\PackageCacheInterface;
use TYPO3\CMS\Core\Package\Cache\PackageStatesPackageCache;
use TYPO3\CMS\Core\Package\FailsafePackageManager;
use TYPO3\CMS\Core\Package\PackageManager;
use TYPO3\CMS\Core\Schema\TcaSchemaFactory;
use TYPO3\CMS\Core\Service\DependencyOrderingService;
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* This class encapsulates bootstrap related methods.
* It is required directly as the very first thing in entry scripts and
* used to define all base things like constants and paths and so on.
*
* Most methods in this class have dependencies to each other. They can
* not be called in arbitrary order. The methods are ordered top down, so
* a method at the beginning has lower dependencies than a method further
* down. Do not fiddle with the load order in own scripts except you know
* exactly what you are doing!
*/
readonly class Bootstrap
{
/**
* Bootstrap TYPO3 and return a Container that may be used
* to initialize an Application class.
*
* @param ClassLoader $classLoader an instance of the class loader
* @param bool $failsafe true if no caching and a failsafe package manager should be used
*/
public static function init(
ClassLoader $classLoader,
bool $failsafe = false
): ContainerInterface {
$requestId = new RequestId();
static::initializeClassLoader($classLoader);
if (!Environment::isComposerMode() && ClassLoadingInformation::isClassLoadingInformationAvailable()) {
ClassLoadingInformation::registerClassLoadingInformation();
}
// We need an early instance of the configuration manager.
// Since makeInstance relies on the object configuration, we create it here with new instead.
$configurationManager = new ConfigurationManager();
if (!static::checkIfEssentialConfigurationExists($configurationManager)) {
$failsafe = true;
}
// TYPO3_CONF_VARS is now loaded by settings.php and additional.php
$configurationManager->exportConfiguration();
$logManager = new LogManager($requestId);
// LogManager is used by the core ErrorHandler (using GeneralUtility::makeInstance),
// therefore we have to push the LogManager to GeneralUtility, in case there
// happen errors before we call GeneralUtility::setContainer().
GeneralUtility::setSingletonInstance(LogManager::class, $logManager);
static::initializeErrorHandling();
$disableCaching = $failsafe ? true : false;
/** @var PhpFrontend $coreCache */
$coreCache = static::createCache('core', $disableCaching);
$packageCache = static::createPackageCache($coreCache);
$packageManager = static::createPackageManager(
$failsafe ? FailsafePackageManager::class : PackageManager::class,
$packageCache
);
static::setDefaultTimezone();
static::setMemoryLimit();
$dependencyInjectionContainerCache = static::createCache('di');
$bootState = new \stdClass();
$bootState->complete = false;
$bootState->cacheDisabled = $disableCaching;
$builder = new ContainerBuilder([
ClassLoader::class => $classLoader,
ApplicationContext::class => Environment::getContext(),
ConfigurationManager::class => $configurationManager,
LogManager::class => $logManager,
RequestId::class => $requestId,
'cache.di' => $dependencyInjectionContainerCache,
'cache.core' => $coreCache,
PackageManager::class => $packageManager,
// @internal
'boot.state' => $bootState,
]);
$container = $builder->createDependencyInjectionContainer($packageManager, $dependencyInjectionContainerCache, $failsafe);
// Push the container to GeneralUtility as we want to make sure its
// makeInstance() method creates classes using the container from now on.
GeneralUtility::setContainer($container);
// Reset LogManager singleton instance in order for GeneralUtility::makeInstance()
// to proxy LogManager retrieval to ContainerInterface->get() from now on.
GeneralUtility::removeSingletonInstance(LogManager::class, $logManager);
// Push PackageManager instance to ExtensionManagementUtility
ExtensionManagementUtility::setPackageManager($packageManager);
if ($failsafe) {
$bootState->complete = true;
return $container;
}
// The encryption key is part of the system configuration and must be set up
// before any extension code is executed.
static::checkEncryptionKey();
$eventDispatcher = $container->get(EventDispatcherInterface::class);
$container->get(ExtLocalconfFactory::class)->load();
$tca = $container->get(TcaFactory::class)->get();
$bootState->complete = true;
// $GLOBALS['TCA'] is only published once the schema is built, so consumers
// triggered by the schema factory can not work with a half-initialized state.
$container->get(TcaSchemaFactory::class)->load($tca);
$GLOBALS['TCA'] = $tca;
$eventDispatcher->dispatch(new BootCompletedEvent(true));
return $container;
}
/**
* Sets the class loader to the bootstrap
*
* @param ClassLoader $classLoader an instance of the class loader
* @internal This is not a public API method, do not use in own extensions
*/
public static function initializeClassLoader(ClassLoader $classLoader): void
{
ClassLoadingInformation::setClassLoader($classLoader);
}
/**
* checks if config/system/settings.php or PackageStates.php is missing,
* used to see if a redirect to the installer is needed
*
* All file_exists checks are delayed as far as possible to avoid I/O impact
*
* @return bool TRUE when the essential configuration is available, otherwise FALSE
* @internal This is not a public API method, do not use in own extensions
*/
public static function checkIfEssentialConfigurationExists(ConfigurationManager $configurationManager): bool
{
if (!Environment::isComposerMode()
&& !file_exists(Environment::getPackageStatesFile())
) {
// Early return in case system is not properly set up
return false;
}
// The system configuration file (settings.php) is mandatory, the additional configuration
// file (additional.php) is optional.
return file_exists($configurationManager->getSystemConfigurationFileLocation());
}
/**
* Initializes the package system and loads the package configuration and settings
* provided by the packages.
*
* @param string $packageManagerClassName Define an alternative package manager implementation (usually for the installer)
* @internal This is not a public API method, do not use in own extensions
*/
public static function createPackageManager($packageManagerClassName, PackageCacheInterface $packageCache): PackageManager
{
$dependencyOrderingService = GeneralUtility::makeInstance(DependencyOrderingService::class);
/** @var PackageManager $packageManager */
$packageManager = new $packageManagerClassName($dependencyOrderingService);
$packageManager->setPackageCache($packageCache);
$packageManager->initialize();
return $packageManager;
}
/**
* @internal
*/
public static function createPackageCache(FrontendInterface $coreCache): PackageCacheInterface
{
if (!Environment::isComposerMode()) {
return new PackageStatesPackageCache(Environment::getPackageStatesFile(), $coreCache);
}
$composerInstallersPath = InstalledVersions::getInstallPath('typo3/cms-composer-installers');
if ($composerInstallersPath === null) {
throw new \RuntimeException('Package "typo3/cms-composer-installers" not found. Replacing the package is not allowed. Fork the package instead and pull in the fork with the same name.', 1636145677);
}
return new ComposerPackageArtifact(dirname($composerInstallersPath));
}
/**
* Instantiates an early cache instance
*
* Creates a cache instances independently of the CacheManager.
* The is used to create the core cache during early bootstrap when the CacheManager
* is not yet available (i.e. configuration is not yet loaded).
*
* @param class-string<BackendInterface>|null $enforcedCacheBackend
* @internal
*/
public static function createCache(
string $identifier,
bool $disableCaching = false,
?string $enforcedCacheBackend = null
): FrontendInterface {
$cacheConfigurations = $GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations'] ?? [];
$cacheConfigurations['di']['frontend'] = PhpFrontend::class;
$cacheConfigurations['di']['backend'] = ContainerBackend::class;
$cacheConfigurations['di']['options'] = [];
$configuration = $cacheConfigurations[$identifier] ?? [];
$frontend = $configuration['frontend'] ?? VariableFrontend::class;
$backend = $enforcedCacheBackend ?? $configuration['backend'] ?? Typo3DatabaseBackend::class;
$options = $configuration['options'] ?? [];
if ($disableCaching) {
$backend = NullBackend::class;
$options = [];
}
$backendInstance = new $backend($options);
if (!$backendInstance instanceof BackendInterface) {
throw new InvalidBackendException('"' . $backend . '" is not a valid cache backend object.', 1545260108);
}
if (is_callable([$backendInstance, 'initializeObject'])) {
$backendInstance->initializeObject();
}
$frontendInstance = new $frontend($identifier, $backendInstance);
if (!$frontendInstance instanceof FrontendInterface) {
throw new InvalidCacheException('"' . $frontend . '" is not a valid cache frontend object.', 1545260109);
}
if (is_callable([$frontendInstance, 'initializeObject'])) {
$frontendInstance->initializeObject();
}
return $frontendInstance;
}
/**
* Set default timezone
*/
protected static function setDefaultTimezone(): void
{
$timeZone = $GLOBALS['TYPO3_CONF_VARS']['SYS']['phpTimeZone'];
if (empty($timeZone)) {
// Time zone from the server environment (TZ env or OS query)
$defaultTimeZone = @date_default_timezone_get();
if ($defaultTimeZone !== '') {
$timeZone = $defaultTimeZone;
} else {
$timeZone = 'UTC';
}
}
// Set default to avoid E_WARNINGs with PHP > 5.3
date_default_timezone_set($timeZone);
}
/**
* Configure and set up exception and error handling
*/
protected static function initializeErrorHandling(): void
{
$productionExceptionHandlerClassName = $GLOBALS['TYPO3_CONF_VARS']['SYS']['productionExceptionHandler'];
$debugExceptionHandlerClassName = $GLOBALS['TYPO3_CONF_VARS']['SYS']['debugExceptionHandler'];
$errorHandlerClassName = $GLOBALS['TYPO3_CONF_VARS']['SYS']['errorHandler'];
$errorHandlerErrors = $GLOBALS['TYPO3_CONF_VARS']['SYS']['errorHandlerErrors'] | E_USER_DEPRECATED;
$exceptionalErrors = $GLOBALS['TYPO3_CONF_VARS']['SYS']['exceptionalErrors'];
$displayErrorsSetting = (int)$GLOBALS['TYPO3_CONF_VARS']['SYS']['displayErrors'];
switch ($displayErrorsSetting) {
case -1:
$ipMatchesDevelopmentSystem = GeneralUtility::cmpIP(NormalizedParams::createFromServerParams($_SERVER)->getRemoteAddress(), $GLOBALS['TYPO3_CONF_VARS']['SYS']['devIPmask']);
$exceptionHandlerClassName = $ipMatchesDevelopmentSystem ? $debugExceptionHandlerClassName : $productionExceptionHandlerClassName;
$displayErrors = $ipMatchesDevelopmentSystem ? 1 : 0;
$exceptionalErrors = $ipMatchesDevelopmentSystem ? $exceptionalErrors : 0;
break;
case 0:
$exceptionHandlerClassName = $productionExceptionHandlerClassName;
$displayErrors = 0;
break;
case 1:
$exceptionHandlerClassName = $debugExceptionHandlerClassName;
$displayErrors = 1;
break;
default:
// Throw exception if an invalid option is set. A default for displayErrors is set
// in very early install tool, coming from DefaultConfiguration.php. It is safe here
// to just throw if there is no value for whatever reason.
throw new \RuntimeException(
'The option $TYPO3_CONF_VARS[SYS][displayErrors] is not set to "-1", "0" or "1".',
1476046290
);
}
@ini_set('display_errors', (string)$displayErrors);
if (!empty($errorHandlerClassName)) {
// Register an error handler for the given errorHandlerError
$errorHandler = GeneralUtility::makeInstance($errorHandlerClassName, $errorHandlerErrors);
$errorHandler->setExceptionalErrors($exceptionalErrors);
if (is_callable([$errorHandler, 'setDebugMode'])) {
$errorHandler->setDebugMode($displayErrors === 1);
}
if (is_callable([$errorHandler, 'registerErrorHandler'])) {
$errorHandler->registerErrorHandler();
}
}
if (!empty($exceptionHandlerClassName)) {
// Registering the exception handler is done in the constructor
GeneralUtility::makeInstance($exceptionHandlerClassName);
}
}
/**
* Set PHP memory limit depending on value of
* $GLOBALS['TYPO3_CONF_VARS']['SYS']['setMemoryLimit']
*/
protected static function setMemoryLimit(): void
{
if ((int)$GLOBALS['TYPO3_CONF_VARS']['SYS']['setMemoryLimit'] > 16) {
@ini_set('memory_limit', (string)((int)$GLOBALS['TYPO3_CONF_VARS']['SYS']['setMemoryLimit'] . 'm'));
}
}
/**
* Check if a configuration key has been configured
*/
protected static function checkEncryptionKey(): void
{
if (empty($GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey'])) {
throw new \RuntimeException(
'TYPO3 Encryption is empty. $GLOBALS[\'TYPO3_CONF_VARS\'][\'SYS\'][\'encryptionKey\'] needs to be set for TYPO3 to work securely',
1502987245
);
}
}
/**
* Initialize backend user object in globals
*
* @param string $className usually \TYPO3\CMS\Core\Authentication\BackendUserAuthentication::class but can be used for CLI
*/
public static function initializeBackendUser($className = BackendUserAuthentication::class, ?ServerRequestInterface $request = null): BackendUserAuthentication
{
/** @var BackendUserAuthentication $backendUser */
$backendUser = GeneralUtility::makeInstance($className);
// The global must be available very early, because methods below
// might trigger code which relies on it. See: #45625
$GLOBALS['BE_USER'] = $backendUser;
$backendUser->start($request);
return $backendUser;
}
/**
* Initializes and ensures authenticated access
*/
public static function initializeBackendAuthentication(): void
{
$GLOBALS['BE_USER']->backendCheckLogin();
}
}
+294
View File
@@ -0,0 +1,294 @@
<?php
declare(strict_types=1);
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
namespace TYPO3\CMS\Core\Core;
use Composer\Autoload\ClassLoader;
use TYPO3\ClassAliasLoader\ClassAliasMap;
use TYPO3\CMS\Core\Package\PackageInterface;
use TYPO3\CMS\Core\Package\PackageManager;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* Get and manipulate class loading information, only necessary/in use
* when TYPO3 is not purely set up by composer but when e.g. extensions are installed via the extension manager
* by utilizing the composer class loader and adding more information built by the ClassLoadingInformationGenerator
* class.
*
* @internal
*/
class ClassLoadingInformation
{
/**
* Base directory storing all autoload information
*/
public const AUTOLOAD_INFO_DIR = 'autoload/';
/**
* Base directory storing all autoload information in testing context
*/
public const AUTOLOAD_INFO_DIR_TESTS = 'autoload-tests/';
/**
* Name of file that contains all classes-filename mappings
*/
public const AUTOLOAD_CLASSMAP_FILENAME = 'autoload_classmap.php';
/**
* Name of file that contains all PSR4 mappings, fetched from the composer.json files of extensions
*/
public const AUTOLOAD_PSR4_FILENAME = 'autoload_psr4.php';
/**
* Name of file that contains all package provides, fetched from the composer.json files of extensions
*/
private const string AUTOLOAD_INCLUDE_FILENAME = 'autoload_files.php';
/**
* Name of file that contains all class alias mappings
*/
public const AUTOLOAD_CLASSALIASMAP_FILENAME = 'autoload_classaliasmap.php';
/**
* @var ClassLoader
*/
protected static $classLoader;
/**
* Sets the package manager instance
*
* @internal
*/
public static function setClassLoader(ClassLoader $classLoader)
{
static::$classLoader = $classLoader;
}
/**
* Checks if the autoload_classmap.php exists and we are not in testing context.
* Used to see if the ClassLoadingInformationGenerator should be called.
*
* @return bool
*/
public static function isClassLoadingInformationAvailable()
{
return file_exists(self::getClassLoadingInformationDirectory() . self::AUTOLOAD_CLASSMAP_FILENAME);
}
/**
* Puts all information compiled by the ClassLoadingInformationGenerator to files
*/
public static function dumpClassLoadingInformation()
{
self::ensureAutoloadInfoDirExists();
$activeExtensionPackages = static::getActiveExtensionPackages();
$generator = new ClassLoadingInformationGenerator();
$classInfoFiles = $generator->buildAutoloadInformationFiles(self::isTestingContext(), Environment::getProjectPath() . '/', $activeExtensionPackages);
GeneralUtility::writeFile(self::getClassLoadingInformationDirectory() . self::AUTOLOAD_CLASSMAP_FILENAME, $classInfoFiles['classMapFile'], true);
GeneralUtility::writeFile(self::getClassLoadingInformationDirectory() . self::AUTOLOAD_PSR4_FILENAME, $classInfoFiles['psr-4File'], true);
GeneralUtility::writeFile(self::getClassLoadingInformationDirectory() . self::AUTOLOAD_INCLUDE_FILENAME, $classInfoFiles['includesFile'], true);
$classAliasMapFile = $generator->buildClassAliasMapFile($activeExtensionPackages);
GeneralUtility::writeFile(self::getClassLoadingInformationDirectory() . self::AUTOLOAD_CLASSALIASMAP_FILENAME, $classAliasMapFile, true);
}
/**
* Registers the class aliases, the class maps and the PSR4 prefixes previously identified by
* the ClassLoadingInformationGenerator during runtime.
*/
public static function registerClassLoadingInformation()
{
$composerClassLoader = static::getClassLoader();
$dynamicClassAliasMapFile = self::getClassLoadingInformationDirectory() . self::AUTOLOAD_CLASSALIASMAP_FILENAME;
if (file_exists($dynamicClassAliasMapFile)) {
$classAliasMap = require $dynamicClassAliasMapFile;
if (is_array($classAliasMap) && !empty($classAliasMap['aliasToClassNameMapping']) && !empty($classAliasMap['classNameToAliasMapping'])) {
ClassAliasMap::addAliasMap($classAliasMap);
}
}
$dynamicClassMapFile = self::getClassLoadingInformationDirectory() . self::AUTOLOAD_CLASSMAP_FILENAME;
if (file_exists($dynamicClassMapFile)) {
$classMap = require $dynamicClassMapFile;
if (!empty($classMap) && is_array($classMap)) {
$composerClassLoader->addClassMap($classMap);
}
}
$dynamicPsr4File = self::getClassLoadingInformationDirectory() . self::AUTOLOAD_PSR4_FILENAME;
if (file_exists($dynamicPsr4File)) {
$psr4 = require $dynamicPsr4File;
if (is_array($psr4)) {
self::registerPsr4Prefixes($composerClassLoader, $psr4);
}
}
$dynamicIncludesFile = self::getClassLoadingInformationDirectory() . self::AUTOLOAD_INCLUDE_FILENAME;
if (file_exists($dynamicIncludesFile)) {
$includes = require $dynamicIncludesFile;
if (is_array($includes)) {
foreach ($includes as $fileIdentifier => $file) {
self::requireFile($fileIdentifier, $file);
}
}
}
}
/**
* Sets class loading information for a package for the current web request
*
* @throws \TYPO3\CMS\Core\Error\Exception
*/
public static function registerTransientClassLoadingInformationForPackage(PackageInterface $package)
{
$composerClassLoader = static::getClassLoader();
$generator = new ClassLoadingInformationGenerator();
$classInformation = $generator->buildClassLoadingInformationForPackage($package, false, self::isTestingContext(), Environment::getPublicPath() . '/');
$composerClassLoader->addClassMap($classInformation['classMap']);
self::registerPsr4Prefixes($composerClassLoader, $classInformation['psr-4']);
foreach ($classInformation['files'] as $fileIdentifier => $file) {
self::requireFile($fileIdentifier, $file);
}
$classAliasMap = $generator->buildClassAliasMapForPackage($package);
if (!empty($classAliasMap['aliasToClassNameMapping']) && !empty($classAliasMap['classNameToAliasMapping'])) {
ClassAliasMap::addAliasMap($classAliasMap);
}
}
/**
* Registers PSR-4 prefixes on the Composer class loader, keeping the directories
* that are already registered for the very same prefix.
*
* ClassLoader::setPsr4() replaces all directories of a prefix. A prefix can be used
* by more than one package, for instance when an extension ships a library of the
* same namespace as a dedicated Composer package. Extension directories take
* precedence, all other directories are kept as fallback.
*
* De-duplication keeps repeated registration idempotent: functional tests bootstrap
* TYPO3 more than once per process and would otherwise grow the directory list of a
* prefix with every single bootstrap.
*
* @param array<string, string|list<string>> $psr4
*/
private static function registerPsr4Prefixes(ClassLoader $composerClassLoader, array $psr4): void
{
$registeredPrefixes = $composerClassLoader->getPrefixesPsr4();
foreach ($psr4 as $prefix => $paths) {
$composerClassLoader->setPsr4(
$prefix,
array_values(array_unique(array_merge((array)$paths, $registeredPrefixes[$prefix] ?? [])))
);
}
}
private static function requireFile(string $fileIdentifier, string $file): void
{
$requireFile = \Closure::bind(static function ($fileIdentifier, $file) {
if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
$GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
require $file;
}
}, null, null);
try {
if (!file_exists($file)) {
return;
}
$requireFile($fileIdentifier, $file);
} catch (\Throwable) {
// Make sure to not break everything in case the does something weird
// and especially allow to dump new class loading information to eventually recover
}
}
/**
* @return string
*/
protected static function getClassLoadingInformationDirectory()
{
if (self::isTestingContext()) {
return dirname(Environment::getExtensionsPath()) . '/' . self::AUTOLOAD_INFO_DIR_TESTS;
}
return dirname(Environment::getExtensionsPath()) . '/' . self::AUTOLOAD_INFO_DIR;
}
/**
* Get class name for alias
*
* @param string $alias
* @return class-string
*/
public static function getClassNameForAlias($alias)
{
return ClassAliasMap::getClassNameForAlias($alias);
}
/**
* Ensures the defined path for class information files exists
* And clears it in case we're in testing context
*/
protected static function ensureAutoloadInfoDirExists()
{
$autoloadInfoDir = self::getClassLoadingInformationDirectory();
if (!file_exists($autoloadInfoDir)) {
GeneralUtility::mkdir_deep($autoloadInfoDir);
}
}
/**
* Internal method calling the bootstrap to fetch the composer class loader
*
* @return ClassLoader
* @internal Currently used in TYPO3 testing. Public visibility is experimental and may vanish without further notice.
*/
public static function getClassLoader()
{
return static::$classLoader;
}
/**
* Internal method calling the bootstrap to get application context information
*
* @return bool
* @throws \TYPO3\CMS\Core\Exception
*/
protected static function isTestingContext()
{
return Environment::getContext()->isTesting();
}
/**
* Get all packages except the protected ones, as they are covered already
*
* @return PackageInterface[]
*/
protected static function getActiveExtensionPackages()
{
$activeExtensionPackages = [];
$packageManager = GeneralUtility::makeInstance(PackageManager::class);
foreach ($packageManager->getActivePackages() as $package) {
if ($package->getPackageMetaData()->isFrameworkType()) {
// Skip all core packages as the class loading info is prepared for them already
continue;
}
$activeExtensionPackages[] = $package;
}
return $activeExtensionPackages;
}
}
@@ -0,0 +1,293 @@
<?php
declare(strict_types=1);
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
namespace TYPO3\CMS\Core\Core;
use Composer\ClassMapGenerator\ClassMapGenerator;
use TYPO3\CMS\Core\Error\Exception;
use TYPO3\CMS\Core\Package\Package;
use TYPO3\CMS\Core\Package\PackageInterface;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* Generates class loading information (class maps, class aliases etc.) and writes it to files
* for further inclusion in the bootstrap
* @internal
*/
readonly class ClassLoadingInformationGenerator
{
/**
* Returns class loading information for a single package
*
* @param bool $useRelativePaths If set to TRUE, make the path relative to the current TYPO3 public web path
*/
public function buildClassLoadingInformationForPackage(
PackageInterface $package,
bool $useRelativePaths,
bool $isDevMode,
string $installationRoot,
): array {
$classMap = [];
$psr4 = [];
$includeFiles = [];
$packagePath = $package->getPackagePath();
$manifest = $package->getValueFromComposerManifest();
if (empty($manifest->autoload)) {
// Legacy mode: Scan the complete extension directory for class files
// @todo: Drop this as breaking change in v14?! Extensions must deliver a proper
// composer.json nowadays, and PSR-4 can and should be strong requirement as well?!
$classMap = $this->createClassMap($packagePath, $useRelativePaths, $installationRoot, !$isDevMode);
} else {
$autoloadPsr4 = $this->getAutoloadSectionFromManifest($manifest, 'psr-4', $isDevMode);
if (!empty($autoloadPsr4)) {
foreach ($autoloadPsr4 as $namespacePrefix => $paths) {
foreach ((array)$paths as $path) {
$namespacePath = $packagePath . $path;
$namespaceRealPath = (string)realpath($namespacePath);
if ($useRelativePaths) {
$psr4[$namespacePrefix][] = $this->makePathRelative($namespacePath, $namespaceRealPath, $installationRoot);
} else {
$psr4[$namespacePrefix][] = $namespacePath;
}
if (!empty($namespaceRealPath) && is_dir($namespaceRealPath)) {
// Add all prs-4 classes to the class map for improved class loading performance
$classMap = array_merge($classMap, $this->createClassMap($namespacePath, $useRelativePaths, $installationRoot, false, $namespacePrefix));
}
}
}
}
$autoloadClassmap = $this->getAutoloadSectionFromManifest($manifest, 'classmap', $isDevMode);
if (!empty($autoloadClassmap)) {
foreach ($autoloadClassmap as $path) {
$classMap = array_merge($classMap, $this->createClassMap($packagePath . $path, $useRelativePaths, $installationRoot));
}
}
if ($package instanceof Package) {
$packageProvides = $package->getProvidesPackages();
foreach ($packageProvides as $relativePath) {
if ($relativePath === '') {
continue;
}
$libraryPath = $packagePath . rtrim($relativePath, '/') . '/autoload.php';
$libraryRealPath = (string)realpath($libraryPath);
if (!file_exists($libraryRealPath)) {
continue;
}
$contentToCheck = file_get_contents($libraryRealPath);
if (!str_contains($contentToCheck, 'return ComposerAutoloaderInit')) {
continue;
}
$relativeLibraryPath = $this->makePathRelative($libraryPath, $libraryRealPath, $installationRoot);
$fileHash = md5($package->getPackageKey() . ':' . $relativeLibraryPath);
$includeFiles[$fileHash] = $useRelativePaths ? $relativeLibraryPath : $libraryPath;
}
}
}
return [
'classMap' => $classMap,
'psr-4' => $psr4,
'files' => $includeFiles,
];
}
/**
* Returns class alias map for given package
*
* @throws Exception
*/
public function buildClassAliasMapForPackage(PackageInterface $package): array
{
$aliasToClassNameMapping = [];
$classNameToAliasMapping = [];
$possibleClassAliasFiles = [];
$manifest = $package->getValueFromComposerManifest();
if (!empty($manifest->extra->{'typo3/class-alias-loader'}->{'class-alias-maps'})) {
$possibleClassAliasFiles = $manifest->extra->{'typo3/class-alias-loader'}->{'class-alias-maps'};
if (!is_array($possibleClassAliasFiles)) {
throw new Exception('"typo3/class-alias-loader"/"class-alias-maps" must return an array!', 1444142481);
}
} else {
$possibleClassAliasFiles[] = 'Migrations/Code/ClassAliasMap.php';
}
$packagePath = $package->getPackagePath();
foreach ($possibleClassAliasFiles as $possibleClassAliasFile) {
$possiblePathToClassAliasFile = $packagePath . $possibleClassAliasFile;
if (file_exists($possiblePathToClassAliasFile)) {
$packageAliasMap = require $possiblePathToClassAliasFile;
if (!is_array($packageAliasMap)) {
throw new Exception('"class alias maps" must return an array', 1422625075);
}
foreach ($packageAliasMap as $aliasClassName => $className) {
$lowerCasedAliasClassName = strtolower($aliasClassName);
$aliasToClassNameMapping[$lowerCasedAliasClassName] = $className;
$classNameToAliasMapping[$className][$lowerCasedAliasClassName] = $lowerCasedAliasClassName;
}
}
}
return [
'aliasToClassNameMapping' => $aliasToClassNameMapping,
'classNameToAliasMapping' => $classNameToAliasMapping,
];
}
/**
* Generate the class map file
*
* @return string[]
*/
public function buildAutoloadInformationFiles(
bool $isDevMode,
string $installationRoot,
array $activeExtensionPackages,
): array {
$psr4File = $classMapFile = $includesFile = <<<EOF
<?php
// autoload_classmap.php @generated by TYPO3
\$typo3InstallDir = \TYPO3\CMS\Core\Core\Environment::getPublicPath() . '/';
return array(
EOF;
$classMap = [];
$psr4 = [];
$includeFiles = [];
foreach ($activeExtensionPackages as $package) {
$classLoadingInformation = $this->buildClassLoadingInformationForPackage($package, true, $isDevMode, $installationRoot);
$classMap = array_merge($classMap, $classLoadingInformation['classMap']);
// Accumulate per prefix: more than one package can use the same PSR-4 prefix,
// array_merge() would drop all directories but the ones of the last package.
foreach ($classLoadingInformation['psr-4'] as $namespacePrefix => $namespacePaths) {
$psr4[$namespacePrefix] = array_merge($psr4[$namespacePrefix] ?? [], $namespacePaths);
}
$includeFiles = array_merge($includeFiles, $classLoadingInformation['files']);
}
ksort($classMap);
ksort($psr4);
ksort($includeFiles);
foreach ($classMap as $class => $relativePath) {
$classMapFile .= sprintf(' %s => %s,', var_export($class, true), $this->getPathCode($relativePath)) . "\n";
}
$classMapFile .= ");\n";
foreach ($psr4 as $prefix => $relativePaths) {
$psr4File .= sprintf(' %s => array(%s),', var_export($prefix, true), implode(',', array_map($this->getPathCode(...), $relativePaths))) . "\n";
}
$psr4File .= ");\n";
foreach ($includeFiles as $hash => $relativePath) {
$includesFile .= sprintf(' %s => %s,', var_export($hash, true), $this->getPathCode($relativePath)) . "\n";
}
$includesFile .= ");\n";
return ['classMapFile' => $classMapFile, 'psr-4File' => $psr4File, 'includesFile' => $includesFile];
}
/**
* Build class alias mapping file
*/
public function buildClassAliasMapFile(array $activeExtensionPackages): string
{
$aliasToClassNameMapping = [];
$classNameToAliasMapping = [];
foreach ($activeExtensionPackages as $package) {
$aliasMappingForPackage = $this->buildClassAliasMapForPackage($package);
$aliasToClassNameMapping = array_merge($aliasToClassNameMapping, $aliasMappingForPackage['aliasToClassNameMapping']);
$classNameToAliasMapping = array_merge($classNameToAliasMapping, $aliasMappingForPackage['classNameToAliasMapping']);
}
$exportArray = [
'aliasToClassNameMapping' => $aliasToClassNameMapping,
'classNameToAliasMapping' => $classNameToAliasMapping,
];
$fileContent = "<?php\nreturn ";
$fileContent .= var_export($exportArray, true);
$fileContent .= ";\n";
return $fileContent;
}
/**
* Fetches class loading info from the according section from the manifest file.
* Development information will be extracted and merged as well.
*/
protected function getAutoloadSectionFromManifest(\stdClass $manifest, string $section, bool $isDevMode): array
{
$finalAutoloadSection = [];
$autoloadDefinition = json_decode((string)json_encode($manifest->autoload), true);
if (!empty($autoloadDefinition[$section]) && is_array($autoloadDefinition[$section])) {
$finalAutoloadSection = $autoloadDefinition[$section];
}
if ($isDevMode) {
if (isset($manifest->{'autoload-dev'})) {
$autoloadDefinitionDev = json_decode((string)json_encode($manifest->{'autoload-dev'}), true);
if (!empty($autoloadDefinitionDev[$section]) && is_array($autoloadDefinitionDev[$section])) {
$finalAutoloadSection = array_merge($finalAutoloadSection, $autoloadDefinitionDev[$section]);
}
}
}
return $finalAutoloadSection;
}
/**
* Creates a class map for a given absolute path
*/
protected function createClassMap(
string $classesPath,
bool $useRelativePaths,
string $installationRoot,
bool $ignorePotentialTestClasses = false,
?string $namespace = null
): array {
$classMap = [];
$blacklistExpression = null;
if ($ignorePotentialTestClasses) {
$blacklistPathPrefix = (string)realpath($classesPath);
$blacklistPathPrefix = str_replace('\\', '/', $blacklistPathPrefix);
$blacklistExpression = "{($blacklistPathPrefix/tests/|$blacklistPathPrefix/Tests/|$blacklistPathPrefix/Resources/|$blacklistPathPrefix/res/)}";
}
$generator = new ClassMapGenerator();
$generator->scanPaths($classesPath, $blacklistExpression, 'classmap', $namespace);
$map = $generator->getClassMap()->getMap();
foreach ($map as $class => $path) {
if ($useRelativePaths) {
$classMap[$class] = $this->makePathRelative($classesPath, realpath($path), $installationRoot);
} else {
$classMap[$class] = $path;
}
}
return $classMap;
}
/**
* Generate a relative path string from an absolute path within a give package path
*/
protected function makePathRelative(string $packagePath, string $realPathOfClassFile, string $installationRoot): string
{
$realPathOfClassFile = GeneralUtility::fixWindowsFilePath($realPathOfClassFile);
$packageRealPath = GeneralUtility::fixWindowsFilePath((string)realpath($packagePath));
$relativePackagePath = rtrim(substr($packagePath, strlen($installationRoot)), '/');
if ($realPathOfClassFile === $packageRealPath) {
return $relativePackagePath;
}
return $relativePackagePath . '/' . ltrim(substr($realPathOfClassFile, strlen($packageRealPath)), '/');
}
/**
* Generate a relative path string from a relative path
*/
protected function getPathCode(string $relativePathToClassFile): string
{
return '$typo3InstallDir . ' . var_export($relativePathToClassFile, true);
}
}
@@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
namespace TYPO3\CMS\Core\Core;
use TYPO3\CMS\Core\Attribute\AsEventListener;
use TYPO3\CMS\Core\Package\Event\AfterPackageActivationEvent;
use TYPO3\CMS\Core\Package\Event\AfterPackageDeactivationEvent;
/*
* @internal
*/
readonly class ClassLoadingInformationUpdater
{
#[AsEventListener(identifier: 'non-composer-class-loader', event: AfterPackageDeactivationEvent::class)]
#[AsEventListener(identifier: 'non-composer-class-loader', event: AfterPackageActivationEvent::class)]
public function __invoke(): void
{
if (Environment::isComposerMode()) {
return;
}
ClassLoadingInformation::dumpClassLoadingInformation();
}
}
+341
View File
@@ -0,0 +1,341 @@
<?php
declare(strict_types=1);
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
namespace TYPO3\CMS\Core\Core;
/**
* This class is initialized once in the SystemEnvironmentBuilder, and can then
* be used throughout the application to access common variables
* related to path-resolving and OS-/PHP-application specific information.
*
* It's main design goal is to remove any access to constants within TYPO3 code and to provide a static,
* for TYPO3 core and extensions non-changeable information.
*
* This class does not contain any HTTP related information, as this is handled in NormalizedParams functionality.
*
* All path-related methods do return the realpath to the paths without (!) the trailing slash.
*
* This class only defines what is configured through the environment, does not do any checks if paths exist
* etc. This should be part of the application or the SystemEnvironmentBuilder.
*
* In your application, use it like this: "Environment::isCli()"
*/
class Environment
{
/**
* A list of supported CGI server APIs
* @var array
*/
protected static $supportedCgiServerApis = [
'fpm-fcgi',
'cgi',
'isapi',
'cgi-fcgi',
'srv', // HHVM with fastcgi
];
/**
* @var bool
*/
protected static $cli;
/**
* @var bool
*/
protected static $composerMode;
/**
* @var ApplicationContext
*/
protected static $context;
/**
* @var string
*/
protected static $projectPath;
/**
* @var string
*/
protected static $publicPath;
/**
* @var string
*/
protected static $currentScript;
/**
* @var string
*/
protected static $os;
/**
* @var string
*/
protected static $varPath;
/**
* @var string
*/
protected static $configPath;
// Storage for derived values
protected static string $labelsPath;
protected static string $relativePublicPath;
protected static string $extensionsBasePath;
protected static string $frameworkBasePath;
protected static string $packageStatesFile;
/**
* Sets up the Environment. Please note that this is not public API and only used within the very early
* Set up of TYPO3, or to be used within tests. If you ever call this method in your extension, you're probably
* doing something wrong. Never call this method! Never rely on it!
*
* @internal
*/
public static function initialize(
ApplicationContext $context,
bool $cli,
bool $composerMode,
string $projectPath,
string $publicPath,
string $varPath,
string $configPath,
string $currentScript,
string $os
) {
self::$cli = $cli;
self::$composerMode = $composerMode;
self::$context = $context;
self::$projectPath = $projectPath;
self::$publicPath = $publicPath;
self::$varPath = $varPath;
self::$configPath = $configPath;
self::$currentScript = $currentScript;
self::$os = $os;
// Derived values
self::$relativePublicPath = substr($publicPath, strlen($projectPath) + 1) . '/';
self::$labelsPath = $varPath . '/labels';
self::$extensionsBasePath = $projectPath . '/packages/ext';
self::$frameworkBasePath = $projectPath . '/packages/sysext';
self::$packageStatesFile = $projectPath . '/packages/PackageStates.php';
if ($projectPath === $publicPath) {
// Classic mode, legacy directory layout with everything in document root
self::$relativePublicPath = '';
self::$labelsPath = $configPath . '/l10n';
self::$extensionsBasePath = $projectPath . '/typo3conf/ext';
self::$frameworkBasePath = $projectPath . '/typo3/sysext';
self::$packageStatesFile = $configPath . '/PackageStates.php';
}
}
/**
* Delivers the ApplicationContext object, usually defined in TYPO3_CONTEXT environment variables.
* This is something like "Production", "Testing", or "Development" or any additional information
* "Production/Staging".
*/
public static function getContext(): ApplicationContext
{
return self::$context;
}
/**
* Informs whether TYPO3 has been installed via composer or not. Typically this is useful inside the
* Maintenance Modules, or the Extension Manager.
*/
public static function isComposerMode(): bool
{
return self::$composerMode;
}
/**
* Whether the current PHP request is handled by a CLI SAPI module or not.
*/
public static function isCli(): bool
{
return self::$cli;
}
/**
* The root path to the project. For installations set up via composer, this is the path where your
* composer.json file is stored. For non-composer-setups, this is (due to legacy reasons) the public web folder
* where the TYPO3 installation has been unzipped (something like htdocs/ or public/ on your webfolder).
* However, non-composer-mode installations define an environment variable called "TYPO3_PATH_APP"
* to define a different folder (usually a parent folder) to allow TYPO3 to access and store data outside
* of the public web folder.
*
* @return string The absolute path to the project without the trailing slash
*/
public static function getProjectPath(): string
{
return self::$projectPath;
}
/**
* The public web folder where index.php (= the frontend application) is put, without trailing slash.
* For non-composer installations, the project path = the public path.
*/
public static function getPublicPath(): string
{
return self::$publicPath;
}
/**
* The public web folder where index.php (= the frontend application) is put,
* relative to project path, WITH trailing slash.
* For non-composer installations, this is empty
*
* @internal do not use outside TYPO3 Core.
*/
public static function getRelativePublicPath(): string
{
return self::$relativePublicPath;
}
/**
* The folder where variable data like logs, sessions, locks, and cache files can be stored.
* When project path = public path, then this folder is usually typo3temp/var/, otherwise it's set to
* $project_path/var.
*/
public static function getVarPath(): string
{
return self::$varPath;
}
/**
* The folder where all global (= installation-wide) configuration like
* - system/settings.php and
* - system/additional.php
* is put.
* This folder usually has to be writable for TYPO3 in order to work.
*
* When project path = public path, then this folder is usually typo3conf/, otherwise it's set to
* $project_path/config.
*/
public static function getConfigPath(): string
{
return self::$configPath;
}
/**
* The path + filename to the current PHP script.
*/
public static function getCurrentScript(): string
{
return self::$currentScript;
}
/**
* Helper methods to easily find occurrences, however as these properties are not computed
* it is very possible that these methods will become obsolete in the near future.
*/
/**
* Previously found under typo3conf/l10n/
* Please note that this might be gone at some point
*/
public static function getLabelsPath(): string
{
return self::$labelsPath;
}
/**
* Previously known as PATH_typo3 . 'sysext/'
* Please note that this might be gone at some point
*/
public static function getFrameworkBasePath(): string
{
return self::$frameworkBasePath;
}
/**
* Please note that this might be gone at some point
*/
public static function getExtensionsPath(): string
{
return self::$extensionsBasePath;
}
/**
* @internal Only for use in Bootstrap and PackageManager
*/
public static function getPackageStatesFile(): string
{
return self::$packageStatesFile;
}
/**
* Previously known as PATH_typo3conf
* Please note that this might be gone at some point
*
* The folder where global configuration like
* - legacy LocalConfiguration.php,
* - legacy AdditionalConfiguration.php, and
* - PackageStates.php
* is located.
*/
public static function getLegacyConfigPath(): string
{
return self::getProjectPath() . '/typo3conf';
}
/**
* Whether this TYPO3 installation runs on windows
*/
public static function isWindows(): bool
{
return self::$os === 'WINDOWS';
}
/**
* Whether this TYPO3 installation runs on unix (= non-windows machines)
*/
public static function isUnix(): bool
{
return self::$os === 'UNIX';
}
/**
* Returns true if the server is running on a list of supported CGI server APIs.
*/
public static function isRunningOnCgiServer(): bool
{
return in_array(PHP_SAPI, self::$supportedCgiServerApis, true);
}
public static function usesCgiFixPathInfo(): bool
{
return !empty(ini_get('cgi.fix_pathinfo'));
}
/**
* Returns the currently configured Environment information as array.
*/
public static function toArray(): array
{
return [
'context' => (string)self::getContext(),
'cli' => self::isCli(),
'projectPath' => self::getProjectPath(),
'publicPath' => self::getPublicPath(),
'varPath' => self::getVarPath(),
'configPath' => self::getConfigPath(),
'currentScript' => self::getCurrentScript(),
'os' => self::isWindows() ? 'WINDOWS' : 'UNIX',
];
}
}
+31
View File
@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
namespace TYPO3\CMS\Core\Core\Event;
/**
* Executed when TYPO3 has fully booted
*/
final readonly class BootCompletedEvent
{
public function __construct(private bool $cachingEnabled) {}
public function isCachingEnabled(): bool
{
return $this->cachingEnabled;
}
}
+44
View File
@@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
namespace TYPO3\CMS\Core\Core;
use TYPO3\CMS\Core\Security\ContentSecurityPolicy\ConsumableNonce;
/**
* @internal
*/
final class RequestId implements \Stringable
{
public readonly string $long;
public readonly string $short;
public readonly int $microtime;
public readonly ConsumableNonce $nonce;
public function __construct()
{
$this->long = bin2hex(random_bytes(20));
$this->short = substr($this->long, 0, 13);
$this->microtime = (int)(microtime(true) * 1000000);
$this->nonce = new ConsumableNonce();
}
public function __toString(): string
{
return $this->short;
}
}
+352
View File
@@ -0,0 +1,352 @@
<?php
declare(strict_types=1);
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
namespace TYPO3\CMS\Core\Core;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\PathUtility;
/**
* Class to encapsulate base setup of bootstrap.
*
* This class contains all code that must be executed by every entry script.
*
* It sets up all basic paths, constants, global variables and checks
* the basic environment TYPO3 runs in.
*
* This class does not use any TYPO3 instance specific configuration, it only
* sets up things based on the server environment and core code. Even with a
* missing system/settings.php this script will be successful.
*
* The script aborts execution with an error message if
* some part fails or conditions are not met.
*
* @internal This script is internal code and subject to change.
*
* Note that changes in this file must be carefully tested along with the
* typo3/testing-framework not only in Core CI, but also when used to test
* extensions or projects to ensure that things like link generation works
* as expected and returns leading slashes, important value calculation in
* NormalizedParams during frontend sub-requests determines correct values
* and similar.
*/
class SystemEnvironmentBuilder
{
/** @internal */
public const REQUESTTYPE_FE = 1;
/** @internal */
public const REQUESTTYPE_BE = 2;
/** @internal */
public const REQUESTTYPE_CLI = 4;
/** @internal */
public const REQUESTTYPE_AJAX = 8;
/** @internal */
public const REQUESTTYPE_INSTALL = 16;
/**
* Run base setup.
* This entry method is used in all scopes (FE, BE, Install Tool and CLI)
*
* @internal This method should not be used by 3rd party code. It will change without further notice.
* @param int $entryPointLevel Number of subdirectories where the entry script is located under the document root
*/
public static function run(int $entryPointLevel = 0, int $requestType = 0)
{
static::defineBaseConstants();
$scriptPath = static::calculateScriptPath($entryPointLevel, $requestType);
$rootPath = static::calculateRootPath($entryPointLevel, $requestType);
static::initializeGlobalVariables();
static::initializeGlobalTimeTrackingVariables();
static::initializeEnvironment($requestType, $scriptPath, $rootPath);
}
/**
* Some notes:
*
* HTTP_TYPO3_CONTEXT -> used with Apache suexec support
* REDIRECT_TYPO3_CONTEXT -> used under some circumstances when value is set in the webserver and proxying the values to FPM
* @throws \TYPO3\CMS\Core\Exception
*/
protected static function createApplicationContext(): ApplicationContext
{
$applicationContext = getenv('TYPO3_CONTEXT') ?: (getenv('REDIRECT_TYPO3_CONTEXT') ?: (getenv('HTTP_TYPO3_CONTEXT') ?: 'Production'));
return new ApplicationContext($applicationContext);
}
/**
* Define all simple constants that have no dependency to local configuration
*/
protected static function defineBaseConstants()
{
// A linefeed, a carriage return, a CR-LF combination
defined('LF') ?: define('LF', chr(10));
defined('CR') ?: define('CR', chr(13));
defined('CRLF') ?: define('CRLF', CR . LF);
// A generic constant to state we are in TYPO3 scope. This is especially used in script files
// like ext_localconf.php that run in global scope without class encapsulation: "defined('TYPO3') or die();"
// This is a security measure to prevent script output if those files are located within document root and
// called directly without bootstrap and error handling setup.
defined('TYPO3') ?: define('TYPO3', true);
}
/**
* Calculate script path. This is the absolute path to the entry script.
* Can be something like '.../public/index.php' for web calls, or
* '.../bin/typo3' or similar for cli calls.
*
* @param int $entryPointLevel Number of subdirectories where the entry script is located under the document root
* @return string Absolute path to entry script
*/
protected static function calculateScriptPath(int $entryPointLevel, int $requestType): string
{
$isCli = static::isCliRequestType($requestType);
// Absolute path of the entry script that was called
$scriptPath = GeneralUtility::fixWindowsFilePath((string)static::getPathThisScript($isCli));
$rootPath = static::getRootPathFromScriptPath($scriptPath, $entryPointLevel);
// Check if the root path has been set in the environment (e.g. by the composer installer)
$rootPathFromEnvironment = static::getDefinedPathRoot();
if ($rootPathFromEnvironment) {
if ($isCli && static::usesComposerClassLoading()) {
// $scriptPath is used for various path calculations based on the document root
// Therefore we assume it is always a subdirectory of the document root, which is not the case
// in composer mode on cli, as the binary is in the composer bin directory.
// Because of that, we enforce the document root path of this binary to be set
$scriptName = 'typo3/sysext/core/bin/typo3';
} else {
// Base the script path on the path taken from the environment
// to make relative path calculations work in case only one of both is symlinked
// or has the real path
$scriptName = ltrim(substr($scriptPath, strlen($rootPath)), '/');
}
$rootPath = rtrim(GeneralUtility::fixWindowsFilePath($rootPathFromEnvironment), '/');
$scriptPath = $rootPath . '/' . $scriptName;
}
return $scriptPath;
}
/**
* Absolute path to the "classic" site root of the TYPO3 application.
* This semantically refers to the directory where executable server-side code, configuration
* and runtime files are located (e.g. typo3conf/ext, typo3/sysext, typo3temp/var).
* In practice this is always identical to the public web document root path which contains
* files that are served by the webserver directly (fileadmin/ and public resources).
*
* This is not to be confused with the app-path that is used in composer-mode installations (by default).
* Resources in app-path are located outside the document root.
*
* @param int $entryPointLevel Number of subdirectories where the entry script is located under the document root
* @param int $requestType
* @return string Absolute path without trailing slash
*/
protected static function calculateRootPath(int $entryPointLevel, int $requestType): string
{
// Check if the root path has been set in the environment (e.g. by the composer installer)
$pathRoot = static::getDefinedPathRoot();
if ($pathRoot) {
return rtrim(GeneralUtility::fixWindowsFilePath($pathRoot), '/');
}
$isCli = static::isCliRequestType($requestType);
// Absolute path of the entry script that was called
$scriptPath = GeneralUtility::fixWindowsFilePath((string)static::getPathThisScript($isCli));
return static::getRootPathFromScriptPath($scriptPath, $entryPointLevel);
}
/**
* Set up / initialize several globals variables
*/
protected static function initializeGlobalVariables()
{
// Unset variable(s) in global scope (security issue #13959)
$GLOBALS['T3_SERVICES'] = [];
}
/**
* Initialize global time tracking variables.
* These are helpers to for example output script parsetime at the end of a script.
*/
protected static function initializeGlobalTimeTrackingVariables()
{
// EXEC_TIME is set so that the rest of the script has a common value for the script execution time
$GLOBALS['EXEC_TIME'] = time();
// $ACCESS_TIME is a common time in minutes for access control
$GLOBALS['ACCESS_TIME'] = $GLOBALS['EXEC_TIME'] - $GLOBALS['EXEC_TIME'] % 60;
// $SIM_EXEC_TIME is set to $EXEC_TIME but can be altered later in the script if we want to
// simulate another execution-time when selecting from eg. a database
$GLOBALS['SIM_EXEC_TIME'] = $GLOBALS['EXEC_TIME'];
// If $SIM_EXEC_TIME is changed this value must be set accordingly
$GLOBALS['SIM_ACCESS_TIME'] = $GLOBALS['ACCESS_TIME'];
}
protected static function getDefinedPathRoot(): string
{
return getenv('TYPO3_PATH_ROOT') ?: getenv('REDIRECT_TYPO3_PATH_ROOT') ?: '';
}
/**
* Initialize the Environment class
*/
protected static function initializeEnvironment(int $requestType, string $scriptPath, string $sitePath)
{
$pathRoot = static::getDefinedPathRoot();
if ($pathRoot) {
$rootPathFromEnvironment = rtrim(GeneralUtility::fixWindowsFilePath($pathRoot), '/');
if ($sitePath !== $rootPathFromEnvironment) {
// This means, that we re-initialized the environment during a single request
// This currently only happens in custom code or during functional testing
// Once the constants are removed, we might be able to remove this code here as well and directly pass an environment to the application
$scriptPath = $rootPathFromEnvironment . substr($scriptPath, strlen($sitePath));
$sitePath = $rootPathFromEnvironment;
}
}
$projectRootPath = (string)(getenv('TYPO3_PATH_APP') ?: getenv('REDIRECT_TYPO3_PATH_APP') ?: '');
$projectRootPath = GeneralUtility::fixWindowsFilePath($projectRootPath);
$isDifferentRootPath = ($projectRootPath && $projectRootPath !== $sitePath);
Environment::initialize(
static::createApplicationContext(),
static::isCliRequestType($requestType),
static::usesComposerClassLoading(),
$isDifferentRootPath ? $projectRootPath : $sitePath,
$sitePath,
$isDifferentRootPath ? $projectRootPath . '/var' : $sitePath . '/typo3temp/var',
$isDifferentRootPath ? $projectRootPath . '/config' : $sitePath . '/typo3conf',
$scriptPath,
static::isRunningOnWindows() ? 'WINDOWS' : 'UNIX'
);
}
/**
* Determine if the operating system TYPO3 is running on is windows.
*/
protected static function isRunningOnWindows(): bool
{
return stripos(PHP_OS, 'darwin') === false
&& stripos(PHP_OS, 'cygwin') === false
&& stripos(PHP_OS, 'win') !== false;
}
/**
* Calculate script path.
*
* First step in path calculation: Goal is to find the absolute path of the entry script
* that was called without resolving any links. This is important since the TYPO3 entry
* points are often linked to a central core location, so we can not use the php magic
* __FILE__ here, but resolve the called script path from given server environments.
*
* This path is important to calculate the document root. The strategy is to
* find out the script name that was called in the first place and to subtract the local
* part from it to find the document root.
*
* @param bool $isCli
* @return string Absolute path to entry script
*/
protected static function getPathThisScript(bool $isCli)
{
if ($isCli) {
return static::getPathThisScriptCli();
}
return static::getPathThisScriptNonCli();
}
/**
* Return path to entry script if not in cli mode.
*
* @return string Absolute path to entry script
*/
protected static function getPathThisScriptNonCli()
{
if (Environment::isRunningOnCgiServer() && !Environment::usesCgiFixPathInfo()) {
throw new \Exception('TYPO3 does only support being used with cgi.fix_pathinfo=1 on CGI server APIs.', 1675108421);
}
return $_SERVER['SCRIPT_FILENAME'];
}
/**
* Calculate path to entry script if in cli mode.
*
* First argument of a cli script is the path to the script that was called. If the script does not start
* with / (or A:\ for Windows), the path is not absolute yet, and the current working directory is added.
*
* @return string Absolute path to entry script
*/
protected static function getPathThisScriptCli()
{
// Possible relative path of the called script
$scriptPath = $_SERVER['argv'][0] ?? $_ENV['_'] ?? $_SERVER['_'];
// Find out if path is relative or not
$isRelativePath = false;
if (self::isRunningOnWindows()) {
if (!preg_match('/^([a-zA-Z]:)?\\\\/', $scriptPath)) {
$isRelativePath = true;
}
} elseif ($scriptPath[0] !== '/') {
$isRelativePath = true;
}
// Concatenate path to current working directory with relative path and remove "/./" constructs
if ($isRelativePath) {
$workingDirectory = $_SERVER['PWD'] ?? getcwd();
$scriptPath = $workingDirectory . '/' . preg_replace('/\\.\\//', '', $scriptPath);
}
return $scriptPath;
}
/**
* Calculate the document root part to the instance from $scriptPath.
* This is based on the amount of subdirectories "under" root path where $scriptPath is located.
*
* The following main scenarios for entry points exist by default in the TYPO3 core:
* - Directly called documentRoot/index.php (-> FE, BE or install tool call):
* index.php is located in the same directory as the main project.
* The document root is identical to the directory the script is located at.
* - The CLI script 'typo3/sysext/core/bin/typo3' which is located inside typo3/ directly.
*
* @param string $scriptPath Calculated path to the entry script
* @param int $entryPointLevel Number of subdirectories where the entry script is located under the document root
* @return string Absolute path to document root of installation without trailing slash
*/
protected static function getRootPathFromScriptPath($scriptPath, $entryPointLevel)
{
$entryScriptDirectory = PathUtility::dirnameDuringBootstrap($scriptPath);
if ($entryPointLevel > 0) {
[$rootPath] = GeneralUtility::revExplode('/', $entryScriptDirectory, $entryPointLevel + 1);
} else {
$rootPath = $entryScriptDirectory;
}
return $rootPath;
}
protected static function usesComposerClassLoading(): bool
{
return defined('TYPO3_COMPOSER_MODE') && TYPO3_COMPOSER_MODE;
}
/**
* Checks if request type is cli.
* Falls back to check PHP_SAPI in case request type is not provided
*/
protected static function isCliRequestType(?int $requestType): bool
{
if ($requestType === null) {
return PHP_SAPI === 'cli';
}
return ($requestType & self::REQUESTTYPE_CLI) === self::REQUESTTYPE_CLI;
}
}