|
<?php
|
|
namespace TYPO3\CMS\Core\Configuration;
|
|
|
|
/*
|
|
* 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!
|
|
*/
|
|
|
|
use TYPO3\CMS\Core\Service\OpcodeCacheService;
|
|
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
|
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
|
|
|
/**
|
|
* Handle loading and writing of global and local (instance specific)
|
|
* configuration.
|
|
*
|
|
* This class handles the access to the files
|
|
* - EXT:core/Configuration/DefaultConfiguration.php (default TYPO3_CONF_VARS)
|
|
* - typo3conf/LocalConfiguration.php (overrides of TYPO3_CONF_VARS)
|
|
* - typo3conf/AdditionalConfiguration.php (optional additional local code blocks)
|
|
*
|
|
* IMPORTANT:
|
|
* This class is intended for internal core use ONLY.
|
|
* Extensions should usually use the resulting $GLOBALS['TYPO3_CONF_VARS'] array,
|
|
* do not try to modify settings in LocalConfiguration.php with an extension.
|
|
* @internal
|
|
*/
|
|
class ConfigurationManager
|
|
{
|
|
/**
|
|
* @var string Path to default TYPO3_CONF_VARS file, relative to PATH_site
|
|
*/
|
|
protected $defaultConfigurationFile = 'typo3/sysext/core/Configuration/DefaultConfiguration.php';
|
|
|
|
/**
|
|
* @var string Path to local overload TYPO3_CONF_VARS file, relative to PATH_site
|
|
*/
|
|
protected $localConfigurationFile = 'typo3conf/LocalConfiguration.php';
|
|
|
|
/**
|
|
* @var string Path pattern to context specific local overload TYPO3_CONF_VARS file, relative to PATH_site
|
|
*/
|
|
protected $contextLocalConfigurationOverridesFilePattern = 'typo3conf/LocalConfigurationOverrides.%s.php';
|
|
|
|
/**
|
|
* @var string Path to additional local file, relative to PATH_site
|
|
*/
|
|
protected $additionalConfigurationFile = 'typo3conf/AdditionalConfiguration.php';
|
|
|
|
/**
|
|
* @var string Path to factory configuration file used during installation as LocalConfiguration boilerplate
|
|
*/
|
|
protected $factoryConfigurationFile = 'typo3/sysext/core/Configuration/FactoryConfiguration.php';
|
|
|
|
/**
|
|
* @var string Path to possible additional factory configuration file delivered by packages
|
|
*/
|
|
protected $additionalFactoryConfigurationFile = 'typo3conf/AdditionalFactoryConfiguration.php';
|
|
|
|
/**
|
|
* @var string Absolute path to typo3conf directory
|
|
*/
|
|
protected $pathTypo3Conf = PATH_typo3conf;
|
|
|
|
/**
|
|
* Writing to these configuration pathes is always allowed,
|
|
* even if the requested sub path does not exist yet.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $whiteListedLocalConfigurationPaths = array(
|
|
'EXT/extConf',
|
|
'EXTCONF',
|
|
'INSTALL/wizardDone',
|
|
'DB',
|
|
'SYS/caching/cacheConfigurations',
|
|
);
|
|
|
|
/**
|
|
* Return default configuration array
|
|
*
|
|
* @return array
|
|
*/
|
|
public function getDefaultConfiguration()
|
|
{
|
|
return require $this->getDefaultConfigurationFileLocation();
|
|
}
|
|
|
|
/**
|
|
* Get the file location of the default configuration file,
|
|
* currently the path and filename.
|
|
*
|
|
* @return string
|
|
* @access private
|
|
*/
|
|
public function getDefaultConfigurationFileLocation()
|
|
{
|
|
return PATH_site . $this->defaultConfigurationFile;
|
|
}
|
|
|
|
/**
|
|
* Return local configuration array typo3conf/LocalConfiguration.php
|
|
*
|
|
* @param \TYPO3\CMS\Core\Core\ApplicationContext $context An optional application context to get only the overridden values for that context
|
|
* @param boolean $disableContextConfigMerge Disable merging with context specific configurations to fetch just the config from LocalConfiguration.php
|
|
* @return array Content array of local configuration file
|
|
*/
|
|
public function getLocalConfiguration($context = null, $disableContextConfigMerge = false)
|
|
{
|
|
if(!$disableContextConfigMerge && !$context) {
|
|
$config = require $this->getLocalConfigurationFileLocation();
|
|
// Merge with context specific configurations if exists
|
|
if(GeneralUtility::getApplicationContext()->getParent() != null) {
|
|
$contextConfigFilePath = $this->getLocalConfigurationFileLocation(GeneralUtility::getApplicationContext()->getParent());
|
|
if( @file_exists($contextConfigFilePath)) {
|
|
ArrayUtility::mergeRecursiveWithOverrule($config, require $contextConfigFilePath);
|
|
}
|
|
}
|
|
$contextConfigFilePath = $this->getLocalConfigurationFileLocation(GeneralUtility::getApplicationContext());
|
|
if( @file_exists($contextConfigFilePath)) {
|
|
ArrayUtility::mergeRecursiveWithOverrule($config, require $contextConfigFilePath);
|
|
}
|
|
return $config;
|
|
}
|
|
elseif($context) {
|
|
$config = array();
|
|
$contextConfigFilePath = $this->getLocalConfigurationFileLocation($context);
|
|
if( @file_exists($contextConfigFilePath)) {
|
|
$config = require $contextConfigFilePath;
|
|
}
|
|
return $config;
|
|
}
|
|
return require $this->getLocalConfigurationFileLocation();
|
|
}
|
|
|
|
/**
|
|
* Get the file location of the local configuration file,
|
|
* currently the path and filename.
|
|
*
|
|
* @param \TYPO3\CMS\Core\Core\ApplicationContext $context An optional application context as postfix for the configuration file
|
|
* @return string
|
|
* @access private
|
|
*/
|
|
public function getLocalConfigurationFileLocation($context = '')
|
|
{
|
|
$localConfigurationFile = PATH_site . $this->localConfigurationFile;
|
|
if($context) {
|
|
$ontextName = array($context->__toString());
|
|
if($context->getParent() != null) {
|
|
array_unshift($ontextName, $context->getParent()->__toString());
|
|
}
|
|
$localConfigurationFile = PATH_site . sprintf($this->contextLocalConfigurationOverridesFilePattern, implode('.', $ontextName));
|
|
}
|
|
return $localConfigurationFile;
|
|
}
|
|
|
|
/**
|
|
* Get the file location of the additional configuration file,
|
|
* currently the path and filename.
|
|
*
|
|
* @return string
|
|
* @access private
|
|
*/
|
|
public function getAdditionalConfigurationFileLocation()
|
|
{
|
|
return PATH_site . $this->additionalConfigurationFile;
|
|
}
|
|
|
|
/**
|
|
* Get absolute file location of factory configuration file
|
|
*
|
|
* @return string
|
|
*/
|
|
protected function getFactoryConfigurationFileLocation()
|
|
{
|
|
return PATH_site . $this->factoryConfigurationFile;
|
|
}
|
|
|
|
/**
|
|
* Get absolute file location of factory configuration file
|
|
*
|
|
* @return string
|
|
*/
|
|
protected function getAdditionalFactoryConfigurationFileLocation()
|
|
{
|
|
return PATH_site . $this->additionalFactoryConfigurationFile;
|
|
}
|
|
|
|
/**
|
|
* Override local configuration with new values.
|
|
*
|
|
* @param array $configurationToMerge Override configuration array
|
|
* @return void
|
|
*/
|
|
public function updateLocalConfiguration(array $configurationToMerge)
|
|
{
|
|
$newLocalConfiguration = $this->getLocalConfiguration();
|
|
ArrayUtility::mergeRecursiveWithOverrule($newLocalConfiguration, $configurationToMerge);
|
|
$this->writeLocalConfiguration($newLocalConfiguration);
|
|
}
|
|
|
|
/**
|
|
* Get a value at given path from default configuration
|
|
*
|
|
* @param string $path Path to search for
|
|
* @return mixed Value at path
|
|
*/
|
|
public function getDefaultConfigurationValueByPath($path)
|
|
{
|
|
return ArrayUtility::getValueByPath($this->getDefaultConfiguration(), $path);
|
|
}
|
|
|
|
/**
|
|
* Get a value at given path from local configuration
|
|
*
|
|
* @param string $path Path to search for
|
|
* @return mixed Value at path
|
|
*/
|
|
public function getLocalConfigurationValueByPath($path)
|
|
{
|
|
return ArrayUtility::getValueByPath($this->getLocalConfiguration(), $path);
|
|
}
|
|
|
|
/**
|
|
* Get a value from configuration, this is default configuration
|
|
* merged with local configuration
|
|
*
|
|
* @param string $path Path to search for
|
|
* @return mixed
|
|
*/
|
|
public function getConfigurationValueByPath($path)
|
|
{
|
|
$defaultConfiguration = $this->getDefaultConfiguration();
|
|
ArrayUtility::mergeRecursiveWithOverrule($defaultConfiguration, $this->getLocalConfiguration());
|
|
return ArrayUtility::getValueByPath($defaultConfiguration, $path);
|
|
}
|
|
|
|
/**
|
|
* Update a given path in local configuration to a new value.
|
|
*
|
|
* @param string $path Path to update
|
|
* @param mixed $value Value to set
|
|
* @return bool TRUE on success
|
|
*/
|
|
public function setLocalConfigurationValueByPath($path, $value)
|
|
{
|
|
$result = false;
|
|
if ($this->isValidLocalConfigurationPath($path)) {
|
|
// Check if the value is overridden in context specific configuration and store it there if this is the case
|
|
$localConfiguration = array();
|
|
$context = $this->getContextOfConfigurationPath($path, $localConfiguration);
|
|
if(!$context) {
|
|
// Get config from LocalConfiguration.php only - without context specific merging
|
|
$localConfiguration = $this->getLocalConfiguration(null, true);
|
|
}
|
|
$localConfiguration = ArrayUtility::setValueByPath($localConfiguration, $path, $value);
|
|
$result = $this->writeLocalConfiguration($localConfiguration, $context);
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Searches in context specific configurations for the specified config value path and returns the context where the value path is
|
|
* found. NULL is returned if the value path is not found in any context specific configuration.
|
|
*
|
|
* @param string $path The configuration value path
|
|
* @return \TYPO3\CMS\Core\Core\ApplicationContext The context where the path was found or null otherwise
|
|
*/
|
|
protected function getContextOfConfigurationPath($path, &$localConfiguration)
|
|
{
|
|
$localConfiguration = $this->getLocalConfiguration(GeneralUtility::getApplicationContext());
|
|
if(ArrayUtility::isValidPath($localConfiguration, $path))
|
|
{
|
|
return GeneralUtility::getApplicationContext();
|
|
}
|
|
if(GeneralUtility::getApplicationContext()->getParent())
|
|
{
|
|
$localConfiguration = $this->getLocalConfiguration(GeneralUtility::getApplicationContext()->getParent());
|
|
if(ArrayUtility::isValidPath($localConfiguration, $path))
|
|
{
|
|
return GeneralUtility::getApplicationContext()->getParent();
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Update / set a list of path and value pairs in local configuration file
|
|
*
|
|
* @param array $pairs Key is path, value is value to set
|
|
* @return bool TRUE on success
|
|
*/
|
|
public function setLocalConfigurationValuesByPathValuePairs(array $pairs)
|
|
{
|
|
$localConfigurationModified = false;
|
|
$localConfigurationContextModified = false;
|
|
$localConfigurationSubContextModified = false;
|
|
|
|
$localConfiguration = $this->getLocalConfiguration(null, true);
|
|
if(GeneralUtility::getApplicationContext()->getParent()) {
|
|
$localConfigurationContext = $this->getLocalConfiguration(GeneralUtility::getApplicationContext()->getParent());
|
|
$localConfigurationSubContext = $this->getLocalConfiguration(GeneralUtility::getApplicationContext());
|
|
}
|
|
else {
|
|
$localConfigurationContext = $this->getLocalConfiguration(GeneralUtility::getApplicationContext());
|
|
$localConfigurationSubContext = array();
|
|
}
|
|
foreach ($pairs as $path => $value) {
|
|
if ($this->isValidLocalConfigurationPath($path)) {
|
|
if(ArrayUtility::isValidPath($localConfigurationSubContext, $path)) {
|
|
$localConfigurationSubContext = ArrayUtility::setValueByPath($localConfigurationSubContext, $path, $value);
|
|
$localConfigurationSubContextModified = true;
|
|
}
|
|
elseif(ArrayUtility::isValidPath($localConfigurationContext, $path)) {
|
|
$localConfigurationContext = ArrayUtility::setValueByPath($localConfigurationContext, $path, $value);
|
|
$localConfigurationContextModified = true;
|
|
}
|
|
else {
|
|
$localConfiguration = ArrayUtility::setValueByPath($localConfiguration, $path, $value);
|
|
$localConfigurationModified = true;
|
|
}
|
|
}
|
|
}
|
|
if($localConfigurationSubContextModified) {
|
|
$this->writeLocalConfiguration($localConfigurationSubContext, GeneralUtility::getApplicationContext());
|
|
}
|
|
if($localConfigurationContextModified) {
|
|
$context = GeneralUtility::getApplicationContext()->getParent()? GeneralUtility::getApplicationContext()->getParent() : GeneralUtility::getApplicationContext();
|
|
$this->writeLocalConfiguration($localConfigurationContext, $context);
|
|
}
|
|
if($localConfigurationModified) {
|
|
$this->writeLocalConfiguration($localConfiguration);
|
|
}
|
|
return $this->getLocalConfiguration();
|
|
}
|
|
|
|
/**
|
|
* Remove keys from LocalConfiguration
|
|
*
|
|
* @param array $keys Array with key paths to remove from LocalConfiguration
|
|
* @return bool TRUE if something was removed
|
|
*/
|
|
public function removeLocalConfigurationKeysByPath(array $keys)
|
|
{
|
|
$result = false;
|
|
$localConfigurationModified = false;
|
|
$localConfigurationContextModified = false;
|
|
$localConfigurationSubContextModified = false;
|
|
|
|
$localConfiguration = $this->getLocalConfiguration(null, true);
|
|
if(GeneralUtility::getApplicationContext()->getParent()) {
|
|
$localConfigurationContext = $this->getLocalConfiguration(GeneralUtility::getApplicationContext()->getParent());
|
|
$localConfigurationSubContext = $this->getLocalConfiguration(GeneralUtility::getApplicationContext());
|
|
}
|
|
else {
|
|
$localConfigurationContext = $this->getLocalConfiguration(GeneralUtility::getApplicationContext());
|
|
$localConfigurationSubContext = array();
|
|
}
|
|
foreach ($keys as $path) {
|
|
// Remove key if path is within LocalConfiguration
|
|
if (ArrayUtility::isValidPath($localConfiguration, $path)) {
|
|
$result = true;
|
|
if(ArrayUtility::isValidPath($localConfigurationSubContext, $path)) {
|
|
$localConfigurationSubContext = ArrayUtility::removeByPath($localConfigurationSubContext, $path);
|
|
$localConfigurationSubContextModified = true;
|
|
}
|
|
elseif(ArrayUtility::isValidPath($localConfigurationContext, $path)) {
|
|
$localConfigurationContext = ArrayUtility::removeByPath($localConfigurationContext, $path);
|
|
$localConfigurationContextModified = true;
|
|
}
|
|
else {
|
|
$localConfiguration = ArrayUtility::removeByPath($localConfiguration, $path);
|
|
$localConfigurationModified = true;
|
|
}
|
|
}
|
|
}
|
|
if ($result) {
|
|
if($localConfigurationSubContextModified) {
|
|
$this->writeLocalConfiguration($localConfigurationSubContext, GeneralUtility::getApplicationContext());
|
|
}
|
|
if($localConfigurationContextModified) {
|
|
$context = GeneralUtility::getApplicationContext()->getParent()? GeneralUtility::getApplicationContext()->getParent() : GeneralUtility::getApplicationContext();
|
|
$this->writeLocalConfiguration($localConfigurationContext, $context);
|
|
}
|
|
if($localConfigurationModified) {
|
|
$this->writeLocalConfiguration($localConfiguration);
|
|
}
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Checks if the configuration can be written.
|
|
*
|
|
* @param string $context An optional environment context as postfix of the configuration file
|
|
* @return bool
|
|
* @access private
|
|
*/
|
|
public function canWriteConfiguration($context = '')
|
|
{
|
|
$fileLocation = $this->getLocalConfigurationFileLocation($context);
|
|
return @is_writable($this->pathTypo3Conf) && (!file_exists($fileLocation) || @is_writable($fileLocation));
|
|
}
|
|
|
|
/**
|
|
* Reads the configuration array and exports it to the global variable
|
|
*
|
|
* @access private
|
|
* @throws \UnexpectedValueException
|
|
* @return void
|
|
*/
|
|
public function exportConfiguration()
|
|
{
|
|
if (@is_file($this->getLocalConfigurationFileLocation())) {
|
|
$localConfiguration = $this->getLocalConfiguration();
|
|
if (is_array($localConfiguration)) {
|
|
$defaultConfiguration = $this->getDefaultConfiguration();
|
|
ArrayUtility::mergeRecursiveWithOverrule($defaultConfiguration, $localConfiguration);
|
|
$GLOBALS['TYPO3_CONF_VARS'] = $defaultConfiguration;
|
|
} else {
|
|
throw new \UnexpectedValueException('LocalConfiguration invalid.', 1349272276);
|
|
}
|
|
if (@is_file($this->getAdditionalConfigurationFileLocation())) {
|
|
require $this->getAdditionalConfigurationFileLocation();
|
|
}
|
|
} else {
|
|
// No LocalConfiguration (yet), load DefaultConfiguration only
|
|
$GLOBALS['TYPO3_CONF_VARS'] = $this->getDefaultConfiguration();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Write local configuration array to typo3conf/LocalConfiguration.php
|
|
*
|
|
* @param array $configuration The local configuration to be written
|
|
* \TYPO3\CMS\Core\Core\ApplicationContext $context An optional application context to write a context specific file
|
|
* @throws \RuntimeException
|
|
* @return bool TRUE on success
|
|
* @access private
|
|
*/
|
|
public function writeLocalConfiguration(array $configuration, $context = '')
|
|
{
|
|
$localConfigurationFile = $this->getLocalConfigurationFileLocation($context);
|
|
if (!$this->canWriteConfiguration($context)) {
|
|
throw new \RuntimeException(
|
|
$localConfigurationFile . ' is not writable.', 1346323822
|
|
);
|
|
}
|
|
$configuration = ArrayUtility::sortByKeyRecursive($configuration);
|
|
$result = GeneralUtility::writeFile(
|
|
$localConfigurationFile,
|
|
'<?php' . LF .
|
|
'return ' .
|
|
ArrayUtility::arrayExport(
|
|
ArrayUtility::renumberKeysToAvoidLeapsIfKeysAreAllNumeric($configuration)
|
|
) .
|
|
';' . LF,
|
|
true
|
|
);
|
|
|
|
GeneralUtility::makeInstance(OpcodeCacheService::class)->clearAllActive($localConfigurationFile);
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Write additional configuration array to typo3conf/AdditionalConfiguration.php
|
|
*
|
|
* @param array $additionalConfigurationLines The configuration lines to be written
|
|
* @throws \RuntimeException
|
|
* @return bool TRUE on success
|
|
* @access private
|
|
*/
|
|
public function writeAdditionalConfiguration(array $additionalConfigurationLines)
|
|
{
|
|
return GeneralUtility::writeFile(
|
|
PATH_site . $this->additionalConfigurationFile,
|
|
'<?php' . LF .
|
|
implode(LF, $additionalConfigurationLines) . LF
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Uses FactoryConfiguration file and a possible AdditionalFactoryConfiguration
|
|
* file in typo3conf to create a basic LocalConfiguration.php. This is used
|
|
* by the install tool in an early step.
|
|
*
|
|
* @throws \RuntimeException
|
|
* @return void
|
|
* @access private
|
|
*/
|
|
public function createLocalConfigurationFromFactoryConfiguration()
|
|
{
|
|
if (file_exists($this->getLocalConfigurationFileLocation())) {
|
|
throw new \RuntimeException(
|
|
'LocalConfiguration.php exists already',
|
|
1364836026
|
|
);
|
|
}
|
|
$localConfigurationArray = require $this->getFactoryConfigurationFileLocation();
|
|
$additionalFactoryConfigurationFileLocation = $this->getAdditionalFactoryConfigurationFileLocation();
|
|
if (file_exists($additionalFactoryConfigurationFileLocation)) {
|
|
$additionalFactoryConfigurationArray = require $additionalFactoryConfigurationFileLocation;
|
|
ArrayUtility::mergeRecursiveWithOverrule(
|
|
$localConfigurationArray,
|
|
$additionalFactoryConfigurationArray
|
|
);
|
|
}
|
|
$this->writeLocalConfiguration($localConfigurationArray);
|
|
}
|
|
|
|
/**
|
|
* Check if access / write to given path in local configuration is allowed.
|
|
*
|
|
* @param string $path Path to search for
|
|
* @return bool TRUE if access is allowed
|
|
*/
|
|
protected function isValidLocalConfigurationPath($path)
|
|
{
|
|
// Early return for white listed paths
|
|
foreach ($this->whiteListedLocalConfigurationPaths as $whiteListedPath) {
|
|
if (GeneralUtility::isFirstPartOfStr($path, $whiteListedPath)) {
|
|
return true;
|
|
}
|
|
}
|
|
return ArrayUtility::isValidPath($this->getDefaultConfiguration(), $path);
|
|
}
|
|
}
|
|
|