PageRenderTime 45ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/typo3/sysext/extbase/Classes/Core/Bootstrap.php

https://github.com/TYPO3/TYPO3.CMS
PHP | 254 lines | 147 code | 22 blank | 85 comment | 18 complexity | e98417a96bdccb6650a2b0459a3fb96f MD5 | raw file
  1. <?php
  2. declare(strict_types=1);
  3. /*
  4. * This file is part of the TYPO3 CMS project.
  5. *
  6. * It is free software; you can redistribute it and/or modify it under
  7. * the terms of the GNU General Public License, either version 2
  8. * of the License, or any later version.
  9. *
  10. * For the full copyright and license information, please read the
  11. * LICENSE.txt file that was distributed with this source code.
  12. *
  13. * The TYPO3 project - inspiring people to share!
  14. */
  15. namespace TYPO3\CMS\Extbase\Core;
  16. use Psr\Container\ContainerInterface;
  17. use Psr\Http\Message\ResponseInterface;
  18. use Psr\Http\Message\ServerRequestInterface;
  19. use TYPO3\CMS\Core\Core\Environment;
  20. use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface;
  21. use TYPO3\CMS\Extbase\Mvc\Dispatcher;
  22. use TYPO3\CMS\Extbase\Mvc\RequestInterface;
  23. use TYPO3\CMS\Extbase\Mvc\Web\RequestBuilder;
  24. use TYPO3\CMS\Extbase\Persistence\PersistenceManagerInterface;
  25. use TYPO3\CMS\Extbase\Service\CacheService;
  26. use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
  27. use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
  28. /**
  29. * Creates a request and dispatches it to the controller which was specified
  30. * by TS Setup, flexForm and returns the content.
  31. *
  32. * This class is the main entry point for extbase extensions.
  33. */
  34. class Bootstrap
  35. {
  36. public static array $persistenceClasses = [];
  37. /**
  38. * Set by UserContentObject (USER) via setContentObjectRenderer() in frontend
  39. */
  40. protected ?ContentObjectRenderer $cObj = null;
  41. protected ContainerInterface $container;
  42. protected ConfigurationManagerInterface $configurationManager;
  43. protected PersistenceManagerInterface $persistenceManager;
  44. protected CacheService $cacheService;
  45. protected Dispatcher $dispatcher;
  46. protected RequestBuilder $extbaseRequestBuilder;
  47. public function __construct(
  48. ContainerInterface $container,
  49. ConfigurationManagerInterface $configurationManager,
  50. PersistenceManagerInterface $persistenceManager,
  51. CacheService $cacheService,
  52. Dispatcher $dispatcher,
  53. RequestBuilder $extbaseRequestBuilder
  54. ) {
  55. $this->container = $container;
  56. $this->configurationManager = $configurationManager;
  57. $this->persistenceManager = $persistenceManager;
  58. $this->cacheService = $cacheService;
  59. $this->dispatcher = $dispatcher;
  60. $this->extbaseRequestBuilder = $extbaseRequestBuilder;
  61. }
  62. /**
  63. * Called for frontend plugins from UserContentObject via ContentObjectRenderer->callUserFunction().
  64. */
  65. public function setContentObjectRenderer(ContentObjectRenderer $cObj)
  66. {
  67. $this->cObj = $cObj;
  68. }
  69. /**
  70. * Explicitly initializes all necessary Extbase objects by invoking the various initialize* methods.
  71. *
  72. * Usually this method is only called from unit tests or other applications which need a more fine grained control over
  73. * the initialization and request handling process. Most other applications just call the run() method.
  74. *
  75. * @param array $configuration The TS configuration array
  76. * @throws \RuntimeException
  77. * @see run()
  78. */
  79. public function initialize(array $configuration): void
  80. {
  81. if (!Environment::isCli()) {
  82. if (!isset($configuration['extensionName']) || $configuration['extensionName'] === '') {
  83. throw new \RuntimeException('Invalid configuration: "extensionName" is not set', 1290623020);
  84. }
  85. if (!isset($configuration['pluginName']) || $configuration['pluginName'] === '') {
  86. throw new \RuntimeException('Invalid configuration: "pluginName" is not set', 1290623027);
  87. }
  88. }
  89. $this->initializeConfiguration($configuration);
  90. }
  91. /**
  92. * Initializes the Object framework.
  93. *
  94. * @param array $configuration
  95. * @see initialize()
  96. * @internal
  97. */
  98. public function initializeConfiguration(array $configuration): void
  99. {
  100. $this->cObj ??= $this->container->get(ContentObjectRenderer::class);
  101. $this->configurationManager->setContentObject($this->cObj);
  102. $this->configurationManager->setConfiguration($configuration);
  103. // todo: Shouldn't the configuration manager object – which is a singleton – be stateless?
  104. // todo: At this point we give the configuration manager a state, while we could directly pass the
  105. // todo: configuration (i.e. controllerName, actionName and such), directly to the request
  106. // todo: handler, which then creates stateful request objects.
  107. // todo: Once this has changed, \TYPO3\CMS\Extbase\Mvc\Web\RequestBuilder::loadDefaultValues does not need
  108. // todo: to fetch this configuration from the configuration manager.
  109. }
  110. /**
  111. * Runs the the Extbase Framework by resolving an appropriate Request Handler and passing control to it.
  112. * If the Framework is not initialized yet, it will be initialized.
  113. *
  114. * This is usually used in Frontend plugins.
  115. *
  116. * @param string $content The content. Not used
  117. * @param array $configuration The TS configuration array
  118. * @param ServerRequestInterface $request the incoming server request
  119. * @return string $content The processed content
  120. */
  121. public function run(string $content, array $configuration, ServerRequestInterface $request): string
  122. {
  123. $this->initialize($configuration);
  124. return $this->handleFrontendRequest($request);
  125. }
  126. protected function handleFrontendRequest(ServerRequestInterface $request): string
  127. {
  128. $extbaseRequest = $this->extbaseRequestBuilder->build($request);
  129. if (!$this->isExtbaseRequestCacheable($extbaseRequest)) {
  130. if ($this->cObj->getUserObjectType() === ContentObjectRenderer::OBJECTTYPE_USER) {
  131. // ContentObjectRenderer::convertToUserIntObject() will recreate the object,
  132. // so we have to stop the request here before the action is actually called
  133. $this->cObj->convertToUserIntObject();
  134. return '';
  135. }
  136. }
  137. // Dispatch the extbase request
  138. $response = $this->dispatcher->dispatch($extbaseRequest);
  139. if ($response->getStatusCode() >= 300) {
  140. // Avoid caching the plugin when we issue a redirect or error response
  141. // This means that even when an action is configured as cachable
  142. // we avoid the plugin to be cached, but keep the page cache untouched
  143. if ($this->cObj->getUserObjectType() === ContentObjectRenderer::OBJECTTYPE_USER) {
  144. $this->cObj->convertToUserIntObject();
  145. }
  146. }
  147. // Usually coming from an error action, ensure all caches are cleared
  148. if ($response->getStatusCode() === 400) {
  149. $this->clearCacheOnError();
  150. }
  151. // In case TSFE is available and this is a json response, we have to let TSFE know we have a specific Content-Type
  152. if (($typoScriptFrontendController = ($GLOBALS['TSFE'] ?? null)) instanceof TypoScriptFrontendController
  153. && str_starts_with($response->getHeaderLine('Content-Type'), 'application/json')
  154. ) {
  155. // Do not send the header directly (see below)
  156. $response = $response->withoutHeader('Content-Type');
  157. $typoScriptFrontendController->setContentType('application/json');
  158. }
  159. if (headers_sent() === false) {
  160. foreach ($response->getHeaders() as $name => $values) {
  161. foreach ($values as $value) {
  162. header(sprintf('%s: %s', $name, $value));
  163. }
  164. }
  165. // Set status code from extbase response
  166. // @todo: Remove when ContentObjectRenderer is response aware
  167. if ($response->getStatusCode() >= 300) {
  168. header('HTTP/' . $response->getProtocolVersion() . ' ' . $response->getStatusCode() . ' ' . $response->getReasonPhrase());
  169. }
  170. }
  171. $body = $response->getBody();
  172. $body->rewind();
  173. $content = $body->getContents();
  174. $this->resetSingletons();
  175. $this->cacheService->clearCachesOfRegisteredPageIds();
  176. return $content;
  177. }
  178. /**
  179. * Entrypoint for backend modules, handling PSR-7 requests/responses.
  180. *
  181. * Creates an Extbase Request, dispatches it and then returns the Response
  182. *
  183. * @param ServerRequestInterface $request
  184. * @return ResponseInterface
  185. * @internal
  186. */
  187. public function handleBackendRequest(ServerRequestInterface $request): ResponseInterface
  188. {
  189. // build the configuration from the module, included in the current request
  190. $module = $request->getAttribute('module');
  191. $configuration = [
  192. 'extensionName' => $module?->getExtensionName(),
  193. 'pluginName' => $module?->getIdentifier(),
  194. ];
  195. $this->initialize($configuration);
  196. $extbaseRequest = $this->extbaseRequestBuilder->build($request);
  197. $response = $this->dispatcher->dispatch($extbaseRequest);
  198. $this->resetSingletons();
  199. $this->cacheService->clearCachesOfRegisteredPageIds();
  200. return $response;
  201. }
  202. /**
  203. * Clear cache of current page on error. Needed because we want a re-evaluation of the data.
  204. */
  205. protected function clearCacheOnError(): void
  206. {
  207. $extbaseSettings = $this->configurationManager->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK);
  208. if (isset($extbaseSettings['persistence']['enableAutomaticCacheClearing']) && $extbaseSettings['persistence']['enableAutomaticCacheClearing'] === '1') {
  209. if (isset($GLOBALS['TSFE'])) {
  210. $this->cacheService->clearPageCache([$GLOBALS['TSFE']->id]);
  211. }
  212. }
  213. }
  214. /**
  215. * Resets global singletons for the next plugin
  216. */
  217. protected function resetSingletons(): void
  218. {
  219. $this->persistenceManager->persistAll();
  220. }
  221. protected function isExtbaseRequestCacheable(RequestInterface $extbaseRequest): bool
  222. {
  223. $controllerClassName = $extbaseRequest->getControllerObjectName();
  224. $actionName = $extbaseRequest->getControllerActionName();
  225. $frameworkConfiguration = $this->configurationManager->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK);
  226. $nonCacheableActions = $frameworkConfiguration['controllerConfiguration'][$controllerClassName]['nonCacheableActions'] ?? null;
  227. if (!is_array($nonCacheableActions)) {
  228. return true;
  229. }
  230. return !in_array($actionName, $nonCacheableActions, true);
  231. }
  232. }