TYPO3 v15 dev-main snapshot ()
This commit is contained in:
@@ -0,0 +1,336 @@
|
||||
<?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\Configuration;
|
||||
|
||||
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||
use Symfony\Component\Finder\Finder;
|
||||
use TYPO3\CMS\Core\Attribute\AsEventListener;
|
||||
use TYPO3\CMS\Core\Cache\Event\CacheWarmupEvent;
|
||||
use TYPO3\CMS\Core\Cache\Exception\InvalidDataException;
|
||||
use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
|
||||
use TYPO3\CMS\Core\Cache\Frontend\PhpFrontend;
|
||||
use TYPO3\CMS\Core\Configuration\Event\SiteConfigurationChangedEvent;
|
||||
use TYPO3\CMS\Core\Configuration\Event\SiteConfigurationLoadedEvent;
|
||||
use TYPO3\CMS\Core\Configuration\Loader\YamlFileLoader;
|
||||
use TYPO3\CMS\Core\Site\Entity\Site;
|
||||
use TYPO3\CMS\Core\Site\Entity\SiteSettings;
|
||||
use TYPO3\CMS\Core\Site\Entity\SiteTSconfig;
|
||||
use TYPO3\CMS\Core\Site\Entity\SiteTypoScript;
|
||||
use TYPO3\CMS\Core\Site\Set\SetError;
|
||||
use TYPO3\CMS\Core\Site\Set\SetRegistry;
|
||||
use TYPO3\CMS\Core\Site\SiteSettingsFactory;
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Responsibility: Handles the format of the configuration (currently yaml), and the location of the file system folder
|
||||
*
|
||||
* Reads all available site configuration options, and puts them into Site objects.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
#[Autoconfigure(public: true)]
|
||||
readonly class SiteConfiguration
|
||||
{
|
||||
/**
|
||||
* Config yaml file name.
|
||||
*/
|
||||
private const string CONFIG_FILE_NAME = 'config.yaml';
|
||||
|
||||
/**
|
||||
* File naming containing TypoScript Setup.
|
||||
*/
|
||||
private const string TYPOSCRIPT_SETUP_FILE_NAME = 'setup.typoscript';
|
||||
|
||||
/**
|
||||
* File naming containing TypoScript Constants.
|
||||
*/
|
||||
private const string TYPOSCRIPT_CONSTANTS_FILE_NAME = 'constants.typoscript';
|
||||
|
||||
/**
|
||||
* File naming containing page TSconfig definitions
|
||||
*/
|
||||
private const string PAGE_TSCONFIG_FILE_NAME = 'page.tsconfig';
|
||||
|
||||
/**
|
||||
* YAML file name with all settings related to Content-Security-Policies.
|
||||
*/
|
||||
private const string CONTENT_SECURITY_FILE_NAME = 'csp.yaml';
|
||||
|
||||
/**
|
||||
* Identifier to store all configuration data in the core cache.
|
||||
*/
|
||||
private const string CACHE_IDENTIFIER = 'sites-configuration';
|
||||
|
||||
public function __construct(
|
||||
#[Autowire('%env(TYPO3:configPath)%/sites')]
|
||||
protected string $configPath,
|
||||
protected SiteSettingsFactory $siteSettingsFactory,
|
||||
protected SetRegistry $setRegistry,
|
||||
protected EventDispatcherInterface $eventDispatcher,
|
||||
#[Autowire(service: 'cache.core')]
|
||||
protected PhpFrontend $cache,
|
||||
private readonly YamlFileLoader $yamlFileLoader,
|
||||
#[Autowire(service: 'cache.runtime')]
|
||||
protected readonly FrontendInterface $runtimeCache,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Return all site objects which have been found in the filesystem.
|
||||
*
|
||||
* @return Site[]
|
||||
*/
|
||||
public function getAllExistingSites(bool $useCache = true): array
|
||||
{
|
||||
if ($useCache && $this->runtimeCache->has(self::CACHE_IDENTIFIER)) {
|
||||
return $this->runtimeCache->get(self::CACHE_IDENTIFIER);
|
||||
}
|
||||
return $this->resolveAllExistingSites($useCache);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve all site objects which have been found in the filesystem.
|
||||
*
|
||||
* @return Site[]
|
||||
*/
|
||||
public function resolveAllExistingSites(bool $useCache = true): array
|
||||
{
|
||||
$sites = [];
|
||||
$siteConfiguration = $this->getAllSiteConfigurationFromFiles($useCache);
|
||||
foreach ($siteConfiguration as $identifier => $configuration) {
|
||||
// cast $identifier to string, as the identifier can potentially only consist of (int) digit numbers
|
||||
$identifier = (string)$identifier;
|
||||
$siteSettings = $this->siteSettingsFactory->getSettings($identifier, $configuration);
|
||||
$siteTypoScript = $this->getSiteTypoScript($identifier);
|
||||
$siteTSconfig = $this->getSiteTSconfig($identifier);
|
||||
$configuration['contentSecurityPolicies'] = $this->getContentSecurityPolicies($identifier);
|
||||
$configuration['routeEnhancers'] = ArrayUtility::replaceAndAppendScalarValuesRecursive(
|
||||
$this->getRouteEnhancersFromSets($configuration['dependencies'] ?? []),
|
||||
$configuration['routeEnhancers'] ?? []
|
||||
);
|
||||
|
||||
$rootPageId = (int)($configuration['rootPageId'] ?? 0);
|
||||
if ($rootPageId > 0) {
|
||||
$site = new Site($identifier, $rootPageId, $configuration, $siteSettings, $siteTypoScript, $siteTSconfig);
|
||||
$this->determineInvalidSets($site);
|
||||
$sites[$identifier] = $site;
|
||||
|
||||
}
|
||||
}
|
||||
$this->runtimeCache->set(self::CACHE_IDENTIFIER, $sites);
|
||||
return $sites;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve all site objects which have been found in the filesystem containing settings only from the `config.yaml`
|
||||
* file ignoring values from the `settings.yaml` and `csp.yaml` file.
|
||||
*
|
||||
* @return Site[]
|
||||
* @internal Not part of public API. Used as intermediate solution until settings are handled by a dedicated GUI.
|
||||
*/
|
||||
public function resolveAllExistingSitesRaw(): array
|
||||
{
|
||||
$sites = [];
|
||||
$siteConfiguration = $this->getAllSiteConfigurationFromFiles(false);
|
||||
foreach ($siteConfiguration as $identifier => $configuration) {
|
||||
// cast $identifier to string, as the identifier can potentially only consist of (int) digit numbers
|
||||
$identifier = (string)$identifier;
|
||||
$inlineSettings = $configuration['settings'] ?? [];
|
||||
$siteSettings = SiteSettings::createFromSettingsTree($inlineSettings);
|
||||
$siteTypoScript = $this->getSiteTypoScript($identifier);
|
||||
|
||||
$rootPageId = (int)($configuration['rootPageId'] ?? 0);
|
||||
if ($rootPageId > 0) {
|
||||
$site = new Site($identifier, $rootPageId, $configuration, $siteSettings, $siteTypoScript);
|
||||
$this->determineInvalidSets($site);
|
||||
$sites[$identifier] = $site;
|
||||
}
|
||||
}
|
||||
return $sites;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of paths in which a site configuration is found.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function getAllSiteConfigurationPaths(): array
|
||||
{
|
||||
$finder = new Finder();
|
||||
$paths = [];
|
||||
try {
|
||||
$finder->files()->depth(0)->name(self::CONFIG_FILE_NAME)->in($this->configPath . '/*');
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
$finder = [];
|
||||
}
|
||||
|
||||
foreach ($finder as $fileInfo) {
|
||||
$path = $fileInfo->getPath();
|
||||
$paths[basename($path)] = $path;
|
||||
}
|
||||
return $paths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the site configuration from config files.
|
||||
*
|
||||
* @throws InvalidDataException
|
||||
*/
|
||||
protected function getAllSiteConfigurationFromFiles(bool $useCache = true): array
|
||||
{
|
||||
// Check if the data is already cached
|
||||
$siteConfiguration = $useCache ? $this->cache->require(self::CACHE_IDENTIFIER) : false;
|
||||
if ($siteConfiguration !== false) {
|
||||
return $siteConfiguration;
|
||||
}
|
||||
$finder = new Finder();
|
||||
try {
|
||||
$finder->files()->depth(0)->name(self::CONFIG_FILE_NAME)->in($this->configPath . '/*');
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
// Directory $this->configPath does not exist yet
|
||||
$finder = [];
|
||||
}
|
||||
$siteConfiguration = [];
|
||||
foreach ($finder as $fileInfo) {
|
||||
$configuration = $this->yamlFileLoader->load(GeneralUtility::fixWindowsFilePath((string)$fileInfo));
|
||||
$identifier = basename($fileInfo->getPath());
|
||||
$event = $this->eventDispatcher->dispatch(new SiteConfigurationLoadedEvent($identifier, $configuration));
|
||||
$siteConfiguration[$identifier] = $event->getConfiguration();
|
||||
}
|
||||
$this->cache->set(self::CACHE_IDENTIFIER, 'return ' . var_export($siteConfiguration, true) . ';');
|
||||
|
||||
return $siteConfiguration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load plain configuration without additional settings.
|
||||
*
|
||||
* This method should only be used in case the original configuration as it exists in the file should be loaded,
|
||||
* for example for writing / editing configuration.
|
||||
*
|
||||
* All read related actions should be performed on the site entity.
|
||||
*
|
||||
* @param string $siteIdentifier
|
||||
*/
|
||||
public function load(string $siteIdentifier): array
|
||||
{
|
||||
$fileName = $this->configPath . '/' . $siteIdentifier . '/' . self::CONFIG_FILE_NAME;
|
||||
return $this->yamlFileLoader->load(GeneralUtility::fixWindowsFilePath($fileName), YamlFileLoader::PROCESS_IMPORTS);
|
||||
}
|
||||
|
||||
protected function getSiteTypoScript(string $siteIdentifier): ?SiteTypoScript
|
||||
{
|
||||
$data = [
|
||||
'setup' => self::TYPOSCRIPT_SETUP_FILE_NAME,
|
||||
'constants' => self::TYPOSCRIPT_CONSTANTS_FILE_NAME,
|
||||
];
|
||||
$definitions = [];
|
||||
foreach ($data as $type => $fileName) {
|
||||
$path = $this->configPath . '/' . $siteIdentifier . '/' . $fileName;
|
||||
if (file_exists($path)) {
|
||||
$contents = @file_get_contents(GeneralUtility::fixWindowsFilePath($path));
|
||||
if ($contents !== false) {
|
||||
$definitions[$type] = $contents;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($definitions === []) {
|
||||
return null;
|
||||
}
|
||||
return new SiteTypoScript(...$definitions);
|
||||
}
|
||||
|
||||
protected function getSiteTSconfig(string $siteIdentifier): ?SiteTSconfig
|
||||
{
|
||||
$pageTSconfig = null;
|
||||
$path = $this->configPath . '/' . $siteIdentifier . '/' . self::PAGE_TSCONFIG_FILE_NAME;
|
||||
if (file_exists($path)) {
|
||||
$contents = @file_get_contents(GeneralUtility::fixWindowsFilePath($path));
|
||||
if ($contents !== false) {
|
||||
$pageTSconfig = $contents;
|
||||
}
|
||||
}
|
||||
if ($pageTSconfig === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new SiteTSconfig(
|
||||
pageTSconfig: $pageTSconfig
|
||||
);
|
||||
}
|
||||
|
||||
protected function getContentSecurityPolicies(string $siteIdentifier): array
|
||||
{
|
||||
$fileName = $this->configPath . '/' . $siteIdentifier . '/' . self::CONTENT_SECURITY_FILE_NAME;
|
||||
if (file_exists($fileName)) {
|
||||
return $this->yamlFileLoader->load(GeneralUtility::fixWindowsFilePath($fileName));
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get route enhancers from site sets.
|
||||
*/
|
||||
protected function getRouteEnhancersFromSets(array $dependencies): array
|
||||
{
|
||||
$routeEnhancers = [];
|
||||
$sets = $this->setRegistry->getSets(...$dependencies);
|
||||
foreach ($sets as $set) {
|
||||
$routeEnhancers = ArrayUtility::replaceAndAppendScalarValuesRecursive(
|
||||
$routeEnhancers,
|
||||
$set->routeEnhancers
|
||||
);
|
||||
}
|
||||
return $routeEnhancers;
|
||||
}
|
||||
|
||||
protected function determineInvalidSets(Site $site): void
|
||||
{
|
||||
$site->invalidSets = array_filter(
|
||||
$this->setRegistry->getInvalidSets(),
|
||||
static fn($setName) => in_array($setName, $site->getSets(), true),
|
||||
ARRAY_FILTER_USE_KEY
|
||||
);
|
||||
foreach ($site->getSets() as $set) {
|
||||
if (!$this->setRegistry->hasSet($set) && !isset($site->invalidSets[$set])) {
|
||||
$site->invalidSets[$set] = [
|
||||
'name' => $set,
|
||||
'error' => SetError::notFound,
|
||||
'context' => 'site:' . $site->getIdentifier(),
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[AsEventListener(event: SiteConfigurationChangedEvent::class)]
|
||||
public function siteConfigurationChanged(): void
|
||||
{
|
||||
$this->cache->remove(self::CACHE_IDENTIFIER);
|
||||
$this->runtimeCache->remove(self::CACHE_IDENTIFIER);
|
||||
}
|
||||
|
||||
#[AsEventListener('typo3-core/site-configuration')]
|
||||
public function warmupCaches(CacheWarmupEvent $event): void
|
||||
{
|
||||
if ($event->hasGroup('system')) {
|
||||
$this->getAllSiteConfigurationFromFiles(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user