[FEATURE] EXT:form - integrate new form framework
[Packages/TYPO3.CMS.git] / typo3 / sysext / form / Classes / Hooks / DataStructureIdentifierHook.php
1 <?php
2 declare(strict_types=1);
3 namespace TYPO3\CMS\Form\Hooks;
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\Utility\ArrayUtility as CoreArrayUtility;
19 use TYPO3\CMS\Core\Utility\GeneralUtility;
20 use TYPO3\CMS\Extbase\Object\ObjectManager;
21 use TYPO3\CMS\Extbase\Utility\ArrayUtility as ExtbaseArrayUtility;
22 use TYPO3\CMS\Form\Domain\Configuration\ConfigurationService;
23 use TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManagerInterface;
24 use TYPO3\CMS\Form\Service\TranslationService;
25
26 /**
27 * Hooks into flex form handling of backend for tt_content form elements:
28 *
29 * * Adds existing forms to flex form drop down list
30 * * Adds finisher settings if "override finishers" is active
31 *
32 * Scope: backend
33 * @internal
34 */
35 class DataStructureIdentifierHook
36 {
37
38 /**
39 * The data structure depends on a current form selection (persistenceIdentifier)
40 * and if the field "overrideFinishers" is active. Add both to the identifier to
41 * hand these information over to parseDataStructureByIdentifierPostProcess() hook.
42 *
43 * @param array $fieldTca Incoming field TCA
44 * @param string $tableName Handled table
45 * @param string $fieldName Handled field
46 * @param array $row Current data row
47 * @param array $identifier Already calculated identifier
48 * @return array Modified identifier
49 */
50 public function getDataStructureIdentifierPostProcess(
51 array $fieldTca,
52 string $tableName,
53 string $fieldName,
54 array $row,
55 array $identifier
56 ): array {
57 if ($tableName === 'tt_content' && $fieldName === 'pi_flexform' && $row['CType'] === 'form_formframework') {
58 $currentFlexData = [];
59 if (!is_array($row['pi_flexform']) && !empty($row['pi_flexform'])) {
60 $currentFlexData = GeneralUtility::xml2array($row['pi_flexform']);
61 }
62
63 // Add selected form value
64 $identifier['ext-form-persistenceIdentifier'] = '';
65 if (!empty($currentFlexData['data']['sDEF']['lDEF']['settings.persistenceIdentifier']['vDEF'])) {
66 $identifier['ext-form-persistenceIdentifier'] = $currentFlexData['data']['sDEF']['lDEF']['settings.persistenceIdentifier']['vDEF'];
67 }
68
69 // Add bool - finisher override active or not
70 $identifier['ext-form-overrideFinishers'] = false;
71 if (
72 isset($currentFlexData['data']['sDEF']['lDEF']['settings.overrideFinishers']['vDEF'])
73 && (int)$currentFlexData['data']['sDEF']['lDEF']['settings.overrideFinishers']['vDEF'] === 1
74 ) {
75 $identifier['ext-form-overrideFinishers'] = true;
76 }
77 }
78 return $identifier;
79 }
80
81 /**
82 * Returns a modified flexform data array.
83 *
84 * This adds the list of existing form definitions to the form selection drop down
85 * and adds sheets to override finisher settings if requested.
86 *
87 * @param array $dataStructure
88 * @param array $identifier
89 * @return array
90 */
91 public function parseDataStructureByIdentifierPostProcess(array $dataStructure, array $identifier): array
92 {
93 if (isset($identifier['ext-form-persistenceIdentifier'])) {
94 // Add list of existing forms to drop down if we find our key in the identifier
95 $formPersistenceManager = GeneralUtility::makeInstance(ObjectManager::class)->get(FormPersistenceManagerInterface::class);
96 foreach ($formPersistenceManager->listForms() as $form) {
97 $dataStructure['sheets']['sDEF']['ROOT']['el']['settings.persistenceIdentifier']['TCEforms']['config']['items'][] = [
98 $form['name'] . ' (' . $form['persistenceIdentifier'] . ')',
99 $form['persistenceIdentifier'],
100 ];
101 }
102
103 // If a specific form is selected and if finisher override is active, add finisher sheets
104 if (!empty($identifier['ext-form-persistenceIdentifier'])
105 && isset($identifier['ext-form-overrideFinishers'])
106 && $identifier['ext-form-overrideFinishers'] === true
107 ) {
108 $persistenceIdentifier = $identifier['ext-form-persistenceIdentifier'];
109 $formDefinition = $formPersistenceManager->load($persistenceIdentifier);
110 $newSheets = $this->getAdditionalFinisherSheets($persistenceIdentifier, $formDefinition);
111 CoreArrayUtility::mergeRecursiveWithOverrule(
112 $dataStructure,
113 $newSheets
114 );
115 }
116 }
117 return $dataStructure;
118 }
119
120 /**
121 * Returns additional flexform sheets with finisher fields
122 *
123 * @param string $persistenceIdentifier Current persistence identifier
124 * @param array $formDefinition The form definition
125 * @return array
126 */
127 protected function getAdditionalFinisherSheets(string $persistenceIdentifier, array $formDefinition): array
128 {
129 if (!isset($formDefinition['finishers']) || empty($formDefinition['finishers'])) {
130 return [];
131 }
132
133 $prototypeName = isset($formDefinition['prototypeName']) ? $formDefinition['prototypeName'] : 'standard';
134 $prototypeConfiguration = GeneralUtility::makeInstance(ObjectManager::class)
135 ->get(ConfigurationService::class)
136 ->getPrototypeConfiguration($prototypeName);
137
138 if (!isset($prototypeConfiguration['finishersDefinition']) || empty($prototypeConfiguration['finishersDefinition'])) {
139 return [];
140 }
141
142 $formIdentifier = $formDefinition['identifier'];
143 $finishersDefinition = $prototypeConfiguration['finishersDefinition'];
144
145 $sheets = ['sheets' => []];
146 foreach ($formDefinition['finishers'] as $finisherValue) {
147 $finisherIdentifier = $finisherValue['identifier'];
148 if (!isset($finishersDefinition[$finisherIdentifier]['FormEngine']['elements'])) {
149 continue;
150 }
151 $sheetIdentifier = md5(
152 implode('', [
153 $persistenceIdentifier,
154 $prototypeName,
155 $formIdentifier,
156 $finisherIdentifier
157 ])
158 );
159
160 $translationFile = $finishersDefinition[$finisherIdentifier]['FormEngine']['translationFile'];
161 $finishersDefinition[$finisherIdentifier]['FormEngine'] = TranslationService::getInstance()->translateValuesRecursive(
162 $finishersDefinition[$finisherIdentifier]['FormEngine'],
163 $translationFile
164 );
165 $finisherLabel = $finishersDefinition[$finisherIdentifier]['FormEngine']['label'];
166 $sheet = $this->initializeNewSheetArray($sheetIdentifier, $finisherLabel);
167
168 $sheetElements = [];
169 foreach ($finisherValue['options'] as $optionKey => $optionValue) {
170 if (is_array($optionValue)) {
171 $optionKey = $optionKey . '.' . $this->extractDottedPathToLastElement($finisherValue['options'][$optionKey]);
172 $elementConfiguration = ExtbaseArrayUtility::getValueByPath($finishersDefinition[$finisherIdentifier]['FormEngine']['elements'], $optionKey);
173 $optionValue = ExtbaseArrayUtility::getValueByPath($finisherValue['options'], $optionKey);
174 } else {
175 $elementConfiguration = $finishersDefinition[$finisherIdentifier]['FormEngine']['elements'][$optionKey];
176 }
177
178 if (empty($elementConfiguration)) {
179 continue;
180 }
181
182 if (empty($optionValue)) {
183 $elementConfiguration['label'] .= ' (default: "[Empty]")';
184 } else {
185 $elementConfiguration['label'] .= ' (default: "' . $optionValue . '")';
186 }
187 $elementConfiguration['config']['default'] = $optionValue;
188 $sheetElements['settings.finishers.' . $finisherIdentifier . '.' . $optionKey] = $elementConfiguration;
189 }
190
191 ksort($sheetElements);
192
193 $sheet[$sheetIdentifier]['ROOT']['el'] = $sheetElements;
194 CoreArrayUtility::mergeRecursiveWithOverrule($sheets['sheets'], $sheet);
195 }
196 if (empty($sheets['sheets'])) {
197 return [];
198 }
199
200 return $sheets;
201 }
202
203 /**
204 * Boilerplate XML array of a new sheet
205 *
206 * @param string $sheetIdentifier
207 * @param string $finisherName
208 * @throws \InvalidArgumentException
209 * @return array
210 */
211 protected function initializeNewSheetArray(string $sheetIdentifier, string $finisherName): array
212 {
213 if (empty($sheetIdentifier)) {
214 throw new \InvalidArgumentException('$sheetIdentifier must not be empty.', 1472060918);
215 }
216 if (empty($finisherName)) {
217 throw new \InvalidArgumentException('$finisherName must not be empty.', 1472060919);
218 }
219
220 return [
221 $sheetIdentifier => [
222 'ROOT' => [
223 'TCEforms' => [
224 'sheetTitle' => $finisherName,
225 ],
226 'type' => 'array',
227 'el' => [],
228 ],
229 ],
230 ];
231 }
232
233 /**
234 * Recursive helper to implode a nested array to a dotted path notation
235 *
236 * @param array $array
237 * @return string
238 */
239 protected function extractDottedPathToLastElement(array $array): string
240 {
241 $dottedPath = key($array);
242 foreach ($array as $key => $value) {
243 if (is_array($value)) {
244 $dottedPath = $dottedPath . '.' . $this->extractDottedPathToLastElement($value);
245 }
246 }
247 return $dottedPath;
248 }
249 }