[TASK] Use null coalescing operator where possible
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / Http / RouteDispatcher.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 Psr\Http\Message\ResponseInterface;
18 use Psr\Http\Message\ServerRequestInterface;
19 use TYPO3\CMS\Backend\Routing\Exception\InvalidRequestTokenException;
20 use TYPO3\CMS\Backend\Routing\Route;
21 use TYPO3\CMS\Backend\Routing\Router;
22 use TYPO3\CMS\Backend\Utility\BackendUtility;
23 use TYPO3\CMS\Core\FormProtection\FormProtectionFactory;
24 use TYPO3\CMS\Core\Http\Dispatcher;
25 use TYPO3\CMS\Core\Http\DispatcherInterface;
26 use TYPO3\CMS\Core\Http\Response;
27 use TYPO3\CMS\Core\Type\Bitmask\Permission;
28 use TYPO3\CMS\Core\Utility\GeneralUtility;
29 use TYPO3\CMS\Core\Utility\MathUtility;
30
31 /**
32 * Dispatcher which resolves a route to call a controller and method (but also a callable)
33 */
34 class RouteDispatcher extends Dispatcher implements DispatcherInterface
35 {
36 /**
37 * Main method to resolve the route and checks the target of the route, and tries to call it.
38 *
39 * @param ServerRequestInterface $request the current server request
40 * @param ResponseInterface $response the prepared response
41 * @return ResponseInterface the filled response by the callable / controller/action
42 * @throws InvalidRequestTokenException if the route was not found
43 * @throws \InvalidArgumentException if the defined target for the route is invalid
44 */
45 public function dispatch(ServerRequestInterface $request, ResponseInterface $response)
46 {
47 /** @var Router $router */
48 $router = GeneralUtility::makeInstance(Router::class);
49 /** @var Route $route */
50 $route = $router->matchRequest($request);
51 $request = $request->withAttribute('route', $route);
52 $request = $request->withAttribute('target', $route->getOption('target'));
53 if (!$this->isValidRequest($request)) {
54 throw new InvalidRequestTokenException('Invalid request for route "' . $route->getPath() . '"', 1425389455);
55 }
56
57 if ($route->getOption('module')) {
58 $this->addAndValidateModuleConfiguration($request, $route);
59 }
60 $targetIdentifier = $route->getOption('target');
61 $target = $this->getCallableFromTarget($targetIdentifier);
62 return call_user_func_array($target, [$request, $response]);
63 }
64
65 /**
66 * Wrapper method for static form protection utility
67 *
68 * @return \TYPO3\CMS\Core\FormProtection\AbstractFormProtection
69 */
70 protected function getFormProtection()
71 {
72 return FormProtectionFactory::get();
73 }
74
75 /**
76 * Checks if the request token is valid. This is checked to see if the route is really
77 * created by the same instance. Should be called for all routes in the backend except
78 * for the ones that don't require a login.
79 *
80 * @param \Psr\Http\Message\ServerRequestInterface $request
81 * @return bool
82 * @see \TYPO3\CMS\Backend\Routing\UriBuilder where the token is generated.
83 */
84 protected function isValidRequest($request)
85 {
86 $route = $request->getAttribute('route');
87 if ($route->getOption('access') === 'public') {
88 return true;
89 }
90 $token = (string)($request->getParsedBody()['token'] ?? $request->getQueryParams()['token']);
91 return $this->getFormProtection()->validateToken($token, 'route', $route->getOption('_identifier'));
92 }
93
94 /**
95 * Adds configuration for a module and checks module permissions for the
96 * current user.
97 *
98 * @param ServerRequestInterface $request
99 * @param Route $route
100 * @throws \RuntimeException
101 */
102 protected function addAndValidateModuleConfiguration(ServerRequestInterface $request, Route $route)
103 {
104 $moduleName = $route->getOption('moduleName');
105 $moduleConfiguration = $this->getModuleConfiguration($moduleName);
106 $route->setOption('moduleConfiguration', $moduleConfiguration);
107
108 $backendUserAuthentication = $GLOBALS['BE_USER'];
109
110 // Check permissions and exit if the user has no permission for entry
111 // @todo please do not use "true" here, what a bad coding paradigm
112 $backendUserAuthentication->modAccess($moduleConfiguration, true);
113 $id = $request->getQueryParams()['id'] ?? $request->getParsedBody()['id'];
114 if (MathUtility::canBeInterpretedAsInteger($id) && $id > 0) {
115 $permClause = $backendUserAuthentication->getPagePermsClause(Permission::PAGE_SHOW);
116 // Check page access
117 if (!is_array(BackendUtility::readPageAccess($id, $permClause))) {
118 // Check if page has been deleted
119 $deleteField = $GLOBALS['TCA']['pages']['ctrl']['delete'];
120 $pageInfo = BackendUtility::getRecord('pages', $id, $deleteField, $permClause ? ' AND ' . $permClause : '', false);
121 if (!$pageInfo[$deleteField]) {
122 throw new \RuntimeException('You don\'t have access to this page', 1289917924);
123 }
124 }
125 }
126 }
127
128 /**
129 * Returns the module configuration which is provided during module registration
130 *
131 * @param string $moduleName
132 * @return array
133 * @throws \RuntimeException
134 */
135 protected function getModuleConfiguration($moduleName)
136 {
137 if (!isset($GLOBALS['TBE_MODULES']['_configuration'][$moduleName])) {
138 throw new \RuntimeException('Module ' . $moduleName . ' is not configured.', 1289918325);
139 }
140 return $GLOBALS['TBE_MODULES']['_configuration'][$moduleName];
141 }
142 }