[!!!][TASK] Improve flex and TCA handling in FormEngine
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / Form / FormDataProvider / TcaFlexProcess.php
1 <?php
2 namespace TYPO3\CMS\Backend\Form\FormDataProvider;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use TYPO3\CMS\Backend\Form\FormDataCompiler;
18 use TYPO3\CMS\Backend\Form\FormDataGroup\FlexFormSegment;
19 use TYPO3\CMS\Backend\Form\FormDataProviderInterface;
20 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
21 use TYPO3\CMS\Core\Utility\GeneralUtility;
22
23 /**
24 * Process data structures and data values, calculate defaults.
25 *
26 * This is typically the last provider, executed after TcaFlexPrepare
27 */
28 class TcaFlexProcess implements FormDataProviderInterface
29 {
30 /**
31 * Determine possible pageTsConfig overrides and apply them to ds.
32 * Determine available languages and sanitize ds for further processing. Then kick
33 * and validate further details like excluded fields. Finally for each possible
34 * value and ds, call FormDataCompiler with set FlexFormSegment group to resolve
35 * single field stuff like item processor functions.
36 *
37 * @param array $result
38 * @throws \RuntimeException
39 * @return array
40 */
41 public function addData(array $result)
42 {
43 foreach ($result['processedTca']['columns'] as $fieldName => $fieldConfig) {
44 if (empty($fieldConfig['config']['type']) || $fieldConfig['config']['type'] !== 'flex') {
45 continue;
46 }
47 if (!isset($result['processedTca']['columns'][$fieldName]['config']['dataStructureIdentifier'])) {
48 throw new \RuntimeException(
49 'Data structure identifier must be set, typically by executing TcaFlexPrepare data provider before',
50 1480765571
51 );
52 }
53 $this->scanForInvalidSectionContainerTca($result, $fieldName);
54 $dataStructureIdentifier = $result['processedTca']['columns'][$fieldName]['config']['dataStructureIdentifier'];
55 $simpleDataStructureIdentifier = $this->getSimplifiedDataStructureIdentifier($dataStructureIdentifier);
56 $pageTsConfigOfFlex = $this->getPageTsOfFlex($result, $fieldName, $simpleDataStructureIdentifier);
57 $result = $this->modifyOuterDataStructure($result, $fieldName, $pageTsConfigOfFlex);
58 $result = $this->removeExcludeFieldsFromDataStructure($result, $fieldName, $simpleDataStructureIdentifier);
59 $result = $this->removeDisabledFieldsFromDataStructure($result, $fieldName, $pageTsConfigOfFlex);
60 // A "normal" call opening a record: Process data structure and field values
61 // This is called for "new" container ajax request too, since display conditions from section container
62 // elements can access record values of other flex form sheets and we need their values then.
63 $result = $this->modifyDataStructureAndDataValuesByFlexFormSegmentGroup($result, $fieldName, $pageTsConfigOfFlex);
64 if (!empty($result['flexSectionContainerPreparation'])) {
65 // Create data and default values for a new section container, set by FormFlexAjaxController
66 $result = $this->prepareNewSectionContainer($result, $fieldName);
67 }
68 }
69
70 return $result;
71 }
72
73 /**
74 * Some TCA combinations like inline or nesting a section into a section container is not
75 * supported and throws exceptions.
76 *
77 * @param array $result Result array
78 * @param string $fieldName Handled field name
79 * @return void
80 * @throws \UnexpectedValueException
81 */
82 protected function scanForInvalidSectionContainerTca(array $result, string $fieldName)
83 {
84 $dataStructure = $result['processedTca']['columns'][$fieldName]['config']['ds'];
85 if (!isset($dataStructure['sheets']) || !is_array($dataStructure['sheets'])) {
86 return;
87 }
88 foreach ($dataStructure['sheets'] as $dataStructureSheetName => $dataStructureSheetDefinition) {
89 if (!isset($dataStructureSheetDefinition['ROOT']['el']) || !is_array($dataStructureSheetDefinition['ROOT']['el'])) {
90 continue;
91 }
92 $dataStructureFields = $dataStructureSheetDefinition['ROOT']['el'];
93 foreach ($dataStructureFields as $dataStructureFieldName => $dataStructureFieldDefinition) {
94 if (isset($dataStructureFieldDefinition['type']) && $dataStructureFieldDefinition['type'] === 'array'
95 && isset($dataStructureFieldDefinition['section']) && (string)$dataStructureFieldDefinition['section'] === '1'
96 ) {
97 if (isset($dataStructureFieldDefinition['el']) && is_array($dataStructureFieldDefinition['el'])) {
98 foreach ($dataStructureFieldDefinition['el'] as $containerName => $containerConfiguration) {
99 if (isset($containerConfiguration['el']) && is_array($containerConfiguration['el'])) {
100 foreach ($containerConfiguration['el'] as $singleFieldName => $singleFieldConfiguration) {
101 // Nesting type=inline in container sections is not supported. Throw an exception if configured.
102 if (isset($singleFieldConfiguration['config']['type']) && $singleFieldConfiguration['config']['type'] === 'inline') {
103 throw new \UnexpectedValueException(
104 'Invalid flex form data structure on field name "' . $fieldName . '" with element "' . $singleFieldName . '"'
105 . ' in section container "' . $containerName . '": Nesting inline elements in flex form'
106 . ' sections is not allowed.',
107 1458745468
108 );
109 }
110
111 // Nesting sections is not supported. Throw an exception if configured.
112 if (is_array($singleFieldConfiguration)
113 && isset($singleFieldConfiguration['type']) && $singleFieldConfiguration['type'] === 'array'
114 && isset($singleFieldConfiguration['section']) && (string)$singleFieldConfiguration['section'] === '1'
115 ) {
116 throw new \UnexpectedValueException(
117 'Invalid flex form data structure on field name "' . $fieldName . '" with element "' . $singleFieldName . '"'
118 . ' in section container "' . $containerName . '": Nesting sections in container elements'
119 . ' sections is not allowed.',
120 1458745712
121 );
122 }
123
124 // Nesting type="select" and type="group" within section containers is not supported,
125 // the data storage can not deal with that and in general it is not supported to add a
126 // named reference to the anonymous section container structure.
127 if (is_array($singleFieldConfiguration)
128 && isset($singleFieldConfiguration['config']['type'])
129 && ($singleFieldConfiguration['config']['type'] === 'group' || $singleFieldConfiguration['config']['type'] === 'select')
130 && array_key_exists('MM', $singleFieldConfiguration['config'])
131 ) {
132 throw new \UnexpectedValueException(
133 'Invalid flex form data structure on field name "' . $fieldName . '" with element "' . $singleFieldName . '"'
134 . ' in section container "' . $containerName . '": Nesting select and group elements in flex form'
135 . ' sections is not allowed with MM relations.',
136 1481647089
137 );
138 }
139 }
140 }
141 }
142 }
143 } elseif (isset($dataStructureFieldDefinition['type']) || isset($dataStructureFieldDefinition['section'])) {
144 // type without section is not ok
145 throw new \UnexpectedValueException(
146 'Broken data structure on field name ' . $fieldName . '. section without type or vice versa is not allowed',
147 1440685208
148 );
149 }
150 }
151 }
152 }
153
154 /**
155 * Calculate a simplified (and wrong) data structure identifier.
156 * This is used to find pageTsConfig options of flex fields and exclude field definitions later, see methods below.
157 * If the data structure identifier is not type=tca based and if dataStructureKey is not as expected, fallback is "default"
158 *
159 * Example pi_flexform with ext:news in tt_content:
160 * * TCA config of pi_flexform ds_pointerfield is set to "list_type,CType"
161 * * list_type in databaseRow is "news_pi1"
162 * * CType in databaseRow is "list"
163 * * The resulting dataStructureIdentifier calculated by FlexFormTools is then:
164 * {"type":"tca","tableName":"tt_content","fieldName":"pi_flexform","dataStructureKey":"news_pi1,list"}
165 * * The resulting simpleDataStructureIdentifier is "news_pi1"
166 * * The pageTsConfig base path used for flex field overrides is "TCEFORM.tt_content.pi_flexform.news_pi1", a full
167 * example path disabling a field: "TCEFORM.tt_content.pi_flexform.news_pi1.sDEF.settings\.orderBy.disabled = 1"
168 * * The exclude path for be_user exclude rights is "tt_content:pi_flexform;news_pi1", a full example:
169 * tt_content:pi_flexform;news_pi1;sDEF;settings.orderBy
170 *
171 * Notes:
172 * This approach is obviously limited. It is not possible to override flex form DS via pageTsConfig for other complex
173 * or dynamically created data structure definitions. And worse, the fallback to "default" may lead to naming clashes
174 * if two different data structures have identical sheet and field names.
175 * Also, the exclude field handling is limited and it is not possible to respect 'exclude' fields in flex form
176 * data structures if the dataStructureIdentifier is based on type="record" or manipulated by a hook in FlexFormTools.
177 * All that can only be solved by changing the pageTsConfig syntax referencing flex fields, probably by involving the whole
178 * data structure identifier and going away from this "simple" approach. For exclude fields there is the additional
179 * issue that the special="exclude" code is based on guess work, to find possible data structures. If this area here is
180 * changed and a pageTsConfig syntax change is raised, it would probably be a good idea to solve the access restrictions
181 * area at the same time - see the related methods that deal with flex field handling for special="exclude" for
182 * more comments on this.
183 * Another limitation is that the current syntax in both pageTsConfig and exclude fields does not
184 * consider flex form section containers at all.
185 *
186 * @param string $dataStructureIdentifier
187 * @return string
188 */
189 protected function getSimplifiedDataStructureIdentifier(string $dataStructureIdentifier): string
190 {
191 $identifierArray = json_decode($dataStructureIdentifier, true);
192 $simpleDataStructureIdentifier = 'default';
193 if (isset($identifierArray['type']) && $identifierArray['type'] === 'tca' && isset($identifierArray['dataStructureKey'])) {
194 $explodedKey = explode(',', $identifierArray['dataStructureKey']);
195 if (!empty($explodedKey[1]) && $explodedKey[1] !== 'list' && $explodedKey[1] !== '*') {
196 $simpleDataStructureIdentifier = $explodedKey[1];
197 } elseif (!empty($explodedKey[0]) && $explodedKey[0] !== 'list' && $explodedKey[0] !== '*') {
198 $simpleDataStructureIdentifier = $explodedKey[0];
199 }
200 }
201 return $simpleDataStructureIdentifier;
202 }
203
204 /**
205 * Determine TCEFORM.aTable.aField.matchingIdentifier
206 *
207 * @param array $result Result array
208 * @param string $fieldName Handled field name
209 * @param string $flexIdentifier Determined identifier
210 * @return array PageTsConfig for this flex
211 */
212 protected function getPageTsOfFlex(array $result, $fieldName, $flexIdentifier)
213 {
214 $table = $result['tableName'];
215 $pageTs = [];
216 if (!empty($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.'][$flexIdentifier . '.'])
217 && is_array($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.'][$flexIdentifier . '.'])) {
218 $pageTs = $result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.'][$flexIdentifier . '.'];
219 }
220 return $pageTs;
221 }
222
223 /**
224 * Handle "outer" flex data structure changes like language and sheet
225 * description. Does not change "TCA" or values of single elements
226 *
227 * @param array $result Result array
228 * @param string $fieldName Current handle field name
229 * @param array $pageTsConfig Given pageTsConfig of this flex form
230 * @return array Modified item array
231 */
232 protected function modifyOuterDataStructure(array $result, $fieldName, $pageTsConfig)
233 {
234 $modifiedDataStructure = $result['processedTca']['columns'][$fieldName]['config']['ds'];
235
236 if (isset($modifiedDataStructure['sheets']) && is_array($modifiedDataStructure['sheets'])) {
237 // Handling multiple sheets
238 foreach ($modifiedDataStructure['sheets'] as $sheetName => $sheetStructure) {
239 if (isset($pageTsConfig[$sheetName . '.']) && is_array($pageTsConfig[$sheetName . '.'])) {
240 $pageTsOfSheet = $pageTsConfig[$sheetName . '.'];
241
242 // Remove whole sheet if disabled
243 if (!empty($pageTsOfSheet['disabled'])) {
244 unset($modifiedDataStructure['sheets'][$sheetName]);
245 continue;
246 }
247
248 // sheetTitle, sheetDescription, sheetShortDescr
249 $modifiedDataStructure['sheets'][$sheetName] = $this->modifySingleSheetInformation($sheetStructure, $pageTsOfSheet);
250 }
251 }
252 }
253
254 $result['processedTca']['columns'][$fieldName]['config']['ds'] = $modifiedDataStructure;
255
256 return $result;
257 }
258
259 /**
260 * Removes fields from data structure the user has no access to
261 *
262 * @param array $result Result array
263 * @param string $fieldName Current handle field name
264 * @param string $flexIdentifier Determined identifier
265 * @return array Modified result
266 */
267 protected function removeExcludeFieldsFromDataStructure(array $result, $fieldName, $flexIdentifier)
268 {
269 $dataStructure = $result['processedTca']['columns'][$fieldName]['config']['ds'];
270 $backendUser = $this->getBackendUser();
271 if ($backendUser->isAdmin() || !isset($dataStructure['sheets']) || !is_array($dataStructure['sheets'])) {
272 return $result;
273 }
274
275 $userNonExcludeFields = GeneralUtility::trimExplode(',', $backendUser->groupData['non_exclude_fields']);
276 $excludeFieldsPrefix = $result['tableName'] . ':' . $fieldName . ';' . $flexIdentifier . ';';
277 $nonExcludeFields = [];
278 foreach ($userNonExcludeFields as $userNonExcludeField) {
279 if (strpos($userNonExcludeField, $excludeFieldsPrefix) !== false) {
280 $exploded = explode(';', $userNonExcludeField);
281 $sheetName = $exploded[2];
282 $allowedFlexFieldName = $exploded[3];
283 $nonExcludeFields[$sheetName][$allowedFlexFieldName] = true;
284 }
285 }
286 foreach ($dataStructure['sheets'] as $sheetName => $sheetDefinition) {
287 if (!isset($sheetDefinition['ROOT']['el']) || !is_array($sheetDefinition['ROOT']['el'])) {
288 continue;
289 }
290 foreach ($sheetDefinition['ROOT']['el'] as $flexFieldName => $fieldDefinition) {
291 if (!empty($fieldDefinition['exclude']) && !isset($nonExcludeFields[$sheetName][$flexFieldName])) {
292 unset($result['processedTca']['columns'][$fieldName]['config']['ds']['sheets'][$sheetName]['ROOT']['el'][$flexFieldName]);
293 }
294 }
295 }
296
297 return $result;
298 }
299
300 /**
301 * Remove fields from data structure that are disabled in pageTsConfig.
302 *
303 * @param array $result Result array
304 * @param string $fieldName Current handle field name
305 * @param array $pageTsConfig Given pageTsConfig of this flex form
306 * @return array Modified item array
307 */
308 protected function removeDisabledFieldsFromDataStructure(array $result, $fieldName, $pageTsConfig)
309 {
310 $dataStructure = $result['processedTca']['columns'][$fieldName]['config']['ds'];
311 if (!isset($dataStructure['sheets']) || !is_array($dataStructure['sheets'])) {
312 return $result;
313 }
314 foreach ($dataStructure['sheets'] as $sheetName => $sheetDefinition) {
315 if (!isset($sheetDefinition['ROOT']['el']) || !is_array($sheetDefinition['ROOT']['el'])
316 || !isset($pageTsConfig[$sheetName . '.'])) {
317 continue;
318 }
319 foreach ($sheetDefinition['ROOT']['el'] as $flexFieldName => $fieldDefinition) {
320 if (!empty($pageTsConfig[$sheetName . '.'][$flexFieldName . '.']['disabled'])) {
321 unset($result['processedTca']['columns'][$fieldName]['config']['ds']['sheets'][$sheetName]['ROOT']['el'][$flexFieldName]);
322 }
323 }
324 }
325 return $result;
326 }
327
328 /**
329 * Feed single flex field and data to FlexFormSegment FormData compiler and merge result.
330 * This one is nasty. Goal is to have processed TCA stuff in DS and also have validated / processed data values.
331 *
332 * Two main parts in this method:
333 * * Process values and TCA of existing section containers
334 * * Process TCA of "normal" fields
335 *
336 * @param array $result Result array
337 * @param string $fieldName Current handle field name
338 * @param array $pageTsConfig Given pageTsConfig of this flex form
339 * @return array Modified item array
340 * @throws \UnexpectedValueException
341 */
342 protected function modifyDataStructureAndDataValuesByFlexFormSegmentGroup(array $result, $fieldName, $pageTsConfig)
343 {
344 $dataStructure = $result['processedTca']['columns'][$fieldName]['config']['ds'];
345 $dataValues = $result['databaseRow'][$fieldName];
346 $tableName = $result['tableName'];
347
348 if (!isset($dataStructure['sheets']) || !is_array($dataStructure['sheets'])) {
349 return $result;
350 }
351
352 $formDataGroup = GeneralUtility::makeInstance(FlexFormSegment::class);
353 $formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup);
354
355 foreach ($dataStructure['sheets'] as $dataStructureSheetName => $dataStructureSheetDefinition) {
356 if (!isset($dataStructureSheetDefinition['ROOT']['el']) || !is_array($dataStructureSheetDefinition['ROOT']['el'])) {
357 continue;
358 }
359 $dataStructureFields = $dataStructureSheetDefinition['ROOT']['el'];
360
361 // Prepare pageTsConfig of this sheet
362 $pageTsConfig['TCEFORM.'][$tableName . '.'] = [];
363 if (isset($pageTsConfig[$dataStructureSheetName . '.']) && is_array($pageTsConfig[$dataStructureSheetName . '.'])) {
364 $pageTsConfig['TCEFORM.'][$tableName . '.'] = $pageTsConfig[$dataStructureSheetName . '.'];
365 }
366
367 // It is possible to have a flex field field with of foreign_table (eg. type=select) that has markers in
368 // a foreign_table_where like ###PAGE_TSCONFIG_ID###. It was possible to set this in page TSConfig for flex fields like this:
369 // TCEFORM.theTable.theFlexfield.PAGE_TSCONFIG_ID = 42
370 // This hands over this PAGE_TSCONFIG_ID to all flex fields that have this foreign_table_where marker.
371 // This is a contradiction to the "usual" page TSConfig flex configuration that should be done for single flex fields:
372 // TCEFORM.theTable.theFlexfield.theDataStructure.theSheet.theField.PAGE_TSCONFIG_ID = 42
373 // The below code is a hack to still simulate the old behavior that is now deprecated.
374 // @deprecated since TYPO3 v8, will be removed in TYPO3 v9
375 // When deleting this code and comment block, the according code within AbstractItemProvider can be removed, too.
376 if (isset($result['pageTsConfig']['TCEFORM.'][$tableName . '.'][$fieldName . '.']['PAGE_TSCONFIG_ID'])) {
377 GeneralUtility::deprecationLog(
378 'The page TSConfig setting TCEFORM.' . $tableName . '.' . $fieldName . '.PAGE_TSCONFIG_ID for flex forms'
379 . ' is deprecated. Use this setting for single flex fields instead, example: TCEFORM.' . $tableName . '.'
380 . $fieldName . '.theDataStructureName.theSheet.theFieldName.PAGE_TSCONFIG_ID. Be aware these settings are'
381 . ' no longer allowed for fields within flex form section container elements.'
382 );
383 $pageTsConfig['flexHack.']['PAGE_TSCONFIG_ID'] = $result['pageTsConfig']['TCEFORM.'][$tableName . '.'][$fieldName . '.']['PAGE_TSCONFIG_ID'];
384 }
385 if (isset($result['pageTsConfig']['TCEFORM.'][$tableName . '.'][$fieldName . '.']['PAGE_TSCONFIG_IDLIST'])) {
386 GeneralUtility::deprecationLog(
387 'The page TSConfig setting TCEFORM.' . $tableName . '.' . $fieldName . '.PAGE_TSCONFIG_IDLIST for flex forms'
388 . ' is deprecated. Use this setting for single flex fields instead, example: TCEFORM.' . $tableName . '.'
389 . $fieldName . '.theDataStructureName.theSheet.theFieldName.PAGE_TSCONFIG_IDLIST. Be aware these settings are'
390 . ' no longer allowed for fields within flex form section container elements.'
391 );
392 $pageTsConfig['flexHack.']['PAGE_TSCONFIG_IDLIST'] = $result['pageTsConfig']['TCEFORM.'][$tableName . '.'][$fieldName . '.']['PAGE_TSCONFIG_IDLIST'];
393 }
394 if (isset($result['pageTsConfig']['TCEFORM.'][$tableName . '.'][$fieldName . '.']['PAGE_TSCONFIG_STR'])) {
395 GeneralUtility::deprecationLog(
396 'The page TSConfig setting TCEFORM.' . $tableName . '.' . $fieldName . '.PAGE_TSCONFIG_STR for flex forms'
397 . ' is deprecated. Use this setting for single flex fields instead, example: TCEFORM.' . $tableName . '.'
398 . $fieldName . '.theDataStructureName.theSheet.theFieldName.PAGE_TSCONFIG_STR. Be aware these settings are'
399 . ' no longer allowed for fields within flex form section container elements.'
400 );
401 $pageTsConfig['flexHack.']['PAGE_TSCONFIG_STR'] = $result['pageTsConfig']['TCEFORM.'][$tableName . '.'][$fieldName . '.']['PAGE_TSCONFIG_STR'];
402 }
403
404 // List of "new" tca fields that have no value within the flexform, yet. Those will be compiled in one go later.
405 $tcaNewColumns = [];
406 // List of "edit" tca fields that have a value in flexform, already. Those will be compiled in one go later.
407 $tcaEditColumns = [];
408 // Contains the data values for the "edit" tca fields.
409 $tcaValueArray = [
410 'uid' => $result['databaseRow']['uid'],
411 ];
412 foreach ($dataStructureFields as $dataStructureFieldName => $dataStructureFieldDefinition) {
413 if (isset($dataStructureFieldDefinition['type']) && $dataStructureFieldDefinition['type'] === 'array'
414 && isset($dataStructureFieldDefinition['section']) && (string)$dataStructureFieldDefinition['section'] === '1'
415 ) {
416 // Existing section containers. Prepare data values and create a unique data structure per container.
417 // This is important for instance for display conditions later enabling them to change ds per container instance.
418 // In the end, the data values in
419 // ['databaseRow']['aFieldName']['data']['aSheet']['lDEF']['aSectionField']['el']['aContainer']
420 // are prepared, and additionally, the processedTca data structure is changed and has a specific container
421 // name per container instance in
422 // ['processedTca']['columns']['aFieldName']['config']['ds']['sheets']['aSheet']['ROOT']['el']['aSectionField']['children']['aContainer']
423 if (isset($dataValues['data'][$dataStructureSheetName]['lDEF'][$dataStructureFieldName]['el'])
424 && is_array($dataValues['data'][$dataStructureSheetName]['lDEF'][$dataStructureFieldName]['el'])
425 ) {
426 $containerValueArray = $dataValues['data'][$dataStructureSheetName]['lDEF'][$dataStructureFieldName]['el'];
427 $containerDataStructuresPerContainer = [];
428 foreach ($containerValueArray as $aContainerIdentifier => $aContainerArray) {
429 if (is_array($aContainerArray)) {
430 foreach ($aContainerArray as $aContainerName => $aContainerElementArray) {
431 if ($aContainerName === '_TOGGLE') {
432 // Don't handle internal toggle state field
433 continue;
434 }
435 if (!isset($dataStructureFields[$dataStructureFieldName]['el'][$aContainerName])) {
436 // Container not defined in ds
437 continue;
438 }
439 $vanillaContainerDataStructure = $dataStructureFields[$dataStructureFieldName]['el'][$aContainerName];
440
441 $newColumns = [];
442 $editColumns = [];
443 $valueArray = [
444 'uid' => $result['databaseRow']['uid'],
445 ];
446 foreach ($vanillaContainerDataStructure['el'] as $singleFieldName => $singleFieldConfiguration) {
447 // $singleFieldValueArray = ['data']['sSections']['lDEF']['section_1']['el']['1']['container_1']['el']['element_1']
448 $singleFieldValueArray = [];
449 if (isset($aContainerElementArray['el'][$singleFieldName])
450 && is_array($aContainerElementArray['el'][$singleFieldName])
451 ) {
452 $singleFieldValueArray = $aContainerElementArray['el'][$singleFieldName];
453 }
454
455 if (array_key_exists('vDEF', $singleFieldValueArray)) {
456 $valueArray[$singleFieldName] = $singleFieldValueArray['vDEF'];
457 } else {
458 $newColumns[$singleFieldName] = $singleFieldConfiguration;
459 }
460 $editColumns[$singleFieldName] = $singleFieldConfiguration;
461 }
462
463 $inputToFlexFormSegment = [
464 'tableName' => $result['tableName'],
465 'command' => '',
466 // It is currently not possible to have pageTsConfig for section container
467 'pageTsConfig' => [],
468 'databaseRow' => $valueArray,
469 'processedTca' => [
470 'ctrl' => [],
471 'columns' => [],
472 ],
473 'selectTreeCompileItems' => $result['selectTreeCompileItems'],
474 'flexParentDatabaseRow' => $result['databaseRow'],
475 ];
476
477 if (!empty($newColumns)) {
478 // This is scenario "field has been added to data structure, but field value does not exist in value array yet"
479 // We want that stuff like TCA "default" values are then applied to those fields. What we do here is
480 // calling the data compiler with those "new" fields to fetch their values and set them in value array.
481 // Those fields are then compiled a second time in the "edit" phase to prepare their final TCA.
482 // This two-phase compiling is needed to ensure that for instance display conditions work with
483 // fields that may just have been added to the data structure but are not yet initialized as data value.
484 $inputToFlexFormSegment['command'] = 'new';
485 $inputToFlexFormSegment['processedTca']['columns'] = $newColumns;
486 $flexSegmentResult = $formDataCompiler->compile($inputToFlexFormSegment);
487 foreach ($newColumns as $singleFieldName => $_) {
488 // Set data value result to feed it to "edit" next
489 $valueArray[$singleFieldName] = $flexSegmentResult['databaseRow'][$singleFieldName];
490 }
491 }
492
493 if (!empty($editColumns)) {
494 $inputToFlexFormSegment['command'] = 'edit';
495 $inputToFlexFormSegment['processedTca']['columns'] = $editColumns;
496 $flexSegmentResult = $formDataCompiler->compile($inputToFlexFormSegment);
497 foreach ($editColumns as $singleFieldName => $_) {
498 $result['databaseRow'][$fieldName]
499 ['data'][$dataStructureSheetName]['lDEF'][$dataStructureFieldName]
500 ['el'][$aContainerIdentifier][$aContainerName]['el'][$singleFieldName]['vDEF']
501 = $flexSegmentResult['databaseRow'][$singleFieldName];
502 $containerDataStructuresPerContainer[$aContainerIdentifier] = $vanillaContainerDataStructure;
503 $containerDataStructuresPerContainer[$aContainerIdentifier]['el'] = $flexSegmentResult['processedTca']['columns'];
504 }
505 }
506 }
507 }
508 } // End of existing data value handling
509 // Set 'data structures per container' next to 'el' that contains vanilla data structures
510 $result['processedTca']['columns'][$fieldName]['config']['ds']
511 ['sheets'][$dataStructureSheetName]['ROOT']['el']
512 [$dataStructureFieldName]['children'] = $containerDataStructuresPerContainer;
513 } else {
514 // Force the section data array to be an empty array if there are no existing containers
515 $result['databaseRow'][$fieldName]
516 ['data'][$dataStructureSheetName]['lDEF'][$dataStructureFieldName]['el'] = [];
517 // Force data structure array to be empty if there are no existing containers
518 $result['processedTca']['columns'][$fieldName]['config']['ds']
519 ['sheets'][$dataStructureSheetName]['ROOT']['el']
520 [$dataStructureFieldName]['children'] = [];
521 }
522
523 // A "normal" TCA flex form element, no section
524 } else {
525 if (isset($dataValues['data'][$dataStructureSheetName]['lDEF'][$dataStructureFieldName])
526 && array_key_exists('vDEF', $dataValues['data'][$dataStructureSheetName]['lDEF'][$dataStructureFieldName])
527 ) {
528 $tcaEditColumns[$dataStructureFieldName] = $dataStructureFieldDefinition;
529 $tcaValueArray[$dataStructureFieldName] = $dataValues['data'][$dataStructureSheetName]['lDEF'][$dataStructureFieldName]['vDEF'];
530 } else {
531 $tcaNewColumns[$dataStructureFieldName] = $dataStructureFieldDefinition;
532 }
533 } // End of single element handling
534 }
535
536 // process the tca columns for the current sheet
537 $inputToFlexFormSegment = [
538 'tableName' => $result['tableName'],
539 'command' => '',
540 'pageTsConfig' => $pageTsConfig,
541 'databaseRow' => $tcaValueArray,
542 'processedTca' => [
543 'ctrl' => [],
544 'columns' => [],
545 ],
546 'flexParentDatabaseRow' => $result['databaseRow'],
547 // Whether to compile TCA tree items - inherit from parent
548 'selectTreeCompileItems' => $result['selectTreeCompileItems'],
549 ];
550
551 if (!empty($tcaNewColumns)) {
552 // @todo: this has the same problem in scenario "a field was added later" as flex section container
553 $inputToFlexFormSegment['command'] = 'new';
554 $inputToFlexFormSegment['processedTca']['columns'] = $tcaNewColumns;
555 $flexSegmentResult = $formDataCompiler->compile($inputToFlexFormSegment);
556
557 foreach ($tcaNewColumns as $dataStructureFieldName => $_) {
558 // Set data value result
559 if (array_key_exists($dataStructureFieldName, $flexSegmentResult['databaseRow'])) {
560 $result['databaseRow'][$fieldName]
561 ['data'][$dataStructureSheetName]['lDEF'][$dataStructureFieldName]['vDEF']
562 = $flexSegmentResult['databaseRow'][$dataStructureFieldName];
563 }
564 // Set TCA structure result
565 $result['processedTca']['columns'][$fieldName]['config']['ds']
566 ['sheets'][$dataStructureSheetName]['ROOT']['el'][$dataStructureFieldName]
567 = $flexSegmentResult['processedTca']['columns'][$dataStructureFieldName];
568 }
569 }
570
571 if (!empty($tcaEditColumns)) {
572 $inputToFlexFormSegment['command'] = 'edit';
573 $inputToFlexFormSegment['processedTca']['columns'] = $tcaEditColumns;
574 $flexSegmentResult = $formDataCompiler->compile($inputToFlexFormSegment);
575
576 foreach ($tcaEditColumns as $dataStructureFieldName => $_) {
577 // Set data value result
578 if (array_key_exists($dataStructureFieldName, $flexSegmentResult['databaseRow'])) {
579 $result['databaseRow'][$fieldName]
580 ['data'][$dataStructureSheetName]['lDEF'][$dataStructureFieldName]['vDEF']
581 = $flexSegmentResult['databaseRow'][$dataStructureFieldName];
582 }
583 // Set TCA structure result
584 $result['processedTca']['columns'][$fieldName]['config']['ds']
585 ['sheets'][$dataStructureSheetName]['ROOT']['el'][$dataStructureFieldName]
586 = $flexSegmentResult['processedTca']['columns'][$dataStructureFieldName];
587 }
588 }
589 }
590
591 return $result;
592 }
593
594 /**
595 * Prepare data structure and data values for a new section container.
596 *
597 * @param array $result Incoming result array
598 * @param string $fieldName The field name with this flex form
599 * @return array Modified result
600 */
601 protected function prepareNewSectionContainer(array $result, string $fieldName): array
602 {
603 $flexSectionContainerPreparation = $result['flexSectionContainerPreparation'];
604 $flexFormSheetName = $flexSectionContainerPreparation['flexFormSheetName'];
605 $flexFormFieldName = $flexSectionContainerPreparation['flexFormFieldName'];
606 $flexFormContainerName = $flexSectionContainerPreparation['flexFormContainerName'];
607 $flexFormContainerIdentifier = $flexSectionContainerPreparation['flexFormContainerIdentifier'];
608
609 $containerConfiguration = $result['processedTca']['columns'][$fieldName]['config']['ds']
610 ['sheets'][$flexFormSheetName]['ROOT']['el'][$flexFormFieldName]['el'][$flexFormContainerName];
611
612 if (isset($containerConfiguration['el']) && is_array($containerConfiguration['el'])) {
613 $formDataGroup = GeneralUtility::makeInstance(FlexFormSegment::class);
614 $formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup);
615 $inputToFlexFormSegment = [
616 'tableName' => $result['tableName'],
617 'command' => 'new',
618 // It is currently not possible to have pageTsConfig for section container
619 'pageTsConfig' => [],
620 'databaseRow' => [
621 'uid' => $result['databaseRow']['uid'],
622 ],
623 'processedTca' => [
624 'ctrl' => [],
625 'columns' => $containerConfiguration['el'],
626 ],
627 'selectTreeCompileItems' => $result['selectTreeCompileItems'],
628 'flexParentDatabaseRow' => $result['databaseRow'],
629 ];
630 $flexSegmentResult = $formDataCompiler->compile($inputToFlexFormSegment);
631
632 foreach ($containerConfiguration['el'] as $singleFieldName => $singleFieldConfiguration) {
633 // Set 'data structures for this new container' to 'children'
634 $result['processedTca']['columns'][$fieldName]['config']['ds']
635 ['sheets'][$flexFormSheetName]['ROOT']['el']
636 [$flexFormFieldName]['children'][$flexFormContainerIdentifier]
637 = $containerConfiguration;
638 $result['processedTca']['columns'][$fieldName]['config']['ds']
639 ['sheets'][$flexFormSheetName]['ROOT']['el']
640 [$flexFormFieldName]['children'][$flexFormContainerIdentifier]['el']
641 = $flexSegmentResult['processedTca']['columns'];
642 // Set calculated value - this especially contains "default values from TCA"
643 $result['databaseRow'][$fieldName]['data'][$flexFormSheetName]['lDEF']
644 [$flexFormFieldName]['el']
645 [$flexFormContainerIdentifier][$flexFormContainerName]['el'][$singleFieldName]['vDEF']
646 = $flexSegmentResult['databaseRow'][$singleFieldName];
647 }
648 }
649
650 return $result;
651 }
652
653 /**
654 * Modify data structure of a single "sheet"
655 * Sets "secondary" data like sheet names and so on, but does NOT modify single elements
656 *
657 * @param array $dataStructure Given data structure
658 * @param array $pageTsOfSheet Page Ts config of given field
659 * @return array Modified data structure
660 */
661 protected function modifySingleSheetInformation(array $dataStructure, array $pageTsOfSheet)
662 {
663 // Return if no elements defined
664 if (!isset($dataStructure['ROOT']['el']) || !is_array($dataStructure['ROOT']['el'])) {
665 return $dataStructure;
666 }
667 // Rename sheet (tab)
668 if (!empty($pageTsOfSheet['sheetTitle'])) {
669 $dataStructure['ROOT']['sheetTitle'] = $pageTsOfSheet['sheetTitle'];
670 }
671 // Set sheet description (tab)
672 if (!empty($pageTsOfSheet['sheetDescription'])) {
673 $dataStructure['ROOT']['sheetDescription'] = $pageTsOfSheet['sheetDescription'];
674 }
675 // Set sheet short description (tab)
676 if (!empty($pageTsOfSheet['sheetShortDescr'])) {
677 $dataStructure['ROOT']['sheetShortDescr'] = $pageTsOfSheet['sheetShortDescr'];
678 }
679
680 return $dataStructure;
681 }
682
683 /**
684 * @return BackendUserAuthentication
685 */
686 protected function getBackendUser()
687 {
688 return $GLOBALS['BE_USER'];
689 }
690 }