[FEATURE] TS-list: allow to unique, reverse and sort 39/17939/9
authorStefan Neufeind <typo3.neufeind@speedpartner.de>
Sat, 2 Feb 2013 03:43:02 +0000 (04:43 +0100)
committerChristian Kuhn <lolli@schwarzbu.ch>
Sun, 31 Mar 2013 13:02:30 +0000 (15:02 +0200)
Add missing, basic list-functionality for TypoScript.
Also splits out the TS-operator-functions into a
separate function and adds unit-tests.

Resolves: #45091
Releases: 6.1
Change-Id: I2ff425845681494053dc56b785ebd0064f100ed6
Reviewed-on: https://review.typo3.org/17939
Reviewed-by: Philipp Gampe
Tested-by: Philipp Gampe
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..a7edc7d 100644 (file)
@@ -373,45 +373,12 @@ class TypoScriptParser {
                                                                }
                                                                // Checking for special TSparser properties (to change TS values at parsetime)
                                                                $match = array();
-                                                               if (preg_match('/^:=([^\\(]+)\\((.+)\\).*/', $line, $match)) {
+                                                               if (preg_match('/^:=([^\\(]+)\\((.*)\\).*/', $line, $match)) {
                                                                        $tsFunc = trim($match[1]);
                                                                        $tsFuncArg = $match[2];
                                                                        list($currentValue) = $this->getVal($objStrName, $setup);
                                                                        $tsFuncArg = str_replace(array('\\\\', '\\n', '\\t'), array('\\', LF, TAB), $tsFuncArg);
-                                                                       switch ($tsFunc) {
-                                                                       case 'prependString':
-                                                                               $newValue = $tsFuncArg . $currentValue;
-                                                                               break;
-                                                                       case 'appendString':
-                                                                               $newValue = $currentValue . $tsFuncArg;
-                                                                               break;
-                                                                       case 'removeString':
-                                                                               $newValue = str_replace($tsFuncArg, '', $currentValue);
-                                                                               break;
-                                                                       case 'replaceString':
-                                                                               list($fromStr, $toStr) = explode('|', $tsFuncArg, 2);
-                                                                               $newValue = str_replace($fromStr, $toStr, $currentValue);
-                                                                               break;
-                                                                       case 'addToList':
-                                                                               $newValue = (strcmp('', $currentValue) ? $currentValue . ',' : '') . trim($tsFuncArg);
-                                                                               break;
-                                                                       case 'removeFromList':
-                                                                               $existingElements = \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode(',', $currentValue);
-                                                                               $removeElements = \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode(',', $tsFuncArg);
-                                                                               if (count($removeElements)) {
-                                                                                       $newValue = implode(',', array_diff($existingElements, $removeElements));
-                                                                               }
-                                                                               break;
-                                                                       default:
-                                                                               if (isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsparser.php']['preParseFunc'][$tsFunc])) {
-                                                                                       $hookMethod = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsparser.php']['preParseFunc'][$tsFunc];
-                                                                                       $params = array('currentValue' => $currentValue, 'functionArgument' => $tsFuncArg);
-                                                                                       $fakeThis = FALSE;
-                                                                                       $newValue = \TYPO3\CMS\Core\Utility\GeneralUtility::callUserFunction($hookMethod, $params, $fakeThis);
-                                                                               } else {
-                                                                                       \TYPO3\CMS\Core\Utility\GeneralUtility::sysLog('Missing function definition for ' . $tsFunc . ' on TypoScript line ' . $lineP, 'Core', \TYPO3\CMS\Core\Utility\GeneralUtility::SYSLOG_SEVERITY_WARNING);
-                                                                               }
-                                                                       }
+                                                                       $newValue = $this->executeValueModifier($tsFunc, $tsFuncArg, $currentValue);
                                                                        if (isset($newValue)) {
                                                                                $line = '= ' . $newValue;
                                                                        }
@@ -520,6 +487,80 @@ class TypoScriptParser {
        }
 
        /**
+        * Executes operator functions, called from TypoScript
+        * example: page.10.value := appendString(!)
+        *
+        * @param string $modifierName TypoScript function called
+        * @param string $modifierArgument Function arguments; In case of multiple arguments, the method must split on its own
+        * @param string $currentValue Current TypoScript value
+        * @return string Modification result
+        */
+       protected function executeValueModifier($modifierName, $modifierArgument = NULL, $currentValue = NULL) {
+               $newValue = NULL;
+               switch ($modifierName) {
+                       case 'prependString':
+                               $newValue = $modifierArgument . $currentValue;
+                               break;
+                       case 'appendString':
+                               $newValue = $currentValue . $modifierArgument;
+                               break;
+                       case 'removeString':
+                               $newValue = str_replace($modifierArgument, '', $currentValue);
+                               break;
+                       case 'replaceString':
+                               list($fromStr, $toStr) = explode('|', $modifierArgument, 2);
+                               $newValue = str_replace($fromStr, $toStr, $currentValue);
+                               break;
+                       case 'addToList':
+                               $newValue = (strcmp('', $currentValue) ? $currentValue . ',' : '') . trim($modifierArgument);
+                               break;
+                       case 'removeFromList':
+                               $existingElements = \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode(',', $currentValue);
+                               $removeElements = \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode(',', $modifierArgument);
+                               if (count($removeElements)) {
+                                       $newValue = implode(',', array_diff($existingElements, $removeElements));
+                               }
+                               break;
+                       case 'uniqueList':
+                               $elements = \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode(',', $currentValue);
+                               $newValue = implode(',', array_unique($elements));
+                               break;
+                       case 'reverseList':
+                               $elements = \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode(',', $currentValue);
+                               $newValue = implode(',', array_reverse($elements));
+                               break;
+                       case 'sortList':
+                               $elements = \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode(',', $currentValue);
+                               $arguments = \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode(',', $modifierArgument);
+                               $arguments = array_map('strtolower', $arguments);
+                               $sort_flags = SORT_REGULAR;
+                               if (in_array('numeric', $arguments)) {
+                                       $sort_flags = SORT_NUMERIC;
+                               }
+                               sort($elements, $sort_flags);
+                               if (in_array('descending', $arguments)) {
+                                       $elements = array_reverse($elements);
+                               }
+                               $newValue = implode(',', $elements);
+                               break;
+                       default:
+                               if (isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsparser.php']['preParseFunc'][$modifierName])) {
+                                       $hookMethod = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsparser.php']['preParseFunc'][$modifierName];
+                                       $params = array('currentValue' => $currentValue, 'functionArgument' => $modifierArgument);
+                                       $fakeThis = FALSE;
+                                       $newValue = \TYPO3\CMS\Core\Utility\GeneralUtility::callUserFunction($hookMethod, $params, $fakeThis);
+                               } else {
+                                       \TYPO3\CMS\Core\Utility\GeneralUtility::sysLog(
+                                               'Missing function definition for ' . $modifierName . ' on TypoScript',
+                                               'Core',
+                                               \TYPO3\CMS\Core\Utility\GeneralUtility::SYSLOG_SEVERITY_WARNING
+                                       );
+                               }
+               }
+               return $newValue;
+       }
+
+       /**
         * Parsing of TypoScript keys inside a curly brace where the key is composite of at least two keys, thus having to recursively call itself to get the value
         *
         * @param string $string The object sub-path, eg "thisprop.another_prot
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..ff50247
--- /dev/null
@@ -0,0 +1,246 @@
+<?php
+namespace TYPO3\CMS\Core\Tests\Unit\TypoScript\Parser;
+
+/***************************************************************
+ * Copyright notice
+ *
+ * (c) 2013 Stefan Neufeind <info (at) speedpartner.de>
+ * 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 2 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!
+ ***************************************************************/
+
+/**
+ * Test case
+ */
+class TypoScriptParserTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
+
+       /**
+        * @var \TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser|\PHPUnit_Framework_MockObject_MockObject|\TYPO3\CMS\Core\Tests\AccessibleObjectInterface
+        */
+       protected $typoScriptParser;
+
+       /**
+        * Set up
+        *
+        * @return void
+        */
+       protected function setUp() {
+               $this->typoScriptParser = $this->getAccessibleMock(
+                       'TYPO3\\CMS\\Core\\TypoScript\\Parser\\TypoScriptParser', array('dummy'), array(), '', FALSE
+               );
+       }
+
+       /**
+        * Tear down
+        *
+        * @return void
+        */
+       protected function tearDown() {
+               unset($this->typoScriptParser);
+       }
+
+       /**
+        * Data provider for executeValueModifierReturnsModifiedResult
+        *
+        * @return array modifier name, modifier arguments, current value, expected result
+        */
+       public function executeValueModifierDataProvider() {
+               return array(
+                       'prependString with string' => array(
+                               'prependString',
+                               'abc',
+                               '!',
+                               '!abc'
+                       ),
+                       'prependString with empty string' => array(
+                               'prependString',
+                               'foo',
+                               '',
+                               'foo',
+                       ),
+                       'appendString with string' => array(
+                               'appendString',
+                               'abc',
+                               '!',
+                               'abc!',
+                       ),
+                       'appendString with empty string' => array(
+                               'appendString',
+                               'abc',
+                               '',
+                               'abc',
+                       ),
+                       'removeString removes simple string' => array(
+                               'removeString',
+                               'abcdef',
+                               'bc',
+                               'adef',
+                       ),
+                       'removeString removes nothing if no match' => array(
+                               'removeString',
+                               'abcdef',
+                               'foo',
+                               'abcdef',
+                       ),
+                       'removeString removes multiple matches' => array(
+                               'removeString',
+                               'FooBarFoo',
+                               'Foo',
+                               'Bar',
+                       ),
+                       'replaceString replaces simple match' => array(
+                               'replaceString',
+                               'abcdef',
+                               'bc|123',
+                               'a123def',
+                       ),
+                       'replaceString replaces simple match with nothing' => array(
+                               'replaceString',
+                               'abcdef',
+                               'bc',
+                               'adef',
+                       ),
+                       'replaceString replaces multiple matches' => array(
+                               'replaceString',
+                               'FooBarFoo',
+                               'Foo|Bar',
+                               'BarBarBar',
+                       ),
+                       'addToList adds at end of existing list' => array(
+                               'addToList',
+                               '123,456',
+                               '789',
+                               '123,456,789',
+                       ),
+                       'addToList adds nothing' => array(
+                               'addToList',
+                               '123,456',
+                               '',
+                               '123,456,', // This result is probably not what we want (appended comma) ... fix it?
+                       ),
+                       'addToList adds to empty list' => array(
+                               'addToList',
+                               '',
+                               'foo',
+                               'foo',
+                       ),
+                       'removeFromList removes value from list' => array(
+                               'removeFromList',
+                               '123,456,789,abc',
+                               '456',
+                               '123,789,abc',
+                       ),
+                       'removeFromList removes value at beginning of list' => array(
+                               'removeFromList',
+                               '123,456,abc',
+                               '123',
+                               '456,abc',
+                       ),
+                       'removeFromList removes value at end of list' => array(
+                               'removeFromList',
+                               '123,456,abc',
+                               'abc',
+                               '123,456',
+                       ),
+                       'removeFromList removes multiple values from list' => array(
+                               'removeFromList',
+                               'foo,123,bar,123',
+                               '123',
+                               'foo,bar',
+                       ),
+                       'removeFromList removes empty values' => array(
+                               'removeFromList',
+                               'foo,,bar',
+                               '',
+                               'foo,bar',
+                       ),
+                       'uniqueList removes duplicates' => array(
+                               'uniqueList',
+                               '123,456,abc,456,456',
+                               '',
+                               '123,456,abc',
+                       ),
+                       'uniqueList removes duplicate empty list values' => array(
+                               'uniqueList',
+                               '123,,456,,abc',
+                               '',
+                               '123,,456,abc',
+                       ),
+                       'reverseList returns list reversed' => array(
+                               'reverseList',
+                               '123,456,abc,456',
+                               '',
+                               '456,abc,456,123',
+                       ),
+                       'reverseList keeps empty values' => array(
+                               'reverseList',
+                               ',123,,456,abc,,456',
+                               '',
+                               '456,,abc,456,,123,',
+                       ),
+                       'reverseList does not change single element' => array(
+                               'reverseList',
+                               '123',
+                               '',
+                               '123',
+                       ),
+                       'sortList sorts a list' => array(
+                               'sortList',
+                               '10,100,0,20,abc',
+                               '',
+                               '0,10,20,100,abc',
+                       ),
+                       'sortList sorts a list numeric' => array(
+                               'sortList',
+                               '10,0,100,-20,abc',
+                               'numeric',
+                               '-20,0,abc,10,100',
+                       ),
+                       'sortList sorts a list descending' => array(
+                               'sortList',
+                               '10,100,0,20,abc,-20',
+                               'descending',
+                               'abc,100,20,10,0,-20',
+                       ),
+                       'sortList sorts a list numeric descending' => array(
+                               'sortList',
+                               '10,100,0,20,abc,-20',
+                               'descending,numeric',
+                               '100,20,10,0,abc,-20',
+                       ),
+                       'sortList ignores invalid modifier arguments' => array(
+                               'sortList',
+                               '10,100,20',
+                               'foo,descending,bar',
+                               '100,20,10',
+                       ),
+               );
+       }
+
+       /**
+        * @test
+        * @dataProvider executeValueModifierDataProvider
+        */
+       public function executeValueModifierReturnsModifiedResult($modifierName, $currentValue, $modifierArgument, $expected) {
+               $actualValue = $this->typoScriptParser->_call('executeValueModifier', $modifierName, $modifierArgument, $currentValue);
+               $this->assertEquals($expected, $actualValue);
+       }
+
+}
+
+?>
\ No newline at end of file