From ecc0e0e643a77a18265c9211e7e8b4220110247f Mon Sep 17 00:00:00 2001 From: Ralf Zimmermann Date: Thu, 25 Apr 2019 17:11:23 +0200 Subject: [PATCH] [BUGFIX] Use form specific flexform sheets within the frontend Use contextual flexform sheets to identify ext:form finisher overrides within the frontend. Resolves: #88011 Releases: master, 9.5 Change-Id: I0a21deed29419281478f358ff61986d65b26dd0e Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/60604 Tested-by: TYPO3com Tested-by: Susanne Moog Reviewed-by: Susanne Moog --- .../Controller/FormFrontendController.php | 70 ++++- .../Processors/FinisherOptionGenerator.php | 12 +- .../Controller/FormFrontendControllerTest.php | 257 +++++++++++++++++- 3 files changed, 318 insertions(+), 21 deletions(-) diff --git a/typo3/sysext/form/Classes/Controller/FormFrontendController.php b/typo3/sysext/form/Classes/Controller/FormFrontendController.php index d2eae2bf1030..044b9d4bda43 100644 --- a/typo3/sysext/form/Classes/Controller/FormFrontendController.php +++ b/typo3/sysext/form/Classes/Controller/FormFrontendController.php @@ -15,6 +15,8 @@ * The TYPO3 project - inspiring people to share! */ +use TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools; +use TYPO3\CMS\Core\Service\FlexFormService; use TYPO3\CMS\Core\Utility\ArrayUtility; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; @@ -91,15 +93,24 @@ public function performAction() */ protected function overrideByFlexFormSettings(array $formDefinition): array { + $flexFormData = GeneralUtility::xml2array($this->configurationManager->getContentObject()->data['pi_flexform'] ?? ''); + + if (!is_array($flexFormData)) { + return $formDefinition; + } + if (isset($formDefinition['finishers'])) { + $prototypeName = $formDefinition['prototypeName'] ?? 'standard'; + $configurationService = $this->objectManager->get(ConfigurationService::class); + $prototypeConfiguration = $configurationService->getPrototypeConfiguration($prototypeName); + foreach ($formDefinition['finishers'] as $index => $formFinisherDefinition) { $finisherIdentifier = $formFinisherDefinition['identifier']; - $prototypeName = $formDefinition['prototypeName'] ?? 'standard'; - if ($this->settings['overrideFinishers'] && isset($this->settings['finishers'][$finisherIdentifier])) { - $configurationService = $this->objectManager->get(ConfigurationService::class); - $prototypeConfiguration = $configurationService->getPrototypeConfiguration($prototypeName); - $flexFormSheetSettings = $this->settings; + $sheetIdentifier = $this->getFlexformSheetIdentifier($formDefinition, $prototypeName, $finisherIdentifier); + $flexFormSheetSettings = $this->getFlexFormSettingsFromSheet($flexFormData, $sheetIdentifier); + + if ($this->settings['overrideFinishers'] && isset($flexFormSheetSettings['finishers'][$finisherIdentifier])) { $prototypeFinisherDefinition = $prototypeConfiguration['finishersDefinition'][$finisherIdentifier] ?? []; $converterDto = GeneralUtility::makeInstance( FlexFormFinisherOverridesConverterDto::class, @@ -149,4 +160,53 @@ protected function overrideByTypoScriptSettings(array $formDefinition): array } return $formDefinition; } + + /** + * @param array $formDefinition + * @param string $prototypeName + * @param string $finisherIdentifier + * @return string + */ + protected function getFlexformSheetIdentifier( + array $formDefinition, + string $prototypeName, + string $finisherIdentifier + ): string { + return md5( + implode('', [ + $formDefinition['persistenceIdentifier'], + $prototypeName, + $formDefinition['identifier'], + $finisherIdentifier + ]) + ); + } + + /** + * @param array $flexForm + * @param string $sheetIdentifier + * @return array + */ + protected function getFlexFormSettingsFromSheet( + array $flexForm, + string $sheetIdentifier + ): array { + $sheetData['data'] = array_filter( + $flexForm['data'] ?? [], + function ($key) use ($sheetIdentifier) { + return $key === $sheetIdentifier; + }, + ARRAY_FILTER_USE_KEY + ); + + if (empty($sheetData['data'])) { + return []; + } + + $flexFormService = GeneralUtility::makeInstance(FlexFormService::class); + $flexFormTools = GeneralUtility::makeInstance(FlexFormTools::class); + + $sheetDataXml = $flexFormTools->flexArray2Xml($sheetData); + return $flexFormService->convertFlexFormContentToArray($sheetDataXml)['settings'] ?? []; + } } diff --git a/typo3/sysext/form/Classes/Domain/Configuration/FlexformConfiguration/Processors/FinisherOptionGenerator.php b/typo3/sysext/form/Classes/Domain/Configuration/FlexformConfiguration/Processors/FinisherOptionGenerator.php index 0915a19aacf9..1a28cf2d29a6 100644 --- a/typo3/sysext/form/Classes/Domain/Configuration/FlexformConfiguration/Processors/FinisherOptionGenerator.php +++ b/typo3/sysext/form/Classes/Domain/Configuration/FlexformConfiguration/Processors/FinisherOptionGenerator.php @@ -67,14 +67,22 @@ public function __invoke(string $_, $__, array $matches) // use the option value from the ext:form setup from the current finisher as default value try { - $optionValue = ArrayUtility::getValueByPath($finisherDefinitionFromSetup['options'], $optionKey, '.'); + $optionValue = ArrayUtility::getValueByPath( + $finisherDefinitionFromSetup, + sprintf('options.%s', $optionKey), + '.' + ); } catch (MissingArrayPathException $exception) { $optionValue = null; } // use the option value from the form definition from the current finisher (if exists) as default value try { - $optionValue = ArrayUtility::getValueByPath($finisherDefinitionFromFormDefinition['options'], $optionKey, '.'); + $optionValue = ArrayUtility::getValueByPath( + $finisherDefinitionFromFormDefinition, + sprintf('options.%s', $optionKey), + '.' + ); } catch (MissingArrayPathException $exception) { } diff --git a/typo3/sysext/form/Tests/Unit/Controller/FormFrontendControllerTest.php b/typo3/sysext/form/Tests/Unit/Controller/FormFrontendControllerTest.php index 66a4656bc377..b8167095ad60 100644 --- a/typo3/sysext/form/Tests/Unit/Controller/FormFrontendControllerTest.php +++ b/typo3/sysext/form/Tests/Unit/Controller/FormFrontendControllerTest.php @@ -15,6 +15,11 @@ */ use Prophecy\Argument; +use TYPO3\CMS\Core\Cache\CacheManager; +use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface; +use TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Configuration\FrontendConfigurationManager; use TYPO3\CMS\Extbase\Object\ObjectManager; use TYPO3\CMS\Form\Controller\FormFrontendController; use TYPO3\CMS\Form\Domain\Configuration\ConfigurationService; @@ -26,6 +31,21 @@ */ class FormFrontendControllerTest extends UnitTestCase { + public function setUp() + { + $cacheManagerProphecy = $this->prophesize(CacheManager::class); + GeneralUtility::setSingletonInstance(CacheManager::class, $cacheManagerProphecy->reveal()); + $cacheFrontendProphecy = $this->prophesize(FrontendInterface::class); + $cacheManagerProphecy->getCache('cache_runtime')->willReturn($cacheFrontendProphecy->reveal()); + $cacheFrontendProphecy->get(Argument::cetera())->willReturn(false); + $cacheFrontendProphecy->set(Argument::cetera())->willReturn(null); + } + + public function tearDown() + { + GeneralUtility::purgeInstances(); + parent::tearDown(); + } /** * @test @@ -45,6 +65,38 @@ public function overrideByFlexFormSettingsReturnsNoOverriddenConfigurationIfFlex ->with(ConfigurationService::class) ->willReturn($configurationServiceProphecy->reveal()); + $sheetIdentifier = md5( + implode('', [ + '1:/foo', + 'standard', + 'ext-form-identifier', + 'EmailToReceiver' + ]) + ); + + $flexFormTools = new FlexFormTools; + $contentObject = new \stdClass(); + $contentObject->data = [ + 'pi_flexform' => $flexFormTools->flexArray2Xml([ + 'data' => [ + $sheetIdentifier => [ + 'lDEF' => [ + 'settings.finishers.EmailToReceiver.subject' => ['vDEF' => 'Mesage Subject overridden'], + 'settings.finishers.EmailToReceiver.recipientAddress' => ['vDEF' => 'your.company@example.com overridden'], + 'settings.finishers.EmailToReceiver.format' => ['vDEF' => 'html overridden'], + ], + ], + ], + ]), + ]; + + $frontendConfigurationManager = $this->createMock(FrontendConfigurationManager::class); + $frontendConfigurationManager + ->expects($this->any()) + ->method('getContentObject') + ->willReturn($contentObject); + + $mockController->_set('configurationManager', $frontendConfigurationManager); $mockController->_set('objectManager', $objectManagerMock); $configurationServiceProphecy->getPrototypeConfiguration(Argument::cetera())->willReturn([ @@ -63,17 +115,11 @@ public function overrideByFlexFormSettingsReturnsNoOverriddenConfigurationIfFlex $mockController->_set('settings', [ 'overrideFinishers' => 0, - 'finishers' => [ - 'EmailToReceiver' => [ - 'subject' => 'Mesage Subject overridden', - 'recipientAddress' => 'your.company@example.com overridden', - 'format' => 'html overridden', - ], - ], ]); $input = [ 'identifier' => 'ext-form-identifier', + 'persistenceIdentifier' => '1:/foo', 'prototypeName' => 'standard', 'finishers' => [ 0 => [ @@ -89,6 +135,7 @@ public function overrideByFlexFormSettingsReturnsNoOverriddenConfigurationIfFlex $expected = [ 'identifier' => 'ext-form-identifier', + 'persistenceIdentifier' => '1:/foo', 'prototypeName' => 'standard', 'finishers' => [ 0 => [ @@ -123,6 +170,38 @@ public function overrideByFlexFormSettingsReturnsOverriddenConfigurationIfFlexfo ->with(ConfigurationService::class) ->willReturn($configurationServiceProphecy->reveal()); + $sheetIdentifier = md5( + implode('', [ + '1:/foo', + 'standard', + 'ext-form-identifier', + 'EmailToReceiver' + ]) + ); + + $flexFormTools = new FlexFormTools; + $contentObject = new \stdClass(); + $contentObject->data = [ + 'pi_flexform' => $flexFormTools->flexArray2Xml([ + 'data' => [ + $sheetIdentifier => [ + 'lDEF' => [ + 'settings.finishers.EmailToReceiver.subject' => ['vDEF' => 'Mesage Subject overridden'], + 'settings.finishers.EmailToReceiver.recipientAddress' => ['vDEF' => 'your.company@example.com overridden'], + 'settings.finishers.EmailToReceiver.format' => ['vDEF' => 'html overridden'], + ], + ], + ], + ]), + ]; + + $frontendConfigurationManager = $this->createMock(FrontendConfigurationManager::class); + $frontendConfigurationManager + ->expects($this->any()) + ->method('getContentObject') + ->willReturn($contentObject); + + $mockController->_set('configurationManager', $frontendConfigurationManager); $mockController->_set('objectManager', $objectManagerMock); $configurationServiceProphecy->getPrototypeConfiguration(Argument::cetera())->willReturn([ @@ -141,16 +220,10 @@ public function overrideByFlexFormSettingsReturnsOverriddenConfigurationIfFlexfo $mockController->_set('settings', [ 'overrideFinishers' => 1, - 'finishers' => [ - 'EmailToReceiver' => [ - 'subject' => 'Mesage Subject overridden', - 'recipientAddress' => 'your.company@example.com overridden', - 'format' => 'html overridden', - ], - ], ]); $input = [ + 'persistenceIdentifier' => '1:/foo', 'identifier' => 'ext-form-identifier', 'prototypeName' => 'standard', 'finishers' => [ @@ -166,6 +239,7 @@ public function overrideByFlexFormSettingsReturnsOverriddenConfigurationIfFlexfo ]; $expected = [ + 'persistenceIdentifier' => '1:/foo', 'identifier' => 'ext-form-identifier', 'prototypeName' => 'standard', 'finishers' => [ @@ -201,6 +275,38 @@ public function overrideByFlexFormSettingsReturnsNotOverriddenConfigurationKeyIf ->with(ConfigurationService::class) ->willReturn($configurationServiceProphecy->reveal()); + $sheetIdentifier = md5( + implode('', [ + '1:/foo', + 'standard', + 'ext-form-identifier', + 'EmailToReceiver' + ]) + ); + + $flexFormTools = new FlexFormTools; + $contentObject = new \stdClass(); + $contentObject->data = [ + 'pi_flexform' => $flexFormTools->flexArray2Xml([ + 'data' => [ + $sheetIdentifier => [ + 'lDEF' => [ + 'settings.finishers.EmailToReceiver.subject' => ['vDEF' => 'Mesage Subject overridden'], + 'settings.finishers.EmailToReceiver.recipientAddress' => ['vDEF' => 'your.company@example.com overridden'], + 'settings.finishers.EmailToReceiver.format' => ['vDEF' => 'html overridden'], + ], + ], + ], + ]), + ]; + + $frontendConfigurationManager = $this->createMock(FrontendConfigurationManager::class); + $frontendConfigurationManager + ->expects($this->any()) + ->method('getContentObject') + ->willReturn($contentObject); + + $mockController->_set('configurationManager', $frontendConfigurationManager); $mockController->_set('objectManager', $objectManagerMock); $configurationServiceProphecy->getPrototypeConfiguration(Argument::cetera())->willReturn([ @@ -228,6 +334,7 @@ public function overrideByFlexFormSettingsReturnsNotOverriddenConfigurationKeyIf ]); $input = [ + 'persistenceIdentifier' => '1:/foo', 'identifier' => 'ext-form-identifier', 'prototypeName' => 'standard', 'finishers' => [ @@ -243,6 +350,7 @@ public function overrideByFlexFormSettingsReturnsNotOverriddenConfigurationKeyIf ]; $expected = [ + 'persistenceIdentifier' => '1:/foo', 'identifier' => 'ext-form-identifier', 'prototypeName' => 'standard', 'finishers' => [ @@ -260,6 +368,127 @@ public function overrideByFlexFormSettingsReturnsNotOverriddenConfigurationKeyIf $this->assertSame($expected, $mockController->_call('overrideByFlexFormSettings', $input)); } + /** + * @test + */ + public function overrideByFlexFormSettingsReturnsOverriddenConfigurationWhileMultipleSheetsExists() + { + $mockController = $this->getAccessibleMock(FormFrontendController::class, [ + 'dummy' + ], [], '', false); + + $configurationServiceProphecy = $this->prophesize(ConfigurationService::class); + + $objectManagerMock = $this->createMock(ObjectManager::class); + $objectManagerMock + ->expects($this->any()) + ->method('get') + ->with(ConfigurationService::class) + ->willReturn($configurationServiceProphecy->reveal()); + + $sheetIdentifier = md5( + implode('', [ + '1:/foo', + 'standard', + 'ext-form-identifier', + 'EmailToReceiver' + ]) + ); + + $anotherSheetIdentifier = md5( + implode('', [ + '1:/foobar', + 'standard', + 'another-ext-form-identifier', + 'EmailToReceiver' + ]) + ); + + $flexFormTools = new FlexFormTools; + $contentObject = new \stdClass(); + $contentObject->data = [ + 'pi_flexform' => $flexFormTools->flexArray2Xml([ + 'data' => [ + $sheetIdentifier => [ + 'lDEF' => [ + 'settings.finishers.EmailToReceiver.subject' => ['vDEF' => 'Mesage Subject overridden 1'], + 'settings.finishers.EmailToReceiver.recipientAddress' => ['vDEF' => 'your.company@example.com overridden 1'], + 'settings.finishers.EmailToReceiver.format' => ['vDEF' => 'html overridden 1'], + ], + ], + $anotherSheetIdentifier => [ + 'lDEF' => [ + 'settings.finishers.EmailToReceiver.subject' => ['vDEF' => 'Mesage Subject overridden 2'], + 'settings.finishers.EmailToReceiver.recipientAddress' => ['vDEF' => 'your.company@example.com overridden 2'], + 'settings.finishers.EmailToReceiver.format' => ['vDEF' => 'html overridden 2'], + ], + ], + ], + ]), + ]; + + $frontendConfigurationManager = $this->createMock(FrontendConfigurationManager::class); + $frontendConfigurationManager + ->expects($this->any()) + ->method('getContentObject') + ->willReturn($contentObject); + + $mockController->_set('configurationManager', $frontendConfigurationManager); + $mockController->_set('objectManager', $objectManagerMock); + + $configurationServiceProphecy->getPrototypeConfiguration(Argument::cetera())->willReturn([ + 'finishersDefinition' => [ + 'EmailToReceiver' => [ + 'FormEngine' => [ + 'elements' => [ + 'subject' => ['config' => ['type' => 'input']], + 'recipientAddress' => ['config' => ['type' => 'input']], + 'format' => ['config' => ['type' => 'input']], + ], + ], + ], + ], + ]); + + $mockController->_set('settings', [ + 'overrideFinishers' => 1, + ]); + + $input = [ + 'persistenceIdentifier' => '1:/foo', + 'identifier' => 'ext-form-identifier', + 'prototypeName' => 'standard', + 'finishers' => [ + 0 => [ + 'identifier' => 'EmailToReceiver', + 'options' => [ + 'subject' => 'Mesage Subject', + 'recipientAddress' => 'your.company@example.com', + 'format' => 'html', + ], + ], + ], + ]; + + $expected = [ + 'persistenceIdentifier' => '1:/foo', + 'identifier' => 'ext-form-identifier', + 'prototypeName' => 'standard', + 'finishers' => [ + 0 => [ + 'identifier' => 'EmailToReceiver', + 'options' => [ + 'subject' => 'Mesage Subject overridden 1', + 'recipientAddress' => 'your.company@example.com overridden 1', + 'format' => 'html overridden 1', + ], + ], + ], + ]; + + $this->assertSame($expected, $mockController->_call('overrideByFlexFormSettings', $input)); + } + /** * @test */