2 /***************************************************************
5 * (c) 2009 Jochen Rau <jochen.rau@typoplanet.de>
8 * This class is a backport of the corresponding class of FLOW3.
9 * All credits go to the v5 team.
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.
17 * The GNU General Public License can be found at
18 * http://www.gnu.org/copyleft/gpl.html.
20 * This script is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU General Public License for more details.
25 * This copyright notice MUST APPEAR in all copies of the script!
26 ***************************************************************/
29 * A multi action controller. This is by far the most common base class for Controllers.
32 * @subpackage MVC\Controller
36 class Tx_Extbase_MVC_Controller_ActionController
extends Tx_Extbase_MVC_Controller_AbstractController
{
39 * @var Tx_Extbase_Reflection_Service
41 protected $reflectionService;
44 * @var Tx_Extbase_Service_CacheService
46 protected $cacheService;
49 * The current view, as resolved by resolveView()
51 * @var Tx_Extbase_MVC_View_ViewInterface
54 protected $view = NULL
;
57 * Pattern after which the view object name is built if no Fluid template
62 protected $viewObjectNamePattern = 'Tx_@extension_View_@controller_@action@format';
65 * A list of formats and object names of the views which should render them.
69 * array('html' => 'Tx_MyExtension_View_MyHtmlView', 'json' => 'F3...
73 protected $viewFormatToObjectNameMap = array();
76 * The default view object to use if none of the resolved views can render
77 * a response for the current request.
82 protected $defaultViewObjectName = 'Tx_Fluid_View_TemplateView';
85 * Name of the action method
89 protected $actionMethodName = 'indexAction';
92 * Name of the special error action method which is called in case of errors
96 protected $errorMethodName = 'errorAction';
99 * @param Tx_Extbase_Reflection_Service $reflectionService
102 public function injectReflectionService(Tx_Extbase_Reflection_Service
$reflectionService) {
103 $this->reflectionService
= $reflectionService;
107 * @param Tx_Extbase_Service_CacheService $cacheService
110 public function injectCacheService(Tx_Extbase_Service_CacheService
$cacheService) {
111 $this->cacheService
= $cacheService;
115 * Checks if the current request type is supported by the controller.
117 * If your controller only supports certain request types, either
118 * replace / modify the supporteRequestTypes property or override this
121 * @param Tx_Extbase_MVC_Request $request The current request
122 * @return boolean TRUE if this request type is supported, otherwise FALSE
124 public function canProcessRequest(Tx_Extbase_MVC_RequestInterface
$request) {
125 return parent
::canProcessRequest($request);
130 * Handles a request. The result output is returned by altering the given response.
132 * @param Tx_Extbase_MVC_Request $request The request object
133 * @param Tx_Extbase_MVC_Response $response The response, modified by this handler
136 public function processRequest(Tx_Extbase_MVC_RequestInterface
$request, Tx_Extbase_MVC_ResponseInterface
$response) {
137 if (!$this->canProcessRequest($request)) {
138 throw new Tx_Extbase_MVC_Exception_UnsupportedRequestType(get_class($this) . ' does not support requests of type "' . get_class($request) . '". Supported types are: ' . implode(' ', $this->supportedRequestTypes
) , 1187701131);
141 $this->request
= $request;
142 $this->request
->setDispatched(TRUE
);
143 $this->response
= $response;
145 $this->uriBuilder
= $this->objectManager
->create('Tx_Extbase_MVC_Web_Routing_UriBuilder');
146 $this->uriBuilder
->setRequest($request);
148 $this->actionMethodName
= $this->resolveActionMethodName();
150 $this->initializeActionMethodArguments();
151 $this->initializeActionMethodValidators();
153 $this->initializeAction();
154 $actionInitializationMethodName = 'initialize' . ucfirst($this->actionMethodName
);
155 if (method_exists($this, $actionInitializationMethodName)) {
156 call_user_func(array($this, $actionInitializationMethodName));
159 $this->mapRequestArgumentsToControllerArguments();
160 $this->checkRequestHash();
161 $this->controllerContext
= $this->buildControllerContext();
162 $this->view
= $this->resolveView();
163 if ($this->view
!== NULL
) {
164 $this->initializeView($this->view
);
166 $this->callActionMethod();
170 * Implementation of the arguments initilization in the action controller:
171 * Automatically registers arguments of the current action
173 * Don't override this method - use initializeAction() instead.
176 * @see initializeArguments()
178 protected function initializeActionMethodArguments() {
179 $methodParameters = $this->reflectionService
->getMethodParameters(get_class($this), $this->actionMethodName
);
181 foreach ($methodParameters as $parameterName => $parameterInfo) {
183 if (isset($parameterInfo['type'])) {
184 $dataType = $parameterInfo['type'];
185 } elseif ($parameterInfo['array']) {
188 if ($dataType === NULL
) throw new Tx_Extbase_MVC_Exception_InvalidArgumentType('The argument type for parameter $' . $parameterName . ' of method ' . get_class($this) . '->' . $this->actionMethodName
. '() could not be detected.' , 1253175643);
190 $defaultValue = (isset($parameterInfo['defaultValue']) ?
$parameterInfo['defaultValue'] : NULL
);
192 $this->arguments
->addNewArgument($parameterName, $dataType, ($parameterInfo['optional'] === FALSE
), $defaultValue);
197 * Adds the needed valiators to the Arguments:
198 * - Validators checking the data type from the @param annotation
199 * - Custom validators specified with @validate.
201 * In case @dontvalidate is NOT set for an argument, the following two
202 * validators are also added:
203 * - Model-based validators (@validate annotations in the model)
204 * - Custom model validator classes
208 protected function initializeActionMethodValidators() {
209 // TODO: still needs to be modified
210 $parameterValidators = $this->validatorResolver
->buildMethodArgumentsValidatorConjunctions(get_class($this), $this->actionMethodName
);
212 $dontValidateAnnotations = array();
214 if (!$this->configurationManager
->isFeatureEnabled('rewrittenPropertyMapper')) {
215 // If the rewritten property mapper is *enabled*, we do not support @dontvalidate annotation, thus $dontValidateAnnotations stays empty.
216 $methodTagsValues = $this->reflectionService
->getMethodTagsValues(get_class($this), $this->actionMethodName
);
217 if (isset($methodTagsValues['dontvalidate'])) {
218 $dontValidateAnnotations = $methodTagsValues['dontvalidate'];
222 foreach ($this->arguments
as $argument) {
223 $validator = $parameterValidators[$argument->getName()];
225 if (array_search('$' . $argument->getName(), $dontValidateAnnotations) === FALSE
) {
226 $baseValidatorConjunction = $this->validatorResolver
->getBaseValidatorConjunction($argument->getDataType());
227 if ($baseValidatorConjunction !== NULL
) {
228 $validator->addValidator($baseValidatorConjunction);
231 $argument->setValidator($validator);
236 * Resolves and checks the current action method name
238 * @return string Method name of the current action
239 * @throws Tx_Extbase_MVC_Exception_NoSuchAction if the action specified in the request object does not exist (and if there's no default action either).
241 protected function resolveActionMethodName() {
242 $actionMethodName = $this->request
->getControllerActionName() . 'Action';
243 if (!method_exists($this, $actionMethodName)) throw new Tx_Extbase_MVC_Exception_NoSuchAction('An action "' . $actionMethodName . '" does not exist in controller "' . get_class($this) . '".', 1186669086);
244 return $actionMethodName;
248 * Calls the specified action method and passes the arguments.
250 * If the action returns a string, it is appended to the content in the
251 * response object. If the action doesn't return anything and a valid
252 * view exists, the view is rendered automatically.
254 * @param string $actionMethodName Name of the action method to call
258 protected function callActionMethod() {
259 if ($this->configurationManager
->isFeatureEnabled('rewrittenPropertyMapper')) {
260 // enabled since Extbase 1.4.0.
261 $preparedArguments = array();
262 foreach ($this->arguments
as $argument) {
263 $preparedArguments[] = $argument->getValue();
266 $validationResult = $this->arguments
->getValidationResults();
268 if (!$validationResult->hasErrors()) {
269 $actionResult = call_user_func_array(array($this, $this->actionMethodName
), $preparedArguments);
271 $methodTagsValues = $this->reflectionService
->getMethodTagsValues(get_class($this), $this->actionMethodName
);
272 $ignoreValidationAnnotations = array();
273 if (isset($methodTagsValues['ignorevalidation'])) {
274 $ignoreValidationAnnotations = $methodTagsValues['ignorevalidation'];
277 // if there exists more errors than in ignoreValidationAnnotations_=> call error method
278 // else => call action method
279 $shouldCallActionMethod = TRUE
;
280 foreach ($validationResult->getSubResults() as $argumentName => $subValidationResult) {
281 if (!$subValidationResult->hasErrors()) continue;
283 if (array_search($argumentName, $ignoreValidationAnnotations) !== FALSE
) continue;
285 $shouldCallActionMethod = FALSE
;
288 if ($shouldCallActionMethod) {
289 $actionResult = call_user_func_array(array($this, $this->actionMethodName
), $preparedArguments);
291 $actionResult = call_user_func(array($this, $this->errorMethodName
));
295 // @deprecated since Extbase 1.4.0, will be removed with Extbase 1.6.0.
296 $argumentsAreValid = TRUE
;
297 $preparedArguments = array();
298 foreach ($this->arguments
as $argument) {
299 $preparedArguments[] = $argument->getValue();
302 if ($this->argumentsMappingResults
->hasErrors()) {
303 $actionResult = call_user_func(array($this, $this->errorMethodName
));
305 $actionResult = call_user_func_array(array($this, $this->actionMethodName
), $preparedArguments);
309 if ($actionResult === NULL
&& $this->view
instanceof Tx_Extbase_MVC_View_ViewInterface
) {
310 $this->response
->appendContent($this->view
->render());
311 } elseif (is_string($actionResult) && strlen($actionResult) > 0) {
312 $this->response
->appendContent($actionResult);
313 } elseif (is_object($actionResult) && method_exists($actionResult, '__toString')) {
314 $this->response
->appendContent((string)$actionResult);
319 * Prepares a view for the current action and stores it in $this->view.
320 * By default, this method tries to locate a view with a name matching
321 * the current action.
326 protected function resolveView() {
327 $viewObjectName = $this->resolveViewObjectName();
328 if ($viewObjectName !== FALSE
) {
329 $view = $this->objectManager
->create($viewObjectName);
330 $this->setViewConfiguration($view);
331 if ($view->canRender($this->controllerContext
) === FALSE
) {
335 if (!isset($view) && $this->defaultViewObjectName
!= '') {
336 $view = $this->objectManager
->create($this->defaultViewObjectName
);
337 $this->setViewConfiguration($view);
338 if ($view->canRender($this->controllerContext
) === FALSE
) {
343 $view = $this->objectManager
->create('Tx_Extbase_MVC_View_NotFoundView');
344 $view->assign('errorMessage', 'No template was found. View could not be resolved for action "' . $this->request
->getControllerActionName() . '"');
346 $view->setControllerContext($this->controllerContext
);
348 if (method_exists($view, 'injectSettings')) {
349 $view->injectSettings($this->settings
);
351 $view->initializeView(); // In FLOW3, solved through Object Lifecycle methods, we need to call it explicitely
352 $view->assign('settings', $this->settings
); // same with settings injection.
357 * @param Tx_Extbase_MVC_View_ViewInterface $view
360 protected function setViewConfiguration(Tx_Extbase_MVC_View_ViewInterface
$view) {
361 // Template Path Override
362 $extbaseFrameworkConfiguration = $this->configurationManager
->getConfiguration(Tx_Extbase_Configuration_ConfigurationManagerInterface
::CONFIGURATION_TYPE_FRAMEWORK
);
363 if (isset($extbaseFrameworkConfiguration['view']['templateRootPath'])
364 && strlen($extbaseFrameworkConfiguration['view']['templateRootPath']) > 0
365 && method_exists($view, 'setTemplateRootPath')) {
366 $view->setTemplateRootPath(t3lib_div
::getFileAbsFileName($extbaseFrameworkConfiguration['view']['templateRootPath']));
368 if (isset($extbaseFrameworkConfiguration['view']['layoutRootPath'])
369 && strlen($extbaseFrameworkConfiguration['view']['layoutRootPath']) > 0
370 && method_exists($view, 'setLayoutRootPath')) {
371 $view->setLayoutRootPath(t3lib_div
::getFileAbsFileName($extbaseFrameworkConfiguration['view']['layoutRootPath']));
373 if (isset($extbaseFrameworkConfiguration['view']['partialRootPath'])
374 && strlen($extbaseFrameworkConfiguration['view']['partialRootPath']) > 0
375 && method_exists($view, 'setPartialRootPath')) {
376 $view->setPartialRootPath(t3lib_div
::getFileAbsFileName($extbaseFrameworkConfiguration['view']['partialRootPath']));
381 * Determines the fully qualified view object name.
383 * @return mixed The fully qualified view object name or FALSE if no matching view could be found.
386 protected function resolveViewObjectName() {
387 $possibleViewName = $this->viewObjectNamePattern
;
388 $extensionName = $this->request
->getControllerExtensionName();
389 $possibleViewName = str_replace('@extension', $extensionName, $possibleViewName);
390 $possibleViewName = str_replace('@controller', $this->request
->getControllerName(), $possibleViewName);
391 $possibleViewName = str_replace('@action', ucfirst($this->request
->getControllerActionName()), $possibleViewName);
392 $format = $this->request
->getFormat();
394 $viewObjectName = str_replace('@format', ucfirst($this->request
->getFormat()), $possibleViewName);
395 if (class_exists($viewObjectName) === FALSE
) {
396 $viewObjectName = str_replace('@format', '', $possibleViewName);
398 if (class_exists($viewObjectName) === FALSE
&& isset($this->viewFormatToObjectNameMap
[$format])) {
399 $viewObjectName = $this->viewFormatToObjectNameMap
[$format];
401 return class_exists($viewObjectName) ?
$viewObjectName : FALSE
;
405 * Initializes the view before invoking an action method.
407 * Override this method to solve assign variables common for all actions
408 * or prepare the view in another way before the action is called.
410 * @param Tx_Extbase_View_ViewInterface $view The view to be initialized
414 protected function initializeView(Tx_Extbase_MVC_View_ViewInterface
$view) {
418 * Initializes the controller before invoking an action method.
420 * Override this method to solve tasks which all actions have in
426 protected function initializeAction() {
430 * A special action which is called if the originally intended action could
431 * not be called, for example if the arguments were not valid.
433 * The default implementation sets a flash message, request errors and forwards back
434 * to the originating action. This is suitable for most actions dealing with form input.
436 * We clear the page cache by default on an error as well, as we need to make sure the
437 * data is re-evaluated when the user changes something.
442 protected function errorAction() {
443 $this->clearCacheOnError();
445 if ($this->configurationManager
->isFeatureEnabled('rewrittenPropertyMapper')) {
446 $errorFlashMessage = $this->getErrorFlashMessage();
447 if ($errorFlashMessage !== FALSE
) {
448 $this->flashMessageContainer
->add($errorFlashMessage);
451 $referringRequest = $this->request
->getReferringRequest();
452 if ($referringRequest !== NULL
) {
453 $originalRequest = clone $this->request
;
454 $this->request
->setOriginalRequest($originalRequest);
455 $this->request
->setOriginalRequestMappingResults($this->arguments
->getValidationResults());
457 $this->forward($referringRequest->getControllerActionName(), $referringRequest->getControllerName(), $referringRequest->getControllerExtensionName(), $referringRequest->getArguments());
460 $message = 'An error occurred while trying to call ' . get_class($this) . '->' . $this->actionMethodName
. '().' . PHP_EOL
;
461 foreach ($this->arguments
->getValidationResults()->getFlattenedErrors() as $propertyPath => $errors) {
462 foreach ($errors as $error) {
463 $message .= 'Error for ' . $propertyPath . ': ' . $error->getMessage() . PHP_EOL
;
469 // @deprecated since Extbase 1.4.0, will be removed in Extbase 1.6.0.
470 $this->request
->setErrors($this->argumentsMappingResults
->getErrors());
472 $errorFlashMessage = $this->getErrorFlashMessage();
473 if ($errorFlashMessage !== FALSE
) {
474 $this->flashMessageContainer
->add($errorFlashMessage, '', t3lib_FlashMessage
::ERROR
);
477 $referrer = $this->request
->getInternalArgument('__referrer');
478 if ($referrer !== NULL
) {
479 $this->forward($referrer['actionName'], $referrer['controllerName'], $referrer['extensionName'], $this->request
->getArguments());
482 $message = 'An error occurred while trying to call ' . get_class($this) . '->' . $this->actionMethodName
. '().' . PHP_EOL
;
483 foreach ($this->argumentsMappingResults
->getErrors() as $error) {
484 $message .= 'Error: ' . $error->getMessage() . PHP_EOL
;
486 foreach ($this->argumentsMappingResults
->getWarnings() as $warning) {
487 $message .= 'Warning: ' . $warning->getMessage() . PHP_EOL
;
494 * A template method for displaying custom error flash messages, or to
495 * display no flash message at all on errors. Override this to customize
496 * the flash message in your action controller.
498 * @return string|boolean The flash message or FALSE if no flash message should be set
501 protected function getErrorFlashMessage() {
502 return 'An error occurred while trying to call ' . get_class($this) . '->' . $this->actionMethodName
. '()';
506 * Checks the request hash (HMAC), if arguments have been touched by the property mapper.
508 * In case the @dontverifyrequesthash-Annotation has been set, this suppresses the exception.
511 * @throws Tx_Extbase_MVC_Exception_InvalidOrNoRequestHash In case request hash checking failed
512 * @author Sebastian Kurfürst <sebastian@typo3.org>
513 * @deprecated since Extbase 1.4.0, will be removed in Extbase 1.6.0.
515 protected function checkRequestHash() {
516 if ($this->configurationManager
->isFeatureEnabled('rewrittenPropertyMapper')) {
517 // If the new property mapper is enabled, the request hash is not needed anymore.
520 if (!($this->request
instanceof Tx_Extbase_MVC_Web_Request
)) return; // We only want to check it for now for web requests.
521 if ($this->request
->isHmacVerified()) return; // all good
523 $verificationNeeded = FALSE
;
524 foreach ($this->arguments
as $argument) {
525 if ($argument->getOrigin() == Tx_Extbase_MVC_Controller_Argument
::ORIGIN_NEWLY_CREATED
526 ||
$argument->getOrigin() == Tx_Extbase_MVC_Controller_Argument
::ORIGIN_PERSISTENCE_AND_MODIFIED
) {
527 $verificationNeeded = TRUE
;
530 if ($verificationNeeded) {
531 $methodTagsValues = $this->reflectionService
->getMethodTagsValues(get_class($this), $this->actionMethodName
);
532 if (!isset($methodTagsValues['dontverifyrequesthash'])) {
533 throw new Tx_Extbase_MVC_Exception_InvalidOrNoRequestHash('Request hash (HMAC) checking failed. The parameter __hmac was invalid or not set, and objects were modified.', 1255082824);
539 * Clear cache of current page on error. Needed because we want a re-evaluation of the data.
540 * Better would be just do delete the cache for the error action, but that is not possible right now.
544 protected function clearCacheOnError() {
545 $extbaseSettings = $this->configurationManager
->getConfiguration(Tx_Extbase_Configuration_ConfigurationManagerInterface
::CONFIGURATION_TYPE_FRAMEWORK
);
546 if (isset($extbaseSettings['persistence']['enableAutomaticCacheClearing']) && $extbaseSettings['persistence']['enableAutomaticCacheClearing'] === '1') {
547 if (isset($GLOBALS['TSFE'])) {
548 $pageUid = $GLOBALS['TSFE']->id
;
549 $this->cacheService
->clearPageCache(array($pageUid));