[CLEANUP] Replace wrong/old file copyright comments
[Packages/TYPO3.CMS.git] / typo3 / sysext / extbase / Classes / Mvc / Controller / ActionController.php
1 <?php
2 namespace TYPO3\CMS\Extbase\Mvc\Controller;
3
4 /***************************************************************
5 * Copyright notice
6 *
7 * (c) 2010-2012 Extbase Team (http://forge.typo3.org/projects/typo3v4-mvc)
8 * Extbase is a backport of TYPO3 Flow. All credits go to the TYPO3 Flow team.
9 * All rights reserved
10 *
11 * This script is part of the TYPO3 project. The TYPO3 project is
12 * free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
16 *
17 * The GNU General Public License can be found at
18 * http://www.gnu.org/copyleft/gpl.html.
19 * A copy is found in the textfile GPL.txt and important notices to the license
20 * from the author is found in LICENSE.txt distributed with these scripts.
21 *
22 *
23 * This script is distributed in the hope that it will be useful,
24 * but WITHOUT ANY WARRANTY; without even the implied warranty of
25 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26 * GNU General Public License for more details.
27 *
28 * This copyright notice MUST APPEAR in all copies of the script!
29 ***************************************************************/
30 /**
31 * A multi action controller. This is by far the most common base class for Controllers.
32 *
33 * @api
34 */
35 class ActionController extends \TYPO3\CMS\Extbase\Mvc\Controller\AbstractController {
36
37 /**
38 * @var \TYPO3\CMS\Extbase\Reflection\ReflectionService
39 */
40 protected $reflectionService;
41
42 /**
43 * @var \TYPO3\CMS\Extbase\Service\CacheService
44 */
45 protected $cacheService;
46
47 /**
48 * The current view, as resolved by resolveView()
49 *
50 * @var \TYPO3\CMS\Extbase\Mvc\View\ViewInterface
51 * @api
52 */
53 protected $view = NULL;
54
55 /**
56 * Pattern after which the view object name is built if no Fluid template
57 * is found.
58 *
59 * @var string
60 * @api
61 */
62 protected $viewObjectNamePattern = 'Tx_@extension_View_@controller_@action@format';
63
64 /**
65 * A list of formats and object names of the views which should render them.
66 *
67 * Example:
68 *
69 * array('html' => 'Tx_MyExtension_View_MyHtmlView', 'json' => 'F3...
70 *
71 * @var array
72 */
73 protected $viewFormatToObjectNameMap = array();
74
75 /**
76 * The default view object to use if none of the resolved views can render
77 * a response for the current request.
78 *
79 * @var string
80 * @api
81 */
82 protected $defaultViewObjectName = 'TYPO3\\CMS\\Fluid\\View\\TemplateView';
83
84 /**
85 * Name of the action method
86 *
87 * @var string
88 * @api
89 */
90 protected $actionMethodName = 'indexAction';
91
92 /**
93 * Name of the special error action method which is called in case of errors
94 *
95 * @var string
96 * @api
97 */
98 protected $errorMethodName = 'errorAction';
99
100 /**
101 * @param \TYPO3\CMS\Extbase\Reflection\ReflectionService $reflectionService
102 * @return void
103 */
104 public function injectReflectionService(\TYPO3\CMS\Extbase\Reflection\ReflectionService $reflectionService) {
105 $this->reflectionService = $reflectionService;
106 }
107
108 /**
109 * @param \TYPO3\CMS\Extbase\Service\CacheService $cacheService
110 * @return void
111 */
112 public function injectCacheService(\TYPO3\CMS\Extbase\Service\CacheService $cacheService) {
113 $this->cacheService = $cacheService;
114 }
115
116 /**
117 * Checks if the current request type is supported by the controller.
118 *
119 * If your controller only supports certain request types, either
120 * replace / modify the supporteRequestTypes property or override this
121 * method.
122 *
123 * @param \TYPO3\CMS\Extbase\Mvc\RequestInterface $request The current request
124 * @return boolean TRUE if this request type is supported, otherwise FALSE
125 */
126 public function canProcessRequest(\TYPO3\CMS\Extbase\Mvc\RequestInterface $request) {
127 return parent::canProcessRequest($request);
128 }
129
130 /**
131 * Handles a request. The result output is returned by altering the given response.
132 *
133 * @param \TYPO3\CMS\Extbase\Mvc\RequestInterface $request The request object
134 * @param \TYPO3\CMS\Extbase\Mvc\ResponseInterface $response The response, modified by this handler
135 * @throws \TYPO3\CMS\Extbase\Mvc\Exception\UnsupportedRequestTypeException
136 * @return void
137 */
138 public function processRequest(\TYPO3\CMS\Extbase\Mvc\RequestInterface $request, \TYPO3\CMS\Extbase\Mvc\ResponseInterface $response) {
139 if (!$this->canProcessRequest($request)) {
140 throw new \TYPO3\CMS\Extbase\Mvc\Exception\UnsupportedRequestTypeException(get_class($this) . ' does not support requests of type "' . get_class($request) . '". Supported types are: ' . implode(' ', $this->supportedRequestTypes), 1187701131);
141 }
142 if ($response instanceof \TYPO3\CMS\Extbase\Mvc\Web\Response) {
143 $response->setRequest($request);
144 }
145 $this->request = $request;
146 $this->request->setDispatched(TRUE);
147 $this->response = $response;
148 $this->uriBuilder = $this->objectManager->create('TYPO3\\CMS\\Extbase\\Mvc\\Web\\Routing\\UriBuilder');
149 $this->uriBuilder->setRequest($request);
150 $this->actionMethodName = $this->resolveActionMethodName();
151 $this->initializeActionMethodArguments();
152 $this->initializeActionMethodValidators();
153 $this->initializeAction();
154 $actionInitializationMethodName = 'initialize' . ucfirst($this->actionMethodName);
155 if (method_exists($this, $actionInitializationMethodName)) {
156 call_user_func(array($this, $actionInitializationMethodName));
157 }
158 $this->mapRequestArgumentsToControllerArguments();
159 $this->checkRequestHash();
160 $this->controllerContext = $this->buildControllerContext();
161 $this->view = $this->resolveView();
162 if ($this->view !== NULL) {
163 $this->initializeView($this->view);
164 }
165 $this->callActionMethod();
166 }
167
168 /**
169 * Implementation of the arguments initilization in the action controller:
170 * Automatically registers arguments of the current action
171 *
172 * Don't override this method - use initializeAction() instead.
173 *
174 * @throws \TYPO3\CMS\Extbase\Mvc\Exception\InvalidArgumentTypeException
175 * @return void
176 * @see initializeArguments()
177 */
178 protected function initializeActionMethodArguments() {
179 $methodParameters = $this->reflectionService->getMethodParameters(get_class($this), $this->actionMethodName);
180 foreach ($methodParameters as $parameterName => $parameterInfo) {
181 $dataType = NULL;
182 if (isset($parameterInfo['type'])) {
183 $dataType = $parameterInfo['type'];
184 } elseif ($parameterInfo['array']) {
185 $dataType = 'array';
186 }
187 if ($dataType === NULL) {
188 throw new \TYPO3\CMS\Extbase\Mvc\Exception\InvalidArgumentTypeException('The argument type for parameter $' . $parameterName . ' of method ' . get_class($this) . '->' . $this->actionMethodName . '() could not be detected.', 1253175643);
189 }
190 $defaultValue = isset($parameterInfo['defaultValue']) ? $parameterInfo['defaultValue'] : NULL;
191 $this->arguments->addNewArgument($parameterName, $dataType, $parameterInfo['optional'] === FALSE, $defaultValue);
192 }
193 }
194
195 /**
196 * Adds the needed valiators to the Arguments:
197 * - Validators checking the data type from the @param annotation
198 * - Custom validators specified with @validate.
199 *
200 * In case @dontvalidate is NOT set for an argument, the following two
201 * validators are also added:
202 * - Model-based validators (@validate annotations in the model)
203 * - Custom model validator classes
204 *
205 * @return void
206 */
207 protected function initializeActionMethodValidators() {
208 // TODO: still needs to be modified
209 $parameterValidators = $this->validatorResolver->buildMethodArgumentsValidatorConjunctions(get_class($this), $this->actionMethodName);
210 $dontValidateAnnotations = array();
211 if (!$this->configurationManager->isFeatureEnabled('rewrittenPropertyMapper')) {
212 // If the rewritten property mapper is *enabled*, we do not support @dontvalidate annotation, thus $dontValidateAnnotations stays empty.
213 $methodTagsValues = $this->reflectionService->getMethodTagsValues(get_class($this), $this->actionMethodName);
214 if (isset($methodTagsValues['dontvalidate'])) {
215 $dontValidateAnnotations = $methodTagsValues['dontvalidate'];
216 }
217 }
218 foreach ($this->arguments as $argument) {
219 $validator = $parameterValidators[$argument->getName()];
220 if (array_search('$' . $argument->getName(), $dontValidateAnnotations) === FALSE) {
221 $baseValidatorConjunction = $this->validatorResolver->getBaseValidatorConjunction($argument->getDataType());
222 if ($baseValidatorConjunction !== NULL) {
223 $validator->addValidator($baseValidatorConjunction);
224 }
225 }
226 $argument->setValidator($validator);
227 }
228 }
229
230 /**
231 * Resolves and checks the current action method name
232 *
233 * @return string Method name of the current action
234 * @throws \TYPO3\CMS\Extbase\Mvc\Exception\NoSuchActionException if the action specified in the request object does not exist (and if there's no default action either).
235 */
236 protected function resolveActionMethodName() {
237 $actionMethodName = $this->request->getControllerActionName() . 'Action';
238 if (!method_exists($this, $actionMethodName)) {
239 throw new \TYPO3\CMS\Extbase\Mvc\Exception\NoSuchActionException('An action "' . $actionMethodName . '" does not exist in controller "' . get_class($this) . '".', 1186669086);
240 }
241 return $actionMethodName;
242 }
243
244 /**
245 * Calls the specified action method and passes the arguments.
246 *
247 * If the action returns a string, it is appended to the content in the
248 * response object. If the action doesn't return anything and a valid
249 * view exists, the view is rendered automatically.
250 *
251 * @return void
252 * @api
253 */
254 protected function callActionMethod() {
255 if ($this->configurationManager->isFeatureEnabled('rewrittenPropertyMapper')) {
256 // enabled since Extbase 1.4.0.
257 $preparedArguments = array();
258 foreach ($this->arguments as $argument) {
259 $preparedArguments[] = $argument->getValue();
260 }
261 $validationResult = $this->arguments->getValidationResults();
262 if (!$validationResult->hasErrors()) {
263 $actionResult = call_user_func_array(array($this, $this->actionMethodName), $preparedArguments);
264 } else {
265 $methodTagsValues = $this->reflectionService->getMethodTagsValues(get_class($this), $this->actionMethodName);
266 $ignoreValidationAnnotations = array();
267 if (isset($methodTagsValues['ignorevalidation'])) {
268 $ignoreValidationAnnotations = $methodTagsValues['ignorevalidation'];
269 }
270 // if there exists more errors than in ignoreValidationAnnotations_=> call error method
271 // else => call action method
272 $shouldCallActionMethod = TRUE;
273 foreach ($validationResult->getSubResults() as $argumentName => $subValidationResult) {
274 if (!$subValidationResult->hasErrors()) {
275 continue;
276 }
277 if (array_search('$' . $argumentName, $ignoreValidationAnnotations) !== FALSE) {
278 continue;
279 }
280 $shouldCallActionMethod = FALSE;
281 }
282 if ($shouldCallActionMethod) {
283 $actionResult = call_user_func_array(array($this, $this->actionMethodName), $preparedArguments);
284 } else {
285 $actionResult = call_user_func(array($this, $this->errorMethodName));
286 }
287 }
288 } else {
289 // @deprecated since Extbase 1.4.0, will be removed with Extbase 6.1
290 $preparedArguments = array();
291 foreach ($this->arguments as $argument) {
292 $preparedArguments[] = $argument->getValue();
293 }
294 if ($this->argumentsMappingResults->hasErrors()) {
295 $actionResult = call_user_func(array($this, $this->errorMethodName));
296 } else {
297 $actionResult = call_user_func_array(array($this, $this->actionMethodName), $preparedArguments);
298 }
299 }
300 if ($actionResult === NULL && $this->view instanceof \TYPO3\CMS\Extbase\Mvc\View\ViewInterface) {
301 $this->response->appendContent($this->view->render());
302 } elseif (is_string($actionResult) && strlen($actionResult) > 0) {
303 $this->response->appendContent($actionResult);
304 } elseif (is_object($actionResult) && method_exists($actionResult, '__toString')) {
305 $this->response->appendContent((string) $actionResult);
306 }
307 }
308
309 /**
310 * Prepares a view for the current action and stores it in $this->view.
311 * By default, this method tries to locate a view with a name matching
312 * the current action.
313 *
314 * @return string
315 * @api
316 */
317 protected function resolveView() {
318 $viewObjectName = $this->resolveViewObjectName();
319 if ($viewObjectName !== FALSE) {
320 /** @var $view \TYPO3\CMS\Extbase\Mvc\View\ViewInterface */
321 $view = $this->objectManager->create($viewObjectName);
322 $this->setViewConfiguration($view);
323 if ($view->canRender($this->controllerContext) === FALSE) {
324 unset($view);
325 }
326 }
327 if (!isset($view) && $this->defaultViewObjectName != '') {
328 /** @var $view \TYPO3\CMS\Extbase\Mvc\View\ViewInterface */
329 $view = $this->objectManager->create($this->defaultViewObjectName);
330 $this->setViewConfiguration($view);
331 if ($view->canRender($this->controllerContext) === FALSE) {
332 unset($view);
333 }
334 }
335 if (!isset($view)) {
336 $view = $this->objectManager->create('TYPO3\\CMS\\Extbase\\Mvc\\View\\NotFoundView');
337 $view->assign('errorMessage', 'No template was found. View could not be resolved for action "' . $this->request->getControllerActionName() . '"');
338 }
339 $view->setControllerContext($this->controllerContext);
340 if (method_exists($view, 'injectSettings')) {
341 $view->injectSettings($this->settings);
342 }
343 $view->initializeView();
344 // In FLOW3, solved through Object Lifecycle methods, we need to call it explicitely
345 $view->assign('settings', $this->settings);
346 // same with settings injection.
347 return $view;
348 }
349
350 /**
351 * @param \TYPO3\CMS\Extbase\Mvc\View\ViewInterface $view
352 * @return void
353 */
354 protected function setViewConfiguration(\TYPO3\CMS\Extbase\Mvc\View\ViewInterface $view) {
355 // Template Path Override
356 $extbaseFrameworkConfiguration = $this->configurationManager->getConfiguration(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK);
357 if (isset($extbaseFrameworkConfiguration['view']['templateRootPath']) && strlen($extbaseFrameworkConfiguration['view']['templateRootPath']) > 0 && method_exists($view, 'setTemplateRootPath')) {
358 $view->setTemplateRootPath(\TYPO3\CMS\Core\Utility\GeneralUtility::getFileAbsFileName($extbaseFrameworkConfiguration['view']['templateRootPath']));
359 }
360 if (isset($extbaseFrameworkConfiguration['view']['layoutRootPath']) && strlen($extbaseFrameworkConfiguration['view']['layoutRootPath']) > 0 && method_exists($view, 'setLayoutRootPath')) {
361 $view->setLayoutRootPath(\TYPO3\CMS\Core\Utility\GeneralUtility::getFileAbsFileName($extbaseFrameworkConfiguration['view']['layoutRootPath']));
362 }
363 if (isset($extbaseFrameworkConfiguration['view']['partialRootPath']) && strlen($extbaseFrameworkConfiguration['view']['partialRootPath']) > 0 && method_exists($view, 'setPartialRootPath')) {
364 $view->setPartialRootPath(\TYPO3\CMS\Core\Utility\GeneralUtility::getFileAbsFileName($extbaseFrameworkConfiguration['view']['partialRootPath']));
365 }
366 }
367
368 /**
369 * Determines the fully qualified view object name.
370 *
371 * @return mixed The fully qualified view object name or FALSE if no matching view could be found.
372 * @api
373 */
374 protected function resolveViewObjectName() {
375 $possibleViewName = $this->viewObjectNamePattern;
376 $extensionName = $this->request->getControllerExtensionName();
377 $possibleViewName = str_replace('@extension', $extensionName, $possibleViewName);
378 $possibleViewName = str_replace('@controller', $this->request->getControllerName(), $possibleViewName);
379 $possibleViewName = str_replace('@action', ucfirst($this->request->getControllerActionName()), $possibleViewName);
380 $format = $this->request->getFormat();
381 $viewObjectName = str_replace('@format', ucfirst($this->request->getFormat()), $possibleViewName);
382 if (class_exists($viewObjectName) === FALSE) {
383 $viewObjectName = str_replace('@format', '', $possibleViewName);
384 }
385 if (class_exists($viewObjectName) === FALSE && isset($this->viewFormatToObjectNameMap[$format])) {
386 $viewObjectName = $this->viewFormatToObjectNameMap[$format];
387 }
388 return class_exists($viewObjectName) ? $viewObjectName : FALSE;
389 }
390
391 /**
392 * Initializes the view before invoking an action method.
393 *
394 * Override this method to solve assign variables common for all actions
395 * or prepare the view in another way before the action is called.
396 *
397 * @param \TYPO3\CMS\Extbase\Mvc\View\ViewInterface $view The view to be initialized
398 * @return void
399 * @api
400 */
401 protected function initializeView(\TYPO3\CMS\Extbase\Mvc\View\ViewInterface $view) {
402 }
403
404 /**
405 * Initializes the controller before invoking an action method.
406 *
407 * Override this method to solve tasks which all actions have in
408 * common.
409 *
410 * @return void
411 * @api
412 */
413 protected function initializeAction() {
414 }
415
416 /**
417 * A special action which is called if the originally intended action could
418 * not be called, for example if the arguments were not valid.
419 *
420 * The default implementation sets a flash message, request errors and forwards back
421 * to the originating action. This is suitable for most actions dealing with form input.
422 *
423 * We clear the page cache by default on an error as well, as we need to make sure the
424 * data is re-evaluated when the user changes something.
425 *
426 * @return string
427 * @api
428 */
429 protected function errorAction() {
430 $this->clearCacheOnError();
431 if ($this->configurationManager->isFeatureEnabled('rewrittenPropertyMapper')) {
432 $errorFlashMessage = $this->getErrorFlashMessage();
433 if ($errorFlashMessage !== FALSE) {
434 $this->flashMessageContainer->add($errorFlashMessage, '', \TYPO3\CMS\Core\Messaging\FlashMessage::ERROR);
435 }
436 $referringRequest = $this->request->getReferringRequest();
437 if ($referringRequest !== NULL) {
438 $originalRequest = clone $this->request;
439 $this->request->setOriginalRequest($originalRequest);
440 $this->request->setOriginalRequestMappingResults($this->arguments->getValidationResults());
441 $this->forward($referringRequest->getControllerActionName(), $referringRequest->getControllerName(), $referringRequest->getControllerExtensionName(), $referringRequest->getArguments());
442 }
443 $message = 'An error occurred while trying to call ' . get_class($this) . '->' . $this->actionMethodName . '().' . PHP_EOL;
444 foreach ($this->arguments->getValidationResults()->getFlattenedErrors() as $propertyPath => $errors) {
445 foreach ($errors as $error) {
446 $message .= 'Error for ' . $propertyPath . ': ' . $error->render() . PHP_EOL;
447 }
448 }
449 return $message;
450 } else {
451 // @deprecated since Extbase 1.4.0, will be removed in Extbase 6.1
452 $this->request->setErrors($this->argumentsMappingResults->getErrors());
453 $errorFlashMessage = $this->getErrorFlashMessage();
454 if ($errorFlashMessage !== FALSE) {
455 $this->flashMessageContainer->add($errorFlashMessage, '', \TYPO3\CMS\Core\Messaging\FlashMessage::ERROR);
456 }
457 $referrer = $this->request->getInternalArgument('__referrer');
458 if ($referrer !== NULL) {
459 $this->forward($referrer['actionName'], $referrer['controllerName'], $referrer['extensionName'], $this->request->getArguments());
460 }
461 $message = 'An error occurred while trying to call ' . get_class($this) . '->' . $this->actionMethodName . '().' . PHP_EOL;
462 foreach ($this->argumentsMappingResults->getErrors() as $error) {
463 $message .= 'Error: ' . $error->getMessage() . PHP_EOL;
464 }
465 foreach ($this->argumentsMappingResults->getWarnings() as $warning) {
466 $message .= 'Warning: ' . $warning->getMessage() . PHP_EOL;
467 }
468 return $message;
469 }
470 }
471
472 /**
473 * A template method for displaying custom error flash messages, or to
474 * display no flash message at all on errors. Override this to customize
475 * the flash message in your action controller.
476 *
477 * @return string|boolean The flash message or FALSE if no flash message should be set
478 * @api
479 */
480 protected function getErrorFlashMessage() {
481 return 'An error occurred while trying to call ' . get_class($this) . '->' . $this->actionMethodName . '()';
482 }
483
484 /**
485 * Checks the request hash (HMAC), if arguments have been touched by the property mapper.
486 *
487 * In case the @dontverifyrequesthash-Annotation has been set, this suppresses the exception.
488 *
489 * @return void
490 * @throws \TYPO3\CMS\Extbase\Mvc\Exception\InvalidOrNoRequestHashException In case request hash checking failed
491 * @author Sebastian Kurf├╝rst <sebastian@typo3.org>
492 * @deprecated since Extbase 1.4.0, will be removed in Extbase 6.1
493 */
494 protected function checkRequestHash() {
495 if ($this->configurationManager->isFeatureEnabled('rewrittenPropertyMapper')) {
496 // If the new property mapper is enabled, the request hash is not needed anymore.
497 return;
498 }
499 if (!$this->request instanceof \TYPO3\CMS\Extbase\Mvc\Web\Request) {
500 return;
501 }
502 // We only want to check it for now for web requests.
503 if ($this->request->isHmacVerified()) {
504 return;
505 }
506 // all good
507 $verificationNeeded = FALSE;
508 foreach ($this->arguments as $argument) {
509 if ($argument->getOrigin() == \TYPO3\CMS\Extbase\Mvc\Controller\Argument::ORIGIN_NEWLY_CREATED || $argument->getOrigin() == \TYPO3\CMS\Extbase\Mvc\Controller\Argument::ORIGIN_PERSISTENCE_AND_MODIFIED) {
510 $verificationNeeded = TRUE;
511 }
512 }
513 if ($verificationNeeded) {
514 $methodTagsValues = $this->reflectionService->getMethodTagsValues(get_class($this), $this->actionMethodName);
515 if (!isset($methodTagsValues['dontverifyrequesthash'])) {
516 throw new \TYPO3\CMS\Extbase\Mvc\Exception\InvalidOrNoRequestHashException('Request hash (HMAC) checking failed. The parameter __hmac was invalid or not set, and objects were modified.', 1255082824);
517 }
518 }
519 }
520
521 /**
522 * Clear cache of current page on error. Needed because we want a re-evaluation of the data.
523 * Better would be just do delete the cache for the error action, but that is not possible right now.
524 *
525 * @return void
526 */
527 protected function clearCacheOnError() {
528 $extbaseSettings = $this->configurationManager->getConfiguration(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK);
529 if (isset($extbaseSettings['persistence']['enableAutomaticCacheClearing']) && $extbaseSettings['persistence']['enableAutomaticCacheClearing'] === '1') {
530 if (isset($GLOBALS['TSFE'])) {
531 $pageUid = $GLOBALS['TSFE']->id;
532 $this->cacheService->clearPageCache(array($pageUid));
533 }
534 }
535 }
536 }
537
538 ?>