Skip to content

Commit

Permalink
[TASK] Fix content rendering
Browse files Browse the repository at this point in the history
  • Loading branch information
o-ba committed Apr 11, 2024
1 parent 2bd7607 commit 381a35b
Show file tree
Hide file tree
Showing 18 changed files with 1,003 additions and 12 deletions.
8 changes: 1 addition & 7 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,14 +67,8 @@
},
"extra": {
"patches": {
"typo3/cms-frontend": {
"83638": "composer_patches/typo3-cms-frontend-review-83638.patch"
},
"typo3/cms-core": {
"83638": "composer_patches/typo3-cms-core-review-83638.patch"
},
"typo3/cms-fluid": {
"83638": "composer_patches/typo3-cms-fluid-review-83718.diff"
"cc410ef": "composer_patches/typo3-cms-core-review-cc410ef.diff"
}
}
}
Expand Down
170 changes: 170 additions & 0 deletions composer_patches/typo3-cms-core-review-cc410ef.diff
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
From cc410ef0d44db8d9eb9feb4954cc7039267d4099 Mon Sep 17 00:00:00 2001
From: Benjamin Franzke <[email protected]>
Date: Tue, 09 Apr 2024 17:57:37 +0100
Subject: [PATCH] [TASK] Refine site set default TypoScript handling

Treat site sets (#103439) as a default content rendering template,
in order to load Extbase plugin registrations prior to site sets.

Also add a possibility to opt out from being loaded globally in
site sets in ExtensionManagementUtility::addTypoScriptSetup
and ExtensionManagementUtility::addTypoScriptConstants in order
to reduce global TypoScript in upcoming commits (#103556).

Resolves: #103580
Related: #103439
Related: #103556
Releases: main
Change-Id: Ib9297c775d89f1689410f83e83955d6be829d2e6
---

diff --git a/typo3/sysext/core/Classes/TypoScript/IncludeTree/SysTemplateTreeBuilder.php b/typo3/sysext/core/Classes/TypoScript/IncludeTree/SysTemplateTreeBuilder.php
index 4685703..1eb215e 100644
--- a/typo3/sysext/core/Classes/TypoScript/IncludeTree/SysTemplateTreeBuilder.php
+++ b/typo3/sysext/core/Classes/TypoScript/IncludeTree/SysTemplateTreeBuilder.php
@@ -189,7 +189,8 @@
$includeNode->setRoot(true);
$includeNode->setClear(true);

- $this->addDefaultTypoScriptFromGlobals($includeNode);
+ $this->addScopedStaticsFromGlobals($includeNode, 'siteSets');
+ $this->addContentRenderingFromGlobals($includeNode, 'TYPO3_CONF_VARS defaultContentRendering');

$sets = $this->setRegistry->getSets(...$site->getSets());
if (count($sets) > 0) {
@@ -198,7 +199,6 @@
$includeSetInclude->setPath('site:' . $site->getIdentifier() . '/');
foreach ($sets as $set) {
$this->handleSetInclude($includeSetInclude, rtrim($set->typoscript, '/') . '/', 'set:' . $set->name);
- $this->addStaticMagicFromGlobals($includeSetInclude, 'set:' . $set->name);
}
$includeNode->addChild($includeSetInclude);
}
@@ -534,12 +534,9 @@
$parentConstantNode->addChild($node);
}

- /**
- * A rather weird lookup in $GLOBALS['TYPO3_CONF_VARS']['FE'] for magic includes.
- * See ExtensionManagementUtility::addTypoScript() for more details on this.
- */
- private function addStaticMagicFromGlobals(IncludeInterface $parentNode, string $identifier): void
+ private function addScopedStaticsFromGlobals(IncludeInterface $parentNode, string $identifier): void
{
+ $defaultTypoScriptConstants = $GLOBALS['TYPO3_CONF_VARS']['FE']['defaultTypoScript_' . $this->type] ?? '';
// defaultTypoScript_constants.' or defaultTypoScript_setup.'
$source = $GLOBALS['TYPO3_CONF_VARS']['FE']['defaultTypoScript_' . $this->type . '.'][$identifier] ?? null;
if (!empty($source)) {
@@ -549,16 +546,30 @@
$this->treeFromTokenStreamBuilder->buildTree($node, $this->type, $this->tokenizer);
$parentNode->addChild($node);
}
+ }
+
+ private function addContentRenderingFromGlobals(IncludeInterface $parentNode, string $name): void
+ {
+ $source = $GLOBALS['TYPO3_CONF_VARS']['FE']['defaultTypoScript_' . $this->type . '.']['defaultContentRendering'] ?? null;
+ if (!empty($source)) {
+ $node = new DefaultTypoScriptMagicKeyInclude();
+ $node->setName($name);
+ $node->setLineStream($this->tokenizer->tokenize($source));
+ $this->treeFromTokenStreamBuilder->buildTree($node, $this->type, $this->tokenizer);
+ $parentNode->addChild($node);
+ }
+ }
+
+ /**
+ * A rather weird lookup in $GLOBALS['TYPO3_CONF_VARS']['FE'] for magic includes.
+ * See ExtensionManagementUtility::addTypoScript() for more details on this.
+ */
+ private function addStaticMagicFromGlobals(IncludeInterface $parentNode, string $identifier): void
+ {
+ $this->addScopedStaticsFromGlobals($parentNode, $identifier);
// If this is a template of type "default content rendering", see if other extensions have added their TypoScript that should be included.
if (in_array($identifier, $GLOBALS['TYPO3_CONF_VARS']['FE']['contentRenderingTemplates'], true)) {
- $source = $GLOBALS['TYPO3_CONF_VARS']['FE']['defaultTypoScript_' . $this->type . '.']['defaultContentRendering'] ?? null;
- if (!empty($source)) {
- $node = new DefaultTypoScriptMagicKeyInclude();
- $node->setName('TYPO3_CONF_VARS defaultContentRendering ' . $this->type . ' for ' . $identifier);
- $node->setLineStream($this->tokenizer->tokenize($source));
- $this->treeFromTokenStreamBuilder->buildTree($node, $this->type, $this->tokenizer);
- $parentNode->addChild($node);
- }
+ $this->addContentRenderingFromGlobals($parentNode, 'TYPO3_CONF_VARS defaultContentRendering ' . $this->type . ' for ' . $identifier);
}
}

diff --git a/typo3/sysext/core/Classes/Utility/ExtensionManagementUtility.php b/typo3/sysext/core/Classes/Utility/ExtensionManagementUtility.php
index 65b0a41..317a87b 100644
--- a/typo3/sysext/core/Classes/Utility/ExtensionManagementUtility.php
+++ b/typo3/sysext/core/Classes/Utility/ExtensionManagementUtility.php
@@ -1025,14 +1025,23 @@
* FOR USE IN ext_localconf.php FILES
*
* @param string $content TypoScript Setup string
+ * @param bool $includeInSiteSets
*/
- public static function addTypoScriptSetup(string $content): void
+ public static function addTypoScriptSetup(string $content, bool $includeInSiteSets = true): void
{
$GLOBALS['TYPO3_CONF_VARS']['FE']['defaultTypoScript_setup'] ??= '';
if (!empty($GLOBALS['TYPO3_CONF_VARS']['FE']['defaultTypoScript_setup'])) {
$GLOBALS['TYPO3_CONF_VARS']['FE']['defaultTypoScript_setup'] .= LF;
}
$GLOBALS['TYPO3_CONF_VARS']['FE']['defaultTypoScript_setup'] .= $content;
+
+ if ($includeInSiteSets) {
+ $GLOBALS['TYPO3_CONF_VARS']['FE']['defaultTypoScript_setup.']['siteSets'] ??= '';
+ if (!empty($GLOBALS['TYPO3_CONF_VARS']['FE']['defaultTypoScript_setup.']['siteSets'])) {
+ $GLOBALS['TYPO3_CONF_VARS']['FE']['defaultTypoScript_setup.']['siteSets'] .= LF;
+ }
+ $GLOBALS['TYPO3_CONF_VARS']['FE']['defaultTypoScript_setup.']['siteSets'] .= $content;
+ }
}

/**
@@ -1041,14 +1050,22 @@
* FOR USE IN ext_localconf.php FILES
*
* @param string $content TypoScript Constants string
+ * @param bool $includeInSiteSets
*/
- public static function addTypoScriptConstants(string $content): void
+ public static function addTypoScriptConstants(string $content, bool $includeInSiteSets = true): void
{
$GLOBALS['TYPO3_CONF_VARS']['FE']['defaultTypoScript_constants'] ??= '';
if (!empty($GLOBALS['TYPO3_CONF_VARS']['FE']['defaultTypoScript_constants'])) {
$GLOBALS['TYPO3_CONF_VARS']['FE']['defaultTypoScript_constants'] .= LF;
}
$GLOBALS['TYPO3_CONF_VARS']['FE']['defaultTypoScript_constants'] .= $content;
+ if ($includeInSiteSets) {
+ $GLOBALS['TYPO3_CONF_VARS']['FE']['defaultTypoScript_constants.']['siteSets'] ??= '';
+ if (!empty($GLOBALS['TYPO3_CONF_VARS']['FE']['defaultTypoScript_constants.']['siteSets'])) {
+ $GLOBALS['TYPO3_CONF_VARS']['FE']['defaultTypoScript_constants.']['siteSets'] .= LF;
+ }
+ $GLOBALS['TYPO3_CONF_VARS']['FE']['defaultTypoScript_constants.']['siteSets'] .= $content;
+ }
}

/**
@@ -1068,7 +1085,7 @@
* @param int|string $afterStaticUid string pointing to the "key" of a static_file template ([reduced extension_key]/[local path]). The points is that the TypoScript you add is included only IF that static template is included (and in that case, right after). So effectively the TypoScript you set can specifically overrule settings from those static templates.
* @throws \InvalidArgumentException
*/
- public static function addTypoScript(string $key, string $type, string $content, int|string $afterStaticUid = 0): void
+ public static function addTypoScript(string $key, string $type, string $content, int|string $afterStaticUid = 0, bool $includeInSiteSets = true): void
{
if ($type !== 'setup' && $type !== 'constants') {
throw new \InvalidArgumentException('Argument $type must be set to either "setup" or "constants" when calling addTypoScript from extension "' . $key . '"', 1507321200);
@@ -1095,6 +1112,11 @@
} else {
$GLOBALS['TYPO3_CONF_VARS']['FE']['defaultTypoScript_' . $type] ??= '';
$GLOBALS['TYPO3_CONF_VARS']['FE']['defaultTypoScript_' . $type] .= $content;
+ if ($includeInSiteSets) {
+ // 'siteSets' is an @internal identifier
+ $GLOBALS['TYPO3_CONF_VARS']['FE']['defaultTypoScript_' . $type . '.']['siteSets'] ??= '';
+ $GLOBALS['TYPO3_CONF_VARS']['FE']['defaultTypoScript_' . $type . '.']['siteSets'] .= $content;
+ }
}
}

23 changes: 23 additions & 0 deletions local_packages/psi/Classes/Content/ContentSlideMode.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

declare(strict_types=1);

namespace Jacuzzi\Psi\Content;

enum ContentSlideMode
{
case None;
case Slide;
case Collect;
case CollectReverse;

public static function tryFrom(?string $slideMode): ContentSlideMode
{
return match ($slideMode) {
'slide' => self::Slide,
'collect' => self::Collect,
'collectReverse' => self::CollectReverse,
default => self::None,
};
}
}
68 changes: 68 additions & 0 deletions local_packages/psi/Classes/Content/RecordCollector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<?php

declare(strict_types=1);

namespace Jacuzzi\Psi\Content;

use Jacuzzi\Psi\Domain\RecordFactory;
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;

/**
* Executes a SQL query, and retrieves TCA-based records for Frontend rendering.
*/
class RecordCollector
{
public function __construct(protected readonly RecordFactory $recordFactory) {}

public function collect(
string $table,
array $select,
ContentSlideMode $slideMode,
ContentObjectRenderer $contentObjectRenderer
): array {
$slideCollectReverse = false;
$collect = false;
switch ($slideMode) {
case ContentSlideMode::Slide:
$slide = true;
break;
case ContentSlideMode::Collect:
$slide = true;
$collect = true;
break;
case ContentSlideMode::CollectReverse:
$slide = true;
$collect = true;
$slideCollectReverse = true;
break;
default:
$slide = false;
}
$again = false;
$totalRecords = [];

do {
$recordsOnPid = $contentObjectRenderer->getRecords($table, $select);
$recordsOnPid = array_map(
function ($record) use ($table) {
return $this->recordFactory->createFromDatabaseRecord($table, $record);
},
$recordsOnPid
);

if ($slideCollectReverse) {
$totalRecords = array_merge($totalRecords, $recordsOnPid);
} else {
$totalRecords = array_merge($recordsOnPid, $totalRecords);
}
if ($slide) {
$select['pidInList'] = $contentObjectRenderer->getSlidePids($select['pidInList'] ?? '', $select['pidInList.'] ?? []);
if (isset($select['pidInList.'])) {
unset($select['pidInList.']);
}
$again = $select['pidInList'] !== '';
}
} while ($again && $slide && ($recordsOnPid === [] || $collect));
return $totalRecords;
}
}
92 changes: 92 additions & 0 deletions local_packages/psi/Classes/Content/RecordEnricher.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
<?php

declare(strict_types=1);

namespace Jacuzzi\Psi\Content;

use Jacuzzi\Psi\Domain\Record;
use Jacuzzi\Psi\Domain\RecordFactory;
use TYPO3\CMS\Core\Service\FlexFormService;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
use TYPO3\CMS\Frontend\Resource\FileCollector;

/**
* Enriches a record with resolved properties from relations.
* It's like a DataMapper approach
*/
class RecordEnricher
{

public function __construct(
protected readonly RecordFactory $recordFactory
)
{
}

public function createResolvedRecordFromRecord(Record $record): ResolvedRecord
{
$resolvedProperties = [];
$mainType = $record->getMainType();
if ($mainType === 'content') {
$mainType = 'tt_content';
}
$tcaSchema = $GLOBALS['TCA'][$mainType];
$subSchemaConfig = $this->recordFactory->getSubschemaConfig($tcaSchema, $record->getType());

foreach ($record->toArray() as $fieldName => $fieldValue) {
$fieldConfiguration = $this->recordFactory->getFinalFieldConfiguration($fieldName, $tcaSchema, $subSchemaConfig);
if (isset($fieldConfiguration['config'])) {
switch ($fieldConfiguration['config']['type']) {
case 'file':
/** @var FileCollector $fileCollector */
$fileCollector = GeneralUtility::makeInstance(FileCollector::class);
$fileCollector->addFilesFromRelation($mainType, $fieldName, $record->getRawRecord()->toArray());
$resolvedProperties[$fieldName] = $fileCollector->getFiles();
if ((int)($fieldConfiguration['config']['maxitems'] ?? 0) === 1) {
$resolvedProperties[$fieldName] = reset($resolvedProperties[$fieldName]);
}
break;
case 'inline':
case 'select':
if (!isset($fieldConfiguration['config']['foreign_table']) || !isset($fieldConfiguration['config']['foreign_field'])) {
break;
}
$selectConfiguration = [
'where' => $fieldConfiguration['config']['foreign_field'] . '=' . (int)$record->getRawUid(),
];
if (isset($GLOBALS['TCA'][$fieldConfiguration['config']['foreign_table']]['ctrl']['sortby'])) {
$selectConfiguration[$fieldName]['config']['sortBy'] = $GLOBALS['TCA'][$fieldConfiguration['config']['foreign_table']]['ctrl']['sortby'];
} elseif (isset($GLOBALS['TCA'][$fieldConfiguration['config']['foreign_table']]['ctrl']['default_sortby'])) {
$selectConfiguration[$fieldName]['config']['sortBy'] = $GLOBALS['TCA'][$fieldConfiguration['config']['foreign_table']]['ctrl']['default_sortby'];
}

$cObj = GeneralUtility::makeInstance(ContentObjectRenderer::class);
$cObj->start($record->getRawRecord()->toArray(), $record->getMainType());
$recordFactory = new RecordFactory();
$subRecords = GeneralUtility::makeInstance(RecordCollector::class, $recordFactory)->collect(
$fieldConfiguration['config']['foreign_table'],
$selectConfiguration,
ContentSlideMode::None,
$cObj
);
/** @var Record $subRecord */
foreach ($subRecords as $subRecord) {
$resolvedProperties[$fieldName][] = $this->createResolvedRecordFromRecord($subRecord)->toArray(true);
}

if ((int)($fieldConfiguration['config']['maxitems'] ?? 0) === 1) {
$resolvedProperties[$fieldName] = reset($resolvedProperties[$fieldName]);
}

break;
case 'flex':
$resolvedProperties[$fieldName] = GeneralUtility::makeInstance(FlexFormService::class)->convertFlexFormContentToArray($fieldValue);
break;
}
}
}
return new ResolvedRecord($record, $resolvedProperties);
}

}
Loading

0 comments on commit 381a35b

Please sign in to comment.