Files
cms-extbase/Classes/Core/Bootstrap.php
T

254 lines
11 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?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);
}
}