diff --git a/typo3/sysext/form/Classes/Domain/Finishers/AbstractFinisher.php b/typo3/sysext/form/Classes/Domain/Finishers/AbstractFinisher.php index 2c14a976c68c56b9caba57d539045563680619db..9ee1ac728d81d6cc1cba1d7fa6d99f7050080410 100644 --- a/typo3/sysext/form/Classes/Domain/Finishers/AbstractFinisher.php +++ b/typo3/sysext/form/Classes/Domain/Finishers/AbstractFinisher.php @@ -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 index 0000000000000000000000000000000000000000..c4d9dcf99bf12380c7a53799beb1e78d53421d59 --- /dev/null +++ b/typo3/sysext/form/Classes/Domain/Model/FormElements/Date.php @@ -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); + } +} diff --git a/typo3/sysext/form/Classes/Domain/Model/FormElements/DatePicker.php b/typo3/sysext/form/Classes/Domain/Model/FormElements/DatePicker.php index 7b30a887246f813bd94bc945e95e76bb21cffb85..14bc841b1da60976ae5e3b94a946291b480f8d8b 100644 --- a/typo3/sysext/form/Classes/Domain/Model/FormElements/DatePicker.php +++ b/typo3/sysext/form/Classes/Domain/Model/FormElements/DatePicker.php @@ -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 index 0000000000000000000000000000000000000000..84aecc4d39c2f7544ba5da6c0c183bb0d2e136f8 --- /dev/null +++ b/typo3/sysext/form/Classes/Domain/Model/FormElements/StringableFormElementInterface.php @@ -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; +} diff --git a/typo3/sysext/form/Classes/Hooks/FormElementHooks.php b/typo3/sysext/form/Classes/Hooks/FormElementHooks.php index 536b9bf831f65c36f8f5724ca4dbbf7190b28d76..052953aed21e3dce7a4db386322e10a4b0f5d557 100644 --- a/typo3/sysext/form/Classes/Hooks/FormElementHooks.php +++ b/typo3/sysext/form/Classes/Hooks/FormElementHooks.php @@ -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'); - } - } } diff --git a/typo3/sysext/form/Classes/ViewHelpers/RenderAllFormValuesViewHelper.php b/typo3/sysext/form/Classes/ViewHelpers/RenderAllFormValuesViewHelper.php index abaac177dbc055644edbe0d2149724968f0d3cad..c5f0d3f7405002bf2bf13d2262860eb5373e4228 100644 --- a/typo3/sysext/form/Classes/ViewHelpers/RenderAllFormValuesViewHelper.php +++ b/typo3/sysext/form/Classes/ViewHelpers/RenderAllFormValuesViewHelper.php @@ -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) { diff --git a/typo3/sysext/form/Configuration/Yaml/FormElements/Date.yaml b/typo3/sysext/form/Configuration/Yaml/FormElements/Date.yaml index b1020ed44d0079e8eb9c21f50bebfc76ff1bfc32..78b52375ecd89b1d6955e722111721ae6618c8f6 100644 --- a/typo3/sysext/form/Configuration/Yaml/FormElements/Date.yaml +++ b/typo3/sysext/form/Configuration/Yaml/FormElements/Date.yaml @@ -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: '' diff --git a/typo3/sysext/form/Configuration/Yaml/FormElements/DatePicker.yaml b/typo3/sysext/form/Configuration/Yaml/FormElements/DatePicker.yaml index 0ea3cba9fe498b6addaa80e18dd5d39292d9f7b4..7c552f21b70d5e51b64b534cbb9056134db71dcd 100644 --- a/typo3/sysext/form/Configuration/Yaml/FormElements/DatePicker.yaml +++ b/typo3/sysext/form/Configuration/Yaml/FormElements/DatePicker.yaml @@ -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' diff --git a/typo3/sysext/form/Tests/Unit/Domain/Finishers/AbstractFinisherTest.php b/typo3/sysext/form/Tests/Unit/Domain/Finishers/AbstractFinisherTest.php index fabd9d9c37d7cefe4c500e524d9a68244f40c6f7..4c2b7449e70a067564aab6f7ee5c335566ea6410 100644 --- a/typo3/sysext/form/Tests/Unit/Domain/Finishers/AbstractFinisherTest.php +++ b/typo3/sysext/form/Tests/Unit/Domain/Finishers/AbstractFinisherTest.php @@ -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; @@ -480,6 +482,74 @@ class AbstractFinisherTest extends UnitTestCase self::assertSame($expected, $mockAbstractFinisher->_call('substituteRuntimeReferences', $input, $formRuntimeProphecy->reveal())); } + /** + * @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 */ diff --git a/typo3/sysext/form/ext_localconf.php b/typo3/sysext/form/ext_localconf.php index 43c8e5c5ebc3811a1a18ea1a3d5691ccb97c763c..b9cda2f07234edb3b62bc91068438e815816fcae 100644 --- a/typo3/sysext/form/ext_localconf.php +++ b/typo3/sysext/form/ext_localconf.php @@ -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;