[TASK] Move LanguageService to EXT:core
[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 * @throws \UnexpectedValueException
80 */
81 protected function scanForInvalidSectionContainerTca(array $result, string $fieldName)
82 {
83 $dataStructure = $result['processedTca']['columns'][$fieldName]['config']['ds'];
84 if (!isset($dataStructure['sheets']) || !is_array($dataStructure['sheets'])) {
85 return;
86 }
87 foreach ($dataStructure['sheets'] as $dataStructureSheetName => $dataStructureSheetDefinition) {
88 if (!isset($dataStructureSheetDefinition['ROOT']['el']) || !is_array($dataStructureSheetDefinition['ROOT']['el'])) {
89 continue;
90 }
91 $dataStructureFields = $dataStructureSheetDefinition['ROOT']['el'];
92 foreach ($dataStructureFields as $dataStructureFieldName => $dataStructureFieldDefinition) {
93 if (isset($dataStructureFieldDefinition['type']) && $dataStructureFieldDefinition['type'] === 'array'
94 && isset($dataStructureFieldDefinition['section']) && (string)$dataStructureFieldDefinition['section'] === '1'
95 ) {
96 if (isset($dataStructureFieldDefinition['el']) && is_array($dataStructureFieldDefinition['el'])) {
97 foreach ($dataStructureFieldDefinition['el'] as $containerName => $containerConfiguration) {
98 if (isset($containerConfiguration['el']) && is_array($containerConfiguration['el'])) {
99 foreach ($containerConfiguration['el'] as $singleFieldName => $singleFieldConfiguration) {
100 // Nesting type=inline in container sections is not supported. Throw an exception if configured.
101 if (isset($singleFieldConfiguration['config']['type']) && $singleFieldConfiguration['config']['type'] === 'inline') {
102 throw new \UnexpectedValueException(
103 'Invalid flex form data structure on field name "' . $fieldName . '" with element "' . $singleFieldName . '"'
104 . ' in section container "' . $containerName . '": Nesting inline elements in flex form'
105 . ' sections is not allowed.',
106 1458745468
107 );
108 }
109
110 // Nesting sections is not supported. Throw an exception if configured.
111 if (is_array($singleFieldConfiguration)
112 && isset($singleFieldConfiguration['type']) && $singleFieldConfiguration['type'] === 'array'
113 && isset($singleFieldConfiguration['section']) && (string)$singleFieldConfiguration['section'] === '1'
114 ) {
115 throw new \UnexpectedValueException(
116 'Invalid flex form data structure on field name "' . $fieldName . '" with element "' . $singleFieldName . '"'
117 . ' in section container "' . $containerName . '": Nesting sections in container elements'
118 . ' sections is not allowed.',
119 1458745712
120 );
121 }
122
123 // Nesting type="select" and type="group" within section containers is not supported,
124 // the data storage can not deal with that and in general it is not supported to add a
125 // named reference to the anonymous section container structure.
126 if (is_array($singleFieldConfiguration)
127 && isset($singleFieldConfiguration['config']['type'])
128 && ($singleFieldConfiguration['config']['type'] === 'group' || $singleFieldConfiguration['config']['type'] === 'select')
129 && array_key_exists('MM', $singleFieldConfiguration['config'])
130 ) {
131 throw new \UnexpectedValueException(
132 'Invalid flex form data structure on field name "' . $fieldName . '" with element "' . $singleFieldName . '"'
133 . ' in section container "' . $containerName . '": Nesting select and group elements in flex form'
134 . ' sections is not allowed with MM relations.',
135 1481647089
136 );
137 }
138 }
139 }
140 }
141 }
142 } elseif (isset($dataStructureFieldDefinition['type']) || isset($dataStructureFieldDefinition['section'])) {
143 // type without section is not ok
144 throw new \UnexpectedValueException(
145 'Broken data structure on field name ' . $fieldName . '. section without type or vice versa is not allowed',
146 1440685208
147 );
148 }
149 }
150 }
151 }
152
153 /**
154 * Calculate a simplified (and wrong) data structure identifier.
155 * This is used to find pageTsConfig options of flex fields and exclude field definitions later, see methods below.
156 * If the data structure identifier is not type=tca based and if dataStructureKey is not as expected, fallback is "default"
157 *
158 * Example pi_flexform with ext:news in tt_content:
159 * * TCA config of pi_flexform ds_pointerfield is set to "list_type,CType"
160 * * list_type in databaseRow is "news_pi1"
161 * * CType in databaseRow is "list"
162 * * The resulting dataStructureIdentifier calculated by FlexFormTools is then:
163 * {"type":"tca","tableName":"tt_content","fieldName":"pi_flexform","dataStructureKey":"news_pi1,list"}
164 * * The resulting simpleDataStructureIdentifier is "news_pi1"
165 * * The pageTsConfig base path used for flex field overrides is "TCEFORM.tt_content.pi_flexform.news_pi1", a full
166 * example path disabling a field: "TCEFORM.tt_content.pi_flexform.news_pi1.sDEF.settings\.orderBy.disabled = 1"
167 * * The exclude path for be_user exclude rights is "tt_content:pi_flexform;news_pi1", a full example:
168 * tt_content:pi_flexform;news_pi1;sDEF;settings.orderBy
169 *
170 * Notes:
171 * This approach is obviously limited. It is not possible to override flex form DS via pageTsConfig for other complex
172 * or dynamically created data structure definitions. And worse, the fallback to "default" may lead to naming clashes
173 * if two different data structures have identical sheet and field names.
174 * Also, the exclude field handling is limited and it is not possible to respect 'exclude' fields in flex form
175 * data structures if the dataStructureIdentifier is based on type="record" or manipulated by a hook in FlexFormTools.
176 * All that can only be solved by changing the pageTsConfig syntax referencing flex fields, probably by involving the whole
177 * data structure identifier and going away from this "simple" approach. For exclude fields there is the additional
178 * issue that the special="exclude" code is based on guess work, to find possible data structures. If this area here is
179 * changed and a pageTsConfig syntax change is raised, it would probably be a good idea to solve the access restrictions
180 * area at the same time - see the related methods that deal with flex field handling for special="exclude" for
181 * more comments on this.
182 * Another limitation is that the current syntax in both pageTsConfig and exclude fields does not
183 * consider flex form section containers at all.
184 *
185 * @param string $dataStructureIdentifier
186 * @return string
187 */
188 protected function getSimplifiedDataStructureIdentifier(string $dataStructureIdentifier): string
189 {
190 $identifierArray = json_decode($dataStructureIdentifier, true);
191 $simpleDataStructureIdentifier = 'default';
192 if (isset($identifierArray['type']) && $identifierArray['type'] === 'tca' && isset($identifierArray['dataStructureKey'])) {
193 $explodedKey = explode(',', $identifierArray['dataStructureKey']);
194 if (!empty($explodedKey[1]) && $explodedKey[1] !== 'list' && $explodedKey[1] !== '*') {
195 $simpleDataStructureIdentifier = $explodedKey[1];
196 } elseif (!empty($explodedKey[0]) && $explodedKey[0] !== 'list' && $explodedKey[0] !== '*') {
197 $simpleDataStructureIdentifier = $explodedKey[0];
198 }
199 }
200 return $simpleDataStructureIdentifier;
201 }
202
203 /**
204 * Determine TCEFORM.aTable.aField.matchingIdentifier
205 *
206 * @param array $result Result array
207 * @param string $fieldName Handled field name
208 * @param string $flexIdentifier Determined identifier
209 * @return array PageTsConfig for this flex
210 */
211 protected function getPageTsOfFlex(array $result, $fieldName, $flexIdentifier)
212 {
213 $table = $result['tableName'];
214 $pageTs = [];
215 if (!empty($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.'][$flexIdentifier . '.'])
216 && is_array($result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.'][$flexIdentifier . '.'])) {
217 $pageTs = $result['pageTsConfig']['TCEFORM.'][$table . '.'][$fieldName . '.'][$flexIdentifier . '.'];
218 }
219 return $pageTs;
220 }
221
222 /**
223 * Handle "outer" flex data structure changes like language and sheet
224 * description. Does not change "TCA" or values of single elements
225 *
226 * @param array $result Result array
227 * @param string $fieldName Current handle field name
228 * @param array $pageTsConfig Given pageTsConfig of this flex form
229 * @return array Modified item array
230 */
231 protected function modifyOuterDataStructure(array $result, $fieldName, $pageTsConfig)
232 {
233 $modifiedDataStructure = $result['processedTca']['columns'][$fieldName]['config']['ds'];
234
235 if (isset($modifiedDataStructure['sheets']) && is_array($modifiedDataStructure['sheets'])) {
236 // Handling multiple sheets
237 foreach ($modifiedDataStructure['sheets'] as $sheetName => $sheetStructure) {
238 if (isset($pageTsConfig[$sheetName . '.']) && is_array($pageTsConfig[$sheetName . '.'])) {
239 $pageTsOfSheet = $pageTsConfig[$sheetName . '.'];
240
241 // Remove whole sheet if disabled
242 if (!empty($pageTsOfSheet['disabled'])) {
243 unset($modifiedDataStructure['sheets'][$sheetName]);
244 continue;
245 }
246
247 // sheetTitle, sheetDescription, sheetShortDescr
248 $modifiedDataStructure['sheets'][$sheetName] = $this->modifySingleSheetInformation($sheetStructure, $pageTsOfSheet);
249 }
250 }
251 }
252
253 $result['processedTca']['columns'][$fieldName]['config']['ds'] = $modifiedDataStructure;
254
255 return $result;
256 }
257
258 /**
259 * Removes fields from data structure the user has no access to
260 *
261 * @param array $result Result array
262 * @param string $fieldName Current handle field name
263 * @param string $flexIdentifier Determined identifier
264 * @return array Modified result
265 */
266 protected function removeExcludeFieldsFromDataStructure(array $result, $fieldName, $flexIdentifier)
267 {
268 $dataStructure = $result['processedTca']['columns'][$fieldName]['config']['ds'];
269 $backendUser = $this->getBackendUser();
270 if ($backendUser->isAdmin() || !isset($dataStructure['sheets']) || !is_array($dataStructure['sheets'])) {
271 return $result;
272 }
273
274 $userNonExcludeFields = GeneralUtility::trimExplode(',', $backendUser->groupData['non_exclude_fields']);
275 $excludeFieldsPrefix = $result['tableName'] . ':' . $fieldName . ';' . $flexIdentifier . ';';
276 $nonExcludeFields = [];
277 foreach ($userNonExcludeFields as $userNonExcludeField) {
278 if (strpos($userNonExcludeField, $excludeFieldsPrefix) !== false) {
279 $exploded = explode(';', $userNonExcludeField);
280 $sheetName = $exploded[2];
281 $allowedFlexFieldName = $exploded[3];
282 $nonExcludeFields[$sheetName][$allowedFlexFieldName] = true;
283 }
284 }
285 foreach ($dataStructure['sheets'] as $sheetName => $sheetDefinition) {
286 if (!isset($sheetDefinition['ROOT']['el']) || !is_array($sheetDefinition['ROOT']['el'])) {
287 continue;
288 }
289 foreach ($sheetDefinition['ROOT']['el'] as $flexFieldName => $fieldDefinition) {
290 if (!empty($fieldDefinition['exclude']) && !isset($nonExcludeFields[$sheetName][$flexFieldName])) {
291 unset($result['processedTca']['columns'][$fieldName]['config']['ds']['sheets'][$sheetName]['ROOT']['el'][$flexFieldName]);
292 }
293 }
294 }
295
296 return $result;
297 }
298
299 /**
300 * Remove fields from data structure that are disabled in pageTsConfig.
301 *
302 * @param array $result Result array
303 * @param string $fieldName Current handle field name
304 * @param array $pageTsConfig Given pageTsConfig of this flex form
305 * @return array Modified item array
306 */
307 protected function removeDisabledFieldsFromDataStructure(array $result, $fieldName, $pageTsConfig)
308 {
309 $dataStructure = $result['processedTca']['columns'][$fieldName]['config']['ds'];
310 if (!isset($dataStructure['sheets']) || !is_array($dataStructure['sheets'])) {
311 return $result;
312 }
313 foreach ($dataStructure['sheets'] as $sheetName => $sheetDefinition) {
314 if (!isset($sheetDefinition['ROOT']['el']) || !is_array($sheetDefinition['ROOT']['el'])
315 || !isset($pageTsConfig[$sheetName . '.'])) {
316 continue;
317 }
318 foreach ($sheetDefinition['ROOT']['el'] as $flexFieldName => $fieldDefinition) {
319 if (!empty($pageTsConfig[$sheetName . '.'][$flexFieldName . '.']['disabled'])) {
320 unset($result['processedTca']['columns'][$fieldName]['config']['ds']['sheets'][$sheetName]['ROOT']['el'][$flexFieldName]);
321 }
322 }
323 }
324 return $result;
325 }
326
327 /**
328 * Feed single flex field and data to FlexFormSegment FormData compiler and merge result.
329 * This one is nasty. Goal is to have processed TCA stuff in DS and also have validated / processed data values.
330 *
331 * Two main parts in this method:
332 * * Process values and TCA of existing section containers
333 * * Process TCA of "normal" fields
334 *
335 * @param array $result Result array
336 * @param string $fieldName Current handle field name
337 * @param array $pageTsConfig Given pageTsConfig of this flex form
338 * @return array Modified item array
339 * @throws \UnexpectedValueException
340 */
341 protected function modifyDataStructureAndDataValuesByFlexFormSegmentGroup(array $result, $fieldName, $pageTsConfig)
342 {
343 $dataStructure = $result['processedTca']['columns'][$fieldName]['config']['ds'];
344 $dataValues = $result['databaseRow'][$fieldName];
345 $tableName = $result['tableName'];
346
347 if (!isset($dataStructure['sheets']) || !is_array($dataStructure['sheets'])) {
348 return $result;
349 }
350
351 $formDataGroup = GeneralUtility::makeInstance(FlexFormSegment::class);
352 $formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup);
353
354 foreach ($dataStructure['sheets'] as $dataStructureSheetName => $dataStructureSheetDefinition) {
355 if (!isset($dataStructureSheetDefinition['ROOT']['el']) || !is_array($dataStructureSheetDefinition['ROOT']['el'])) {
356 continue;
357 }
358 $dataStructureFields = $dataStructureSheetDefinition['ROOT']['el'];
359
360 // Prepare pageTsConfig of this sheet
361 $pageTsConfig['TCEFORM.'][$tableName . '.'] = [];
362 if (isset($pageTsConfig[$dataStructureSheetName . '.']) && is_array($pageTsConfig[$dataStructureSheetName . '.'])) {
363 $pageTsConfig['TCEFORM.'][$tableName . '.'] = $pageTsConfig[$dataStructureSheetName . '.'];
364 }
365
366 // List of "new" tca fields that have no value within the flexform, yet. Those will be compiled in one go later.
367 $tcaNewColumns = [];
368 // List of "edit" tca fields that have a value in flexform, already. Those will be compiled in one go later.
369 $tcaEditColumns = [];
370 // Contains the data values for the "edit" tca fields.
371 $tcaValueArray = [
372 'uid' => $result['databaseRow']['uid'],
373 ];
374 foreach ($dataStructureFields as $dataStructureFieldName => $dataStructureFieldDefinition) {
375 if (isset($dataStructureFieldDefinition['type']) && $dataStructureFieldDefinition['type'] === 'array'
376 && isset($dataStructureFieldDefinition['section']) && (string)$dataStructureFieldDefinition['section'] === '1'
377 ) {
378 // Existing section containers. Prepare data values and create a unique data structure per container.
379 // This is important for instance for display conditions later enabling them to change ds per container instance.
380 // In the end, the data values in
381 // ['databaseRow']['aFieldName']['data']['aSheet']['lDEF']['aSectionField']['el']['aContainer']
382 // are prepared, and additionally, the processedTca data structure is changed and has a specific container
383 // name per container instance in
384 // ['processedTca']['columns']['aFieldName']['config']['ds']['sheets']['aSheet']['ROOT']['el']['aSectionField']['children']['aContainer']
385 if (isset($dataValues['data'][$dataStructureSheetName]['lDEF'][$dataStructureFieldName]['el'])
386 && is_array($dataValues['data'][$dataStructureSheetName]['lDEF'][$dataStructureFieldName]['el'])
387 ) {
388 $containerValueArray = $dataValues['data'][$dataStructureSheetName]['lDEF'][$dataStructureFieldName]['el'];
389 $containerDataStructuresPerContainer = [];
390 foreach ($containerValueArray as $aContainerIdentifier => $aContainerArray) {
391 if (is_array($aContainerArray)) {
392 foreach ($aContainerArray as $aContainerName => $aContainerElementArray) {
393 if ($aContainerName === '_TOGGLE') {
394 // Don't handle internal toggle state field
395 continue;
396 }
397 if (!isset($dataStructureFields[$dataStructureFieldName]['el'][$aContainerName])) {
398 // Container not defined in ds
399 continue;
400 }
401 $vanillaContainerDataStructure = $dataStructureFields[$dataStructureFieldName]['el'][$aContainerName];
402
403 $newColumns = [];
404 $editColumns = [];
405 $valueArray = [
406 'uid' => $result['databaseRow']['uid'],
407 ];
408 foreach ($vanillaContainerDataStructure['el'] as $singleFieldName => $singleFieldConfiguration) {
409 // $singleFieldValueArray = ['data']['sSections']['lDEF']['section_1']['el']['1']['container_1']['el']['element_1']
410 $singleFieldValueArray = [];
411 if (isset($aContainerElementArray['el'][$singleFieldName])
412 && is_array($aContainerElementArray['el'][$singleFieldName])
413 ) {
414 $singleFieldValueArray = $aContainerElementArray['el'][$singleFieldName];
415 }
416
417 if (array_key_exists('vDEF', $singleFieldValueArray)) {
418 $valueArray[$singleFieldName] = $singleFieldValueArray['vDEF'];
419 } else {
420 $newColumns[$singleFieldName] = $singleFieldConfiguration;
421 }
422 $editColumns[$singleFieldName] = $singleFieldConfiguration;
423 }
424
425 $inputToFlexFormSegment = [
426 'tableName' => $result['tableName'],
427 'command' => '',
428 // It is currently not possible to have pageTsConfig for section container
429 'pageTsConfig' => [],
430 'databaseRow' => $valueArray,
431 'processedTca' => [
432 'ctrl' => [],
433 'columns' => [],
434 ],
435 'selectTreeCompileItems' => $result['selectTreeCompileItems'],
436 'flexParentDatabaseRow' => $result['databaseRow'],
437 ];
438
439 if (!empty($newColumns)) {
440 // This is scenario "field has been added to data structure, but field value does not exist in value array yet"
441 // We want that stuff like TCA "default" values are then applied to those fields. What we do here is
442 // calling the data compiler with those "new" fields to fetch their values and set them in value array.
443 // Those fields are then compiled a second time in the "edit" phase to prepare their final TCA.
444 // This two-phase compiling is needed to ensure that for instance display conditions work with
445 // fields that may just have been added to the data structure but are not yet initialized as data value.
446 $inputToFlexFormSegment['command'] = 'new';
447 $inputToFlexFormSegment['processedTca']['columns'] = $newColumns;
448 $flexSegmentResult = $formDataCompiler->compile($inputToFlexFormSegment);
449 foreach ($newColumns as $singleFieldName => $_) {
450 // Set data value result to feed it to "edit" next
451 $valueArray[$singleFieldName] = $flexSegmentResult['databaseRow'][$singleFieldName];
452 }
453 }
454
455 if (!empty($editColumns)) {
456 $inputToFlexFormSegment['command'] = 'edit';
457 $inputToFlexFormSegment['processedTca']['columns'] = $editColumns;
458 $flexSegmentResult = $formDataCompiler->compile($inputToFlexFormSegment);
459 foreach ($editColumns as $singleFieldName => $_) {
460 $result['databaseRow'][$fieldName]
461 ['data'][$dataStructureSheetName]['lDEF'][$dataStructureFieldName]
462 ['el'][$aContainerIdentifier][$aContainerName]['el'][$singleFieldName]['vDEF']
463 = $flexSegmentResult['databaseRow'][$singleFieldName];
464 $containerDataStructuresPerContainer[$aContainerIdentifier] = $vanillaContainerDataStructure;
465 $containerDataStructuresPerContainer[$aContainerIdentifier]['el'] = $flexSegmentResult['processedTca']['columns'];
466 }
467 }
468 }
469 }
470 } // End of existing data value handling
471 // Set 'data structures per container' next to 'el' that contains vanilla data structures
472 $result['processedTca']['columns'][$fieldName]['config']['ds']
473 ['sheets'][$dataStructureSheetName]['ROOT']['el']
474 [$dataStructureFieldName]['children'] = $containerDataStructuresPerContainer;
475 } else {
476 // Force the section data array to be an empty array if there are no existing containers
477 $result['databaseRow'][$fieldName]
478 ['data'][$dataStructureSheetName]['lDEF'][$dataStructureFieldName]['el'] = [];
479 // Force data structure array to be empty if there are no existing containers
480 $result['processedTca']['columns'][$fieldName]['config']['ds']
481 ['sheets'][$dataStructureSheetName]['ROOT']['el']
482 [$dataStructureFieldName]['children'] = [];
483 }
484
485 // A "normal" TCA flex form element, no section
486 } else {
487 if (isset($dataValues['data'][$dataStructureSheetName]['lDEF'][$dataStructureFieldName])
488 && array_key_exists('vDEF', $dataValues['data'][$dataStructureSheetName]['lDEF'][$dataStructureFieldName])
489 ) {
490 $tcaEditColumns[$dataStructureFieldName] = $dataStructureFieldDefinition;
491 $tcaValueArray[$dataStructureFieldName] = $dataValues['data'][$dataStructureSheetName]['lDEF'][$dataStructureFieldName]['vDEF'];
492 } else {
493 $tcaNewColumns[$dataStructureFieldName] = $dataStructureFieldDefinition;
494 }
495 } // End of single element handling
496 }
497
498 // process the tca columns for the current sheet
499 $inputToFlexFormSegment = [
500 'tableName' => $result['tableName'],
501 'command' => '',
502 'pageTsConfig' => $pageTsConfig,
503 'databaseRow' => $tcaValueArray,
504 'processedTca' => [
505 'ctrl' => [],
506 'columns' => [],
507 ],
508 'flexParentDatabaseRow' => $result['databaseRow'],
509 // Whether to compile TCA tree items - inherit from parent
510 'selectTreeCompileItems' => $result['selectTreeCompileItems'],
511 ];
512
513 if (!empty($tcaNewColumns)) {
514 // @todo: this has the same problem in scenario "a field was added later" as flex section container
515 $inputToFlexFormSegment['command'] = 'new';
516 $inputToFlexFormSegment['processedTca']['columns'] = $tcaNewColumns;
517 $flexSegmentResult = $formDataCompiler->compile($inputToFlexFormSegment);
518
519 foreach ($tcaNewColumns as $dataStructureFieldName => $_) {
520 // Set data value result
521 if (array_key_exists($dataStructureFieldName, $flexSegmentResult['databaseRow'])) {
522 $result['databaseRow'][$fieldName]
523 ['data'][$dataStructureSheetName]['lDEF'][$dataStructureFieldName]['vDEF']
524 = $flexSegmentResult['databaseRow'][$dataStructureFieldName];
525 }
526 // Set TCA structure result
527 $result['processedTca']['columns'][$fieldName]['config']['ds']
528 ['sheets'][$dataStructureSheetName]['ROOT']['el'][$dataStructureFieldName]
529 = $flexSegmentResult['processedTca']['columns'][$dataStructureFieldName];
530 }
531 }
532
533 if (!empty($tcaEditColumns)) {
534 $inputToFlexFormSegment['command'] = 'edit';
535 $inputToFlexFormSegment['processedTca']['columns'] = $tcaEditColumns;
536 $flexSegmentResult = $formDataCompiler->compile($inputToFlexFormSegment);
537
538 foreach ($tcaEditColumns as $dataStructureFieldName => $_) {
539 // Set data value result
540 if (array_key_exists($dataStructureFieldName, $flexSegmentResult['databaseRow'])) {
541 $result['databaseRow'][$fieldName]
542 ['data'][$dataStructureSheetName]['lDEF'][$dataStructureFieldName]['vDEF']
543 = $flexSegmentResult['databaseRow'][$dataStructureFieldName];
544 }
545 // Set TCA structure result
546 $result['processedTca']['columns'][$fieldName]['config']['ds']
547 ['sheets'][$dataStructureSheetName]['ROOT']['el'][$dataStructureFieldName]
548 = $flexSegmentResult['processedTca']['columns'][$dataStructureFieldName];
549 }
550 }
551 }
552
553 return $result;
554 }
555
556 /**
557 * Prepare data structure and data values for a new section container.
558 *
559 * @param array $result Incoming result array
560 * @param string $fieldName The field name with this flex form
561 * @return array Modified result
562 */
563 protected function prepareNewSectionContainer(array $result, string $fieldName): array
564 {
565 $flexSectionContainerPreparation = $result['flexSectionContainerPreparation'];
566 $flexFormSheetName = $flexSectionContainerPreparation['flexFormSheetName'];
567 $flexFormFieldName = $flexSectionContainerPreparation['flexFormFieldName'];
568 $flexFormContainerName = $flexSectionContainerPreparation['flexFormContainerName'];
569 $flexFormContainerIdentifier = $flexSectionContainerPreparation['flexFormContainerIdentifier'];
570
571 $containerConfiguration = $result['processedTca']['columns'][$fieldName]['config']['ds']
572 ['sheets'][$flexFormSheetName]['ROOT']['el'][$flexFormFieldName]['el'][$flexFormContainerName];
573
574 if (isset($containerConfiguration['el']) && is_array($containerConfiguration['el'])) {
575 $formDataGroup = GeneralUtility::makeInstance(FlexFormSegment::class);
576 $formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup);
577 $inputToFlexFormSegment = [
578 'tableName' => $result['tableName'],
579 'command' => 'new',
580 // It is currently not possible to have pageTsConfig for section container
581 'pageTsConfig' => [],
582 'databaseRow' => [
583 'uid' => $result['databaseRow']['uid'],
584 ],
585 'processedTca' => [
586 'ctrl' => [],
587 'columns' => $containerConfiguration['el'],
588 ],
589 'selectTreeCompileItems' => $result['selectTreeCompileItems'],
590 'flexParentDatabaseRow' => $result['databaseRow'],
591 ];
592 $flexSegmentResult = $formDataCompiler->compile($inputToFlexFormSegment);
593
594 foreach ($containerConfiguration['el'] as $singleFieldName => $singleFieldConfiguration) {
595 // Set 'data structures for this new container' to 'children'
596 $result['processedTca']['columns'][$fieldName]['config']['ds']
597 ['sheets'][$flexFormSheetName]['ROOT']['el']
598 [$flexFormFieldName]['children'][$flexFormContainerIdentifier]
599 = $containerConfiguration;
600 $result['processedTca']['columns'][$fieldName]['config']['ds']
601 ['sheets'][$flexFormSheetName]['ROOT']['el']
602 [$flexFormFieldName]['children'][$flexFormContainerIdentifier]['el']
603 = $flexSegmentResult['processedTca']['columns'];
604 // Set calculated value - this especially contains "default values from TCA"
605 $result['databaseRow'][$fieldName]['data'][$flexFormSheetName]['lDEF']
606 [$flexFormFieldName]['el']
607 [$flexFormContainerIdentifier][$flexFormContainerName]['el'][$singleFieldName]['vDEF']
608 = $flexSegmentResult['databaseRow'][$singleFieldName];
609 }
610 }
611
612 return $result;
613 }
614
615 /**
616 * Modify data structure of a single "sheet"
617 * Sets "secondary" data like sheet names and so on, but does NOT modify single elements
618 *
619 * @param array $dataStructure Given data structure
620 * @param array $pageTsOfSheet Page Ts config of given field
621 * @return array Modified data structure
622 */
623 protected function modifySingleSheetInformation(array $dataStructure, array $pageTsOfSheet)
624 {
625 // Return if no elements defined
626 if (!isset($dataStructure['ROOT']['el']) || !is_array($dataStructure['ROOT']['el'])) {
627 return $dataStructure;
628 }
629 // Rename sheet (tab)
630 if (!empty($pageTsOfSheet['sheetTitle'])) {
631 $dataStructure['ROOT']['sheetTitle'] = $pageTsOfSheet['sheetTitle'];
632 }
633 // Set sheet description (tab)
634 if (!empty($pageTsOfSheet['sheetDescription'])) {
635 $dataStructure['ROOT']['sheetDescription'] = $pageTsOfSheet['sheetDescription'];
636 }
637 // Set sheet short description (tab)
638 if (!empty($pageTsOfSheet['sheetShortDescr'])) {
639 $dataStructure['ROOT']['sheetShortDescr'] = $pageTsOfSheet['sheetShortDescr'];
640 }
641
642 return $dataStructure;
643 }
644
645 /**
646 * @return BackendUserAuthentication
647 */
648 protected function getBackendUser()
649 {
650 return $GLOBALS['BE_USER'];
651 }
652 }