[BUGFIX] Convert date objects to string in finisher options 61/62361/10
authorMathias Brodala <mbrodala@pagemachine.de>
Thu, 21 Nov 2019 19:08:25 +0000 (20:08 +0100)
committerRalf Zimmermann <ralf.zimmermann@tritum.de>
Sat, 23 Nov 2019 11:27:17 +0000 (12:27 +0100)
Resolves: #88899
Releases: master
Change-Id: I775b73eed5b257a65e48f6cc824e0ce855a67f6a
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/62361
Tested-by: TYPO3com <noreply@typo3.com>
Tested-by: Björn Jacob <bjoern.jacob@tritum.de>
Tested-by: Ralf Zimmermann <ralf.zimmermann@tritum.de>
Reviewed-by: Björn Jacob <bjoern.jacob@tritum.de>
Reviewed-by: Ralf Zimmermann <ralf.zimmermann@tritum.de>
typo3/sysext/form/Classes/Domain/Finishers/AbstractFinisher.php
typo3/sysext/form/Classes/Domain/Model/FormElements/Date.php [new file with mode: 0644]
typo3/sysext/form/Classes/Domain/Model/FormElements/DatePicker.php
typo3/sysext/form/Classes/Domain/Model/FormElements/StringableFormElementInterface.php [new file with mode: 0644]
typo3/sysext/form/Classes/Hooks/FormElementHooks.php
typo3/sysext/form/Classes/ViewHelpers/RenderAllFormValuesViewHelper.php
typo3/sysext/form/Configuration/Yaml/FormElements/Date.yaml
typo3/sysext/form/Configuration/Yaml/FormElements/DatePicker.yaml
typo3/sysext/form/Tests/Unit/Domain/Finishers/AbstractFinisherTest.php
typo3/sysext/form/ext_localconf.php

index 2c14a97..9ee1ac7 100644 (file)
@@ -21,6 +21,7 @@ use TYPO3\CMS\Core\Utility\ArrayUtility;
 use TYPO3\CMS\Core\Utility\Exception\MissingArrayPathException;
 use TYPO3\CMS\Extbase\Reflection\ObjectAccess;
 use TYPO3\CMS\Form\Domain\Finishers\Exception\FinisherException;
+use TYPO3\CMS\Form\Domain\Model\FormElements\StringableFormElementInterface;
 use TYPO3\CMS\Form\Domain\Runtime\FormRuntime;
 use TYPO3\CMS\Form\Service\TranslationService;
 use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
@@ -342,8 +343,23 @@ abstract class AbstractFinisher implements FinisherInterface
         if ($property === '__currentTimestamp') {
             return time();
         }
+
         // try to resolve the path '{...}' within the FormRuntime
         $value = ObjectAccess::getPropertyPath($formRuntime, $property);
+
+        if (is_object($value)) {
+            $element = $formRuntime->getFormDefinition()->getElementByIdentifier($property);
+
+            if (!$element instanceof StringableFormElementInterface) {
+                throw new FinisherException(
+                    sprintf('Cannot convert object value of "%s" to string', $property),
+                    1574362327
+                );
+            }
+
+            $value = $element->valueToString($value);
+        }
+
         if ($value === null) {
             // try to resolve the path '{...}' within the FinisherVariableProvider
             $value = ObjectAccess::getPropertyPath(
@@ -351,9 +367,11 @@ abstract class AbstractFinisher implements FinisherInterface
                 $property
             );
         }
+
         if ($value !== null) {
             return $value;
         }
+
         // in case no value could be resolved
         return '{' . $property . '}';
     }
diff --git a/typo3/sysext/form/Classes/Domain/Model/FormElements/Date.php b/typo3/sysext/form/Classes/Domain/Model/FormElements/Date.php
new file mode 100644 (file)
index 0000000..c4d9dcf
--- /dev/null
@@ -0,0 +1,47 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Form\Domain\Model\FormElements;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It originated from the Neos.Form package (www.neos.io)
+ *
+ * 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!
+ */
+
+/**
+ * A date form element
+ *
+ * Scope: frontend
+ */
+class Date extends AbstractFormElement implements StringableFormElementInterface
+{
+
+    /**
+     * Initializes the Form Element by setting the data type to "DateTime"
+     * @internal
+     */
+    public function initializeFormElement()
+    {
+        $this->setDataType('DateTime');
+        parent::initializeFormElement();
+    }
+
+    /**
+     * @param \DateTime $value
+     */
+    public function valueToString($value): string
+    {
+        $dateFormat = $this->properties['displayFormat'] ?? 'Y-m-d';
+
+        return $value->format($dateFormat);
+    }
+}
index 7b30a88..14bc841 100644 (file)
@@ -22,7 +22,7 @@ namespace TYPO3\CMS\Form\Domain\Model\FormElements;
  *
  * Scope: frontend
  */
-class DatePicker extends AbstractFormElement
+class DatePicker extends AbstractFormElement implements StringableFormElementInterface
 {
 
     /**
@@ -34,4 +34,22 @@ class DatePicker extends AbstractFormElement
         $this->setDataType('DateTime');
         parent::initializeFormElement();
     }
+
+    /**
+     * @param \DateTime $value
+     */
+    public function valueToString($value): string
+    {
+        $dateFormat = \DateTime::W3C;
+
+        if (isset($this->properties['dateFormat'])) {
+            $dateFormat = $this->properties['dateFormat'];
+
+            if ($this->properties['displayTimeSelector'] ?? false) {
+                $dateFormat .= ' H:i';
+            }
+        }
+
+        return $value->format($dateFormat);
+    }
 }
diff --git a/typo3/sysext/form/Classes/Domain/Model/FormElements/StringableFormElementInterface.php b/typo3/sysext/form/Classes/Domain/Model/FormElements/StringableFormElementInterface.php
new file mode 100644 (file)
index 0000000..84aecc4
--- /dev/null
@@ -0,0 +1,28 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Form\Domain\Model\FormElements;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It originated from the Neos.Form package (www.neos.io)
+ *
+ * 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!
+ */
+
+/**
+ * Interface for form elements capable of converting their complex values to string
+ *
+ * @internal
+ */
+interface StringableFormElementInterface
+{
+    public function valueToString($value): string;
+}
index 536b9bf..052953a 100644 (file)
@@ -80,20 +80,4 @@ class FormElementHooks
             }
         }
     }
-
-    /**
-     * This hook is invoked whenever a form element is created.
-     * Note that this hook will be called **after** all properties from the
-     * prototype configuration are set in the form element but **before**
-     * the properties from the form definition are set in the form element.
-     *
-     * @param RenderableInterface $renderable
-     */
-    public function initializeFormElement(RenderableInterface $renderable)
-    {
-        if ($renderable->getType() === 'Date' || $renderable->getType() === 'DatePicker') {
-            // Set the property mapping type for the `Date` and `DatePicker` element.
-            $renderable->setDataType('DateTime');
-        }
-    }
 }
index abaac17..c5f0d3f 100644 (file)
@@ -20,6 +20,7 @@ namespace TYPO3\CMS\Form\ViewHelpers;
 use TYPO3\CMS\Core\Resource\File;
 use TYPO3\CMS\Extbase\Domain\Model\FileReference;
 use TYPO3\CMS\Form\Domain\Model\FormElements\FormElementInterface;
+use TYPO3\CMS\Form\Domain\Model\FormElements\StringableFormElementInterface;
 use TYPO3\CMS\Form\Domain\Model\Renderable\CompositeRenderableInterface;
 use TYPO3\CMS\Form\Domain\Model\Renderable\RenderableInterface;
 use TYPO3\CMS\Form\Domain\Model\Renderable\RootRenderableInterface;
@@ -184,26 +185,13 @@ class RenderAllFormValuesViewHelper extends AbstractViewHelper
     public static function processObject(FormElementInterface $element, $object): string
     {
         $properties = $element->getProperties();
-        if ($object instanceof \DateTime) {
-            if (
-                $element->getType() === 'DatePicker'
-                && isset($properties['dateFormat'])
-            ) {
-                $dateFormat = $properties['dateFormat'];
-                if (isset($properties['displayTimeSelector']) && $properties['displayTimeSelector'] === true) {
-                    $dateFormat .= ' H:i';
-                }
-            } elseif ($element->getType() === 'Date') {
-                if (isset($properties['displayFormat'])) {
-                    $dateFormat = $properties['displayFormat'];
-                } else {
-                    $dateFormat = 'Y-m-d';
-                }
-            } else {
-                $dateFormat = \DateTime::W3C;
-            }
 
-            return $object->format($dateFormat);
+        if ($element instanceof StringableFormElementInterface) {
+            return $element->valueToString($object);
+        }
+
+        if ($object instanceof \DateTime) {
+            return $object->format(\DateTime::W3C);
         }
 
         if ($object instanceof File || $object instanceof FileReference) {
index b1020ed..78b5237 100644 (file)
@@ -317,7 +317,7 @@ TYPO3:
                 group: html5
                 groupSorting: 500
                 iconIdentifier: form-date-picker
-              implementationClassName: TYPO3\CMS\Form\Domain\Model\FormElements\GenericFormElement
+              implementationClassName: TYPO3\CMS\Form\Domain\Model\FormElements\Date
               properties:
                 containerClassAttribute: input
                 elementClassAttribute: ''
index 0ea3cba..7c552f2 100644 (file)
@@ -123,7 +123,7 @@ TYPO3:
                         9999:
                           identifier: removeButton
                           templateName: Inspector-RemoveElementEditor
-              implementationClassName: TYPO3\CMS\Form\Domain\Model\FormElements\GenericFormElement
+              implementationClassName: TYPO3\CMS\Form\Domain\Model\FormElements\DatePicker
               properties:
                 containerClassAttribute: input
                 elementClassAttribute: 'small form-control'
index fabd9d9..4c2b744 100644 (file)
@@ -21,6 +21,8 @@ use TYPO3\CMS\Form\Domain\Finishers\AbstractFinisher;
 use TYPO3\CMS\Form\Domain\Finishers\Exception\FinisherException;
 use TYPO3\CMS\Form\Domain\Finishers\FinisherContext;
 use TYPO3\CMS\Form\Domain\Finishers\FinisherVariableProvider;
+use TYPO3\CMS\Form\Domain\Model\FormDefinition;
+use TYPO3\CMS\Form\Domain\Model\FormElements\StringableFormElementInterface;
 use TYPO3\CMS\Form\Domain\Runtime\FormRuntime;
 use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
 
@@ -483,6 +485,74 @@ class AbstractFinisherTest extends UnitTestCase
     /**
      * @test
      */
+    public function substituteRuntimeReferencesConvertsObjectsToString(): void
+    {
+        $date = new \DateTime('@1574415600');
+        $formRuntimeProphecy = $this->createFormRuntimeProphecy([
+            'date-1' => $date,
+        ]);
+
+        $stringableElement = new class implements StringableFormElementInterface {
+            /**
+             * @param \DateTimeInterface $value
+             */
+            public function valueToString($value): string
+            {
+                return $value->format('Y-m-d');
+            }
+        };
+        $formDefinitionProphecy = $this->prophesize(FormDefinition::class);
+        $formDefinitionProphecy->getElementByIdentifier('date-1')->willReturn($stringableElement);
+        $formRuntimeProphecy->getFormDefinition()->willReturn($formDefinitionProphecy->reveal());
+
+        $mockAbstractFinisher = $this->getAccessibleMockForAbstractClass(
+            AbstractFinisher::class,
+            [],
+            '',
+            false
+        );
+        $result = $mockAbstractFinisher->_call(
+            'substituteRuntimeReferences',
+            'When: {date-1}',
+            $formRuntimeProphecy->reveal()
+        );
+
+        self::assertSame('When: 2019-11-22', $result);
+    }
+
+    /**
+     * @test
+     */
+    public function substituteRuntimeReferencesThrowsExceptionOnObjectWithoutStringableElement(): void
+    {
+        $formRuntimeProphecy = $this->createFormRuntimeProphecy([
+            'date-1' => new \DateTime(),
+        ]);
+
+        $formDefinitionProphecy = $this->prophesize(FormDefinition::class);
+        $formDefinitionProphecy->getElementByIdentifier('date-1')->willReturn($this->prophesize(FormElementInterface::class)->reveal());
+        $formRuntimeProphecy->getFormDefinition()->willReturn($formDefinitionProphecy->reveal());
+
+        $mockAbstractFinisher = $this->getAccessibleMockForAbstractClass(
+            AbstractFinisher::class,
+            [],
+            '',
+            false
+        );
+
+        $this->expectException(FinisherException::class);
+        $this->expectExceptionCode(1574362327);
+
+        $mockAbstractFinisher->_call(
+            'substituteRuntimeReferences',
+            'When: {date-1}',
+            $formRuntimeProphecy->reveal()
+        );
+    }
+
+    /**
+     * @test
+     */
     public function substituteRuntimeReferencesThrowsExceptionOnMultipleVariablesResolvedAsArray(): void
     {
         $mockAbstractFinisher = $this->getAccessibleMockForAbstractClass(
index 43c8e5c..b9cda2f 100644 (file)
@@ -58,9 +58,6 @@ call_user_func(function () {
     $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/form']['afterSubmit'][1489772699]
         = \TYPO3\CMS\Form\Hooks\FormElementHooks::class;
 
-    $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/form']['initializeFormElement'][1489772699]
-        = \TYPO3\CMS\Form\Hooks\FormElementHooks::class;
-
     $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/form']['beforeRendering'][1489772699]
         = \TYPO3\CMS\Form\Hooks\FormElementHooks::class;