Passed
Push — master ( 92999a...0943a9 )
by
unknown
22:12 queued 10:13
created

Bootstrap::handleFrontendRequest()   C

Complexity

Conditions 12
Paths 73

Size

Total Lines 61
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 29
dl 0
loc 61
rs 6.9666
c 0
b 0
f 0
cc 12
nc 73
nop 1

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
38
39
/**
40
 * Creates a request and dispatches it to the controller which was specified
41
 * by TS Setup, flexForm and returns the content.
42
 *
43
 * This class is the main entry point for extbase extensions.
44
 */
45
class Bootstrap
46
{
47
    /**
48
     * @var array
49
     */
50
    public static $persistenceClasses = [];
51
52
    /**
53
     * Back reference to the parent content object
54
     * This has to be public as it is set directly from TYPO3
55
     *
56
     * @var \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer
57
     */
58
    public $cObj;
59
60
    protected ContainerInterface $container;
61
    protected ConfigurationManagerInterface $configurationManager;
62
    protected PersistenceManagerInterface $persistenceManager;
63
    protected RequestHandlerResolver $requestHandlerResolver;
64
    protected CacheService $cacheService;
65
    protected Dispatcher $dispatcher;
66
    protected RequestBuilder $extbaseRequestBuilder;
67
68
    public function __construct(
69
        ContainerInterface $container,
70
        ConfigurationManagerInterface $configurationManager,
71
        PersistenceManagerInterface $persistenceManager,
72
        RequestHandlerResolver $requestHandlerResolver,
73
        CacheService $cacheService,
74
        Dispatcher $dispatcher,
75
        RequestBuilder $extbaseRequestBuilder
76
    ) {
77
        $this->container = $container;
78
        $this->configurationManager = $configurationManager;
79
        $this->persistenceManager = $persistenceManager;
80
        $this->requestHandlerResolver = $requestHandlerResolver;
81
        $this->cacheService = $cacheService;
82
        $this->dispatcher = $dispatcher;
83
        $this->extbaseRequestBuilder = $extbaseRequestBuilder;
84
    }
85
86
    /**
87
     * Explicitly initializes all necessary Extbase objects by invoking the various initialize* methods.
88
     *
89
     * Usually this method is only called from unit tests or other applications which need a more fine grained control over
90
     * the initialization and request handling process. Most other applications just call the run() method.
91
     *
92
     * @param array $configuration The TS configuration array
93
     * @throws \RuntimeException
94
     * @see run()
95
     */
96
    public function initialize(array $configuration): void
97
    {
98
        if (!Environment::isCli()) {
99
            if (!isset($configuration['extensionName']) || $configuration['extensionName'] === '') {
100
                throw new \RuntimeException('Invalid configuration: "extensionName" is not set', 1290623020);
101
            }
102
            if (!isset($configuration['pluginName']) || $configuration['pluginName'] === '') {
103
                throw new \RuntimeException('Invalid configuration: "pluginName" is not set', 1290623027);
104
            }
105
        }
106
        $this->initializeConfiguration($configuration);
107
        $this->initializePersistenceClassesConfiguration();
108
        $this->initializeRequestHandlersConfiguration();
109
    }
110
111
    /**
112
     * Initializes the Object framework.
113
     *
114
     * @param array $configuration
115
     * @see initialize()
116
     * @internal
117
     */
118
    public function initializeConfiguration(array $configuration): void
119
    {
120
        $this->cObj ??= $this->container->get(ContentObjectRenderer::class);
121
        $this->configurationManager->setContentObject($this->cObj);
122
        $this->configurationManager->setConfiguration($configuration);
123
        // todo: Shouldn't the configuration manager object – which is a singleton – be stateless?
124
        // todo: At this point we give the configuration manager a state, while we could directly pass the
125
        // todo: configuration (i.e. controllerName, actionName and such), directly to the request
126
        // todo: handler, which then creates stateful request objects.
127
        // todo: Once this has changed, \TYPO3\CMS\Extbase\Mvc\Web\RequestBuilder::loadDefaultValues does not need
128
        // todo: to fetch this configuration from the configuration manager.
129
    }
130
131
    /**
132
     * @throws \TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException
133
     */
134
    private function initializePersistenceClassesConfiguration(): void
135
    {
136
        $cacheManager = GeneralUtility::makeInstance(CacheManager::class);
137
        GeneralUtility::makeInstance(ClassesConfigurationFactory::class, $cacheManager)
138
            ->createClassesConfiguration();
139
    }
140
141
    /**
142
     * @throws \TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException
143
     */
144
    private function initializeRequestHandlersConfiguration(): void
145
    {
146
        $cacheManager = GeneralUtility::makeInstance(CacheManager::class);
147
        GeneralUtility::makeInstance(RequestHandlersConfigurationFactory::class, $cacheManager)
148
            ->createRequestHandlersConfiguration();
149
    }
150
151
    /**
152
     * Runs the the Extbase Framework by resolving an appropriate Request Handler and passing control to it.
153
     * If the Framework is not initialized yet, it will be initialized.
154
     *
155
     * This is usually used in Frontend plugins.
156
     *
157
     * @param string $content The content. Not used
158
     * @param array $configuration The TS configuration array
159
     * @return string $content The processed content
160
     */
161
    public function run(string $content, array $configuration, ?ServerRequestInterface $request = null): string
162
    {
163
        $request = $request ?? $GLOBALS['TYPO3_REQUEST'];
164
        $this->initialize($configuration);
165
        return $this->handleFrontendRequest($request);
166
    }
167
168
    protected function handleFrontendRequest(ServerRequestInterface $request): string
169
    {
170
        $extbaseRequest = $this->extbaseRequestBuilder->build($request);
171
        if (!$this->isExtbaseRequestCacheable($extbaseRequest)) {
172
            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...
173
                // ContentObjectRenderer::convertToUserIntObject() will recreate the object,
174
                // so we have to stop the request here before the action is actually called
175
                $this->cObj->convertToUserIntObject();
176
                return '';
177
            }
178
        }
179
180
        // Dispatch the extbase request
181
        $requestHandler = $this->requestHandlerResolver->resolveRequestHandler($extbaseRequest);
182
        $response = $requestHandler->handleRequest($extbaseRequest);
183
        if ($response->getStatusCode() >= 300) {
184
            // Avoid caching the plugin when we issue a redirect or error response
185
            // This means that even when an action is configured as cachable
186
            // we avoid the plugin to be cached, but keep the page cache untouched
187
            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...
188
                $this->cObj->convertToUserIntObject();
189
            }
190
        }
191
        // Usually coming from an error action, ensure all caches are cleared
192
        if ($response->getStatusCode() === 400) {
193
            $this->clearCacheOnError();
194
        }
195
196
        // In case TSFE is available and this is a json response, we have
197
        // to take the TypoScript settings regarding charset into account.
198
        // @todo Since HTML5 only utf-8 is a valid charset, this settings should be deprecated
199
        if (($typoScriptFrontendController = ($GLOBALS['TSFE'] ?? null)) instanceof TypoScriptFrontendController
200
            && strpos($response->getHeaderLine('Content-Type'), 'application/json') === 0
201
        ) {
202
            // Unset the already defined Content-Type
203
            $response = $response->withoutHeader('Content-Type');
204
            if (empty($typoScriptFrontendController->config['config']['disableCharsetHeader'])) {
205
                // If the charset header is *not* disabled in configuration,
206
                // TypoScriptFrontendController will send the header later with the Content-Type which we set here.
207
                $typoScriptFrontendController->setContentType('application/json');
208
            } else {
209
                // Although the charset header is disabled in configuration, we *must* send a Content-Type header here.
210
                // Content-Type headers optionally carry charset information at the same time.
211
                // Since we have the information about the charset, there is no reason to not include the charset information although disabled in TypoScript.
212
                $response = $response->withHeader('Content-Type', 'application/json; charset=' . trim($typoScriptFrontendController->metaCharset));
213
            }
214
        }
215
216
        if (headers_sent() === false) {
217
            foreach ($response->getHeaders() as $name => $values) {
218
                foreach ($values as $value) {
219
                    header(sprintf('%s: %s', $name, $value));
220
                }
221
            }
222
        }
223
        $body = $response->getBody();
224
        $body->rewind();
225
        $content = $body->getContents();
226
        $this->resetSingletons();
227
        $this->cacheService->clearCachesOfRegisteredPageIds();
228
        return $content;
229
    }
230
231
    /**
232
     * Entrypoint for backend modules, handling PSR-7 requests/responses.
233
     *
234
     * Creates an Extbase Request, dispatches it and then returns the Response
235
     *
236
     * @param ServerRequestInterface $request
237
     * @return ResponseInterface
238
     * @internal
239
     */
240
    public function handleBackendRequest(ServerRequestInterface $request): ResponseInterface
241
    {
242
        // build the configuration from the Server request / route
243
        /** @var Route $route */
244
        $route = $request->getAttribute('route');
245
        $moduleConfiguration = $route->getOption('moduleConfiguration');
246
        $configuration = [
247
            'extensionName' => $moduleConfiguration['extensionName'],
248
            'pluginName' => $route->getOption('moduleName')
249
        ];
250
251
        $this->initialize($configuration);
252
        $extbaseRequest = $this->extbaseRequestBuilder->build($request);
253
        $response = $this->dispatcher->dispatch($extbaseRequest);
254
        $this->resetSingletons();
255
        $this->cacheService->clearCachesOfRegisteredPageIds();
256
        return $response;
257
    }
258
259
    /**
260
     * Clear cache of current page on error. Needed because we want a re-evaluation of the data.
261
     */
262
    protected function clearCacheOnError(): void
263
    {
264
        $extbaseSettings = $this->configurationManager->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK);
265
        if (isset($extbaseSettings['persistence']['enableAutomaticCacheClearing']) && $extbaseSettings['persistence']['enableAutomaticCacheClearing'] === '1') {
266
            if (isset($GLOBALS['TSFE'])) {
267
                $this->cacheService->clearPageCache([$GLOBALS['TSFE']->id]);
268
            }
269
        }
270
    }
271
272
    /**
273
     * Resets global singletons for the next plugin
274
     */
275
    protected function resetSingletons(): void
276
    {
277
        $this->persistenceManager->persistAll();
278
    }
279
280
    protected function isExtbaseRequestCacheable(RequestInterface $extbaseRequest): bool
281
    {
282
        $controllerClassName = $extbaseRequest->getControllerObjectName();
283
        $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

283
        /** @scrutinizer ignore-call */ 
284
        $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...
284
        $frameworkConfiguration = $this->configurationManager->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK);
285
        $nonCacheableActions = $frameworkConfiguration['controllerConfiguration'][$controllerClassName]['nonCacheableActions'] ?? null;
286
        if (!is_array($nonCacheableActions)) {
287
            return true;
288
        }
289
        return !in_array($actionName, $nonCacheableActions, true);
290
    }
291
}
292