[!!!][FEATURE] Introduce Backend Routing
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / Http / RequestHandler.php
1 <?php
2 namespace TYPO3\CMS\Backend\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\Core\Core\Bootstrap;
18 use TYPO3\CMS\Core\Http\RequestHandlerInterface;
19 use TYPO3\CMS\Backend\Routing\Exception\RouteNotFoundException;
20 use TYPO3\CMS\Core\FormProtection\FormProtectionFactory;
21 use TYPO3\CMS\Core\Utility\GeneralUtility;
22 use TYPO3\CMS\Backend\Routing\Router;
23 use TYPO3\CMS\Backend\Routing\Route;
24
25 /**
26 * General RequestHandler for the TYPO3 Backend. This is used for all Backend requests except for CLI
27 * or AJAX calls. Unlike all other RequestHandlers in the TYPO3 CMS Core, the actual logic for choosing
28 * the controller is still done inside places like each single file.
29 * This RequestHandler here serves solely to check and set up all requirements needed for a TYPO3 Backend.
30 * This class might be changed in the future.
31 *
32 * At first, this request handler serves as a replacement to typo3/init.php. It is called but does not exit
33 * so any typical script that is not dispatched, is just running through the handleRequest() method and then
34 * calls its own code.
35 *
36 * However, if a get/post parameter "route" is set, the unified Backend Routing is called and searches for a
37 * matching route inside the Router. The corresponding controller / action is called then which returns content.
38 *
39 * The following get/post parameters are evaluated here:
40 * - route
41 * - token
42 */
43 class RequestHandler implements RequestHandlerInterface {
44
45 /**
46 * Instance of the current TYPO3 bootstrap
47 * @var Bootstrap
48 */
49 protected $bootstrap;
50
51 /**
52 * Constructor handing over the bootstrap and the original request
53 *
54 * @param Bootstrap $bootstrap
55 */
56 public function __construct(Bootstrap $bootstrap) {
57 $this->bootstrap = $bootstrap;
58 }
59
60 /**
61 * Handles any backend request
62 *
63 * @param \Psr\Http\Message\ServerRequestInterface $request
64 * @return NULL|\Psr\Http\Message\ResponseInterface
65 */
66 public function handleRequest(\Psr\Http\Message\ServerRequestInterface $request) {
67 // enable dispatching via Request/Response logic only for typo3/index.php
68 // This fallback will be removed in TYPO3 CMS 8, as only index.php will be allowed
69 $path = substr($request->getUri()->getPath(), strlen(GeneralUtility::getIndpEnv('TYPO3_SITE_PATH')));
70 $routingEnabled = ($path === TYPO3_mainDir . 'index.php' || $path === TYPO3_mainDir);
71 $proceedIfNoUserIsLoggedIn = FALSE;
72
73 if ($routingEnabled) {
74 $pathToRoute = (string)$request->getQueryParams()['route'];
75 // Allow the login page to be displayed if routing is not used and on index.php
76 if (empty($pathToRoute)) {
77 $pathToRoute = '/login';
78 }
79 $request = $request->withAttribute('routePath', $pathToRoute);
80
81 // Evaluate the constant for skipping the BE user check for the bootstrap
82 // should be handled differently in the future by checking the Bootstrap directly
83 if ($pathToRoute === '/login') {
84 $proceedIfNoUserIsLoggedIn = TRUE;
85 }
86 }
87
88 $this->boot($proceedIfNoUserIsLoggedIn);
89
90 // Check if the router has the available route and dispatch.
91 if ($routingEnabled) {
92 return $this->dispatch($request);
93 }
94
95 // No route found, so the system proceeds in called entrypoint as fallback.
96 return NULL;
97 }
98
99 /**
100 * Does the main work for setting up the backend environment for any Backend request
101 *
102 * @param bool $proceedIfNoUserIsLoggedIn option to allow to render the request even if no user is logged in
103 * @return void
104 */
105 protected function boot($proceedIfNoUserIsLoggedIn) {
106 $this->bootstrap
107 ->checkLockedBackendAndRedirectOrDie()
108 ->checkBackendIpOrDie()
109 ->checkSslBackendAndRedirectIfNeeded()
110 ->initializeBackendRouter()
111 ->loadExtensionTables(TRUE)
112 ->initializeSpriteManager()
113 ->initializeBackendUser()
114 ->initializeBackendAuthentication($proceedIfNoUserIsLoggedIn)
115 ->initializeLanguageObject()
116 ->initializeBackendTemplate()
117 ->endOutputBufferingAndCleanPreviousOutput()
118 ->initializeOutputCompression()
119 ->sendHttpHeaders();
120 }
121
122 /**
123 * This request handler can handle any backend request (but not CLI).
124 *
125 * @param \Psr\Http\Message\ServerRequestInterface $request
126 * @return bool If the request is not a CLI script, TRUE otherwise FALSE
127 */
128 public function canHandleRequest(\Psr\Http\Message\ServerRequestInterface $request) {
129 return (TYPO3_REQUESTTYPE & TYPO3_REQUESTTYPE_BE && !(TYPO3_REQUESTTYPE & TYPO3_REQUESTTYPE_CLI));
130 }
131
132 /**
133 * Returns the priority - how eager the handler is to actually handle the
134 * request.
135 *
136 * @return int The priority of the request handler.
137 */
138 public function getPriority() {
139 return 50;
140 }
141
142 /**
143 * Wrapper method for static form protection utility
144 *
145 * @return \TYPO3\CMS\Core\FormProtection\AbstractFormProtection
146 */
147 protected function getFormProtection() {
148 return FormProtectionFactory::get();
149 }
150
151 /**
152 * Checks if the request token is valid. This is checked to see if the route is really
153 * created by the same instance. Should be called for all routes in the backend except
154 * for the ones that don't require a login.
155 *
156 * @param \Psr\Http\Message\ServerRequestInterface $request
157 * @return bool
158 * @see \TYPO3\CMS\Backend\Routing\UriBuilder where the token is generated.
159 */
160 protected function isValidRequest($request) {
161 $token = (string)(isset($request->getParsedBody()['token']) ? $request->getParsedBody()['token'] : $request->getQueryParams()['token']);
162 $route = $request->getAttribute('route');
163 return ($route->getOption('access') === 'public' || $this->getFormProtection()->validateToken($token, 'route', $route->getOption('_identifier')));
164 }
165
166 /**
167 * Dispatch the request to the appropriate controller
168 *
169 * @param \Psr\Http\Message\ServerRequestInterface $request
170 * @return \Psr\Http\Message\ResponseInterface
171 * @throws RouteNotFoundException when no route is registered
172 * @throws \RuntimeException when a route is found but the controller to be called does not implement the Controller Interface
173 */
174 protected function dispatch($request) {
175 /** @var Route $route */
176 $router = GeneralUtility::makeInstance(Router::class);
177 $route = $router->matchRequest($request);
178 $request = $request->withAttribute('route', $route);
179 if (!$this->isValidRequest($request)) {
180 throw new RouteNotFoundException('Invalid request for route "' . $route->getPath() . '"', 1425389455);
181 }
182 $className = $route->getOption('controller');
183 $controller = GeneralUtility::makeInstance($className);
184 if (!$controller instanceof \TYPO3\CMS\Core\Http\ControllerInterface) {
185 throw new \RuntimeException('Requested controller "' . $className . '" does not implement the ControllerInterface', 1425389452);
186 }
187 return $controller->processRequest($request);
188 }
189 }