From d79cda8d81a9f4bc2f834ee0feea06edf4fa967a Mon Sep 17 00:00:00 2001 From: Benjamin Franzke <ben@bnf.dev> Date: Thu, 21 Mar 2024 11:34:23 +0100 Subject: [PATCH] [TASK] Extract site persistence into separate service MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The SiteConfiguration service is currently needed during installation phase for writing site configurations. This limits symfony dependency injection usage and basically requires that all services needed by SiteConfiguration need to be defined and manually wired in ServiceProvider php code. The writing part is now split into a separate service that can be used in EXT:install SetupService as before. Resolves: #103450 Releases: main Change-Id: I9cb579ade537c794ce7c1a844b3d7bec7c1b653e Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/83557 Tested-by: core-ci <typo3@b13.com> Reviewed-by: Jörg Bösche <typo3@joergboesche.de> Reviewed-by: Georg Ringer <georg.ringer@gmail.com> Tested-by: Georg Ringer <georg.ringer@gmail.com> Tested-by: Benni Mack <benni@typo3.org> Reviewed-by: Benni Mack <benni@typo3.org> --- .../SiteConfigurationController.php | 9 +- .../Event/SiteConfigurationChangedEvent.php | 28 ++ .../Configuration/SiteConfiguration.php | 194 +------------- .../core/Classes/Configuration/SiteWriter.php | 247 ++++++++++++++++++ .../Classes/Hooks/CreateSiteConfiguration.php | 6 +- typo3/sysext/core/Classes/ServiceProvider.php | 7 +- typo3/sysext/core/Configuration/Services.yaml | 5 - .../Aspect/PersistedAliasMapperTest.php | 4 +- .../Aspect/PersistedPatternMapperTest.php | 4 +- .../SiteHandling/SiteBasedTestTrait.php | 8 +- .../Configuration/SiteConfigurationTest.php | 109 -------- .../Unit/Configuration/SiteWriterTest.php | 164 ++++++++++++ ...eConfigurationsOnPackageInitialization.php | 4 +- .../install/Classes/Service/SetupService.php | 6 +- .../install/Classes/ServiceProvider.php | 4 +- .../MigrateSiteSettingsConfigUpdate.php | 5 +- .../MigrateSiteSettingsConfigUpdateTest.php | 7 +- .../AddPageTypeZeroSourceTest.php | 6 +- .../AddPlainSlugReplacementSourceTest.php | 6 +- .../SlugRedirectChangeItemFactoryTest.php | 7 +- .../Functional/Service/SlugServiceTest.php | 18 +- .../TcaDataGenerator/AbstractGenerator.php | 8 +- .../Classes/TcaDataGenerator/Generator.php | 4 +- .../TcaDataGenerator/GeneratorFrontend.php | 4 +- 24 files changed, 508 insertions(+), 356 deletions(-) create mode 100644 typo3/sysext/core/Classes/Configuration/Event/SiteConfigurationChangedEvent.php create mode 100644 typo3/sysext/core/Classes/Configuration/SiteWriter.php create mode 100644 typo3/sysext/core/Tests/Unit/Configuration/SiteWriterTest.php diff --git a/typo3/sysext/backend/Classes/Controller/SiteConfigurationController.php b/typo3/sysext/backend/Classes/Controller/SiteConfigurationController.php index 9eea8a9ae0bb..83b40ffed1de 100644 --- a/typo3/sysext/backend/Classes/Controller/SiteConfigurationController.php +++ b/typo3/sysext/backend/Classes/Controller/SiteConfigurationController.php @@ -34,6 +34,7 @@ use TYPO3\CMS\Backend\Utility\BackendUtility; use TYPO3\CMS\Core\Authentication\BackendUserAuthentication; use TYPO3\CMS\Core\Configuration\Exception\SiteConfigurationWriteException; use TYPO3\CMS\Core\Configuration\SiteConfiguration; +use TYPO3\CMS\Core\Configuration\SiteWriter; use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Database\Query\Restriction\HiddenRestriction; use TYPO3\CMS\Core\Database\Query\Restriction\WorkspaceRestriction; @@ -72,6 +73,7 @@ class SiteConfigurationController private readonly FormDataCompiler $formDataCompiler, private readonly PageRenderer $pageRenderer, private readonly SiteConfiguration $siteConfiguration, + private readonly SiteWriter $siteWriter, ) {} /** @@ -402,13 +404,12 @@ class SiteConfigurationController ); // Persist the configuration - $siteConfigurationManager = GeneralUtility::makeInstance(SiteConfiguration::class); try { if (!$isNewConfiguration && $currentIdentifier !== $siteIdentifier) { - $siteConfigurationManager->rename($currentIdentifier, $siteIdentifier); + $this->siteWriter->rename($currentIdentifier, $siteIdentifier); $this->getBackendUser()->writelog(Type::SITE, SiteAction::RENAME, SystemLogErrorClassification::MESSAGE, 0, 'Site configuration \'%s\' was renamed to \'%s\'.', [$currentIdentifier, $siteIdentifier], 'site'); } - $siteConfigurationManager->write($siteIdentifier, $newSiteConfiguration, true); + $this->siteWriter->write($siteIdentifier, $newSiteConfiguration, true); if ($isNewConfiguration) { $this->getBackendUser()->writelog(Type::SITE, SiteAction::CREATE, SystemLogErrorClassification::MESSAGE, 0, 'Site configuration \'%s\' was created.', [$siteIdentifier], 'site'); } else { @@ -656,7 +657,7 @@ class SiteConfigurationController } try { // Verify site does exist, method throws if not - GeneralUtility::makeInstance(SiteConfiguration::class)->delete($siteIdentifier); + $this->siteWriter->delete($siteIdentifier); $this->getBackendUser()->writelog(Type::SITE, SiteAction::DELETE, SystemLogErrorClassification::MESSAGE, 0, 'Site configuration \'%s\' was deleted.', [$siteIdentifier], 'site'); } catch (SiteConfigurationWriteException $e) { $flashMessage = GeneralUtility::makeInstance(FlashMessage::class, $e->getMessage(), '', ContextualFeedbackSeverity::WARNING, true); diff --git a/typo3/sysext/core/Classes/Configuration/Event/SiteConfigurationChangedEvent.php b/typo3/sysext/core/Classes/Configuration/Event/SiteConfigurationChangedEvent.php new file mode 100644 index 000000000000..264052de11bd --- /dev/null +++ b/typo3/sysext/core/Classes/Configuration/Event/SiteConfigurationChangedEvent.php @@ -0,0 +1,28 @@ +<?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\Event; + +/** + * @internal + */ +final readonly class SiteConfigurationChangedEvent +{ + public function __construct( + public string $siteIdentifier, + ) {} +} diff --git a/typo3/sysext/core/Classes/Configuration/SiteConfiguration.php b/typo3/sysext/core/Classes/Configuration/SiteConfiguration.php index fc3f084ad31d..91a5eeb13838 100644 --- a/typo3/sysext/core/Classes/Configuration/SiteConfiguration.php +++ b/typo3/sysext/core/Classes/Configuration/SiteConfiguration.php @@ -18,22 +18,19 @@ declare(strict_types=1); namespace TYPO3\CMS\Core\Configuration; use Psr\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfony\Component\Finder\Finder; use Symfony\Component\Yaml\Yaml; +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\PhpFrontend; -use TYPO3\CMS\Core\Configuration\Event\SiteConfigurationBeforeWriteEvent; +use TYPO3\CMS\Core\Configuration\Event\SiteConfigurationChangedEvent; use TYPO3\CMS\Core\Configuration\Event\SiteConfigurationLoadedEvent; -use TYPO3\CMS\Core\Configuration\Exception\SiteConfigurationWriteException; -use TYPO3\CMS\Core\Configuration\Loader\Exception\YamlPlaceholderException; use TYPO3\CMS\Core\Configuration\Loader\YamlFileLoader; -use TYPO3\CMS\Core\Configuration\Loader\YamlPlaceholderGuard; -use TYPO3\CMS\Core\Exception\SiteNotFoundException; use TYPO3\CMS\Core\SingletonInterface; use TYPO3\CMS\Core\Site\Entity\Site; use TYPO3\CMS\Core\Site\Entity\SiteSettings; -use TYPO3\CMS\Core\Utility\ArrayUtility; use TYPO3\CMS\Core\Utility\GeneralUtility; /** @@ -82,8 +79,10 @@ class SiteConfiguration implements SingletonInterface protected $firstLevelCache; public function __construct( + #[Autowire('%env(TYPO3:configPath)%/sites')] protected string $configPath, protected EventDispatcherInterface $eventDispatcher, + #[Autowire(service: 'cache.core')] protected PhpFrontend $cache ) {} @@ -100,33 +99,6 @@ class SiteConfiguration implements SingletonInterface return $this->resolveAllExistingSites($useCache); } - /** - * Creates a site configuration with one language "English" which is the de-facto default language for TYPO3 in general. - * - * @throws SiteConfigurationWriteException - */ - public function createNewBasicSite(string $identifier, int $rootPageId, string $base): void - { - // Create a default site configuration called "main" as best practice - $this->write($identifier, [ - 'rootPageId' => $rootPageId, - 'base' => $base, - 'languages' => [ - 0 => [ - 'title' => 'English', - 'enabled' => true, - 'languageId' => 0, - 'base' => '/', - 'locale' => 'en_US.UTF-8', - 'navigationTitle' => 'English', - 'flag' => 'us', - ], - ], - 'errorHandling' => [], - 'routes' => [], - ]); - } - /** * Resolve all site objects which have been found in the filesystem. * @@ -277,163 +249,13 @@ class SiteConfiguration implements SingletonInterface return []; } - public function writeSettings(string $siteIdentifier, array $settings): void - { - $fileName = $this->configPath . '/' . $siteIdentifier . '/' . $this->settingsFileName; - $yamlFileContents = Yaml::dump($settings, 99, 2); - if (!GeneralUtility::writeFile($fileName, $yamlFileContents)) { - throw new SiteConfigurationWriteException('Unable to write site settings in sites/' . $siteIdentifier . '/' . $this->configFileName, 1590487411); - } - } - - /** - * Add or update a site configuration - * - * @param bool $protectPlaceholders whether to disallow introducing new placeholders - * @todo enforce $protectPlaceholders with TYPO3 v13.0 - * @throws SiteConfigurationWriteException - */ - public function write(string $siteIdentifier, array $configuration, bool $protectPlaceholders = false): void + #[AsEventListener(event: SiteConfigurationChangedEvent::class)] + public function siteConfigurationChanged() { - $folder = $this->configPath . '/' . $siteIdentifier; - $fileName = $folder . '/' . $this->configFileName; - $newConfiguration = $configuration; - if (!file_exists($folder)) { - GeneralUtility::mkdir_deep($folder); - if ($protectPlaceholders && $newConfiguration !== []) { - $newConfiguration = $this->protectPlaceholders([], $newConfiguration); - } - } elseif (file_exists($fileName)) { - $loader = GeneralUtility::makeInstance(YamlFileLoader::class); - // load without any processing to have the unprocessed base to modify - $newConfiguration = $loader->load(GeneralUtility::fixWindowsFilePath($fileName), 0); - // load the processed configuration to diff changed values - $processed = $loader->load(GeneralUtility::fixWindowsFilePath($fileName)); - // find properties that were modified via GUI - $newModified = array_replace_recursive( - self::findRemoved($processed, $configuration), - self::findModified($processed, $configuration) - ); - if ($protectPlaceholders && $newModified !== []) { - $newModified = $this->protectPlaceholders($newConfiguration, $newModified); - } - // change _only_ the modified keys, leave the original non-changed areas alone - ArrayUtility::mergeRecursiveWithOverrule($newConfiguration, $newModified); - } - $event = $this->eventDispatcher->dispatch(new SiteConfigurationBeforeWriteEvent($siteIdentifier, $newConfiguration)); - $newConfiguration = $this->sortConfiguration($event->getConfiguration()); - $yamlFileContents = Yaml::dump($newConfiguration, 99, 2); - if (!GeneralUtility::writeFile($fileName, $yamlFileContents)) { - throw new SiteConfigurationWriteException('Unable to write site configuration in sites/' . $siteIdentifier . '/' . $this->configFileName, 1590487011); - } $this->firstLevelCache = null; - $this->cache->remove($this->cacheIdentifier); - } - - /** - * Renames a site identifier (and moves the folder) - * - * @throws SiteConfigurationWriteException - */ - public function rename(string $currentIdentifier, string $newIdentifier): void - { - if (!rename($this->configPath . '/' . $currentIdentifier, $this->configPath . '/' . $newIdentifier)) { - throw new SiteConfigurationWriteException('Unable to rename folder sites/' . $currentIdentifier, 1522491300); - } - $this->cache->remove($this->cacheIdentifier); - $this->firstLevelCache = null; - } - - /** - * Removes the config.yaml file of a site configuration. - * Also clears the cache. - * - * @throws SiteNotFoundException|SiteConfigurationWriteException - */ - public function delete(string $siteIdentifier): void - { - $sites = $this->getAllExistingSites(); - if (!isset($sites[$siteIdentifier])) { - throw new SiteNotFoundException('Site configuration named ' . $siteIdentifier . ' not found.', 1522866183); - } - $fileName = $this->configPath . '/' . $siteIdentifier . '/' . $this->configFileName; - if (!file_exists($fileName)) { - throw new SiteNotFoundException('Site configuration file ' . $this->configFileName . ' within the site ' . $siteIdentifier . ' not found.', 1522866184); - } - if (!unlink($fileName)) { - throw new SiteConfigurationWriteException('Unable to delete folder sites/' . $siteIdentifier, 1596462020); - } - $this->cache->remove($this->cacheIdentifier); - $this->firstLevelCache = null; - } - - /** - * Detects placeholders that have been introduced and handles* them. - * (*) currently throws an exception, but could be purged or escaped as well - * - * @param array<string, mixed> $existingConfiguration - * @param array<string, mixed> $modifiedConfiguration - * @return array<string, mixed> sanitized configuration (currently not used, exception thrown before) - * @throws SiteConfigurationWriteException - */ - protected function protectPlaceholders(array $existingConfiguration, array $modifiedConfiguration): array - { - try { - return GeneralUtility::makeInstance(YamlPlaceholderGuard::class, $existingConfiguration) - ->process($modifiedConfiguration); - } catch (YamlPlaceholderException $exception) { - throw new SiteConfigurationWriteException($exception->getMessage(), 1670361271, $exception); - } - } - - protected function sortConfiguration(array $newConfiguration): array - { - ksort($newConfiguration); - if (isset($newConfiguration['imports'])) { - $imports = $newConfiguration['imports']; - unset($newConfiguration['imports']); - $newConfiguration['imports'] = $imports; - } - return $newConfiguration; - } - - protected static function findModified(array $currentConfiguration, array $newConfiguration): array - { - $differences = []; - foreach ($newConfiguration as $key => $value) { - if (!isset($currentConfiguration[$key]) || $currentConfiguration[$key] !== $newConfiguration[$key]) { - if (!isset($newConfiguration[$key]) && isset($currentConfiguration[$key])) { - $differences[$key] = '__UNSET'; - } elseif (isset($currentConfiguration[$key]) - && is_array($newConfiguration[$key]) - && is_array($currentConfiguration[$key]) - ) { - $differences[$key] = self::findModified($currentConfiguration[$key], $newConfiguration[$key]); - } else { - $differences[$key] = $value; - } - } - } - return $differences; - } - - protected static function findRemoved(array $currentConfiguration, array $newConfiguration): array - { - $removed = []; - foreach ($currentConfiguration as $key => $value) { - if (!isset($newConfiguration[$key])) { - $removed[$key] = '__UNSET'; - } elseif (isset($currentConfiguration[$key]) && is_array($currentConfiguration[$key]) && is_array($newConfiguration[$key])) { - $removedInRecursion = self::findRemoved($currentConfiguration[$key], $newConfiguration[$key]); - if (!empty($removedInRecursion)) { - $removed[$key] = $removedInRecursion; - } - } - } - - return $removed; } + #[AsEventListener('typo3-core/site-configuration')] public function warmupCaches(CacheWarmupEvent $event): void { if ($event->hasGroup('system')) { diff --git a/typo3/sysext/core/Classes/Configuration/SiteWriter.php b/typo3/sysext/core/Classes/Configuration/SiteWriter.php new file mode 100644 index 000000000000..98e951e34f6e --- /dev/null +++ b/typo3/sysext/core/Classes/Configuration/SiteWriter.php @@ -0,0 +1,247 @@ +<?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\Yaml\Yaml; +use TYPO3\CMS\Core\Cache\Frontend\PhpFrontend; +use TYPO3\CMS\Core\Configuration\Event\SiteConfigurationBeforeWriteEvent; +use TYPO3\CMS\Core\Configuration\Event\SiteConfigurationChangedEvent; +use TYPO3\CMS\Core\Configuration\Exception\SiteConfigurationWriteException; +use TYPO3\CMS\Core\Configuration\Loader\Exception\YamlPlaceholderException; +use TYPO3\CMS\Core\Configuration\Loader\YamlFileLoader; +use TYPO3\CMS\Core\Configuration\Loader\YamlPlaceholderGuard; +use TYPO3\CMS\Core\Exception\SiteNotFoundException; +use TYPO3\CMS\Core\Utility\ArrayUtility; +use TYPO3\CMS\Core\Utility\GeneralUtility; + +/** + * Writes Site objects into site configuration files. + * + * @internal + */ +class SiteWriter +{ + /** + * Config yaml file name. + * + * @internal + */ + protected string $configFileName = 'config.yaml'; + + /** + * YAML file name with all settings. + * + * @internal + * @todo remove, move usages to SiteSettingsFactory + */ + protected string $settingsFileName = 'settings.yaml'; + + /** + * Identifier to store all configuration data in the core cache. + * + * @internal + */ + protected string $cacheIdentifier = 'sites-configuration'; + + public function __construct( + protected string $configPath, + protected EventDispatcherInterface $eventDispatcher, + protected PhpFrontend $cache + ) {} + + /** + * Creates a site configuration with one language "English" which is the de-facto default language for TYPO3 in general. + * + * @throws SiteConfigurationWriteException + */ + public function createNewBasicSite(string $identifier, int $rootPageId, string $base): void + { + // Create a default site configuration called "main" as best practice + $this->write($identifier, [ + 'rootPageId' => $rootPageId, + 'base' => $base, + 'languages' => [ + 0 => [ + 'title' => 'English', + 'enabled' => true, + 'languageId' => 0, + 'base' => '/', + 'locale' => 'en_US.UTF-8', + 'navigationTitle' => 'English', + 'flag' => 'us', + ], + ], + 'errorHandling' => [], + 'routes' => [], + ]); + } + + public function writeSettings(string $siteIdentifier, array $settings): void + { + $fileName = $this->configPath . '/' . $siteIdentifier . '/' . $this->settingsFileName; + $yamlFileContents = Yaml::dump($settings, 99, 2); + if (!GeneralUtility::writeFile($fileName, $yamlFileContents)) { + throw new SiteConfigurationWriteException('Unable to write site settings in sites/' . $siteIdentifier . '/' . $this->configFileName, 1590487411); + } + } + + /** + * Add or update a site configuration + * + * @param bool $protectPlaceholders whether to disallow introducing new placeholders + * @todo enforce $protectPlaceholders with TYPO3 v13.0 + * @throws SiteConfigurationWriteException + */ + public function write(string $siteIdentifier, array $configuration, bool $protectPlaceholders = false): void + { + $folder = $this->configPath . '/' . $siteIdentifier; + $fileName = $folder . '/' . $this->configFileName; + $newConfiguration = $configuration; + if (!file_exists($folder)) { + GeneralUtility::mkdir_deep($folder); + if ($protectPlaceholders && $newConfiguration !== []) { + $newConfiguration = $this->protectPlaceholders([], $newConfiguration); + } + } elseif (file_exists($fileName)) { + $loader = GeneralUtility::makeInstance(YamlFileLoader::class); + // load without any processing to have the unprocessed base to modify + $newConfiguration = $loader->load(GeneralUtility::fixWindowsFilePath($fileName), 0); + // load the processed configuration to diff changed values + $processed = $loader->load(GeneralUtility::fixWindowsFilePath($fileName)); + // find properties that were modified via GUI + $newModified = array_replace_recursive( + self::findRemoved($processed, $configuration), + self::findModified($processed, $configuration) + ); + if ($protectPlaceholders && $newModified !== []) { + $newModified = $this->protectPlaceholders($newConfiguration, $newModified); + } + // change _only_ the modified keys, leave the original non-changed areas alone + ArrayUtility::mergeRecursiveWithOverrule($newConfiguration, $newModified); + } + $event = $this->eventDispatcher->dispatch(new SiteConfigurationBeforeWriteEvent($siteIdentifier, $newConfiguration)); + $newConfiguration = $this->sortConfiguration($event->getConfiguration()); + $yamlFileContents = Yaml::dump($newConfiguration, 99, 2); + if (!GeneralUtility::writeFile($fileName, $yamlFileContents)) { + throw new SiteConfigurationWriteException('Unable to write site configuration in sites/' . $siteIdentifier . '/' . $this->configFileName, 1590487011); + } + $this->cache->remove($this->cacheIdentifier); + $this->eventDispatcher->dispatch(new SiteConfigurationChangedEvent($siteIdentifier)); + } + + /** + * Renames a site identifier (and moves the folder) + * + * @throws SiteConfigurationWriteException + */ + public function rename(string $currentIdentifier, string $newIdentifier): void + { + if (!rename($this->configPath . '/' . $currentIdentifier, $this->configPath . '/' . $newIdentifier)) { + throw new SiteConfigurationWriteException('Unable to rename folder sites/' . $currentIdentifier, 1522491300); + } + $this->cache->remove($this->cacheIdentifier); + $this->eventDispatcher->dispatch(new SiteConfigurationChangedEvent($newIdentifier)); + } + + /** + * Removes the config.yaml file of a site configuration. + * Also clears the cache. + * + * @throws SiteNotFoundException|SiteConfigurationWriteException + */ + public function delete(string $siteIdentifier): void + { + $fileName = $this->configPath . '/' . $siteIdentifier . '/' . $this->configFileName; + if (!file_exists($fileName)) { + throw new SiteNotFoundException('Site configuration file ' . $this->configFileName . ' within the site ' . $siteIdentifier . ' not found.', 1522866184); + } + if (!unlink($fileName)) { + throw new SiteConfigurationWriteException('Unable to delete folder sites/' . $siteIdentifier, 1596462020); + } + $this->cache->remove($this->cacheIdentifier); + $this->eventDispatcher->dispatch(new SiteConfigurationChangedEvent($siteIdentifier)); + } + + /** + * Detects placeholders that have been introduced and handles* them. + * (*) currently throws an exception, but could be purged or escaped as well + * + * @param array<string, mixed> $existingConfiguration + * @param array<string, mixed> $modifiedConfiguration + * @return array<string, mixed> sanitized configuration (currently not used, exception thrown before) + * @throws SiteConfigurationWriteException + */ + protected function protectPlaceholders(array $existingConfiguration, array $modifiedConfiguration): array + { + try { + return GeneralUtility::makeInstance(YamlPlaceholderGuard::class, $existingConfiguration) + ->process($modifiedConfiguration); + } catch (YamlPlaceholderException $exception) { + throw new SiteConfigurationWriteException($exception->getMessage(), 1670361271, $exception); + } + } + + protected function sortConfiguration(array $newConfiguration): array + { + ksort($newConfiguration); + if (isset($newConfiguration['imports'])) { + $imports = $newConfiguration['imports']; + unset($newConfiguration['imports']); + $newConfiguration['imports'] = $imports; + } + return $newConfiguration; + } + + protected static function findModified(array $currentConfiguration, array $newConfiguration): array + { + $differences = []; + foreach ($newConfiguration as $key => $value) { + if (!isset($currentConfiguration[$key]) || $currentConfiguration[$key] !== $newConfiguration[$key]) { + if (!isset($newConfiguration[$key]) && isset($currentConfiguration[$key])) { + $differences[$key] = '__UNSET'; + } elseif (isset($currentConfiguration[$key]) + && is_array($newConfiguration[$key]) + && is_array($currentConfiguration[$key]) + ) { + $differences[$key] = self::findModified($currentConfiguration[$key], $newConfiguration[$key]); + } else { + $differences[$key] = $value; + } + } + } + return $differences; + } + + protected static function findRemoved(array $currentConfiguration, array $newConfiguration): array + { + $removed = []; + foreach ($currentConfiguration as $key => $value) { + if (!isset($newConfiguration[$key])) { + $removed[$key] = '__UNSET'; + } elseif (isset($currentConfiguration[$key]) && is_array($currentConfiguration[$key]) && is_array($newConfiguration[$key])) { + $removedInRecursion = self::findRemoved($currentConfiguration[$key], $newConfiguration[$key]); + if (!empty($removedInRecursion)) { + $removed[$key] = $removedInRecursion; + } + } + } + + return $removed; + } +} diff --git a/typo3/sysext/core/Classes/Hooks/CreateSiteConfiguration.php b/typo3/sysext/core/Classes/Hooks/CreateSiteConfiguration.php index 882eb61b965b..aaa137d96105 100644 --- a/typo3/sysext/core/Classes/Hooks/CreateSiteConfiguration.php +++ b/typo3/sysext/core/Classes/Hooks/CreateSiteConfiguration.php @@ -19,7 +19,7 @@ namespace TYPO3\CMS\Core\Hooks; use TYPO3\CMS\Core\Authentication\BackendUserAuthentication; use TYPO3\CMS\Core\Configuration\Exception\SiteConfigurationWriteException; -use TYPO3\CMS\Core\Configuration\SiteConfiguration; +use TYPO3\CMS\Core\Configuration\SiteWriter; use TYPO3\CMS\Core\Core\Environment; use TYPO3\CMS\Core\DataHandling\DataHandler; use TYPO3\CMS\Core\Domain\Repository\PageRepository; @@ -87,11 +87,11 @@ class CreateSiteConfiguration $siteIdentifier = $entryPoint . '-' . md5((string)$pageId); if (!$this->siteExistsByRootPageId($pageId)) { - $siteConfiguration = GeneralUtility::makeInstance(SiteConfiguration::class); + $siteWriter = GeneralUtility::makeInstance(SiteWriter::class); $normalizedParams = $this->getNormalizedParams(); $basePrefix = Environment::isCli() ? $normalizedParams->getSitePath() : $normalizedParams->getSiteUrl(); try { - $siteConfiguration->createNewBasicSite( + $siteWriter->createNewBasicSite( $siteIdentifier, $pageId, $basePrefix . $entryPoint diff --git a/typo3/sysext/core/Classes/ServiceProvider.php b/typo3/sysext/core/Classes/ServiceProvider.php index 44cf4125dca3..e24c81bcf790 100644 --- a/typo3/sysext/core/Classes/ServiceProvider.php +++ b/typo3/sysext/core/Classes/ServiceProvider.php @@ -59,7 +59,7 @@ class ServiceProvider extends AbstractServiceProvider Database\DriverMiddlewareService::class => self::getDriverMiddlewaresService(...), Charset\CharsetConverter::class => self::getCharsetConverter(...), Configuration\Loader\YamlFileLoader::class => self::getYamlFileLoader(...), - Configuration\SiteConfiguration::class => self::getSiteConfiguration(...), + Configuration\SiteWriter::class => self::getSiteWriter(...), Command\ListCommand::class => self::getListCommand(...), HelpCommand::class => self::getHelpCommand(...), Command\CacheFlushCommand::class => self::getCacheFlushCommand(...), @@ -184,9 +184,9 @@ class ServiceProvider extends AbstractServiceProvider return self::new($container, Configuration\Loader\YamlFileLoader::class); } - public static function getSiteConfiguration(ContainerInterface $container): Configuration\SiteConfiguration + public static function getSiteWriter(ContainerInterface $container): Configuration\SiteWriter { - return self::new($container, Configuration\SiteConfiguration::class, [ + return self::new($container, Configuration\SiteWriter::class, [ Environment::getConfigPath() . '/sites', $container->get(EventDispatcherInterface::class), $container->get('cache.core'), @@ -269,7 +269,6 @@ class ServiceProvider extends AbstractServiceProvider ); $cacheWarmers = [ - Configuration\SiteConfiguration::class, Http\MiddlewareStackResolver::class, Imaging\IconRegistry::class, Package\PackageManager::class, diff --git a/typo3/sysext/core/Configuration/Services.yaml b/typo3/sysext/core/Configuration/Services.yaml index 138be5a33462..55091672a1bc 100644 --- a/typo3/sysext/core/Configuration/Services.yaml +++ b/typo3/sysext/core/Configuration/Services.yaml @@ -56,11 +56,6 @@ services: TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools: public: true - TYPO3\CMS\Core\Configuration\SiteConfiguration: - arguments: - $cache: '@cache.core' - $configPath: '%env(TYPO3:configPath)%/sites' - TYPO3\CMS\Core\Package\UnitTestPackageManager: autoconfigure: false diff --git a/typo3/sysext/core/Tests/Functional/Routing/Aspect/PersistedAliasMapperTest.php b/typo3/sysext/core/Tests/Functional/Routing/Aspect/PersistedAliasMapperTest.php index bc8f79d14366..568cec75a7da 100644 --- a/typo3/sysext/core/Tests/Functional/Routing/Aspect/PersistedAliasMapperTest.php +++ b/typo3/sysext/core/Tests/Functional/Routing/Aspect/PersistedAliasMapperTest.php @@ -20,7 +20,7 @@ namespace TYPO3\CMS\Core\Tests\Functional\Routing\Aspect; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\Test; use Psr\EventDispatcher\EventDispatcherInterface; -use TYPO3\CMS\Core\Configuration\SiteConfiguration; +use TYPO3\CMS\Core\Configuration\SiteWriter; use TYPO3\CMS\Core\Context\Context; use TYPO3\CMS\Core\Context\DateTimeAspect; use TYPO3\CMS\Core\Context\UserAspect; @@ -309,7 +309,7 @@ final class PersistedAliasMapperTest extends FunctionalTestCase $cache = $this->get('cache.core'); $eventDispatcher = $this->get(EventDispatcherInterface::class); GeneralUtility::rmdir($path . '/' . $site->getIdentifier(), true); - GeneralUtility::makeInstance(SiteConfiguration::class, $path, $eventDispatcher, $cache)->write($site->getIdentifier(), $site->getConfiguration()); + GeneralUtility::makeInstance(SiteWriter::class, $path, $eventDispatcher, $cache)->write($site->getIdentifier(), $site->getConfiguration()); } catch (\Exception $exception) { self::markTestSkipped($exception->getMessage()); } diff --git a/typo3/sysext/core/Tests/Functional/Routing/Aspect/PersistedPatternMapperTest.php b/typo3/sysext/core/Tests/Functional/Routing/Aspect/PersistedPatternMapperTest.php index 66e9169c843d..568f1bb68e32 100644 --- a/typo3/sysext/core/Tests/Functional/Routing/Aspect/PersistedPatternMapperTest.php +++ b/typo3/sysext/core/Tests/Functional/Routing/Aspect/PersistedPatternMapperTest.php @@ -20,7 +20,7 @@ namespace TYPO3\CMS\Core\Tests\Functional\Routing\Aspect; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\Test; use Psr\EventDispatcher\EventDispatcherInterface; -use TYPO3\CMS\Core\Configuration\SiteConfiguration; +use TYPO3\CMS\Core\Configuration\SiteWriter; use TYPO3\CMS\Core\Context\Context; use TYPO3\CMS\Core\Context\DateTimeAspect; use TYPO3\CMS\Core\Context\UserAspect; @@ -317,7 +317,7 @@ final class PersistedPatternMapperTest extends FunctionalTestCase $cache = $this->get('cache.core'); $eventDispatcher = $this->get(EventDispatcherInterface::class); GeneralUtility::rmdir($path . '/' . $site->getIdentifier(), true); - GeneralUtility::makeInstance(SiteConfiguration::class, $path, $eventDispatcher, $cache)->write($site->getIdentifier(), $site->getConfiguration()); + GeneralUtility::makeInstance(SiteWriter::class, $path, $eventDispatcher, $cache)->write($site->getIdentifier(), $site->getConfiguration()); } catch (\Exception $exception) { self::markTestSkipped($exception->getMessage()); } diff --git a/typo3/sysext/core/Tests/Functional/SiteHandling/SiteBasedTestTrait.php b/typo3/sysext/core/Tests/Functional/SiteHandling/SiteBasedTestTrait.php index 0b8c55a1fb0f..81a6ab09e5a3 100644 --- a/typo3/sysext/core/Tests/Functional/SiteHandling/SiteBasedTestTrait.php +++ b/typo3/sysext/core/Tests/Functional/SiteHandling/SiteBasedTestTrait.php @@ -18,6 +18,7 @@ declare(strict_types=1); namespace TYPO3\CMS\Core\Tests\Functional\SiteHandling; use TYPO3\CMS\Core\Configuration\SiteConfiguration; +use TYPO3\CMS\Core\Configuration\SiteWriter; use TYPO3\CMS\Core\Tests\Functional\Fixtures\Frontend\PhpError; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\Internal\AbstractInstruction; @@ -59,11 +60,11 @@ trait SiteBasedTestTrait if (!empty($errorHandling)) { $configuration['errorHandling'] = $errorHandling; } - $siteConfiguration = $this->get(SiteConfiguration::class); + $siteWriter = $this->get(SiteWriter::class); try { // ensure no previous site configuration influences the test GeneralUtility::rmdir($this->instancePath . '/typo3conf/sites/' . $identifier, true); - $siteConfiguration->write($identifier, $configuration); + $siteWriter->write($identifier, $configuration); } catch (\Exception $exception) { $this->markTestSkipped($exception->getMessage()); } @@ -74,10 +75,11 @@ trait SiteBasedTestTrait array $overrides ): void { $siteConfiguration = $this->get(SiteConfiguration::class); + $siteWriter = $this->get(SiteWriter::class); $configuration = $siteConfiguration->load($identifier); $configuration = array_merge($configuration, $overrides); try { - $siteConfiguration->write($identifier, $configuration); + $siteWriter->write($identifier, $configuration); } catch (\Exception $exception) { $this->markTestSkipped($exception->getMessage()); } diff --git a/typo3/sysext/core/Tests/Unit/Configuration/SiteConfigurationTest.php b/typo3/sysext/core/Tests/Unit/Configuration/SiteConfigurationTest.php index eb00bcc3cd0b..c02f46f10b81 100644 --- a/typo3/sysext/core/Tests/Unit/Configuration/SiteConfigurationTest.php +++ b/typo3/sysext/core/Tests/Unit/Configuration/SiteConfigurationTest.php @@ -17,12 +17,9 @@ declare(strict_types=1); namespace TYPO3\CMS\Core\Tests\Unit\Configuration; -use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\Test; use Symfony\Component\Yaml\Yaml; use TYPO3\CMS\Core\Cache\Frontend\NullFrontend; -use TYPO3\CMS\Core\Configuration\Exception\SiteConfigurationWriteException; -use TYPO3\CMS\Core\Configuration\Loader\YamlFileLoader; use TYPO3\CMS\Core\Configuration\SiteConfiguration; use TYPO3\CMS\Core\Core\Environment; use TYPO3\CMS\Core\EventDispatcher\NoopEventDispatcher; @@ -80,110 +77,4 @@ final class SiteConfigurationTest extends UnitTestCase self::assertSame(42, $currentSite->getRootPageId()); self::assertEquals(new Uri('https://example.com'), $currentSite->getBase()); } - - #[Test] - public function writeOnlyWritesModifiedKeys(): void - { - $identifier = 'testsite'; - GeneralUtility::mkdir_deep($this->fixturePath . '/' . $identifier); - $configFixture = __DIR__ . '/Fixtures/SiteConfigs/config1.yaml'; - $expected = __DIR__ . '/Fixtures/SiteConfigs/config1_expected.yaml'; - $siteConfig = $this->fixturePath . '/' . $identifier . '/config.yaml'; - copy($configFixture, $siteConfig); - - // load with resolved imports as the module does - $configuration = GeneralUtility::makeInstance(YamlFileLoader::class) - ->load( - GeneralUtility::fixWindowsFilePath($siteConfig), - YamlFileLoader::PROCESS_IMPORTS - ); - // modify something on base level - $configuration['base'] = 'https://example.net/'; - // modify something nested - $configuration['languages'][0]['title'] = 'English'; - // delete values - unset($configuration['someOtherValue'], $configuration['languages'][1]); - - $this->siteConfiguration->write($identifier, $configuration, true); - - // expect modified base but intact imports - self::assertFileEquals($expected, $siteConfig); - } - - #[Test] - public function writingOfNestedStructuresPreservesOrder(): void - { - $identifier = 'testsite'; - GeneralUtility::mkdir_deep($this->fixturePath . '/' . $identifier); - $configFixture = __DIR__ . '/Fixtures/SiteConfigs/config2.yaml'; - $expected = __DIR__ . '/Fixtures/SiteConfigs/config2_expected.yaml'; - $siteConfig = $this->fixturePath . '/' . $identifier . '/config.yaml'; - copy($configFixture, $siteConfig); - - // load with resolved imports as the module does - $configuration = GeneralUtility::makeInstance(YamlFileLoader::class) - ->load( - GeneralUtility::fixWindowsFilePath($siteConfig), - YamlFileLoader::PROCESS_IMPORTS - ); - // add new language - $languageConfig = [ - 'title' => 'English', - 'enabled' => true, - 'languageId' => '0', - 'base' => '/en', - 'locale' => 'en_US.utf8', - 'flag' => 'en', - 'navigationTitle' => 'English', - ]; - array_unshift($configuration['languages'], $languageConfig); - $this->siteConfiguration->write($identifier, $configuration, true); - - // expect modified base but intact imports - self::assertFileEquals($expected, $siteConfig); - } - - public static function writingPlaceholdersIsHandledDataProvider(): \Generator - { - yield 'unchanged' => [ - ['customProperty' => 'Using %env("existing")% variable'], - false, - ]; - yield 'removed placeholder variable' => [ - ['customProperty' => 'Not using any variable'], - false, - ]; - yield 'changed raw text only' => [ - ['customProperty' => 'Using %env("existing")% variable from system environment'], - false, - ]; - yield 'added new placeholder variable' => [ - ['customProperty' => 'Using %env("existing")% and %env("secret")% variable'], - true, - ]; - } - - #[DataProvider('writingPlaceholdersIsHandledDataProvider')] - #[Test] - public function writingPlaceholdersIsHandled(array $changes, bool $expectedException): void - { - if ($expectedException) { - $this->expectException(SiteConfigurationWriteException::class); - $this->expectExceptionCode(1670361271); - } - - $identifier = 'testsite'; - GeneralUtility::mkdir_deep($this->fixturePath . '/' . $identifier); - $configFixture = __DIR__ . '/Fixtures/SiteConfigs/config2.yaml'; - $siteConfig = $this->fixturePath . '/' . $identifier . '/config.yaml'; - copy($configFixture, $siteConfig); - // load with resolved imports as the module does - $configuration = GeneralUtility::makeInstance(YamlFileLoader::class) - ->load( - GeneralUtility::fixWindowsFilePath($siteConfig), - YamlFileLoader::PROCESS_IMPORTS - ); - $configuration = array_merge($configuration, $changes); - $this->siteConfiguration->write($identifier, $configuration, true); - } } diff --git a/typo3/sysext/core/Tests/Unit/Configuration/SiteWriterTest.php b/typo3/sysext/core/Tests/Unit/Configuration/SiteWriterTest.php new file mode 100644 index 000000000000..df8cae6f2f20 --- /dev/null +++ b/typo3/sysext/core/Tests/Unit/Configuration/SiteWriterTest.php @@ -0,0 +1,164 @@ +<?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\Tests\Unit\Configuration; + +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Test; +use TYPO3\CMS\Core\Cache\Frontend\NullFrontend; +use TYPO3\CMS\Core\Configuration\Exception\SiteConfigurationWriteException; +use TYPO3\CMS\Core\Configuration\Loader\YamlFileLoader; +use TYPO3\CMS\Core\Configuration\SiteWriter; +use TYPO3\CMS\Core\Core\Environment; +use TYPO3\CMS\Core\EventDispatcher\NoopEventDispatcher; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\TestingFramework\Core\Unit\UnitTestCase; + +final class SiteWriterTest extends UnitTestCase +{ + protected bool $resetSingletonInstances = true; + + protected ?SiteWriter $siteWriter; + + /** + * store temporarily used files here + * will be removed after each test + */ + protected ?string $fixturePath; + + protected function setUp(): void + { + parent::setUp(); + $basePath = Environment::getVarPath() . '/tests/unit'; + $this->fixturePath = $basePath . '/fixture/config/sites'; + if (!file_exists($this->fixturePath)) { + GeneralUtility::mkdir_deep($this->fixturePath); + } + $this->testFilesToDelete[] = $basePath; + $this->siteWriter = new SiteWriter( + $this->fixturePath, + new NoopEventDispatcher(), + new NullFrontend('test') + ); + } + + #[Test] + public function writeOnlyWritesModifiedKeys(): void + { + $identifier = 'testsite'; + GeneralUtility::mkdir_deep($this->fixturePath . '/' . $identifier); + $configFixture = __DIR__ . '/Fixtures/SiteConfigs/config1.yaml'; + $expected = __DIR__ . '/Fixtures/SiteConfigs/config1_expected.yaml'; + $siteConfig = $this->fixturePath . '/' . $identifier . '/config.yaml'; + copy($configFixture, $siteConfig); + + // load with resolved imports as the module does + $configuration = GeneralUtility::makeInstance(YamlFileLoader::class) + ->load( + GeneralUtility::fixWindowsFilePath($siteConfig), + YamlFileLoader::PROCESS_IMPORTS + ); + // modify something on base level + $configuration['base'] = 'https://example.net/'; + // modify something nested + $configuration['languages'][0]['title'] = 'English'; + // delete values + unset($configuration['someOtherValue'], $configuration['languages'][1]); + + $this->siteWriter->write($identifier, $configuration, true); + + // expect modified base but intact imports + self::assertFileEquals($expected, $siteConfig); + } + + #[Test] + public function writingOfNestedStructuresPreservesOrder(): void + { + $identifier = 'testsite'; + GeneralUtility::mkdir_deep($this->fixturePath . '/' . $identifier); + $configFixture = __DIR__ . '/Fixtures/SiteConfigs/config2.yaml'; + $expected = __DIR__ . '/Fixtures/SiteConfigs/config2_expected.yaml'; + $siteConfig = $this->fixturePath . '/' . $identifier . '/config.yaml'; + copy($configFixture, $siteConfig); + + // load with resolved imports as the module does + $configuration = GeneralUtility::makeInstance(YamlFileLoader::class) + ->load( + GeneralUtility::fixWindowsFilePath($siteConfig), + YamlFileLoader::PROCESS_IMPORTS + ); + // add new language + $languageConfig = [ + 'title' => 'English', + 'enabled' => true, + 'languageId' => '0', + 'base' => '/en', + 'locale' => 'en_US.utf8', + 'flag' => 'en', + 'navigationTitle' => 'English', + ]; + array_unshift($configuration['languages'], $languageConfig); + $this->siteWriter->write($identifier, $configuration, true); + + // expect modified base but intact imports + self::assertFileEquals($expected, $siteConfig); + } + + public static function writingPlaceholdersIsHandledDataProvider(): \Generator + { + yield 'unchanged' => [ + ['customProperty' => 'Using %env("existing")% variable'], + false, + ]; + yield 'removed placeholder variable' => [ + ['customProperty' => 'Not using any variable'], + false, + ]; + yield 'changed raw text only' => [ + ['customProperty' => 'Using %env("existing")% variable from system environment'], + false, + ]; + yield 'added new placeholder variable' => [ + ['customProperty' => 'Using %env("existing")% and %env("secret")% variable'], + true, + ]; + } + + #[DataProvider('writingPlaceholdersIsHandledDataProvider')] + #[Test] + public function writingPlaceholdersIsHandled(array $changes, bool $expectedException): void + { + if ($expectedException) { + $this->expectException(SiteConfigurationWriteException::class); + $this->expectExceptionCode(1670361271); + } + + $identifier = 'testsite'; + GeneralUtility::mkdir_deep($this->fixturePath . '/' . $identifier); + $configFixture = __DIR__ . '/Fixtures/SiteConfigs/config2.yaml'; + $siteConfig = $this->fixturePath . '/' . $identifier . '/config.yaml'; + copy($configFixture, $siteConfig); + // load with resolved imports as the module does + $configuration = GeneralUtility::makeInstance(YamlFileLoader::class) + ->load( + GeneralUtility::fixWindowsFilePath($siteConfig), + YamlFileLoader::PROCESS_IMPORTS + ); + $configuration = array_merge($configuration, $changes); + $this->siteWriter->write($identifier, $configuration, true); + } +} diff --git a/typo3/sysext/impexp/Classes/Initialization/ImportSiteConfigurationsOnPackageInitialization.php b/typo3/sysext/impexp/Classes/Initialization/ImportSiteConfigurationsOnPackageInitialization.php index 73854190c009..50142bbcec27 100644 --- a/typo3/sysext/impexp/Classes/Initialization/ImportSiteConfigurationsOnPackageInitialization.php +++ b/typo3/sysext/impexp/Classes/Initialization/ImportSiteConfigurationsOnPackageInitialization.php @@ -23,6 +23,7 @@ use Symfony\Component\Finder\Finder; use TYPO3\CMS\Core\Attribute\AsEventListener; use TYPO3\CMS\Core\Configuration\Exception\SiteConfigurationWriteException; use TYPO3\CMS\Core\Configuration\SiteConfiguration; +use TYPO3\CMS\Core\Configuration\SiteWriter; use TYPO3\CMS\Core\Core\Environment; use TYPO3\CMS\Core\Package\Event\PackageInitializationEvent; use TYPO3\CMS\Core\Registry; @@ -39,6 +40,7 @@ final class ImportSiteConfigurationsOnPackageInitialization implements LoggerAwa public function __construct( private readonly Registry $registry, private readonly SiteConfiguration $siteConfiguration, + private readonly SiteWriter $siteWriter, ) {} #[AsEventListener(after: ImportContentOnPackageInitialization::class)] @@ -98,7 +100,7 @@ final class ImportSiteConfigurationsOnPackageInitialization implements LoggerAwa $configuration = $this->siteConfiguration->load($siteIdentifier); $configuration['rootPageId'] = $importedPageId; try { - $this->siteConfiguration->write($siteIdentifier, $configuration); + $this->siteWriter->write($siteIdentifier, $configuration); } catch (SiteConfigurationWriteException $e) { $this->logger->warning( sprintf( diff --git a/typo3/sysext/install/Classes/Service/SetupService.php b/typo3/sysext/install/Classes/Service/SetupService.php index 72d64aacf70b..40577f466d46 100644 --- a/typo3/sysext/install/Classes/Service/SetupService.php +++ b/typo3/sysext/install/Classes/Service/SetupService.php @@ -20,7 +20,7 @@ namespace TYPO3\CMS\Install\Service; use TYPO3\CMS\Core\Configuration\ConfigurationManager; use TYPO3\CMS\Core\Configuration\Exception\SiteConfigurationWriteException; use TYPO3\CMS\Core\Configuration\Loader\YamlFileLoader; -use TYPO3\CMS\Core\Configuration\SiteConfiguration; +use TYPO3\CMS\Core\Configuration\SiteWriter; use TYPO3\CMS\Core\Crypto\PasswordHashing\Argon2idPasswordHash; use TYPO3\CMS\Core\Crypto\PasswordHashing\Argon2iPasswordHash; use TYPO3\CMS\Core\Crypto\PasswordHashing\BcryptPasswordHash; @@ -42,7 +42,7 @@ readonly class SetupService { public function __construct( private ConfigurationManager $configurationManager, - private SiteConfiguration $siteConfiguration, + private SiteWriter $siteWriter, private YamlFileLoader $yamlFileLoader, ) {} @@ -58,7 +58,7 @@ readonly class SetupService public function createSiteConfiguration(string $identifier, int $rootPageId, string $siteUrl): void { // Create a default site configuration called "main" as best practice - $this->siteConfiguration->createNewBasicSite($identifier, $rootPageId, $siteUrl); + $this->siteWriter->createNewBasicSite($identifier, $rootPageId, $siteUrl); } /** diff --git a/typo3/sysext/install/Classes/ServiceProvider.php b/typo3/sysext/install/Classes/ServiceProvider.php index 13e7f2783f1a..45acdc612da6 100644 --- a/typo3/sysext/install/Classes/ServiceProvider.php +++ b/typo3/sysext/install/Classes/ServiceProvider.php @@ -21,7 +21,7 @@ use Psr\Container\ContainerInterface; use Psr\EventDispatcher\EventDispatcherInterface; use TYPO3\CMS\Core\Configuration\ConfigurationManager; use TYPO3\CMS\Core\Configuration\Loader\YamlFileLoader; -use TYPO3\CMS\Core\Configuration\SiteConfiguration; +use TYPO3\CMS\Core\Configuration\SiteWriter; use TYPO3\CMS\Core\Console\CommandRegistry; use TYPO3\CMS\Core\Context\Context; use TYPO3\CMS\Core\Crypto\HashService; @@ -236,7 +236,7 @@ class ServiceProvider extends AbstractServiceProvider { return new Service\SetupService( $container->get(ConfigurationManager::class), - $container->get(SiteConfiguration::class), + $container->get(SiteWriter::class), $container->get(YamlFileLoader::class) ); } diff --git a/typo3/sysext/install/Classes/Updates/MigrateSiteSettingsConfigUpdate.php b/typo3/sysext/install/Classes/Updates/MigrateSiteSettingsConfigUpdate.php index 861386c787e8..f0162e605b66 100644 --- a/typo3/sysext/install/Classes/Updates/MigrateSiteSettingsConfigUpdate.php +++ b/typo3/sysext/install/Classes/Updates/MigrateSiteSettingsConfigUpdate.php @@ -19,6 +19,7 @@ namespace TYPO3\CMS\Install\Updates; use TYPO3\CMS\Core\Configuration\Exception\SiteConfigurationWriteException; use TYPO3\CMS\Core\Configuration\SiteConfiguration; +use TYPO3\CMS\Core\Configuration\SiteWriter; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Install\Attribute\UpgradeWizard; @@ -34,11 +35,13 @@ class MigrateSiteSettingsConfigUpdate implements UpgradeWizardInterface protected const SETTINGS_FILENAME = 'settings.yaml'; protected ?SiteConfiguration $siteConfiguration = null; + protected ?SiteWriter $siteWriter = null; protected array $sitePathsToMigrate = []; public function __construct() { $this->siteConfiguration = GeneralUtility::makeInstance(SiteConfiguration::class); + $this->siteWriter = GeneralUtility::makeInstance(SiteWriter::class); $this->sitePathsToMigrate = $this->getSitePathsToMigrate(); } @@ -58,7 +61,7 @@ class MigrateSiteSettingsConfigUpdate implements UpgradeWizardInterface { try { foreach ($this->sitePathsToMigrate as $siteIdentifier => $settings) { - $this->siteConfiguration->writeSettings($siteIdentifier, $settings); + $this->siteWriter->writeSettings($siteIdentifier, $settings); } } catch (SiteConfigurationWriteException $e) { return false; diff --git a/typo3/sysext/install/Tests/Functional/Updates/MigrateSiteSettingsConfigUpdateTest.php b/typo3/sysext/install/Tests/Functional/Updates/MigrateSiteSettingsConfigUpdateTest.php index 76e4ddba4b28..96fb9cec1cbf 100644 --- a/typo3/sysext/install/Tests/Functional/Updates/MigrateSiteSettingsConfigUpdateTest.php +++ b/typo3/sysext/install/Tests/Functional/Updates/MigrateSiteSettingsConfigUpdateTest.php @@ -18,9 +18,8 @@ declare(strict_types=1); namespace TYPO3\CMS\Install\Tests\Functional\Updates; use PHPUnit\Framework\Attributes\Test; -use TYPO3\CMS\Core\Configuration\SiteConfiguration; +use TYPO3\CMS\Core\Configuration\SiteWriter; use TYPO3\CMS\Core\Core\Environment; -use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Install\Updates\MigrateSiteSettingsConfigUpdate; use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase; @@ -31,7 +30,7 @@ final class MigrateSiteSettingsConfigUpdateTest extends FunctionalTestCase { $siteconfigurationIdentifier = 'settings'; - GeneralUtility::makeInstance(SiteConfiguration::class)->write( + $this->get(SiteWriter::class)->write( $siteconfigurationIdentifier, [ 'rootPageId' => 1, @@ -66,7 +65,7 @@ final class MigrateSiteSettingsConfigUpdateTest extends FunctionalTestCase { $siteconfigurationIdentifier = 'withoutSettings'; - GeneralUtility::makeInstance(SiteConfiguration::class)->write( + $this->get(SiteWriter::class)->write( $siteconfigurationIdentifier, [ 'rootPageId' => 2, diff --git a/typo3/sysext/redirects/Tests/Functional/EventListener/AddPageTypeZeroSourceTest.php b/typo3/sysext/redirects/Tests/Functional/EventListener/AddPageTypeZeroSourceTest.php index c6add9614f44..80092641180e 100644 --- a/typo3/sysext/redirects/Tests/Functional/EventListener/AddPageTypeZeroSourceTest.php +++ b/typo3/sysext/redirects/Tests/Functional/EventListener/AddPageTypeZeroSourceTest.php @@ -20,7 +20,7 @@ namespace TYPO3\CMS\Redirects\Tests\Functional\EventListener; use PHPUnit\Framework\Attributes\Test; use Psr\EventDispatcher\EventDispatcherInterface; use Symfony\Component\DependencyInjection\Container; -use TYPO3\CMS\Core\Configuration\SiteConfiguration; +use TYPO3\CMS\Core\Configuration\SiteWriter; use TYPO3\CMS\Core\Context\Context; use TYPO3\CMS\Core\EventDispatcher\ListenerProvider; use TYPO3\CMS\Core\Routing\InvalidRouteArgumentsException; @@ -250,7 +250,7 @@ final class AddPageTypeZeroSourceTest extends FunctionalTestCase protected function buildSite(array $configuration): void { - $siteConfiguration = GeneralUtility::makeInstance(SiteConfiguration::class); - $siteConfiguration->write('testing', $configuration); + $siteWriter = $this->get(SiteWriter::class); + $siteWriter->write('testing', $configuration); } } diff --git a/typo3/sysext/redirects/Tests/Functional/EventListener/AddPlainSlugReplacementSourceTest.php b/typo3/sysext/redirects/Tests/Functional/EventListener/AddPlainSlugReplacementSourceTest.php index 090b4ae1b18a..a56bcffc591d 100644 --- a/typo3/sysext/redirects/Tests/Functional/EventListener/AddPlainSlugReplacementSourceTest.php +++ b/typo3/sysext/redirects/Tests/Functional/EventListener/AddPlainSlugReplacementSourceTest.php @@ -20,7 +20,7 @@ namespace TYPO3\CMS\Redirects\Tests\Functional\EventListener; use PHPUnit\Framework\Attributes\Test; use Psr\EventDispatcher\EventDispatcherInterface; use Symfony\Component\DependencyInjection\Container; -use TYPO3\CMS\Core\Configuration\SiteConfiguration; +use TYPO3\CMS\Core\Configuration\SiteWriter; use TYPO3\CMS\Core\Site\SiteFinder; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Redirects\Event\SlugRedirectChangeItemCreatedEvent; @@ -80,7 +80,7 @@ final class AddPlainSlugReplacementSourceTest extends FunctionalTestCase 'base' => '/', 'settings' => $settings, ]; - $siteConfiguration = GeneralUtility::makeInstance(SiteConfiguration::class); - $siteConfiguration->write('testing', $configuration); + $siteWriter = $this->get(SiteWriter::class); + $siteWriter->write('testing', $configuration); } } diff --git a/typo3/sysext/redirects/Tests/Functional/RedirectUpdate/SlugRedirectChangeItemFactoryTest.php b/typo3/sysext/redirects/Tests/Functional/RedirectUpdate/SlugRedirectChangeItemFactoryTest.php index e66183d1e3dc..084d5674a4b6 100644 --- a/typo3/sysext/redirects/Tests/Functional/RedirectUpdate/SlugRedirectChangeItemFactoryTest.php +++ b/typo3/sysext/redirects/Tests/Functional/RedirectUpdate/SlugRedirectChangeItemFactoryTest.php @@ -19,9 +19,8 @@ namespace TYPO3\CMS\Redirects\Tests\Functional\RedirectUpdate; use PHPUnit\Framework\Attributes\Test; use Symfony\Component\DependencyInjection\Container; -use TYPO3\CMS\Core\Configuration\SiteConfiguration; +use TYPO3\CMS\Core\Configuration\SiteWriter; use TYPO3\CMS\Core\EventDispatcher\ListenerProvider; -use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Redirects\Event\SlugRedirectChangeItemCreatedEvent; use TYPO3\CMS\Redirects\RedirectUpdate\SlugRedirectChangeItem; use TYPO3\CMS\Redirects\RedirectUpdate\SlugRedirectChangeItemFactory; @@ -138,7 +137,7 @@ final class SlugRedirectChangeItemFactoryTest extends FunctionalTestCase 'base' => '/', 'settings' => $settings, ]; - $siteConfiguration = GeneralUtility::makeInstance(SiteConfiguration::class); - $siteConfiguration->write('testing', $configuration); + $siteWriter = $this->get(SiteWriter::class); + $siteWriter->write('testing', $configuration); } } diff --git a/typo3/sysext/redirects/Tests/Functional/Service/SlugServiceTest.php b/typo3/sysext/redirects/Tests/Functional/Service/SlugServiceTest.php index 160cfb46c0a3..38b8947cc511 100644 --- a/typo3/sysext/redirects/Tests/Functional/Service/SlugServiceTest.php +++ b/typo3/sysext/redirects/Tests/Functional/Service/SlugServiceTest.php @@ -21,7 +21,7 @@ use PHPUnit\Framework\Attributes\Test; use Psr\EventDispatcher\EventDispatcherInterface; use Psr\Log\NullLogger; use Symfony\Component\DependencyInjection\Container; -use TYPO3\CMS\Core\Configuration\SiteConfiguration; +use TYPO3\CMS\Core\Configuration\SiteWriter; use TYPO3\CMS\Core\Context\Context; use TYPO3\CMS\Core\DataHandling\Model\CorrelationId; use TYPO3\CMS\Core\Domain\Repository\PageRepository; @@ -563,8 +563,8 @@ final class SlugServiceTest extends FunctionalTestCase 'rootPageId' => 1, 'base' => '/', ]; - $siteConfiguration = $this->get(SiteConfiguration::class); - $siteConfiguration->write('testing', $configuration); + $siteWriter = $this->get(SiteWriter::class); + $siteWriter->write('testing', $configuration); } protected function buildBaseSiteInSubfolder(): void @@ -573,8 +573,8 @@ final class SlugServiceTest extends FunctionalTestCase 'rootPageId' => 1, 'base' => '/sub-folder', ]; - $siteConfiguration = $this->get(SiteConfiguration::class); - $siteConfiguration->write('testing', $configuration); + $siteWriter = $this->get(SiteWriter::class); + $siteWriter->write('testing', $configuration); } protected function buildBaseSiteWithLanguages(): void @@ -584,8 +584,8 @@ final class SlugServiceTest extends FunctionalTestCase 'base' => '/', 'languages' => $this->languages, ]; - $siteConfiguration = $this->get(SiteConfiguration::class); - $siteConfiguration->write('testing', $configuration); + $siteWriter = $this->get(SiteWriter::class); + $siteWriter->write('testing', $configuration); } protected function buildBaseSiteWithLanguagesInSubFolder(): void @@ -603,8 +603,8 @@ final class SlugServiceTest extends FunctionalTestCase 'base' => '/sub-folder', 'languages' => $languages, ]; - $siteConfiguration = $this->get(SiteConfiguration::class); - $siteConfiguration->write('testing', $configuration); + $siteWriter = $this->get(SiteWriter::class); + $siteWriter->write('testing', $configuration); } protected function createSubject(): void diff --git a/typo3/sysext/styleguide/Classes/TcaDataGenerator/AbstractGenerator.php b/typo3/sysext/styleguide/Classes/TcaDataGenerator/AbstractGenerator.php index d31fce1ead7d..d4f118a87c74 100644 --- a/typo3/sysext/styleguide/Classes/TcaDataGenerator/AbstractGenerator.php +++ b/typo3/sysext/styleguide/Classes/TcaDataGenerator/AbstractGenerator.php @@ -18,7 +18,7 @@ declare(strict_types=1); namespace TYPO3\CMS\Styleguide\TcaDataGenerator; use TYPO3\CMS\Backend\Utility\BackendUtility; -use TYPO3\CMS\Core\Configuration\SiteConfiguration; +use TYPO3\CMS\Core\Configuration\SiteWriter; use TYPO3\CMS\Core\Core\Environment; use TYPO3\CMS\Core\Database\Connection; use TYPO3\CMS\Core\Database\ConnectionPool; @@ -46,9 +46,9 @@ abstract class AbstractGenerator $recordFinder = GeneralUtility::makeInstance(RecordFinder::class); // When the DataHandler created the page tree, a default site configuration has been added. Fetch, rename, update. $site = GeneralUtility::makeInstance(SiteFinder::class)->getSiteByRootPageId($topPageUid); - $siteConfiguration = GeneralUtility::makeInstance(SiteConfiguration::class); + $siteWriter = GeneralUtility::makeInstance(SiteWriter::class); $siteIdentifier = 'styleguide-demo-' . $topPageUid; - $siteConfiguration->rename($site->getIdentifier(), $siteIdentifier); + $siteWriter->rename($site->getIdentifier(), $siteIdentifier); $highestLanguageId = $recordFinder->findHighestLanguageId(); $configuration = [ 'base' => $base . 'styleguide-demo-' . $topPageUid, @@ -138,7 +138,7 @@ abstract class AbstractGenerator ], ], ]; - $siteConfiguration->write($siteIdentifier, $configuration); + $siteWriter->write($siteIdentifier, $configuration); } /** diff --git a/typo3/sysext/styleguide/Classes/TcaDataGenerator/Generator.php b/typo3/sysext/styleguide/Classes/TcaDataGenerator/Generator.php index fb9c514bff25..e7c7cb46efeb 100644 --- a/typo3/sysext/styleguide/Classes/TcaDataGenerator/Generator.php +++ b/typo3/sysext/styleguide/Classes/TcaDataGenerator/Generator.php @@ -17,7 +17,7 @@ declare(strict_types=1); namespace TYPO3\CMS\Styleguide\TcaDataGenerator; -use TYPO3\CMS\Core\Configuration\SiteConfiguration; +use TYPO3\CMS\Core\Configuration\SiteWriter; use TYPO3\CMS\Core\Crypto\PasswordHashing\PasswordHashFactory; use TYPO3\CMS\Core\Crypto\Random; use TYPO3\CMS\Core\Database\ConnectionPool; @@ -200,7 +200,7 @@ final class Generator extends AbstractGenerator // Delete site configuration if ($topUids[0] ?? false) { $site = GeneralUtility::makeInstance(SiteFinder::class)->getSiteByRootPageId($topUids[0]); - GeneralUtility::makeInstance(SiteConfiguration::class)->delete($site->getIdentifier()); + GeneralUtility::makeInstance(SiteWriter::class)->delete($site->getIdentifier()); } } diff --git a/typo3/sysext/styleguide/Classes/TcaDataGenerator/GeneratorFrontend.php b/typo3/sysext/styleguide/Classes/TcaDataGenerator/GeneratorFrontend.php index 38fd0947114b..b6196d4d8bf6 100644 --- a/typo3/sysext/styleguide/Classes/TcaDataGenerator/GeneratorFrontend.php +++ b/typo3/sysext/styleguide/Classes/TcaDataGenerator/GeneratorFrontend.php @@ -17,7 +17,7 @@ declare(strict_types=1); namespace TYPO3\CMS\Styleguide\TcaDataGenerator; -use TYPO3\CMS\Core\Configuration\SiteConfiguration; +use TYPO3\CMS\Core\Configuration\SiteWriter; use TYPO3\CMS\Core\Exception\SiteNotFoundException; use TYPO3\CMS\Core\Site\SiteFinder; use TYPO3\CMS\Core\Utility\GeneralUtility; @@ -204,7 +204,7 @@ final class GeneratorFrontend extends AbstractGenerator if (!empty($rootUid)) { $site = GeneralUtility::makeInstance(SiteFinder::class)->getSiteByRootPageId((int)$rootUid[0]); $identifier = $site->getIdentifier(); - GeneralUtility::makeInstance(SiteConfiguration::class)->delete($identifier); + GeneralUtility::makeInstance(SiteWriter::class)->delete($identifier); } } catch (SiteNotFoundException $e) { // Do not throw a thing if site config does not exist -- GitLab