diff --git a/typo3/sysext/core/Classes/Configuration/Parser/PageTsConfigParser.php b/typo3/sysext/core/Classes/Configuration/Parser/PageTsConfigParser.php index a57d6022c5d4f44b7b3450891184e6d0d8e2a9ce..871903bfc616185c1ecdb1cd6e422779777cf4f1 100644 --- a/typo3/sysext/core/Classes/Configuration/Parser/PageTsConfigParser.php +++ b/typo3/sysext/core/Classes/Configuration/Parser/PageTsConfigParser.php @@ -65,7 +65,7 @@ class PageTsConfigParser if ($site) { $siteSettings = $site->getConfiguration()['settings'] ?? []; if (!empty($siteSettings)) { - $siteSettings = ArrayUtility::flatten($siteSettings); + $siteSettings = ArrayUtility::flattenPlain($siteSettings); } if (!empty($siteSettings)) { // Recursive substitution of site settings (up to 10 nested levels) diff --git a/typo3/sysext/core/Classes/TypoScript/Parser/TypoScriptParser.php b/typo3/sysext/core/Classes/TypoScript/Parser/TypoScriptParser.php index b39f7be87997b26ee80d8e2f7f0e846518c64b0d..3ffeb0bba596dfc57881eb5c91b72204b03eb5aa 100644 --- a/typo3/sysext/core/Classes/TypoScript/Parser/TypoScriptParser.php +++ b/typo3/sysext/core/Classes/TypoScript/Parser/TypoScriptParser.php @@ -292,8 +292,8 @@ class TypoScriptParser $objStrName = substr($line, 0, $varL); if ($objStrName !== '') { $r = []; - if (preg_match('/[^[:alnum:]_\\\\\\.:-]/i', $objStrName, $r)) { - $this->error('Line ' . ($this->lineNumberOffset + $this->rawP - 1) . ': Object Name String, "' . htmlspecialchars($objStrName) . '" contains invalid character "' . $r[0] . '". Must be alphanumeric or one of: "_:-\\."'); + if (preg_match('/[^[:alnum:]\\/_\\\\\\.:-]/i', $objStrName, $r)) { + $this->error('Line ' . ($this->lineNumberOffset + $this->rawP - 1) . ': Object Name String, "' . htmlspecialchars($objStrName) . '" contains invalid character "' . $r[0] . '". Must be alphanumeric or one of: "_:-/\\."'); } else { $line = ltrim(substr($line, $varL)); if ($line === '') { diff --git a/typo3/sysext/core/Classes/TypoScript/TemplateService.php b/typo3/sysext/core/Classes/TypoScript/TemplateService.php index 47d120ed65ed9d77bf2b2f47bdcfb6750a0f87bc..e8a268014ad5b9bbece3910bfbee00a4e006d323 100644 --- a/typo3/sysext/core/Classes/TypoScript/TemplateService.php +++ b/typo3/sysext/core/Classes/TypoScript/TemplateService.php @@ -1200,7 +1200,7 @@ class TemplateService if ($site instanceof Site) { $siteSettings = $site->getConfiguration()['settings'] ?? []; if (!empty($siteSettings)) { - $siteSettings = ArrayUtility::flatten($siteSettings); + $siteSettings = ArrayUtility::flattenPlain($siteSettings); foreach ($siteSettings as $k => $v) { $siteConstants .= $k . ' = ' . $v . LF; } diff --git a/typo3/sysext/core/Classes/Utility/ArrayUtility.php b/typo3/sysext/core/Classes/Utility/ArrayUtility.php index b5d87f8110def0dc246e86c56847712887e43c98..746c2b93fc8a1446cefabe9fe523edac873fbd8f 100644 --- a/typo3/sysext/core/Classes/Utility/ArrayUtility.php +++ b/typo3/sysext/core/Classes/Utility/ArrayUtility.php @@ -449,6 +449,8 @@ class ArrayUtility /** * Converts a multidimensional array to a flat representation. + * @todo: The current implementation isn't a generic array flatten method, but tailored for TypoScript flattening + * @todo: It should be deprecated and removed and the required specialities should be put under the domain of TypoScript parsing * * See unit tests for more details * @@ -502,6 +504,33 @@ class ArrayUtility return $flatArray; } + /** + * Just like flatten, but not tailored for TypoScript but for plain simple arrays + * It is internal for now, as it needs to be decided how to deprecate/ rename flatten + * + * @param array $array + * @return array + * @internal + */ + public static function flattenPlain(array $array): array + { + $flattenRecursive = static function (array $array, string $prefix = '') use (&$flattenRecursive) { + $flatArray = []; + foreach ($array as $key => $value) { + $key = addcslashes((string)$key, '.'); + if (!is_array($value)) { + $flatArray[] = [$prefix . $key => $value]; + } else { + $flatArray[] = $flattenRecursive($value, $prefix . $key . '.'); + } + } + + return array_merge(...$flatArray); + }; + + return $flattenRecursive($array); + } + /** * Determine the intersections between two arrays, recursively comparing keys * A complete sub array of $source will be preserved, if the key exists in $mask. diff --git a/typo3/sysext/core/Tests/Unit/TypoScript/Parser/TypoScriptParserTest.php b/typo3/sysext/core/Tests/Unit/TypoScript/Parser/TypoScriptParserTest.php index 03b4b159c581a784a06e298391edba16558d9f2e..cf6e24dc2401b0a253a89c624b51fadefe2cb342 100644 --- a/typo3/sysext/core/Tests/Unit/TypoScript/Parser/TypoScriptParserTest.php +++ b/typo3/sysext/core/Tests/Unit/TypoScript/Parser/TypoScriptParserTest.php @@ -385,7 +385,7 @@ class TypoScriptParserTest extends UnitTestCase $typoScript = '$.10 = invalid'; $this->typoScriptParser->parse($typoScript); - $expected = 'Line 0: Object Name String, "$.10" contains invalid character "$". Must be alphanumeric or one of: "_:-\."'; + $expected = 'Line 0: Object Name String, "$.10" contains invalid character "$". Must be alphanumeric or one of: "_:-/\."'; self::assertEquals($expected, $this->typoScriptParser->errors[0][0]); } @@ -724,6 +724,12 @@ test.TYPO3Forever.TypoScript = 1 'key' => 'value', ], ], + 'simple assignment with slash in key' => [ + 'lib/key = value', + [ + 'lib/key' => 'value', + ], + ], 'simple assignment with escaped dot at the beginning' => [ '\\.key = value', [ diff --git a/typo3/sysext/core/Tests/Unit/Utility/ArrayUtilityTest.php b/typo3/sysext/core/Tests/Unit/Utility/ArrayUtilityTest.php index 079a97f5d5ec5453654e23808b8bbe7b9663bc3c..a977c56239f563d69c336a358616ddc97d89cc00 100644 --- a/typo3/sysext/core/Tests/Unit/Utility/ArrayUtilityTest.php +++ b/typo3/sysext/core/Tests/Unit/Utility/ArrayUtilityTest.php @@ -1366,6 +1366,182 @@ class ArrayUtilityTest extends UnitTestCase self::assertEquals($expected, ArrayUtility::flatten($array)); } + /////////////////////// + // Tests concerning flattenPlain + /////////////////////// + + /** + * @return array + */ + public function flattenPlainCalculatesExpectedResultDataProvider(): array + { + return [ + 'plain array' => [ + [ + 'first' => 1, + 'second' => 2, + ], + [ + 'first' => 1, + 'second' => 2, + ], + ], + 'plain array with trailing dots' => [ + [ + 'first.' => 1, + 'second.' => 2, + ], + [ + 'first\.' => 1, + 'second\.' => 2, + ], + ], + 'nested array of 2 levels' => [ + [ + 'first' => [ + 'firstSub' => 1, + ], + 'second' => [ + 'secondSub' => 2, + ], + ], + [ + 'first.firstSub' => 1, + 'second.secondSub' => 2, + ], + ], + 'nested array of 2 levels with dots in keys' => [ + [ + 'first.el' => [ + 'firstSub.' => 1, + ], + 'second.el' => [ + 'secondSub.' => 2, + ], + ], + [ + 'first\.el.firstSub\.' => 1, + 'second\.el.secondSub\.' => 2, + ], + ], + 'nested array of 2 levels with dots inside keys' => [ + [ + 'first' => [ + 'first.sub' => 1, + ], + 'second' => [ + 'second.sub' => 2, + ], + ], + [ + 'first.first\.sub' => 1, + 'second.second\.sub' => 2, + ], + ], + 'nested array of 3 levels' => [ + [ + 'first' => [ + 'firstSub' => [ + 'firstSubSub' => 1, + ], + ], + 'second' => [ + 'secondSub' => [ + 'secondSubSub' => 2, + ], + ], + ], + [ + 'first.firstSub.firstSubSub' => 1, + 'second.secondSub.secondSubSub' => 2, + ], + ], + 'nested array of 3 levels with dots in keys' => [ + [ + 'first.' => [ + 'firstSub.' => [ + 'firstSubSub.' => 1, + ], + ], + 'second.' => [ + 'secondSub.' => [ + 'secondSubSub.' => 2, + ], + ], + ], + [ + 'first\..firstSub\..firstSubSub\.' => 1, + 'second\..secondSub\..secondSubSub\.' => 2, + ], + ], + 'duplicate keys, one with dot, one without' => [ + [ + 'foo' => 'node', + 'foo.' => [ + 'bar' => 'bla', + ], + ], + [ + 'foo' => 'node', + 'foo\..bar' => 'bla', + ], + ], + 'duplicate keys, one with dot with scalar value, one without, last wins' => [ + [ + 'foo.' => 'dot', + 'foo' => 'node', + ], + [ + 'foo\.' => 'dot', + 'foo' => 'node', + ], + ], + 'empty key' => [ + [ + '' => 'node', + ], + [ + '' => 'node', + ], + ], + 'dot key' => [ + [ + '.' => 'node', + ], + [ + '\.' => 'node', + ], + ], + 'empty array' => [ + [], + [], + ], + 'nested lists' => [ + [ + ['foo', 'bar'], + ['bla', 'baz'], + ], + [ + '0.0' => 'foo', + '0.1' => 'bar', + '1.0' => 'bla', + '1.1' => 'baz', + ], + ], + ]; + } + + /** + * @test + * @param array $array + * @param array $expected + * @dataProvider flattenPlainCalculatesExpectedResultDataProvider + */ + public function flattenPlainCalculatesExpectedResult(array $array, array $expected): void + { + self::assertEquals($expected, ArrayUtility::flattenPlain($array)); + } + /** * @return array */ diff --git a/typo3/sysext/fluid/Classes/ViewHelpers/Be/Menus/ActionMenuItemViewHelper.php b/typo3/sysext/fluid/Classes/ViewHelpers/Be/Menus/ActionMenuItemViewHelper.php index 4a50551ec19254258cb5475001c1cba468b00916..d46439d732f28879f2a6b4f519646fc807f360b1 100644 --- a/typo3/sysext/fluid/Classes/ViewHelpers/Be/Menus/ActionMenuItemViewHelper.php +++ b/typo3/sysext/fluid/Classes/ViewHelpers/Be/Menus/ActionMenuItemViewHelper.php @@ -103,13 +103,13 @@ class ActionMenuItemViewHelper extends AbstractTagBasedViewHelper protected function evaluateSelectItemState(string $controller, string $action, array $arguments): void { $currentRequest = $this->renderingContext->getRequest(); - $flatRequestArguments = ArrayUtility::flatten( + $flatRequestArguments = ArrayUtility::flattenPlain( array_merge([ 'controller' => $currentRequest->getControllerName(), 'action' => $currentRequest->getControllerActionName(), ], $currentRequest->getArguments()) ); - $flatViewHelperArguments = ArrayUtility::flatten( + $flatViewHelperArguments = ArrayUtility::flattenPlain( array_merge(['controller' => $controller, 'action' => $action], $arguments) ); if ( diff --git a/typo3/sysext/form/Classes/Domain/Configuration/ArrayProcessing/ArrayProcessor.php b/typo3/sysext/form/Classes/Domain/Configuration/ArrayProcessing/ArrayProcessor.php index 64bee86bff5e05721d83cc1f38d566936f10cfce..aa884b92f65bf5b64fb31600d422b75620fd5ad4 100644 --- a/typo3/sysext/form/Classes/Domain/Configuration/ArrayProcessing/ArrayProcessor.php +++ b/typo3/sysext/form/Classes/Domain/Configuration/ArrayProcessing/ArrayProcessor.php @@ -39,7 +39,7 @@ class ArrayProcessor */ public function __construct(array $data) { - $this->data = ArrayUtility::flatten($data); + $this->data = ArrayUtility::flattenPlain($data); } /**