[BUGFIX] Localization State does not properly sanitize and enrich 27/54427/2
authorOliver Hader <oliver@typo3.org>
Tue, 17 Oct 2017 20:59:31 +0000 (22:59 +0200)
committerChristian Kuhn <lolli@schwarzbu.ch>
Tue, 17 Oct 2017 23:14:38 +0000 (01:14 +0200)
The Localization State object has an incomplete assignment in the
constructor - sanitize processing is overridden by enrich processing,
however both should be combined instead.

The current side-effects were, that
* localization state could contain fields that were not defined in TCA
  having support for the `allowLanguageSynchronization` setting
* state values different than `custom`, `source` or `parent` have not
  been overridden by the default `parent` value

Resolves: #82793
Releases: 8.7, master
Change-Id: I25e794736df216b17a5b8de3bc5203bd18ee9694
Reviewed-on: https://review.typo3.org/54427
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
typo3/sysext/core/Classes/DataHandling/Localization/State.php
typo3/sysext/core/Tests/Unit/DataHandling/Localization/StateTest.php [new file with mode: 0644]

index 9c1026d..fce8365 100644 (file)
@@ -82,7 +82,7 @@ class State
     {
         return array_keys(
             array_filter(
-                $GLOBALS['TCA'][$tableName]['columns'],
+                $GLOBALS['TCA'][$tableName]['columns'] ?? [],
                 function (array $fieldConfiguration) {
                     return !empty(
                         $fieldConfiguration['config']
@@ -139,6 +139,15 @@ class State
     protected $originalStates;
 
     /**
+     * @var array
+     */
+    protected $validStates = [
+        self::STATE_CUSTOM,
+        self::STATE_SOURCE,
+        self::STATE_PARENT,
+    ];
+
+    /**
      * @param string $tableName
      * @param array $states
      */
@@ -148,8 +157,9 @@ class State
         $this->states = $states;
         $this->originalStates = $states;
 
-        $this->states = $this->sanitize($states);
-        $this->states = $this->enrich($states);
+        $this->states = $this->enrich(
+            $this->sanitize($states)
+        );
     }
 
     /**
@@ -311,7 +321,12 @@ class State
     protected function enrich(array $states)
     {
         foreach (static::getFieldNames($this->tableName) as $fieldName) {
-            if (!empty($states[$fieldName])) {
+            $isValid = in_array(
+                $states[$fieldName] ?? null,
+                $this->validStates,
+                true
+            );
+            if ($isValid) {
                 continue;
             }
             $states[$fieldName] = static::STATE_PARENT;
diff --git a/typo3/sysext/core/Tests/Unit/DataHandling/Localization/StateTest.php b/typo3/sysext/core/Tests/Unit/DataHandling/Localization/StateTest.php
new file mode 100644 (file)
index 0000000..3092146
--- /dev/null
@@ -0,0 +1,193 @@
+<?php
+
+namespace TYPO3\CMS\Core\Tests\Unit\DataHandler\Localization;
+
+/*
+ * 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\DataHandling\Localization\State;
+use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
+
+/**
+ * Test case
+ */
+class StateTest extends UnitTestCase
+{
+    const TABLE_NAME = 'tx_test_table';
+
+    /**
+     * Set up the tests
+     */
+    protected function setUp()
+    {
+        $GLOBALS['TCA'] = [];
+    }
+
+    /**
+     * @param string $tableName
+     * @param array $states
+     *
+     * @test
+     * @dataProvider stateObjectCanBeCreatedDataProvider
+     */
+    public function stateObjectCanBeCreated(string $tableName, array $states)
+    {
+        $subject = new State($tableName, $states);
+
+        $this->assertInstanceOf(State::class, $subject);
+    }
+
+    /**
+     * @return array
+     */
+    public function stateObjectCanBeCreatedDataProvider(): array
+    {
+        return [
+            'without states' => [
+                static::TABLE_NAME,
+                [],
+            ],
+            'with states' => [
+                static::TABLE_NAME,
+                ['nonExistingField' => 'invalidState'],
+            ],
+        ];
+    }
+
+    /**
+     * @param array $states
+     * @param array $expected
+     *
+     * @test
+     * @dataProvider statesAreEnrichedAndSanitizedOnObjectCreationDataProvider
+     */
+    public function statesAreEnrichedAndSanitizedOnObjectCreation(
+        array $states,
+        array $expected
+    ) {
+        $GLOBALS['TCA'] = $this->provideTableConfiguration(
+            'first_field',
+            'second_field'
+        );
+
+        $subject = new State(static::TABLE_NAME, $states);
+
+        $this->assertSame(
+            $expected,
+            $subject->toArray()
+        );
+    }
+
+    /**
+     * @return array
+     */
+    public function statesAreEnrichedAndSanitizedOnObjectCreationDataProvider(): array
+    {
+        return [
+            'empty' => [
+                [],
+                [
+                    'first_field' => 'parent',
+                    'second_field' => 'parent',
+                ],
+            ],
+            'invalid field only' => [
+                [
+                    'invalid_field' => 'invalidState',
+                ],
+                [
+                    'first_field' => 'parent',
+                    'second_field' => 'parent',
+                ],
+            ],
+            'first_field only, valid state' => [
+                [
+                    'first_field' => 'custom',
+                ],
+                [
+                    'first_field' => 'custom',
+                    'second_field' => 'parent',
+                ],
+            ],
+            'first_field only, invalid state' => [
+                [
+                    'first_field' => 'invalidState',
+                ],
+                [
+                    'first_field' => 'parent',
+                    'second_field' => 'parent',
+                ],
+            ],
+            'all valid fields, valid states' => [
+                [
+                    'first_field' => 'custom',
+                    'second_field' => 'parent',
+                ],
+                [
+                    'first_field' => 'custom',
+                    'second_field' => 'parent',
+                ],
+            ],
+            'all valid fields, invalid states' => [
+                [
+                    'first_field' => 'invalidState',
+                    'second_field' => 'invalidState',
+                ],
+                [
+                    'first_field' => 'parent',
+                    'second_field' => 'parent',
+                ],
+            ],
+            'all valid fields, valid states and invalid field' => [
+                [
+                    'invalid_field' => 'invalidState',
+                    'first_field' => 'custom',
+                    'second_field' => 'parent',
+                ],
+                [
+                    'first_field' => 'custom',
+                    'second_field' => 'parent',
+                ],
+            ],
+            'all valid fields, invalid states and invalid field' => [
+                [
+                    'invalid_field' => 'invalidState',
+                    'first_field' => 'invalidState',
+                    'second_field' => 'invalidState',
+                ],
+                [
+                    'first_field' => 'parent',
+                    'second_field' => 'parent',
+                ],
+            ],
+        ];
+    }
+
+    /**
+     * @param string[] ...$fieldNames
+     *
+     * @return array
+     */
+    private function provideTableConfiguration(string ...$fieldNames): array
+    {
+        $columnsConfiguration = [];
+        foreach ($fieldNames as $fieldName) {
+            $columnsConfiguration[$fieldName]['config']['behaviour']['allowLanguageSynchronization'] = true;
+        }
+        return [
+            static::TABLE_NAME => [
+                'columns' => $columnsConfiguration,
+            ],
+        ];
+    }
+}