diff --git a/typo3/sysext/form/Classes/Controller/FormEditorController.php b/typo3/sysext/form/Classes/Controller/FormEditorController.php index 1c54bed90e6fbf1a2d2db5197f5f4493cfe44f57..63cf254c4774743b0d74a42f4a02a5b544270260 100644 --- a/typo3/sysext/form/Classes/Controller/FormEditorController.php +++ b/typo3/sysext/form/Classes/Controller/FormEditorController.php @@ -29,6 +29,7 @@ use TYPO3\CMS\Form\Domain\Exception\RenderingException; use TYPO3\CMS\Form\Domain\Factory\ArrayFormFactory; use TYPO3\CMS\Form\Mvc\Persistence\Exception\PersistenceManagerException; use TYPO3\CMS\Form\Service\TranslationService; +use TYPO3\CMS\Form\Type\FormDefinitionArray; /** * The form editor controller @@ -145,14 +146,12 @@ class FormEditorController extends AbstractBackendController * Save a formDefinition which was build by the form editor. * * @param string $formPersistenceIdentifier - * @param array $formDefinition + * @param FormDefinitionArray $formDefinition * @internal */ - public function saveFormAction(string $formPersistenceIdentifier, array $formDefinition) + public function saveFormAction(string $formPersistenceIdentifier, FormDefinitionArray $formDefinition) { - $formDefinition = ArrayUtility::stripTagsFromValuesRecursive($formDefinition); - $formDefinition = $this->convertJsonArrayToAssociativeArray($formDefinition); - + $formDefinition = $formDefinition->getArrayCopy(); foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/form']['beforeFormSave'] ?? [] as $className) { $hookObj = GeneralUtility::makeInstance($className); if (method_exists($hookObj, 'beforeFormSave')) { @@ -189,22 +188,20 @@ class FormEditorController extends AbstractBackendController * Render a page from the formDefinition which was build by the form editor. * Use the frontend rendering and set the form framework to preview mode. * - * @param array $formDefinition + * @param FormDefinitionArray $formDefinition * @param int $pageIndex * @param string $prototypeName * @return string * @internal */ - public function renderFormPageAction(array $formDefinition, int $pageIndex, string $prototypeName = null): string + public function renderFormPageAction(FormDefinitionArray $formDefinition, int $pageIndex, string $prototypeName = null): string { - $formDefinition = ArrayUtility::stripTagsFromValuesRecursive($formDefinition); - $formDefinition = $this->convertJsonArrayToAssociativeArray($formDefinition); if (empty($prototypeName)) { $prototypeName = isset($formDefinition['prototypeName']) ? $formDefinition['prototypeName'] : 'standard'; } $formFactory = $this->objectManager->get(ArrayFormFactory::class); - $formDefinition = $formFactory->build($formDefinition, $prototypeName); + $formDefinition = $formFactory->build($formDefinition->getArrayCopy(), $prototypeName); $formDefinition->setRenderingOption('previewMode', true); $form = $formDefinition->bind($this->request, $this->response); $form->overrideCurrentPage($pageIndex); @@ -375,42 +372,6 @@ class FormEditorController extends AbstractBackendController } } - /** - * Some data which is build by the form editor needs a transformation before - * it can be used by the framework. - * Multivalue elements like select elements produce data like: - * - * [ - * _label => 'label' - * _value => 'value' - * ] - * - * This method transform this into: - * - * [ - * 'value' => 'label' - * ] - * - * @param array $input - * @return array - */ - protected function convertJsonArrayToAssociativeArray(array $input): array - { - $output = []; - foreach ($input as $key => $value) { - if (is_int($key) && is_array($value) && isset($value['_label']) && isset($value['_value'])) { - $key = $value['_value']; - $value = $value['_label']; - } - if (is_array($value)) { - $output[$key] = $this->convertJsonArrayToAssociativeArray($value); - } else { - $output[$key] = $value; - } - } - return $output; - } - /** * Render the "text/x-formeditor-template" templates. * diff --git a/typo3/sysext/form/Classes/Domain/Finishers/AbstractFinisher.php b/typo3/sysext/form/Classes/Domain/Finishers/AbstractFinisher.php index 5c011fd2dddd151aeefbd6adbff09f514c708bbe..c7c69c2573a61de2b639ab442f3a04fefdc87130 100644 --- a/typo3/sysext/form/Classes/Domain/Finishers/AbstractFinisher.php +++ b/typo3/sysext/form/Classes/Domain/Finishers/AbstractFinisher.php @@ -170,7 +170,7 @@ abstract class AbstractFinisher implements FinisherInterface return null; } - if (is_array($optionValue)) { + if (is_array($optionValue) || is_bool($optionValue)) { return $optionValue; } diff --git a/typo3/sysext/form/Classes/Property/TypeConverter/FormDefinitionArrayConverter.php b/typo3/sysext/form/Classes/Property/TypeConverter/FormDefinitionArrayConverter.php new file mode 100644 index 0000000000000000000000000000000000000000..4a8089ea0daaf2a56f9eca4e98656a505a50d284 --- /dev/null +++ b/typo3/sysext/form/Classes/Property/TypeConverter/FormDefinitionArrayConverter.php @@ -0,0 +1,110 @@ +<?php +declare(strict_types=1); +namespace TYPO3\CMS\Form\Property\TypeConverter; + +/* + * This file is part of the TYPO3 CMS project. + * + * 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! + */ + +use TYPO3\CMS\Core\Utility\ArrayUtility; +use TYPO3\CMS\Extbase\Property\Exception as PropertyException; +use TYPO3\CMS\Extbase\Property\PropertyMappingConfigurationInterface; +use TYPO3\CMS\Extbase\Property\TypeConverter\AbstractTypeConverter; +use TYPO3\CMS\Form\Type\FormDefinitionArray; + +/** + * Converter for form definition arrays + * + * @internal + */ +class FormDefinitionArrayConverter extends AbstractTypeConverter +{ + /** + * @var array<string> + */ + protected $sourceTypes = ['string']; + + /** + * @var string + */ + protected $targetType = FormDefinitionArray::class; + + /** + * @var int + */ + protected $priority = 10; + + /** + * Convert from $source to $targetType, a noop if the source is an array. + * If it is an empty string it will be converted to an empty array. + * + * @param string $source + * @param string $targetType + * @param array $convertedChildProperties + * @param PropertyMappingConfigurationInterface $configuration + * @return FormDefinitionArray + * @throws PropertyException + */ + public function convertFrom($source, $targetType, array $convertedChildProperties = [], PropertyMappingConfigurationInterface $configuration = null) + { + $rawFormDefinitionArray = json_decode($source, true); + + if (json_last_error() !== JSON_ERROR_NONE) { + throw new PropertyException('Unable to decode JSON source: ' . json_last_error_msg(), 1512578002); + } + + $rawFormDefinitionArray = ArrayUtility::stripTagsFromValuesRecursive($rawFormDefinitionArray); + $rawFormDefinitionArray = $this->convertJsonArrayToAssociativeArray($rawFormDefinitionArray); + $formDefinitionArray = new FormDefinitionArray($rawFormDefinitionArray); + + return $formDefinitionArray; + } + + /** + * Some data which is build by the form editor needs a transformation before + * it can be used by the framework. + * Multivalue elements like select elements produce data like: + * + * [ + * _label => 'label' + * _value => 'value' + * ] + * + * This method transform this into: + * + * [ + * 'value' => 'label' + * ] + * + * @param array $input + * @return array + */ + protected function convertJsonArrayToAssociativeArray(array $input): array + { + $output = []; + + foreach ($input as $key => $value) { + if (is_int($key) && is_array($value) && isset($value['_label']) && isset($value['_value'])) { + $key = $value['_value']; + $value = $value['_label']; + } + + if (is_array($value)) { + $output[$key] = $this->convertJsonArrayToAssociativeArray($value); + } else { + $output[$key] = $value; + } + } + + return $output; + } +} diff --git a/typo3/sysext/form/Classes/Type/FormDefinitionArray.php b/typo3/sysext/form/Classes/Type/FormDefinitionArray.php new file mode 100644 index 0000000000000000000000000000000000000000..cdc99e3ae4a982416ba02292b1c58ef5cc8e5258 --- /dev/null +++ b/typo3/sysext/form/Classes/Type/FormDefinitionArray.php @@ -0,0 +1,23 @@ +<?php +declare(strict_types=1); +namespace TYPO3\CMS\Form\Type; + +/* + * This file is part of the TYPO3 CMS project. + * + * 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! + */ + +/** + * Wrapper for basic form definition arrays + */ +class FormDefinitionArray extends \ArrayObject +{ +} diff --git a/typo3/sysext/form/Resources/Public/JavaScript/Backend/FormEditor/Core.js b/typo3/sysext/form/Resources/Public/JavaScript/Backend/FormEditor/Core.js index 73f5165bab7521a0d4cec56131b68f542597c6a3..59dbd8c39ce63593cf0153b6573b2d013ca99482 100644 --- a/typo3/sysext/form/Resources/Public/JavaScript/Backend/FormEditor/Core.js +++ b/typo3/sysext/form/Resources/Public/JavaScript/Backend/FormEditor/Core.js @@ -1982,7 +1982,7 @@ define(['jquery'], function($) { _runningAjaxRequests['saveForm'] = $.post(_dataBackendEndpoints['saveForm'], { tx_form_web_formformbuilder: { formPersistenceIdentifier: _dataBackendPersistenceIdentifier, - formDefinition: utility().convertToSimpleObject(getApplicationStateStack().getCurrentState('formDefinition')) + formDefinition: JSON.stringify(utility().convertToSimpleObject(getApplicationStateStack().getCurrentState('formDefinition'))) } }, function(data, textStatus, jqXHR) { if (_runningAjaxRequests['saveForm'] !== jqXHR) { diff --git a/typo3/sysext/form/Tests/Unit/Controller/FormEditorControllerTest.php b/typo3/sysext/form/Tests/Unit/Controller/FormEditorControllerTest.php index 9a80dda433658e1436b74ef28a1adc9c3d1ee592..a40315fdb7f1a6428052bc31a3adcff50edd420f 100644 --- a/typo3/sysext/form/Tests/Unit/Controller/FormEditorControllerTest.php +++ b/typo3/sysext/form/Tests/Unit/Controller/FormEditorControllerTest.php @@ -280,39 +280,6 @@ class FormEditorControllerTest extends \TYPO3\TestingFramework\Core\Unit\UnitTes $this->assertSame($expected, $mockController->_call('getFormEditorDefinitions')); } - /** - * @test - */ - public function convertJsonArrayToAssociativeArrayReturnTransformedArray() - { - $mockController = $this->getAccessibleMock(FormEditorController::class, [ - 'dummy' - ], [], '', false); - - $input = [ - 'francine' => 'stan', - 'properties' => [ - 'options' => [ - 0 => [ - '_label' => 'label', - '_value' => 'value', - ], - ], - ], - ]; - - $expected = [ - 'francine' => 'stan', - 'properties' => [ - 'options' => [ - 'value' => 'label', - ], - ], - ]; - - $this->assertSame($expected, $mockController->_call('convertJsonArrayToAssociativeArray', $input)); - } - /** * @test */ diff --git a/typo3/sysext/form/Tests/Unit/Domain/Finishers/AbstractFinisherTest.php b/typo3/sysext/form/Tests/Unit/Domain/Finishers/AbstractFinisherTest.php index aa33f232e0f1cfdd076a569155bf6fb7b76681ac..7fdbea9b8625c2758ef8b3483f5397aa2ba73b44 100644 --- a/typo3/sysext/form/Tests/Unit/Domain/Finishers/AbstractFinisherTest.php +++ b/typo3/sysext/form/Tests/Unit/Domain/Finishers/AbstractFinisherTest.php @@ -119,6 +119,27 @@ class AbstractFinisherTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCas $this->assertSame($expected, $mockAbstractFinisher->_call('parseOption', 'foo')); } + /** + * @test + */ + public function parseOptionReturnsBoolOptionValuesAsBool() + { + $mockAbstractFinisher = $this->getAccessibleMockForAbstractClass( + AbstractFinisher::class, + [], + '', + false + ); + + $mockAbstractFinisher->_set('options', [ + 'foo1' => false, + ]); + + $expected = false; + + $this->assertSame($expected, $mockAbstractFinisher->_call('parseOption', 'foo1')); + } + /** * @test */ diff --git a/typo3/sysext/form/Tests/Unit/Property/TypeConverter/FormDefinitionArrayConverterTest.php b/typo3/sysext/form/Tests/Unit/Property/TypeConverter/FormDefinitionArrayConverterTest.php new file mode 100644 index 0000000000000000000000000000000000000000..39670c5dbd6dd57b84f6531bf9f79959b196dbd4 --- /dev/null +++ b/typo3/sysext/form/Tests/Unit/Property/TypeConverter/FormDefinitionArrayConverterTest.php @@ -0,0 +1,47 @@ +<?php +namespace TYPO3\CMS\Form\Tests\Unit\Property\TypeConverter; + +/* + * This file is part of the TYPO3 CMS project. + * + * 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! + */ + +use TYPO3\CMS\Form\Property\TypeConverter\FormDefinitionArrayConverter; +use TYPO3\CMS\Form\Type\FormDefinitionArray; +use TYPO3\TestingFramework\Core\Unit\UnitTestCase; + +/** + * Test case for TYPO3\CMS\Form\Property\TypeConverter\FormDefinitionArrayConverter + */ +class FormDefinitionArrayConverterTest extends UnitTestCase +{ + /** + * @test + */ + public function convertsJsonStringToFormDefinitionArray() + { + $typeConverter = new FormDefinitionArrayConverter(); + $source = '{"francine":"stan","enabled":false,"properties":{"options":[{"_label":"label","_value":"value"}]}}'; + $expected = [ + 'francine' => 'stan', + 'enabled' => false, + 'properties' => [ + 'options' => [ + 'value' => 'label', + ], + ], + ]; + $result = $typeConverter->convertFrom($source, FormDefinitionArray::class); + + $this->assertInstanceOf(FormDefinitionArray::class, $result); + $this->assertSame($expected, $result->getArrayCopy()); + } +} diff --git a/typo3/sysext/form/ext_localconf.php b/typo3/sysext/form/ext_localconf.php index a3f4ec957952921e6548eb65a3601b7a8eab0389..ee892ad352aae4f5061ac34d178629e7cff755d2 100644 --- a/typo3/sysext/form/ext_localconf.php +++ b/typo3/sysext/form/ext_localconf.php @@ -74,6 +74,9 @@ call_user_func(function () { $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/form']['afterBuildingFinished'][1489772699] = \TYPO3\CMS\Form\Mvc\Property\PropertyMappingConfiguration::class; + \TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerTypeConverter( + \TYPO3\CMS\Form\Property\TypeConverter\FormDefinitionArrayConverter::class + ); \TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerTypeConverter( \TYPO3\CMS\Form\Mvc\Property\TypeConverter\UploadedFileReferenceConverter::class );