[TASK] Deprecate script-based modules
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / Http / BackendModuleRequestHandler.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\Backend\Utility\BackendUtility;
18 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
19 use TYPO3\CMS\Core\Core\Bootstrap;
20 use TYPO3\CMS\Core\FormProtection\BackendFormProtection;
21 use TYPO3\CMS\Core\FormProtection\FormProtectionFactory;
22 use TYPO3\CMS\Core\Exception;
23 use TYPO3\CMS\Core\Http\Dispatcher;
24 use TYPO3\CMS\Core\Http\RequestHandlerInterface;
25 use TYPO3\CMS\Core\Http\Response;
26 use TYPO3\CMS\Core\Utility\GeneralUtility;
27 use Psr\Http\Message\ServerRequestInterface;
28 use TYPO3\CMS\Core\Utility\MathUtility;
29
30 /**
31 * Handles the request for backend modules and wizards
32 * Juggles with $GLOBALS['TBE_MODULES']
33 */
34 class BackendModuleRequestHandler implements RequestHandlerInterface
35 {
36 /**
37 * @var Bootstrap
38 */
39 protected $bootstrap;
40
41 /**
42 * @var array
43 */
44 protected $moduleRegistry = array();
45
46 /**
47 * @var BackendUserAuthentication
48 */
49 protected $backendUserAuthentication;
50
51 /**
52 * Instance of the current Http Request
53 * @var ServerRequestInterface
54 */
55 protected $request;
56
57 /**
58 * Constructor handing over the bootstrap and the original request
59 *
60 * @param Bootstrap $bootstrap
61 */
62 public function __construct(Bootstrap $bootstrap)
63 {
64 $this->bootstrap = $bootstrap;
65 }
66
67 /**
68 * Handles the request, evaluating the configuration and executes the module accordingly
69 *
70 * @param ServerRequestInterface $request
71 * @return NULL|\Psr\Http\Message\ResponseInterface
72 * @throws Exception
73 */
74 public function handleRequest(ServerRequestInterface $request)
75 {
76 $this->request = $request;
77 $this->boot();
78
79 $this->moduleRegistry = $GLOBALS['TBE_MODULES'];
80
81 if (!$this->isValidModuleRequest()) {
82 throw new Exception('The CSRF protection token for the requested module is missing or invalid', 1417988921);
83 }
84
85 // Set to empty as it is not needed / always coming from typo3/index.php
86 $GLOBALS['BACK_PATH'] = '';
87
88 $this->backendUserAuthentication = $GLOBALS['BE_USER'];
89
90 $moduleName = (string)$this->request->getQueryParams()['M'];
91 if ($this->isDispatchedModule($moduleName)) {
92 return $this->dispatchModule($moduleName);
93 } else {
94 // @deprecated: This else path is deprecated and throws deprecations logs at registration time. Can be removed with TYPO3 CMS 8.
95 $isDispatched = $this->callTraditionalModule($moduleName);
96 if (!$isDispatched) {
97 throw new Exception('No module "' . $moduleName . '" could be found.', 1294585070);
98 }
99 }
100 return null;
101 }
102
103 /**
104 * Execute TYPO3 bootstrap
105 *
106 * @return void
107 */
108 protected function boot()
109 {
110 // Evaluate the constant for skipping the BE user check for the bootstrap, will be done without the constant at a later point
111 if (defined('TYPO3_PROCEED_IF_NO_USER') && TYPO3_PROCEED_IF_NO_USER) {
112 $proceedIfNoUserIsLoggedIn = true;
113 } else {
114 $proceedIfNoUserIsLoggedIn = false;
115 }
116
117 $this->bootstrap->checkLockedBackendAndRedirectOrDie()
118 ->checkBackendIpOrDie()
119 ->checkSslBackendAndRedirectIfNeeded()
120 ->initializeBackendRouter()
121 ->loadExtensionTables(true)
122 ->initializeSpriteManager()
123 ->initializeBackendUser()
124 ->initializeBackendAuthentication($proceedIfNoUserIsLoggedIn)
125 ->initializeLanguageObject()
126 ->initializeBackendTemplate()
127 ->endOutputBufferingAndCleanPreviousOutput()
128 ->initializeOutputCompression()
129 ->sendHttpHeaders();
130 }
131
132 /**
133 * This request handler can handle any backend request coming from index.php
134 *
135 * @param ServerRequestInterface $request
136 * @return bool
137 */
138 public function canHandleRequest(ServerRequestInterface $request)
139 {
140 return $request->getAttribute('isModuleRequest', false);
141 }
142
143 /**
144 * Checks if all parameters are met.
145 *
146 * @return bool
147 */
148 protected function isValidModuleRequest()
149 {
150 return $this->getFormProtection() instanceof BackendFormProtection
151 && $this->getFormProtection()->validateToken((string)$this->request->getQueryParams()['moduleToken'], 'moduleCall', (string)$this->request->getQueryParams()['M']);
152 }
153
154 /**
155 * A dispatched module is used, when no PATH is given.
156 * Traditional modules have a module path set.
157 *
158 * @param string $moduleName
159 * @return bool
160 */
161 protected function isDispatchedModule($moduleName)
162 {
163 return empty($this->moduleRegistry['_PATHS'][$moduleName]);
164 }
165
166 /**
167 * Executes the modules configured via Extbase
168 *
169 * @param string $moduleName
170 * @return Response A PSR-7 response object
171 * @throws \RuntimeException
172 */
173 protected function dispatchModule($moduleName)
174 {
175 $moduleConfiguration = $this->getModuleConfiguration($moduleName);
176
177 // Check permissions and exit if the user has no permission for entry
178 $this->backendUserAuthentication->modAccess($moduleConfiguration, true);
179 $id = isset($this->request->getQueryParams()['id']) ? $this->request->getQueryParams()['id'] : $this->request->getParsedBody()['id'];
180 if ($id && MathUtility::canBeInterpretedAsInteger($id)) {
181 // Check page access
182 $permClause = $this->backendUserAuthentication->getPagePermsClause(true);
183 $access = is_array(BackendUtility::readPageAccess((int)$id, $permClause));
184 if (!$access) {
185 throw new \RuntimeException('You don\'t have access to this page', 1289917924);
186 }
187 }
188
189 /** @var Response $response */
190 $response = GeneralUtility::makeInstance(Response::class);
191
192 // Use Core Dispatching
193 if (isset($moduleConfiguration['routeTarget'])) {
194 $dispatcher = GeneralUtility::makeInstance(Dispatcher::class);
195 $this->request = $this->request->withAttribute('target', $moduleConfiguration['routeTarget']);
196 $response = $dispatcher->dispatch($this->request, $response);
197 } else {
198 // extbase module
199 $configuration = array(
200 'extensionName' => $moduleConfiguration['extensionName'],
201 'pluginName' => $moduleName
202 );
203 if (isset($moduleConfiguration['vendorName'])) {
204 $configuration['vendorName'] = $moduleConfiguration['vendorName'];
205 }
206
207 // Run Extbase
208 $bootstrap = GeneralUtility::makeInstance(\TYPO3\CMS\Extbase\Core\Bootstrap::class);
209 $content = $bootstrap->run('', $configuration);
210
211 $response->getBody()->write($content);
212 }
213
214 return $response;
215 }
216
217 /**
218 * Calls traditional modules which are identified by having an index.php in their directory
219 * and were previously located within the global scope.
220 *
221 * @param string $moduleName
222 * @return bool Returns TRUE if the module was executed
223 */
224 protected function callTraditionalModule($moduleName)
225 {
226 $moduleBasePath = $this->moduleRegistry['_PATHS'][$moduleName];
227 // Some modules still rely on this global configuration array in a conf.php file
228 // load configuration from an existing conf.php file inside the same directory
229 if (file_exists($moduleBasePath . 'conf.php')) {
230 require $moduleBasePath . 'conf.php';
231 $moduleConfiguration = $MCONF;
232 } else {
233 $moduleConfiguration = $this->getModuleConfiguration($moduleName);
234 }
235 $GLOBALS['MCONF'] = $moduleConfiguration;
236 if (!empty($moduleConfiguration['access'])) {
237 $this->backendUserAuthentication->modAccess($moduleConfiguration, true);
238 }
239 if (file_exists($moduleBasePath . 'index.php')) {
240 global $SOBE;
241 require $moduleBasePath . 'index.php';
242 return true;
243 }
244 return false;
245 }
246
247 /**
248 * Returns the module configuration which is provided during module registration
249 *
250 * @param string $moduleName
251 * @return array
252 * @throws \RuntimeException
253 */
254 protected function getModuleConfiguration($moduleName)
255 {
256 if (!isset($this->moduleRegistry['_configuration'][$moduleName])) {
257 throw new \RuntimeException('Module ' . $moduleName . ' is not configured.', 1289918325);
258 }
259 return $this->moduleRegistry['_configuration'][$moduleName];
260 }
261
262
263 /**
264 * Returns the priority - how eager the handler is to actually handle the request.
265 *
266 * @return int The priority of the request handler.
267 */
268 public function getPriority()
269 {
270 return 90;
271 }
272
273 /**
274 * Wrapper method for static form protection utility
275 *
276 * @return \TYPO3\CMS\Core\FormProtection\AbstractFormProtection
277 */
278 protected function getFormProtection()
279 {
280 return FormProtectionFactory::get();
281 }
282 }