TYPO3 v15 dev-main snapshot ()
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user