2 declare(strict_types = 1);
53 'sheet' =>
'language',
54 'language' =>
'field',
58 'el:_IS_NUM' =>
'section',
59 'section' =>
'itemType'
61 'disableTypeAttrib' => 2
118 $dataStructureIdentifier =
null;
131 if (!empty(
$GLOBALS[
'TYPO3_CONF_VARS'][
'SC_OPTIONS'][self::class][
'flexParsing'])
132 && is_array(
$GLOBALS[
'TYPO3_CONF_VARS'][
'SC_OPTIONS'][self::class][
'flexParsing'])) {
133 $hookClasses =
$GLOBALS[
'TYPO3_CONF_VARS'][
'SC_OPTIONS'][self::class][
'flexParsing'];
134 foreach ($hookClasses as $hookClass) {
135 $hookInstance = GeneralUtility::makeInstance($hookClass);
136 if (method_exists($hookClass,
'getDataStructureIdentifierPreProcess')) {
137 $dataStructureIdentifier = $hookInstance->getDataStructureIdentifierPreProcess(
143 if (!is_array($dataStructureIdentifier)) {
144 throw new \RuntimeException(
145 'Hook class ' . $hookClass .
' method getDataStructureIdentifierPreProcess must return an array',
149 if (!empty($dataStructureIdentifier)) {
158 if (empty($dataStructureIdentifier)) {
159 $tcaDataStructureArray = $fieldTca[
'config'][
'ds'] ??
null;
160 $tcaDataStructurePointerField = $fieldTca[
'config'][
'ds_pointerField'] ??
null;
161 if (!is_array($tcaDataStructureArray) && $tcaDataStructurePointerField) {
169 } elseif (is_array($tcaDataStructureArray)) {
177 throw new \RuntimeException(
178 'TCA misconfiguration in table "' . $tableName .
'" field "' . $fieldName .
'" config section:'
179 .
' The field is configured as type="flex" and no "ds_pointerField" is defined and "ds" is not an array.'
180 .
' Either configure a default data structure in [\'ds\'][\'default\'] or add a "ds_pointerField" lookup mechanism'
181 .
' that specifies the data structure',
193 if (!empty(
$GLOBALS[
'TYPO3_CONF_VARS'][
'SC_OPTIONS'][self::class][
'flexParsing'])
194 && is_array(
$GLOBALS[
'TYPO3_CONF_VARS'][
'SC_OPTIONS'][self::class][
'flexParsing'])) {
195 $hookClasses =
$GLOBALS[
'TYPO3_CONF_VARS'][
'SC_OPTIONS'][self::class][
'flexParsing'];
196 foreach ($hookClasses as $hookClass) {
197 $hookInstance = GeneralUtility::makeInstance($hookClass);
198 if (method_exists($hookClass,
'getDataStructureIdentifierPostProcess')) {
199 $dataStructureIdentifier = $hookInstance->getDataStructureIdentifierPostProcess(
204 $dataStructureIdentifier
206 if (!is_array($dataStructureIdentifier) || empty($dataStructureIdentifier)) {
207 throw new \RuntimeException(
208 'Hook class ' . $hookClass .
' method getDataStructureIdentifierPostProcess must return a non empty array',
216 return json_encode($dataStructureIdentifier);
270 $pointerFieldName = $finalPointerFieldName = $fieldTca[
'config'][
'ds_pointerField'];
271 if (!array_key_exists($pointerFieldName, $row)) {
274 'No data structure for field "' . $fieldName .
'" in table "' . $tableName .
'" found, no "ds" array'
275 .
' configured and given row does not have a field with ds_pointerField name "' . $pointerFieldName .
'".',
279 $pointerValue = $row[$pointerFieldName];
281 $parentFieldName = $fieldTca[
'config'][
'ds_pointerField_searchParent'] ??
null;
282 $pointerSubFieldName = $fieldTca[
'config'][
'ds_pointerField_searchParent_subField'] ??
null;
283 if (!$pointerValue && $parentFieldName) {
286 while (!$pointerValue) {
287 $handledUids[$row[
'uid']] = 1;
288 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($tableName);
289 $queryBuilder->getRestrictions()
291 ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
292 $queryBuilder->select(
'uid', $parentFieldName, $pointerFieldName);
293 if (!empty($pointerSubFieldName)) {
294 $queryBuilder->addSelect($pointerSubFieldName);
296 $queryStatement = $queryBuilder->from($tableName)
298 $queryBuilder->expr()->eq(
300 $queryBuilder->createNamedParameter($row[$parentFieldName], \PDO::PARAM_INT)
304 $rowCount = $queryBuilder
308 if ($rowCount !== 1) {
310 'The data structure for field "' . $fieldName .
'" in table "' . $tableName .
'" has to be looked up'
311 .
' in field "' . $pointerFieldName .
'". That field had no valid value, so a lookup in parent record'
312 .
' with uid "' . $row[$parentFieldName] .
'" was done. This row however does not exist or was deleted.',
316 $row = $queryStatement->fetch();
317 if (isset($handledUids[$row[$parentFieldName]])) {
320 'The data structure for field "' . $fieldName .
'" in table "' . $tableName .
'" has to be looked up'
321 .
' in field "' . $pointerFieldName .
'". That field had no valid value, so a lookup in parent record'
322 .
' with uid "' . $row[$parentFieldName] .
'" was done. A loop of records was detected, the tree is broken.',
330 if ($pointerSubFieldName && $row[$pointerSubFieldName]) {
331 $finalPointerFieldName = $pointerSubFieldName;
332 $pointerValue = $row[$pointerSubFieldName];
334 $pointerValue = $row[$pointerFieldName];
336 if (!$pointerValue && ((
int)$row[$parentFieldName] === 0 || $row[$parentFieldName] ===
null)) {
339 'The data structure for field "' . $fieldName .
'" in table "' . $tableName .
'" has to be looked up'
340 .
' in field "' . $pointerFieldName .
'". That field had no valid value, so a lookup in parent record'
341 .
' with uid "' . $row[$parentFieldName] .
'" was done. Root node with uid "' . $row[
'uid'] .
'"'
342 .
' was fetched and still no valid pointer field value was found.',
348 if (!$pointerValue) {
351 'No data structure for field "' . $fieldName .
'" in table "' . $tableName .
'" found, no "ds" array'
352 .
' configured and data structure could be found by resolving parents. This is probably a TCA misconfiguration.',
360 if (!isset($fieldTca[
'config'][
'ds_tableField'])) {
362 'Invalid data structure pointer for field "' . $fieldName .
'" in table "' . $tableName .
'", the value'
363 .
'resolved to "' . $pointerValue .
'" . which is an integer, so "ds_tableField" must be configured',
367 if (substr_count($fieldTca[
'config'][
'ds_tableField'],
':') !== 1) {
370 'Invalid TCA configuration for field "' . $fieldName .
'" in table "' . $tableName .
'", the setting'
371 .
'"ds_tableField" must be of the form "tableName:fieldName"',
375 list($foreignTableName, $foreignFieldName) = GeneralUtility::trimExplode(
':', $fieldTca[
'config'][
'ds_tableField']);
376 $dataStructureIdentifier = [
378 'tableName' => $foreignTableName,
379 'uid' => (int)$pointerValue,
380 'fieldName' => $foreignFieldName,
383 $dataStructureIdentifier = [
385 'tableName' => $tableName,
386 'uid' => (int)$row[
'uid'],
387 'fieldName' => $finalPointerFieldName,
390 return $dataStructureIdentifier;
436 $dataStructureIdentifier = [
438 'tableName' => $tableName,
439 'fieldName' => $fieldName,
440 'dataStructureKey' =>
null,
442 $tcaDataStructurePointerField = $fieldTca[
'config'][
'ds_pointerField'] ??
null;
443 if ($tcaDataStructurePointerField ===
null) {
445 if (isset($fieldTca[
'config'][
'ds'][
'default'])) {
446 $dataStructureIdentifier[
'dataStructureKey'] =
'default';
452 'TCA misconfiguration in table "' . $tableName .
'" field "' . $fieldName .
'" config section:'
453 .
' The field is configured as type="flex" and no "ds_pointerField" is defined. Either configure'
454 .
' a default data structure in [\'ds\'][\'default\'] or add a "ds_pointerField" lookup mechanism'
455 .
' that specifies the data structure',
461 $pointerFieldArray = GeneralUtility::trimExplode(
',', $tcaDataStructurePointerField,
true);
463 $pointerFieldsCount = count($pointerFieldArray);
464 if ($pointerFieldsCount !== 1 && $pointerFieldsCount !== 2) {
466 throw new \RuntimeException(
467 'TCA misconfiguration in table "' . $tableName .
'" field "' . $fieldName .
'" config section:'
468 .
' ds_pointerField must be either a single field name, or a comma separated list of two fields,'
469 .
' the invalid configuration string provided was: "' . $tcaDataStructurePointerField .
'"',
476 if (!isset($row[$pointerFieldArray[0]])) {
478 throw new \RuntimeException(
479 'TCA misconfiguration in table "' . $tableName .
'" field "' . $fieldName .
'" config section:'
480 .
' ds_pointerField "' . $pointerFieldArray[0] .
'" points to a field name that does not exist.',
485 if (isset($pointerFieldArray[1]) && !isset($row[$pointerFieldArray[1]])) {
487 throw new \RuntimeException(
488 'TCA misconfiguration in table "' . $tableName .
'" field "' . $fieldName .
'" config section:'
489 .
' Second part "' . $pointerFieldArray[1] .
'" of ds_pointerField with full value "'
490 . $tcaDataStructurePointerField .
'" points to a field name that does not exist.',
494 if ($pointerFieldsCount === 1) {
495 if (isset($fieldTca[
'config'][
'ds'][$row[$pointerFieldArray[0]]])) {
497 $dataStructureIdentifier[
'dataStructureKey'] = $row[$pointerFieldArray[0]];
498 } elseif (isset($fieldTca[
'config'][
'ds'][
'default'])) {
500 $dataStructureIdentifier[
'dataStructureKey'] =
'default';
508 'Field value of field "' . $pointerFieldArray[0] .
'" of database record with uid "'
509 . $row[
'uid'] .
'" from table "' . $tableName .
'" points to a "ds" key ' . $row[$pointerFieldArray[0]]
510 .
' but this key does not exist and there is no "default" fallback.',
516 if (isset($fieldTca[
'config'][
'ds'][$row[$pointerFieldArray[0]] .
',' . $row[$pointerFieldArray[1]]])) {
518 $dataStructureIdentifier[
'dataStructureKey'] = $row[$pointerFieldArray[0]] .
',' . $row[$pointerFieldArray[1]];
519 } elseif (isset($fieldTca[
'config'][
'ds'][$row[$pointerFieldArray[0]] .
',*'])) {
521 $dataStructureIdentifier[
'dataStructureKey'] = $row[$pointerFieldArray[0]] .
',*';
522 } elseif (isset($fieldTca[
'config'][
'ds'][
'*,' . $row[$pointerFieldArray[1]]])) {
524 $dataStructureIdentifier[
'dataStructureKey'] =
'*,' . $row[$pointerFieldArray[1]];
525 } elseif (isset($fieldTca[
'config'][
'ds'][$row[$pointerFieldArray[0]]])) {
527 $dataStructureIdentifier[
'dataStructureKey'] = $row[$pointerFieldArray[0]];
528 } elseif (isset($fieldTca[
'config'][
'ds'][
'default'])) {
530 $dataStructureIdentifier[
'dataStructureKey'] =
'default';
536 'Field combination of fields "' . $pointerFieldArray[0] .
'" and "' . $pointerFieldArray[1] .
'" of database'
537 .
'record with uid "' . $row[
'uid'] .
'" from table "' . $tableName .
'" with values "' . $row[$pointerFieldArray[0]] .
'"'
538 .
' and "' . $row[$pointerFieldArray[1]] .
'" could not be resolved to any registered data structure and '
539 .
' no "default" fallback exists.',
545 return $dataStructureIdentifier;
581 if (empty($identifier)) {
583 'Empty string given to parseFlexFormDataStructureByIdentifier(). This exception might '
584 .
' be caught to handle some new record situations properly',
589 $identifier = json_decode($identifier,
true);
591 if (!is_array($identifier) || empty($identifier)) {
593 throw new \RuntimeException(
594 'Identifier could not be decoded to an array.',
608 foreach (
$GLOBALS[
'TYPO3_CONF_VARS'][
'SC_OPTIONS'][self::class][
'flexParsing'] ?? [] as $hookClass) {
609 $hookInstance = GeneralUtility::makeInstance($hookClass);
610 if (method_exists($hookClass,
'parseDataStructureByIdentifierPreProcess')) {
611 $dataStructure = $hookInstance->parseDataStructureByIdentifierPreProcess($identifier);
612 if (!is_string($dataStructure) && !is_array($dataStructure)) {
614 throw new \RuntimeException(
615 'Hook class ' . $hookClass .
' method parseDataStructureByIdentifierPreProcess must either'
616 .
' return an empty string or a data structure string or a parsed data structure array.',
620 if (!empty($dataStructure)) {
628 if (empty($dataStructure)) {
629 if ($identifier[
'type'] ===
'record') {
631 if (empty($identifier[
'tableName']) || empty($identifier[
'uid']) || empty($identifier[
'fieldName'])) {
632 throw new \RuntimeException(
633 'Incomplete "record" based identifier: ' . json_encode($identifier),
637 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($identifier[
'tableName']);
638 $queryBuilder->getRestrictions()->removeAll()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
639 $dataStructure = $queryBuilder
640 ->select($identifier[
'fieldName'])
641 ->from($identifier[
'tableName'])
643 $queryBuilder->expr()->eq(
645 $queryBuilder->createNamedParameter($identifier[
'uid'], \PDO::PARAM_INT)
650 } elseif ($identifier[
'type'] ===
'tca') {
652 if (empty($identifier[
'tableName']) || empty($identifier[
'fieldName']) || empty($identifier[
'dataStructureKey'])) {
653 throw new \RuntimeException(
654 'Incomplete "tca" based identifier: ' . json_encode($identifier),
658 $table = $identifier[
'tableName'];
659 $field = $identifier[
'fieldName'];
660 $dataStructureKey = $identifier[
'dataStructureKey'];
661 if (!isset(
$GLOBALS[
'TCA'][$table][
'columns'][$field][
'config'][
'ds'][$dataStructureKey])
662 || !is_string(
$GLOBALS[
'TCA'][$table][
'columns'][$field][
'config'][
'ds'][$dataStructureKey])
666 'Specified identifier ' . json_encode($identifier) .
' does not resolve to a valid'
667 .
' TCA array value',
671 $dataStructure =
$GLOBALS[
'TCA'][$table][
'columns'][$field][
'config'][
'ds'][$dataStructureKey];
674 'Identifier ' . json_encode($identifier) .
' could not be resolved',
681 if (is_string($dataStructure)) {
683 if (strpos(trim($dataStructure),
'FILE:') === 0) {
684 $file = GeneralUtility::getFileAbsFileName(substr(trim($dataStructure), 5));
685 if (empty($file) || !@is_file($file)) {
686 throw new \RuntimeException(
687 'Data structure file ' . $file .
' could not be resolved to an existing file',
691 $dataStructure = file_get_contents($file);
695 $dataStructure = GeneralUtility::xml2array($dataStructure);
701 if (!is_array($dataStructure)) {
703 'Parse error: Data structure could not be resolved to a valid structure.',
709 if (isset($dataStructure[
'ROOT']) && isset($dataStructure[
'sheets'])) {
710 throw new \RuntimeException(
711 'Parsed data structure has both ROOT and sheets on top level. Thats invalid.',
715 if (isset($dataStructure[
'ROOT']) && is_array($dataStructure[
'ROOT'])) {
716 $dataStructure[
'sheets'][
'sDEF'][
'ROOT'] = $dataStructure[
'ROOT'];
717 unset($dataStructure[
'ROOT']);
721 if (isset($dataStructure[
'sheets']) && is_array($dataStructure[
'sheets'])) {
722 foreach ($dataStructure[
'sheets'] as $sheetName => $sheetStructure) {
723 if (!is_array($sheetStructure)) {
724 if (strpos(trim($sheetStructure),
'FILE:') === 0) {
725 $file = GeneralUtility::getFileAbsFileName(substr(trim($sheetStructure), 5));
727 $file = GeneralUtility::getFileAbsFileName(trim($sheetStructure));
729 if ($file && @is_file($file)) {
730 $sheetStructure = GeneralUtility::xml2array(file_get_contents($file));
733 $dataStructure[
'sheets'][$sheetName] = $sheetStructure;
740 foreach (
$GLOBALS[
'TYPO3_CONF_VARS'][
'SC_OPTIONS'][self::class][
'flexParsing'] ?? [] as $hookClass) {
741 $hookInstance = GeneralUtility::makeInstance($hookClass);
742 if (method_exists($hookClass,
'parseDataStructureByIdentifierPostProcess')) {
743 $dataStructure = $hookInstance->parseDataStructureByIdentifierPostProcess($dataStructure, $identifier);
744 if (!is_array($dataStructure)) {
746 throw new \RuntimeException(
747 'Hook class ' . $hookClass .
' method parseDataStructureByIdentifierPreProcess must return and array.',
754 return $dataStructure;
769 if (!is_array(
$GLOBALS[
'TCA'][$table]) || !is_array(
$GLOBALS[
'TCA'][$table][
'columns'][$field])) {
770 return 'TCA table/field was not defined.';
777 $dataStructureArray = [
'sheets' => [
'sDEF' => []]];
789 $editData = GeneralUtility::xml2array($row[$field]);
790 if (!is_array($editData)) {
791 return 'Parsing error: ' . $editData;
794 if (!is_array($dataStructureArray[
'sheets'])) {
795 return 'Data Structure ERROR: sheets is defined but not an array for table ' . $table . (isset($row[
'uid']) ?
' and uid ' . $row[
'uid'] :
'');
798 foreach ($dataStructureArray[
'sheets'] as $sheetKey => $sheetData) {
800 if (is_array($sheetData[
'ROOT']) && is_array($sheetData[
'ROOT'][
'el'])) {
801 $PA[
'vKeys'] = [
'DEF'];
802 $PA[
'lKey'] =
'lDEF';
803 $PA[
'callBackMethod_value'] = $callBackMethod_value;
804 $PA[
'table'] = $table;
805 $PA[
'field'] = $field;
806 $PA[
'uid'] = $row[
'uid'];
810 return 'Data Structure ERROR: No ROOT element found for sheet "' . $sheetKey .
'".';
826 if (is_array($dataStruct)) {
827 foreach ($dataStruct as $key => $value) {
828 if (isset($value[
'type']) && $value[
'type'] ===
'array') {
830 if ($value[
'section']) {
832 if (isset($editData[$key][
'el']) && is_array($editData[$key][
'el'])) {
833 if ($this->reNumberIndexesOfSectionData) {
836 foreach ($editData[$key][
'el'] as $v3) {
839 $editData[$key][
'el'] = $temp;
841 foreach ($editData[$key][
'el'] as $k3 => $v3) {
845 $theDat = $v3[$theType];
846 $newSectionEl = $value[
'el'][$theType];
847 if (is_array($newSectionEl)) {
855 if (isset($editData[$key][
'el'])) {
859 } elseif (isset($value[
'TCEforms'][
'config']) && is_array($value[
'TCEforms'][
'config'])) {
861 foreach ($PA[
'vKeys'] as $vKey) {
864 if (!empty($PA[
'callBackMethod_value']) && isset($editData[$key][$vKey])) {
867 $editData[$key][$vKey],
869 $path .
'/' . $key .
'/' . $vKey,
888 return call_user_func_array([$this->callBackObj, $methodName], $parameterArray);
910 $flexObj = GeneralUtility::makeInstance(\
TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools::class);
911 $flexObj->reNumberIndexesOfSectionData =
true;
912 $flexObj->traverseFlexFormXMLData($table, $field, $row, $this,
'cleanFlexFormXML_callBackFunction');
946 if (!is_array($pathArray)) {
947 $pathArray = explode(
'/', $pathArray);
949 if (is_array($array) && !empty($pathArray)) {
950 $key = array_shift($pathArray);
951 if (isset($array[$key])) {
952 if (empty($pathArray)) {
972 if (!is_array($pathArray)) {
973 $pathArray = explode(
'/', $pathArray);
975 if (is_array($array) && !empty($pathArray)) {
976 $key = array_shift($pathArray);
977 if (empty($pathArray)) {
978 $array[$key] = $value;
981 if (!isset($array[$key])) {
998 if (
$GLOBALS[
'TYPO3_CONF_VARS'][
'BE'][
'flexformForceCDATA']) {
999 $this->flexArray2Xml_options[
'useCDATA'] = 1;
1001 $output = GeneralUtility::array2xml($array,
'', 0,
'T3FlexForms', 4, $this->flexArray2Xml_options);
1003 $output =
'<?xml version="1.0" encoding="utf-8" standalone="yes" ?>' . LF .
$output;