diff --git a/typo3/sysext/core/Classes/DataHandling/RecordFieldTransformer.php b/typo3/sysext/core/Classes/DataHandling/RecordFieldTransformer.php index 607e595acb3c68671a50526501ac309ec7023ce4..a3d4e05cce847d07f19175c75180815f9268c1e4 100644 --- a/typo3/sysext/core/Classes/DataHandling/RecordFieldTransformer.php +++ b/typo3/sysext/core/Classes/DataHandling/RecordFieldTransformer.php @@ -88,7 +88,7 @@ readonly class RecordFieldTransformer // type=file needs to be handled before RelationalFieldTypeInterface if ($fieldInformation instanceof FileFieldType) { - if ($fieldInformation->getRelationshipType()->isToOne()) { + if ($fieldInformation->getRelationshipType()->hasOne()) { return new RecordPropertyClosure( function () use ($rawRecord, $fieldInformation, $context): ?FileReference { $fileReference = $this->relationResolver->resolveFileReferences($rawRecord, $fieldInformation, $context)[0] ?? null; @@ -108,7 +108,7 @@ readonly class RecordFieldTransformer /** @var RecordFactory $recordFactory */ // @todo This method is called by RecordFactory -> instantiating the factory here again shows, that those classes should actually be somehow belong together. $recordFactory = GeneralUtility::makeInstance(RecordFactory::class); - if ($fieldInformation->getRelationshipType()->isToOne()) { + if ($fieldInformation->getRelationshipType()->hasOne()) { return new RecordPropertyClosure( function () use ($rawRecord, $fieldInformation, $context, $recordFactory): ?RecordInterface { $recordData = $this->relationResolver->resolve($rawRecord, $fieldInformation, $context)[0] ?? null; diff --git a/typo3/sysext/core/Classes/Schema/Capability/FieldCapability.php b/typo3/sysext/core/Classes/Schema/Capability/FieldCapability.php index 21c8e355724e13c3c00928c7fa08230a02edf834..61bdfa3634212dbbb91a14ebfa6582df61c723c6 100644 --- a/typo3/sysext/core/Classes/Schema/Capability/FieldCapability.php +++ b/typo3/sysext/core/Classes/Schema/Capability/FieldCapability.php @@ -44,4 +44,9 @@ final readonly class FieldCapability implements SchemaCapabilityInterface { return $this->field; } + + public function __toString(): string + { + return $this->getFieldName(); + } } diff --git a/typo3/sysext/core/Classes/Schema/Capability/SystemInternalFieldCapability.php b/typo3/sysext/core/Classes/Schema/Capability/SystemInternalFieldCapability.php index 6feada11c52c89efbba53137dcec779d1aee4bbb..fdfca48d3ae11db3288689559cd5902f57593ab4 100644 --- a/typo3/sysext/core/Classes/Schema/Capability/SystemInternalFieldCapability.php +++ b/typo3/sysext/core/Classes/Schema/Capability/SystemInternalFieldCapability.php @@ -36,4 +36,9 @@ final readonly class SystemInternalFieldCapability implements SchemaCapabilityIn { return $this->fieldName; } + + public function __toString(): string + { + return $this->getFieldName(); + } } diff --git a/typo3/sysext/core/Classes/Schema/Field/DateTimeFieldType.php b/typo3/sysext/core/Classes/Schema/Field/DateTimeFieldType.php index e20639f638f52156a4529e59c89029cc6fd801a4..18e1824b83c12611a69298c58bf2a61ff3ebc21d 100644 --- a/typo3/sysext/core/Classes/Schema/Field/DateTimeFieldType.php +++ b/typo3/sysext/core/Classes/Schema/Field/DateTimeFieldType.php @@ -17,6 +17,8 @@ declare(strict_types=1); namespace TYPO3\CMS\Core\Schema\Field; +use TYPO3\CMS\Core\Database\Query\QueryHelper; + /** * @internal This is an experimental implementation and might change until TYPO3 v13 LTS */ @@ -34,7 +36,7 @@ final readonly class DateTimeFieldType extends AbstractFieldType implements Fiel public function getPersistenceType(): ?string { - return $this->configuration['dbtype'] ?? null; + return in_array($this->configuration['dbType'] ?? null, QueryHelper::getDateTimeTypes(), true) ? $this->configuration['dbType'] : null; } public function isNullable(): bool diff --git a/typo3/sysext/core/Classes/Schema/RelationshipType.php b/typo3/sysext/core/Classes/Schema/RelationshipType.php index 541112fe4273c7a76bcca9debd04d4e64c4b878d..7bc2ce621cc90a7e8b1c7860e37e8d0ebdc73a43 100644 --- a/typo3/sysext/core/Classes/Schema/RelationshipType.php +++ b/typo3/sysext/core/Classes/Schema/RelationshipType.php @@ -72,11 +72,16 @@ enum RelationshipType: string return self::Undefined; } - public function isToOne(): bool + public function hasOne(): bool { return in_array($this, [self::OneToOne, self::ManyToOne], true); } + public function hasMany(): bool + { + return in_array($this, [self::ManyToMany, self::OneToMany, self::List], true); + } + public function isSingularRelationship(): bool { return in_array($this, [self::OneToOne, self::ManyToOne, self::OneToMany, self::List], true); diff --git a/typo3/sysext/extbase/Classes/Persistence/Generic/Mapper/ColumnMapFactory.php b/typo3/sysext/extbase/Classes/Persistence/Generic/Mapper/ColumnMapFactory.php index 9814a5feef75e6f06296564e59f1e249eed90e3e..f435e084234b1a01f60824c91f4a8797c070456e 100644 --- a/typo3/sysext/extbase/Classes/Persistence/Generic/Mapper/ColumnMapFactory.php +++ b/typo3/sysext/extbase/Classes/Persistence/Generic/Mapper/ColumnMapFactory.php @@ -17,10 +17,13 @@ declare(strict_types=1); namespace TYPO3\CMS\Extbase\Persistence\Generic\Mapper; -use TYPO3\CMS\Core\Database\Query\QueryHelper; use TYPO3\CMS\Core\DataHandling\TableColumnType; +use TYPO3\CMS\Core\Schema\Field\DateTimeFieldType; +use TYPO3\CMS\Core\Schema\Field\FieldTypeInterface; +use TYPO3\CMS\Core\Schema\Field\FolderFieldType; +use TYPO3\CMS\Core\Schema\Field\RelationalFieldTypeInterface; +use TYPO3\CMS\Core\Schema\RelationshipType; use TYPO3\CMS\Core\Utility\GeneralUtility; -use TYPO3\CMS\Extbase\Persistence\Generic\Exception\UnsupportedRelationException; use TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap\Relation; use TYPO3\CMS\Extbase\Reflection\ClassSchema\Exception\NoPropertyTypesException; use TYPO3\CMS\Extbase\Reflection\ClassSchema\Exception\NoSuchPropertyException; @@ -29,15 +32,15 @@ use TYPO3\CMS\Extbase\Reflection\ReflectionService; /** * @internal only to be used within Extbase, not part of TYPO3 Core API. */ -class ColumnMapFactory +readonly class ColumnMapFactory { public function __construct( - private readonly ReflectionService $reflectionService + private ReflectionService $reflectionService ) {} - public function create(string $columnName, array $columnDefinition, string $propertyName, string $className): ColumnMap + public function create(FieldTypeInterface $field, string $propertyName, string $className): ColumnMap { - $columnMap = GeneralUtility::makeInstance(ColumnMap::class, $columnName); + $columnMap = GeneralUtility::makeInstance(ColumnMap::class, $field->getName()); try { $property = $this->reflectionService->getClassSchema($className)->getProperty($propertyName); $nonProxyPropertyTypes = $property->getFilteredTypes([$property, 'filterLazyLoadingProxyAndLazyObjectStorage']); @@ -59,105 +62,70 @@ class ColumnMapFactory } catch (NoSuchPropertyException|NoPropertyTypesException $e) { [$type, $elementType] = [null, null]; } - $columnMap = $this->setType($columnMap, $columnDefinition['config']); - $columnMap = $this->setRelations($columnMap, $columnDefinition['config'], $type, $elementType); - return $this->setDateTimeStorageFormat($columnMap, $columnDefinition['config']); - } - - /** - * Set the table column type - */ - protected function setType(ColumnMap $columnMap, array $columnConfiguration): ColumnMap - { - // todo: this method should only be called with proper arguments which means that the TCA integrity check should - // todo: take place outside this method. - - $tableColumnType = $columnConfiguration['type'] ?? null; - $columnMap->setType(TableColumnType::tryFrom($tableColumnType) ?? TableColumnType::INPUT); + // @todo Why type "input" - shouldn't we better throw an exception here? + $columnMap->setType(TableColumnType::tryFrom($field->getType()) ?? TableColumnType::INPUT); + if ($field instanceof DateTimeFieldType) { + $columnMap->setDateTimeStorageFormat($field->getPersistenceType()); + } + $columnMap = $this->setRelations($columnMap, $field, $type, $elementType); return $columnMap; } /** * This method tries to determine the type of relation to other tables and sets it based on * the $TCA column configuration - * - * @param ColumnMap $columnMap The column map - * @param array|null $columnConfiguration The column configuration from $TCA */ - protected function setRelations(ColumnMap $columnMap, ?array $columnConfiguration, ?string $type, ?string $elementType): ColumnMap + protected function setRelations(ColumnMap $columnMap, FieldTypeInterface $field, ?string $type, ?string $elementType): ColumnMap { - if (!isset($columnConfiguration)) { + $columnConfiguration = $field->getConfiguration(); + if ($field instanceof FolderFieldType + && !in_array((string)($columnConfiguration['relationship'] ?? ''), ['oneToOne', 'manyToOne'], true) + && (!isset($columnConfiguration['maxitems']) || $columnConfiguration['maxitems'] > 1) + ) { + $columnMap->setTypeOfRelation(Relation::HAS_MANY); + return $columnMap; + } + + if ($field instanceof RelationalFieldTypeInterface === false) { return $columnMap; } - if (isset($columnConfiguration['MM'])) { - return $this->setManyToManyRelation($columnMap, $columnConfiguration); + if ($field->getRelationshipType() === RelationshipType::ManyToMany) { + return $this->setManyToManyRelation($columnMap, $field); } if ($elementType !== null) { - return $this->setOneToManyRelation($columnMap, $columnConfiguration); + return $this->setOneToManyRelation($columnMap, $field); } if ($type !== null && strpbrk($type, '_\\') !== false) { // @todo: check the strpbrk function call. Seems to be a check for Tx_Foo_Bar style class names - return $this->setOneToOneRelation($columnMap, $columnConfiguration); + return $this->setOneToOneRelation($columnMap, $field); } - if ( - isset($columnConfiguration['type'], $columnConfiguration['renderType']) - && $columnConfiguration['type'] === 'select' + $columnConfiguration = $field->getConfiguration(); + // @todo we should get rid of the "maxitems" and "renderType" cases here and rely purely on + // the evaluated relationship type -> to be consistent with all non extbase components. + if ($field->getRelationshipType()->hasMany() && ( - $columnConfiguration['renderType'] !== 'selectSingle' - || (isset($columnConfiguration['maxitems']) && $columnConfiguration['maxitems'] > 1) + !$field->isType(TableColumnType::GROUP, TableColumnType::SELECT) + || ($field->isType(TableColumnType::GROUP) && (!isset($columnConfiguration['maxitems']) || $columnConfiguration['maxitems'] > 1)) + || ($field->isType(TableColumnType::SELECT) && (($columnConfiguration['renderType'] ?? '') !== 'selectSingle' || (int)($columnConfiguration['maxitems'] ?? 0) > 1)) ) ) { $columnMap->setTypeOfRelation(Relation::HAS_MANY); return $columnMap; } - if ( - isset($columnConfiguration['type']) && ($columnConfiguration['type'] === 'group' || $columnConfiguration['type'] === 'folder') - && (!isset($columnConfiguration['maxitems']) || $columnConfiguration['maxitems'] > 1) - ) { - $columnMap->setTypeOfRelation(Relation::HAS_MANY); - return $columnMap; - } - - return $columnMap; - } - - /** - * Sets datetime storage format based on $TCA column configuration. - * - * @param ColumnMap $columnMap The column map - * @param array|null $columnConfiguration The column configuration from $TCA - */ - protected function setDateTimeStorageFormat(ColumnMap $columnMap, ?array $columnConfiguration = null): ColumnMap - { - // todo: this method should only be called with proper arguments which means that the TCA integrity check should - // todo: take place outside this method. - - if ($columnMap->getType() === TableColumnType::DATETIME - && in_array($columnConfiguration['dbType'] ?? '', QueryHelper::getDateTimeTypes(), true) - ) { - $columnMap->setDateTimeStorageFormat($columnConfiguration['dbType']); - } - return $columnMap; } /** - * This method sets the configuration for a 1:1 relation based on - * the $TCA column configuration - * - * @param ColumnMap $columnMap The column map - * @param array|null $columnConfiguration The column configuration from $TCA + * This method sets the configuration for a 1:1 relation based on the $TCA column configuration */ - protected function setOneToOneRelation(ColumnMap $columnMap, ?array $columnConfiguration = null): ColumnMap + protected function setOneToOneRelation(ColumnMap $columnMap, FieldTypeInterface $field): ColumnMap { - // todo: this method should only be called with proper arguments which means that the TCA integrity check should - // todo: take place outside this method. - + $columnConfiguration = $field->getConfiguration(); $columnMap->setTypeOfRelation(Relation::HAS_ONE); // check if foreign_table is set, which usually won't be the case for type "group" fields if (!empty($columnConfiguration['foreign_table'])) { @@ -177,16 +145,11 @@ class ColumnMapFactory * This method sets the configuration for a 1:n relation based on * the $TCA column configuration * - * @param ColumnMap $columnMap The column map - * @param array|null $columnConfiguration The column configuration from $TCA - * * @internal */ - public function setOneToManyRelation(ColumnMap $columnMap, ?array $columnConfiguration = null): ColumnMap + public function setOneToManyRelation(ColumnMap $columnMap, FieldTypeInterface $field): ColumnMap { - // todo: this method should only be called with proper arguments which means that the TCA integrity check should - // todo: take place outside this method. - + $columnConfiguration = $field->getConfiguration(); $columnMap->setTypeOfRelation(Relation::HAS_MANY); // check if foreign_table is set, which usually won't be the case for type "group" fields if (!empty($columnConfiguration['foreign_table'])) { @@ -204,44 +167,30 @@ class ColumnMapFactory } /** - * This method sets the configuration for a m:n relation based on - * the $TCA column configuration - * - * @param ColumnMap $columnMap The column map - * @param array|null $columnConfiguration The column configuration from $TCA - * @throws \TYPO3\CMS\Extbase\Persistence\Generic\Exception\UnsupportedRelationException + * This method sets the configuration for a m:n relation based on the $TCA column configuration */ - protected function setManyToManyRelation(ColumnMap $columnMap, ?array $columnConfiguration = null): ColumnMap + protected function setManyToManyRelation(ColumnMap $columnMap, FieldTypeInterface $field): ColumnMap { - // todo: this method should only be called with proper arguments which means that the TCA integrity check should - // todo: take place outside this method. - - if (isset($columnConfiguration['MM'])) { - $columnMap->setTypeOfRelation(Relation::HAS_AND_BELONGS_TO_MANY); - // check if foreign_table is set, which usually won't be the case for type "group" fields - if (!empty($columnConfiguration['foreign_table'])) { - $columnMap->setChildTableName($columnConfiguration['foreign_table']); - } - // todo: don't update column map if value(s) isn't/aren't set. - $columnMap->setRelationTableName($columnConfiguration['MM']); - if (isset($columnConfiguration['MM_match_fields']) && is_array($columnConfiguration['MM_match_fields'])) { - $columnMap->setRelationTableMatchFields($columnConfiguration['MM_match_fields']); - } - // todo: don't update column map if value(s) isn't/aren't set. - if (!empty($columnConfiguration['MM_opposite_field'])) { - $columnMap->setParentKeyFieldName('uid_foreign'); - $columnMap->setChildKeyFieldName('uid_local'); - $columnMap->setChildSortByFieldName('sorting_foreign'); - } else { - $columnMap->setParentKeyFieldName('uid_local'); - $columnMap->setChildKeyFieldName('uid_foreign'); - $columnMap->setChildSortByFieldName('sorting'); - } + $columnConfiguration = $field->getConfiguration(); + $columnMap->setTypeOfRelation(Relation::HAS_AND_BELONGS_TO_MANY); + // check if foreign_table is set, which usually won't be the case for type "group" fields + if ($columnConfiguration['foreign_table'] ?? false) { + $columnMap->setChildTableName($columnConfiguration['foreign_table']); + } + // todo: don't update column map if value(s) isn't/aren't set. + $columnMap->setRelationTableName($columnConfiguration['MM']); + if (isset($columnConfiguration['MM_match_fields']) && is_array($columnConfiguration['MM_match_fields'])) { + $columnMap->setRelationTableMatchFields($columnConfiguration['MM_match_fields']); + } + // todo: don't update column map if value(s) isn't/aren't set. + if (!empty($columnConfiguration['MM_opposite_field'])) { + $columnMap->setParentKeyFieldName('uid_foreign'); + $columnMap->setChildKeyFieldName('uid_local'); + $columnMap->setChildSortByFieldName('sorting_foreign'); } else { - // todo: this else part is actually superfluous because \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapFactory::setRelations - // todo: only calls this method if $columnConfiguration['MM'] is set. - - throw new UnsupportedRelationException('The given information to build a many-to-many-relation was not sufficient. Check your TCA definitions. mm-relations with IRRE must have at least a defined "MM" or "foreign_selector".', 1268817963); + $columnMap->setParentKeyFieldName('uid_local'); + $columnMap->setChildKeyFieldName('uid_foreign'); + $columnMap->setChildSortByFieldName('sorting'); } return $columnMap; } diff --git a/typo3/sysext/extbase/Classes/Persistence/Generic/Mapper/DataMapFactory.php b/typo3/sysext/extbase/Classes/Persistence/Generic/Mapper/DataMapFactory.php index 69b858d85b3f120921dfc62967ea282028027955..4578e017b5cdb867be22a611475557507e2769fa 100644 --- a/typo3/sysext/extbase/Classes/Persistence/Generic/Mapper/DataMapFactory.php +++ b/typo3/sysext/extbase/Classes/Persistence/Generic/Mapper/DataMapFactory.php @@ -19,6 +19,8 @@ namespace TYPO3\CMS\Extbase\Persistence\Generic\Mapper; use TYPO3\CMS\Core\Cache\CacheManager; use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface; +use TYPO3\CMS\Core\Schema\Capability\TcaSchemaCapability; +use TYPO3\CMS\Core\Schema\TcaSchemaFactory; use TYPO3\CMS\Core\SingletonInterface; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface; @@ -66,6 +68,8 @@ class DataMapFactory implements SingletonInterface private ColumnMapFactory $columnMapFactory; + private TcaSchemaFactory $tcaSchemaFactory; + protected string $baseCacheIdentifier; public function __construct( @@ -74,6 +78,7 @@ class DataMapFactory implements SingletonInterface CacheManager $cacheManager, ClassesConfiguration $classesConfiguration, ColumnMapFactory $columnMapFactory, + TcaSchemaFactory $tcaSchemaFactory, string $baseCacheIdentifier ) { $this->reflectionService = $reflectionService; @@ -82,6 +87,7 @@ class DataMapFactory implements SingletonInterface $this->dataMapCache = $this->cacheManager->getCache('extbase'); $this->classesConfiguration = $classesConfiguration; $this->columnMapFactory = $columnMapFactory; + $this->tcaSchemaFactory = $tcaSchemaFactory; $this->baseCacheIdentifier = $baseCacheIdentifier; } @@ -147,13 +153,12 @@ class DataMapFactory implements SingletonInterface $dataMap = GeneralUtility::makeInstance(DataMap::class, $className, $tableName, $recordType, $subclasses); $dataMap = $this->addMetaDataColumnNames($dataMap, $tableName); - foreach ($this->getColumnsDefinition($tableName) as $columnName => $columnDefinition) { + foreach ($this->tcaSchemaFactory->get($tableName)->getFields() as $columnName => $columnDefinition) { $propertyName = $fieldNameToPropertyNameMapping[$columnName] ?? GeneralUtility::underscoredToLowerCamelCase($columnName); $dataMap->addColumnMap( $propertyName, $this->columnMapFactory->create( - $columnName, $columnDefinition, $propertyName, $className @@ -183,62 +188,55 @@ class DataMapFactory implements SingletonInterface return $tableName; } - /** - * Returns the TCA columns array of the specified table - * - * @param string $tableName An optional table name to fetch the columns definition from - * @return array The TCA columns definition - */ - protected function getColumnsDefinition(string $tableName): array - { - return is_array($GLOBALS['TCA'][$tableName]['columns'] ?? null) ? $GLOBALS['TCA'][$tableName]['columns'] : []; - } - protected function addMetaDataColumnNames(DataMap $dataMap, string $tableName): DataMap { - $controlSection = $GLOBALS['TCA'][$tableName]['ctrl'] ?? null; - if ($controlSection === null) { + if (!$this->tcaSchemaFactory->has($tableName)) { return $dataMap; } + $schema = $this->tcaSchemaFactory->get($tableName); $dataMap->setPageIdColumnName('pid'); - if (isset($controlSection['tstamp'])) { - $dataMap->setModificationDateColumnName($controlSection['tstamp']); + if ($schema->hasCapability(TcaSchemaCapability::UpdatedAt)) { + $dataMap->setModificationDateColumnName((string)$schema->getCapability(TcaSchemaCapability::UpdatedAt)); } - if (isset($controlSection['crdate'])) { - $dataMap->setCreationDateColumnName($controlSection['crdate']); + if ($schema->hasCapability(TcaSchemaCapability::CreatedAt)) { + $dataMap->setCreationDateColumnName((string)$schema->getCapability(TcaSchemaCapability::CreatedAt)); } - if (isset($controlSection['delete'])) { - $dataMap->setDeletedFlagColumnName($controlSection['delete']); + if ($schema->hasCapability(TcaSchemaCapability::SoftDelete)) { + $dataMap->setDeletedFlagColumnName((string)$schema->getCapability(TcaSchemaCapability::SoftDelete)); } - if (isset($controlSection['languageField'])) { - $dataMap->setLanguageIdColumnName($controlSection['languageField']); - } - if (isset($controlSection['transOrigPointerField'])) { - $dataMap->setTranslationOriginColumnName($controlSection['transOrigPointerField']); + if ($schema->hasCapability(TcaSchemaCapability::Language)) { + $languageCapability = $schema->getCapability(TcaSchemaCapability::Language); + $dataMap->setLanguageIdColumnName($languageCapability->getLanguageField()->getName()); + $dataMap->setTranslationOriginColumnName($languageCapability->getTranslationOriginPointerField()->getName()); + if ($languageCapability->hasDiffSourceField()) { + $dataMap->setTranslationOriginDiffSourceName($languageCapability->getDiffSourceField()->getName()); + } } - if (isset($controlSection['transOrigDiffSourceField'])) { - $dataMap->setTranslationOriginDiffSourceName($controlSection['transOrigDiffSourceField']); + if ($schema->getSubSchemaDivisorField() !== null) { + $dataMap->setRecordTypeColumnName($schema->getSubSchemaDivisorField()->getName()); } - if (isset($controlSection['type'])) { - $dataMap->setRecordTypeColumnName($controlSection['type']); + if ($schema->hasCapability(TcaSchemaCapability::RestrictionRootLevel)) { + // @todo Evaluate if this is correct. We currently have to use canExistOnPages() to keep previous + // behaviour, which is (bool)$rootlevel, so treating "-1" and "1" as TURE, and only 0 als FALSE. + $dataMap->setRootLevel($schema->getCapability(TcaSchemaCapability::RestrictionRootLevel)->canExistOnPages()); } - if (isset($controlSection['rootLevel'])) { - $dataMap->setRootLevel($controlSection['rootLevel']); + if (isset($schema->getRawConfiguration()['is_static'])) { + $dataMap->setIsStatic($schema->getRawConfiguration()['is_static']); } - if (isset($controlSection['is_static'])) { - $dataMap->setIsStatic($controlSection['is_static']); + if ($schema->hasCapability(TcaSchemaCapability::RestrictionDisabledField)) { + $dataMap->setDisabledFlagColumnName((string)$schema->getCapability(TcaSchemaCapability::RestrictionDisabledField)); } - if (isset($controlSection['enablecolumns']['disabled'])) { - $dataMap->setDisabledFlagColumnName($controlSection['enablecolumns']['disabled']); + if ($schema->hasCapability(TcaSchemaCapability::RestrictionStartTime)) { + $dataMap->setDisabledFlagColumnName((string)$schema->getCapability(TcaSchemaCapability::RestrictionStartTime)); } - if (isset($controlSection['enablecolumns']['starttime'])) { - $dataMap->setStartTimeColumnName($controlSection['enablecolumns']['starttime']); + if ($schema->hasCapability(TcaSchemaCapability::RestrictionEndTime)) { + $dataMap->setDisabledFlagColumnName((string)$schema->getCapability(TcaSchemaCapability::RestrictionEndTime)); } - if (isset($controlSection['enablecolumns']['endtime'])) { - $dataMap->setEndTimeColumnName($controlSection['enablecolumns']['endtime']); + if ($schema->hasCapability(TcaSchemaCapability::RestrictionUserGroup)) { + $dataMap->setDisabledFlagColumnName((string)$schema->getCapability(TcaSchemaCapability::RestrictionUserGroup)); } - if (isset($controlSection['enablecolumns']['fe_group'])) { - $dataMap->setFrontEndUserGroupColumnName($controlSection['enablecolumns']['fe_group']); + if ($schema->hasCapability(TcaSchemaCapability::RestrictionDisabledField)) { + $dataMap->setDisabledFlagColumnName((string)$schema->getCapability(TcaSchemaCapability::RestrictionDisabledField)); } return $dataMap; } diff --git a/typo3/sysext/extbase/Tests/Functional/Fixtures/Extensions/test_data_mapper/Classes/Domain/Model/Example.php b/typo3/sysext/extbase/Tests/Functional/Fixtures/Extensions/test_data_mapper/Classes/Domain/Model/Example.php index 79bfec7b923a8ce5ef27be15a84f3a0201f15ab1..2af05a5f2911318c7da8c5c2e535a7bead7384ff 100644 --- a/typo3/sysext/extbase/Tests/Functional/Fixtures/Extensions/test_data_mapper/Classes/Domain/Model/Example.php +++ b/typo3/sysext/extbase/Tests/Functional/Fixtures/Extensions/test_data_mapper/Classes/Domain/Model/Example.php @@ -29,6 +29,9 @@ class Example extends AbstractEntity protected ?\DateTime $uninitializedDateTimeProperty; protected \DateTime $uninitializedMandatoryDateTimeProperty; protected ?\DateTime $initializedDateTimeProperty = null; + protected ?\DateTime $initializedDateTimePropertyDate = null; + protected ?\DateTime $initializedDateTimePropertyDatetime = null; + protected ?\DateTime $initializedDateTimePropertyTime = null; protected ?CustomDateTime $customDateTime = null; public $unknownType; public Enum\StringBackedEnum $stringBackedEnum; @@ -116,6 +119,36 @@ class Example extends AbstractEntity $this->initializedDateTimeProperty = $initializedDateTimeProperty; } + public function getInitializedDateTimePropertyDate(): ?\DateTime + { + return $this->initializedDateTimePropertyDate; + } + + public function setInitializedDateTimePropertyDate(?\DateTime $initializedDateTimePropertyDate): void + { + $this->initializedDateTimePropertyDate = $initializedDateTimePropertyDate; + } + + public function getInitializedDateTimePropertyDatetime(): ?\DateTime + { + return $this->initializedDateTimePropertyDatetime; + } + + public function setInitializedDateTimePropertyDatetime(?\DateTime $initializedDateTimePropertyDatetime): void + { + $this->initializedDateTimePropertyDatetime = $initializedDateTimePropertyDatetime; + } + + public function getInitializedDateTimePropertyTime(): ?\DateTime + { + return $this->initializedDateTimePropertyTime; + } + + public function setInitializedDateTimePropertyTime(?\DateTime $initializedDateTimePropertyTime): void + { + $this->initializedDateTimePropertyTime = $initializedDateTimePropertyTime; + } + public function getCustomDateTime(): ?CustomDateTime { return $this->customDateTime; diff --git a/typo3/sysext/extbase/Tests/Functional/Fixtures/Extensions/test_data_mapper/Configuration/TCA/tx_testdatamapper_domain_model_example.php b/typo3/sysext/extbase/Tests/Functional/Fixtures/Extensions/test_data_mapper/Configuration/TCA/tx_testdatamapper_domain_model_example.php index 68adf7331f0775bb0ab31351002edcd2c3a8282d..86d5faa70035392a12134f8657164b7632f4de30 100644 --- a/typo3/sysext/extbase/Tests/Functional/Fixtures/Extensions/test_data_mapper/Configuration/TCA/tx_testdatamapper_domain_model_example.php +++ b/typo3/sysext/extbase/Tests/Functional/Fixtures/Extensions/test_data_mapper/Configuration/TCA/tx_testdatamapper_domain_model_example.php @@ -43,6 +43,24 @@ return [ 'type' => 'datetime', ], ], + 'initialized_date_time_property_date' => [ + 'config' => [ + 'type' => 'datetime', + 'dbType' => 'date', + ], + ], + 'initialized_date_time_property_datetime' => [ + 'config' => [ + 'type' => 'datetime', + 'dbType' => 'datetime', + ], + ], + 'initialized_date_time_property_time' => [ + 'config' => [ + 'type' => 'datetime', + 'dbType' => 'time', + ], + ], 'custom_date_time' => [ 'config' => [ 'type' => 'datetime', diff --git a/typo3/sysext/extbase/Tests/Functional/Persistence/Generic/Mapper/ColumnMapFactoryTest.php b/typo3/sysext/extbase/Tests/Functional/Persistence/Generic/Mapper/ColumnMapFactoryTest.php index d849d6da1fa058a8f0d8806e85e8e857b5fb99ba..7083e0859a7c7056aee99809d923764463a33748 100644 --- a/typo3/sysext/extbase/Tests/Functional/Persistence/Generic/Mapper/ColumnMapFactoryTest.php +++ b/typo3/sysext/extbase/Tests/Functional/Persistence/Generic/Mapper/ColumnMapFactoryTest.php @@ -20,6 +20,8 @@ namespace TYPO3\CMS\Extbase\Tests\Functional\Persistence\Generic\Mapper; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\Test; use TYPO3\CMS\Core\DataHandling\TableColumnType; +use TYPO3\CMS\Core\Schema\FieldTypeFactory; +use TYPO3\CMS\Core\Schema\RelationMap; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap; use TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap\Relation; @@ -32,11 +34,13 @@ final class ColumnMapFactoryTest extends FunctionalTestCase protected bool $initializeDatabase = false; protected ColumnMapFactory $columnMapFactory; + protected FieldTypeFactory $fieldTypeFactory; protected function setUp(): void { parent::setUp(); $this->columnMapFactory = $this->get(ColumnMapFactory::class); + $this->fieldTypeFactory = $this->get(FieldTypeFactory::class); } public static function createWithGroupTypeDataProvider(): \Generator @@ -97,7 +101,7 @@ final class ColumnMapFactoryTest extends FunctionalTestCase { self::assertEquals( $expectedColumnMap, - $this->columnMapFactory->create($columnName, $columnConfiguration, $propertyName, ColumnMapFactoryEntityFixture::class) + $this->columnMapFactory->create($this->fieldTypeFactory->createFieldType($columnName, $columnConfiguration, 'virtual', new RelationMap()), $propertyName, ColumnMapFactoryEntityFixture::class) ); } @@ -219,7 +223,7 @@ final class ColumnMapFactoryTest extends FunctionalTestCase { self::assertEquals( $expectedColumnMap, - $this->columnMapFactory->create($columnName, $columnConfiguration, $propertyName, ColumnMapFactoryEntityFixture::class) + $this->columnMapFactory->create($this->fieldTypeFactory->createFieldType($columnName, $columnConfiguration, 'virtual', new RelationMap()), $propertyName, ColumnMapFactoryEntityFixture::class) ); } @@ -248,7 +252,7 @@ final class ColumnMapFactoryTest extends FunctionalTestCase { self::assertEquals( $expectedColumnMap, - $this->columnMapFactory->create($columnName, $columnConfiguration, $propertyName, ColumnMapFactoryEntityFixture::class) + $this->columnMapFactory->create($this->fieldTypeFactory->createFieldType($columnName, $columnConfiguration, 'virtual', new RelationMap()), $propertyName, ColumnMapFactoryEntityFixture::class) ); } @@ -284,7 +288,7 @@ final class ColumnMapFactoryTest extends FunctionalTestCase { self::assertEquals( $expectedColumnMap, - $this->columnMapFactory->create($columnName, $columnConfiguration, $propertyName, ColumnMapFactoryEntityFixture::class) + $this->columnMapFactory->create($this->fieldTypeFactory->createFieldType($columnName, $columnConfiguration, 'virtual', new RelationMap()), $propertyName, ColumnMapFactoryEntityFixture::class) ); } @@ -304,8 +308,7 @@ final class ColumnMapFactoryTest extends FunctionalTestCase ], ]; - $columnMap = $this->columnMapFactory->create($columnName, $columnConfiguration, $propertyName, ColumnMapFactoryEntityFixture::class); - + $columnMap = $this->columnMapFactory->create($this->fieldTypeFactory->createFieldType($columnName, $columnConfiguration, 'virtual', new RelationMap()), $propertyName, ColumnMapFactoryEntityFixture::class); self::assertSame( [ 'fieldname' => 'foo_model', @@ -330,8 +333,7 @@ final class ColumnMapFactoryTest extends FunctionalTestCase ], ]; - $columnMap = $this->columnMapFactory->create($columnName, $columnConfiguration, $propertyName, ColumnMapFactoryEntityFixture::class); - + $columnMap = $this->columnMapFactory->create($this->fieldTypeFactory->createFieldType($columnName, $columnConfiguration, 'virtual', new RelationMap()), $propertyName, ColumnMapFactoryEntityFixture::class); self::assertSame( [ 'fieldname' => 'foo_model', @@ -364,7 +366,8 @@ final class ColumnMapFactoryTest extends FunctionalTestCase ], ]; - self::assertEquals($expectedColumnMap, $this->columnMapFactory->create($columnName, $columnConfiguration, $propertyName, ColumnMapFactoryEntityFixture::class)); + $columnMap = $this->columnMapFactory->create($this->fieldTypeFactory->createFieldType($columnName, $columnConfiguration, 'virtual', new RelationMap()), $propertyName, ColumnMapFactoryEntityFixture::class); + self::assertEquals($expectedColumnMap, $columnMap); } #[Test] @@ -390,7 +393,8 @@ final class ColumnMapFactoryTest extends FunctionalTestCase ], ]; - self::assertEquals($expectedColumnMap, $this->columnMapFactory->create($columnName, $columnConfiguration, $propertyName, ColumnMapFactoryEntityFixture::class)); + $columnMap = $this->columnMapFactory->create($this->fieldTypeFactory->createFieldType($columnName, $columnConfiguration, 'virtual', new RelationMap()), $propertyName, ColumnMapFactoryEntityFixture::class); + self::assertEquals($expectedColumnMap, $columnMap); } #[Test] @@ -416,7 +420,8 @@ final class ColumnMapFactoryTest extends FunctionalTestCase ], ]; - self::assertEquals($expectedColumnMap, $this->columnMapFactory->create($columnName, $columnConfiguration, $propertyName, ColumnMapFactoryEntityFixture::class)); + $columnMap = $this->columnMapFactory->create($this->fieldTypeFactory->createFieldType($columnName, $columnConfiguration, 'virtual', new RelationMap()), $propertyName, ColumnMapFactoryEntityFixture::class); + self::assertEquals($expectedColumnMap, $columnMap); } public static function columnMapIsInitializedWithFieldEvaluationsForDateTimeFieldsDataProvider(): array @@ -442,8 +447,7 @@ final class ColumnMapFactoryTest extends FunctionalTestCase ], ]; - $columnMap = $this->columnMapFactory->create($columnName, $columnConfiguration, $propertyName, ColumnMapFactoryEntityFixture::class); - + $columnMap = $this->columnMapFactory->create($this->fieldTypeFactory->createFieldType($columnName, $columnConfiguration, 'virtual', new RelationMap()), $propertyName, ColumnMapFactoryEntityFixture::class); self::assertSame($expectedValue, $columnMap->getDateTimeStorageFormat()); } @@ -490,8 +494,7 @@ final class ColumnMapFactoryTest extends FunctionalTestCase ], ]; - $columnMap = $this->columnMapFactory->create($columnName, $columnConfiguration, $propertyName, ColumnMapFactoryEntityFixture::class); - + $columnMap = $this->columnMapFactory->create($this->fieldTypeFactory->createFieldType($columnName, $columnConfiguration, 'virtual', new RelationMap()), $propertyName, ColumnMapFactoryEntityFixture::class); self::assertSame($expectedType, $columnMap->getType()); } } diff --git a/typo3/sysext/extbase/Tests/Functional/Persistence/Generic/Mapper/DataMapperTest.php b/typo3/sysext/extbase/Tests/Functional/Persistence/Generic/Mapper/DataMapperTest.php index d2c9b0477e78b24db3ba09cf28be69c66fdc5963..a6ea93436daaa8f3a76c4ea7c3f7436499db03b6 100644 --- a/typo3/sysext/extbase/Tests/Functional/Persistence/Generic/Mapper/DataMapperTest.php +++ b/typo3/sysext/extbase/Tests/Functional/Persistence/Generic/Mapper/DataMapperTest.php @@ -367,17 +367,17 @@ final class DataMapperTest extends FunctionalTestCase #[Test] public function mapDateTimeHandlesDifferentFieldEvaluations(string|int|null $value, ?string $storageFormat, ?string $expectedValue): void { - $GLOBALS['TCA']['tx_testdatamapper_domain_model_example']['columns']['initialized_date_time_property']['config']['dbType'] = $storageFormat; $rows = [ [ 'uid' => 123, - 'initialized_date_time_property' => $value, + 'initialized_date_time_property' . ($storageFormat !== null ? '_' . $storageFormat : '') => $value, ], ]; $dataMapper = $this->get(DataMapper::class); $mappedObjectsArray = $dataMapper->map(Example::class, $rows); - self::assertSame($expectedValue, $mappedObjectsArray[0]->getInitializedDateTimeProperty()?->format('c')); + $getter = 'getInitializedDateTimeProperty' . ($storageFormat !== null ? ucfirst($storageFormat) : ''); + self::assertSame($expectedValue, $mappedObjectsArray[0]->{$getter}()?->format('c')); // Flush DataMapFactory cache on each run. $cacheManager = $this->get(CacheManager::class); @@ -404,18 +404,18 @@ final class DataMapperTest extends FunctionalTestCase date_default_timezone_set('America/Chicago'); $usedTimeZone = date_default_timezone_get(); - $GLOBALS['TCA']['tx_testdatamapper_domain_model_example']['columns']['initialized_date_time_property']['config']['dbType'] = $storageFormat; $rows = [ [ 'uid' => 123, - 'initialized_date_time_property' => $value, + 'initialized_date_time_property' . ($storageFormat !== null ? '_' . $storageFormat : '') => $value, ], ]; $dataMapper = $this->get(DataMapper::class); $mappedObjectsArray = $dataMapper->map(Example::class, $rows); + $getter = 'getInitializedDateTimeProperty' . ($storageFormat !== null ? ucfirst($storageFormat) : ''); $expectedValue = $expectedValue !== null ? new \DateTime($expectedValue, new \DateTimeZone($usedTimeZone)) : $expectedValue; - self::assertEquals($expectedValue, $mappedObjectsArray[0]->getInitializedDateTimeProperty()); + self::assertEquals($expectedValue, $mappedObjectsArray[0]->{$getter}()); // Flush DataMapFactory cache on each run. $cacheManager = $this->get(CacheManager::class); diff --git a/typo3/sysext/extbase/Tests/Unit/Persistence/Generic/Mapper/DataMapperTest.php b/typo3/sysext/extbase/Tests/Unit/Persistence/Generic/Mapper/DataMapperTest.php index 58e530646455ee091b61cf52e73e65c87f93356a..eeb59ec5529c541835645d4517607fded8e1be6a 100644 --- a/typo3/sysext/extbase/Tests/Unit/Persistence/Generic/Mapper/DataMapperTest.php +++ b/typo3/sysext/extbase/Tests/Unit/Persistence/Generic/Mapper/DataMapperTest.php @@ -21,6 +21,8 @@ use Doctrine\Instantiator\InstantiatorInterface; use PHPUnit\Framework\Attributes\Test; use Psr\EventDispatcher\EventDispatcherInterface; use TYPO3\CMS\Core\Cache\CacheManager; +use TYPO3\CMS\Core\Schema\Field\SelectRelationFieldType; +use TYPO3\CMS\Core\Schema\TcaSchemaFactory; use TYPO3\CMS\Extbase\Configuration\ConfigurationManager; use TYPO3\CMS\Extbase\Persistence\ClassesConfiguration; use TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap; @@ -58,6 +60,7 @@ final class DataMapperTest extends UnitTestCase $this->createMock(CacheManager::class), $this->createMock(ClassesConfiguration::class), $this->columnMapFactory, + $this->createMock(TcaSchemaFactory::class), 'foo' ); @@ -78,9 +81,7 @@ final class DataMapperTest extends UnitTestCase // Arrange $this->columnMapFactory->setOneToManyRelation( $this->columnMap, - [ - 'foreign_table' => 'tx_myextension_bar', - ] + new SelectRelationFieldType('foo', ['foreign_table' => 'tx_myextension_bar'], []) ); // Act @@ -96,10 +97,7 @@ final class DataMapperTest extends UnitTestCase // Arrange $this->columnMapFactory->setOneToManyRelation( $this->columnMap, - [ - 'foreign_table' => 'tx_myextension_bar', - 'foreign_default_sortby' => '', - ] + new SelectRelationFieldType('foo', ['foreign_table' => 'tx_myextension_bar', 'foreign_default_sortby' => ''], []) ); // Act @@ -115,10 +113,7 @@ final class DataMapperTest extends UnitTestCase // Arrange $this->columnMapFactory->setOneToManyRelation( $this->columnMap, - [ - 'foreign_table' => 'tx_myextension_bar', - 'foreign_default_sortby' => 'pid invalid', - ] + new SelectRelationFieldType('foo', ['foreign_table' => 'tx_myextension_bar', 'foreign_default_sortby' => 'pid invalid'], []) ); // Act @@ -137,10 +132,7 @@ final class DataMapperTest extends UnitTestCase // Arrange $this->columnMapFactory->setOneToManyRelation( $this->columnMap, - [ - 'foreign_table' => 'tx_myextension_bar', - 'foreign_sortby' => 'uid', - ] + new SelectRelationFieldType('foo', ['foreign_table' => 'tx_myextension_bar', 'foreign_sortby' => 'uid'], []) ); // Act @@ -159,11 +151,7 @@ final class DataMapperTest extends UnitTestCase // Arrange $this->columnMapFactory->setOneToManyRelation( $this->columnMap, - [ - 'foreign_table' => 'tx_myextension_bar', - 'foreign_sortby' => 'uid', - 'foreign_default_sortby' => 'pid', - ] + new SelectRelationFieldType('foo', ['foreign_table' => 'tx_myextension_bar', 'foreign_sortby' => 'uid', 'foreign_default_sortby' => 'pid'], []) ); // Act @@ -182,10 +170,7 @@ final class DataMapperTest extends UnitTestCase // Arrange $this->columnMapFactory->setOneToManyRelation( $this->columnMap, - [ - 'foreign_table' => 'tx_myextension_bar', - 'foreign_default_sortby' => 'pid', - ] + new SelectRelationFieldType('foo', ['foreign_table' => 'tx_myextension_bar', 'foreign_default_sortby' => 'pid'], []) ); // Act @@ -204,10 +189,7 @@ final class DataMapperTest extends UnitTestCase // Arrange $this->columnMapFactory->setOneToManyRelation( $this->columnMap, - [ - 'foreign_table' => 'tx_myextension_bar', - 'foreign_default_sortby' => 'pid desc', - ] + new SelectRelationFieldType('foo', ['foreign_table' => 'tx_myextension_bar', 'foreign_default_sortby' => 'pid desc'], []) ); // Act @@ -226,10 +208,7 @@ final class DataMapperTest extends UnitTestCase // Arrange $this->columnMapFactory->setOneToManyRelation( $this->columnMap, - [ - 'foreign_table' => 'tx_myextension_bar', - 'foreign_default_sortby' => 'pid desc, title, uid asc', - ] + new SelectRelationFieldType('foo', ['foreign_table' => 'tx_myextension_bar', 'foreign_default_sortby' => 'pid desc, title, uid asc'], []) ); // Act