[FEATURE] Add native browser lazy loading for images
[Packages/TYPO3.CMS.git] / typo3 / sysext / form / Classes / Controller / FormFrontendController.php
1 <?php
2 declare(strict_types = 1);
3 namespace TYPO3\CMS\Form\Controller;
4
5 /*
6 * This file is part of the TYPO3 CMS project.
7 *
8 * It is free software; you can redistribute it and/or modify it under
9 * the terms of the GNU General Public License, either version 2
10 * of the License, or any later version.
11 *
12 * For the full copyright and license information, please read the
13 * LICENSE.txt file that was distributed with this source code.
14 *
15 * The TYPO3 project - inspiring people to share!
16 */
17
18 use TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools;
19 use TYPO3\CMS\Core\Service\FlexFormService;
20 use TYPO3\CMS\Core\Utility\ArrayUtility;
21 use TYPO3\CMS\Core\Utility\GeneralUtility;
22 use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
23 use TYPO3\CMS\Form\Domain\Configuration\ArrayProcessing\ArrayProcessing;
24 use TYPO3\CMS\Form\Domain\Configuration\ArrayProcessing\ArrayProcessor;
25 use TYPO3\CMS\Form\Domain\Configuration\ConfigurationService;
26 use TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Converters\FinisherOptionsFlexFormOverridesConverter;
27 use TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Converters\FlexFormFinisherOverridesConverterDto;
28 use TYPO3\CMS\Form\Mvc\Configuration\TypoScriptService;
29
30 /**
31 * The frontend controller
32 *
33 * Scope: frontend
34 * @internal
35 */
36 class FormFrontendController extends ActionController
37 {
38
39 /**
40 * @var \TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManagerInterface
41 */
42 protected $formPersistenceManager;
43
44 /**
45 * @param \TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManagerInterface $formPersistenceManager
46 * @internal
47 */
48 public function injectFormPersistenceManager(\TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManagerInterface $formPersistenceManager)
49 {
50 $this->formPersistenceManager = $formPersistenceManager;
51 }
52
53 /**
54 * Take the form which should be rendered from the plugin settings
55 * and overlay the formDefinition with additional data from
56 * flexform and typoscript settings.
57 * This method is used directly to display the first page from the
58 * formDefinition because its cached.
59 *
60 * @internal
61 */
62 public function renderAction()
63 {
64 $formDefinition = [];
65 if (!empty($this->settings['persistenceIdentifier'])) {
66 $formDefinition = $this->formPersistenceManager->load($this->settings['persistenceIdentifier']);
67 $formDefinition['persistenceIdentifier'] = $this->settings['persistenceIdentifier'];
68 $formDefinition = $this->overrideByTypoScriptSettings($formDefinition);
69 $formDefinition = $this->overrideByFlexFormSettings($formDefinition);
70 $formDefinition = ArrayUtility::setValueByPath($formDefinition, 'renderingOptions._originalIdentifier', $formDefinition['identifier'], '.');
71 $formDefinition['identifier'] .= '-' . $this->configurationManager->getContentObject()->data['uid'];
72 }
73 $this->view->assign('formConfiguration', $formDefinition);
74 }
75
76 /**
77 * This method is used to display all pages / finishers except the
78 * first page because its non cached.
79 *
80 * @internal
81 */
82 public function performAction()
83 {
84 $this->forward('render');
85 }
86
87 /**
88 * Override the formDefinition with additional data from the Flexform
89 * settings. For now, only finisher settings are overridable.
90 *
91 * @param array $formDefinition
92 * @return array
93 */
94 protected function overrideByFlexFormSettings(array $formDefinition): array
95 {
96 $flexFormData = GeneralUtility::xml2array($this->configurationManager->getContentObject()->data['pi_flexform'] ?? '');
97
98 if (!is_array($flexFormData)) {
99 return $formDefinition;
100 }
101
102 if (isset($formDefinition['finishers'])) {
103 $prototypeName = $formDefinition['prototypeName'] ?? 'standard';
104 $configurationService = $this->objectManager->get(ConfigurationService::class);
105 $prototypeConfiguration = $configurationService->getPrototypeConfiguration($prototypeName);
106
107 foreach ($formDefinition['finishers'] as $index => $formFinisherDefinition) {
108 $finisherIdentifier = $formFinisherDefinition['identifier'];
109
110 $sheetIdentifier = $this->getFlexformSheetIdentifier($formDefinition, $prototypeName, $finisherIdentifier);
111 $flexFormSheetSettings = $this->getFlexFormSettingsFromSheet($flexFormData, $sheetIdentifier);
112
113 if ($this->settings['overrideFinishers'] && isset($flexFormSheetSettings['finishers'][$finisherIdentifier])) {
114 $prototypeFinisherDefinition = $prototypeConfiguration['finishersDefinition'][$finisherIdentifier] ?? [];
115 $converterDto = GeneralUtility::makeInstance(
116 FlexFormFinisherOverridesConverterDto::class,
117 $prototypeFinisherDefinition,
118 $formFinisherDefinition,
119 $finisherIdentifier,
120 $flexFormSheetSettings
121 );
122
123 // Iterate over all `TYPO3.CMS.Form.prototypes.<prototypeName>.finishersDefinition.<finisherIdentifier>.FormEngine.elements` values
124 GeneralUtility::makeInstance(ArrayProcessor::class, $prototypeFinisherDefinition['FormEngine']['elements'])->forEach(
125 GeneralUtility::makeInstance(
126 ArrayProcessing::class,
127 'modifyFinisherOptionsFromFlexFormOverrides',
128 '^(.*)(?:(?<!\.TCEforms)\.config\.type|\.section)$',
129 GeneralUtility::makeInstance(FinisherOptionsFlexFormOverridesConverter::class, $converterDto)
130 )
131 );
132
133 $formDefinition['finishers'][$index] = $converterDto->getFinisherDefinition();
134 }
135 }
136 }
137 return $formDefinition;
138 }
139
140 /**
141 * Every formDefinition setting are overridable by typoscript.
142 * If the typoscript configuration path
143 * plugin.tx_form.settings.formDefinitionOverrides.<identifier>
144 * exists, this settings are merged into the formDefinition.
145 *
146 * @param array $formDefinition
147 * @return array
148 */
149 protected function overrideByTypoScriptSettings(array $formDefinition): array
150 {
151 if (
152 isset($this->settings['formDefinitionOverrides'][$formDefinition['identifier']])
153 && !empty($this->settings['formDefinitionOverrides'][$formDefinition['identifier']])
154 ) {
155 ArrayUtility::mergeRecursiveWithOverrule(
156 $formDefinition,
157 $this->settings['formDefinitionOverrides'][$formDefinition['identifier']]
158 );
159 $formDefinition = $this->objectManager->get(TypoScriptService::class)
160 ->resolvePossibleTypoScriptConfiguration($formDefinition);
161 }
162 return $formDefinition;
163 }
164
165 /**
166 * @param array $formDefinition
167 * @param string $prototypeName
168 * @param string $finisherIdentifier
169 * @return string
170 */
171 protected function getFlexformSheetIdentifier(
172 array $formDefinition,
173 string $prototypeName,
174 string $finisherIdentifier
175 ): string {
176 return md5(
177 implode('', [
178 $formDefinition['persistenceIdentifier'],
179 $prototypeName,
180 $formDefinition['identifier'],
181 $finisherIdentifier
182 ])
183 );
184 }
185
186 /**
187 * @param array $flexForm
188 * @param string $sheetIdentifier
189 * @return array
190 */
191 protected function getFlexFormSettingsFromSheet(
192 array $flexForm,
193 string $sheetIdentifier
194 ): array {
195 $sheetData = [];
196 $sheetData['data'] = array_filter(
197 $flexForm['data'] ?? [],
198 function ($key) use ($sheetIdentifier) {
199 return $key === $sheetIdentifier;
200 },
201 ARRAY_FILTER_USE_KEY
202 );
203
204 if (empty($sheetData['data'])) {
205 return [];
206 }
207
208 $flexFormService = GeneralUtility::makeInstance(FlexFormService::class);
209 $flexFormTools = GeneralUtility::makeInstance(FlexFormTools::class);
210
211 $sheetDataXml = $flexFormTools->flexArray2Xml($sheetData);
212 return $flexFormService->convertFlexFormContentToArray($sheetDataXml)['settings'] ?? [];
213 }
214 }