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