TYPO3 v15 dev-main snapshot ()
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user