setDefaultsFromUserTsConfig($result); $result = $this->setDefaultsFromPageTsConfig($result); $result = $this->setDefaultsFromNeighborRow($result); $result = $this->setDefaultsFromDefaultValues($result); $result = $this->setDefaultsFromInlineRelations($result); $result = $this->setDefaultsFromInlineParentLanguage($result); $result = $this->setDefaultsFromInlineParentUid($result); $result = $this->setPid($result); return $result; } /** * Set defaults defined by user ts "TCAdefaults" * Supports both field-level defaults and type-specific defaults * TCAdefaults.tt_content.header_layout = 1 (field-level) * TCAdefaults.tt_content.header_layout.types.textmedia = 3 (type-specific) * * @param array $result Result array * @return array Modified result array */ protected function setDefaultsFromUserTsConfig(array $result) { $tableNameWithDot = $result['tableName'] . '.'; // Apply default values from user typo script "TCAdefaults" if any if (isset($result['userTsConfig']['TCAdefaults.'][$tableNameWithDot]) && is_array($result['userTsConfig']['TCAdefaults.'][$tableNameWithDot]) ) { $recordType = $this->getRecordTypeFromRow($result); $mergedDefaults = $this->mergeTypeSpecificTcaDefaults( $result['userTsConfig']['TCAdefaults.'][$tableNameWithDot], $recordType ); foreach ($mergedDefaults as $fieldName => $fieldValue) { if (isset($result['processedTca']['columns'][$fieldName])) { $result['databaseRow'][$fieldName] = $fieldValue; } } } return $result; } /** * Set defaults defined by page ts "TCAdefaults" * Supports both field-level defaults and type-specific defaults * TCAdefaults.tt_content.header_layout = 1 (field-level) * TCAdefaults.tt_content.header_layout.types.textmedia = 3 (type-specific) * * @param array $result Result array * @return array Modified result array */ protected function setDefaultsFromPageTsConfig(array $result) { $tableNameWithDot = $result['tableName'] . '.'; if (isset($result['pageTsConfig']['TCAdefaults.'][$tableNameWithDot]) && is_array($result['pageTsConfig']['TCAdefaults.'][$tableNameWithDot]) ) { $recordType = $this->getRecordTypeFromRow($result); $mergedDefaults = $this->mergeTypeSpecificTcaDefaults( $result['pageTsConfig']['TCAdefaults.'][$tableNameWithDot], $recordType ); foreach ($mergedDefaults as $fieldName => $fieldValue) { if (isset($result['processedTca']['columns'][$fieldName])) { $result['databaseRow'][$fieldName] = $fieldValue; } } } return $result; } /** * If a neighbor row is given (if vanillaUid was negative), field can be initialized with values * from neighbor for fields registered in TCA['ctrl']['useColumnsForDefaultValues']. * * @param array $result Result array * @return array Modified result array */ protected function setDefaultsFromNeighborRow(array $result) { if (is_array($result['neighborRow']) && !empty($result['processedTca']['ctrl']['useColumnsForDefaultValues']) ) { $defaultColumns = GeneralUtility::trimExplode(',', $result['processedTca']['ctrl']['useColumnsForDefaultValues'], true); foreach ($defaultColumns as $fieldName) { if (isset($result['processedTca']['columns'][$fieldName]) && isset($result['neighborRow'][$fieldName]) ) { $result['databaseRow'][$fieldName] = $result['neighborRow'][$fieldName]; } } } return $result; } /** * Apply default values. * These are typically carried around as "defVals" GET vars and set by controllers * in $result['defaultValues'] array as init values. * * @param array $result Result array * @return array Modified result array */ protected function setDefaultsFromDefaultValues(array $result) { $tableName = $result['tableName']; $defaultValues = $result['defaultValues'] ?? []; if (isset($defaultValues[$tableName]) && is_array($defaultValues[$tableName])) { foreach ($defaultValues[$tableName] as $fieldName => $fieldValue) { if (isset($result['processedTca']['columns'][$fieldName])) { $result['databaseRow'][$fieldName] = $fieldValue; } } } return $result; } /** * Inline scenario if a new intermediate record to an existing child-child is * compiled. Set "foreign_selector" field of this intermediate row to given * "childChildUid". See TcaDataCompiler array comment of inlineChildChildUid * for more details. * * @param array $result Result array * @return array Modified result array * @throws \UnexpectedValueException */ protected function setDefaultsFromInlineRelations(array $result) { if ($result['inlineChildChildUid'] === null) { return $result; } if (!is_int($result['inlineChildChildUid'])) { throw new \UnexpectedValueException( 'An inlineChildChildUid is given for table ' . $result['tableName'] . ', but is not an integer', 1444434103 ); } if (!isset($result['inlineParentConfig']['foreign_selector'])) { throw new \UnexpectedValueException( 'An inlineChildChildUid is given for table ' . $result['tableName'] . ', but no foreign_selector in inlineParentConfig', 1444434102 ); } $selectorFieldName = $result['inlineParentConfig']['foreign_selector']; $fieldType = (string)($result['processedTca']['columns'][$selectorFieldName]['config']['type'] ?? ''); if (!in_array($fieldType, ['select', 'category', 'group'], true)) { throw new \UnexpectedValueException( $selectorFieldName . ' is target type of a foreign_selector field to table ' . $result['tableName'] . ' and must be either a select, category or group type field', 1444434104 ); } if ($result['inlineChildChildUid']) { $result['databaseRow'][$selectorFieldName] = $result['inlineChildChildUid']; } return $result; } /** * If a new child is created in an inline relation via ajax, and if the parent is a localized record, * the child should have the same sys_language_uid set in the field declared in ['ctrl']['languageField'] * if the child is localizable itself. * A localized parent transfers its sys_language_uid via inlineParentConfig['inline']['parentSysLanguageUid'], * use that value as default for the child record languageField. * * @param array $result Result array * @return array Modified result array * @throws \UnexpectedValueException */ protected function setDefaultsFromInlineParentLanguage(array $result): array { if (!isset($result['inlineParentConfig']['inline']['parentSysLanguageUid']) || empty($result['processedTca']['ctrl']['languageField']) || empty($result['processedTca']['ctrl']['transOrigPointerField']) ) { return $result; } if (!MathUtility::canBeInterpretedAsInteger($result['inlineParentConfig']['inline']['parentSysLanguageUid'])) { throw new \UnexpectedValueException( 'A sys_language_uid is set from inline parent config but the value is no integer', 1490360772 ); } $parentSysLanguageUid = (int)$result['inlineParentConfig']['inline']['parentSysLanguageUid']; $languageFieldName = $result['processedTca']['ctrl']['languageField']; $result['databaseRow'][$languageFieldName] = $parentSysLanguageUid; return $result; } /** * Set the parent uid of inline relations created via ajax to the corresponding foreign field * * @param array $result Result array */ protected function setDefaultsFromInlineParentUid(array $result): array { $isInlineChild = $result['isInlineChild'] ?? false; $parentField = $result['inlineParentConfig']['foreign_field'] ?? false; if ($isInlineChild && $parentField && !empty($result['inlineParentUid'])) { $result['databaseRow'][$parentField] = $result['inlineParentUid']; } return $result; } /** * Set the pid. This is either the vanillaUid (see description in FormDataCompiler), * or a pid given by page TSconfig for inline children. * * @param array $result Result array * @return array Modified result array * @throws \UnexpectedValueException */ protected function setPid(array $result) { // Set pid to vanillaUid. This can be a negative value // if the record is added relative to another record. $result['databaseRow']['pid'] = $result['vanillaUid']; // In case a new inline record is created, the pid can be set to a different value // by page TSconfig, but not by user TSconfig. This overrides the above pid selection // and forces the pid of new inline children. $tableNameWithDot = $result['tableName'] . '.'; if ($result['isInlineChild'] && isset($result['pageTsConfig']['TCAdefaults.'][$tableNameWithDot]['pid'])) { if (!MathUtility::canBeInterpretedAsInteger($result['pageTsConfig']['TCAdefaults.'][$tableNameWithDot]['pid'])) { throw new \UnexpectedValueException( 'page TSconfig setting TCAdefaults.' . $tableNameWithDot . 'pid must be a number, but given string ' . $result['pageTsConfig']['TCAdefaults.'][$tableNameWithDot]['pid'] . ' can not be interpreted as integer', 1461598332 ); } $result['databaseRow']['pid'] = (int)$result['pageTsConfig']['TCAdefaults.'][$tableNameWithDot]['pid']; } return $result; } /** * Determine the record type from the current database row * and additionally fall back to defaultValues as this is * basically the only source we have for new records at this point. * * @param array $result The form data result array * @return string The record type value */ protected function getRecordTypeFromRow(array $result): string { // If recordTypeValue is already set, use it if (is_string($result['recordTypeValue'] ?? false) && $result['recordTypeValue'] !== '') { return $result['recordTypeValue']; } $recordTypeValue = '0'; // Check if there's a type field defined in TCA if (is_string($result['processedTca']['ctrl']['type'] ?? false) && $result['processedTca']['ctrl']['type'] !== '') { $tcaTypeField = $result['processedTca']['ctrl']['type']; // Handle simple type field (not foreign field reference) if (!str_contains($tcaTypeField, ':')) { // First check if the type field exists in the database row and has a value if (array_key_exists($tcaTypeField, $result['databaseRow']) && $result['databaseRow'][$tcaTypeField] !== null) { $recordTypeValue = (string)$result['databaseRow'][$tcaTypeField]; } else { // Check if the type field is set in defaultValues $tableName = $result['tableName']; if (isset($result['defaultValues'][$tableName][$tcaTypeField])) { $recordTypeValue = (string)$result['defaultValues'][$tableName][$tcaTypeField]; } } } } // Apply the same fallback logic as DatabaseRecordTypeValue: // Check the determined value actually exists as types key, otherwise fall back to 0 or 1 if (empty($result['processedTca']['types'][$recordTypeValue])) { $recordTypeValue = !empty($result['processedTca']['types']['0']) ? '0' : '1'; } return $recordTypeValue; } /** * Merge type-specific TCAdefaults over field-level defaults * Similar to how TCEFORM type-specific settings work * * @param array $tcaDefaults The TCAdefaults configuration for the table * @param string $recordType The current record type value * @return array Merged defaults with type-specific values taking precedence */ protected function mergeTypeSpecificTcaDefaults(array $tcaDefaults, string $recordType): array { $mergedDefaults = []; foreach ($tcaDefaults as $fieldKey => $fieldConfiguration) { if (str_ends_with($fieldKey, '.')) { // This is a field with sub-configuration (potentially types) $fieldName = rtrim($fieldKey, '.'); if (!is_array($fieldConfiguration)) { continue; } $fieldDefault = $fieldConfiguration; // Check if there are type-specific overrides if (!empty($fieldConfiguration['types.']) && is_array($fieldConfiguration['types.'])) { $typeSpecificConfiguration = $fieldConfiguration['types.']; unset($fieldDefault['types.']); // If we have a matching type-specific configuration, merge it if (!empty($typeSpecificConfiguration[$recordType . '.']) && is_array($typeSpecificConfiguration[$recordType . '.'])) { ArrayUtility::mergeRecursiveWithOverrule($fieldDefault, $typeSpecificConfiguration[$recordType . '.']); } elseif (!empty($typeSpecificConfiguration[$recordType])) { // Simple value (not array) $mergedDefaults[$fieldName] = $typeSpecificConfiguration[$recordType]; continue; } } // If the field configuration is now empty (only had types.), skip it // Otherwise, use the first non-array value or the whole configuration foreach ($fieldDefault as $key => $value) { if (!str_ends_with($key, '.') && !is_array($value)) { $mergedDefaults[$fieldName] = $value; break; } } } else { // Simple field-level default $mergedDefaults[$fieldKey] = $fieldConfiguration; } } return $mergedDefaults; } }