[FEATURE] Store extension configuration as plain array 57/53657/8
authorSusanne Moog <susanne.moog@typo3.com>
Thu, 20 Jul 2017 16:37:23 +0000 (18:37 +0200)
committerChristian Kuhn <lolli@schwarzbu.ch>
Thu, 7 Sep 2017 14:59:24 +0000 (16:59 +0200)
Extension configuration is now stored as plain array
instead of serialized values. To ensure backwards-
compatibility and stream-line core usage, the old
values will still be stored and written in
$GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf'] whereas
the new array will be stored in
$GLOBALS['TYPO3_CONF_VARS']['EXTCONF'].

As a second step we are going to introduce an API for
retrieving extension configuration to remove the necessity
for GLOBALS access in this case.

Resolves: #82254
Releases: master
Change-Id: I52ef7768491633e114e7e1b153a4ba63e07243ff
Reviewed-on: https://review.typo3.org/53657
Reviewed-by: Andreas Fernandez <typo3@scripting-base.de>
Tested-by: Andreas Fernandez <typo3@scripting-base.de>
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Joerg Kummer <typo3@enobe.de>
Tested-by: Joerg Kummer <typo3@enobe.de>
Reviewed-by: Romain Canon <romain.hydrocanon@gmail.com>
Tested-by: Romain Canon <romain.hydrocanon@gmail.com>
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
typo3/sysext/core/Documentation/Changelog/master/Deprecation-82254-DeprecateGLOBALSTYPO3_CONF_VARSEXTextConf.rst [new file with mode: 0644]
typo3/sysext/core/Documentation/Changelog/master/Feature-82254-StoreExtensionConfigurationAsPlainArray.rst [new file with mode: 0644]
typo3/sysext/extensionmanager/Classes/Domain/Repository/ConfigurationItemRepository.php
typo3/sysext/extensionmanager/Classes/Utility/ConfigurationUtility.php
typo3/sysext/extensionmanager/Tests/Unit/Domain/Repository/ConfigurationItemRepositoryTest.php
typo3/sysext/extensionmanager/Tests/Unit/Utility/ConfigurationUtilityTest.php
typo3/sysext/install/Configuration/ExtensionScanner/Php/ArrayDimensionMatcher.php

diff --git a/typo3/sysext/core/Documentation/Changelog/master/Deprecation-82254-DeprecateGLOBALSTYPO3_CONF_VARSEXTextConf.rst b/typo3/sysext/core/Documentation/Changelog/master/Deprecation-82254-DeprecateGLOBALSTYPO3_CONF_VARSEXTextConf.rst
new file mode 100644 (file)
index 0000000..4ecae44
--- /dev/null
@@ -0,0 +1,26 @@
+.. include:: ../../Includes.txt
+
+=============================================================================
+Deprecation: #82254 - Deprecate $GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf']
+=============================================================================
+
+See :issue:`82254`
+
+Description
+===========
+
+The extension configuration stored in $GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf'] has been deprecated and replaced by a plain array in $GLOBALS['TYPO3_CONF_VARS']['EXTCONF'].
+
+
+Affected Installations
+======================
+
+All extensions manually getting settings and unserializing them from $GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf'].
+
+
+Migration
+=========
+
+Switch to the use of $GLOBALS['TYPO3_CONF_VARS']['EXTCONF'] instead and remove all unserialize calls.
+
+.. index:: LocalConfiguration, PHP-API, FullyScanned
\ No newline at end of file
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Feature-82254-StoreExtensionConfigurationAsPlainArray.rst b/typo3/sysext/core/Documentation/Changelog/master/Feature-82254-StoreExtensionConfigurationAsPlainArray.rst
new file mode 100644 (file)
index 0000000..9af5bfb
--- /dev/null
@@ -0,0 +1,22 @@
+.. include:: ../../Includes.txt
+
+==============================================================
+Feature: #82254 - Store extension configuration as plain array
+==============================================================
+
+See :issue:`82254`
+
+Description
+===========
+
+There is no reason to save the extension configuration as serialized values instead of an plain array. Arrays are easier to handle and there are already parts of the core using arrays (for example the avatar provider registration).
+
+Therefore the API has been changed to store the extension configuration as an array in $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']. The configuration is merged on save with the default configuration and the full configuration is written to LocalConfiguration.
+
+
+Impact
+======
+
+Extension configuration can now be accessed as array directly, without calling unserialize(). The old and new API will co-exist in version 9.
+
+.. index:: LocalConfiguration, PHP-API
\ No newline at end of file
index c9a576c..0c35733 100644 (file)
@@ -57,12 +57,11 @@ class ConfigurationItemRepository
     {
         /** @var $configurationUtility \TYPO3\CMS\Extensionmanager\Utility\ConfigurationUtility */
         $configurationUtility = $this->objectManager->get(\TYPO3\CMS\Extensionmanager\Utility\ConfigurationUtility::class);
-        $defaultConfiguration = $configurationUtility->getDefaultConfigurationFromExtConfTemplateAsValuedArray($extensionKey);
+        $configuration = $configurationUtility->getCurrentConfiguration($extensionKey);
 
         $resultArray = [];
-        if (!empty($defaultConfiguration)) {
-            $metaInformation = $this->addMetaInformation($defaultConfiguration);
-            $configuration = $this->mergeWithExistingConfiguration($defaultConfiguration, $extensionKey);
+        if (!empty($configuration)) {
+            $metaInformation = $this->addMetaInformation($configuration);
             $hierarchicConfiguration = [];
             foreach ($configuration as $configurationOption) {
                 $originalConfiguration = $this->buildConfigurationArray($configurationOption, $extensionKey);
@@ -167,35 +166,6 @@ class ConfigurationItemRepository
         return $metaInformation;
     }
 
-    /**
-     * Merge current local configuration over default configuration
-     *
-     * @param array $defaultConfiguration Default configuration from ext_conf_template.txt
-     * @param string $extensionKey the extension information
-     * @return array
-     */
-    protected function mergeWithExistingConfiguration(array $defaultConfiguration, $extensionKey)
-    {
-        try {
-            $currentExtensionConfig = unserialize(
-                $this->objectManager->get(\TYPO3\CMS\Core\Configuration\ConfigurationManager::class)
-                    ->getConfigurationValueByPath('EXT/extConf/' . $extensionKey)
-            );
-            if (!is_array($currentExtensionConfig)) {
-                $currentExtensionConfig = [];
-            }
-        } catch (\RuntimeException $e) {
-            $currentExtensionConfig = [];
-        }
-        $flatExtensionConfig = ArrayUtility::flatten($currentExtensionConfig);
-        $valuedCurrentExtensionConfig = [];
-        foreach ($flatExtensionConfig as $key => $value) {
-            $valuedCurrentExtensionConfig[$key]['value'] = $value;
-        }
-        ArrayUtility::mergeRecursiveWithOverrule($defaultConfiguration, $valuedCurrentExtensionConfig);
-        return $defaultConfiguration;
-    }
-
     /**
      * Converts a hierarchic configuration array to an
      * hierarchic object storage structure
index 19e43c1..d52ae16 100644 (file)
@@ -15,6 +15,7 @@ namespace TYPO3\CMS\Extensionmanager\Utility;
  */
 
 use TYPO3\CMS\Core\TypoScript\ConfigurationForm;
+use TYPO3\CMS\Core\Utility\ArrayUtility;
 
 /**
  * Utility for dealing with ext_emconf and ext_conf_template settings
@@ -66,6 +67,7 @@ class ConfigurationUtility implements \TYPO3\CMS\Core\SingletonInterface
         /** @var $configurationManager \TYPO3\CMS\Core\Configuration\ConfigurationManager */
         $configurationManager = $this->objectManager->get(\TYPO3\CMS\Core\Configuration\ConfigurationManager::class);
         $configurationManager->setLocalConfigurationValueByPath('EXT/extConf/' . $extensionKey, serialize($configuration));
+        $configurationManager->setLocalConfigurationValueByPath('EXTCONF/' . $extensionKey, $configuration);
     }
 
     /**
@@ -74,17 +76,21 @@ class ConfigurationUtility implements \TYPO3\CMS\Core\SingletonInterface
      * @param string $extensionKey
      * @return array
      */
-    public function getCurrentConfiguration($extensionKey)
+    public function getCurrentConfiguration(string $extensionKey): array
     {
         $mergedConfiguration = $this->getDefaultConfigurationFromExtConfTemplateAsValuedArray($extensionKey);
+
+        // @deprecated loading serialized configuration is deprecated and will be removed in v10 - use EXTCONF array instead
         // No objects allowed in extConf at all - it is safe to deny that during unserialize()
-        $currentExtensionConfig = unserialize($GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf'][$extensionKey], ['allowed_classes' => false]);
-        $currentExtensionConfig = is_array($currentExtensionConfig) ? $currentExtensionConfig : [];
-        $currentExtensionConfig = $this->convertNestedToValuedConfiguration($currentExtensionConfig);
-        \TYPO3\CMS\Core\Utility\ArrayUtility::mergeRecursiveWithOverrule(
-            $mergedConfiguration,
-            $currentExtensionConfig
-        );
+        $legacyCurrentExtensionConfiguration = unserialize($GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf'][$extensionKey], ['allowed_classes' => false]);
+        $legacyCurrentExtensionConfiguration = is_array($legacyCurrentExtensionConfiguration) ? $legacyCurrentExtensionConfiguration : [];
+        $mergedConfiguration = $this->mergeExtensionConfigurations($mergedConfiguration, $legacyCurrentExtensionConfiguration);
+
+        if (isset($GLOBALS['TYPO3_CONF_VARS']['EXTCONF'][$extensionKey]) && is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF'][$extensionKey])) {
+            $currentExtensionConfiguration = $GLOBALS['TYPO3_CONF_VARS']['EXTCONF'][$extensionKey];
+            $mergedConfiguration = $this->mergeExtensionConfigurations($mergedConfiguration, $currentExtensionConfiguration);
+        }
+
         return $mergedConfiguration;
     }
 
@@ -190,7 +196,7 @@ class ConfigurationUtility implements \TYPO3\CMS\Core\SingletonInterface
         $nestedConfiguration = [];
         foreach ($valuedConfiguration as $name => $section) {
             $path = str_replace('.', './', $name);
-            $nestedConfiguration = \TYPO3\CMS\Core\Utility\ArrayUtility::setValueByPath($nestedConfiguration, $path, $section['value'], '/');
+            $nestedConfiguration = ArrayUtility::setValueByPath($nestedConfiguration, $path, $section['value'], '/');
         }
         return $nestedConfiguration;
     }
@@ -206,11 +212,35 @@ class ConfigurationUtility implements \TYPO3\CMS\Core\SingletonInterface
      */
     public function convertNestedToValuedConfiguration(array $nestedConfiguration)
     {
-        $flatExtensionConfig = \TYPO3\CMS\Core\Utility\ArrayUtility::flatten($nestedConfiguration);
+        $flatExtensionConfig = ArrayUtility::flatten($nestedConfiguration);
         $valuedCurrentExtensionConfig = [];
         foreach ($flatExtensionConfig as $key => $value) {
             $valuedCurrentExtensionConfig[$key]['value'] = $value;
         }
         return $valuedCurrentExtensionConfig;
     }
+
+    /**
+     * Merges two existing configuration arrays,
+     * expects configuration as valued flat structure
+     * and overrides as nested array
+     *
+     * @see convertNestedToValuedConfiguration
+     *
+     * @param array $configuration
+     * @param array $configurationOverride
+     *
+     * @return array
+     */
+    private function mergeExtensionConfigurations(array $configuration, array $configurationOverride): array
+    {
+        $configurationOverride = $this->convertNestedToValuedConfiguration(
+            $configurationOverride
+        );
+        ArrayUtility::mergeRecursiveWithOverrule(
+            $configuration,
+            $configurationOverride
+        );
+        return $configuration;
+    }
 }
index 481cef1..f373847 100644 (file)
@@ -14,6 +14,8 @@ namespace TYPO3\CMS\Extensionmanager\Tests\Unit\Domain\Repository;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Extbase\Object\ObjectManager;
+
 /**
  * Tests for ConfigurationItemRepository
  */
@@ -90,7 +92,7 @@ class ConfigurationItemRepositoryTest extends \TYPO3\TestingFramework\Core\Unit\
 
         $configurationItemRepository = $this->getAccessibleMock(
             \TYPO3\CMS\Extensionmanager\Domain\Repository\ConfigurationItemRepository::class,
-            ['mergeWithExistingConfiguration', 'translate']
+            ['translate']
         );
         $configurationItemRepository->_set(
             'objectManager',
@@ -99,16 +101,13 @@ class ConfigurationItemRepositoryTest extends \TYPO3\TestingFramework\Core\Unit\
         $configurationUtilityMock = $this->createMock(\TYPO3\CMS\Extensionmanager\Utility\ConfigurationUtility::class);
         $configurationUtilityMock
             ->expects($this->once())
-            ->method('getDefaultConfigurationFromExtConfTemplateAsValuedArray')
+            ->method('getCurrentConfiguration')
             ->will($this->returnValue($flatConfigurationItemArray));
+
         $this->injectedObjectManagerMock
             ->expects($this->any())
             ->method('get')
             ->will($this->returnValue($configurationUtilityMock));
-        $configurationItemRepository
-            ->expects($this->any())
-            ->method('mergeWithExistingConfiguration')
-            ->will($this->returnValue($flatConfigurationItemArray));
 
         $expectedArray = [
             'basic' => [
@@ -315,122 +314,4 @@ class ConfigurationItemRepositoryTest extends \TYPO3\TestingFramework\Core\Unit\
         $this->assertEquals($option['genericComparisonValue'], $optionModified['generic']);
         $this->assertEquals($option['typeComparisonValue'], $optionModified['type']);
     }
-
-    /**
-     * @test
-     */
-    public function mergeDefaultConfigurationCatchesExceptionOfConfigurationManagerIfNoLocalConfigurationExists()
-    {
-        $exception = $this->getMockBuilder('RuntimeException')->getMock();
-        $configurationManagerMock = $this->getMockBuilder(\TYPO3\CMS\Core\Configuration\ConfigurationManager::class)->getMock();
-        $configurationManagerMock
-            ->expects($this->once())
-            ->method('getConfigurationValueByPath')
-            ->will($this->throwException($exception));
-        $this->injectedObjectManagerMock
-            ->expects($this->any())
-            ->method('get')
-            ->will($this->returnValue($configurationManagerMock));
-
-        $this->configurationItemRepository->_call(
-            'mergeWithExistingConfiguration',
-            [],
-            $this->getUniqueId('not_existing_extension')
-        );
-    }
-
-    /**
-     * @test
-     */
-    public function mergeDefaultConfigurationWithNoCurrentValuesReturnsTheDefaultConfiguration()
-    {
-        $exception = $this->getMockBuilder('RuntimeException')->getMock();
-        $configurationManagerMock = $this->getMockBuilder(\TYPO3\CMS\Core\Configuration\ConfigurationManager::class)->getMock();
-        $configurationManagerMock
-            ->expects($this->once())
-            ->method('getConfigurationValueByPath')
-            ->will($this->throwException($exception));
-        $this->injectedObjectManagerMock
-            ->expects($this->any())
-            ->method('get')
-            ->will($this->returnValue($configurationManagerMock));
-        $defaultConfiguration = [
-            'foo' => 'bar'
-        ];
-        $configuration = $this->configurationItemRepository->_call(
-            'mergeWithExistingConfiguration',
-            $defaultConfiguration,
-            $this->getUniqueId('not_existing_extension')
-        );
-        $this->assertEquals($defaultConfiguration, $configuration);
-    }
-
-    /**
-     * @test
-     */
-    public function mergeWithExistingConfigurationOverwritesDefaultKeysWithCurrent()
-    {
-        $localConfiguration = serialize([
-            'FE.' => [
-                'enabled' => '1',
-                'saltedPWHashingMethod' => \TYPO3\CMS\Saltedpasswords\Salt\SaltInterface_sha1::class
-            ],
-            'CLI.' => [
-                'enabled' => '0'
-            ]
-        ]);
-
-        $configurationManagerMock = $this->getMockBuilder(\TYPO3\CMS\Core\Configuration\ConfigurationManager::class)->getMock();
-        $configurationManagerMock
-            ->expects($this->once())
-            ->method('getConfigurationValueByPath')
-            ->with('EXT/extConf/testextensionkey')
-            ->will($this->returnValue($localConfiguration));
-
-        $this->injectedObjectManagerMock
-            ->expects($this->any())
-            ->method('get')
-            ->will($this->returnValue($configurationManagerMock));
-
-        $defaultConfiguration = [
-            'FE.enabled' => [
-                'value' => '0'
-            ],
-            'FE.saltedPWHashingMethod' => [
-                'value' => \TYPO3\CMS\Saltedpasswords\Salt\Md5Salt::class
-            ],
-            'BE.enabled' => [
-                'value' => '1'
-            ],
-            'BE.saltedPWHashingMethod' => [
-                'value' => \TYPO3\CMS\Saltedpasswords\Salt\Md5Salt::class
-            ]
-        ];
-
-        $expectedResult = [
-            'FE.enabled' => [
-                'value' => '1'
-            ],
-            'FE.saltedPWHashingMethod' => [
-                'value' => \TYPO3\CMS\Saltedpasswords\Salt\SaltInterface_sha1::class
-            ],
-            'BE.enabled' => [
-                'value' => '1'
-            ],
-            'BE.saltedPWHashingMethod' => [
-                'value' => \TYPO3\CMS\Saltedpasswords\Salt\Md5Salt::class
-            ],
-            'CLI.enabled' => [
-                'value' => '0'
-            ]
-        ];
-
-        $actualResult = $this->configurationItemRepository->_call(
-            'mergeWithExistingConfiguration',
-            $defaultConfiguration,
-            'testextensionkey'
-        );
-
-        $this->assertEquals($expectedResult, $actualResult);
-    }
 }
index 8989deb..ecbe829 100644 (file)
@@ -14,6 +14,8 @@ namespace TYPO3\CMS\Extensionmanager\Tests\Unit\Utility;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Extensionmanager\Utility\ConfigurationUtility;
+
 /**
  * Configuration utility test
  */
@@ -24,8 +26,8 @@ class ConfigurationUtilityTest extends \TYPO3\TestingFramework\Core\Unit\UnitTes
      */
     public function getCurrentConfigurationReturnsExtensionConfigurationAsValuedConfiguration()
     {
-        /** @var $configurationUtility \TYPO3\CMS\Extensionmanager\Utility\ConfigurationUtility|\TYPO3\TestingFramework\Core\AccessibleObjectInterface|\PHPUnit_Framework_MockObject_MockObject */
-        $configurationUtility = $this->getMockBuilder(\TYPO3\CMS\Extensionmanager\Utility\ConfigurationUtility::class)
+        /** @var $configurationUtility ConfigurationUtility|\TYPO3\TestingFramework\Core\AccessibleObjectInterface|\PHPUnit_Framework_MockObject_MockObject */
+        $configurationUtility = $this->getMockBuilder(ConfigurationUtility::class)
             ->setMethods(['getDefaultConfigurationFromExtConfTemplateAsValuedArray'])
             ->getMock();
         $configurationUtility
@@ -55,6 +57,59 @@ class ConfigurationUtilityTest extends \TYPO3\TestingFramework\Core\Unit\UnitTes
         $this->assertEquals($expected, $actual);
     }
 
+    /**
+     * @test
+     */
+    public function getCurrentConfigurationReturnsExtensionConfigurationWithExtconfBasedConfigurationPrioritized()
+    {
+        /** @var $configurationUtility \TYPO3\CMS\Extensionmanager\Utility\ConfigurationUtility|\TYPO3\TestingFramework\Core\AccessibleObjectInterface|\PHPUnit_Framework_MockObject_MockObject */
+        $configurationUtility = $this->getMockBuilder(\TYPO3\CMS\Extensionmanager\Utility\ConfigurationUtility::class)
+            ->setMethods(['getDefaultConfigurationFromExtConfTemplateAsValuedArray'])
+            ->getMock();
+        $configurationUtility
+            ->expects($this->once())
+            ->method('getDefaultConfigurationFromExtConfTemplateAsValuedArray')
+            ->will($this->returnValue([]));
+        $extensionKey = $this->getUniqueId('some-extension');
+
+        $serializedConfiguration = [
+            'key1' => 'value1',
+            'key2.' => [
+                'subkey1' => 'somevalue'
+            ]
+        ];
+        $currentExtconfConfiguration = [
+            'key1' => 'value1',
+            'key2.' => [
+                'subkey1' => 'overwritten'
+            ]
+        ];
+
+        $expected = [
+            'key1' => [
+                'value' => 'value1',
+            ],
+            'key2.subkey1' => [
+                'value' => 'overwritten',
+            ],
+        ];
+
+        $GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf'][$extensionKey] = serialize($serializedConfiguration);
+        $GLOBALS['TYPO3_CONF_VARS']['EXTCONF'][$extensionKey] = $currentExtconfConfiguration;
+        $actual = $configurationUtility->getCurrentConfiguration($extensionKey);
+        $this->assertEquals($expected, $actual);
+    }
+
+    /**
+     * @test
+     */
+    public function mergeDefaultConfigurationReturnsEmptyArrayIfNoConfigurationForExtensionExists()
+    {
+        $configurationUtility = new ConfigurationUtility();
+        $result = $configurationUtility->getCurrentConfiguration('not_existing_extension');
+        self::assertSame([], $result);
+    }
+
     /**
      * @test
      */
index 28b6c1a..b6d0b5b 100644 (file)
@@ -64,4 +64,9 @@ return [
             'Breaking-81171-EditAbilityOfTypoScriptTemplateInEXTtstemplateRemoved.rst',
         ],
     ],
+    '$GLOBALS[\'TYPO3_CONF_VARS\'][\'EXT\'][\'extConf\']' => [
+        'restFiles' => [
+            'Deprecation-82254-DeprecateGLOBALSTYPO3_CONF_VARSEXTextConf.rst',
+        ],
+    ],
 ];