Passed
Push — master ( a73d8f...d183e8 )
by
unknown
14:04
created

Bootstrap::resetSingletons()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the TYPO3 CMS project.
7
 *
8
 * It is free software; you can redistribute it and/or modify it under
9
 * the terms of the GNU General Public License, either version 2
10
 * of the License, or any later version.
11
 *
12
 * For the full copyright and license information, please read the
13
 * LICENSE.txt file that was distributed with this source code.
14
 *
15
 * The TYPO3 project - inspiring people to share!
16
 */
17
18
namespace TYPO3\CMS\Extbase\Core;
19
20
use Psr\Container\ContainerInterface;
21
use Psr\Http\Message\ResponseInterface;
22
use Psr\Http\Message\ServerRequestInterface;
23
use TYPO3\CMS\Backend\Routing\Route;
24
use TYPO3\CMS\Core\Cache\CacheManager;
25
use TYPO3\CMS\Core\Core\Environment;
26
use TYPO3\CMS\Core\Utility\GeneralUtility;
27
use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface;
28
use TYPO3\CMS\Extbase\Configuration\RequestHandlersConfigurationFactory;
29
use TYPO3\CMS\Extbase\Mvc\Dispatcher;
30
use TYPO3\CMS\Extbase\Mvc\RequestHandlerResolver;
31
use TYPO3\CMS\Extbase\Mvc\RequestInterface;
32
use TYPO3\CMS\Extbase\Mvc\Web\RequestBuilder;
33
use TYPO3\CMS\Extbase\Persistence\ClassesConfigurationFactory;
34
use TYPO3\CMS\Extbase\Persistence\PersistenceManagerInterface;
35
use TYPO3\CMS\Extbase\Service\CacheService;
36
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
37
38
/**
39
 * Creates a request and dispatches it to the controller which was specified
40
 * by TS Setup, flexForm and returns the content.
41
 *
42
 * This class is the main entry point for extbase extensions.
43
 */
44
class Bootstrap
45
{
46
    /**
47
     * @var array
48
     */
49
    public static $persistenceClasses = [];
50
51
    /**
52
     * Back reference to the parent content object
53
     * This has to be public as it is set directly from TYPO3
54
     *
55
     * @var \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer
56
     */
57
    public $cObj;
58
59
    protected ContainerInterface $container;
60
    protected ConfigurationManagerInterface $configurationManager;
61
    protected PersistenceManagerInterface $persistenceManager;
62
    protected RequestHandlerResolver $requestHandlerResolver;
63
    protected CacheService $cacheService;
64
    protected Dispatcher $dispatcher;
65
    protected RequestBuilder $extbaseRequestBuilder;
66
67
    public function __construct(
68
        ContainerInterface $container,
69
        ConfigurationManagerInterface $configurationManager,
70
        PersistenceManagerInterface $persistenceManager,
71
        RequestHandlerResolver $requestHandlerResolver,
72
        CacheService $cacheService,
73
        Dispatcher $dispatcher,
74
        RequestBuilder $extbaseRequestBuilder
75
    ) {
76
        $this->container = $container;
77
        $this->configurationManager = $configurationManager;
78
        $this->persistenceManager = $persistenceManager;
79
        $this->requestHandlerResolver = $requestHandlerResolver;
80
        $this->cacheService = $cacheService;
81
        $this->dispatcher = $dispatcher;
82
        $this->extbaseRequestBuilder = $extbaseRequestBuilder;
83
    }
84
85
    /**
86
     * Explicitly initializes all necessary Extbase objects by invoking the various initialize* methods.
87
     *
88
     * Usually this method is only called from unit tests or other applications which need a more fine grained control over
89
     * the initialization and request handling process. Most other applications just call the run() method.
90
     *
91
     * @param array $configuration The TS configuration array
92
     * @throws \RuntimeException
93
     * @see run()
94
     */
95
    public function initialize(array $configuration): void
96
    {
97
        if (!Environment::isCli()) {
98
            if (!isset($configuration['extensionName']) || $configuration['extensionName'] === '') {
99
                throw new \RuntimeException('Invalid configuration: "extensionName" is not set', 1290623020);
100
            }
101
            if (!isset($configuration['pluginName']) || $configuration['pluginName'] === '') {
102
                throw new \RuntimeException('Invalid configuration: "pluginName" is not set', 1290623027);
103
            }
104
        }
105
        $this->initializeConfiguration($configuration);
106
        $this->initializePersistenceClassesConfiguration();
107
        $this->initializeRequestHandlersConfiguration();
108
    }
109
110
    /**
111
     * Initializes the Object framework.
112
     *
113
     * @param array $configuration
114
     * @see initialize()
115
     * @internal
116
     */
117
    public function initializeConfiguration(array $configuration): void
118
    {
119
        $this->cObj ??= $this->container->get(ContentObjectRenderer::class);
120
        $this->configurationManager->setContentObject($this->cObj);
121
        $this->configurationManager->setConfiguration($configuration);
122
        // todo: Shouldn't the configuration manager object – which is a singleton – be stateless?
123
        // todo: At this point we give the configuration manager a state, while we could directly pass the
124
        // todo: configuration (i.e. controllerName, actionName and such), directly to the request
125
        // todo: handler, which then creates stateful request objects.
126
        // todo: Once this has changed, \TYPO3\CMS\Extbase\Mvc\Web\RequestBuilder::loadDefaultValues does not need
127
        // todo: to fetch this configuration from the configuration manager.
128
    }
129
130
    /**
131
     * @throws \TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException
132
     */
133
    private function initializePersistenceClassesConfiguration(): void
134
    {
135
        $cacheManager = GeneralUtility::makeInstance(CacheManager::class);
136
        GeneralUtility::makeInstance(ClassesConfigurationFactory::class, $cacheManager)
137
            ->createClassesConfiguration();
138
    }
139
140
    /**
141
     * @throws \TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException
142
     */
143
    private function initializeRequestHandlersConfiguration(): void
144
    {
145
        $cacheManager = GeneralUtility::makeInstance(CacheManager::class);
146
        GeneralUtility::makeInstance(RequestHandlersConfigurationFactory::class, $cacheManager)
147
            ->createRequestHandlersConfiguration();
148
    }
149
150
    /**
151
     * Runs the the Extbase Framework by resolving an appropriate Request Handler and passing control to it.
152
     * If the Framework is not initialized yet, it will be initialized.
153
     *
154
     * This is usually used in Frontend plugins.
155
     *
156
     * @param string $content The content. Not used
157
     * @param array $configuration The TS configuration array
158
     * @return string $content The processed content
159
     */
160
    public function run(string $content, array $configuration, ?ServerRequestInterface $request = null): string
161
    {
162
        $request = $request ?? $GLOBALS['TYPO3_REQUEST'];
163
        $this->initialize($configuration);
164
        return $this->handleFrontendRequest($request);
165
    }
166
167
    protected function handleFrontendRequest(ServerRequestInterface $request): string
168
    {
169
        $extbaseRequest = $this->extbaseRequestBuilder->build($request);
170
        if (!$this->isExtbaseRequestCacheable($extbaseRequest)) {
171
            if ($this->cObj->getUserObjectType() === ContentObjectRenderer::OBJECTTYPE_USER) {
0 ignored issues
show
introduced by
The condition $this->cObj->getUserObje...nderer::OBJECTTYPE_USER is always false.
Loading history...
172
                // ContentObjectRenderer::convertToUserIntObject() will recreate the object,
173
                // so we have to stop the request here before the action is actually called
174
                $this->cObj->convertToUserIntObject();
175
                return '';
176
            }
177
            $extbaseRequest->setIsCached(false);
178
        } else {
179
            $extbaseRequest->setIsCached(true);
180
        }
181
182
        // Dispatch the extbase request
183
        $requestHandler = $this->requestHandlerResolver->resolveRequestHandler($extbaseRequest);
184
        $response = $requestHandler->handleRequest($extbaseRequest);
185
        if ($response->getStatusCode() >= 300) {
186
            // Avoid caching the plugin when we issue a redirect or error response
187
            // This means that even when an action is configured as cachable
188
            // we avoid the plugin to be cached, but keep the page cache untouched
189
            if ($this->cObj->getUserObjectType() === ContentObjectRenderer::OBJECTTYPE_USER) {
0 ignored issues
show
introduced by
The condition $this->cObj->getUserObje...nderer::OBJECTTYPE_USER is always false.
Loading history...
190
                $this->cObj->convertToUserIntObject();
191
            }
192
        }
193
        // Usually coming from an error action, ensure all caches are cleared
194
        if ($response->getStatusCode() === 400) {
195
            $this->clearCacheOnError();
196
        }
197
        if (headers_sent() === false) {
198
            foreach ($response->getHeaders() as $name => $values) {
199
                foreach ($values as $value) {
200
                    header(sprintf('%s: %s', $name, $value));
201
                }
202
            }
203
        }
204
        $body = $response->getBody();
205
        $body->rewind();
206
        $content = $body->getContents();
207
        $this->resetSingletons();
208
        $this->cacheService->clearCachesOfRegisteredPageIds();
209
        return $content;
210
    }
211
212
    /**
213
     * Entrypoint for backend modules, handling PSR-7 requests/responses.
214
     *
215
     * Creates an Extbase Request, dispatches it and then returns the Response
216
     *
217
     * @param ServerRequestInterface $request
218
     * @return ResponseInterface
219
     * @internal
220
     */
221
    public function handleBackendRequest(ServerRequestInterface $request): ResponseInterface
222
    {
223
        // build the configuration from the Server request / route
224
        /** @var Route $route */
225
        $route = $request->getAttribute('route');
226
        $moduleConfiguration = $route->getOption('moduleConfiguration');
227
        $configuration = [
228
            'extensionName' => $moduleConfiguration['extensionName'],
229
            'pluginName' => $route->getOption('moduleName')
230
        ];
231
232
        $this->initialize($configuration);
233
        $extbaseRequest = $this->extbaseRequestBuilder->build($request);
234
        $response = $this->dispatcher->dispatch($extbaseRequest);
235
        $this->resetSingletons();
236
        $this->cacheService->clearCachesOfRegisteredPageIds();
237
        return $response;
238
    }
239
240
    /**
241
     * Clear cache of current page on error. Needed because we want a re-evaluation of the data.
242
     */
243
    protected function clearCacheOnError(): void
244
    {
245
        $extbaseSettings = $this->configurationManager->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK);
246
        if (isset($extbaseSettings['persistence']['enableAutomaticCacheClearing']) && $extbaseSettings['persistence']['enableAutomaticCacheClearing'] === '1') {
247
            if (isset($GLOBALS['TSFE'])) {
248
                $this->cacheService->clearPageCache([$GLOBALS['TSFE']->id]);
249
            }
250
        }
251
    }
252
253
    /**
254
     * Resets global singletons for the next plugin
255
     */
256
    protected function resetSingletons(): void
257
    {
258
        $this->persistenceManager->persistAll();
259
    }
260
261
    protected function isExtbaseRequestCacheable(RequestInterface $extbaseRequest): bool
262
    {
263
        $controllerClassName = $extbaseRequest->getControllerObjectName();
264
        $actionName = $extbaseRequest->getControllerActionName();
0 ignored issues
show
Bug introduced by
The method getControllerActionName() does not exist on TYPO3\CMS\Extbase\Mvc\RequestInterface. Did you maybe mean getControllerObjectName()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

264
        /** @scrutinizer ignore-call */ 
265
        $actionName = $extbaseRequest->getControllerActionName();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
265
        $frameworkConfiguration = $this->configurationManager->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK);
266
        $nonCacheableActions = $frameworkConfiguration['controllerConfiguration'][$controllerClassName]['nonCacheableActions'] ?? null;
267
        if (!is_array($nonCacheableActions)) {
268
            return true;
269
        }
270
        return !in_array($actionName, $nonCacheableActions, true);
271
    }
272
}
273