TYPO3 v15 dev-main snapshot ()

This commit is contained in:
2026-08-10 22:31:09 +02:00
commit af8cc155b5
6818 changed files with 642608 additions and 0 deletions
+239
View File
@@ -0,0 +1,239 @@
<?php
declare(strict_types=1);
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
namespace TYPO3\CMS\Core\Schema;
use TYPO3\CMS\Core\Schema\Exception\FieldTypeNotAvailableException;
use TYPO3\CMS\Core\Schema\Exception\UndefinedFieldException;
use TYPO3\CMS\Core\Schema\Field\FieldCollection;
use TYPO3\CMS\Core\Schema\Field\FieldTypeInterface;
use TYPO3\CMS\Core\Schema\Struct\WizardStep;
use TYPO3\CMS\Core\Service\DependencyOrderingService;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* This class builds a TCA schema for a given TCA
* This is done the following way:
*
* As the relations need to be fully resolved first (done in RelationMapBuilder),
* the TcaSchemaFactory does two-step processing:
* 1a. Traverse TCA (and, if type=flex parts are registered), and find relations of all TCA parts pointing to each other
* 1b. Store this in a RelationMap object as a multi-level array.
* ---
* 2. Loop through all TCA tables one by one
* 2a. Build field objects for the TCA table.
* 2b. Detect "sub schemata" (if [ctrl][type] is set), build the field objects only relevant for the sub-schema
* 2c. Build the sub-schema
* 2d. Build the main schema
*
* @internal Not part of TYPO3's API.
*/
final readonly class TcaSchemaBuilder
{
public function __construct(
private RelationMapBuilder $relationMapBuilder,
private FieldTypeFactory $fieldTypeFactory,
) {}
public function buildFromStructure(array $fullTca): SchemaCollection
{
$schemata = [];
ksort($fullTca);
$relationMap = $this->relationMapBuilder->buildFromStructure($fullTca);
foreach (array_keys($fullTca) as $table) {
$schemata[$table] = $this->build($table, $fullTca, $relationMap);
}
return new SchemaCollection($schemata);
}
/**
* Builds a schema from a TCA table, if a sub-schema is requested, it will build the main schema and
* all sub-schematas first.
*
* First builds all fields, then the schema and attach the fields, so all parts can never be
* modified (except for adding sub-schema - this might be removed at some point hopefully).
*
* Then, resolves the sub-schema and the relevant fields for there with columnsOverrides taken into
* account.
*
* As it is crucial to understand, parts such as FlexForms (incl. Sheet, SectionContainers and their Fields)
* NEED to be resolved first, because they need to be attached.
*/
private function build(string $schemaName, array $fullTca, RelationMap $relationMap): TcaSchema
{
if (str_contains($schemaName, '.')) {
// @todo: This 'if' is dead code, isn't it?
[$mainSchema, $subSchema] = explode('.', $schemaName, 2);
$mainSchema = $this->build($mainSchema, $fullTca, $relationMap);
return $mainSchema->getSubSchema($subSchema);
}
// Collect all fields
$allFields = [];
$schemaDefinition = $fullTca[$schemaName];
foreach ($schemaDefinition['columns'] ?? [] as $fieldName => $fieldConfiguration) {
try {
$field = $this->fieldTypeFactory->createFieldType(
$fieldName,
$fieldConfiguration,
$schemaName,
$relationMap
);
} catch (FieldTypeNotAvailableException) {
continue;
}
$allFields[$fieldName] = $field;
}
$schemaConfiguration = $schemaDefinition['ctrl'] ?? [];
// Store "palettes" information into the ctrl section
if (is_array($schemaDefinition['palettes'] ?? null)) {
$schemaConfiguration['palettes'] = $schemaDefinition['palettes'];
}
// Resolve all sub schemas and collect their fields while keeping the system fields
$subSchemata = [];
if (isset($schemaDefinition['ctrl']['type'])) {
foreach ($schemaDefinition['types'] ?? [] as $subSchemaName => $subSchemaDefinition) {
$subSchemaName = (string)$subSchemaName;
$subSchemaFields = [];
$subSchemaFieldInformation = $this->findRelevantFieldsForSubSchema($schemaDefinition, $subSchemaName);
foreach ($subSchemaFieldInformation as $fieldName => $subSchemaFieldConfiguration) {
try {
$field = $this->fieldTypeFactory->createFieldType(
$fieldName,
$subSchemaFieldConfiguration,
$subSchemaName,
// Interesting side-note: The relations stay the same as it is not possible to modify
// this for a subtype.
$relationMap,
$schemaName
);
} catch (FieldTypeNotAvailableException) {
continue;
}
$subSchemaFields[$fieldName] = $field;
}
$subSchemaFieldCollection = new FieldCollection($subSchemaFields);
$subSchemata[$subSchemaName] = new TcaSchema(
$schemaName . '.' . $subSchemaName,
$subSchemaFieldCollection,
// Merge parts from the "types" section into the ctrl section of the main schema
array_replace_recursive($schemaConfiguration, $subSchemaDefinition),
null,
[],
$this->getOrderedWizardSteps($subSchemaDefinition, $subSchemaFieldCollection, $subSchemaName)
);
}
} elseif (($schemaDefinition['types'] ?? []) !== []) {
// Merge parts from the "types" section into the ctrl section of the main schema
$schemaConfiguration = array_replace_recursive(
$schemaConfiguration,
array_first($schemaDefinition['types'])
);
}
return new TcaSchema(
$schemaName,
new FieldCollection($allFields),
$schemaConfiguration,
$subSchemata !== [] ? new SchemaCollection($subSchemata) : null,
$relationMap->getPassiveRelations($schemaName)
);
}
private function findRelevantFieldsForSubSchema(array $tcaForTable, string $subSchemaName): array
{
$fields = [];
if (!isset($tcaForTable['types'][$subSchemaName])) {
throw new \InvalidArgumentException('Subschema "' . $subSchemaName . '" not found.', 1715269835);
}
$subSchemaConfig = $tcaForTable['types'][$subSchemaName];
$showItemArray = GeneralUtility::trimExplode(',', $subSchemaConfig['showitem'] ?? '', true);
foreach ($showItemArray as $aShowItemFieldString) {
[$fieldName, $fieldLabel, $paletteName] = GeneralUtility::trimExplode(';', $aShowItemFieldString . ';;;');
if ($fieldName === '--div--') {
// tabs are not of interest here
continue;
}
if ($fieldName === '--palette--' && !empty($paletteName)) {
// showitem references to a palette field. unpack the palette and process
// label overrides that may be in there.
if (!isset($tcaForTable['palettes'][$paletteName]['showitem'])) {
// No palette with this name found? Skip it.
continue;
}
$palettesArray = GeneralUtility::trimExplode(
',',
$tcaForTable['palettes'][$paletteName]['showitem']
);
foreach ($palettesArray as $aPalettesString) {
[$fieldName, $fieldLabel] = GeneralUtility::trimExplode(';', $aPalettesString . ';;');
if (isset($tcaForTable['columns'][$fieldName])) {
$fields[$fieldName] = $this->getFinalFieldConfiguration($fieldName, $tcaForTable, $subSchemaConfig, $fieldLabel);
}
}
} elseif (isset($tcaForTable['columns'][$fieldName])) {
$fields[$fieldName] = $this->getFinalFieldConfiguration($fieldName, $tcaForTable, $subSchemaConfig, $fieldLabel);
}
}
return $fields;
}
/**
* Handle label and possible columnsOverrides
*/
private function getFinalFieldConfiguration(string $fieldName, array $schemaConfiguration, array $subSchemaConfiguration, ?string $fieldLabel): array
{
$fieldConfiguration = $schemaConfiguration['columns'][$fieldName] ?? [];
if (isset($subSchemaConfiguration['columnsOverrides'][$fieldName])) {
$fieldConfiguration = array_replace_recursive($fieldConfiguration, $subSchemaConfiguration['columnsOverrides'][$fieldName]);
}
if (!empty($fieldLabel)) {
$fieldConfiguration['label'] = $fieldLabel;
}
return $fieldConfiguration;
}
/**
* @throws UndefinedFieldException
*/
private function getOrderedWizardSteps(array $schemaDefinition, FieldCollection $fieldCollection, string $subSchemaName): array
{
if (!isset($schemaDefinition['wizardSteps'])) {
return [];
}
$wizardSteps = [];
$dependencyOrderingService = GeneralUtility::makeInstance(DependencyOrderingService::class);
$orderedWizardSteps = $dependencyOrderingService->orderByDependencies($schemaDefinition['wizardSteps']);
foreach ($orderedWizardSteps as $stepIdentifier => $wizardStep) {
$fields = $wizardStep['fields'] ?? throw new \UnexpectedValueException('Wizard step fields are missing', 1774356281);
$undefinedFields = array_diff($fields, $fieldCollection->getNames());
if ($undefinedFields !== []) {
throw new UndefinedFieldException(sprintf('Wizard step fields: "%s" are not configured in TCA schema: "%s"', implode(',', $undefinedFields), $subSchemaName), 1774355993);
}
$wizardFieldCollection = array_filter(iterator_to_array($fieldCollection), fn(FieldTypeInterface $field) => in_array($field->getName(), $fields));
$wizardSteps[$stepIdentifier] = new WizardStep($stepIdentifier, $wizardStep['title'] ?? '', new FieldCollection($wizardFieldCollection));
}
return $wizardSteps;
}
}