Project

General

Profile

Feature #77792 » ConfigurationManager.php

Implementation of case 2 - M. Stichweh, 2016-09-02 17:14

 
<?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);
}
}

(1-1/2)