[!!!][TASK] Make TimeTracker a singleton
[Packages/TYPO3.CMS.git] / typo3 / sysext / frontend / Classes / Http / RequestHandler.php
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\TimeTracker\TimeTracker;
22 use TYPO3\CMS\Core\Utility\GeneralUtility;
23 use TYPO3\CMS\Core\Utility\MathUtility;
24 use TYPO3\CMS\Core\Utility\MonitorUtility;
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 NULL|\Psr\Http\Message\ResponseInterface
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 if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/index_ts.php']['preprocessRequest'])) {
89 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/index_ts.php']['preprocessRequest'] as $hookFunction) {
90 $hookParameters = array();
91 GeneralUtility::callUserFunction($hookFunction, $hookParameters, $hookParameters);
92 }
93 unset($hookFunction);
94 unset($hookParameters);
95 }
96
97 $this->initializeController();
98
99 if ($GLOBALS['TYPO3_CONF_VARS']['FE']['pageUnavailable_force']
100 && !GeneralUtility::cmpIP(
101 GeneralUtility::getIndpEnv('REMOTE_ADDR'),
102 $GLOBALS['TYPO3_CONF_VARS']['SYS']['devIPmask'])
103 ) {
104 $this->controller->pageUnavailableAndExit('This page is temporarily unavailable.');
105 }
106
107 $this->controller->connectToDB();
108 $this->controller->sendRedirect();
109
110 // Output compression
111 // Remove any output produced until now
112 $this->bootstrap->endOutputBufferingAndCleanPreviousOutput();
113 $this->initializeOutputCompression();
114
115 // Initializing the Frontend User
116 $this->timeTracker->push('Front End user initialized', '');
117 $this->controller->initFEuser();
118 $this->timeTracker->pull();
119
120 // Initializing a possible logged-in Backend User
121 /** @var $GLOBALS['BE_USER'] \TYPO3\CMS\Backend\FrontendBackendUserAuthentication */
122 $GLOBALS['BE_USER'] = $this->controller->initializeBackendUser();
123
124 // Process the ID, type and other parameters.
125 // After this point we have an array, $page in TSFE, which is the page-record
126 // of the current page, $id.
127 $this->timeTracker->push('Process ID', '');
128 // Initialize admin panel since simulation settings are required here:
129 if ($this->controller->isBackendUserLoggedIn()) {
130 $GLOBALS['BE_USER']->initializeAdminPanel();
131 $this->bootstrap
132 ->initializeBackendRouter()
133 ->loadExtensionTables();
134 } else {
135 $this->bootstrap->loadCachedTca();
136 }
137 $this->controller->checkAlternativeIdMethods();
138 $this->controller->clear_preview();
139 $this->controller->determineId();
140
141 // Now, if there is a backend user logged in and he has NO access to this page,
142 // then re-evaluate the id shown! _GP('ADMCMD_noBeUser') is placed here because
143 // \TYPO3\CMS\Version\Hook\PreviewHook might need to know if a backend user is logged in.
144 if (
145 $this->controller->isBackendUserLoggedIn()
146 && (!$GLOBALS['BE_USER']->extPageReadAccess($this->controller->page) || GeneralUtility::_GP('ADMCMD_noBeUser'))
147 ) {
148 // Remove user
149 unset($GLOBALS['BE_USER']);
150 $this->controller->beUserLogin = false;
151 // Re-evaluate the page-id.
152 $this->controller->checkAlternativeIdMethods();
153 $this->controller->clear_preview();
154 $this->controller->determineId();
155 }
156
157 $this->controller->makeCacheHash();
158 $this->timeTracker->pull();
159
160 // Admin Panel & Frontend editing
161 if ($this->controller->isBackendUserLoggedIn()) {
162 $GLOBALS['BE_USER']->initializeFrontendEdit();
163 if ($GLOBALS['BE_USER']->adminPanel instanceof AdminPanelView) {
164 $this->bootstrap->initializeLanguageObject();
165 }
166 if ($GLOBALS['BE_USER']->frontendEdit instanceof FrontendEditingController) {
167 $GLOBALS['BE_USER']->frontendEdit->initConfigOptions();
168 }
169 }
170
171 // Starts the template
172 $this->timeTracker->push('Start Template', '');
173 $this->controller->initTemplate();
174 $this->timeTracker->pull();
175 // Get from cache
176 $this->timeTracker->push('Get Page from cache', '');
177 $this->controller->getFromCache();
178 $this->timeTracker->pull();
179 // Get config if not already gotten
180 // After this, we should have a valid config-array ready
181 $this->controller->getConfigArray();
182 // Setting language and locale
183 $this->timeTracker->push('Setting language and locale', '');
184 $this->controller->settingLanguage();
185 $this->controller->settingLocale();
186 $this->timeTracker->pull();
187
188 // Convert POST data to internal "renderCharset" if different from the metaCharset
189 $this->controller->convPOSTCharset();
190
191 $this->controller->initializeRedirectUrlHandlers();
192
193 $this->controller->handleDataSubmission();
194
195 // Check for shortcut page and redirect
196 $this->controller->checkPageForShortcutRedirect();
197 $this->controller->checkPageForMountpointRedirect();
198
199 // Generate page
200 $this->controller->setUrlIdToken();
201 $this->timeTracker->push('Page generation', '');
202 if ($this->controller->isGeneratePage()) {
203 $this->controller->generatePage_preProcessing();
204 $temp_theScript = $this->controller->generatePage_whichScript();
205 if ($temp_theScript) {
206 include $temp_theScript;
207 } else {
208 PageGenerator::pagegenInit();
209 // Global content object
210 $this->controller->newCObj();
211 // Content generation
212 if (!$this->controller->isINTincScript()) {
213 PageGenerator::renderContent();
214 $this->controller->setAbsRefPrefix();
215 }
216 }
217 $this->controller->generatePage_postProcessing();
218 } elseif ($this->controller->isINTincScript()) {
219 PageGenerator::pagegenInit();
220 // Global content object
221 $this->controller->newCObj();
222 }
223 $this->controller->releaseLocks();
224 $this->timeTracker->pull();
225
226 // Render non-cached parts
227 if ($this->controller->isINTincScript()) {
228 $this->timeTracker->push('Non-cached objects', '');
229 $this->controller->INTincScript();
230 $this->timeTracker->pull();
231 }
232
233 // Output content
234 $sendTSFEContent = false;
235 if ($this->controller->isOutputting()) {
236 $this->timeTracker->push('Print Content', '');
237 $this->controller->processOutput();
238 $sendTSFEContent = true;
239 $this->timeTracker->pull();
240 }
241 // Store session data for fe_users
242 $this->controller->storeSessionData();
243 // Statistics
244 $GLOBALS['TYPO3_MISC']['microtime_end'] = microtime(true);
245 $this->controller->setParseTime();
246 if (isset($this->controller->config['config']['debug'])) {
247 $debugParseTime = (bool)$this->controller->config['config']['debug'];
248 } else {
249 $debugParseTime = !empty($this->controller->TYPO3_CONF_VARS['FE']['debug']);
250 }
251 if ($this->controller->isOutputting() && $debugParseTime) {
252 $this->controller->content .= LF . '<!-- Parsetime: ' . $this->controller->scriptParseTime . 'ms -->';
253 }
254 $this->controller->redirectToExternalUrl();
255 // Preview info
256 $this->controller->previewInfo();
257 // Hook for end-of-frontend
258 $this->controller->hook_eofe();
259 // Finish timetracking
260 $this->timeTracker->pull();
261 // Check memory usage
262 MonitorUtility::peakMemoryUsage();
263 // beLoginLinkIPList
264 echo $this->controller->beLoginLinkIPList();
265
266 // Admin panel
267 if ($this->controller->isBackendUserLoggedIn() && $GLOBALS['BE_USER'] instanceof FrontendBackendUserAuthentication) {
268 if ($GLOBALS['BE_USER']->extAdmEnabled) {
269 // Style sheet is also used for frontend editing.
270 $this->controller->content = str_ireplace('</head>', $GLOBALS['BE_USER']->adminPanel->getAdminPanelHeaderData() . '</head>', $this->controller->content);
271 }
272 if ($GLOBALS['BE_USER']->isAdminPanelVisible()) {
273 $this->controller->content = str_ireplace('</body>', $GLOBALS['BE_USER']->displayAdminPanel() . '</body>', $this->controller->content);
274 }
275 }
276
277 if ($sendTSFEContent) {
278 /** @var \TYPO3\CMS\Core\Http\Response $response */
279 $response = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Http\Response::class);
280 $response->getBody()->write($this->controller->content);
281 }
282 // Debugging Output
283 if (isset($GLOBALS['error']) && is_object($GLOBALS['error']) && @is_callable(array($GLOBALS['error'], 'debugOutput'))) {
284 $GLOBALS['error']->debugOutput();
285 }
286 if (TYPO3_DLOG) {
287 GeneralUtility::devLog('END of FRONTEND session', 'cms', 0, array('_FLUSH' => true));
288 }
289 return $response;
290 }
291
292 /**
293 * This request handler can handle any frontend request.
294 *
295 * @param \Psr\Http\Message\ServerRequestInterface $request
296 * @return bool If the request is not an eID request, TRUE otherwise FALSE
297 */
298 public function canHandleRequest(\Psr\Http\Message\ServerRequestInterface $request)
299 {
300 return $request->getQueryParams()['eID'] || $request->getParsedBody()['eID'] ? false : true;
301 }
302
303 /**
304 * Returns the priority - how eager the handler is to actually handle the
305 * request.
306 *
307 * @return int The priority of the request handler.
308 */
309 public function getPriority()
310 {
311 return 50;
312 }
313
314 /**
315 * Initializes output compression when enabled, could be split up and put into Bootstrap
316 * at a later point
317 */
318 protected function initializeOutputCompression()
319 {
320 if ($GLOBALS['TYPO3_CONF_VARS']['FE']['compressionLevel'] && extension_loaded('zlib')) {
321 if (MathUtility::canBeInterpretedAsInteger($GLOBALS['TYPO3_CONF_VARS']['FE']['compressionLevel'])) {
322 @ini_set('zlib.output_compression_level', $GLOBALS['TYPO3_CONF_VARS']['FE']['compressionLevel']);
323 }
324 ob_start(array(GeneralUtility::makeInstance(CompressionUtility::class), 'compressionOutputHandler'));
325 }
326 }
327
328 /**
329 * Timetracking started depending if a Backend User is logged in
330 *
331 * @return void
332 */
333 protected function initializeTimeTracker()
334 {
335 $configuredCookieName = trim($GLOBALS['TYPO3_CONF_VARS']['BE']['cookieName']) ?: 'be_typo_user';
336
337 /** @var TimeTracker timeTracker */
338 $this->timeTracker = GeneralUtility::makeInstance(TimeTracker::class, ($this->request->getCookieParams()[$configuredCookieName] ? true : false));
339 $this->timeTracker->start();
340 }
341
342 /**
343 * Creates an instance of TSFE and sets it as a global variable
344 *
345 * @return void
346 */
347 protected function initializeController()
348 {
349 $this->controller = GeneralUtility::makeInstance(
350 TypoScriptFrontendController::class,
351 $GLOBALS['TYPO3_CONF_VARS'],
352 GeneralUtility::_GP('id'),
353 GeneralUtility::_GP('type'),
354 GeneralUtility::_GP('no_cache'),
355 GeneralUtility::_GP('cHash'),
356 null,
357 GeneralUtility::_GP('MP'),
358 GeneralUtility::_GP('RDCT')
359 );
360 // setting the global variable for the controller
361 // We have to define this as reference here, because there is code around
362 // which exchanges the TSFE object in the global variable. The reference ensures
363 // that the $controller member always works on the same object as the global variable.
364 // This is a dirty workaround and bypasses the protected access modifier of the controller member.
365 $GLOBALS['TSFE'] = &$this->controller;
366 }
367 }