[BUGFIX] TSConfig for fields with a dot in the name does not work 11/7711/19
authorKai Vogel <kai.vogel@speedprogs.de>
Mon, 9 Jan 2012 20:29:48 +0000 (21:29 +0100)
committerChristian Kuhn <lolli@schwarzbu.ch>
Mon, 25 Mar 2013 18:27:29 +0000 (19:27 +0100)
This patch enables the use of double quotes in TypoScript setup paths.
Thus, for example, can Extbase field names be separated using quotes in
TSConfig.

Example:

TCEFORM.tt_content.pi_flexform.myext_myplugin.sDEF {
  "settings.fieldOne".config.type = text
  "settings.fieldTwo" = foo
}

Fixes: #29461
Releases: 6.1
Change-Id: Idf30faff9c45c662b2c2f976aeda5cc407e9610a
Reviewed-on: https://review.typo3.org/7711
Reviewed-by: Wouter Wolters
Tested-by: Wouter Wolters
Reviewed-by: Eric Chavaillaz
Tested-by: Eric Chavaillaz
Reviewed-by: Stefan Neufeind
Tested-by: Stefan Neufeind
Reviewed-by: Christian Kuhn
Tested-by: Christian Kuhn
typo3/sysext/core/Classes/TypoScript/Parser/TypoScriptParser.php
typo3/sysext/core/Tests/Unit/TypoScript/Parser/TypoScriptParserTest.php [new file with mode: 0644]

index 4c03873..dbff0f9 100644 (file)
@@ -5,6 +5,7 @@ namespace TYPO3\CMS\Core\TypoScript\Parser;
  *  Copyright notice
  *
  *  (c) 1999-2013 Kasper Skårhøj (kasperYYYY@typo3.com)
+ *      2012-2013 Kai Vogel <kai.vogel@speedprogs.de>, Speedprogs.de
  *  All rights reserved
  *
  *  This script is part of the TYPO3 project. The TYPO3 project is
@@ -41,7 +42,7 @@ namespace TYPO3\CMS\Core\TypoScript\Parser;
  */
 class TypoScriptParser {
 
-       // If set, then key names cannot contain characters other than [:alnum:]_\.-
+       // If set, then key names cannot contain characters other than [:alnum:]_\.\"-
        /**
         * @todo Define visibility
         */
@@ -353,15 +354,20 @@ class TypoScriptParser {
                                        } elseif (strcspn($line, '}#/') != 0) {
                                                // If not brace-end or comment
                                                // Find object name string until we meet an operator
-                                               $varL = strcspn($line, ' {=<>:(');
+                                               $operatorMask = ' {=<>:(';
+                                               $varL = strcspn($line, $operatorMask);
+                                               if (preg_match('/[^"]*"[^"]*"[^\s\{=<>:\(]*/', $line, $matches)) {
+                                                       $matchLength = strlen($matches[0]);
+                                                       $varL = $matchLength + strcspn($line, $operatorMask, $matchLength);
+                                               }
                                                $objStrName = trim(substr($line, 0, $varL));
                                                if ($this->syntaxHighLight) {
                                                        $this->regHighLight('objstr', $lineP, strlen(substr($line, $varL)));
                                                }
                                                if (strlen($objStrName)) {
                                                        $r = array();
-                                                       if ($this->strict && 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 ($this->strict && 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 ($this->syntaxHighLight) {
@@ -426,6 +432,7 @@ class TypoScriptParser {
                                                                                $value[0] = trim(substr($line, 1));
                                                                                $this->setVal($objStrName, $setup, $value);
                                                                        } else {
+                                                                               $objStrName = trim($objStrName, '"');
                                                                                $setup[$objStrName] = trim(substr($line, 1));
                                                                                if ($this->lastComment && $this->regComments) {
                                                                                        // Setting comment..
@@ -590,17 +597,19 @@ class TypoScriptParser {
         * @param array $setup The local setup array from the function calling this function.
         * @param array $value The value/property pair array to set. If only one of them is set, then the other is not touched (unless $wipeOut is set, which it is when copies are made which must include both value and property)
         * @param boolean $wipeOut If set, then both value and property is wiped out when a copy is made of another value.
+        * @param boolean $forceValue If set, the value will be set even if the key string contains dots
         * @return void
         * @todo Define visibility
         */
-       public function setVal($string, &$setup, $value, $wipeOut = 0) {
+       public function setVal($string, &$setup, $value, $wipeOut = 0, $forceValue = FALSE) {
                if ((string) $string != '') {
                        $keyLen = strcspn($string, '.');
-                       if ($keyLen == strlen($string)) {
+                       if ($forceValue || $keyLen == strlen($string)) {
                                if ($value == 'UNSET') {
                                        unset($setup[$string]);
                                        unset($setup[$string . '.']);
                                        if ($this->regLinenumbers) {
+                                               $string = trim($string, '"');
                                                $setup[$string . '.ln..'][] = ($this->lineNumberOffset + $this->rawP - 1) . '>';
                                        }
                                } else {
@@ -609,10 +618,12 @@ class TypoScriptParser {
                                                unset($setup[$string]);
                                                unset($setup[$string . '.']);
                                                if ($this->regLinenumbers) {
+                                                       $string = trim($string, '"');
                                                        $setup[$string . '.ln..'][] = ($this->lineNumberOffset + $this->rawP - 1) . '<';
                                                        $lnRegisDone = 1;
                                                }
                                        }
+                                       $string = trim($string, '"');
                                        if (isset($value[0])) {
                                                $setup[$string] = $value[0];
                                        }
@@ -626,6 +637,20 @@ class TypoScriptParser {
                                                $setup[$string . '.ln..'][] = $this->lineNumberOffset + $this->rawP - 1;
                                        }
                                }
+                       } elseif (substr($string, 0, 1) === '"' && substr_count($string, '"') > 1 && substr_count($string, '.')) {
+                               // Key string contains quoted part, e.g.: myext."settings.myfield".config
+                               $key = substr($string, 1, strpos($string, '"', 1) - 1);
+                               if (trim($string, '".') === $key) {
+                                       // Key ends with quotes, so ignore dots in key and set value
+                                       $this->setVal($key, $setup, $value, $wipeOut, TRUE);
+                               } else {
+                                       // Continue with next key part
+                                       $key .= '.';
+                                       if (!isset($setup[$key])) {
+                                               $setup[$key] = array();
+                                       }
+                                       $this->setVal(substr($string, strlen($key) + 2), $setup[$key], $value);
+                               }
                        } else {
                                $key = substr($string, 0, $keyLen) . '.';
                                if (!isset($setup[$key])) {
diff --git a/typo3/sysext/core/Tests/Unit/TypoScript/Parser/TypoScriptParserTest.php b/typo3/sysext/core/Tests/Unit/TypoScript/Parser/TypoScriptParserTest.php
new file mode 100644 (file)
index 0000000..57e6f56
--- /dev/null
@@ -0,0 +1,287 @@
+<?php
+namespace TYPO3\CMS\Core\Tests\Unit\TypoScript\Parser;
+
+/***********************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2012 Kai Vogel <kai.vogel@speedprogs.de>, Speedprogs.de
+ *      2012 Oliver Hader <oliver.hader@typo3.org>
+ *
+ *  All rights reserved
+ *
+ *  This script is part of the TYPO3 project. The TYPO3 project is
+ *  free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  The GNU General Public License can be found at
+ *  http://www.gnu.org/copyleft/gpl.html.
+ *
+ *  This script is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  This copyright notice MUST APPEAR in all copies of the script!
+ **********************************************************************/
+
+/**
+ * Testcase for \TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser
+ *
+ * @author Kai Vogel <kai.vogel@speedprogs.de>
+ * @author Oliver Hader <oliver.hader@typo3.org>
+ */
+class TypoScriptParserTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
+
+       /**
+        * @var \TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser
+        */
+       protected $fixture;
+
+       /**
+        * Sets up the test cases.
+        */
+       protected function setUp() {
+               $this->fixture = new \TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser();
+       }
+
+       /**
+        * Tears down the test cases.
+        */
+       protected function tearDown() {
+               unset($this->fixture);
+       }
+
+       /**
+        * @param string $typoScript
+        * @param array $expected
+        * @dataProvider typoScriptIsParsedToArrayDataProvider
+        * @test
+        */
+       public function typoScriptIsParsedToArray($typoScript, array $expected) {
+               $this->fixture->parse($typoScript);
+               $this->assertEquals($expected, $this->fixture->setup);
+       }
+
+       /**
+        * @param string $typoScript
+        * @param array $expected
+        * @dataProvider typoScriptIsParsedToArrayDataProvider
+        * @test
+        */
+       public function typoScriptIsStrictlyParsedToArray($typoScript, array $expected) {
+               $this->fixture->strict = TRUE;
+               $this->fixture->parse($typoScript);
+               $this->assertEquals($expected, $this->fixture->setup);
+       }
+
+       /**
+        * @return array
+        */
+       public function typoScriptIsParsedToArrayDataProvider() {
+               return array(
+                       'simple assignment' => array(
+                               'key = value',
+                               array(
+                                       'key' => 'value',
+                               )
+                       ),
+                       'nested assignment' => array(
+                               'lib.key = value',
+                               array(
+                                       'lib.' => array(
+                                               'key' => 'value',
+                                       ),
+                               ),
+                       ),
+                       'nested structured assignment' => array(
+                               'lib {' . LF .
+                                       'key = value' . LF .
+                               '}',
+                               array(
+                                       'lib.' => array(
+                                               'key' => 'value',
+                                       ),
+                               ),
+                       ),
+                       'multiline assignment' => array(
+                               'key (' . LF .
+                                       'first' . LF .
+                                       'second' . LF .
+                               ')',
+                               array(
+                                       'key' => 'first' . LF . 'second',
+                               ),
+                       ),
+                       'copying values' => array(
+                               'lib.default = value' . LF .
+                               'lib.copy < lib.default',
+                               array(
+                                       'lib.' => array(
+                                               'default' => 'value',
+                                               'copy' => 'value',
+                                       ),
+                               ),
+                       ),
+                       'one-line hash comment' => array(
+                               'first = 1' . LF .
+                               '# ignore = me' . LF .
+                               'second = 2',
+                               array(
+                                       'first' => '1',
+                                       'second' => '2',
+                               ),
+                       ),
+                       'one-line slash comment' => array(
+                               'first = 1' . LF .
+                               '// ignore = me' . LF .
+                               'second = 2',
+                               array(
+                                       'first' => '1',
+                                       'second' => '2',
+                               ),
+                       ),
+                       'multi-line slash comment' => array(
+                               'first = 1' . LF .
+                               '/*' . LF .
+                                       'ignore = me' . LF .
+                               '*/' . LF .
+                               'second = 2',
+                               array(
+                                       'first' => '1',
+                                       'second' => '2',
+                               ),
+                       ),
+               );
+       }
+
+       /**
+        * @param string $typoScript
+        * @param array $expected
+        * @dataProvider typoScriptWithQuotedKeysIsParsedToArrayDataProvider
+        * @test
+        */
+       public function typoScriptWithQuotedKeysIsParsedToArray($typoScript, array $expected) {
+               $this->fixture->parse($typoScript);
+               $this->assertEquals($expected, $this->fixture->setup);
+       }
+
+       /**
+        * @param string $typoScript
+        * @param array $expected
+        * @dataProvider typoScriptWithQuotedKeysIsParsedToArrayDataProvider
+        * @test
+        */
+       public function typoScriptWithQuotedKeysIsStrictlyParsedToArray($typoScript, array $expected) {
+               $this->fixture->strict = TRUE;
+               $this->fixture->parse($typoScript);
+               $this->assertEquals($expected, $this->fixture->setup);
+       }
+
+       /**
+        * @return array
+        */
+       public function typoScriptWithQuotedKeysIsParsedToArrayDataProvider() {
+               return array(
+                       'quoted key with regular characters' => array(
+                               '"key" = value',
+                               array(
+                                       'key' => 'value',
+                               ),
+                       ),
+                       'nested quoted key with regular characters' => array(
+                               'lib."key" = value',
+                               array(
+                                       'lib.' => array(
+                                               'key' => 'value',
+                                       ),
+                               ),
+                       ),
+                       'nested structured quoted key with regular characters' => array(
+                               'lib {' . LF .
+                                       '"key" = value' . LF .
+                               '}',
+                               array(
+                                       'lib.' => array(
+                                               'key' => 'value',
+                                       ),
+                               ),
+                       ),
+                       'quoted key containing dash' => array(
+                               '"the-key" = value',
+                               array(
+                                       'the-key' => 'value',
+                               ),
+                       ),
+                       'nested quoted key containing dash' => array(
+                               'lib."the-key" = value',
+                               array(
+                                       'lib.' => array(
+                                               'the-key' => 'value',
+                                       ),
+                               ),
+                       ),
+                       'nested structured quoted key containing dash' => array(
+                               'lib {' . LF .
+                                       '"the-key" = value' . LF .
+                               '}',
+                               array(
+                                       'lib.' => array(
+                                               'the-key' => 'value',
+                                       ),
+                               ),
+                       ),
+                       'quoted key containing dot' => array(
+                               '"the.key" = value',
+                               array(
+                                       'the.key' => 'value',
+                               ),
+                       ),
+                       'nested quoted key containing dot' => array(
+                               'lib."the.key" = value',
+                               array(
+                                       'lib.' => array(
+                                               'the.key' => 'value',
+                                       ),
+                               ),
+                       ),
+                       'nested structured quoted key containing dot' => array(
+                               'lib {' . LF .
+                                       '"the.key" = value' . LF .
+                               '}',
+                               array(
+                                       'lib.' => array(
+                                               'the.key' => 'value',
+                                       ),
+                               ),
+                       ),
+                       'quoted key containing equal operator' => array(
+                               '"the=key" = value',
+                               array(
+                                       'the=key' => 'value',
+                               ),
+                       ),
+                       'nested quoted key containing equal operator' => array(
+                               'lib."the=key" = value',
+                               array(
+                                       'lib.' => array(
+                                               'the=key' => 'value',
+                                       ),
+                               ),
+                       ),
+                       'nested structured quoted key containing equal operator' => array(
+                               'lib {' . LF .
+                                       '"the=key" = value' . LF .
+                               '}',
+                               array(
+                                       'lib.' => array(
+                                               'the=key' => 'value',
+                                       ),
+                               ),
+                       ),
+               );
+       }
+
+}
+?>
\ No newline at end of file