TYPO3 v15 dev-main snapshot ()
This commit is contained in:
@@ -0,0 +1,319 @@
|
||||
<?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\DataHandling\TableColumnType;
|
||||
use TYPO3\CMS\Core\Schema\Capability\TcaSchemaCapability;
|
||||
use TYPO3\CMS\Core\Schema\Exception\InvalidSchemaTypeException;
|
||||
use TYPO3\CMS\Core\Schema\Exception\UndefinedFieldException;
|
||||
use TYPO3\CMS\Core\Schema\Exception\UndefinedSchemaException;
|
||||
use TYPO3\CMS\Core\Schema\Field\FieldCollection;
|
||||
use TYPO3\CMS\Core\Schema\Field\FieldTypeInterface;
|
||||
use TYPO3\CMS\Core\Schema\Field\LanguageFieldType;
|
||||
use TYPO3\CMS\Core\Schema\Field\RelationalFieldTypeInterface;
|
||||
use TYPO3\CMS\Core\Schema\Struct\WizardStep;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Main implementation class for TCA-based schema.
|
||||
*/
|
||||
readonly class TcaSchema implements SchemaInterface
|
||||
{
|
||||
public function __construct(
|
||||
protected string $name,
|
||||
protected FieldCollection $fields,
|
||||
protected array $schemaConfiguration,
|
||||
protected ?SchemaCollection $subSchemata = null,
|
||||
/** @var PassiveRelation[] */
|
||||
protected array $passiveRelations = [],
|
||||
/** @var list<WizardStep> $wizardSteps */
|
||||
protected array $wizardSteps = [],
|
||||
) {}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function getFields(?callable $filterFunction = null): FieldCollection
|
||||
{
|
||||
if ($filterFunction === null) {
|
||||
return $this->fields;
|
||||
}
|
||||
|
||||
return new FieldCollection(array_filter(iterator_to_array($this->fields), $filterFunction));
|
||||
}
|
||||
|
||||
public function hasField(string $fieldName): bool
|
||||
{
|
||||
return isset($this->fields[$fieldName]);
|
||||
}
|
||||
|
||||
public function getField(string $fieldName): FieldTypeInterface
|
||||
{
|
||||
if (!$this->hasField($fieldName)) {
|
||||
throw new UndefinedFieldException('The field "' . $fieldName . '" is not defined for the TCA schema "' . $this->name . '".', 1661615151);
|
||||
}
|
||||
return $this->fields[$fieldName];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return FieldTypeInterface[]
|
||||
* @internal not part of TYPO3 Core API.
|
||||
*/
|
||||
public function getFieldsOfType(TableColumnType $type): iterable
|
||||
{
|
||||
foreach ($this->fields as $field) {
|
||||
if (TableColumnType::tryFrom($field->getType()) !== $type) {
|
||||
continue;
|
||||
}
|
||||
yield $field;
|
||||
}
|
||||
}
|
||||
|
||||
public function getTitle(?callable $fn = null): string
|
||||
{
|
||||
// If a title is defined in the schema configuration, use it.
|
||||
if (isset($this->schemaConfiguration['title']) && $fn) {
|
||||
return $fn($this->schemaConfiguration['title']);
|
||||
}
|
||||
return $this->schemaConfiguration['title'] ?? '';
|
||||
}
|
||||
|
||||
public function getRawConfiguration(): array
|
||||
{
|
||||
return $this->schemaConfiguration;
|
||||
}
|
||||
|
||||
public function isLanguageAware(): bool
|
||||
{
|
||||
return isset($this->schemaConfiguration['languageField']) && isset($this->schemaConfiguration['transOrigPointerField']);
|
||||
}
|
||||
|
||||
public function isWorkspaceAware(): bool
|
||||
{
|
||||
return (bool)($this->schemaConfiguration['versioningWS'] ?? false);
|
||||
}
|
||||
|
||||
public function hasCapability(TcaSchemaCapability $capability): bool
|
||||
{
|
||||
return match ($capability) {
|
||||
TcaSchemaCapability::SoftDelete => !empty($this->schemaConfiguration['delete'] ?? null),
|
||||
TcaSchemaCapability::CreatedAt => (bool)($this->schemaConfiguration['crdate'] ?? null),
|
||||
TcaSchemaCapability::UpdatedAt => (bool)($this->schemaConfiguration['tstamp'] ?? null),
|
||||
TcaSchemaCapability::SortByField => !empty($this->schemaConfiguration['sortby'] ?? null),
|
||||
TcaSchemaCapability::DefaultSorting => (bool)($this->schemaConfiguration['default_sortby'] ?? null),
|
||||
TcaSchemaCapability::AncestorReferenceField => (bool)($this->schemaConfiguration['origUid'] ?? null),
|
||||
|
||||
TcaSchemaCapability::EditLock => isset($this->schemaConfiguration['editlock']) && isset($this->fields[$this->schemaConfiguration['editlock']]),
|
||||
TcaSchemaCapability::InternalDescription => isset($this->schemaConfiguration['descriptionColumn']) && isset($this->fields[$this->schemaConfiguration['descriptionColumn']]),
|
||||
|
||||
TcaSchemaCapability::Language => $this->isLanguageAware(),
|
||||
TcaSchemaCapability::Workspace => $this->isWorkspaceAware(),
|
||||
|
||||
TcaSchemaCapability::Label => (bool)($this->schemaConfiguration['label'] ?? ''),
|
||||
|
||||
TcaSchemaCapability::AccessAdminOnly => (bool)($this->schemaConfiguration['adminOnly'] ?? false),
|
||||
TcaSchemaCapability::AccessReadOnly => (bool)($this->schemaConfiguration['readOnly'] ?? false),
|
||||
TcaSchemaCapability::HideRecordsAtCopy => (bool)($this->schemaConfiguration['hideAtCopy'] ?? false),
|
||||
TcaSchemaCapability::HideInUi => (bool)($this->schemaConfiguration['hideTable'] ?? false),
|
||||
TcaSchemaCapability::PrependLabelTextAtCopy => (bool)((string)($this->schemaConfiguration['prependAtCopy'] ?? '')),
|
||||
TcaSchemaCapability::RestrictionDisabledField => isset($this->schemaConfiguration['enablecolumns']['disabled']),
|
||||
TcaSchemaCapability::RestrictionStartTime => isset($this->schemaConfiguration['enablecolumns']['starttime']),
|
||||
TcaSchemaCapability::RestrictionEndTime => isset($this->schemaConfiguration['enablecolumns']['endtime']),
|
||||
TcaSchemaCapability::RestrictionUserGroup => isset($this->schemaConfiguration['enablecolumns']['fe_group']),
|
||||
// This is an implicit restriction with a custom configuration
|
||||
TcaSchemaCapability::RestrictionRootLevel => true,
|
||||
TcaSchemaCapability::RestrictionWebMount => !empty($this->schemaConfiguration['security']['ignoreWebMountRestriction'] ?? false),
|
||||
|
||||
TcaSchemaCapability::ExtbaseHistoryTracking => (bool)($this->schemaConfiguration['extbase']['enableHistoryTracking'] ?? true),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ($capability is TcaSchemaCapability::Language ? Capability\LanguageAwareSchemaCapability
|
||||
* : ($capability is TcaSchemaCapability::RestrictionRootLevel ? Capability\RootLevelCapability
|
||||
* : ($capability is TcaSchemaCapability::EditLock ? Capability\FieldCapability
|
||||
* : ($capability is TcaSchemaCapability::InternalDescription ? Capability\FieldCapability
|
||||
* : ($capability is TcaSchemaCapability::RestrictionDisabledField ? Capability\FieldCapability
|
||||
* : ($capability is TcaSchemaCapability::RestrictionStartTime ? Capability\FieldCapability
|
||||
* : ($capability is TcaSchemaCapability::RestrictionEndTime ? Capability\FieldCapability
|
||||
* : ($capability is TcaSchemaCapability::RestrictionUserGroup ? Capability\FieldCapability
|
||||
* : ($capability is TcaSchemaCapability::AccessReadOnly ? Capability\ScalarCapability
|
||||
* : ($capability is TcaSchemaCapability::AccessAdminOnly ? Capability\ScalarCapability
|
||||
* : ($capability is TcaSchemaCapability::HideRecordsAtCopy ? Capability\ScalarCapability
|
||||
* : ($capability is TcaSchemaCapability::HideInUi ? Capability\ScalarCapability
|
||||
* : ($capability is TcaSchemaCapability::PrependLabelTextAtCopy ? Capability\ScalarCapability
|
||||
* : ($capability is TcaSchemaCapability::DefaultSorting ? Capability\ScalarCapability
|
||||
* : ($capability is TcaSchemaCapability::Label ? Capability\LabelCapability
|
||||
* : ($capability is TcaSchemaCapability::ExtbaseHistoryTracking ? Capability\ScalarCapability
|
||||
* : ($capability is TcaSchemaCapability::AncestorReferenceField ? Capability\SystemInternalFieldCapability
|
||||
* : Capability\SystemInternalFieldCapability)))))))))))))))))
|
||||
*/
|
||||
public function getCapability(TcaSchemaCapability $capability): Capability\SchemaCapabilityInterface
|
||||
{
|
||||
return match ($capability) {
|
||||
TcaSchemaCapability::SoftDelete => new Capability\SystemInternalFieldCapability((string)($this->schemaConfiguration['delete'] ?? '')),
|
||||
TcaSchemaCapability::CreatedAt => new Capability\SystemInternalFieldCapability((string)($this->schemaConfiguration['crdate'] ?? '')),
|
||||
TcaSchemaCapability::UpdatedAt => new Capability\SystemInternalFieldCapability((string)($this->schemaConfiguration['tstamp'] ?? '')),
|
||||
TcaSchemaCapability::SortByField => new Capability\SystemInternalFieldCapability((string)($this->schemaConfiguration['sortby'] ?? '')),
|
||||
TcaSchemaCapability::DefaultSorting => new Capability\ScalarCapability((string)($this->schemaConfiguration['default_sortby'] ?? '')),
|
||||
TcaSchemaCapability::AncestorReferenceField => new Capability\SystemInternalFieldCapability((string)($this->schemaConfiguration['origUid'] ?? '')),
|
||||
|
||||
TcaSchemaCapability::EditLock => new Capability\FieldCapability($this->fields[$this->schemaConfiguration['editlock']]),
|
||||
TcaSchemaCapability::InternalDescription => new Capability\FieldCapability($this->fields[$this->schemaConfiguration['descriptionColumn']]),
|
||||
|
||||
TcaSchemaCapability::Language => $this->buildLanguageCapability(),
|
||||
TcaSchemaCapability::Workspace => new Capability\ScalarCapability((bool)($this->schemaConfiguration['versioningWS'] ?? false)),
|
||||
|
||||
TcaSchemaCapability::Label => $this->buildLabelCapability(),
|
||||
|
||||
TcaSchemaCapability::AccessAdminOnly => new Capability\ScalarCapability((bool)($this->schemaConfiguration['adminOnly'] ?? false)),
|
||||
TcaSchemaCapability::AccessReadOnly => new Capability\ScalarCapability((bool)($this->schemaConfiguration['readOnly'] ?? false)),
|
||||
TcaSchemaCapability::HideRecordsAtCopy => new Capability\ScalarCapability((bool)($this->schemaConfiguration['hideAtCopy'] ?? false)),
|
||||
TcaSchemaCapability::HideInUi => new Capability\ScalarCapability((bool)($this->schemaConfiguration['hideTable'] ?? false)),
|
||||
TcaSchemaCapability::PrependLabelTextAtCopy => new Capability\ScalarCapability((string)($this->schemaConfiguration['prependAtCopy'] ?? '')),
|
||||
TcaSchemaCapability::RestrictionDisabledField => new Capability\FieldCapability($this->getField($this->schemaConfiguration['enablecolumns']['disabled'])),
|
||||
TcaSchemaCapability::RestrictionStartTime => new Capability\FieldCapability($this->getField($this->schemaConfiguration['enablecolumns']['starttime'])),
|
||||
TcaSchemaCapability::RestrictionEndTime => new Capability\FieldCapability($this->getField($this->schemaConfiguration['enablecolumns']['endtime'])),
|
||||
TcaSchemaCapability::RestrictionUserGroup => new Capability\FieldCapability($this->getField($this->schemaConfiguration['enablecolumns']['fe_group'])),
|
||||
TcaSchemaCapability::RestrictionRootLevel => new Capability\RootLevelCapability((int)($this->schemaConfiguration['rootLevel'] ?? 0), (bool)($this->schemaConfiguration['security']['ignoreRootLevelRestriction'] ?? false)),
|
||||
TcaSchemaCapability::RestrictionWebMount => new Capability\ScalarCapability((bool)($this->schemaConfiguration['security']['ignoreWebMountRestriction'] ?? false)),
|
||||
|
||||
TcaSchemaCapability::ExtbaseHistoryTracking => new Capability\ScalarCapability((bool)($this->schemaConfiguration['extbase']['enableHistoryTracking'] ?? true)),
|
||||
};
|
||||
}
|
||||
|
||||
protected function buildLanguageCapability(): Capability\LanguageAwareSchemaCapability
|
||||
{
|
||||
/** @var LanguageFieldType $languageField */
|
||||
$languageField = $this->fields[$this->schemaConfiguration['languageField']];
|
||||
return new Capability\LanguageAwareSchemaCapability(
|
||||
$languageField,
|
||||
$this->fields[$this->schemaConfiguration['transOrigPointerField']],
|
||||
(isset($this->schemaConfiguration['translationSource']) ? ($this->fields[$this->schemaConfiguration['translationSource']] ?? null) : null),
|
||||
(isset($this->schemaConfiguration['transOrigDiffSourceField']) ? ($this->fields[$this->schemaConfiguration['transOrigDiffSourceField']] ?? null) : null),
|
||||
);
|
||||
}
|
||||
|
||||
protected function buildLabelCapability(): Capability\LabelCapability
|
||||
{
|
||||
$labelConfiguration = [];
|
||||
if (isset($this->schemaConfiguration['label_userFunc'])) {
|
||||
$labelConfiguration['generator'] = $this->schemaConfiguration['label_userFunc'];
|
||||
$labelConfiguration['generatorOptions'] = $this->schemaConfiguration['label_userFunc_options'] ?? [];
|
||||
}
|
||||
if (isset($this->schemaConfiguration['formattedLabel_userFunc'])) {
|
||||
$labelConfiguration['formatter'] = $this->schemaConfiguration['formattedLabel_userFunc'];
|
||||
$labelConfiguration['formatterOptions'] = $this->schemaConfiguration['formattedLabel_userFunc_options'] ?? [];
|
||||
}
|
||||
return new Capability\LabelCapability(
|
||||
$this->schemaConfiguration['label'] ?? null,
|
||||
array_unique(GeneralUtility::trimExplode(',', $this->schemaConfiguration['label_alt'] ?? '', true)),
|
||||
(bool)($this->schemaConfiguration['label_alt_force'] ?? false),
|
||||
$labelConfiguration
|
||||
);
|
||||
}
|
||||
|
||||
public function hasSubSchema(string $subSchema): bool
|
||||
{
|
||||
return isset($this->subSchemata[$subSchema]);
|
||||
}
|
||||
|
||||
public function getSubSchema(string $subSchema): TcaSchema
|
||||
{
|
||||
if (!$this->hasSubSchema($subSchema)) {
|
||||
throw new UndefinedSchemaException('The sub schema "' . $subSchema . '" is not defined for the TCA schema "' . $this->name . '".', 1661617062);
|
||||
}
|
||||
|
||||
return $this->subSchemata[$subSchema];
|
||||
}
|
||||
|
||||
public function getSubSchemata(): SchemaCollection
|
||||
{
|
||||
return $this->subSchemata ?? new SchemaCollection([]);
|
||||
}
|
||||
|
||||
public function supportsSubSchema(): bool
|
||||
{
|
||||
return isset($this->schemaConfiguration['type']);
|
||||
}
|
||||
|
||||
public function getSubSchemaTypeInformation(): SchemaTypeInformation
|
||||
{
|
||||
$typeInformation = $this->schemaConfiguration['type'] ?? null;
|
||||
if ($typeInformation === null) {
|
||||
throw new InvalidSchemaTypeException('The schema "' . $this->name . '" has no type information.', 1749241443);
|
||||
}
|
||||
if (str_contains($typeInformation, ':')) {
|
||||
[$localField, $foreignField] = explode(':', $typeInformation, 2);
|
||||
if (!$this->fields->offsetExists($localField) || $this->fields[$localField] instanceof RelationalFieldTypeInterface === false) {
|
||||
throw new InvalidSchemaTypeException('The schema "' . $this->name . '" defines a foreign field type "' . $typeInformation . '" but there is either no such local field "' . $localField . '" or the field is no relational field.', 1749241444);
|
||||
}
|
||||
$activeRelation = $this->fields[$localField]->getRelations()[0] ?? null;
|
||||
if ($activeRelation instanceof ActiveRelation === false || $activeRelation->toTable() === '') {
|
||||
throw new InvalidSchemaTypeException('The schema "' . $this->name . '" defines a foreign field type "' . $typeInformation . '" but the local field "' . $localField . '" does not provide a valid realtion.', 1749241445);
|
||||
}
|
||||
return new SchemaTypeInformation(
|
||||
$this->getName(),
|
||||
$localField,
|
||||
$foreignField,
|
||||
$activeRelation->toTable()
|
||||
);
|
||||
}
|
||||
if (!$this->fields->offsetExists($typeInformation)) {
|
||||
throw new InvalidSchemaTypeException('The schema "' . $this->name . '" defines a field type "' . $typeInformation . '" but there is no such field.', 1749241446);
|
||||
}
|
||||
return new SchemaTypeInformation(
|
||||
$this->getName(),
|
||||
$typeInformation,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return PassiveRelation[]
|
||||
*/
|
||||
public function getPassiveRelations(): array
|
||||
{
|
||||
return $this->passiveRelations;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ActiveRelation[]
|
||||
*/
|
||||
public function getActiveRelations(): array
|
||||
{
|
||||
$relations = [];
|
||||
foreach ($this->fields as $field) {
|
||||
if ($field instanceof RelationalFieldTypeInterface) {
|
||||
$relations = array_merge($relations, $field->getRelations());
|
||||
}
|
||||
}
|
||||
return $relations;
|
||||
}
|
||||
|
||||
public function getWizardSteps(): array
|
||||
{
|
||||
return $this->wizardSteps;
|
||||
}
|
||||
|
||||
public static function __set_state(array $state): self
|
||||
{
|
||||
return new self(...$state);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user