[BUGFIX] Inline in flex
[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 extends AbstractItemProvider implements FormDataProviderInterface {
29
30 /**
31 * Determine possible pageTsConfig overrides and apply them to ds.
32 * Determine available languages and sanitize dv 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 * @return array
39 */
40 public function addData(array $result) {
41 foreach ($result['processedTca']['columns'] as $fieldName => $fieldConfig) {
42 if (empty($fieldConfig['config']['type']) || $fieldConfig['config']['type'] !== 'flex') {
43 continue;
44 }
45
46 $flexIdentifier = $this->getFlexIdentifier($result, $fieldName);
47 $pageTsConfigOfFlex = $this->getPageTsOfFlex($result, $fieldName, $flexIdentifier);
48 $result = $this->modifyOuterDataStructure($result, $fieldName, $pageTsConfigOfFlex);
49 $result = $this->removeExcludeFieldsFromDataStructure($result, $fieldName, $flexIdentifier);
50 $result = $this->removeDisabledFieldsFromDataStructure($result, $fieldName, $pageTsConfigOfFlex);
51 $result = $this->prepareLanguageHandlingInDataValues($result, $fieldName);
52 $result = $this->modifyDataStructureAndDataValuesByFlexFormSegmentGroup($result, $fieldName, $pageTsConfigOfFlex);
53 }
54
55 return $result;
56 }
57
58 /**
59 * Take care of ds_pointerField and friends to determine the correct sub array within
60 * TCA config ds.
61 *
62 * Gets extension identifier. Use second pointer field if it's value is not empty, "list" or "*",
63 * else it must be a plugin and first one will be used.
64 * This code basically determines the sub key of ds field:
65 * config = array(
66 * ds => array(
67 * 'aFlexConfig' => '<flexXml ...
68 * ^^^^^^^^^^^
69 * $flexformIdentifier contains "aFlexConfig" after this operation.
70 *
71 * @todo: This method is only implemented half. It basically should do all the
72 * @todo: pointer handling that is done within BackendUtility::getFlexFormDS() to $srcPointer.
73 *
74 * @param array $result Result array
75 * @param string $fieldName Current handle field name
76 * @return string Pointer
77 */
78 protected function getFlexIdentifier(array $result, $fieldName) {
79 // @todo: Current implementation with the "list_type, CType" fallback is rather limited and customized for
80 // @todo: tt_content, also it forces a ds_pointerField to be defined and a casual "default" sub array does not work
81 $pointerFields = !empty($result['processedTca']['columns'][$fieldName]['config']['ds_pointerField'])
82 ? $result['processedTca']['columns'][$fieldName]['config']['ds_pointerField']
83 : 'list_type,CType';
84 $pointerFields = GeneralUtility::trimExplode(',', $pointerFields);
85 $flexformIdentifier = !empty($result['databaseRow'][$pointerFields[0]]) ? $result['databaseRow'][$pointerFields[0]] : '';
86 if (!empty($result['databaseRow'][$pointerFields[1]])
87 && $result['databaseRow'][$pointerFields[1]] !== 'list'
88 && $result['databaseRow'][$pointerFields[1]] !== '*'
89 ) {
90 $flexformIdentifier = $result['databaseRow'][$pointerFields[1]];
91 }
92 if (empty($flexformIdentifier)) {
93 $flexformIdentifier = 'default';
94 }
95
96 return $flexformIdentifier;
97 }
98
99 /**
100 * Determine TCEFORM.aTable.aField.matchingIdentifier
101 *
102 * @param array $result Result array
103 * @param string $fieldName Handled field name
104 * @param string $flexIdentifier Determined identifier
105 * @return array PageTsConfig for this flex
106 */
107 protected function getPageTsOfFlex(array $result, $fieldName, $flexIdentifier) {
108 $table = $result['tableName'];
109 $pageTs = [];
110 if (!empty($result['pageTsConfigMerged']['TCEFORM.'][$table . '.'][$fieldName . '.'][$flexIdentifier . '.'])
111 && is_array($result['pageTsConfigMerged']['TCEFORM.'][$table . '.'][$fieldName . '.'][$flexIdentifier . '.'])) {
112 $pageTs = $result['pageTsConfigMerged']['TCEFORM.'][$table . '.'][$fieldName . '.'][$flexIdentifier . '.'];
113 }
114 return $pageTs;
115 }
116
117 /**
118 * Handle "outer" flex data structure changes like language and sheet
119 * description. Does not change "TCA" or values of single elements
120 *
121 * @param array $result Result array
122 * @param string $fieldName Current handle field name
123 * @param array $pageTsConfig Given pageTsConfig of this flex form
124 * @return array Modified item array
125 */
126 protected function modifyOuterDataStructure(array $result, $fieldName, $pageTsConfig) {
127 $modifiedDataStructure = $result['processedTca']['columns'][$fieldName]['config']['ds'];
128
129 if (isset($pageTsConfig['langDisable'])) {
130 $modifiedDataStructure['meta']['langDisable'] = $pageTsConfig['langDisable'];
131 }
132 if (isset($pageTsConfig['langChildren'])) {
133 $modifiedDataStructure['meta']['langChildren'] = $pageTsConfig['langChildren'];
134 }
135
136 if (isset($modifiedDataStructure['sheets']) && is_array($modifiedDataStructure['sheets'])) {
137 // Handling multiple sheets
138 foreach ($modifiedDataStructure['sheets'] as $sheetName => $sheetStructure) {
139 if (isset($pageTsConfig[$sheetName . '.']) && is_array($pageTsConfig[$sheetName . '.'])) {
140 $pageTsOfSheet = $pageTsConfig[$sheetName . '.'];
141
142 // Remove whole sheet if disabled
143 if (!empty($pageTsOfSheet['disabled'])) {
144 unset($modifiedDataStructure['sheets'][$sheetName]);
145 continue;
146 }
147
148 // sheetTitle, sheetDescription, sheetShortDescr
149 $modifiedDataStructure['sheets'][$sheetName] = $this->modifySingleSheetInformation($sheetStructure, $pageTsOfSheet);
150 }
151 }
152 }
153
154 $modifiedDataStructure['meta']['langDisable'] = isset($modifiedDataStructure['meta']['langDisable'])
155 ? (bool)$modifiedDataStructure['meta']['langDisable']
156 : FALSE;
157 $modifiedDataStructure['meta']['langChildren'] = isset($modifiedDataStructure['meta']['langChildren'])
158 ? (bool)$modifiedDataStructure['meta']['langChildren']
159 : FALSE;
160
161 $result['processedTca']['columns'][$fieldName]['config']['ds'] = $modifiedDataStructure;
162
163 return $result;
164 }
165
166 /**
167 * Removes fields from data structure the user has no access to
168 *
169 * @param array $result Result array
170 * @param string $fieldName Current handle field name
171 * @param string $flexIdentifier Determined identifier
172 * @return array Modified result
173 */
174 protected function removeExcludeFieldsFromDataStructure(array $result, $fieldName, $flexIdentifier) {
175 $dataStructure = $result['processedTca']['columns'][$fieldName]['config']['ds'];
176 $backendUser = $this->getBackendUser();
177 if ($backendUser->isAdmin() || !isset($dataStructure['sheets']) || !is_array($dataStructure['sheets'])) {
178 return $result;
179 }
180
181 $userNonExcludeFields = GeneralUtility::trimExplode(',', $backendUser->groupData['non_exclude_fields']);
182 $excludeFieldsPrefix = $result['tableName'] . ':' . $fieldName . ';' . $flexIdentifier . ';';
183 $nonExcludeFields = [];
184 foreach ($userNonExcludeFields as $userNonExcludeField) {
185 if (strpos($userNonExcludeField, $excludeFieldsPrefix) !== FALSE) {
186 $exploded = explode(';', $userNonExcludeField);
187 $sheetName = $exploded[2];
188 $fieldName = $exploded[3];
189 $nonExcludeFields[$sheetName] = $fieldName;
190 }
191 }
192
193 foreach ($dataStructure['sheets'] as $sheetName => $sheetDefinition) {
194 if (!isset($sheetDefinition['ROOT']['el']) || !is_array($sheetDefinition['ROOT']['el'])) {
195 continue;
196 }
197 foreach ($sheetDefinition['ROOT']['el'] as $flexFieldName => $fieldDefinition) {
198 if (!empty($fieldDefinition['exclude']) && empty($nonExcludeFields[$sheetName])) {
199 unset($result['processedTca']['columns'][$fieldName]['config']['ds']['sheets'][$sheetName]['ROOT']['el'][$flexFieldName]);
200 }
201 }
202 }
203
204 return $result;
205 }
206
207 /**
208 * Handle "outer" flex data structure changes like language and sheet
209 * description. Does not change "TCA" or values of single elements
210 *
211 * @param array $result Result array
212 * @param string $fieldName Current handle field name
213 * @param array $pageTsConfig Given pageTsConfig of this flex form
214 * @return array Modified item array
215 */
216 protected function removeDisabledFieldsFromDataStructure(array $result, $fieldName, $pageTsConfig) {
217 $dataStructure = $result['processedTca']['columns'][$fieldName]['config']['ds'];
218 if (!isset($dataStructure['sheets']) || !is_array($dataStructure['sheets'])) {
219 return $result;
220 }
221 foreach ($dataStructure['sheets'] as $sheetName => $sheetDefinition) {
222 if (!isset($sheetDefinition['ROOT']['el']) || !is_array($sheetDefinition['ROOT']['el'])
223 || !isset($pageTsConfig[$sheetName . '.'])) {
224 continue;
225 }
226 foreach ($sheetDefinition['ROOT']['el'] as $flexFieldName => $fieldDefinition) {
227 if (!empty($pageTsConfig[$sheetName . '.'][$flexFieldName . '.']['disabled'])) {
228 unset($result['processedTca']['columns'][$fieldName]['config']['ds']['sheets'][$sheetName]['ROOT']['el'][$flexFieldName]);
229 }
230 }
231 }
232
233 return $result;
234 }
235
236 /**
237 * Remove data values in languages the user has no access to and add dummy entries
238 * for languages that are available but do not exist in data values yet.
239 *
240 * @param array $result Result array
241 * @param string $fieldName Current handle field name
242 * @return array Modified item array
243 */
244 protected function prepareLanguageHandlingInDataValues(array $result, $fieldName) {
245 $backendUser = $this->getBackendUser();
246 $dataStructure = $result['processedTca']['columns'][$fieldName]['config']['ds'];
247
248 $langDisabled = $dataStructure['meta']['langDisable'];
249 $langChildren = $dataStructure['meta']['langChildren'];
250
251 // Existing page language overlays are only considered if options.checkPageLanguageOverlay is set in userTs
252 $checkPageLanguageOverlay = FALSE;
253 if (isset($result['userTsConfig']['options.']) && is_array($result['userTsConfig']['options.'])
254 && array_key_exists('checkPageLanguageOverlay', $result['userTsConfig']['options.'])
255 ) {
256 $checkPageLanguageOverlay = (bool)$result['userTsConfig']['options.']['checkPageLanguageOverlay'];
257 }
258
259 $systemLanguageRows = $result['systemLanguageRows'];
260
261 // Contains all language iso code that are valid and user has access to
262 $availableLanguageCodes = [];
263 $defaultCodeWasAdded = FALSE;
264 foreach ($systemLanguageRows as $systemLanguageRow) {
265 $isoCode = $systemLanguageRow['iso'];
266 $isAvailable = TRUE;
267 if ($langDisabled && $isoCode !== 'DEF') {
268 $isAvailable = FALSE;
269 }
270 // @todo: Is it possible a user has no write access to default lang? If so, what to do?
271 if (!$backendUser->checkLanguageAccess($systemLanguageRow['uid'])) {
272 $isAvailable = FALSE;
273 }
274 if ($checkPageLanguageOverlay && $systemLanguageRow['uid'] > 0) {
275 $found = FALSE;
276 foreach ($result['pageLanguageOverlayRows'] as $overlayRow) {
277 if ((int)$overlayRow['sys_language_uid'] === (int)$systemLanguageRow['uid']) {
278 $found = TRUE;
279 break;
280 }
281 }
282 if (!$found) {
283 $isAvailable = FALSE;
284 }
285 }
286 if ($isoCode === 'DEF' && $defaultCodeWasAdded) {
287 $isAvailable = FALSE;
288 }
289 if ($isAvailable) {
290 $availableLanguageCodes[] = $isoCode;
291 }
292 if ($isoCode === 'DEF') {
293 $defaultCodeWasAdded = TRUE;
294 }
295 }
296 // Set the list of available languages in the data structure "meta" section to have it
297 // available for the render engine to iterate over it.
298 $result['processedTca']['columns'][$fieldName]['config']['ds']['meta']['availableLanguageCodes'] = $availableLanguageCodes;
299
300 if (!$langChildren) {
301 $allowedLanguageSheetKeys = [];
302 foreach ($availableLanguageCodes as $isoCode) {
303 $allowedLanguageSheetKeys['l' . $isoCode] = [];
304 }
305 $result = $this->setLanguageSheetsInDataValues($result, $fieldName, $allowedLanguageSheetKeys);
306
307 // With $langChildren = 0, values must only contain vDEF prefixed keys
308 $allowedValueLevelLanguageKeys = [];
309 $allowedValueLevelLanguageKeys['vDEF'] = [];
310 $allowedValueLevelLanguageKeys['vDEF.vDEFbase'] = [];
311 // A richtext special
312 $allowedValueLevelLanguageKeys['_TRANSFORM_vDEF.vDEFbase'] = [];
313 $result = $this->setLanguageValueLevelValues($result, $fieldName, $allowedValueLevelLanguageKeys);
314 } else {
315 // langChildren is set - only lDEF as sheet language is allowed, but more fields on value field level
316 $allowedLanguageSheetKeys = [
317 'lDEF' => [],
318 ];
319 $result = $this->setLanguageSheetsInDataValues($result, $fieldName, $allowedLanguageSheetKeys);
320
321 $allowedValueLevelLanguageKeys = [];
322 foreach ($availableLanguageCodes as $isoCode) {
323 $allowedValueLevelLanguageKeys['v' . $isoCode] = [];
324 $allowedValueLevelLanguageKeys['v' . $isoCode . '.vDEFbase'] = [];
325 $allowedValueLevelLanguageKeys['_TRANSFORM_v' . $isoCode . '.vDEFbase'] = [];
326 }
327 $result = $this->setLanguageValueLevelValues($result, $fieldName, $allowedValueLevelLanguageKeys);
328 }
329
330 return $result;
331 }
332
333 /**
334 * Feed single flex field and data to FlexFormSegment FormData compiler and merge result.
335 * This one is nasty. Goal is to have processed TCA stuff in DS and also have validated / processed data values.
336 *
337 * Three main parts in this method:
338 * * Process values of existing section container for default values
339 * * Process values and TCA of possible section container and create a default value row for each
340 * * Process TCA of "normal" fields and have default values in data ['templateRows']['containerName'] parallel to section ['el']
341 *
342 * @param array $result Result array
343 * @param string $fieldName Current handle field name
344 * @param array $pageTsConfig Given pageTsConfig of this flex form
345 * @return array Modified item array
346 */
347 protected function modifyDataStructureAndDataValuesByFlexFormSegmentGroup(array $result, $fieldName, $pageTsConfig) {
348 $dataStructure = $result['processedTca']['columns'][$fieldName]['config']['ds'];
349 $dataValues = $result['databaseRow'][$fieldName];
350 $tableName = $result['tableName'];
351
352 $availableLanguageCodes = $result['processedTca']['columns'][$fieldName]['config']['ds']['meta']['availableLanguageCodes'];
353 if ($dataStructure['meta']['langChildren']) {
354 $languagesOnSheetLevel = [ 'DEF' ];
355 $languagesOnElementLevel = $availableLanguageCodes;
356 } else {
357 $languagesOnSheetLevel = $availableLanguageCodes;
358 $languagesOnElementLevel = [ 'DEF' ];
359 }
360
361 $result['processedTca']['columns'][$fieldName]['config']['ds']['meta']['languagesOnSheetLevel'] = $languagesOnSheetLevel;
362 $result['processedTca']['columns'][$fieldName]['config']['ds']['meta']['languagesOnElement'] = $languagesOnElementLevel;
363
364 if (!isset($dataStructure['sheets']) || !is_array($dataStructure['sheets'])) {
365 return $result;
366 }
367
368 /** @var FlexFormSegment $formDataGroup */
369 $formDataGroup = GeneralUtility::makeInstance(FlexFormSegment::class);
370 /** @var FormDataCompiler $formDataCompiler */
371 $formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup);
372
373 foreach ($dataStructure['sheets'] as $dataStructureSheetName => $dataStructureSheetDefinition) {
374 if (!isset($dataStructureSheetDefinition['ROOT']['el']) || !is_array($dataStructureSheetDefinition['ROOT']['el'])) {
375 continue;
376 }
377 $dataStructureSheetElements = $dataStructureSheetDefinition['ROOT']['el'];
378
379 // Prepare pageTsConfig of this sheet
380 $pageTsConfigMerged['TCEFORM.'][$tableName . '.'] = [];
381 if (isset($pageTsConfig[$dataStructureSheetName . '.']) && is_array($pageTsConfig[$dataStructureSheetName . '.'])) {
382 $pageTsConfigMerged['TCEFORM.'][$tableName . '.'] = $pageTsConfig[$dataStructureSheetName . '.'];
383 }
384
385 foreach ($languagesOnSheetLevel as $isoSheetLevel) {
386 $langSheetLevel = 'l' . $isoSheetLevel;
387 foreach ($dataStructureSheetElements as $dataStructureSheetElementName => $dataStructureSheetElementDefinition) {
388 if (isset($dataStructureSheetElementDefinition['type']) && $dataStructureSheetElementDefinition['type'] === 'array'
389 && isset($dataStructureSheetElementDefinition['section']) && $dataStructureSheetElementDefinition['section'] === '1'
390 ) {
391 // A section
392
393 // Existing section container elements
394 if (isset($dataValues['data'][$dataStructureSheetName][$langSheetLevel][$dataStructureSheetElementName]['el'])
395 && is_array($dataValues['data'][$dataStructureSheetName][$langSheetLevel][$dataStructureSheetElementName]['el'])
396 ) {
397 $containerArray = $dataValues['data'][$dataStructureSheetName][$langSheetLevel][$dataStructureSheetElementName]['el'];
398 foreach ($containerArray as $aContainerNumber => $aContainerArray) {
399 if (is_array($aContainerArray)) {
400 foreach ($aContainerArray as $aContainerName => $aContainerElementArray) {
401 if ($aContainerName === '_TOGGLE') {
402 // Don't handle internal toggle state field
403 continue;
404 }
405 if (!isset($dataStructureSheetElements[$dataStructureSheetElementName]['el'][$aContainerName])) {
406 // Container not defined in ds
407 continue;
408 }
409 foreach ($dataStructureSheetElements[$dataStructureSheetElementName]['el'][$aContainerName]['el'] as $singleFieldName => $singleFieldConfiguration) {
410 // $singleFieldValueArray = ['data']['sSections']['lDEF']['section_1']['el']['1']['container_1']['el']['element_1']
411 $singleFieldValueArray = [];
412 if (isset($aContainerElementArray['el'][$singleFieldName])
413 && is_array($aContainerElementArray['el'][$singleFieldName])
414 ) {
415 $singleFieldValueArray = $aContainerElementArray['el'][$singleFieldName];
416 }
417 foreach ($languagesOnElementLevel as $isoElementLevel) {
418 $langElementLevel = 'v' . $isoElementLevel;
419 $valueArray = [
420 'uid' => $result['databaseRow']['uid'],
421 ];
422 $command = 'new';
423 if (array_key_exists($langElementLevel, $singleFieldValueArray)) {
424 $command = 'edit';
425 $valueArray[$singleFieldName] = $singleFieldValueArray[$langElementLevel];
426 }
427 $inputToFlexFormSegment = [
428 'tableName' => $result['tableName'],
429 'command' => $command,
430 // It is currently not possible to have pageTsConfig for section container
431 'pageTsConfigMerged' => [],
432 'databaseRow' => $valueArray,
433 'vanillaTableTca' => [
434 'ctrl' => [],
435 'columns' => [
436 $singleFieldName => $singleFieldConfiguration,
437 ],
438 ],
439 'processedTca' => [
440 'ctrl' => [],
441 'columns' => [
442 $singleFieldName => $singleFieldConfiguration,
443 ],
444 ],
445 ];
446 $flexSegmentResult = $formDataCompiler->compile($inputToFlexFormSegment);
447 // Set data value result
448 if (array_key_exists($singleFieldName, $flexSegmentResult['databaseRow'])) {
449 $result['databaseRow'][$fieldName]
450 ['data'][$dataStructureSheetName][$langSheetLevel][$dataStructureSheetElementName]['el']
451 [$aContainerNumber][$aContainerName]['el']
452 [$singleFieldName][$langElementLevel]
453 = $flexSegmentResult['databaseRow'][$singleFieldName];
454 }
455 // Set TCA structure result, actually, this call *might* be obsolete since the "dummy"
456 // handling below will set it again.
457 $result['processedTca']['columns'][$fieldName]['config']['ds']
458 ['sheets'][$dataStructureSheetName]['ROOT']['el'][$dataStructureSheetElementName]['el']
459 [$aContainerName]['el'][$singleFieldName]
460 = $flexSegmentResult['processedTca']['columns'][$singleFieldName];
461 }
462 }
463 }
464 }
465 }
466 } // End of existing data value handling
467
468 // Prepare "fresh" row for every possible container
469 if (isset($dataStructureSheetElements[$dataStructureSheetElementName]['el']) && is_array($dataStructureSheetElements[$dataStructureSheetElementName]['el'])) {
470 foreach ($dataStructureSheetElements[$dataStructureSheetElementName]['el'] as $possibleContainerName => $possibleContainerConfiguration) {
471 if (isset($possibleContainerConfiguration['el']) && is_array($possibleContainerConfiguration['el'])) {
472 // Initialize result data array templateRows
473 $result['databaseRow'][$fieldName]
474 ['data'][$dataStructureSheetName][$langSheetLevel][$dataStructureSheetElementName]['templateRows']
475 [$possibleContainerName]['el']
476 = [];
477 foreach ($possibleContainerConfiguration['el'] as $singleFieldName => $singleFieldConfiguration) {
478 foreach ($languagesOnElementLevel as $isoElementLevel) {
479 $langElementLevel = 'v' . $isoElementLevel;
480 $inputToFlexFormSegment = [
481 'tableName' => $result['tableName'],
482 'command' => 'new',
483 'pageTsConfigMerged' => [],
484 'databaseRow' => [
485 'uid' => $result['databaseRow']['uid'],
486 ],
487 'vanillaTableTca' => [
488 'ctrl' => [],
489 'columns' => [
490 $singleFieldName => $singleFieldConfiguration,
491 ],
492 ],
493 'processedTca' => [
494 'ctrl' => [],
495 'columns' => [
496 $singleFieldName => $singleFieldConfiguration,
497 ],
498 ],
499 ];
500 $flexSegmentResult = $formDataCompiler->compile($inputToFlexFormSegment);
501 if (array_key_exists($singleFieldName, $flexSegmentResult['databaseRow'])) {
502 $result['databaseRow'][$fieldName]
503 ['data'][$dataStructureSheetName][$langSheetLevel][$dataStructureSheetElementName]['templateRows']
504 [$possibleContainerName]['el'][$singleFieldName][$langElementLevel]
505 = $flexSegmentResult['databaseRow'][$singleFieldName];
506 }
507 $result['processedTca']['columns'][$fieldName]['config']['ds']
508 ['sheets'][$dataStructureSheetName]['ROOT']['el'][$dataStructureSheetElementName]['el']
509 [$possibleContainerName]['el'][$singleFieldName]
510 = $flexSegmentResult['processedTca']['columns'][$singleFieldName];
511 }
512 }
513 }
514 }
515 } // End of preparation for each possible container
516
517 // type without section is not ok
518 } elseif (isset($dataStructureSheetElementDefinition['type']) || isset($dataStructureSheetElementDefinition['section'])) {
519 throw new \UnexpectedValueException(
520 'Broken data structure on field name ' . $fieldName . '. section without type or vice versa is not allowed',
521 1440685208
522 );
523
524 // A "normal" TCA element
525 } else {
526 foreach ($languagesOnElementLevel as $isoElementLevel) {
527 $langElementLevel = 'v' . $isoElementLevel;
528 $valueArray = [
529 // uid of "parent" is given down for inline elements to resolve correctly
530 'uid' => $result['databaseRow']['uid'],
531 ];
532 $command = 'new';
533 if (isset($dataValues['data'][$dataStructureSheetName][$langSheetLevel][$dataStructureSheetElementName])
534 && array_key_exists($langElementLevel, $dataValues['data'][$dataStructureSheetName][$langSheetLevel][$dataStructureSheetElementName])
535 ) {
536 $command = 'edit';
537 $valueArray[$dataStructureSheetElementName] = $dataValues['data'][$dataStructureSheetName][$langSheetLevel][$dataStructureSheetElementName][$langElementLevel];
538 }
539 $inputToFlexFormSegment = [
540 // tablename of "parent" is given down for inline elements to resolve correctly
541 'tableName' => $result['tableName'],
542 'command' => $command,
543 'pageTsConfigMerged' => $pageTsConfigMerged,
544 'databaseRow' => $valueArray,
545 'vanillaTableTca' => [
546 'ctrl' => [],
547 'columns' => [
548 $dataStructureSheetElementName => $dataStructureSheetElementDefinition,
549 ],
550 ],
551 'processedTca' => [
552 'ctrl' => [],
553 'columns' => [
554 $dataStructureSheetElementName => $dataStructureSheetElementDefinition,
555 ],
556 ],
557 ];
558 $flexSegmentResult = $formDataCompiler->compile($inputToFlexFormSegment);
559 // Set data value result
560 if (array_key_exists($dataStructureSheetElementName, $flexSegmentResult['databaseRow'])) {
561 $result['databaseRow'][$fieldName]
562 ['data'][$dataStructureSheetName][$langSheetLevel][$dataStructureSheetElementName][$langElementLevel]
563 = $flexSegmentResult['databaseRow'][$dataStructureSheetElementName];
564 }
565 // Set TCA structure result
566 $result['processedTca']['columns'][$fieldName]['config']['ds']
567 ['sheets'][$dataStructureSheetName]['ROOT']['el'][$dataStructureSheetElementName]
568 = $flexSegmentResult['processedTca']['columns'][$dataStructureSheetElementName];
569 }
570 } // End of single element handling
571
572 }
573 }
574 }
575
576 return $result;
577 }
578
579 /**
580 * Modify data structure of a single "sheet"
581 * Sets "secondary" data like sheet names and so on, but does NOT modify single elements
582 *
583 * @param array $dataStructure Given data structure
584 * @param array $pageTsOfSheet Page Ts config of given field
585 * @return array Modified data structure
586 */
587 protected function modifySingleSheetInformation(array $dataStructure, array $pageTsOfSheet) {
588 // Return if no elements defined
589 if (!isset($dataStructure['ROOT']['el']) || !is_array($dataStructure['ROOT']['el'])) {
590 return $dataStructure;
591 }
592
593 // Rename sheet (tab)
594 if (!empty($pageTsOfSheet['sheetTitle'])) {
595 $dataStructure['ROOT']['sheetTitle'] = $pageTsOfSheet['sheetTitle'];
596 }
597 // Set sheet description (tab)
598 if (!empty($pageTsOfSheet['sheetDescription'])) {
599 $dataStructure['ROOT']['sheetDescription'] = $pageTsOfSheet['sheetDescription'];
600 }
601 // Set sheet short description (tab)
602 if (!empty($pageTsOfSheet['sheetShortDescr'])) {
603 $dataStructure['ROOT']['sheetShortDescr'] = $pageTsOfSheet['sheetShortDescr'];
604 }
605
606 return $dataStructure;
607 }
608
609 /**
610 * Add new sheet languages not yet in data values and remove invalid ones
611 *
612 * databaseRow['aFlex']['data']['sDEF'] = array('lDEF', 'lNotAllowed');
613 * allowedLanguageKeys = array('lDEF', 'lNEW')
614 * -> databaseRow['aFlex']['data']['sDEF'] = array('lDEF', 'lNEW');
615 *
616 * @param array $result Result array
617 * @param string $fieldName Current handle field name
618 * @param array $allowedKeys List of allowed keys
619 * @return array Modified result
620 */
621 protected function setLanguageSheetsInDataValues(array $result, $fieldName, array $allowedKeys) {
622 $valueArray = [];
623 if (isset($result['databaseRow'][$fieldName]['data']) && is_array($result['databaseRow'][$fieldName]['data'])) {
624 $valueArray = $result['databaseRow'][$fieldName]['data'];
625 }
626 foreach ($valueArray as $sheetName => $sheetLanguages) {
627 // Add iso code with empty array if it does not yet exist in data
628 // and remove codes from data that do not exist in $allowed
629 $result['databaseRow'][$fieldName]['data'][$sheetName]
630 = array_intersect_key(array_merge($allowedKeys, $sheetLanguages), $allowedKeys);
631 }
632 return $result;
633 }
634
635 /**
636 * Remove invalid keys from data value array the user has no access to
637 * or that were removed or similar to prevent any rendering of this stuff
638 *
639 * Handles this for "normal" fields and also for section container element values.
640 *
641 * @param array $result Result array
642 * @param string $fieldName Current handle field name
643 * @param array $allowedKeys List of allowed keys
644 * @return array Modified result
645 */
646 protected function setLanguageValueLevelValues(array $result, $fieldName, $allowedKeys) {
647 $valueArray = [];
648 if (isset($result['databaseRow'][$fieldName]['data']) && is_array($result['databaseRow'][$fieldName]['data'])) {
649 $valueArray = $result['databaseRow'][$fieldName]['data'];
650 }
651 foreach ($valueArray as $sheetName => $sheetLanguages) {
652 if (!is_array($sheetLanguages)) {
653 continue;
654 }
655 foreach ($sheetLanguages as $languageName => $languageFields) {
656 if (!is_array($languageFields)) {
657 continue;
658 }
659 foreach ($languageFields as $flexFieldName => $fieldValues) {
660 if (!is_array($fieldValues)) {
661 continue;
662 }
663 $allowedSingleValues = [];
664 foreach ($fieldValues as $fieldValueName => $fieldValueValue) {
665 if (is_array($fieldValueValue) && $fieldValueName === 'el') {
666 // A section container
667 foreach ($fieldValueValue as $sectionNumber => $sectionElementArray) {
668 if (is_array($sectionElementArray)) {
669 $allowedSingleValues['el'][$sectionNumber] = [];
670 foreach ($sectionElementArray as $sectionElementName => $containerElementArray) {
671 if (isset($containerElementArray['el']) && is_array($containerElementArray['el']) && !empty($containerElementArray['el'])) {
672 foreach ($containerElementArray['el'] as $aContainerElementName => $aContainerElementValues) {
673 if (is_array($aContainerElementValues)) {
674 foreach ($aContainerElementValues as $aContainerElementValueKey => $aContainerElementValueValue) {
675 if (array_key_exists($aContainerElementValueKey, $allowedKeys)) {
676 $allowedSingleValues['el'][$sectionNumber][$sectionElementName]
677 ['el'][$aContainerElementName][$aContainerElementValueKey] = $aContainerElementValueValue;
678 }
679 }
680 } else {
681 $allowedSingleValues['el'][$sectionNumber][$sectionElementName]['el']
682 [$aContainerElementName] = $aContainerElementValues;
683 }
684 }
685 } else {
686 $allowedSingleValues['el'][$sectionNumber][$sectionElementName] = $containerElementArray;
687 }
688 }
689 } else {
690 $allowedSingleValues = $sectionElementArray;
691 }
692 }
693 } else {
694 // "normal" value field
695 if (array_key_exists($fieldValueName, $allowedKeys)) {
696 $allowedSingleValues[$fieldValueName] = $fieldValueValue;
697 }
698 }
699 }
700 $result['databaseRow'][$fieldName]['data'][$sheetName][$languageName][$flexFieldName] = $allowedSingleValues;
701 }
702 }
703 }
704 return $result;
705 }
706
707 /**
708 * @return BackendUserAuthentication
709 */
710 protected function getBackendUser() {
711 return $GLOBALS['BE_USER'];
712 }
713
714 }