diff --git a/typo3/sysext/extbase/Classes/Core/Bootstrap.php b/typo3/sysext/extbase/Classes/Core/Bootstrap.php index 33646b0d4a3f41ab248e5ba352a732c4b3397d85..13674d3e8fd39a82d0c95f50dafaae7048c031f4 100644 --- a/typo3/sysext/extbase/Classes/Core/Bootstrap.php +++ b/typo3/sysext/extbase/Classes/Core/Bootstrap.php @@ -109,9 +109,8 @@ class Bootstrap { if ($this->cObj === null) { $this->cObj = $this->container->get(ContentObjectRenderer::class); - $request = $request->withAttribute('currentContentObject', $this->cObj); + $this->cObj->setRequest($request); } - $this->cObj->setRequest($request); // @deprecated since v12. Remove in v13. $this->configurationManager->setContentObject($this->cObj); if (method_exists($this->configurationManager, 'setRequest')) { diff --git a/typo3/sysext/extbase/Classes/Mvc/Web/Routing/UriBuilder.php b/typo3/sysext/extbase/Classes/Mvc/Web/Routing/UriBuilder.php index f680831ef5e36e9fd2a0dc2a787907107bac394b..29b78404708be846700223b9689b8424e9203540 100644 --- a/typo3/sysext/extbase/Classes/Mvc/Web/Routing/UriBuilder.php +++ b/typo3/sysext/extbase/Classes/Mvc/Web/Routing/UriBuilder.php @@ -696,7 +696,7 @@ class UriBuilder E_USER_DEPRECATED ); - return ($contentObject = $this->getRequest()?->getArgument('currentContentObject')) instanceof ContentObjectRenderer + return ($contentObject = $this->getRequest()?->getAttribute('currentContentObject')) instanceof ContentObjectRenderer ? $contentObject : GeneralUtility::makeInstance(ContentObjectRenderer::class); } diff --git a/typo3/sysext/fluid/Classes/ViewHelpers/CObjectViewHelper.php b/typo3/sysext/fluid/Classes/ViewHelpers/CObjectViewHelper.php index 4e3e2bb4a7eb030126c90c44efe0689b7dcdd9ba..ea28f0d16f2a3bc65fcb864287397626abf80535 100644 --- a/typo3/sysext/fluid/Classes/ViewHelpers/CObjectViewHelper.php +++ b/typo3/sysext/fluid/Classes/ViewHelpers/CObjectViewHelper.php @@ -128,7 +128,7 @@ final class CObjectViewHelper extends AbstractViewHelper /** @var RenderingContext $renderingContext */ $request = $renderingContext->getRequest(); $contentObjectRenderer = self::getContentObjectRenderer($request); - $contentObjectRenderer->setRequest($request->withAttribute('currentContentObject', $contentObjectRenderer)); + $contentObjectRenderer->setRequest($request); $tsfeBackup = null; if (!isset($GLOBALS['TSFE']) || !($GLOBALS['TSFE'] instanceof TypoScriptFrontendController)) { $tsfeBackup = self::simulateFrontendEnvironment(); @@ -216,7 +216,12 @@ final class CObjectViewHelper extends AbstractViewHelper GeneralUtility::makeInstance(FrontendUserAuthentication::class) ); } - return GeneralUtility::makeInstance(ContentObjectRenderer::class, $tsfe); + $contentObjectRenderer = GeneralUtility::makeInstance(ContentObjectRenderer::class, $tsfe); + $parent = $request->getAttribute('currentContentObject'); + if ($parent instanceof ContentObjectRenderer) { + $contentObjectRenderer->setParent($parent->data, $parent->currentRecord); + } + return $contentObjectRenderer; } /** diff --git a/typo3/sysext/frontend/Classes/ContentObject/AbstractContentObject.php b/typo3/sysext/frontend/Classes/ContentObject/AbstractContentObject.php index 1461f53e1a2628c9d5423671cbf7657fe0e26fa9..fe9c4c41da618ab60ea78345a389281cd6319b01 100644 --- a/typo3/sysext/frontend/Classes/ContentObject/AbstractContentObject.php +++ b/typo3/sysext/frontend/Classes/ContentObject/AbstractContentObject.php @@ -59,6 +59,11 @@ abstract class AbstractContentObject public function setContentObjectRenderer(ContentObjectRenderer $cObj): void { $this->cObj = $cObj; + // Provide the ContentObjectRenderer to the request as well, for code + // that only passes the request to more underlying layers, like Extbase does. + // Also makes sure the request in a Fluid RenderingContext also has the current + // content object available. + $this->request = $this->request->withAttribute('currentContentObject', $cObj); } protected function hasTypoScriptFrontendController(): bool diff --git a/typo3/sysext/frontend/Classes/ContentObject/ContentObjectRenderer.php b/typo3/sysext/frontend/Classes/ContentObject/ContentObjectRenderer.php index 822d110e7d4273659d7b9b05eaa649bbd0b17b96..55fb2b9da7b0aff0b8f6acb3c58ab793039955fe 100644 --- a/typo3/sysext/frontend/Classes/ContentObject/ContentObjectRenderer.php +++ b/typo3/sysext/frontend/Classes/ContentObject/ContentObjectRenderer.php @@ -4732,7 +4732,7 @@ class ContentObjectRenderer implements LoggerAwareInterface if (method_exists($classObj, 'setContentObjectRenderer') && is_callable([$classObj, 'setContentObjectRenderer'])) { $classObj->setContentObjectRenderer($this); } - $content = $callable($content, $conf, $this->getRequest()); + $content = $callable($content, $conf, $this->getRequest()->withAttribute('currentContentObject', $this)); } else { $this->getTimeTracker()->setTSlogMessage('Method "' . $parts[1] . '" did not exist in class "' . $parts[0] . '"', LogLevel::ERROR); } @@ -4740,7 +4740,7 @@ class ContentObjectRenderer implements LoggerAwareInterface $this->getTimeTracker()->setTSlogMessage('Class "' . $parts[0] . '" did not exist', LogLevel::ERROR); } } elseif (function_exists($funcName)) { - $content = $funcName($content, $conf); + $content = $funcName($content, $conf, $this->getRequest()->withAttribute('currentContentObject', $this)); } else { $this->getTimeTracker()->setTSlogMessage('Function "' . $funcName . '" did not exist', LogLevel::ERROR); } @@ -5869,35 +5869,32 @@ class ContentObjectRenderer implements LoggerAwareInterface } /** - * @todo: This getRequest() handling is pretty messy. We created a loop from - * request to 'currentContentObject' back to request with this. - * v13 should be refactored, probably with this patch chain: - * * Remove fallback to $GLOBALS['TYPO3_REQUEST'] to force consumers - * actually setting the request using setRequest(). - * * Protect this method. - * * Get rid of public TSFE->cObj (the "page" instance of cObj). - * * Avoid TSFE as constructor argument and make ContentObjectRenderer - * free for DI, for instance to get the container injected. - * * When getRequest() is protected or private, setRequest() should - * *remove* the currentContentObject attribute again, to prevent - * the object loop. This will work, since local getRequest() could - * use $this when needed. + * @todo: This getRequest() is still a bit messy. + * Underling code depends on both, a ContentObjectRenderer instance and a request, + * but the API currently only passes one or the other. For instance Extbase and Fluid + * only pass the Request, DataProcessors only a ContentObjectRenderer. + * This is why getRequest() is currently public here. + * A potential refactoring could: + * * Create interfaces to pass both where needed (or pass a combined context object) + * * Deprecate access to getRequest() here afterwards + * A circular dependency that the instance of ContentObjectRenderer holds a + * request with the instance of itself as attribute must be avoided. + * This is currently achieved by adding a new request with + * $this->request->withAttribute('currentContentObject', $cObj) in code that needs + * it, but this new request is NOT passed back into the ContentObjectRenderer instance. * - * @internal This method will be set to protected with TYPO3 v13. + * @internal This method might be deprecated with TYPO3 v13. */ public function getRequest(): ServerRequestInterface { if ($this->request instanceof ServerRequestInterface) { - // Note attribute 'currentContentObject' has been set by setRequest() already. return $this->request; } - if (($GLOBALS['TYPO3_REQUEST'] ?? null) instanceof ServerRequestInterface) { // @todo: We may want to deprecate this fallback and force consumers // to setRequest() after object instantiation / unserialization instead. - return $GLOBALS['TYPO3_REQUEST']->withAttribute('currentContentObject', $this); + return $GLOBALS['TYPO3_REQUEST']; } - throw new ContentRenderingException( 'PSR-7 request is missing in ContentObjectRenderer. Inject with start(), setRequest() or provide via $GLOBALS[\'TYPO3_REQUEST\'].', 1607172972 diff --git a/typo3/sysext/frontend/Classes/Controller/TypoScriptFrontendController.php b/typo3/sysext/frontend/Classes/Controller/TypoScriptFrontendController.php index 725539a63d980be65b26d01cb99da841746f871e..f0b0c6fdc2c65bbe57a28a78e1117303a2a91c56 100644 --- a/typo3/sysext/frontend/Classes/Controller/TypoScriptFrontendController.php +++ b/typo3/sysext/frontend/Classes/Controller/TypoScriptFrontendController.php @@ -2280,7 +2280,7 @@ class TypoScriptFrontendController implements LoggerAwareInterface $nonCacheableContent = ''; $contentObjectRendererForNonCacheable = unserialize($nonCacheableData[$nonCacheableKey]['cObj']); if ($contentObjectRendererForNonCacheable instanceof ContentObjectRenderer) { - $contentObjectRendererForNonCacheable->setRequest($request->withAttribute('currentContentObject', $contentObjectRendererForNonCacheable)); + $contentObjectRendererForNonCacheable->setRequest($request); $nonCacheableContent = match ($nonCacheableData[$nonCacheableKey]['type']) { 'COA' => $contentObjectRendererForNonCacheable->cObjGetSingle('COA', $nonCacheableData[$nonCacheableKey]['conf']), 'FUNC' => $contentObjectRendererForNonCacheable->cObjGetSingle('USER', $nonCacheableData[$nonCacheableKey]['conf']), diff --git a/typo3/sysext/frontend/Classes/DataProcessing/FlexFormProcessor.php b/typo3/sysext/frontend/Classes/DataProcessing/FlexFormProcessor.php index f9082c60373d8f0a17c4ad5a571df2cf3251ee8a..a508fe82f4369a6dcb912e47898fc488c3e82ee2 100644 --- a/typo3/sysext/frontend/Classes/DataProcessing/FlexFormProcessor.php +++ b/typo3/sysext/frontend/Classes/DataProcessing/FlexFormProcessor.php @@ -96,7 +96,7 @@ class FlexFormProcessor implements DataProcessorInterface protected function processAdditionalDataProcessors(array $data, array $processorConfiguration, ServerRequestInterface $request): array { $contentObjectRenderer = GeneralUtility::makeInstance(ContentObjectRenderer::class); - $contentObjectRenderer->setRequest($request->withAttribute('currentContentObject', $contentObjectRenderer)); + $contentObjectRenderer->setRequest($request); $contentObjectRenderer->start([$data], ''); return GeneralUtility::makeInstance(ContentDataProcessor::class)->process( $contentObjectRenderer, diff --git a/typo3/sysext/frontend/Tests/Unit/ContentObject/CaseContentObjectTest.php b/typo3/sysext/frontend/Tests/Unit/ContentObject/CaseContentObjectTest.php index 95b64783ef1362b6ad35f11926195905911242ba..237b1b9cb2227fabf49e1482a5d2bac6b8196799 100644 --- a/typo3/sysext/frontend/Tests/Unit/ContentObject/CaseContentObjectTest.php +++ b/typo3/sysext/frontend/Tests/Unit/ContentObject/CaseContentObjectTest.php @@ -43,7 +43,6 @@ final class CaseContentObjectTest extends UnitTestCase $request = new ServerRequest(); $contentObjectRenderer = new ContentObjectRenderer($tsfe); - $request = $request->withAttribute('currentContentObject', $contentObjectRenderer); $contentObjectRenderer->setRequest($request); $cObjectFactoryMock = $this->getMockBuilder(ContentObjectFactory::class)->disableOriginalConstructor()->getMock(); @@ -64,6 +63,7 @@ final class CaseContentObjectTest extends UnitTestCase GeneralUtility::setContainer($container); $this->subject = new CaseContentObject(); + $this->subject->setRequest($request); $this->subject->setContentObjectRenderer($contentObjectRenderer); } diff --git a/typo3/sysext/frontend/Tests/Unit/ContentObject/ImageContentObjectTest.php b/typo3/sysext/frontend/Tests/Unit/ContentObject/ImageContentObjectTest.php index 8d220809051d31938e4e455736a55f8875136eae..de5f807ee41895b50e4fe495c4442ea228fe43b3 100644 --- a/typo3/sysext/frontend/Tests/Unit/ContentObject/ImageContentObjectTest.php +++ b/typo3/sysext/frontend/Tests/Unit/ContentObject/ImageContentObjectTest.php @@ -19,6 +19,7 @@ namespace TYPO3\CMS\Frontend\Tests\Unit\ContentObject; use PHPUnit\Framework\MockObject\MockObject; use TYPO3\CMS\Core\Cache\Frontend\NullFrontend; +use TYPO3\CMS\Core\Http\ServerRequest; use TYPO3\CMS\Core\Page\PageRenderer; use TYPO3\CMS\Core\Resource\ResourceFactory; use TYPO3\CMS\Core\Service\MarkerBasedTemplateService; @@ -50,6 +51,7 @@ final class ImageContentObjectTest extends UnitTestCase new NullFrontend('runtime'), ), ]); + $this->subject->setRequest(new ServerRequest()); $this->subject->setContentObjectRenderer($contentObjectRenderer); $pageRenderer = $this->getMockBuilder(PageRenderer::class)->disableOriginalConstructor()->addMethods(['dummy'])->getMock(); $this->subject->_set('pageRenderer', $pageRenderer); @@ -381,6 +383,7 @@ final class ImageContentObjectTest extends UnitTestCase ), ]); $subject->_set('pageRenderer', $pageRenderer); + $subject->setRequest(new ServerRequest()); $subject->setContentObjectRenderer($cObj); $result = $subject->_call('getImageSourceCollection', $layoutKey, $configuration, $file); diff --git a/typo3/sysext/frontend/Tests/Unit/DataProcessing/FlexFormProcessorTest.php b/typo3/sysext/frontend/Tests/Unit/DataProcessing/FlexFormProcessorTest.php index 6763db6861a67d6837d2dbc69893142c85db9ea1..0c1f75771bb032815e789f59edbfdae6571b4198 100644 --- a/typo3/sysext/frontend/Tests/Unit/DataProcessing/FlexFormProcessorTest.php +++ b/typo3/sysext/frontend/Tests/Unit/DataProcessing/FlexFormProcessorTest.php @@ -18,7 +18,6 @@ declare(strict_types=1); namespace TYPO3\CMS\Frontend\Tests\Unit\DataProcessing; use PHPUnit\Framework\MockObject\MockObject; -use TYPO3\CMS\Core\Http\ServerRequest; use TYPO3\CMS\Core\Service\FlexFormService; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Frontend\ContentObject\ContentDataProcessor; @@ -205,9 +204,6 @@ final class FlexFormProcessorTest extends UnitTestCase ], ]; $this->contentObjectRendererMock->expects(self::once())->method('start')->with([$convertedFlexFormData]); - $request = new ServerRequest(); - $request = $request->withAttribute('currentContentObject', $this->contentObjectRendererMock); - $this->contentObjectRendererMock->method('getRequest')->willReturn($request); $contentDataProcessorMock = $this->getMockBuilder(ContentDataProcessor::class)->disableOriginalConstructor()->getMock(); $renderedDataFromProcessors = [