TYPO3 v15 dev-main snapshot ()

This commit is contained in:
2026-08-10 22:31:15 +02:00
commit 6830982e7d
295 changed files with 31995 additions and 0 deletions
+253
View File
@@ -0,0 +1,253 @@
<?php
declare(strict_types=1);
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
namespace TYPO3\CMS\Extbase\Core;
use Psr\Container\ContainerInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;
use TYPO3\CMS\Core\Attribute\AsAllowedCallable;
use TYPO3\CMS\Core\Core\Environment;
use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface;
use TYPO3\CMS\Extbase\Mvc\Dispatcher;
use TYPO3\CMS\Extbase\Mvc\RequestInterface;
use TYPO3\CMS\Extbase\Mvc\Web\RequestBuilder;
use TYPO3\CMS\Extbase\Persistence\PersistenceManagerInterface;
use TYPO3\CMS\Extbase\Service\CacheService;
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
use TYPO3\CMS\Frontend\Response\ResponseData;
/**
* Creates a request and dispatches it to the controller which was specified
* by TS Setup and returns the content.
*
* This class is the main entry point for extbase extensions.
*
* @internal
*/
#[Autoconfigure(public: true, shared: false)]
class Bootstrap
{
protected ?ContentObjectRenderer $cObj = null;
public function __construct(
protected readonly ContainerInterface $container,
protected readonly ConfigurationManagerInterface $configurationManager,
protected readonly PersistenceManagerInterface $persistenceManager,
protected readonly CacheService $cacheService,
protected readonly Dispatcher $dispatcher,
protected readonly RequestBuilder $extbaseRequestBuilder
) {}
/**
* The current (!) cObj.
* Called for frontend plugins from UserContentObject via ContentObjectRenderer->callUserFunction().
*/
public function setContentObjectRenderer(ContentObjectRenderer $cObj): void
{
$this->cObj = $cObj;
}
/**
* Explicitly initializes all necessary Extbase objects by invoking the various initialize* methods.
*
* Usually this method is only called from unit tests or other applications which need a more fine-grained control over
* the initialization and request handling process. Most other applications just call the run() method.
*
* @param array $configuration The TS configuration array
* @throws \RuntimeException
* @see run()
*/
public function initialize(array $configuration, ServerRequestInterface $request): ServerRequestInterface
{
if (!Environment::isCli()) {
if (!isset($configuration['extensionName']) || $configuration['extensionName'] === '') {
throw new \RuntimeException('Invalid configuration: "extensionName" is not set', 1290623020);
}
if (!isset($configuration['pluginName']) || $configuration['pluginName'] === '') {
throw new \RuntimeException('Invalid configuration: "pluginName" is not set', 1290623027);
}
}
return $this->initializeConfiguration($configuration, $request);
}
/**
* Initializes the Object framework.
*
* @see initialize()
* @internal
*/
public function initializeConfiguration(array $configuration, ServerRequestInterface $request): ServerRequestInterface
{
if ($this->cObj === null) {
// @todo: While the frontend sets the current cObj, a backend extbase request does not.
// It is currently not clear if the backend should have a dummy cObj as well.
// For now, extbase initializes one.
$this->cObj = $this->container->get(ContentObjectRenderer::class);
$this->cObj->setRequest($request);
}
$this->configurationManager->setRequest($request);
$this->configurationManager->setConfiguration($configuration);
return $request;
// todo: Outdated todo, recheck in v13.
// Shouldn't the configuration manager object which is a singleton be stateless?
// At this point we give the configuration manager a state, while we could directly pass the
// configuration (i.e. controllerName, actionName and such), directly to the request
// handler, which then creates stateful request objects.
// Once this has changed, \TYPO3\CMS\Extbase\Mvc\Web\RequestBuilder::loadDefaultValues does not need
// to fetch this configuration from the configuration manager.
}
/**
* Runs the Extbase Framework by resolving an appropriate Request Handler and passing control to it.
* If the Framework is not initialized yet, it will be initialized.
*
* This is usually used in Frontend plugins.
* This method will be marked as internal in the future, use EXTBASEPLUGIN in TypoScript to execute an Extbase plugin
* instead.
*
* @param string $content The content. Not used
* @param array $configuration The TS configuration array
* @param ServerRequestInterface $request the incoming server request
* @return string $content The processed content
*/
#[AsAllowedCallable]
public function run(string $content, array $configuration, ServerRequestInterface $request): string
{
$request = $this->initialize($configuration, $request);
return $this->handleFrontendRequest($request);
}
/**
* Used for any Extbase Plugin in the Frontend, be sure to run $this->initialize() before.
*
* @internal
*/
public function handleFrontendRequest(ServerRequestInterface $request): string
{
$extbaseRequest = $this->extbaseRequestBuilder->build($request);
if (!$this->isExtbaseRequestCacheable($extbaseRequest)) {
if ($this->cObj->getUserObjectType() === ContentObjectRenderer::OBJECTTYPE_USER) {
// ContentObjectRenderer::convertToUserIntObject() will recreate the object,
// so we have to stop the request here before the action is actually called
$this->cObj->convertToUserIntObject();
return '';
}
}
// Dispatch the extbase request
$response = $this->dispatcher->dispatch($extbaseRequest);
if ($response->getStatusCode() >= 300) {
// Avoid caching the plugin when we issue a redirect or error response
// This means that even when an action is configured as cachable
// we avoid the plugin to be cached, but keep the page cache untouched
if ($this->cObj->getUserObjectType() === ContentObjectRenderer::OBJECTTYPE_USER) {
$this->cObj->convertToUserIntObject();
}
}
// Usually coming from an error action, ensure all caches are cleared
if ($response->getStatusCode() === 400) {
$this->clearCacheOnError($request);
}
if ($response->hasHeader('Content-Type')) {
// Typically used when extbase for instance created a json response.
$request->getAttribute('frontend.page.parts')->setHttpContentType($response->getHeaderLine('Content-Type'));
// Do not send the header directly (see below)
$response = $response->withoutHeader('Content-Type');
}
$responseData = $request->getAttribute('frontend.response.data');
if ($responseData instanceof ResponseData) {
foreach ($response->getHeaders() as $name => $values) {
$responseData->setHeader($name, $values);
}
// @todo: Get rid of this in TYPO3 v15. See todos in ResponseData.
if ($response->getStatusCode() >= 300) {
$responseData->setProtocolVersion($response->getProtocolVersion());
$responseData->setStatusCode($response->getStatusCode());
$responseData->setReasonPhrase($response->getReasonPhrase());
}
}
$body = $response->getBody();
$body->rewind();
$content = $body->getContents();
$this->resetSingletons();
$this->cacheService->clearCachesOfRegisteredPageIds();
return $content;
}
/**
* Entrypoint for backend modules, handling PSR-7 requests/responses.
*
* Creates an Extbase Request, dispatches it and then returns the Response
*
* @internal
*/
public function handleBackendRequest(ServerRequestInterface $request): ResponseInterface
{
// build the configuration from the module, included in the current request
$module = $request->getAttribute('module');
$configuration = [
'extensionName' => $module?->getExtensionName(),
'pluginName' => $module?->getIdentifier(),
];
$request = $this->initialize($configuration, $request);
$extbaseRequest = $this->extbaseRequestBuilder->build($request);
$response = $this->dispatcher->dispatch($extbaseRequest);
$this->resetSingletons();
$this->cacheService->clearCachesOfRegisteredPageIds();
return $response;
}
/**
* Clear cache of current page on error. Needed because we want a re-evaluation of the data.
*/
protected function clearCacheOnError(ServerRequestInterface $request): void
{
$extbaseSettings = $this->configurationManager->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK);
if (isset($extbaseSettings['persistence']['enableAutomaticCacheClearing']) && $extbaseSettings['persistence']['enableAutomaticCacheClearing'] === '1') {
$pageId = $request->getAttribute('frontend.page.information')?->getId();
if ($pageId !== null) {
$this->cacheService->clearPageCache([$pageId]);
}
}
}
/**
* Resets global singletons for the next plugin
*/
protected function resetSingletons(): void
{
$this->persistenceManager->persistAll();
}
protected function isExtbaseRequestCacheable(RequestInterface $extbaseRequest): bool
{
$controllerClassName = $extbaseRequest->getControllerObjectName();
$actionName = $extbaseRequest->getControllerActionName();
$frameworkConfiguration = $this->configurationManager->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK);
$nonCacheableActions = $frameworkConfiguration['controllerConfiguration'][$controllerClassName]['nonCacheableActions'] ?? null;
if (!is_array($nonCacheableActions)) {
return true;
}
return !in_array($actionName, $nonCacheableActions, true);
}
}