Skip to content

Commit 8897c6a

Browse files
committed
[TASK] Model TS "register" as request attribute
The frontend TypoScript related "register" functionality is one of the very old structures that are pretty hard to understand nowadays and don't find that many usages anymore. It is one of the construct we do have to touch with current TSFE works, but we shouldn't break it either for no good reason. The "register" is a combination of a key/value store with a stack. The patch models this as two classes, attaches an instance of the stack as request attribute 'frontend.register.stack', adapts usages and removes the two TSFE properties $register and $registerStack. The change needs a patch in typo3/testing-framework as well, so we raise it: > composer u typo3/testing-framework Resolves: #107625 Releases: main Change-Id: If79982963baf514f4a2e2d5571d7152f7f0bd7fd Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/90945 Tested-by: core-ci <typo3@b13.com> Tested-by: Christian Kuhn <lolli@schwarzbu.ch> Tested-by: Anja Leichsenring <aleichsenring@ab-softlab.de> Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch> Tested-by: Stefan Bürk <stefan@buerk.tech> Reviewed-by: Anja Leichsenring <aleichsenring@ab-softlab.de> Reviewed-by: Stefan Bürk <stefan@buerk.tech>
1 parent fd640ed commit 8897c6a

16 files changed

+225
-168
lines changed

.phpstorm.meta.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@
9292
'frontend.cache.instruction',
9393
'frontend.page.information',
9494
'frontent.page.parts',
95+
'frontend.register.stack',
9596
);
9697
override(\Psr\Http\Message\ServerRequestInterface::getAttribute(), map([
9798
'frontend.user' => \TYPO3\CMS\Frontend\Authentication\FrontendUserAuthentication::class,
@@ -107,6 +108,7 @@
107108
'frontend.cache.instruction' => \TYPO3\CMS\Frontend\Cache\CacheInstruction::class,
108109
'frontend.page.information' => \TYPO3\CMS\Frontend\Page\PageInformation::class,
109110
'frontend.page.parts' => \TYPO3\CMS\Frontend\Page\PageParts::class,
111+
'frontend.register.stack' => \TYPO3\CMS\Frontend\ContentObject\RegisterStack::class,
110112
]));
111113

112114
expectedArguments(

Build/phpstan/phpstan-baseline.neon

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3102,12 +3102,6 @@ parameters:
31023102
count: 1
31033103
path: ../../typo3/sysext/frontend/Tests/Unit/ContentObject/ContentObjectRendererTest.php
31043104

3105-
-
3106-
message: '#^Offset non\-falsy\-string does not exist on array\{\}\.$#'
3107-
identifier: offsetAccess.notFound
3108-
count: 1
3109-
path: ../../typo3/sysext/frontend/Tests/Unit/ContentObject/ContentObjectRendererTest.php
3110-
31113105
-
31123106
message: '#^Parameter \#2 \$conf of method TYPO3\\CMS\\Frontend\\ContentObject\\ContentObjectRenderer\:\:prefixComment\(\) expects array, null given\.$#'
31133107
identifier: argument.type

composer.lock

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

typo3/sysext/frontend/Classes/ContentObject/ContentObjectRenderer.php

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2865,8 +2865,8 @@ public function splitObj($value, $conf)
28652865
}
28662866
$content = '';
28672867
for ($a = 0; $a < $splitCount; $a++) {
2868-
$this->getTypoScriptFrontendController()->register['SPLIT_COUNT'] = $a;
2869-
$value = '' . $valArr[$a];
2868+
$this->getRequest()->getAttribute('frontend.register.stack')->current()->set('SPLIT_COUNT', $a);
2869+
$value = $valArr[$a];
28702870
$this->data[$this->currentValKey] = $value;
28712871
if ($splitArr[$a]['cObjNum'] ?? false) {
28722872
$objName = (int)$splitArr[$a]['cObjNum'];
@@ -3925,8 +3925,7 @@ public function getData($string, $fieldArray = null)
39253925
// of a request attribute. The register access via TS should continue to work, though.
39263926
$retVal = $this->getRequest()->getAttribute('frontend.page.parts')->getLastChanged();
39273927
} else {
3928-
$tsfe = $this->getTypoScriptFrontendController();
3929-
$retVal = $tsfe->register[$key] ?? null;
3928+
$retVal = $this->getRequest()->getAttribute('frontend.register.stack')->current()->get($key);
39303929
}
39313930
break;
39323931
case 'global':
@@ -4040,8 +4039,7 @@ public function getData($string, $fieldArray = null)
40404039
$retVal = DebugUtility::viewArray($this->data);
40414040
break;
40424041
case 'register':
4043-
$tsfe = $this->getTypoScriptFrontendController();
4044-
$retVal = DebugUtility::viewArray($tsfe->register);
4042+
$retVal = DebugUtility::viewArray($this->getRequest()->getAttribute('frontend.register.stack')->current());
40454043
break;
40464044
case 'page':
40474045
$retVal = DebugUtility::viewArray($this->getRequest()->getAttribute('frontend.page.information')->getPageRecord());

typo3/sysext/frontend/Classes/ContentObject/FilesContentObject.php

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,7 @@ public function render($conf = [])
3636
if (!empty($conf['if.']) && !$this->cObj->checkIf($conf['if.'])) {
3737
return '';
3838
}
39-
$frontendController = $this->hasTypoScriptFrontendController()
40-
? $this->getTypoScriptFrontendController()
41-
: null;
39+
$register = $this->request->getAttribute('frontend.register.stack')->current();
4240
// Store the original "currentFile" within a variable so it can be re-applied later-on
4341
$originalFileInContentObject = $this->cObj->getCurrentFile();
4442

@@ -56,20 +54,15 @@ public function render($conf = [])
5654
$limit = (int)$this->cObj->stdWrapValue('maxItems', $conf, $availableFileObjectCount);
5755
$end = MathUtility::forceIntegerInRange($start + $limit, $start, $availableFileObjectCount);
5856

59-
if ($frontendController !== null) {
60-
$frontendController->register['FILES_COUNT'] = min($limit, $availableFileObjectCount);
61-
}
57+
$register->set('FILES_COUNT', min($limit, $availableFileObjectCount));
6258
$fileObjectCounter = 0;
6359
$keys = array_keys($fileObjects);
6460

6561
$content = '';
6662
for ($i = $start; $i < $end; $i++) {
6763
$key = $keys[$i];
6864
$fileObject = $fileObjects[$key];
69-
70-
if ($frontendController !== null) {
71-
$frontendController->register['FILE_NUM_CURRENT'] = $fileObjectCounter;
72-
}
65+
$register->set('FILE_NUM_CURRENT', $fileObjectCounter);
7366
$this->cObj->setCurrentFile($fileObject);
7467
$content .= $this->cObj->cObjGetSingle($splitConf[$key]['renderObj'], $splitConf[$key]['renderObj.'], 'renderObj');
7568
$fileObjectCounter++;

typo3/sysext/frontend/Classes/ContentObject/HierarchicalMenuContentObject.php

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -39,21 +39,19 @@ public function render($conf = [])
3939
$theValue = '';
4040
$menuType = $conf[1] ?? '';
4141
try {
42-
$frontendController = $this->getTypoScriptFrontendController();
42+
$register = $this->request->getAttribute('frontend.register.stack')->current();
4343
$menuObjectFactory = GeneralUtility::makeInstance(MenuContentObjectFactory::class);
4444
$menu = $menuObjectFactory->getMenuObjectByType($menuType);
45-
if (isset($frontendController->register['count_HMENU'])) {
46-
$frontendController->register['count_HMENU']++;
47-
} else {
48-
$frontendController->register['count_HMENU'] = 1;
49-
}
50-
$frontendController->register['count_HMENU_MENUOBJ'] = 0;
51-
$frontendController->register['count_MENUOBJ'] = 0;
45+
$countHMENU = (int)$register->get('count_HMENU', 0);
46+
$countHMENU++;
47+
$register->set('count_HMENU', $countHMENU);
48+
$register->set('count_HMENU_MENUOBJ', 0);
49+
$register->set('count_MENUOBJ', 0);
5250
$menu->parent_cObj = $this->getContentObjectRenderer();
5351
$menu->start(null, $this->getPageRepository(), '', $conf, 1, '', $this->request);
5452
$menu->makeMenu();
5553
$theValue .= $menu->writeMenu();
56-
} catch (NoSuchMenuTypeException $e) {
54+
} catch (NoSuchMenuTypeException) {
5755
}
5856
$wrap = $this->cObj->stdWrapValue('wrap', $conf);
5957
if ($wrap) {

typo3/sysext/frontend/Classes/ContentObject/LoadRegisterContentObject.php

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,37 +16,42 @@
1616
namespace TYPO3\CMS\Frontend\ContentObject;
1717

1818
/**
19-
* Contains LOAD_REGISTER class object.
19+
* Implement cObj "LOAD_REGISTER":
20+
* Get latest Register, clone it, set a key/value, push as new latest RegisterStack entry.
21+
*
22+
* Note the naming "LOAD_REGISTER" is kinda misleading since it rather "loads into" or
23+
* sets a new value and pushes as key/value to the stack. "RESTORE_REGISTER" is the
24+
* counterpart cObj to get rid of that state again.
2025
*/
2126
class LoadRegisterContentObject extends AbstractContentObject
2227
{
2328
/**
24-
* Rendering the cObject, LOAD_REGISTER
25-
* NOTICE: This cObject does NOT return any content since it just sets internal data based on the TypoScript properties.
29+
* Does not return any content, it just sets internal data based on the TypoScript properties.
2630
*
2731
* @param array $conf Array of TypoScript properties
28-
* @return string Empty string (the cObject only sets internal data!)
32+
* @return string Empty string
2933
*/
30-
public function render($conf = [])
34+
public function render($conf = []): string
3135
{
32-
$frontendController = $this->getTypoScriptFrontendController();
33-
$frontendController->registerStack[] = $frontendController->register;
36+
$registerStack = $this->request->getAttribute('frontend.register.stack');
37+
$clonedRegister = clone $registerStack->current();
3438
if (is_array($conf)) {
3539
$isExecuted = [];
36-
foreach ($conf as $theKey => $theValue) {
37-
$register = rtrim($theKey, '.');
38-
if (!isset($isExecuted[$register]) || !$isExecuted[$register]) {
39-
$registerProperties = $register . '.';
40-
if (isset($conf[$register]) && isset($conf[$registerProperties])) {
41-
$theValue = $this->cObj->stdWrap($conf[$register], $conf[$registerProperties]);
40+
foreach ($conf as $key => $value) {
41+
$key = rtrim($key, '.');
42+
if (!isset($isExecuted[$key]) || !$isExecuted[$key]) {
43+
$registerProperties = $key . '.';
44+
if (isset($conf[$key]) && isset($conf[$registerProperties])) {
45+
$value = $this->cObj->stdWrap($conf[$key], $conf[$registerProperties]);
4246
} elseif (isset($conf[$registerProperties])) {
43-
$theValue = $this->cObj->stdWrap('', $conf[$registerProperties]);
47+
$value = $this->cObj->stdWrap('', $conf[$registerProperties]);
4448
}
45-
$frontendController->register[$register] = $theValue;
46-
$isExecuted[$register] = true;
49+
$clonedRegister->set($key, $value);
50+
$isExecuted[$key] = true;
4751
}
4852
}
4953
}
54+
$registerStack->push($clonedRegister);
5055
return '';
5156
}
5257
}

typo3/sysext/frontend/Classes/ContentObject/Menu/AbstractMenuContentObject.php

Lines changed: 10 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,7 @@
3434
use TYPO3\CMS\Core\Utility\GeneralUtility;
3535
use TYPO3\CMS\Core\Utility\MathUtility;
3636
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
37-
use TYPO3\CMS\Frontend\ContentObject\Exception\ContentRenderingException;
3837
use TYPO3\CMS\Frontend\ContentObject\Menu\Exception\NoSuchMenuTypeException;
39-
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
4038
use TYPO3\CMS\Frontend\Event\FilterMenuItemsEvent;
4139
use TYPO3\CMS\Frontend\Typolink\LinkResult;
4240
use TYPO3\CMS\Frontend\Typolink\LinkResultInterface;
@@ -395,8 +393,7 @@ public function makeMenu()
395393
$this->menuArr = $this->userProcess('itemArrayProcFunc', $this->menuArr);
396394
}
397395
// Setting number of menu items
398-
$frontendController = $this->getTypoScriptFrontendController();
399-
$frontendController->register['count_menuItems'] = count($this->menuArr);
396+
$this->request->getAttribute('frontend.register.stack')->current()->set('count_menuItems', count($this->menuArr));
400397
$this->generate();
401398
// End showAccessRestrictedPages
402399
if ($this->mconf['showAccessRestrictedPages'] ?? false) {
@@ -568,8 +565,7 @@ protected function prepareMenuItemsForLanguageMenu($specialValue)
568565
$languageItems = GeneralUtility::intExplode(',', $specialValue);
569566
}
570567

571-
$tsfe = $this->getTypoScriptFrontendController();
572-
$tsfe->register['languages_HMENU'] = implode(',', $languageItems);
568+
$this->request->getAttribute('frontend.register.stack')->current()->set('languages_HMENU', implode(',', $languageItems));
573569

574570
$currentLanguageId = $this->getCurrentLanguageAspect()->getId();
575571

@@ -1295,18 +1291,17 @@ protected function subMenu(int $uid, string $objSuffix, int $menuItemKey)
12951291
}
12961292
if ($submenu->start(null, $this->sys_page, $uid, $this->conf, $this->menuNumber + 1, $objSuffix, $this->request)) {
12971293
$submenu->makeMenu();
1298-
// Memorize the current menu item count
1299-
$tsfe = $this->getTypoScriptFrontendController();
1300-
$tempCountMenuObj = $tsfe->register['count_MENUOBJ'];
1301-
// Reset the menu item count for the submenu
1302-
$tsfe->register['count_MENUOBJ'] = 0;
1294+
$registerStack = $this->request->getAttribute('frontend.register.stack');
1295+
$clonedRegister = clone $registerStack->current();
1296+
// Reset the menu item count for the submenu by pushing a new register to register stack
1297+
$clonedRegister->set('count_MENUOBJ', 0);
1298+
$registerStack->push($clonedRegister);
13031299
$content = $submenu->writeMenu();
1304-
// Restore the item count now that the submenu has been handled
1305-
$tsfe->register['count_MENUOBJ'] = $tempCountMenuObj;
1306-
$tsfe->register['count_menuItems'] = count($this->menuArr);
1300+
$registerStack->pop();
1301+
$registerStack->current()->set('count_menuItems', count($this->menuArr));
13071302
return $content;
13081303
}
1309-
} catch (NoSuchMenuTypeException $e) {
1304+
} catch (NoSuchMenuTypeException) {
13101305
}
13111306
}
13121307
return '';
@@ -1768,18 +1763,6 @@ public function getParentContentObject()
17681763
return $this->parent_cObj;
17691764
}
17701765

1771-
/**
1772-
* @throws ContentRenderingException
1773-
*/
1774-
protected function getTypoScriptFrontendController(): TypoScriptFrontendController
1775-
{
1776-
$frontendController = $this->parent_cObj->getTypoScriptFrontendController();
1777-
if (!$frontendController instanceof TypoScriptFrontendController) {
1778-
throw new ContentRenderingException('TypoScriptFrontendController is not available.', 1655725105);
1779-
}
1780-
return $frontendController;
1781-
}
1782-
17831766
protected function getCurrentLanguageAspect(): LanguageAspect
17841767
{
17851768
return GeneralUtility::makeInstance(Context::class)->getAspect('language');

typo3/sysext/frontend/Classes/ContentObject/Menu/TextMenuContentObject.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,15 @@ public function writeMenu()
3737
return '';
3838
}
3939

40-
$frontendController = $this->getTypoScriptFrontendController();
40+
$register = $this->request->getAttribute('frontend.register.stack')->current();
4141
$cObjectForCurrentMenu = GeneralUtility::makeInstance(ContentObjectRenderer::class);
4242
$menuContent = '';
4343
$typoScriptService = GeneralUtility::makeInstance(TypoScriptService::class);
4444
$subMenuObjSuffixes = $typoScriptService->explodeConfigurationForOptionSplit(['sOSuffix' => $this->mconf['submenuObjSuffixes'] ?? null], count($this->result));
4545
$explicitSpacerRenderingEnabled = ($this->mconf['SPC'] ?? false);
4646
foreach ($this->result as $key => $val) {
47-
$frontendController->register['count_HMENU_MENUOBJ']++;
48-
$frontendController->register['count_MENUOBJ']++;
47+
$register->set('count_HMENU_MENUOBJ', (int)$register->get('count_HMENU_MENUOBJ', 0) + 1);
48+
$register->set('count_MENUOBJ', (int)$register->get('count_MENUOBJ', 0) + 1);
4949

5050
// Initialize the cObj with the page record of the menu item
5151
$cObjectForCurrentMenu->setRequest($this->request);
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of the TYPO3 CMS project.
7+
*
8+
* It is free software; you can redistribute it and/or modify it under
9+
* the terms of the GNU General Public License, either version 2
10+
* of the License, or any later version.
11+
*
12+
* For the full copyright and license information, please read the
13+
* LICENSE.txt file that was distributed with this source code.
14+
*
15+
* The TYPO3 project - inspiring people to share!
16+
*/
17+
18+
namespace TYPO3\CMS\Frontend\ContentObject;
19+
20+
/**
21+
* A simple key/value store. See class RegisterStack for more information.
22+
*
23+
* @internal This class is not part of the TYPO3 Core API
24+
*/
25+
final class Register
26+
{
27+
private array $keyValues = [];
28+
29+
/**
30+
* Unable to deal with objects, accepts simple types only.
31+
* This is done by intention to not run into issues if this
32+
* state needs to be serialized (cached).
33+
*/
34+
public function set(string $key, string|int|bool|float $value): void
35+
{
36+
$this->keyValues[$key] = $value;
37+
}
38+
39+
public function get(string $key, string|int|bool|float|null $default = null): string|int|bool|float|null
40+
{
41+
return $this->keyValues[$key] ?? $default;
42+
}
43+
}

0 commit comments

Comments
 (0)