[FEATURE] Introduce Request/Response based on PSR-7
[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\Core\RequestHandlerInterface;
20 use TYPO3\CMS\Core\FrontendEditing\FrontendEditingController;
21 use TYPO3\CMS\Core\TimeTracker\NullTimeTracker;
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\Core\Utility\MonitorUtility;
26 use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
27 use TYPO3\CMS\Frontend\Page\PageGenerator;
28 use TYPO3\CMS\Frontend\Utility\CompressionUtility;
29 use TYPO3\CMS\Frontend\View\AdminPanelView;
30
31 /**
32 * This is the main entry point of the TypoScript driven standard front-end
33 *
34 * Basically put, this is the script which all requests for TYPO3 delivered pages goes to in the
35 * frontend (the website). The script instantiates a $TSFE object, includes libraries and does a little logic here
36 * and there in order to instantiate the right classes to create the webpage.
37 * Previously, this was called index_ts.php and also included the logic for the lightweight "eID" concept,
38 * which is now handled in a separate request handler (EidRequestHandler).
39 */
40 class RequestHandler implements RequestHandlerInterface {
41
42 /**
43 * Instance of the current TYPO3 bootstrap
44 * @var Bootstrap
45 */
46 protected $bootstrap;
47
48 /**
49 * Instance of the timetracker
50 * @var NullTimeTracker|TimeTracker
51 */
52 protected $timeTracker;
53
54 /**
55 * Instance of the TSFE object
56 * @var TypoScriptFrontendController
57 */
58 protected $controller;
59
60 /**
61 * The request handed over
62 * @var \Psr\Http\Message\ServerRequestInterface
63 */
64 protected $request;
65
66 /**
67 * Constructor handing over the bootstrap and the original request
68 *
69 * @param Bootstrap $bootstrap
70 */
71 public function __construct(Bootstrap $bootstrap) {
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 $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 = array();
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 // Initializing the Frontend User
115 $this->timeTracker->push('Front End user initialized', '');
116 $this->controller->initFEuser();
117 $this->timeTracker->pull();
118
119 // Initializing a possible logged-in Backend User
120 /** @var $GLOBALS['BE_USER'] \TYPO3\CMS\Backend\FrontendBackendUserAuthentication */
121 $GLOBALS['BE_USER'] = $this->controller->initializeBackendUser();
122
123 // Process the ID, type and other parameters.
124 // After this point we have an array, $page in TSFE, which is the page-record
125 // of the current page, $id.
126 $this->timeTracker->push('Process ID', '');
127 // Initialize admin panel since simulation settings are required here:
128 if ($this->controller->isBackendUserLoggedIn()) {
129 $GLOBALS['BE_USER']->initializeAdminPanel();
130 $this->bootstrap->loadExtensionTables(TRUE);
131 } else {
132 $this->bootstrap->loadCachedTca();
133 }
134 $this->controller->checkAlternativeIdMethods();
135 $this->controller->clear_preview();
136 $this->controller->determineId();
137
138 // Now, if there is a backend user logged in and he has NO access to this page,
139 // then re-evaluate the id shown! _GP('ADMCMD_noBeUser') is placed here because
140 // \TYPO3\CMS\Version\Hook\PreviewHook might need to know if a backend user is logged in.
141 if (
142 $this->controller->isBackendUserLoggedIn()
143 && (!$GLOBALS['BE_USER']->extPageReadAccess($this->controller->page) || GeneralUtility::_GP('ADMCMD_noBeUser'))
144 ) {
145 // Remove user
146 unset($GLOBALS['BE_USER']);
147 $this->controller->beUserLogin = FALSE;
148 // Re-evaluate the page-id.
149 $this->controller->checkAlternativeIdMethods();
150 $this->controller->clear_preview();
151 $this->controller->determineId();
152 }
153
154 $this->controller->makeCacheHash();
155 $this->timeTracker->pull();
156
157 // Admin Panel & Frontend editing
158 if ($this->controller->isBackendUserLoggedIn()) {
159 $GLOBALS['BE_USER']->initializeFrontendEdit();
160 if ($GLOBALS['BE_USER']->adminPanel instanceof AdminPanelView) {
161 $this->bootstrap
162 ->initializeLanguageObject()
163 ->initializeSpriteManager();
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 internal "renderCharset" if different from the metaCharset
188 $this->controller->convPOSTCharset();
189
190 // Check JumpUrl
191 $this->controller->setExternalJumpUrl();
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 // Check JumpUrl
255 $this->controller->jumpurl();
256 // Preview info
257 $this->controller->previewInfo();
258 // Hook for end-of-frontend
259 $this->controller->hook_eofe();
260 // Finish timetracking
261 $this->timeTracker->pull();
262 // Check memory usage
263 MonitorUtility::peakMemoryUsage();
264 // beLoginLinkIPList
265 echo $this->controller->beLoginLinkIPList();
266
267 // Admin panel
268 if (
269 $this->controller->isBackendUserLoggedIn()
270 && $GLOBALS['BE_USER'] instanceof FrontendBackendUserAuthentication
271 && $GLOBALS['BE_USER']->isAdminPanelVisible()
272 ) {
273 $this->controller->content = str_ireplace('</head>', $GLOBALS['BE_USER']->adminPanel->getAdminPanelHeaderData() . '</head>', $this->controller->content);
274 $this->controller->content = str_ireplace('</body>', $GLOBALS['BE_USER']->displayAdminPanel() . '</body>', $this->controller->content);
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 return $request->getQueryParams()['eID'] || $request->getParsedBody()['eID'] ? FALSE : TRUE;
300 }
301
302 /**
303 * Returns the priority - how eager the handler is to actually handle the
304 * request.
305 *
306 * @return int The priority of the request handler.
307 */
308 public function getPriority() {
309 return 50;
310 }
311
312 /**
313 * Initializes output compression when enabled, could be split up and put into Bootstrap
314 * at a later point
315 */
316 protected function initializeOutputCompression() {
317 if ($GLOBALS['TYPO3_CONF_VARS']['FE']['compressionLevel'] && extension_loaded('zlib')) {
318 if (MathUtility::canBeInterpretedAsInteger($GLOBALS['TYPO3_CONF_VARS']['FE']['compressionLevel'])) {
319 @ini_set('zlib.output_compression_level', $GLOBALS['TYPO3_CONF_VARS']['FE']['compressionLevel']);
320 }
321 ob_start(array(GeneralUtility::makeInstance(CompressionUtility::class), 'compressionOutputHandler'));
322 }
323 }
324
325 /**
326 * Timetracking started depending if a Backend User is logged in
327 *
328 * @return void
329 */
330 protected function initializeTimeTracker() {
331 $configuredCookieName = trim($GLOBALS['TYPO3_CONF_VARS']['BE']['cookieName']);
332 if (empty($configuredCookieName)) {
333 $configuredCookieName = 'be_typo_user';
334 }
335 if ($this->request->getCookieParams()[$configuredCookieName]) {
336 $this->timeTracker = new TimeTracker();
337 } else {
338 $this->timeTracker = new NullTimeTracker();
339 }
340
341 // We have to define this as reference here, because there is code around
342 // which exchanges the TT object in the global variable. The reference ensures
343 // that the $timeTracker member always works on the same object as the global variable.
344 // This is a dirty workaround and bypasses the protected access modifier of the $timeTracker member.
345 $GLOBALS['TT'] = &$this->timeTracker;
346 $this->timeTracker->start();
347 }
348
349 /**
350 * Creates an instance of TSFE and sets it as a global variable
351 *
352 * @return void
353 */
354 protected function initializeController() {
355 $this->controller = GeneralUtility::makeInstance(
356 TypoScriptFrontendController::class,
357 $GLOBALS['TYPO3_CONF_VARS'],
358 GeneralUtility::_GP('id'),
359 GeneralUtility::_GP('type'),
360 GeneralUtility::_GP('no_cache'),
361 GeneralUtility::_GP('cHash'),
362 GeneralUtility::_GP('jumpurl'),
363 GeneralUtility::_GP('MP'),
364 GeneralUtility::_GP('RDCT')
365 );
366 // setting the global variable for the controller
367 // We have to define this as reference here, because there is code around
368 // which exchanges the TSFE object in the global variable. The reference ensures
369 // that the $controller member always works on the same object as the global variable.
370 // This is a dirty workaround and bypasses the protected access modifier of the controller member.
371 $GLOBALS['TSFE'] = &$this->controller;
372 }
373
374 }