From 021d393ee0c3bbf748669487734f14562e84d959 Mon Sep 17 00:00:00 2001
From: Oliver Hader <oliver@typo3.org>
Date: Fri, 8 Sep 2017 12:43:03 +0200
Subject: [PATCH] [BUGFIX] Fix multi-checkbox/radiobox labels for values
 containing dots

If checkbox/radiobox values contain dots, these are falsely evaluated as
array lookup paths. Fix this by passing in separate path segments instead
which may contain dots.

Resolves: #82210
Releases: master, 8.7
Change-Id: Ib3d0d1abbeb4fdf84da427f6bea0d597ba9aade6
Reviewed-on: https://review.typo3.org/54007
Reviewed-by: Daniel Lorenz <daniel.lorenz@extco.de>
Tested-by: Daniel Lorenz <daniel.lorenz@extco.de>
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Stefan Neufeind <typo3.neufeind@speedpartner.de>
Tested-by: Stefan Neufeind <typo3.neufeind@speedpartner.de>
---
 .../Classes/Service/TranslationService.php    | 13 +++---
 .../TranslateElementPropertyViewHelper.php    | 40 +++++++++++++++++--
 .../Frontend/Partials/MultiCheckbox.html      |  2 +-
 .../Frontend/Partials/RadioButton.html        |  2 +-
 .../Unit/Service/TranslationServiceTest.php   | 24 +++++------
 5 files changed, 58 insertions(+), 23 deletions(-)

diff --git a/typo3/sysext/form/Classes/Service/TranslationService.php b/typo3/sysext/form/Classes/Service/TranslationService.php
index 989c297e3b13..79d36dc10079 100644
--- a/typo3/sysext/form/Classes/Service/TranslationService.php
+++ b/typo3/sysext/form/Classes/Service/TranslationService.php
@@ -272,7 +272,7 @@ class TranslationService implements SingletonInterface
 
     /**
      * @param RootRenderableInterface $element
-     * @param string $property
+     * @param array $propertyParts
      * @param FormRuntime $formRuntime
      * @return string|array
      * @throws \InvalidArgumentException
@@ -280,14 +280,15 @@ class TranslationService implements SingletonInterface
      */
     public function translateFormElementValue(
         RootRenderableInterface $element,
-        string $property,
+        array $propertyParts,
         FormRuntime $formRuntime
     ) {
-        if (empty($property)) {
-            throw new \InvalidArgumentException('The argument "property" is empty', 1476216007);
+        if (empty($propertyParts)) {
+            throw new \InvalidArgumentException('The argument "propertyParts" is empty', 1476216007);
         }
 
         $propertyType = 'properties';
+        $property = implode('.', $propertyParts);
         $renderingOptions = $element->getRenderingOptions();
 
         if ($property === 'label') {
@@ -295,14 +296,14 @@ class TranslationService implements SingletonInterface
         } else {
             if ($element instanceof FormElementInterface) {
                 try {
-                    $defaultValue = ArrayUtility::getValueByPath($element->getProperties(), $property, '.');
+                    $defaultValue = ArrayUtility::getValueByPath($element->getProperties(), $propertyParts, '.');
                 } catch (\RuntimeException $exception) {
                     $defaultValue = null;
                 }
             } else {
                 $propertyType = 'renderingOptions';
                 try {
-                    $defaultValue = ArrayUtility::getValueByPath($renderingOptions, $property, '.');
+                    $defaultValue = ArrayUtility::getValueByPath($renderingOptions, $propertyParts, '.');
                 } catch (\RuntimeException $exception) {
                     $defaultValue = null;
                 }
diff --git a/typo3/sysext/form/Classes/ViewHelpers/TranslateElementPropertyViewHelper.php b/typo3/sysext/form/Classes/ViewHelpers/TranslateElementPropertyViewHelper.php
index 3e401d111ef6..e3544ed3b59f 100644
--- a/typo3/sysext/form/Classes/ViewHelpers/TranslateElementPropertyViewHelper.php
+++ b/typo3/sysext/form/Classes/ViewHelpers/TranslateElementPropertyViewHelper.php
@@ -20,6 +20,7 @@ use TYPO3\CMS\Form\Domain\Model\Renderable\RootRenderableInterface;
 use TYPO3\CMS\Form\Domain\Runtime\FormRuntime;
 use TYPO3\CMS\Form\Service\TranslationService;
 use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
+use TYPO3Fluid\Fluid\Core\ViewHelper\Exception;
 use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic;
 
 /**
@@ -41,8 +42,8 @@ class TranslateElementPropertyViewHelper extends AbstractViewHelper
     {
         parent::initializeArguments();
         $this->registerArgument('element', RootRenderableInterface::class, 'Form Element to translate', true);
-        $this->registerArgument('property', 'string', 'Property to translate', false);
-        $this->registerArgument('renderingOptionProperty', 'string', 'Property to translate', false);
+        $this->registerArgument('property', 'mixed', 'Property to translate', false);
+        $this->registerArgument('renderingOptionProperty', 'mixed', 'Property to translate', false);
     }
 
     /**
@@ -56,6 +57,8 @@ class TranslateElementPropertyViewHelper extends AbstractViewHelper
      */
     public static function renderStatic(array $arguments, \Closure $renderChildrenClosure, RenderingContextInterface $renderingContext)
     {
+        static::assertArgumentTypes($arguments);
+
         $element = $arguments['element'];
 
         $property = null;
@@ -65,11 +68,42 @@ class TranslateElementPropertyViewHelper extends AbstractViewHelper
             $property = $arguments['renderingOptionProperty'];
         }
 
+        if (empty($property)) {
+            $propertyParts = [];
+        } elseif (is_array($property)) {
+            $propertyParts = $property;
+        } else {
+            $propertyParts = [$property];
+        }
+
         /** @var FormRuntime $formRuntime */
         $formRuntime =  $renderingContext
             ->getViewHelperVariableContainer()
             ->get(RenderRenderableViewHelper::class, 'formRuntime');
 
-        return TranslationService::getInstance()->translateFormElementValue($element, $property, $formRuntime);
+        return TranslationService::getInstance()->translateFormElementValue($element, $propertyParts, $formRuntime);
+    }
+
+    /**
+     * @param array $arguments
+     */
+    protected static function assertArgumentTypes(array $arguments)
+    {
+        foreach (['property', 'renderingOptionProperty'] as $argumentName) {
+            if (
+                !isset($arguments[$argumentName])
+                || is_string($arguments[$argumentName])
+                || is_array($arguments[$argumentName])
+            ) {
+                continue;
+            }
+            throw new Exception(
+                sprintf(
+                    'Arguments "%s" either must be string or array',
+                    $argumentName
+                ),
+                1504871830
+            );
+        }
     }
 }
diff --git a/typo3/sysext/form/Resources/Private/Frontend/Partials/MultiCheckbox.html b/typo3/sysext/form/Resources/Private/Frontend/Partials/MultiCheckbox.html
index 1998a699c496..7f209e440129 100644
--- a/typo3/sysext/form/Resources/Private/Frontend/Partials/MultiCheckbox.html
+++ b/typo3/sysext/form/Resources/Private/Frontend/Partials/MultiCheckbox.html
@@ -13,7 +13,7 @@
 							errorClass="{element.properties.elementErrorClassAttribute}"
 							additionalAttributes="{formvh:translateElementProperty(element: element, property: 'fluidAdditionalAttributes')}"
 						/>
-						<span>{formvh:translateElementProperty(element: element, property: 'options.{value}')}</span>
+						<span>{formvh:translateElementProperty(element: element, property: '{0: \'options\', 1: value}')}</span>
 					</label>
 				</div>
 			</f:for>
diff --git a/typo3/sysext/form/Resources/Private/Frontend/Partials/RadioButton.html b/typo3/sysext/form/Resources/Private/Frontend/Partials/RadioButton.html
index b707826cf2ee..f736e9193533 100644
--- a/typo3/sysext/form/Resources/Private/Frontend/Partials/RadioButton.html
+++ b/typo3/sysext/form/Resources/Private/Frontend/Partials/RadioButton.html
@@ -15,7 +15,7 @@
 									errorClass="{element.properties.elementErrorClassAttribute}"
 									additionalAttributes="{formvh:translateElementProperty(element: element, property: 'fluidAdditionalAttributes')}"
 								/>
-								<span>{formvh:translateElementProperty(element: element, property: 'options.{value}')}</span>
+								<span>{formvh:translateElementProperty(element: element, property: '{0: \'options\', 1: value}')}</span>
 							</label>
 						</div>
 					</f:for>
diff --git a/typo3/sysext/form/Tests/Unit/Service/TranslationServiceTest.php b/typo3/sysext/form/Tests/Unit/Service/TranslationServiceTest.php
index d71a003983b1..76377e931e87 100644
--- a/typo3/sysext/form/Tests/Unit/Service/TranslationServiceTest.php
+++ b/typo3/sysext/form/Tests/Unit/Service/TranslationServiceTest.php
@@ -340,7 +340,7 @@ class TranslationServiceTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestC
         $mockFormRuntime->expects($this->any())->method('getIdentifier')->willReturn($formRuntimeIdentifier);
         $mockFormRuntime->expects($this->any())->method('getRenderingOptions')->willReturn($formRuntimeRenderingOptions);
 
-        $this->assertEquals($expected, $this->mockTranslationService->_call('translateFormElementValue', $mockFormElement, 'label', $mockFormRuntime));
+        $this->assertEquals($expected, $this->mockTranslationService->_call('translateFormElementValue', $mockFormElement, ['label'], $mockFormRuntime));
     }
 
     /**
@@ -384,7 +384,7 @@ class TranslationServiceTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestC
         $mockFormRuntime->expects($this->any())->method('getIdentifier')->willReturn($formRuntimeIdentifier);
         $mockFormRuntime->expects($this->any())->method('getRenderingOptions')->willReturn($formRuntimeRenderingOptions);
 
-        $this->assertEquals($expected, $this->mockTranslationService->_call('translateFormElementValue', $mockFormElement, 'label', $mockFormRuntime));
+        $this->assertEquals($expected, $this->mockTranslationService->_call('translateFormElementValue', $mockFormElement, ['label'], $mockFormRuntime));
     }
 
     /**
@@ -428,7 +428,7 @@ class TranslationServiceTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestC
         $mockFormRuntime->expects($this->any())->method('getIdentifier')->willReturn($formRuntimeIdentifier);
         $mockFormRuntime->expects($this->any())->method('getRenderingOptions')->willReturn($formRuntimeRenderingOptions);
 
-        $this->assertEquals($expected, $this->mockTranslationService->_call('translateFormElementValue', $mockFormElement, 'label', $mockFormRuntime));
+        $this->assertEquals($expected, $this->mockTranslationService->_call('translateFormElementValue', $mockFormElement, ['label'], $mockFormRuntime));
     }
 
     /**
@@ -472,7 +472,7 @@ class TranslationServiceTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestC
         $mockFormRuntime->expects($this->any())->method('getIdentifier')->willReturn($formRuntimeIdentifier);
         $mockFormRuntime->expects($this->any())->method('getRenderingOptions')->willReturn($formRuntimeRenderingOptions);
 
-        $this->assertEquals($expected, $this->mockTranslationService->_call('translateFormElementValue', $mockFormElement, 'label', $mockFormRuntime));
+        $this->assertEquals($expected, $this->mockTranslationService->_call('translateFormElementValue', $mockFormElement, ['label'], $mockFormRuntime));
     }
 
     /**
@@ -516,7 +516,7 @@ class TranslationServiceTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestC
         $mockFormRuntime->expects($this->any())->method('getIdentifier')->willReturn($formRuntimeIdentifier);
         $mockFormRuntime->expects($this->any())->method('getRenderingOptions')->willReturn($formRuntimeRenderingOptions);
 
-        $this->assertEquals($expected, $this->mockTranslationService->_call('translateFormElementValue', $mockFormElement, 'label', $mockFormRuntime));
+        $this->assertEquals($expected, $this->mockTranslationService->_call('translateFormElementValue', $mockFormElement, ['label'], $mockFormRuntime));
     }
 
     /**
@@ -563,7 +563,7 @@ class TranslationServiceTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestC
         $mockFormRuntime->expects($this->any())->method('getIdentifier')->willReturn($formRuntimeIdentifier);
         $mockFormRuntime->expects($this->any())->method('getRenderingOptions')->willReturn($formRuntimeRenderingOptions);
 
-        $this->assertEquals($expected, $this->mockTranslationService->_call('translateFormElementValue', $mockFormElement, 'placeholder', $mockFormRuntime));
+        $this->assertEquals($expected, $this->mockTranslationService->_call('translateFormElementValue', $mockFormElement, ['placeholder'], $mockFormRuntime));
     }
 
     /**
@@ -610,7 +610,7 @@ class TranslationServiceTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestC
         $mockFormRuntime->expects($this->any())->method('getIdentifier')->willReturn($formRuntimeIdentifier);
         $mockFormRuntime->expects($this->any())->method('getRenderingOptions')->willReturn($formRuntimeRenderingOptions);
 
-        $this->assertEquals($expected, $this->mockTranslationService->_call('translateFormElementValue', $mockFormElement, 'placeholder', $mockFormRuntime));
+        $this->assertEquals($expected, $this->mockTranslationService->_call('translateFormElementValue', $mockFormElement, ['placeholder'], $mockFormRuntime));
     }
 
     /**
@@ -658,7 +658,7 @@ class TranslationServiceTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestC
         $mockFormRuntime->expects($this->any())->method('getIdentifier')->willReturn($formRuntimeIdentifier);
         $mockFormRuntime->expects($this->any())->method('getRenderingOptions')->willReturn($formRuntimeRenderingOptions);
 
-        $this->assertEquals($expected, $this->mockTranslationService->_call('translateFormElementValue', $mockFormElement, 'nextButtonLabel', $mockFormRuntime));
+        $this->assertEquals($expected, $this->mockTranslationService->_call('translateFormElementValue', $mockFormElement, ['nextButtonLabel'], $mockFormRuntime));
     }
 
     /**
@@ -711,7 +711,7 @@ class TranslationServiceTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestC
         $mockFormRuntime->expects($this->any())->method('getIdentifier')->willReturn($formRuntimeIdentifier);
         $mockFormRuntime->expects($this->any())->method('getRenderingOptions')->willReturn($formRuntimeRenderingOptions);
 
-        $this->assertEquals($expected, $this->mockTranslationService->_call('translateFormElementValue', $mockFormElement, 'options', $mockFormRuntime));
+        $this->assertEquals($expected, $this->mockTranslationService->_call('translateFormElementValue', $mockFormElement, ['options'], $mockFormRuntime));
     }
 
     /**
@@ -764,7 +764,7 @@ class TranslationServiceTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestC
         $mockFormRuntime->expects($this->any())->method('getIdentifier')->willReturn($formRuntimeIdentifier);
         $mockFormRuntime->expects($this->any())->method('getRenderingOptions')->willReturn($formRuntimeRenderingOptions);
 
-        $this->assertEquals($expected, $this->mockTranslationService->_call('translateFormElementValue', $mockFormElement, 'options', $mockFormRuntime));
+        $this->assertEquals($expected, $this->mockTranslationService->_call('translateFormElementValue', $mockFormElement, ['options'], $mockFormRuntime));
     }
 
     /**
@@ -873,7 +873,7 @@ class TranslationServiceTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestC
         $mockFormRuntime->expects($this->any())->method('getIdentifier')->willReturn($formRuntimeIdentifier);
         $mockFormRuntime->expects($this->any())->method('getRenderingOptions')->willReturn($formRuntimeRenderingOptions);
 
-        $this->assertEquals($expected, $this->mockTranslationService->_call('translateFormElementValue', $mockFormElement, 'label', $mockFormRuntime));
+        $this->assertEquals($expected, $this->mockTranslationService->_call('translateFormElementValue', $mockFormElement, ['label'], $mockFormRuntime));
     }
 
     /**
@@ -955,6 +955,6 @@ class TranslationServiceTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestC
         $mockFormRuntime->expects($this->any())->method('getIdentifier')->willReturn($formRuntimeIdentifier);
         $mockFormRuntime->expects($this->any())->method('getRenderingOptions')->willReturn($formRuntimeRenderingOptions);
 
-        $this->assertEquals($expected, $this->mockTranslationService->_call('translateFormElementValue', $mockFormElement, 'label', $mockFormRuntime));
+        $this->assertEquals($expected, $this->mockTranslationService->_call('translateFormElementValue', $mockFormElement, ['label'], $mockFormRuntime));
     }
 }
-- 
GitLab