diff --git a/Classes/DataProcessing/ContentBlockData.php b/Classes/DataProcessing/ContentBlockData.php index f197f19c..8746a9b4 100644 --- a/Classes/DataProcessing/ContentBlockData.php +++ b/Classes/DataProcessing/ContentBlockData.php @@ -17,6 +17,14 @@ namespace TYPO3\CMS\ContentBlocks\DataProcessing; +use TYPO3\CMS\Core\Domain\RawRecord; +use TYPO3\CMS\Core\Domain\Record; +use TYPO3\CMS\Core\Domain\Record\ComputedProperties; +use TYPO3\CMS\Core\Domain\Record\LanguageInfo; +use TYPO3\CMS\Core\Domain\Record\SystemProperties; +use TYPO3\CMS\Core\Domain\Record\VersionInfo; +use TYPO3\CMS\Core\Domain\RecordInterface; + /** * This class represents the `data` object inside the Fluid template for Content Blocks. * @@ -44,47 +52,142 @@ * - {data.originalUid} * - {data.originalPid} * - * To access the raw database record use: - * - {data._raw.some_field} - * * @internal This is not public TYPO3 PHP API. Only to be used inside of Fluid templates by accessing as variable. */ -final class ContentBlockData extends \stdClass +final class ContentBlockData implements \ArrayAccess, RecordInterface { - /** - * This is a hint for f:debug users. - */ - public string $_debug_hint = 'To access data under `_processed` you must omit the key: {data.identifier}'; - public function __construct( - private string $_name = '', - private array $_raw = [], + protected ?Record $_record = null, + protected string $_name = '', /** @var array|array */ - private array $_grids = [], - private array $_processed = [], + protected array $_grids = [], + protected array $_processed = [], ) {} - public function __get(string $name = ''): mixed + public function getUid(): int + { + return $this->_record->getUid(); + } + + public function getPid(): int + { + return $this->_record->getPid(); + } + + public function getFullType(): string + { + return $this->_record->getFullType(); + } + + public function getRecordType(): ?string { - if ($name === '_name') { + return $this->_record->getRecordType(); + } + + public function getMainType(): string + { + return $this->_record->getMainType(); + } + + public function toArray(bool $includeSpecialProperties = false): array + { + return $this->_record->toArray($includeSpecialProperties); + } + + public function offsetExists(mixed $offset): bool + { + if ($offset === '_name') { + return true; + } + if ($offset === '_raw') { + return true; + } + if ($offset === '_grids') { + return true; + } + if (array_key_exists($offset, $this->_processed)) { + return true; + } + return $this->_record->offsetExists($offset); + } + + public function offsetGet(mixed $offset): mixed + { + if ($offset === '_name') { return $this->_name; } - if ($name === '_raw') { - return $this->_raw; + if ($offset === '_raw') { + return $this->toArray(true); } - if ($name === '_grids') { + if ($offset === '_grids') { return $this->_grids; } - if (array_key_exists($name, $this->_processed)) { - return $this->_processed[$name]; + if (array_key_exists($offset, $this->_processed)) { + return $this->_processed[$offset]; } - return null; + return $this->_record->offsetGet($offset); + } + + public function offsetSet(mixed $offset, mixed $value): void + { + $this->_processed[$offset] = $value; + } + + public function offsetUnset(mixed $offset): void + { + unset($this->_processed[$offset]); + } + + public function getVersionInfo(): ?VersionInfo + { + return $this->_record->getVersionInfo(); + } + + public function getLanguageInfo(): ?LanguageInfo + { + return $this->_record->getLanguageInfo(); + } + + public function getLanguageId(): ?int + { + return $this->_record->getLanguageId(); + } + + public function getSystemProperties(): ?SystemProperties + { + return $this->_record->getSystemProperties(); + } + + public function getComputedProperties(): ComputedProperties + { + return $this->_record->getComputedProperties(); + } + + public function getRawRecord(): RawRecord + { + return $this->_record->getRawRecord(); + } + + public function getOverlaidUid(): int + { + return $this->_record->getOverlaidUid(); + } + + public function get_Name(): string + { + return $this->_name; + } + + public function get_Grids(): array + { + return $this->_grids; } public function override(ContentBlockData $contentBlockData): void { - foreach (get_object_vars($contentBlockData) as $var => $value) { - $this->$var = $contentBlockData->$var; - } + $this->_record = $contentBlockData->_record; + $this->_name = $contentBlockData->_name; + $this->_grids = $contentBlockData->_grids; + $this->_processed = $contentBlockData->_processed; } } diff --git a/Classes/DataProcessing/ContentBlockDataDecorator.php b/Classes/DataProcessing/ContentBlockDataDecorator.php index 7c6ad0d7..f9a8ba79 100644 --- a/Classes/DataProcessing/ContentBlockDataDecorator.php +++ b/Classes/DataProcessing/ContentBlockDataDecorator.php @@ -24,7 +24,7 @@ use TYPO3\CMS\ContentBlocks\Definition\TableDefinitionCollection; use TYPO3\CMS\ContentBlocks\Definition\TcaFieldDefinition; use TYPO3\CMS\ContentBlocks\FieldType\FieldType; -use TYPO3\CMS\Core\Schema\TcaSchemaFactory; +use TYPO3\CMS\Core\Domain\RecordFactory; /** * @internal Not part of TYPO3's public API. @@ -35,10 +35,10 @@ final class ContentBlockDataDecorator public function __construct( private readonly TableDefinitionCollection $tableDefinitionCollection, - private readonly TcaSchemaFactory $tcaSchemaFactory, private readonly ContentBlockDataDecoratorSession $contentBlockDataDecoratorSession, private readonly GridProcessor $gridProcessor, private readonly ContentObjectProcessor $contentObjectProcessor, + private readonly RecordFactory $recordFactory, ) {} public function setRequest(ServerRequestInterface $request): void @@ -55,8 +55,9 @@ public function decorate( ): ContentBlockData { $identifier = $this->getRecordIdentifier($resolvedRelation->table, $resolvedRelation->raw); $this->contentBlockDataDecoratorSession->addContentBlockData($identifier, new ContentBlockData()); + $record = $this->recordFactory->createFromDatabaseRow($resolvedRelation->table, $resolvedRelation->raw); $resolvedContentBlockDataRelation = new ResolvedContentBlockDataRelation(); - $resolvedContentBlockDataRelation->raw = $resolvedRelation->raw; + $resolvedContentBlockDataRelation->record = $record; $resolvedContentBlockDataRelation->resolved = $resolvedRelation->resolved; $contentBlockData = $this->buildContentBlockDataObjectRecursive( $contentTypeDefinition, @@ -102,9 +103,6 @@ private function buildContentBlockDataObjectRecursive( $resolvedRelation->resolved = $processedContentBlockData; $contentBlockDataObject = $this->buildContentBlockDataObject( $resolvedRelation, - $tableDefinition->getTable(), - $tableDefinition->getTypeField(), - $contentTypeDefinition->getTypeName(), $contentTypeDefinition->getName(), $grids, ); @@ -243,7 +241,8 @@ private function transformSingleRelation( ): ContentBlockData { $contentBlockRelation = new ResolvedContentBlockDataRelation(); $foreignTable = $item->table; - $contentBlockRelation->raw = $item->raw; + $record = $this->recordFactory->createFromDatabaseRow($item->table, $item->raw); + $contentBlockRelation->record = $record; $contentBlockRelation->resolved = $item->resolved; $hasTableDefinition = $this->tableDefinitionCollection->hasTable($foreignTable); $collectionTableDefinition = null; @@ -252,10 +251,10 @@ private function transformSingleRelation( } $typeDefinition = null; if ($hasTableDefinition) { - $typeDefinition = ContentTypeResolver::resolve($collectionTableDefinition, $contentBlockRelation->raw); + $typeDefinition = ContentTypeResolver::resolve($collectionTableDefinition, $contentBlockRelation->record->toArray()); } if ($collectionTableDefinition !== null && $typeDefinition !== null) { - $identifier = $this->getRecordIdentifier($foreignTable, $contentBlockRelation->raw); + $identifier = $this->getRecordIdentifier($foreignTable, $contentBlockRelation->record->toArray()); if ($this->contentBlockDataDecoratorSession->hasContentBlockData($identifier)) { $contentBlockData = $this->contentBlockDataDecoratorSession->getContentBlockData($identifier); return $contentBlockData; @@ -271,7 +270,7 @@ private function transformSingleRelation( $this->contentBlockDataDecoratorSession->setContentBlockData($identifier, $contentBlockData); return $contentBlockData; } - $contentBlockData = $this->buildFakeContentBlockDataObject($foreignTable, $contentBlockRelation); + $contentBlockData = $this->buildFakeContentBlockDataObject($contentBlockRelation); return $contentBlockData; } @@ -280,79 +279,24 @@ private function transformSingleRelation( */ private function buildContentBlockDataObject( ResolvedContentBlockDataRelation $resolvedRelation, - string $table, - ?string $typeField, - string|int $typeName, string $name = '', array $grids = [], ): ContentBlockData { - $rawData = $resolvedRelation->raw; $resolvedData = $resolvedRelation->resolved; - $baseData = [ - 'uid' => $rawData['uid'], - 'pid' => $rawData['pid'], - 'tableName' => $table, - 'typeName' => $typeName, - ]; - // Duplicates typeName, but needed for Fluid Styled Content layout integration. - if ($typeField !== null) { - $baseData[$typeField] = $typeName; - } - if (array_key_exists('sys_language_uid', $rawData)) { - $baseData['languageId'] = $rawData['sys_language_uid']; - } - if (array_key_exists('tstamp', $rawData)) { - $baseData['updateDate'] = $rawData['tstamp']; - } - if (array_key_exists('crdate', $rawData)) { - $baseData['creationDate'] = $rawData['crdate']; - } - $baseData = $this->enrichBaseDataWithComputedProperties($baseData, $rawData); - $contentBlockDataArray = $baseData + $resolvedData; - $contentBlockData = new ContentBlockData($name, $rawData, $grids, $contentBlockDataArray); - - // Add dynamic fields so that Fluid can detect them with `property_exists()`. - foreach ($baseData as $key => $baseDataItem) { - $contentBlockData->$key = $baseDataItem; - } - foreach ($resolvedData as $key => $processedContentBlockDataItem) { - $contentBlockData->$key = $processedContentBlockDataItem; - } + $contentBlockData = new ContentBlockData($resolvedRelation->record, $name, $grids, $resolvedData); return $contentBlockData; } - private function enrichBaseDataWithComputedProperties(array $baseData, array $data): array - { - $computedProperties = [ - 'localizedUid' => '_LOCALIZED_UID', - 'originalUid' => '_ORIG_uid', - 'originalPid' => '_ORIG_pid', - ]; - $baseDataWithComputedProperties = $baseData; - foreach ($computedProperties as $key => $computedProperty) { - if (array_key_exists($computedProperty, $data)) { - $baseDataWithComputedProperties[$key] = $data[$computedProperty]; - } - } - return $baseDataWithComputedProperties; - } - /** * If the record is not defined by Content Blocks, we build a fake * Content Block data object for consistent usage. */ - private function buildFakeContentBlockDataObject(string $table, ResolvedContentBlockDataRelation $resolvedRelation): ContentBlockData + private function buildFakeContentBlockDataObject(ResolvedContentBlockDataRelation $resolvedRelation): ContentBlockData { - $tcaSchema = $this->tcaSchemaFactory->get($table); - $typeField = $tcaSchema->getSubSchemaDivisorField(); - $typeFieldIdentifier = $typeField?->getName(); - $typeName = $typeField !== null ? $resolvedRelation->raw[$typeField->getName()] : '1'; + $typeName = $resolvedRelation->record->getRecordType() !== null ? $resolvedRelation->record->getRecordType() : '1'; $fakeName = 'core/' . $typeName; $contentBlockDataObject = $this->buildContentBlockDataObject( $resolvedRelation, - $table, - $typeFieldIdentifier, - $typeName, $fakeName, ); return $contentBlockDataObject; diff --git a/Classes/DataProcessing/ContentObjectProcessor.php b/Classes/DataProcessing/ContentObjectProcessor.php index 6336b890..7bb48dab 100644 --- a/Classes/DataProcessing/ContentObjectProcessor.php +++ b/Classes/DataProcessing/ContentObjectProcessor.php @@ -62,9 +62,9 @@ public function processContentObject(ContentBlockData $contentBlockData, Rendere $this->session->addRenderedGrid($contentBlockData, new RenderedGridItem()); $frontendTypoScript = $this->request->getAttribute('frontend.typoscript'); $setup = $frontendTypoScript->getSetupArray(); - $table = $contentBlockData->tableName; - $this->contentObjectRenderer->start($contentBlockData->_raw, $table); - $typeName = $contentBlockData->typeName; + $table = $contentBlockData->getMainType(); + $this->contentObjectRenderer->start($contentBlockData->toArray(), $table); + $typeName = $contentBlockData->getRecordType(); $typoScriptObjectPath = $table . '.' . $typeName; $pathSegments = GeneralUtility::trimExplode('.', $typoScriptObjectPath); $lastSegment = (string)array_pop($pathSegments); diff --git a/Classes/DataProcessing/ContentObjectProcessorSession.php b/Classes/DataProcessing/ContentObjectProcessorSession.php index da4fd98d..bdfcf3fb 100644 --- a/Classes/DataProcessing/ContentObjectProcessorSession.php +++ b/Classes/DataProcessing/ContentObjectProcessorSession.php @@ -55,7 +55,7 @@ public function getRenderedGrid(ContentBlockData $contentBlockData): RenderedGri private function createIdentifier(ContentBlockData $contentBlockData): string { - $identifier = $contentBlockData->tableName . $contentBlockData->uid; + $identifier = $contentBlockData->getMainType() . $contentBlockData->getUid(); return $identifier; } } diff --git a/Classes/DataProcessing/GridFactory.php b/Classes/DataProcessing/GridFactory.php index e9c1dca8..978017ed 100644 --- a/Classes/DataProcessing/GridFactory.php +++ b/Classes/DataProcessing/GridFactory.php @@ -41,8 +41,8 @@ public function build(PageLayoutContext $context, string $columnName, array $rec GridColumnItem::class, $context, $column, - $record->_raw, - $record->tableName, + $record->toArray(), + $record->getMainType(), ); $column->addItem($gridColumnItem); } diff --git a/Classes/DataProcessing/ResolvedContentBlockDataRelation.php b/Classes/DataProcessing/ResolvedContentBlockDataRelation.php index a3d6b59f..743f3c4e 100644 --- a/Classes/DataProcessing/ResolvedContentBlockDataRelation.php +++ b/Classes/DataProcessing/ResolvedContentBlockDataRelation.php @@ -17,11 +17,13 @@ namespace TYPO3\CMS\ContentBlocks\DataProcessing; +use TYPO3\CMS\Core\Domain\Record; + /** * @internal Not part of TYPO3's public API. */ final class ResolvedContentBlockDataRelation { - public array $raw = []; + public Record $record; public array $resolved = []; } diff --git a/Documentation/Templating/Index.rst b/Documentation/Templating/Index.rst index 2960f98e..622ebebc 100644 --- a/Documentation/Templating/Index.rst +++ b/Documentation/Templating/Index.rst @@ -14,32 +14,13 @@ Accessing variables Inside your `Frontend.html` or `EditorPreview.html` file you can access the properties of your Content Element as usual by the :html:`{data}` variable. -This variable, however, is special. It has real superpowers. Let's have a look -at the debug output of it: - -.. code-block:: text - - TYPO3\CMS\ContentBlocks\DataProcessing\ContentBlockData [prototype] [object] - _raw => [private] array(85 items) - _processed => [private] array(8 items) - uid => 24 (integer) - pid => 1 (integer) - languageId => 0 (integer) - typeName => 'example_element1' (16 chars) - updateDate => 1694625077 (integer) - creationDate => 1694602137 (integer) - header => 'Foo' (3 chars) - -As you can see, in contrast to the usual array, we are dealing with an object -here. This allows us to magically access our own custom properties very easily. -The object consists of two properties `_raw` and `_processed`. As the names -suggest, the one is raw and unprocessed and the other one has magic applied from -Content Blocks. Normally you would access the processed properties. This is done -by simply accessing the desired property like :html:`{data.header}`. Note, that -we are omitting `_processed` here. This is important to remember, as this would -access a custom field named `_processed`. On the other hand, the raw properties -have to be accessed by :html:`{data._raw.some_field}`. But most of the time you -shouldn't need them. +This variable, however, is special. It has real superpowers! + +In contrast to the usual array, we are dealing with an object here. This allows +us to magically access our own custom properties very easily. Normally you would +access the processed properties. This is done by simply accessing the desired +property like :html:`{data.header}`. The raw properties have to be accessed by +:html:`{data._raw.some_field}`. But most of the time you shouldn't need them. All fields with relations are resolved automatically to an array. This includes `Collection`, `Select`, `Relation`, `File`, `Folder`, `Category` and `FlexForm` @@ -50,8 +31,10 @@ Have a look at this code example to grasp what's possible: .. code-block:: html - - {data.my_field} + + {data.title} + {data.uid} + {data.pid} {item.title} @@ -63,24 +46,61 @@ Have a look at this code example to grasp what's possible: - - {data.uid} - {data.pid} - {data.typeName} - - - {data.languageId} - {data.creationDate} - {data.updateDate} - - - {data.localizedUid} - {data.originalUid} - {data.originalPid} + + {data.languageId} + {data.languageInfo.translationParent} + {data.languageInfo.translationSource} + + + {data.overlaidUid} + + + + + + {data.mainType} + + + {data.recordType} + + + {data.fullType} + + + {data.systemProperties.isDeleted} + {data.systemProperties.isDisabled} + {data.systemProperties.isLockedForEditing} + {data.systemProperties.createdAt} + {data.systemProperties.lastUpdatedAt} + {data.systemProperties.publishAt} + {data.systemProperties.publishUntil} + {data.systemProperties.userGroupRestriction} + {data.systemProperties.sorting} + {data.systemProperties.description} + + + {data.computedProperties.versionedUid} + {data.computedProperties.localizedUid} + {data.computedProperties.requestedOverlayLanguageId} + {data.computedProperties.translationSource} + + + {data.versionInfo.workspaceId} + {data.versionInfo.liveId} + {data.versionInfo.state.name} + {data.versionInfo.state.value} + {data.versionInfo.stageId} {data._raw.some_field} +See also: https://docs.typo3.org/c/typo3/cms-core/main/en-us/Changelog/13.2/Feature-103783-RecordTransformationDataProcessor.html#usage-in-fluid-templates + +.. note:: + + Note that we are omitting `_processed` when accessing properties, even + though you might think this would be correct due to the debug output. + Frontend & backend ================== diff --git a/Tests/Fixtures/Extensions/test_content_blocks_c/ContentBlocks/ContentElements/simple-relation/Source/Frontend.html b/Tests/Fixtures/Extensions/test_content_blocks_c/ContentBlocks/ContentElements/simple-relation/Source/Frontend.html index 8afd334c..8de84e1f 100644 --- a/Tests/Fixtures/Extensions/test_content_blocks_c/ContentBlocks/ContentElements/simple-relation/Source/Frontend.html +++ b/Tests/Fixtures/Extensions/test_content_blocks_c/ContentBlocks/ContentElements/simple-relation/Source/Frontend.html @@ -5,24 +5,24 @@ > -TypeName: {data.record.typeName} +TypeName: {data.record.recordType} Title: {data.record.title} - TypeName: {record.typeName} + TypeName: {record.recordType} Title: {record.title} - TypeName: {record.typeName} + TypeName: {record.recordType} Title Relation: {record.title} - Child typeName: {item.typeName} + Child typeName: {item.recordType} {child.content} @@ -30,7 +30,7 @@ - Mixed Relation: {relation.tableName} + Mixed Relation: {relation.mainType} @@ -52,7 +52,7 @@ - Category table: {category.tableName} + Category table: {category.mainType} {category.title} diff --git a/Tests/Fixtures/Extensions/test_content_blocks_c/ContentBlocks/ContentElements/simple/Source/Frontend.html b/Tests/Fixtures/Extensions/test_content_blocks_c/ContentBlocks/ContentElements/simple/Source/Frontend.html index 45aba544..2679f169 100644 --- a/Tests/Fixtures/Extensions/test_content_blocks_c/ContentBlocks/ContentElements/simple/Source/Frontend.html +++ b/Tests/Fixtures/Extensions/test_content_blocks_c/ContentBlocks/ContentElements/simple/Source/Frontend.html @@ -12,11 +12,11 @@

{data.header}

uid:{data.uid}

pid:{data.pid}

languageId:{data.languageId}

-

typeName:{data.typeName}

+

typeName:{data.recordType}

CType:{data.CType}

-

tableName:{data.tableName}

-

creationDate:{data.creationDate}

-

updateDate:{data.updateDate}

+

tableName:{data.mainType}

+

creationDate:{data.systemProperties.createdAt.timestamp}

+

updateDate:{data.systemProperties.lastUpdatedAt.timestamp}