diff --git a/typo3/sysext/core/Classes/Configuration/SiteConfiguration.php b/typo3/sysext/core/Classes/Configuration/SiteConfiguration.php index 37a3e05250c5f0ab59c5fa4a9c5e6153a696cc34..f27cc7f3315a67be667e084790584f41463f91d3 100644 --- a/typo3/sysext/core/Classes/Configuration/SiteConfiguration.php +++ b/typo3/sysext/core/Classes/Configuration/SiteConfiguration.php @@ -57,6 +57,13 @@ class SiteConfiguration implements SingletonInterface */ protected $configFileName = 'config.yaml'; + /** + * YAML file name with all settings. + * + * @internal + */ + protected string $settingsFileName = 'settings.yaml'; + /** * Identifier to store all configuration data in cache_core cache. * @@ -148,6 +155,7 @@ class SiteConfiguration implements SingletonInterface $sites = []; $siteConfiguration = $this->getAllSiteConfigurationFromFiles($useCache); foreach ($siteConfiguration as $identifier => $configuration) { + $configuration['settings'] = $this->getSiteSettings($identifier, $configuration); $rootPageId = (int)($configuration['rootPageId'] ?? 0); if ($rootPageId > 0) { $sites[$identifier] = GeneralUtility::makeInstance(Site::class, $identifier, $rootPageId, $configuration); @@ -157,6 +165,28 @@ class SiteConfiguration implements SingletonInterface 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($this->configFileName)->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. * @@ -208,6 +238,25 @@ class SiteConfiguration implements SingletonInterface return $loader->load(GeneralUtility::fixWindowsFilePath($fileName), YamlFileLoader::PROCESS_IMPORTS); } + protected function getSiteSettings(string $siteIdentifier, array $siteConfiguration): array + { + $fileName = $this->configPath . '/' . $siteIdentifier . '/' . $this->settingsFileName; + if (file_exists($fileName)) { + $loader = GeneralUtility::makeInstance(YamlFileLoader::class); + return $loader->load(GeneralUtility::fixWindowsFilePath($fileName), YamlFileLoader::PROCESS_IMPORTS); + } + return $siteConfiguration['settings'] ?? []; + } + + 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 * diff --git a/typo3/sysext/core/Documentation/Changelog/12.1/Feature-99047-LoadSiteSettingsFromSeparateSettingsyaml.rst b/typo3/sysext/core/Documentation/Changelog/12.1/Feature-99047-LoadSiteSettingsFromSeparateSettingsyaml.rst new file mode 100644 index 0000000000000000000000000000000000000000..b8f72864454e31233dce67ba6419c6ce89623698 --- /dev/null +++ b/typo3/sysext/core/Documentation/Changelog/12.1/Feature-99047-LoadSiteSettingsFromSeparateSettingsyaml.rst @@ -0,0 +1,38 @@ +.. include:: /Includes.rst.txt + +.. _feature-99047-1668081474: + +================================================================ +Feature: #99047 - Load site settings from separate settings.yaml +================================================================ + +See :issue:`99047` + +Description +=========== + +Site Settings have been introduced with TYPO3 v10 as part of the configuration +of a site. In contrast to the site configuration, they are mostly used to +provide sane defaults for TypoScript constants and have a layer of arbitrary +configuration available in any context. + +In order to separate these settings from the system site configuration, make them +accessible and editable in the TYPO3 Backend, and to distinguish between required +site configuration and optional settings, the "settings" part of the settings are +moved to a separate "settings.yaml" file in the site configuration folder. + +A migration wizard is provided as upgrade wizard to migrate settings into the +new file. + + +Impact +====== + +Settings are now loaded from a separate file called "settings.yaml" residing +next to the :file:`config.yaml` of a site. +Executing the upgrade wizard will load all settings of a site and create that +file for the user. The migration wizard will not remove / rewrite the +:file:`config.yaml` - the user should do that on their own, to avoid breaking +custom-built functionality. + +.. index:: Backend, YAML, ext:core diff --git a/typo3/sysext/install/Classes/Updates/MigrateSiteSettingsConfigUpdate.php b/typo3/sysext/install/Classes/Updates/MigrateSiteSettingsConfigUpdate.php new file mode 100644 index 0000000000000000000000000000000000000000..cbe7b59fc9f58aee1453c0cf2d355452f1fed4a5 --- /dev/null +++ b/typo3/sysext/install/Classes/Updates/MigrateSiteSettingsConfigUpdate.php @@ -0,0 +1,106 @@ +<?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\Install\Updates; + +use TYPO3\CMS\Core\Configuration\Exception\SiteConfigurationWriteException; +use TYPO3\CMS\Core\Configuration\SiteConfiguration; +use TYPO3\CMS\Core\Utility\GeneralUtility; + +/** + * @internal + * + * The upgrade wizard cuts the settings part of the config.yaml and moves it into settings.yaml. + */ +class MigrateSiteSettingsConfigUpdate implements UpgradeWizardInterface +{ + protected const SETTINGS_FILENAME = 'settings.yaml'; + + protected ?SiteConfiguration $siteConfiguration = null; + protected array $sitePathsToMigrate = []; + + public function __construct() + { + $this->siteConfiguration = GeneralUtility::makeInstance(SiteConfiguration::class); + $this->sitePathsToMigrate = $this->getSitePathsToMigrate(); + } + + public function getIdentifier(): string + { + return 'migrateSiteSettings'; + } + + public function getTitle(): string + { + return 'Migrate site settings to separate file'; + } + + public function getDescription(): string + { + return + 'If site settings exist in a config.yaml file, this wizard migrates them to a dedicated settings.yaml file. ' . + 'Please note that you should remove them from your existing config manually.'; + } + + public function executeUpdate(): bool + { + try { + foreach ($this->sitePathsToMigrate as $siteIdentifier => $settings) { + $this->siteConfiguration->writeSettings($siteIdentifier, $settings); + } + } catch (SiteConfigurationWriteException $e) { + return false; + } + return true; + } + + /** + * if the settings file does not exist an update is considered as necessary + * + * @return bool + */ + public function updateNecessary(): bool + { + return $this->sitePathsToMigrate !== []; + } + + public function getPrerequisites(): array + { + return []; + } + + /** + * returns an array of siteconfigs, if they don't have a corresponding settings file + */ + protected function getSitePathsToMigrate(): array + { + $settingsCollection = []; + foreach ($this->siteConfiguration->getAllSiteConfigurationPaths() as $siteIdentifier => $configurationPath) { + // settings.yaml already exists, skip + if (file_exists($configurationPath . '/' . self::SETTINGS_FILENAME)) { + continue; + } + // Check if the site has any settings + $siteConfiguration = $this->siteConfiguration->load($siteIdentifier); + if (!isset($siteConfiguration['settings'])) { + continue; + } + $settingsCollection[$siteIdentifier] = $siteConfiguration['settings']; + } + return $settingsCollection; + } +} diff --git a/typo3/sysext/install/Tests/Functional/Updates/MigrateSiteSettingsConfigUpdateTest.php b/typo3/sysext/install/Tests/Functional/Updates/MigrateSiteSettingsConfigUpdateTest.php new file mode 100644 index 0000000000000000000000000000000000000000..2151ea094f7cc6d64c16b7d24c2ad7763c4d25aa --- /dev/null +++ b/typo3/sysext/install/Tests/Functional/Updates/MigrateSiteSettingsConfigUpdateTest.php @@ -0,0 +1,110 @@ +<?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\Install\Tests\Functional\Updates; + +use TYPO3\CMS\Core\Configuration\SiteConfiguration; +use TYPO3\CMS\Core\Core\Environment; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Install\Updates\MigrateSiteSettingsConfigUpdate; +use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase; + +class MigrateSiteSettingsConfigUpdateTest extends FunctionalTestCase +{ + /** + * @test + */ + public function upgradeSettingsUpdateWithSettings(): void + { + $siteconfigurationIdentifier = 'settings'; + + GeneralUtility::makeInstance(SiteConfiguration::class)->write( + $siteconfigurationIdentifier, + [ + 'rootPageId' => 1, + 'base' => 'www.test.org', + 'languages' => [ + 0 => [ + 'title' => 'English', + 'enabled' => true, + 'languageId' => 0, + 'base' => '/', + 'typo3Language' => 'default', + 'locale' => 'en_US.UTF-8', + 'iso-639-1' => 'en', + 'navigationTitle' => 'English', + 'hreflang' => 'en-us', + 'direction' => 'ltr', + 'flag' => 'us', + ], + ], + 'settings' => [ + 'debug' => 1, + 'test' => true, + ], + 'errorHandling' => [], + 'routes' => [], + ] + ); + + $subject = new MigrateSiteSettingsConfigUpdate(); + $subject->executeUpdate(); + self::assertFileExists($this->getSettingsFilePath($siteconfigurationIdentifier)); + } + + /** + * @test + */ + public function upgradeSettingsUpdateWithoutSettings(): void + { + $siteconfigurationIdentifier = 'withoutSettings'; + + GeneralUtility::makeInstance(SiteConfiguration::class)->write( + $siteconfigurationIdentifier, + [ + 'rootPageId' => 2, + 'base' => 'www.testTwo.org', + 'languages' => [ + 0 => [ + 'title' => 'English', + 'enabled' => true, + 'languageId' => 0, + 'base' => '/', + 'typo3Language' => 'default', + 'locale' => 'en_US.UTF-8', + 'iso-639-1' => 'en', + 'navigationTitle' => 'English', + 'hreflang' => 'en-us', + 'direction' => 'ltr', + 'flag' => 'us', + ], + ], + 'errorHandling' => [], + 'routes' => [], + ] + ); + + $subject = new MigrateSiteSettingsConfigUpdate(); + $subject->executeUpdate(); + self::assertFileDoesNotExist($this->getSettingsFilePath($siteconfigurationIdentifier)); + } + + protected function getSettingsFilePath(string $identifier): string + { + return Environment::getConfigPath() . '/sites/' . $identifier . '/settings.yaml'; + } +} diff --git a/typo3/sysext/install/ext_localconf.php b/typo3/sysext/install/ext_localconf.php index 7109819eb4974103f2165667f6abe462cf69a511..780249d30cf0e274fab6ac5ce6dc5ba0c86063fd 100644 --- a/typo3/sysext/install/ext_localconf.php +++ b/typo3/sysext/install/ext_localconf.php @@ -8,6 +8,7 @@ use TYPO3\CMS\Install\Updates\BackendUserLanguageMigration; use TYPO3\CMS\Install\Updates\CollectionsExtractionUpdate; use TYPO3\CMS\Install\Updates\DatabaseRowsUpdateWizard; use TYPO3\CMS\Install\Updates\FeLoginModeExtractionUpdate; +use TYPO3\CMS\Install\Updates\MigrateSiteSettingsConfigUpdate; use TYPO3\CMS\Install\Updates\ShortcutRecordsMigration; use TYPO3\CMS\Install\Updates\SvgFilesSanitization; use TYPO3\CMS\Install\Updates\SysFileMountIdentifierMigration; @@ -34,3 +35,4 @@ $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update']['backendGroup $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update']['sysFileMountIdentifierMigration'] = SysFileMountIdentifierMigration::class; $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update']['backendModulePermission'] = BackendModulePermissionMigration::class; $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update']['sysTemplateNoWorkspaceMigration'] = SysTemplateNoWorkspaceMigration::class; +$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update']['migrateSiteSettings'] = MigrateSiteSettingsConfigUpdate::class;