‪TYPO3CMS  ‪main
FlexFormTools.php
Go to the documentation of this file.
1 <?php
2 
3 declare(strict_types=1);
4 
5 /*
6  * This file is part of the TYPO3 CMS project.
7  *
8  * It is free software; you can redistribute it and/or modify it under
9  * the terms of the GNU General Public License, either version 2
10  * of the License, or any later version.
11  *
12  * For the full copyright and license information, please read the
13  * LICENSE.txt file that was distributed with this source code.
14  *
15  * The TYPO3 project - inspiring people to share!
16  */
17 
19 
20 use Psr\EventDispatcher\EventDispatcherInterface;
32 
40 {
41  public function ‪__construct(
42  private readonly EventDispatcherInterface $eventDispatcher,
43  ) {}
44 
81  public function ‪getDataStructureIdentifier(array $fieldTca, string $tableName, string $fieldName, array $row): string
82  {
83  $dataStructureIdentifier = $this->eventDispatcher
84  ->dispatch(new ‪BeforeFlexFormDataStructureIdentifierInitializedEvent($fieldTca, $tableName, $fieldName, $row))
85  ->getIdentifier() ?? $this->‪getDefaultIdentifier($fieldTca, $tableName, $fieldName, $row);
86  $dataStructureIdentifier = $this->eventDispatcher
87  ->dispatch(new ‪AfterFlexFormDataStructureIdentifierInitializedEvent($fieldTca, $tableName, $fieldName, $row, $dataStructureIdentifier))
88  ->getIdentifier();
89  return json_encode($dataStructureIdentifier, JSON_THROW_ON_ERROR);
90  }
91 
120  public function ‪parseDataStructureByIdentifier(string ‪$identifier): array
121  {
122  // Throw an exception for an empty string. This might be a valid use case for new
123  // records in some situations, so this is catchable to give callers a chance to deal with that.
124  if (empty(‪$identifier)) {
126  'Empty string given to parseFlexFormDataStructureByIdentifier(). This exception might '
127  . ' be caught to handle some new record situations properly',
128  1478100828
129  );
130  }
131  $parsedIdentifier = json_decode(‪$identifier, true);
132  if (!is_array($parsedIdentifier) || $parsedIdentifier === []) {
133  // If there is some identifier and it can't be decoded, programming error -> not catchable
134  throw new \RuntimeException(
135  'Identifier could not be decoded to an array.',
136  1478345642
137  );
138  }
139  $dataStructure = $this->eventDispatcher
140  ->dispatch(new ‪BeforeFlexFormDataStructureParsedEvent($parsedIdentifier))
141  ->getDataStructure() ?? $this->‪getDefaultStructureForIdentifier($parsedIdentifier);
142  $dataStructure = $this->‪convertDataStructureToArray($dataStructure);
143  $dataStructure = $this->‪ensureDefaultSheet($dataStructure);
144  $dataStructure = $this->‪resolveFileDirectives($dataStructure);
145  $dataStructure = $this->‪migrateAndPrepareFlexTca($dataStructure);
146  return $this->eventDispatcher
147  ->dispatch(new ‪AfterFlexFormDataStructureParsedEvent($dataStructure, $parsedIdentifier))
148  ->getDataStructure();
149  }
150 
159  public function ‪cleanFlexFormXML(string $table, string $field, array $row): string
160  {
161  if (!is_array(‪$GLOBALS['TCA'][$table]['columns'][$field]['config'] ?? false) || !isset($row[$field])) {
162  throw new \RuntimeException('Can not clean up FlexForm XML for a column not declared in TCA or not in record.', 1697554398);
163  }
164  try {
165  $dataStructureArray = $this->‪parseDataStructureByIdentifier($this->‪getDataStructureIdentifier(‪$GLOBALS['TCA'][$table]['columns'][$field], $table, $field, $row));
167  // Data structure can not be resolved or parsed. Reset value to empty string.
168  return '';
169  }
170  $valueArray = ‪GeneralUtility::xml2array($row[$field]);
171  if (!is_array($valueArray)) {
172  // Current flex form values can not be parsed to an array. The entire thing is invalid. Reset to empty string.
173  return '';
174  }
175  if (!is_array($dataStructureArray['sheets'] ?? false)) {
176  // We might return empty string instead of throwing here, unsure.
177  throw new \RuntimeException('Data structure should always declare at least one sheet', 1697555523);
178  }
179  $newValueArray = [];
180  foreach ($dataStructureArray['sheets'] as $sheetKey => $sheetData) {
181  foreach (($sheetData['ROOT']['el'] ?? []) as $sheetElementKey => $sheetElementData) {
182  // For all elements allowed in Data Structure.
183  if (($sheetElementData['type'] ?? '') === 'array') {
184  // This is a section.
185  if (!is_array($sheetElementData['el'] ?? false) || !is_array($valueArray['data'][$sheetKey]['lDEF'][$sheetElementKey]['el'] ?? false)) {
186  // No possible containers defined for this section in DS, or no values set for this section.
187  continue;
188  }
189  foreach ($valueArray['data'][$sheetKey]['lDEF'][$sheetElementKey]['el'] as $valueSectionContainerKey => $valueSectionContainers) {
190  // We have containers for this section in values.
191  if (!is_array($valueSectionContainers ?? false)) {
192  // Values don't validate to an array, skip.
193  continue;
194  }
195  foreach ($valueSectionContainers as $valueContainerType => $valueContainerElements) {
196  // For all value containers in this section.
197  if (!is_array($sheetElementData['el'][$valueContainerType]['el'] ?? false)) {
198  // There is no DS for this container type, skip.
199  continue;
200  }
201  foreach (array_keys($sheetElementData['el'][$valueContainerType]['el']) as $containerElement) {
202  // Container type of this value container exists in DS. Iterate DS container to pick allowed single elements.
203  if (isset($valueContainerElements['el'][$containerElement]['vDEF'])) {
204  $newValueArray['data'][$sheetKey]['lDEF'][$sheetElementKey]['el'][$valueSectionContainerKey][$valueContainerType]['el'][$containerElement]['vDEF'] =
205  $valueContainerElements['el'][$containerElement]['vDEF'];
206  }
207  }
208  }
209  if (isset($valueSectionContainers['_TOGGLE'])) {
210  // This was removed in TYPO3 v13, see #102551
211  unset($newValueArray['data'][$sheetKey]['lDEF'][$sheetElementKey]['el'][$valueSectionContainerKey]['_TOGGLE']);
212  }
213  }
214  } elseif (isset($valueArray['data'][$sheetKey]['lDEF'][$sheetElementKey]['vDEF'])) {
215  // Not a section but a simple field. Keep value if set.
216  $newValueArray['data'][$sheetKey]['lDEF'][$sheetElementKey]['vDEF'] = $valueArray['data'][$sheetKey]['lDEF'][$sheetElementKey]['vDEF'];
217  }
218  }
219  }
220  return $this->‪flexArray2Xml($newValueArray);
221  }
222 
228  public function ‪flexArray2Xml(array $array): string
229  {
230  // Map the weird keys from the internal array to tags and attributes.
231  $options = [
232  'parentTagMap' => [
233  'data' => 'sheet',
234  'sheet' => 'language',
235  'language' => 'field',
236  'el' => 'field',
237  'field' => 'value',
238  'field:el' => 'el',
239  'el:_IS_NUM' => 'section',
240  'section' => 'itemType',
241  ],
242  'disableTypeAttrib' => 2,
243  ];
244  return '<?xml version="1.0" encoding="utf-8" standalone="yes" ?>' . LF .
245  GeneralUtility::array2xml($array, '', 0, 'T3FlexForms', 4, $options);
246  }
247 
253  protected function ‪migrateFlexFormTcaRecursive(array $structure): array
254  {
255  $newStructure = [];
256  foreach ($structure as $key => $value) {
257  if ($key === 'el' && is_array($value)) {
258  $newSubStructure = [];
259  $tcaMigration = GeneralUtility::makeInstance(TcaMigration::class);
260  foreach ($value as $subKey => $subValue) {
261  // On-the-fly migration for flex form "TCA". Call the TcaMigration and log any deprecations.
262  $dummyTca = [
263  'dummyTable' => [
264  'columns' => [
265  'dummyField' => $subValue,
266  ],
267  ],
268  ];
269  $migratedTca = $tcaMigration->migrate($dummyTca);
270  $messages = $tcaMigration->getMessages();
271  if (!empty($messages)) {
272  $context = 'FlexFormTools did an on-the-fly migration of a flex form data structure. This is deprecated and will be removed.'
273  . ' Merge the following changes into the flex form definition "' . $subKey . '":';
274  array_unshift($messages, $context);
275  trigger_error(implode(LF, $messages), E_USER_DEPRECATED);
276  }
277  $newSubStructure[$subKey] = $migratedTca['dummyTable']['columns']['dummyField'];
278  }
279  $value = $newSubStructure;
280  }
281  if (is_array($value)) {
282  $value = $this->‪migrateFlexFormTcaRecursive($value);
283  }
284  $newStructure[$key] = $value;
285  }
286  return $newStructure;
287  }
288 
300  protected function ‪getDefaultIdentifier(array $fieldTca, string $tableName, string $fieldName, array $row): array
301  {
302  $tcaDataStructureArray = $fieldTca['config']['ds'] ?? null;
303  if (is_array($tcaDataStructureArray)) {
304  $dataStructureIdentifier = $this->‪getDataStructureIdentifierFromTcaArray(
305  $fieldTca,
306  $tableName,
307  $fieldName,
308  $row
309  );
310  } else {
311  throw new \RuntimeException(
312  'TCA misconfiguration in table "' . $tableName . '" field "' . $fieldName . '" config section:'
313  . ' The field is configured as type="flex" and no "ds_pointerField" is defined and "ds" is not an array.'
314  . ' Either configure a default data structure in [\'ds\'][\'default\'] or add a "ds_pointerField" lookup mechanism'
315  . ' that specifies the data structure',
316  1463826960
317  );
318  }
319  return $dataStructureIdentifier;
320  }
321 
362  protected function ‪getDataStructureIdentifierFromTcaArray(array $fieldTca, string $tableName, string $fieldName, array $row): array
363  {
364  $dataStructureIdentifier = [
365  'type' => 'tca',
366  'tableName' => $tableName,
367  'fieldName' => $fieldName,
368  'dataStructureKey' => null,
369  ];
370  $tcaDataStructurePointerField = $fieldTca['config']['ds_pointerField'] ?? null;
371  if ($tcaDataStructurePointerField === null) {
372  // No ds_pointerField set -> use 'default' as ds array key if exists.
373  if (isset($fieldTca['config']['ds']['default'])) {
374  $dataStructureIdentifier['dataStructureKey'] = 'default';
375  } else {
376  // A tca is configured as flex without ds_pointerField. A 'default' key must exist, otherwise
377  // this is a configuration error.
378  // May happen with an unloaded extension -> catchable
379  throw new ‪InvalidTcaException(
380  'TCA misconfiguration in table "' . $tableName . '" field "' . $fieldName . '" config section:'
381  . ' The field is configured as type="flex" and no "ds_pointerField" is defined. Either configure'
382  . ' a default data structure in [\'ds\'][\'default\'] or add a "ds_pointerField" lookup mechanism'
383  . ' that specifies the data structure',
384  1463652560
385  );
386  }
387  } else {
388  // ds_pointerField is set, it can be a comma separated list of two fields, explode it.
389  $pointerFieldArray = ‪GeneralUtility::trimExplode(',', $tcaDataStructurePointerField, true);
390  // Obvious configuration error, either one or two fields must be declared
391  $pointerFieldsCount = count($pointerFieldArray);
392  if ($pointerFieldsCount !== 1 && $pointerFieldsCount !== 2) {
393  // If it's there, it must be correct -> not catchable
394  throw new \RuntimeException(
395  'TCA misconfiguration in table "' . $tableName . '" field "' . $fieldName . '" config section:'
396  . ' ds_pointerField must be either a single field name, or a comma separated list of two fields,'
397  . ' the invalid configuration string provided was: "' . $tcaDataStructurePointerField . '"',
398  1463577497
399  );
400  }
401  // Verify first field exists in row array. If not, this is a hard error: Any extension that sets a
402  // ds_pointerField to some field name should take care that field does exist, too. They are a pair,
403  // so there shouldn't be a situation where the field does not exist. Throw an exception if that is violated.
404  if (!isset($row[$pointerFieldArray[0]])) {
405  // If it's declared, it must exist -> not catchable
406  throw new \RuntimeException(
407  'TCA misconfiguration in table "' . $tableName . '" field "' . $fieldName . '" config section:'
408  . ' ds_pointerField "' . $pointerFieldArray[0] . '" points to a field name that does not exist.',
409  1463578899
410  );
411  }
412  // Similar situation for the second field: If it is set, the field must exist.
413  if (isset($pointerFieldArray[1]) && !isset($row[$pointerFieldArray[1]])) {
414  // If it's declared, it must exist -> not catchable
415  throw new \RuntimeException(
416  'TCA misconfiguration in table "' . $tableName . '" field "' . $fieldName . '" config section:'
417  . ' Second part "' . $pointerFieldArray[1] . '" of ds_pointerField with full value "'
418  . $tcaDataStructurePointerField . '" points to a field name that does not exist.',
419  1463578900
420  );
421  }
422  if ($pointerFieldsCount === 1) {
423  if (isset($fieldTca['config']['ds'][$row[$pointerFieldArray[0]]])) {
424  // Field value points directly to an existing key in tca ds
425  $dataStructureIdentifier['dataStructureKey'] = $row[$pointerFieldArray[0]];
426  } elseif (isset($fieldTca['config']['ds']['default'])) {
427  // Field value does not exit in tca ds, fall back to default key if exists
428  $dataStructureIdentifier['dataStructureKey'] = 'default';
429  } else {
430  // The value of the ds_pointerField field points to a key in the ds array that does
431  // not exist, and there is no fallback either. This can happen if an extension brings
432  // new flex form definitions and that extension is unloaded later. "Old" records of the
433  // extension could then still point to the no longer existing key in ds. We throw a
434  // specific exception here to give controllers an opportunity to catch this case.
436  'Field value of field "' . $pointerFieldArray[0] . '" of database record with uid "'
437  . $row['uid'] . '" from table "' . $tableName . '" points to a "ds" key ' . $row[$pointerFieldArray[0]]
438  . ' but this key does not exist and there is no "default" fallback.',
439  1463653197
440  );
441  }
442  } else {
443  // Two comma separated field names
444  if (isset($fieldTca['config']['ds'][$row[$pointerFieldArray[0]] . ',' . $row[$pointerFieldArray[1]]])) {
445  // firstValue,secondValue
446  $dataStructureIdentifier['dataStructureKey'] = $row[$pointerFieldArray[0]] . ',' . $row[$pointerFieldArray[1]];
447  } elseif (isset($fieldTca['config']['ds'][$row[$pointerFieldArray[0]] . ',*'])) {
448  // firstValue,*
449  $dataStructureIdentifier['dataStructureKey'] = $row[$pointerFieldArray[0]] . ',*';
450  } elseif (isset($fieldTca['config']['ds']['*,' . $row[$pointerFieldArray[1]]])) {
451  // *,secondValue
452  $dataStructureIdentifier['dataStructureKey'] = '*,' . $row[$pointerFieldArray[1]];
453  } elseif (isset($fieldTca['config']['ds'][$row[$pointerFieldArray[0]]])) {
454  // firstValue
455  $dataStructureIdentifier['dataStructureKey'] = $row[$pointerFieldArray[0]];
456  } elseif (isset($fieldTca['config']['ds']['default'])) {
457  // Fall back to default
458  $dataStructureIdentifier['dataStructureKey'] = 'default';
459  } else {
460  // No ds_pointerField value could be determined and 'default' does not exist as
461  // fallback. This is the same case as the above scenario, throw a
462  // InvalidCombinedPointerFieldException here, too.
464  'Field combination of fields "' . $pointerFieldArray[0] . '" and "' . $pointerFieldArray[1] . '" of database'
465  . 'record with uid "' . $row['uid'] . '" from table "' . $tableName . '" with values "' . $row[$pointerFieldArray[0]] . '"'
466  . ' and "' . $row[$pointerFieldArray[1]] . '" could not be resolved to any registered data structure and '
467  . ' no "default" fallback exists.',
468  1463678524
469  );
470  }
471  }
472  }
473  return $dataStructureIdentifier;
474  }
475 
476  protected function ‪convertDataStructureToArray(string|array $dataStructure): array
477  {
478  if (is_array($dataStructure)) {
479  return $dataStructure;
480  }
481  // Resolve FILE: prefix pointing to a DS in a file
482  if (str_starts_with(trim($dataStructure), 'FILE:')) {
483  $fileName = substr(trim($dataStructure), 5);
484  $file = GeneralUtility::getFileAbsFileName($fileName);
485  if (empty($file) || !is_file($file)) {
486  throw new \RuntimeException(
487  'Data structure file "' . $fileName . '" could not be resolved to an existing file',
488  1478105826
489  );
490  }
491  $dataStructure = (string)file_get_contents($file);
492  }
493  // Parse main structure
494  $dataStructure = ‪GeneralUtility::xml2array($dataStructure);
495  // Throw if it still is not an array, probably because GeneralUtility::xml2array() failed.
496  // This also may happen if artificial identifiers were constructed which don't resolve. The
497  // flex form "exclude" access rights systems does that -> catchable
498  if (!is_array($dataStructure)) {
500  'Parse error: Data structure could not be resolved to a valid structure.',
501  1478106090
502  );
503  }
504 
505  return $dataStructure;
506  }
507 
508  protected function ‪getDefaultStructureForIdentifier(array ‪$identifier): string
509  {
510  if ((‪$identifier['type'] ?? '') === 'tca') {
511  // Handle "tca" type, see getDataStructureIdentifierFromTcaArray
512  if (empty(‪$identifier['tableName']) || empty(‪$identifier['fieldName']) || empty(‪$identifier['dataStructureKey'])) {
513  throw new \RuntimeException(
514  'Incomplete "tca" based identifier: ' . json_encode(‪$identifier),
515  1478113471
516  );
517  }
518  $table = ‪$identifier['tableName'];
519  $field = ‪$identifier['fieldName'];
520  $dataStructureKey = ‪$identifier['dataStructureKey'];
521  if (!isset(‪$GLOBALS['TCA'][$table]['columns'][$field]['config']['ds'][$dataStructureKey])
522  || !is_string(‪$GLOBALS['TCA'][$table]['columns'][$field]['config']['ds'][$dataStructureKey])
523  ) {
524  // This may happen for elements pointing to an unloaded extension -> catchable
526  'Specified identifier ' . json_encode(‪$identifier) . ' does not resolve to a valid'
527  . ' TCA array value',
528  1478105491
529  );
530  }
531  $dataStructure = ‪$GLOBALS['TCA'][$table]['columns'][$field]['config']['ds'][$dataStructureKey];
532  } else {
534  'Identifier ' . json_encode(‪$identifier) . ' could not be resolved',
535  1478104554
536  );
537  }
538  return $dataStructure;
539  }
540 
544  protected function ‪ensureDefaultSheet(array $dataStructure): array
545  {
546  if (isset($dataStructure['ROOT']) && isset($dataStructure['sheets'])) {
547  throw new \RuntimeException(
548  'Parsed data structure has both ROOT and sheets on top level. That is invalid.',
549  1440676540
550  );
551  }
552  if (isset($dataStructure['ROOT']) && is_array($dataStructure['ROOT'])) {
553  $dataStructure['sheets']['sDEF']['ROOT'] = $dataStructure['ROOT'];
554  unset($dataStructure['ROOT']);
555  }
556  return $dataStructure;
557  }
558 
562  protected function ‪resolveFileDirectives(array $dataStructure): array
563  {
564  if (isset($dataStructure['sheets']) && is_array($dataStructure['sheets'])) {
565  foreach ($dataStructure['sheets'] as $sheetName => $sheetStructure) {
566  if (!is_array($sheetStructure)) {
567  if (str_starts_with(trim($sheetStructure), 'FILE:')) {
568  $file = GeneralUtility::getFileAbsFileName(substr(trim($sheetStructure), 5));
569  } else {
570  $file = GeneralUtility::getFileAbsFileName(trim($sheetStructure));
571  }
572  if ($file && @is_file($file)) {
573  $sheetStructure = ‪GeneralUtility::xml2array((string)file_get_contents($file));
574  }
575  }
576  $dataStructure['sheets'][$sheetName] = $sheetStructure;
577  }
578  }
579  return $dataStructure;
580  }
581 
585  protected function ‪migrateAndPrepareFlexTca(array $dataStructure): array
586  {
587  if (isset($dataStructure['sheets']) && is_array($dataStructure['sheets'])) {
588  foreach ($dataStructure['sheets'] as $sheetName => $sheetStructure) {
589  if (is_array($dataStructure['sheets'][$sheetName])) {
590  // @todo Use TcaPreparation instead of duplicating the code.
591  // @todo Actually, the type category preparation is different for FlexForm as is doesn't support manyToMany.
592  // @todo The difficulty for type file is the difference of the field name. For FlexForm it is not the column name of TCA, but the sub key.
593  $dataStructure['sheets'][$sheetName] = $this->‪migrateFlexFormTcaRecursive($dataStructure['sheets'][$sheetName]);
594  $dataStructure['sheets'][$sheetName] = $this->‪prepareCategoryFields($dataStructure['sheets'][$sheetName]);
595  $dataStructure['sheets'][$sheetName] = $this->‪prepareFileFields($dataStructure['sheets'][$sheetName]);
596  }
597  }
598  }
599  return $dataStructure;
600  }
601 
610  protected function ‪prepareCategoryFields(array $dataStructureSheets): array
611  {
612  if ($dataStructureSheets === []) {
613  // Early return in case the no sheets are given
614  return $dataStructureSheets;
615  }
616  foreach ($dataStructureSheets as &$structure) {
617  if (!is_array($structure['el'] ?? false) || $structure['el'] === []) {
618  // Skip if no elements (fields) are defined
619  continue;
620  }
621  foreach ($structure['el'] as $fieldName => &$fieldConfig) {
622  if (($fieldConfig['config']['type'] ?? '') !== 'category') {
623  // Skip if type is not "category"
624  continue;
625  }
626  // Add a default label if none is defined
627  if (!isset($fieldConfig['label'])) {
628  $fieldConfig['label'] = 'LLL:EXT:core/Resources/Private/Language/locallang_tca.xlf:sys_category.categories';
629  }
630  // Initialize default column configuration and merge it with already defined
631  $fieldConfig['config']['size'] ??= 20;
632  // Force foreign_table_* fields for type category
633  $fieldConfig['config']['foreign_table'] = 'sys_category';
634  $fieldConfig['config']['foreign_table_where'] = ' AND {#sys_category}.{#sys_language_uid} IN (-1, 0)';
635  if (empty($fieldConfig['config']['relationship'])) {
636  // Fall back to "oneToMany" when no relationship is given
637  $fieldConfig['config']['relationship'] = 'oneToMany';
638  }
639  if (!in_array($fieldConfig['config']['relationship'], ['oneToOne', 'oneToMany'], true)) {
640  throw new \UnexpectedValueException(
641  '"relationship" must be one of "oneToOne" or "oneToMany", "manyToMany" is not supported as "relationship"' .
642  ' for field ' . $fieldName . ' of type "category" in flexform.',
643  1627640208
644  );
645  }
646  // Set the maxitems value (necessary for DataHandling and FormEngine)
647  if ($fieldConfig['config']['relationship'] === 'oneToOne') {
648  // In case relationship is set to "oneToOne", maxitems must be 1.
649  if ((int)($fieldConfig['config']['maxitems'] ?? 0) > 1) {
650  throw new \UnexpectedValueException(
651  $fieldName . ' is defined as type category with an "oneToOne" relationship. ' .
652  'Therefore maxitems must be 1. Otherwise, use oneToMany as relationship instead.',
653  1627640209
654  );
655  }
656  $fieldConfig['config']['maxitems'] = 1;
657  } else {
658  // In case maxitems is not set or set to 0, set the default value "99999"
659  if (!($fieldConfig['config']['maxitems'] ?? false)) {
660  $fieldConfig['config']['maxitems'] = 99999;
661  } elseif ((int)($fieldConfig['config']['maxitems'] ?? 0) === 1) {
662  throw new \UnexpectedValueException(
663  'Can not use maxitems=1 for field ' . $fieldName . ' with "relationship" set to "oneToMany". Use "oneToOne" instead.',
664  1627640210
665  );
666  }
667  }
668  // Add the default value if not set
669  if (!isset($fieldConfig['config']['default'])
670  && $fieldConfig['config']['relationship'] !== 'oneToMany'
671  ) {
672  $fieldConfig['config']['default'] = 0;
673  }
674  }
675  }
676  return $dataStructureSheets;
677  }
678 
684  protected function ‪prepareFileFields(array $dataStructureSheets): array
685  {
686  if ($dataStructureSheets === []) {
687  // Early return in case the no sheets are given
688  return $dataStructureSheets;
689  }
690  foreach ($dataStructureSheets as &$structure) {
691  if (!is_array($structure['el'] ?? false) || $structure['el'] === []) {
692  // Skip if no elements (fields) are defined
693  continue;
694  }
695  foreach ($structure['el'] as $fieldName => &$fieldConfig) {
696  if (($fieldConfig['config']['type'] ?? '') !== 'file') {
697  // Skip if type is not "file"
698  continue;
699  }
700  $fieldConfig['config'] = array_replace_recursive(
701  $fieldConfig['config'],
702  [
703  'foreign_table' => 'sys_file_reference',
704  'foreign_field' => 'uid_foreign',
705  'foreign_sortby' => 'sorting_foreign',
706  'foreign_table_field' => 'tablenames',
707  'foreign_match_fields' => [
708  'fieldname' => $fieldName,
709  ],
710  'foreign_label' => 'uid_local',
711  'foreign_selector' => 'uid_local',
712  ]
713  );
714 
715  if (!empty(($allowed = ($fieldConfig['config']['allowed'] ?? null)))) {
716  $fieldConfig['config']['allowed'] = ‪TcaPreparation::prepareFileExtensions($allowed);
717  }
718  if (!empty(($disallowed = ($fieldConfig['config']['disallowed'] ?? null)))) {
719  $fieldConfig['config']['disallowed'] = ‪TcaPreparation::prepareFileExtensions($disallowed);
720  }
721  }
722  }
723  return $dataStructureSheets;
724  }
725 }
‪TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools\getDefaultStructureForIdentifier
‪getDefaultStructureForIdentifier(array $identifier)
Definition: FlexFormTools.php:508
‪TYPO3\CMS\Core\Configuration\FlexForm
‪TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools\prepareFileFields
‪array prepareFileFields(array $dataStructureSheets)
Definition: FlexFormTools.php:684
‪TYPO3\CMS\Core\Configuration\Tca\TcaPreparation
Definition: TcaPreparation.php:28
‪TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools\convertDataStructureToArray
‪convertDataStructureToArray(string|array $dataStructure)
Definition: FlexFormTools.php:476
‪TYPO3\CMS\Core\Configuration\FlexForm\Exception\InvalidCombinedPointerFieldException
Definition: InvalidCombinedPointerFieldException.php:21
‪TYPO3\CMS\Core\Configuration\FlexForm\Exception\InvalidSinglePointerFieldException
Definition: InvalidSinglePointerFieldException.php:21
‪TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools\__construct
‪__construct(private readonly EventDispatcherInterface $eventDispatcher,)
Definition: FlexFormTools.php:41
‪TYPO3\CMS\Core\Configuration\Event\BeforeFlexFormDataStructureParsedEvent
Definition: BeforeFlexFormDataStructureParsedEvent.php:35
‪TYPO3\CMS\Core\Configuration\Event\AfterFlexFormDataStructureParsedEvent
Definition: AfterFlexFormDataStructureParsedEvent.php:32
‪TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools\cleanFlexFormXML
‪cleanFlexFormXML(string $table, string $field, array $row)
Definition: FlexFormTools.php:159
‪TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools\flexArray2Xml
‪flexArray2Xml(array $array)
Definition: FlexFormTools.php:228
‪TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools\migrateAndPrepareFlexTca
‪migrateAndPrepareFlexTca(array $dataStructure)
Definition: FlexFormTools.php:585
‪TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools\ensureDefaultSheet
‪ensureDefaultSheet(array $dataStructure)
Definition: FlexFormTools.php:544
‪TYPO3\CMS\Core\Configuration\FlexForm\Exception\InvalidIdentifierException
Definition: InvalidIdentifierException.php:21
‪TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools\resolveFileDirectives
‪resolveFileDirectives(array $dataStructure)
Definition: FlexFormTools.php:562
‪TYPO3\CMS\Core\Configuration\Event\AfterFlexFormDataStructureIdentifierInitializedEvent
Definition: AfterFlexFormDataStructureIdentifierInitializedEvent.php:35
‪TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools
Definition: FlexFormTools.php:40
‪TYPO3\CMS\Core\Configuration\Event\BeforeFlexFormDataStructureIdentifierInitializedEvent
Definition: BeforeFlexFormDataStructureIdentifierInitializedEvent.php:44
‪TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools\getDataStructureIdentifierFromTcaArray
‪array getDataStructureIdentifierFromTcaArray(array $fieldTca, string $tableName, string $fieldName, array $row)
Definition: FlexFormTools.php:362
‪TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools\migrateFlexFormTcaRecursive
‪migrateFlexFormTcaRecursive(array $structure)
Definition: FlexFormTools.php:253
‪TYPO3\CMS\Core\Configuration\FlexForm\Exception\InvalidTcaException
Definition: InvalidTcaException.php:23
‪TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools\getDataStructureIdentifier
‪string getDataStructureIdentifier(array $fieldTca, string $tableName, string $fieldName, array $row)
Definition: FlexFormTools.php:81
‪TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools\getDefaultIdentifier
‪getDefaultIdentifier(array $fieldTca, string $tableName, string $fieldName, array $row)
Definition: FlexFormTools.php:300
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:25
‪TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools\prepareCategoryFields
‪array prepareCategoryFields(array $dataStructureSheets)
Definition: FlexFormTools.php:610
‪TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools\parseDataStructureByIdentifier
‪array parseDataStructureByIdentifier(string $identifier)
Definition: FlexFormTools.php:120
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:52
‪TYPO3\CMS\Core\Configuration\Tca\TcaPreparation\prepareFileExtensions
‪static prepareFileExtensions(mixed $fileExtensions)
Definition: TcaPreparation.php:263
‪TYPO3\CMS\Core\Utility\GeneralUtility\xml2array
‪static array string xml2array(string $string, string $NSprefix='', bool $reportDocTag=false)
Definition: GeneralUtility.php:1265
‪TYPO3\CMS\Core\Utility\GeneralUtility\trimExplode
‪static list< string > trimExplode(string $delim, string $string, bool $removeEmptyValues=false, int $limit=0)
Definition: GeneralUtility.php:822
‪TYPO3\CMS\Webhooks\Message\$identifier
‪identifier readonly string $identifier
Definition: FileAddedMessage.php:37
‪TYPO3\CMS\Core\Configuration\Tca\TcaMigration
Definition: TcaMigration.php:31