[TASK] Clean up GeneralUtility::callUserFunction()
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / Http / AjaxRequestHandler.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\ResourceNotFoundException;
20 use TYPO3\CMS\Backend\Routing\Exception\RouteNotFoundException;
21 use TYPO3\CMS\Backend\Routing\Route;
22 use TYPO3\CMS\Core\Core\Bootstrap;
23 use TYPO3\CMS\Core\FormProtection\FormProtectionFactory;
24 use TYPO3\CMS\Core\Http\RequestHandlerInterface;
25 use TYPO3\CMS\Core\Http\Response;
26 use TYPO3\CMS\Core\Utility\GeneralUtility;
27
28 /**
29 * AJAX dispatcher
30 *
31 * Main entry point for AJAX calls in the TYPO3 Backend. Based on ?ajaxId of the outside application.
32 * Before doing the basic BE-related set up of this request (see the additional calls on $this->bootstrap inside
33 * handleRequest()), some AJAX-calls can be made without a valid user, which is determined here.
34 * See $GLOBALS['TYPO3_CONF_VARS']['BE']['AJAX'] and the Core APIs on how to register an AJAX call in the TYPO3 Backend.
35 *
36 * Due to legacy reasons, the actual logic is in EXT:core/Http/AjaxRequestHandler which will eventually
37 * be moved into this class.
38 */
39 class AjaxRequestHandler implements RequestHandlerInterface
40 {
41 /**
42 * Instance of the current TYPO3 bootstrap
43 * @var Bootstrap
44 */
45 protected $bootstrap;
46
47 /**
48 * List of requests that don't need a valid BE user
49 * @var array
50 */
51 protected $publicAjaxIds = array(
52 '/ajax/login',
53 '/ajax/logout',
54 '/ajax/login/refresh',
55 '/ajax/login/timedout',
56 '/ajax/rsa/publickey'
57 );
58
59 /**
60 * Constructor handing over the bootstrap and the original request
61 *
62 * @param Bootstrap $bootstrap
63 */
64 public function __construct(Bootstrap $bootstrap)
65 {
66 $this->bootstrap = $bootstrap;
67 }
68
69 /**
70 * Handles any AJAX request in the TYPO3 Backend
71 *
72 * @param ServerRequestInterface $request
73 * @return NULL|\Psr\Http\Message\ResponseInterface
74 */
75 public function handleRequest(ServerRequestInterface $request)
76 {
77 // First get the ajaxID
78 $ajaxID = isset($request->getParsedBody()['ajaxID']) ? $request->getParsedBody()['ajaxID'] : $request->getQueryParams()['ajaxID'];
79 $request = $request->withAttribute('routePath', $ajaxID);
80 $proceedIfNoUserIsLoggedIn = $this->isLoggedInBackendUserRequired($ajaxID);
81 $this->boot($proceedIfNoUserIsLoggedIn);
82
83 try {
84 // Backend Routing - check if a valid route is there, and dispatch
85 return $this->dispatch($request);
86 } catch (ResourceNotFoundException $e) {
87 // no Route found, fallback to the traditional AJAX request
88 }
89 return $this->dispatchTraditionalAjaxRequest($request);
90 }
91
92 /**
93 * This request handler can handle any backend request having
94 * an ajaxID as parameter (see Application.php in EXT:backend)
95 *
96 * @param ServerRequestInterface $request
97 * @return bool If the request is an AJAX backend request, TRUE otherwise FALSE
98 */
99 public function canHandleRequest(ServerRequestInterface $request)
100 {
101 return $request->getAttribute('isAjaxRequest', false);
102 }
103
104 /**
105 * Returns the priority - how eager the handler is to actually handle the request.
106 *
107 * @return int The priority of the request handler.
108 */
109 public function getPriority()
110 {
111 return 80;
112 }
113
114 /**
115 * Check if the user is required for the request
116 * If we're trying to do an ajax login, don't require a user
117 *
118 * @param string $ajaxId the Ajax ID to check against
119 * @return bool whether the request can proceed without a login required
120 */
121 protected function isLoggedInBackendUserRequired($ajaxId)
122 {
123 return in_array($ajaxId, $this->publicAjaxIds, true);
124 }
125
126 /**
127 * Start the Backend bootstrap part
128 *
129 * @param bool $proceedIfNoUserIsLoggedIn a flag if a backend user is required
130 */
131 protected function boot($proceedIfNoUserIsLoggedIn)
132 {
133 $this->bootstrap
134 ->checkLockedBackendAndRedirectOrDie($proceedIfNoUserIsLoggedIn)
135 ->checkBackendIpOrDie()
136 ->checkSslBackendAndRedirectIfNeeded()
137 ->initializeBackendRouter()
138 ->loadExtensionTables()
139 ->initializeBackendUser()
140 ->initializeBackendAuthentication($proceedIfNoUserIsLoggedIn)
141 ->initializeLanguageObject()
142 ->initializeBackendTemplate()
143 ->endOutputBufferingAndCleanPreviousOutput()
144 ->initializeOutputCompression()
145 ->sendHttpHeaders();
146 }
147
148 /**
149 * Creates a response object with JSON headers automatically, and then dispatches to the correct route
150 *
151 * @param ServerRequestInterface $request
152 * @return ResponseInterface $response
153 * @throws ResourceNotFoundException if no valid route was found
154 * @throws RouteNotFoundException if the request could not be verified
155 */
156 protected function dispatch(ServerRequestInterface $request)
157 {
158 /** @var Response $response */
159 $response = GeneralUtility::makeInstance(Response::class, 'php://temp', 200, [
160 'Content-type' => 'application/json; charset=utf-8',
161 'X-JSON' => 'true'
162 ]);
163
164 /** @var RouteDispatcher $dispatcher */
165 $dispatcher = GeneralUtility::makeInstance(RouteDispatcher::class);
166 return $dispatcher->dispatch($request, $response);
167 }
168
169 /**
170 * Calls the ajax callback method registered in TYPO3_CONF_VARS[BE][AJAX]
171 *
172 * @param ServerRequestInterface $request
173 * @return ResponseInterface
174 * @deprecated since TYPO3 v8, will be removed in TYPO3 v9
175 */
176 protected function dispatchTraditionalAjaxRequest($request)
177 {
178 GeneralUtility::deprecationLog('Using the traditional way for AJAX requests via $TYPO3_CONF_VARS[BE][AJAX] is discouraged. Use the Backend Routes logic instead.');
179 $ajaxID = $request->getAttribute('routePath');
180 // Finding the script path from the registry
181 $ajaxRegistryEntry = isset($GLOBALS['TYPO3_CONF_VARS']['BE']['AJAX'][$ajaxID]) ? $GLOBALS['TYPO3_CONF_VARS']['BE']['AJAX'][$ajaxID] : null;
182 $ajaxScript = null;
183 $csrfTokenCheck = false;
184 if ($ajaxRegistryEntry !== null && is_array($ajaxRegistryEntry) && isset($ajaxRegistryEntry['callbackMethod'])) {
185 $ajaxScript = $ajaxRegistryEntry['callbackMethod'];
186 $csrfTokenCheck = $ajaxRegistryEntry['csrfTokenCheck'];
187 }
188
189 // Instantiating the AJAX object
190 /** @var \TYPO3\CMS\Core\Http\AjaxRequestHandler $ajaxObj */
191 $ajaxObj = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Http\AjaxRequestHandler::class, $ajaxID);
192 $ajaxParams = array('request' => $request);
193
194 // Evaluating the arguments and calling the AJAX method/function
195 if (empty($ajaxID)) {
196 $ajaxObj->setError('No valid ajaxID parameter given.');
197 } elseif (empty($ajaxScript)) {
198 $ajaxObj->setError('No backend function registered for ajaxID "' . $ajaxID . '".');
199 } elseif ($csrfTokenCheck && !$this->isValidRequest($request)) {
200 $ajaxObj->setError('Invalid CSRF token detected for ajaxID "' . $ajaxID . '"!');
201 } else {
202 $success = GeneralUtility::callUserFunction($ajaxScript, $ajaxParams, $ajaxObj, '', 1);
203 if ($success === false) {
204 $ajaxObj->setError('Registered backend function for ajaxID "' . $ajaxID . '" was not found.');
205 }
206 }
207
208 // Outputting the content (and setting the X-JSON-Header)
209 return $ajaxObj->render();
210 }
211
212 /**
213 * Wrapper method for static form protection utility
214 *
215 * @return \TYPO3\CMS\Core\FormProtection\AbstractFormProtection
216 */
217 protected function getFormProtection()
218 {
219 return FormProtectionFactory::get();
220 }
221
222 /**
223 * Checks if the request token is valid. This is checked to see if the route is really
224 * created by the same instance. Should be called for all routes in the backend except
225 * for the ones that don't require a login.
226 *
227 * @param ServerRequestInterface $request
228 * @return bool
229 * @see \TYPO3\CMS\Backend\Routing\UriBuilder where the token is generated.
230 */
231 protected function isValidRequest(ServerRequestInterface $request)
232 {
233 $token = (string)(isset($request->getParsedBody()['ajaxToken']) ? $request->getParsedBody()['ajaxToken'] : $request->getQueryParams()['ajaxToken']);
234 return $this->getFormProtection()->validateToken($token, 'ajaxCall', $request->getAttribute('routePath'));
235 }
236 }