get() is official API and other public methods are low level * core internal API that is usually only used by extension manager and install tool. */ #[AsAlias('extension-configuration', public: true)] readonly class ExtensionConfiguration { /** * Get a single configuration value, a sub array or the whole configuration. * * Examples: * // Simple and typical usage: Get a single config value, or an array if the key is a "TypoScript" * // a-like sub-path in ext_conf_template.txt "foo.bar = defaultValue" * ->get('myExtension', 'aConfigKey'); * * // Get all current configuration values, always an array * ->get('myExtension'); * * // Get a nested config value if the path is a "TypoScript" a-like sub-path * // in ext_conf_template.txt "topLevelKey.subLevelKey = defaultValue" * ->get('myExtension', 'topLevelKey/subLevelKey') * * Notes: * - If a configuration or configuration path of an extension is not found, the * code tries to synchronize configuration with ext_conf_template.txt first, only * if still not found, it will throw exceptions. * - Return values are NOT type safe: A boolean false could be returned as string 0. * Cast accordingly. * - This API throws exceptions if the path does not exist or the extension * configuration is not available. The install tool takes care any new * ext_conf_template.txt values are available TYPO3_CONF_VARS['EXTENSIONS'], * a thrown exception indicates a programming error on developer side * and should not be caught. * - It is not checked if the extension in question is loaded at all, * it's just checked the extension configuration path exists. * - Extensions should typically not get configuration of a different extension. * * @param string $extension Extension name * @param string $path Configuration path - e.g. "featureCategory/coolThingIsEnabled" * @return mixed The value. Can be a sub array or a single value. * @throws ExtensionConfigurationExtensionNotConfiguredException If the extension configuration does not exist * @throws ExtensionConfigurationPathDoesNotExistException If a requested path in the extension configuration does not exist */ public function get(string $extension, string $path = ''): mixed { $hasBeenSynchronized = false; if (!$this->hasConfiguration($extension)) { // This if() should not be hit at "casual" runtime, but only in early setup phases $this->synchronizeExtConfTemplateWithLocalConfigurationOfAllExtensions(true); $hasBeenSynchronized = true; if (!$this->hasConfiguration($extension)) { // If there is still no such entry, even after sync -> throw throw new ExtensionConfigurationExtensionNotConfiguredException( 'No extension configuration for extension ' . $extension . ' found. Either this extension' . ' has no extension configuration or the configuration is not up to date. Execute the' . ' install tool to update configuration.', 1509654728 ); } } if (empty($path)) { return $GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS'][$extension]; } if (!ArrayUtility::isValidPath($GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS'], $extension . '/' . $path)) { // This if() should not be hit at "casual" runtime, but only in early setup phases if (!$hasBeenSynchronized) { $this->synchronizeExtConfTemplateWithLocalConfigurationOfAllExtensions(true); } // If there is still no such entry, even after sync -> throw if (!ArrayUtility::isValidPath($GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS'], $extension . '/' . $path)) { throw new ExtensionConfigurationPathDoesNotExistException( 'Path ' . $path . ' does not exist in extension configuration', 1509977699 ); } } return ArrayUtility::getValueByPath($GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS'], $extension . '/' . $path); } /** * Store a new or overwrite an existing configuration value. * * This is typically used by core internal low level tasks like the install * tool but may become handy if an extension needs to update extension configuration * on the fly for whatever reason. * * Examples: * // Set a full extension configuration ($value could be a nested array, too) * ->set('myExtension', ['aFeature' => 'true', 'aCustomClass' => 'css-foo']) * * // Unset a whole extension configuration * ->set('myExtension') * * Notes: * - Do NOT call this at arbitrary places during runtime (eg. NOT in ext_localconf.php or * similar). ->set() is not supposed to be called each request since it writes LocalConfiguration * each time. This API is however OK to be called from extension manager hooks. * - Values are not type safe, if the install tool wrote them, * boolean true could become string 1 on ->get() * - It is not possible to store 'null' as value, giving $value=null * or no value at all will unset the path * - Setting a value and calling ->get() afterwards will still return the new value. * - Warning on system/additional.php: If this file overwrites settings, it spoils the * ->set() call and values may not end up as expected. * * @param string $extension Extension name * @param mixed|null $value The value. If null, unset the path * @internal */ public function set(string $extension, mixed $value = null): void { if (empty($extension)) { throw new \RuntimeException('extension name must not be empty', 1509715852); } $configurationManager = GeneralUtility::makeInstance(ConfigurationManager::class); if ($value === null) { // Remove whole extension config $configurationManager->removeLocalConfigurationKeysByPath(['EXTENSIONS/' . $extension]); if (isset($GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS'][$extension])) { unset($GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS'][$extension]); } } else { // Set full extension config $configurationManager->setLocalConfigurationValueByPath('EXTENSIONS/' . $extension, $value); $GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS'][$extension] = $value; } } /** * Set new configuration of all extensions and reload TYPO3_CONF_VARS. * This is a "do all" variant of set() for all extensions that prevents * writing and loading system/settings.php many times. * * @param array $configuration Configuration of all extensions * @internal */ public function setAll(array $configuration, bool $skipWriteIfLocalConfigurationDoesNotExist = false): void { $configurationManager = GeneralUtility::makeInstance(ConfigurationManager::class); if ($skipWriteIfLocalConfigurationDoesNotExist === false || @file_exists($configurationManager->getSystemConfigurationFileLocation())) { $configurationManager->setLocalConfigurationValueByPath('EXTENSIONS', $configuration); } $GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS'] = $configuration; } /** * If there are new config settings in ext_conf_template of an extension, * they are found here and synchronized to LocalConfiguration['EXTENSIONS']. * * Used when entering the install tool, during installation and if calling ->get() * with an extension or path that is not yet found in LocalConfiguration * * @internal */ public function synchronizeExtConfTemplateWithLocalConfigurationOfAllExtensions(bool $skipWriteIfLocalConfigurationDoesNotExist = false): void { $activePackages = GeneralUtility::makeInstance(PackageManager::class)->getActivePackages(); $fullConfiguration = []; $currentLocalConfiguration = $GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS'] ?? []; foreach ($activePackages as $package) { if (!@is_file($package->getPackagePath() . 'ext_conf_template.txt')) { continue; } $extensionKey = $package->getPackageKey(); $currentExtensionConfig = $currentLocalConfiguration[$extensionKey] ?? []; $extConfTemplateConfiguration = $this->getExtConfTablesWithoutCommentsAsNestedArrayWithoutDots($extensionKey); ArrayUtility::mergeRecursiveWithOverrule($extConfTemplateConfiguration, $currentExtensionConfig); if (!empty($extConfTemplateConfiguration)) { $fullConfiguration[$extensionKey] = $extConfTemplateConfiguration; } } // Write new config if changed. Loose array comparison to not write if only array key order is different if ($fullConfiguration != $currentLocalConfiguration) { $this->setAll($fullConfiguration, $skipWriteIfLocalConfigurationDoesNotExist); } } /** * Read values from ext_conf_template, verify if they are in LocalConfiguration.php * already and if not, add them. * * Used public by extension manager when updating extension * * @internal */ public function synchronizeExtConfTemplateWithLocalConfiguration(string $extensionKey): void { $package = GeneralUtility::makeInstance(PackageManager::class)->getPackage($extensionKey); if (!@is_file($package->getPackagePath() . 'ext_conf_template.txt')) { return; } $currentLocalConfiguration = $GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS'][$extensionKey] ?? []; $extConfTemplateConfiguration = $this->getExtConfTablesWithoutCommentsAsNestedArrayWithoutDots($extensionKey); ArrayUtility::mergeRecursiveWithOverrule($extConfTemplateConfiguration, $currentLocalConfiguration); // Write new config if changed. Loose array comparison to not write if only array key order is different if ($extConfTemplateConfiguration != $currentLocalConfiguration) { $this->set($extensionKey, $extConfTemplateConfiguration); } } /** * Helper method of ext_conf_template.txt parsing. * * Poor man version of getDefaultConfigurationFromExtConfTemplateAsValuedArray() which ignores * comments and returns ext_conf_template as array where nested keys have no dots. */ protected function getExtConfTablesWithoutCommentsAsNestedArrayWithoutDots(string $extensionKey): array { $rawConfigurationString = $this->getDefaultConfigurationRawString($extensionKey); $typoScriptStringFactory = GeneralUtility::makeInstance(TypoScriptStringFactory::class); $typoScriptTree = $typoScriptStringFactory->parseFromString($rawConfigurationString, new AstBuilder(new NoopEventDispatcher())); return GeneralUtility::removeDotsFromTS($typoScriptTree->toArray()); } /** * Helper method of ext_conf_template.txt parsing. * * Return content of an extensions' ext_conf_template.txt file if * the file exists, empty string if file does not exist. */ protected function getDefaultConfigurationRawString(string $extensionKey): string { $rawString = ''; $extConfTemplateFileLocation = GeneralUtility::getFileAbsFileName( 'EXT:' . $extensionKey . '/ext_conf_template.txt' ); if (file_exists($extConfTemplateFileLocation)) { $rawString = (string)file_get_contents($extConfTemplateFileLocation); } return $rawString; } protected function hasConfiguration(string $extension): bool { return isset($GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS'][$extension]) && is_array($GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS'][$extension]); } }