62a9f9806845fe71c28932dba1871962cfde86a7
[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\Frontend\Controller\TypoScriptFrontendController;
25 use TYPO3\CMS\Frontend\Page\PageGenerator;
26 use TYPO3\CMS\Frontend\Utility\CompressionUtility;
27 use TYPO3\CMS\Frontend\View\AdminPanelView;
28
29 /**
30 * This is the main entry point of the TypoScript driven standard front-end
31 *
32 * Basically put, this is the script which all requests for TYPO3 delivered pages goes to in the
33 * frontend (the website). The script instantiates a $TSFE object, includes libraries and does a little logic here
34 * and there in order to instantiate the right classes to create the webpage.
35 * Previously, this was called index_ts.php and also included the logic for the lightweight "eID" concept,
36 * which is now handled in a separate request handler (EidRequestHandler).
37 */
38 class RequestHandler implements RequestHandlerInterface
39 {
40 /**
41 * Instance of the current TYPO3 bootstrap
42 * @var Bootstrap
43 */
44 protected $bootstrap;
45
46 /**
47 * Instance of the timetracker
48 * @var TimeTracker
49 */
50 protected $timeTracker;
51
52 /**
53 * Instance of the TSFE object
54 * @var TypoScriptFrontendController
55 */
56 protected $controller;
57
58 /**
59 * The request handed over
60 * @var \Psr\Http\Message\ServerRequestInterface
61 */
62 protected $request;
63
64 /**
65 * Constructor handing over the bootstrap and the original request
66 *
67 * @param Bootstrap $bootstrap
68 */
69 public function __construct(Bootstrap $bootstrap)
70 {
71 $this->bootstrap = $bootstrap;
72 }
73
74 /**
75 * Handles a frontend request
76 *
77 * @param \Psr\Http\Message\ServerRequestInterface $request
78 * @return NULL|\Psr\Http\Message\ResponseInterface
79 */
80 public function handleRequest(\Psr\Http\Message\ServerRequestInterface $request)
81 {
82 $response = null;
83 $this->request = $request;
84 $this->initializeTimeTracker();
85
86 // Hook to preprocess the current request:
87 if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/index_ts.php']['preprocessRequest'])) {
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 unset($hookFunction);
93 unset($hookParameters);
94 }
95
96 $this->initializeController();
97
98 if ($GLOBALS['TYPO3_CONF_VARS']['FE']['pageUnavailable_force']
99 && !GeneralUtility::cmpIP(
100 GeneralUtility::getIndpEnv('REMOTE_ADDR'),
101 $GLOBALS['TYPO3_CONF_VARS']['SYS']['devIPmask'])
102 ) {
103 $this->controller->pageUnavailableAndExit('This page is temporarily unavailable.');
104 }
105
106 $this->controller->connectToDB();
107 $this->controller->sendRedirect();
108
109 // Output compression
110 // Remove any output produced until now
111 $this->bootstrap->endOutputBufferingAndCleanPreviousOutput();
112 $this->initializeOutputCompression();
113
114 $this->bootstrap->loadBaseTca();
115
116 // Initializing the Frontend User
117 $this->timeTracker->push('Front End user initialized', '');
118 $this->controller->initFEuser();
119 $this->timeTracker->pull();
120
121 // Initializing a possible logged-in Backend User
122 /** @var $GLOBALS['BE_USER'] \TYPO3\CMS\Backend\FrontendBackendUserAuthentication */
123 $GLOBALS['BE_USER'] = $this->controller->initializeBackendUser();
124
125 // Process the ID, type and other parameters.
126 // After this point we have an array, $page in TSFE, which is the page-record
127 // of the current page, $id.
128 $this->timeTracker->push('Process ID', '');
129 // Initialize admin panel since simulation settings are required here:
130 if ($this->controller->isBackendUserLoggedIn()) {
131 $GLOBALS['BE_USER']->initializeAdminPanel();
132 $this->bootstrap
133 ->initializeBackendRouter()
134 ->loadExtTables();
135 }
136 $this->controller->checkAlternativeIdMethods();
137 $this->controller->clear_preview();
138 $this->controller->determineId();
139
140 // Now, if there is a backend user logged in and he has NO access to this page,
141 // then re-evaluate the id shown! _GP('ADMCMD_noBeUser') is placed here because
142 // \TYPO3\CMS\Version\Hook\PreviewHook might need to know if a backend user is logged in.
143 if (
144 $this->controller->isBackendUserLoggedIn()
145 && (!$GLOBALS['BE_USER']->extPageReadAccess($this->controller->page) || GeneralUtility::_GP('ADMCMD_noBeUser'))
146 ) {
147 // Remove user
148 unset($GLOBALS['BE_USER']);
149 $this->controller->beUserLogin = false;
150 // Re-evaluate the page-id.
151 $this->controller->checkAlternativeIdMethods();
152 $this->controller->clear_preview();
153 $this->controller->determineId();
154 }
155
156 $this->controller->makeCacheHash();
157 $this->timeTracker->pull();
158
159 // Admin Panel & Frontend editing
160 if ($this->controller->isBackendUserLoggedIn()) {
161 $GLOBALS['BE_USER']->initializeFrontendEdit();
162 if ($GLOBALS['BE_USER']->adminPanel instanceof AdminPanelView) {
163 $this->bootstrap->initializeLanguageObject();
164 }
165 if ($GLOBALS['BE_USER']->frontendEdit instanceof FrontendEditingController) {
166 $GLOBALS['BE_USER']->frontendEdit->initConfigOptions();
167 }
168 }
169
170 // Starts the template
171 $this->timeTracker->push('Start Template', '');
172 $this->controller->initTemplate();
173 $this->timeTracker->pull();
174 // Get from cache
175 $this->timeTracker->push('Get Page from cache', '');
176 $this->controller->getFromCache();
177 $this->timeTracker->pull();
178 // Get config if not already gotten
179 // After this, we should have a valid config-array ready
180 $this->controller->getConfigArray();
181 // Setting language and locale
182 $this->timeTracker->push('Setting language and locale', '');
183 $this->controller->settingLanguage();
184 $this->controller->settingLocale();
185 $this->timeTracker->pull();
186
187 // Convert POST data to utf-8 for internal processing if metaCharset is different
188 $this->controller->convPOSTCharset();
189
190 $this->controller->initializeRedirectUrlHandlers();
191
192 $this->controller->handleDataSubmission();
193
194 // Check for shortcut page and redirect
195 $this->controller->checkPageForShortcutRedirect();
196 $this->controller->checkPageForMountpointRedirect();
197
198 // Generate page
199 $this->controller->setUrlIdToken();
200 $this->timeTracker->push('Page generation', '');
201 if ($this->controller->isGeneratePage()) {
202 $this->controller->generatePage_preProcessing();
203 $this->controller->preparePageContentGeneration();
204 // Content generation
205 if (!$this->controller->isINTincScript()) {
206 PageGenerator::renderContent();
207 $this->controller->setAbsRefPrefix();
208 }
209 $this->controller->generatePage_postProcessing();
210 } elseif ($this->controller->isINTincScript()) {
211 $this->controller->preparePageContentGeneration();
212 }
213 $this->controller->releaseLocks();
214 $this->timeTracker->pull();
215
216 // Render non-cached parts
217 if ($this->controller->isINTincScript()) {
218 $this->timeTracker->push('Non-cached objects', '');
219 $this->controller->INTincScript();
220 $this->timeTracker->pull();
221 }
222
223 // Output content
224 $sendTSFEContent = false;
225 if ($this->controller->isOutputting()) {
226 $this->timeTracker->push('Print Content', '');
227 $this->controller->processOutput();
228 $sendTSFEContent = true;
229 $this->timeTracker->pull();
230 }
231 // Store session data for fe_users
232 $this->controller->storeSessionData();
233 // Statistics
234 $GLOBALS['TYPO3_MISC']['microtime_end'] = microtime(true);
235 if ($this->controller->isOutputting()) {
236 if (isset($this->controller->config['config']['debug'])) {
237 $debugParseTime = (bool)$this->controller->config['config']['debug'];
238 } else {
239 $debugParseTime = !empty($GLOBALS['TYPO3_CONF_VARS']['FE']['debug']);
240 }
241 if ($debugParseTime) {
242 $this->controller->content .= LF . '<!-- Parsetime: ' . $this->timeTracker->getParseTime() . 'ms -->';
243 }
244 }
245 $this->controller->redirectToExternalUrl();
246 // Preview info
247 $this->controller->previewInfo();
248 // Hook for end-of-frontend
249 $this->controller->hook_eofe();
250 // Finish timetracking
251 $this->timeTracker->pull();
252
253 // Admin panel
254 if ($this->controller->isBackendUserLoggedIn() && $GLOBALS['BE_USER'] instanceof FrontendBackendUserAuthentication) {
255 if ($GLOBALS['BE_USER']->isAdminPanelVisible()) {
256 $this->controller->content = str_ireplace('</body>', $GLOBALS['BE_USER']->displayAdminPanel() . '</body>', $this->controller->content);
257 }
258 }
259
260 if ($sendTSFEContent) {
261 /** @var \TYPO3\CMS\Core\Http\Response $response */
262 $response = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Http\Response::class);
263 $response->getBody()->write($this->controller->content);
264 }
265 // Debugging Output
266 if (isset($GLOBALS['error']) && is_object($GLOBALS['error']) && @is_callable([$GLOBALS['error'], 'debugOutput'])) {
267 $GLOBALS['error']->debugOutput();
268 }
269 if (TYPO3_DLOG) {
270 GeneralUtility::devLog('END of FRONTEND session', 'cms', 0, ['_FLUSH' => true]);
271 }
272 return $response;
273 }
274
275 /**
276 * This request handler can handle any frontend request.
277 *
278 * @param \Psr\Http\Message\ServerRequestInterface $request
279 * @return bool If the request is not an eID request, TRUE otherwise FALSE
280 */
281 public function canHandleRequest(\Psr\Http\Message\ServerRequestInterface $request)
282 {
283 return $request->getQueryParams()['eID'] || $request->getParsedBody()['eID'] ? false : true;
284 }
285
286 /**
287 * Returns the priority - how eager the handler is to actually handle the
288 * request.
289 *
290 * @return int The priority of the request handler.
291 */
292 public function getPriority()
293 {
294 return 50;
295 }
296
297 /**
298 * Initializes output compression when enabled, could be split up and put into Bootstrap
299 * at a later point
300 */
301 protected function initializeOutputCompression()
302 {
303 if ($GLOBALS['TYPO3_CONF_VARS']['FE']['compressionLevel'] && extension_loaded('zlib')) {
304 if (MathUtility::canBeInterpretedAsInteger($GLOBALS['TYPO3_CONF_VARS']['FE']['compressionLevel'])) {
305 @ini_set('zlib.output_compression_level', $GLOBALS['TYPO3_CONF_VARS']['FE']['compressionLevel']);
306 }
307 ob_start([GeneralUtility::makeInstance(CompressionUtility::class), 'compressionOutputHandler']);
308 }
309 }
310
311 /**
312 * Timetracking started depending if a Backend User is logged in
313 */
314 protected function initializeTimeTracker()
315 {
316 $configuredCookieName = trim($GLOBALS['TYPO3_CONF_VARS']['BE']['cookieName']) ?: 'be_typo_user';
317
318 /** @var TimeTracker timeTracker */
319 $this->timeTracker = GeneralUtility::makeInstance(TimeTracker::class, ($this->request->getCookieParams()[$configuredCookieName] ? true : false));
320 $this->timeTracker->start();
321 }
322
323 /**
324 * Creates an instance of TSFE and sets it as a global variable
325 */
326 protected function initializeController()
327 {
328 $this->controller = GeneralUtility::makeInstance(
329 TypoScriptFrontendController::class,
330 null,
331 GeneralUtility::_GP('id'),
332 GeneralUtility::_GP('type'),
333 GeneralUtility::_GP('no_cache'),
334 GeneralUtility::_GP('cHash'),
335 null,
336 GeneralUtility::_GP('MP'),
337 GeneralUtility::_GP('RDCT')
338 );
339 // setting the global variable for the controller
340 // We have to define this as reference here, because there is code around
341 // which exchanges the TSFE object in the global variable. The reference ensures
342 // that the $controller member always works on the same object as the global variable.
343 // This is a dirty workaround and bypasses the protected access modifier of the controller member.
344 $GLOBALS['TSFE'] = &$this->controller;
345 }
346 }