Test Failed
Branch master (7b1793)
by Tymoteusz
15:35
created

RequestHandler   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 310
Duplicated Lines 2.58 %

Importance

Changes 0
Metric Value
dl 8
loc 310
rs 8.3157
c 0
b 0
f 0
wmc 43

7 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 3 1
A initializeOutputCompression() 3 7 4
F handleRequest() 5 197 30
A initializeController() 0 18 1
A initializeTimeTracker() 0 7 3
A getPriority() 0 3 1
A canHandleRequest() 0 3 3

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like RequestHandler often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use RequestHandler, and based on these observations, apply Extract Interface, too.

1
<?php
2
namespace TYPO3\CMS\Frontend\Http;
3
4
/*
5
 * This file is part of the TYPO3 CMS project.
6
 *
7
 * It is free software; you can redistribute it and/or modify it under
8
 * the terms of the GNU General Public License, either version 2
9
 * of the License, or any later version.
10
 *
11
 * For the full copyright and license information, please read the
12
 * LICENSE.txt file that was distributed with this source code.
13
 *
14
 * The TYPO3 project - inspiring people to share!
15
 */
16
17
use TYPO3\CMS\Backend\FrontendBackendUserAuthentication;
18
use TYPO3\CMS\Core\Core\Bootstrap;
19
use TYPO3\CMS\Core\FrontendEditing\FrontendEditingController;
20
use TYPO3\CMS\Core\Http\RequestHandlerInterface;
21
use TYPO3\CMS\Core\Log\LogManager;
22
use TYPO3\CMS\Core\TimeTracker\TimeTracker;
23
use TYPO3\CMS\Core\Utility\GeneralUtility;
24
use TYPO3\CMS\Core\Utility\MathUtility;
25
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
26
use TYPO3\CMS\Frontend\Page\PageGenerator;
27
use TYPO3\CMS\Frontend\Utility\CompressionUtility;
28
use TYPO3\CMS\Frontend\View\AdminPanelView;
29
30
/**
31
 * This is the main entry point of the TypoScript driven standard front-end
32
 *
33
 * Basically put, this is the script which all requests for TYPO3 delivered pages goes to in the
34
 * frontend (the website). The script instantiates a $TSFE object, includes libraries and does a little logic here
35
 * and there in order to instantiate the right classes to create the webpage.
36
 * Previously, this was called index_ts.php and also included the logic for the lightweight "eID" concept,
37
 * which is now handled in a separate request handler (EidRequestHandler).
38
 */
39
class RequestHandler implements RequestHandlerInterface
40
{
41
    /**
42
     * Instance of the current TYPO3 bootstrap
43
     * @var Bootstrap
44
     */
45
    protected $bootstrap;
46
47
    /**
48
     * Instance of the timetracker
49
     * @var TimeTracker
50
     */
51
    protected $timeTracker;
52
53
    /**
54
     * Instance of the TSFE object
55
     * @var TypoScriptFrontendController
56
     */
57
    protected $controller;
58
59
    /**
60
     * The request handed over
61
     * @var \Psr\Http\Message\ServerRequestInterface
62
     */
63
    protected $request;
64
65
    /**
66
     * Constructor handing over the bootstrap and the original request
67
     *
68
     * @param Bootstrap $bootstrap
69
     */
70
    public function __construct(Bootstrap $bootstrap)
71
    {
72
        $this->bootstrap = $bootstrap;
73
    }
74
75
    /**
76
     * Handles a frontend request
77
     *
78
     * @param \Psr\Http\Message\ServerRequestInterface $request
79
     * @return \Psr\Http\Message\ResponseInterface|null
80
     */
81
    public function handleRequest(\Psr\Http\Message\ServerRequestInterface $request)
82
    {
83
        $response = null;
84
        $this->request = $request;
85
        $this->initializeTimeTracker();
86
87
        // Hook to preprocess the current request:
88
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/index_ts.php']['preprocessRequest'] ?? [] as $hookFunction) {
89
            $hookParameters = [];
90
            GeneralUtility::callUserFunction($hookFunction, $hookParameters, $hookParameters);
91
        }
92
93
        $this->initializeController();
94
95
        if ($GLOBALS['TYPO3_CONF_VARS']['FE']['pageUnavailable_force']
96
            && !GeneralUtility::cmpIP(
97
                GeneralUtility::getIndpEnv('REMOTE_ADDR'),
98
                $GLOBALS['TYPO3_CONF_VARS']['SYS']['devIPmask']
99
            )
100
        ) {
101
            $this->controller->pageUnavailableAndExit('This page is temporarily unavailable.');
102
        }
103
104
        $this->controller->connectToDB();
105
106
        // Output compression
107
        // Remove any output produced until now
108
        $this->bootstrap->endOutputBufferingAndCleanPreviousOutput();
109
        $this->initializeOutputCompression();
110
111
        // Initializing the Frontend User
112
        $this->timeTracker->push('Front End user initialized', '');
113
        $this->controller->initFEuser();
114
        $this->timeTracker->pull();
115
116
        // Initializing a possible logged-in Backend User
117
        /** @var $GLOBALS['BE_USER'] \TYPO3\CMS\Backend\FrontendBackendUserAuthentication */
118
        $GLOBALS['BE_USER'] = $this->controller->initializeBackendUser();
119
120
        // Process the ID, type and other parameters.
121
        // After this point we have an array, $page in TSFE, which is the page-record
122
        // of the current page, $id.
123
        $this->timeTracker->push('Process ID', '');
124
        // Initialize admin panel since simulation settings are required here:
125
        if ($this->controller->isBackendUserLoggedIn()) {
126
            $GLOBALS['BE_USER']->initializeAdminPanel();
127
            $this->bootstrap
128
                    ->initializeBackendRouter()
129
                    ->loadExtTables();
130
        }
131
        $this->controller->checkAlternativeIdMethods();
132
        $this->controller->clear_preview();
133
        $this->controller->determineId();
134
135
        // Now, if there is a backend user logged in and he has NO access to this page,
136
        // then re-evaluate the id shown! _GP('ADMCMD_noBeUser') is placed here because
137
        // \TYPO3\CMS\Version\Hook\PreviewHook might need to know if a backend user is logged in.
138
        if (
139
            $this->controller->isBackendUserLoggedIn()
140
            && (!$GLOBALS['BE_USER']->extPageReadAccess($this->controller->page) || GeneralUtility::_GP('ADMCMD_noBeUser'))
0 ignored issues
show
Bug Best Practice introduced by
The expression TYPO3\CMS\Core\Utility\G...:_GP('ADMCMD_noBeUser') of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
141
        ) {
142
            // Remove user
143
            unset($GLOBALS['BE_USER']);
144
            $this->controller->beUserLogin = false;
145
            // Re-evaluate the page-id.
146
            $this->controller->checkAlternativeIdMethods();
147
            $this->controller->clear_preview();
148
            $this->controller->determineId();
149
        }
150
151
        $this->controller->makeCacheHash();
152
        $this->timeTracker->pull();
153
154
        // Admin Panel & Frontend editing
155
        if ($this->controller->isBackendUserLoggedIn()) {
156
            $GLOBALS['BE_USER']->initializeFrontendEdit();
157
            if ($GLOBALS['BE_USER']->adminPanel instanceof AdminPanelView) {
158
                $this->bootstrap->initializeLanguageObject();
159
            }
160
            if ($GLOBALS['BE_USER']->frontendEdit instanceof FrontendEditingController) {
161
                $GLOBALS['BE_USER']->frontendEdit->initConfigOptions();
162
            }
163
        }
164
165
        // Starts the template
166
        $this->timeTracker->push('Start Template', '');
167
        $this->controller->initTemplate();
168
        $this->timeTracker->pull();
169
        // Get from cache
170
        $this->timeTracker->push('Get Page from cache', '');
171
        $this->controller->getFromCache();
172
        $this->timeTracker->pull();
173
        // Get config if not already gotten
174
        // After this, we should have a valid config-array ready
175
        $this->controller->getConfigArray();
176
        // Setting language and locale
177
        $this->timeTracker->push('Setting language and locale', '');
178
        $this->controller->settingLanguage();
179
        $this->controller->settingLocale();
180
        $this->timeTracker->pull();
181
182
        // Convert POST data to utf-8 for internal processing if metaCharset is different
183
        $this->controller->convPOSTCharset();
184
185
        $this->controller->initializeRedirectUrlHandlers();
186
187
        $this->controller->handleDataSubmission();
188
189
        // Check for shortcut page and redirect
190
        $this->controller->checkPageForShortcutRedirect();
191
        $this->controller->checkPageForMountpointRedirect();
192
193
        // Generate page
194
        $this->controller->setUrlIdToken();
195
        $this->timeTracker->push('Page generation', '');
196
        if ($this->controller->isGeneratePage()) {
197
            $this->controller->generatePage_preProcessing();
198
            $this->controller->preparePageContentGeneration();
199
            // Content generation
200
            if (!$this->controller->isINTincScript()) {
201
                PageGenerator::renderContent();
202
                $this->controller->setAbsRefPrefix();
203
            }
204
            $this->controller->generatePage_postProcessing();
205
        } elseif ($this->controller->isINTincScript()) {
206
            $this->controller->preparePageContentGeneration();
207
        }
208
        $this->controller->releaseLocks();
209
        $this->timeTracker->pull();
210
211
        // Render non-cached parts
212
        if ($this->controller->isINTincScript()) {
213
            $this->timeTracker->push('Non-cached objects', '');
214
            $this->controller->INTincScript();
215
            $this->timeTracker->pull();
216
        }
217
218
        // Output content
219
        $sendTSFEContent = false;
220
        if ($this->controller->isOutputting()) {
221
            $this->timeTracker->push('Print Content', '');
222
            $this->controller->processOutput();
223
            $sendTSFEContent = true;
224
            $this->timeTracker->pull();
225
        }
226
        // Store session data for fe_users
227
        $this->controller->storeSessionData();
228
229
        // Create a Response object when sending content
230
        if ($sendTSFEContent) {
231
            $response = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Http\Response::class);
232
        }
233
234
        // Statistics
235
        $GLOBALS['TYPO3_MISC']['microtime_end'] = microtime(true);
236
        if ($sendTSFEContent) {
237 View Code Duplication
            if (isset($this->controller->config['config']['debug'])) {
238
                $includeParseTime = (bool)$this->controller->config['config']['debug'];
239
            } else {
240
                $includeParseTime = !empty($GLOBALS['TYPO3_CONF_VARS']['FE']['debug']);
241
            }
242
            if ($includeParseTime) {
243
                $response = $response->withHeader('X-TYPO3-Parsetime', $this->timeTracker->getParseTime() . 'ms');
244
            }
245
        }
246
        $this->controller->redirectToExternalUrl();
247
        // Preview info
248
        $this->controller->previewInfo();
249
        // Hook for end-of-frontend
250
        $this->controller->hook_eofe();
251
        // Finish timetracking
252
        $this->timeTracker->pull();
253
254
        // Admin panel
255
        if ($this->controller->isBackendUserLoggedIn() && $GLOBALS['BE_USER'] instanceof FrontendBackendUserAuthentication) {
256
            if ($GLOBALS['BE_USER']->isAdminPanelVisible()) {
257
                $this->controller->content = str_ireplace('</body>', $GLOBALS['BE_USER']->displayAdminPanel() . '</body>', $this->controller->content);
258
            }
259
        }
260
261
        if ($sendTSFEContent) {
262
            // Send content-length header.
263
            // Notice that all HTML content outside the length of the content-length header will be cut off!
264
            // Therefore content of unknown length from included PHP-scripts and if admin users are logged
265
            // in (admin panel might show...) or if debug mode is turned on, we disable it!
266
            if (
267
                (!isset($this->controller->config['config']['enableContentLengthHeader']) || $this->controller->config['config']['enableContentLengthHeader'])
268
                && !$this->controller->beUserLogin && !$GLOBALS['TYPO3_CONF_VARS']['FE']['debug']
269
                && !$this->controller->config['config']['debug'] && !$this->controller->doWorkspacePreview()
270
            ) {
271
                header('Content-Length: ' . strlen($this->controller->content));
272
            }
273
            $response->getBody()->write($this->controller->content);
274
        }
275
        GeneralUtility::makeInstance(LogManager::class)
276
                      ->getLogger(get_class())->debug('END of FRONTEND session', ['_FLUSH' => true]);
277
        return $response;
278
    }
279
280
    /**
281
     * This request handler can handle any frontend request.
282
     *
283
     * @param \Psr\Http\Message\ServerRequestInterface $request
284
     * @return bool If the request is not an eID request, TRUE otherwise FALSE
285
     */
286
    public function canHandleRequest(\Psr\Http\Message\ServerRequestInterface $request)
287
    {
288
        return $request->getQueryParams()['eID'] || $request->getParsedBody()['eID'] ? false : true;
289
    }
290
291
    /**
292
     * Returns the priority - how eager the handler is to actually handle the
293
     * request.
294
     *
295
     * @return int The priority of the request handler.
296
     */
297
    public function getPriority()
298
    {
299
        return 50;
300
    }
301
302
    /**
303
     * Initializes output compression when enabled, could be split up and put into Bootstrap
304
     * at a later point
305
     */
306
    protected function initializeOutputCompression()
307
    {
308
        if ($GLOBALS['TYPO3_CONF_VARS']['FE']['compressionLevel'] && extension_loaded('zlib')) {
309 View Code Duplication
            if (MathUtility::canBeInterpretedAsInteger($GLOBALS['TYPO3_CONF_VARS']['FE']['compressionLevel'])) {
310
                @ini_set('zlib.output_compression_level', $GLOBALS['TYPO3_CONF_VARS']['FE']['compressionLevel']);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for ini_set(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

310
                /** @scrutinizer ignore-unhandled */ @ini_set('zlib.output_compression_level', $GLOBALS['TYPO3_CONF_VARS']['FE']['compressionLevel']);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
311
            }
312
            ob_start([GeneralUtility::makeInstance(CompressionUtility::class), 'compressionOutputHandler']);
313
        }
314
    }
315
316
    /**
317
     * Timetracking started depending if a Backend User is logged in
318
     */
319
    protected function initializeTimeTracker()
320
    {
321
        $configuredCookieName = trim($GLOBALS['TYPO3_CONF_VARS']['BE']['cookieName']) ?: 'be_typo_user';
322
323
        /** @var TimeTracker timeTracker */
324
        $this->timeTracker = GeneralUtility::makeInstance(TimeTracker::class, ($this->request->getCookieParams()[$configuredCookieName] ? true : false));
0 ignored issues
show
Bug introduced by
$this->request->getCooki...kieName] ? true : false of type boolean is incompatible with the type array<integer,mixed> expected by parameter $constructorArguments of TYPO3\CMS\Core\Utility\G...Utility::makeInstance(). ( Ignorable by Annotation )

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

324
        $this->timeTracker = GeneralUtility::makeInstance(TimeTracker::class, /** @scrutinizer ignore-type */ ($this->request->getCookieParams()[$configuredCookieName] ? true : false));
Loading history...
325
        $this->timeTracker->start();
326
    }
327
328
    /**
329
     * Creates an instance of TSFE and sets it as a global variable
330
     */
331
    protected function initializeController()
332
    {
333
        $this->controller = GeneralUtility::makeInstance(
334
            TypoScriptFrontendController::class,
335
            null,
336
            GeneralUtility::_GP('id'),
0 ignored issues
show
Bug introduced by
It seems like TYPO3\CMS\Core\Utility\GeneralUtility::_GP('id') can also be of type string; however, parameter $constructorArguments of TYPO3\CMS\Core\Utility\G...Utility::makeInstance() does only seem to accept array<integer,mixed>, maybe add an additional type check? ( Ignorable by Annotation )

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

336
            /** @scrutinizer ignore-type */ GeneralUtility::_GP('id'),
Loading history...
337
            GeneralUtility::_GP('type'),
338
            GeneralUtility::_GP('no_cache'),
339
            GeneralUtility::_GP('cHash'),
340
            null,
341
            GeneralUtility::_GP('MP')
342
        );
343
        // setting the global variable for the controller
344
        // We have to define this as reference here, because there is code around
345
        // which exchanges the TSFE object in the global variable. The reference ensures
346
        // that the $controller member always works on the same object as the global variable.
347
        // This is a dirty workaround and bypasses the protected access modifier of the controller member.
348
        $GLOBALS['TSFE'] = &$this->controller;
349
    }
350
}
351