TYPO3 API  SVNRelease
ActionController.php
Go to the documentation of this file.
00001 <?php
00002 /***************************************************************
00003 *  Copyright notice
00004 *
00005 *  (c) 2009 Jochen Rau <jochen.rau@typoplanet.de>
00006 *  All rights reserved
00007 *
00008 *  This class is a backport of the corresponding class of FLOW3.
00009 *  All credits go to the v5 team.
00010 *
00011 *  This script is part of the TYPO3 project. The TYPO3 project is
00012 *  free software; you can redistribute it and/or modify
00013 *  it under the terms of the GNU General Public License as published by
00014 *  the Free Software Foundation; either version 2 of the License, or
00015 *  (at your option) any later version.
00016 *
00017 *  The GNU General Public License can be found at
00018 *  http://www.gnu.org/copyleft/gpl.html.
00019 *
00020 *  This script is distributed in the hope that it will be useful,
00021 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00022 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00023 *  GNU General Public License for more details.
00024 *
00025 *  This copyright notice MUST APPEAR in all copies of the script!
00026 ***************************************************************/
00027 
00028 /**
00029  * A multi action controller. This is by far the most common base class for Controllers.
00030  *
00031  * @package Extbase
00032  * @subpackage MVC\Controller
00033  * @version $ID:$
00034  * @api
00035  */
00036 class Tx_Extbase_MVC_Controller_ActionController extends Tx_Extbase_MVC_Controller_AbstractController {
00037 
00038     /**
00039      * @var Tx_Extbase_Reflection_Service
00040      */
00041     protected $reflectionService;
00042 
00043     /**
00044      * The current view, as resolved by resolveView()
00045      *
00046      * @var Tx_Extbase_MVC_View_ViewInterface
00047      * @api
00048      */
00049     protected $view = NULL;
00050 
00051     /**
00052      * Pattern after which the view object name is built if no Fluid template
00053      * is found.
00054      * @var string
00055      * @api
00056      */
00057     protected $viewObjectNamePattern = 'Tx_@extension_View_@controller_@action@format';
00058 
00059     /**
00060      * A list of formats and object names of the views which should render them.
00061      *
00062      * Example:
00063      *
00064      * array('html' => 'Tx_MyExtension_View_MyHtmlView', 'json' => 'F3...
00065      *
00066      * @var array
00067      */
00068     protected $viewFormatToObjectNameMap = array();
00069 
00070     /**
00071      * The default view object to use if none of the resolved views can render
00072      * a response for the current request.
00073      *
00074      * @var string
00075      * @api
00076      */
00077     protected $defaultViewObjectName = 'Tx_Fluid_View_TemplateView';
00078 
00079     /**
00080      * Name of the action method
00081      * @var string
00082      * @api
00083      */
00084     protected $actionMethodName = 'indexAction';
00085 
00086     /**
00087      * Name of the special error action method which is called in case of errors
00088      * @var string
00089      * @api
00090      */
00091     protected $errorMethodName = 'errorAction';
00092 
00093     /**
00094      * Injects the reflection service
00095      *
00096      * @param Tx_Extbase_Reflection_Service $reflectionService
00097      * @return void
00098      */
00099     public function injectReflectionService(Tx_Extbase_Reflection_Service $reflectionService) {
00100         $this->reflectionService = $reflectionService;
00101     }
00102 
00103     /**
00104      * Checks if the current request type is supported by the controller.
00105      *
00106      * If your controller only supports certain request types, either
00107      * replace / modify the supporteRequestTypes property or override this
00108      * method.
00109      *
00110      * @param Tx_Extbase_MVC_Request $request The current request
00111      * @return boolean TRUE if this request type is supported, otherwise FALSE
00112      */
00113     public function canProcessRequest(Tx_Extbase_MVC_RequestInterface $request) {
00114         return parent::canProcessRequest($request);
00115 
00116     }
00117 
00118     /**
00119      * Handles a request. The result output is returned by altering the given response.
00120      *
00121      * @param Tx_Extbase_MVC_Request $request The request object
00122      * @param Tx_Extbase_MVC_Response $response The response, modified by this handler
00123      * @return void
00124      */
00125     public function processRequest(Tx_Extbase_MVC_RequestInterface $request, Tx_Extbase_MVC_ResponseInterface $response) {
00126         if (!$this->canProcessRequest($request)) {
00127             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);
00128         }
00129 
00130         $this->request = $request;
00131         $this->request->setDispatched(TRUE);
00132         $this->response = $response;
00133 
00134         $this->uriBuilder = $this->objectManager->create('Tx_Extbase_MVC_Web_Routing_UriBuilder');
00135         $this->uriBuilder->setRequest($request);
00136 
00137         $this->actionMethodName = $this->resolveActionMethodName();
00138 
00139         $this->initializeActionMethodArguments();
00140         $this->initializeActionMethodValidators();
00141 
00142         $this->initializeAction();
00143         $actionInitializationMethodName = 'initialize' . ucfirst($this->actionMethodName);
00144         if (method_exists($this, $actionInitializationMethodName)) {
00145             call_user_func(array($this, $actionInitializationMethodName));
00146         }
00147 
00148         $this->mapRequestArgumentsToControllerArguments();
00149         $this->checkRequestHash();
00150         $this->controllerContext = $this->buildControllerContext();
00151         $this->view = $this->resolveView();
00152         if ($this->view !== NULL) {
00153             $this->initializeView($this->view);
00154         }
00155         $this->callActionMethod();
00156     }
00157 
00158     /**
00159      * Implementation of the arguments initilization in the action controller:
00160      * Automatically registers arguments of the current action
00161      *
00162      * Don't override this method - use initializeAction() instead.
00163      *
00164      * @return void
00165      * @see initializeArguments()
00166      */
00167     protected function initializeActionMethodArguments() {
00168         $methodParameters = $this->reflectionService->getMethodParameters(get_class($this), $this->actionMethodName);
00169 
00170         foreach ($methodParameters as $parameterName => $parameterInfo) {
00171             $dataType = NULL;
00172             if (isset($parameterInfo['type'])) {
00173                 $dataType = $parameterInfo['type'];
00174             } elseif ($parameterInfo['array']) {
00175                 $dataType = 'array';
00176             }
00177             if ($dataType === NULL) throw new Tx_Extbase_MVC_Exception_InvalidArgumentType('The argument type for parameter "' . $parameterName . '" could not be detected.', 1253175643);
00178 
00179             $defaultValue = (isset($parameterInfo['defaultValue']) ? $parameterInfo['defaultValue'] : NULL);
00180 
00181             $this->arguments->addNewArgument($parameterName, $dataType, ($parameterInfo['optional'] === FALSE), $defaultValue);
00182         }
00183     }
00184 
00185     /**
00186      * Adds the needed valiators to the Arguments:
00187      * - Validators checking the data type from the @param annotation
00188      * - Custom validators specified with @validate.
00189      *
00190      * In case @dontvalidate is NOT set for an argument, the following two
00191      * validators are also added:
00192      * - Model-based validators (@validate annotations in the model)
00193      * - Custom model validator classes
00194      *
00195      * @return void
00196      */
00197     protected function initializeActionMethodValidators() {
00198         $parameterValidators = $this->validatorResolver->buildMethodArgumentsValidatorConjunctions(get_class($this), $this->actionMethodName);
00199 
00200         $dontValidateAnnotations = array();
00201         $methodTagsValues = $this->reflectionService->getMethodTagsValues(get_class($this), $this->actionMethodName);
00202         if (isset($methodTagsValues['dontvalidate'])) {
00203             $dontValidateAnnotations = $methodTagsValues['dontvalidate'];
00204         }
00205 
00206         foreach ($this->arguments as $argument) {
00207             $validator = $parameterValidators[$argument->getName()];
00208 
00209             if (array_search('$' . $argument->getName(), $dontValidateAnnotations) === FALSE) {
00210                 $baseValidatorConjunction = $this->validatorResolver->getBaseValidatorConjunction($argument->getDataType());
00211                 if ($baseValidatorConjunction !== NULL) {
00212                     $validator->addValidator($baseValidatorConjunction);
00213                 }
00214             }
00215             $argument->setValidator($validator);
00216         }
00217     }
00218 
00219     /**
00220      * Resolves and checks the current action method name
00221      *
00222      * @return string Method name of the current action
00223      * @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).
00224      */
00225     protected function resolveActionMethodName() {
00226         $actionMethodName = $this->request->getControllerActionName() . 'Action';
00227         if (!method_exists($this, $actionMethodName)) throw new Tx_Extbase_MVC_Exception_NoSuchAction('An action "' . $actionMethodName . '" does not exist in controller "' . get_class($this) . '".', 1186669086);
00228         return $actionMethodName;
00229     }
00230 
00231     /**
00232      * Calls the specified action method and passes the arguments.
00233      *
00234      * If the action returns a string, it is appended to the content in the
00235      * response object. If the action doesn't return anything and a valid
00236      * view exists, the view is rendered automatically.
00237      *
00238      * @param string $actionMethodName Name of the action method to call
00239      * @return void
00240      * @api
00241      */
00242     protected function callActionMethod() {
00243         $argumentsAreValid = TRUE;
00244         $preparedArguments = array();
00245         foreach ($this->arguments as $argument) {
00246             $preparedArguments[] = $argument->getValue();
00247         }
00248 
00249         if ($this->argumentsMappingResults->hasErrors()) {
00250             $actionResult = call_user_func(array($this, $this->errorMethodName));
00251         } else {
00252             $actionResult = call_user_func_array(array($this, $this->actionMethodName), $preparedArguments);
00253         }
00254         if ($actionResult === NULL && $this->view instanceof Tx_Extbase_MVC_View_ViewInterface) {
00255             $this->response->appendContent($this->view->render());
00256         } elseif (is_string($actionResult) && strlen($actionResult) > 0) {
00257             $this->response->appendContent($actionResult);
00258         }
00259     }
00260 
00261     /**
00262      * Prepares a view for the current action and stores it in $this->view.
00263      * By default, this method tries to locate a view with a name matching
00264      * the current action.
00265      *
00266      * @return void
00267      * @api
00268      */
00269     protected function resolveView() {
00270         $viewObjectName = $this->resolveViewObjectName();
00271         if ($viewObjectName !== FALSE) {
00272             $view = $this->objectManager->create($viewObjectName);
00273             $this->setViewConfiguration($view);
00274             if ($view->canRender($this->controllerContext) === FALSE) {
00275                 unset($view);
00276             }
00277         }
00278         if (!isset($view) && $this->defaultViewObjectName != '') {
00279             $view = $this->objectManager->create($this->defaultViewObjectName);
00280             $this->setViewConfiguration($view);
00281             if ($view->canRender($this->controllerContext) === FALSE) {
00282                 unset($view);
00283             }
00284         }
00285         if (!isset($view)) {
00286             $view = $this->objectManager->create('Tx_Extbase_MVC_View_NotFoundView');
00287             $view->assign('errorMessage', 'No template was found. View could not be resolved for action "' . $this->request->getControllerActionName() . '"');
00288         }
00289         $view->setControllerContext($this->controllerContext);
00290 
00291         if (method_exists($view, 'injectSettings')) {
00292             $view->injectSettings($this->settings);
00293         }
00294         $view->initializeView(); // In FLOW3, solved through Object Lifecycle methods, we need to call it explicitely
00295         $view->assign('settings', $this->settings); // same with settings injection.
00296         return $view;
00297     }
00298 
00299     /**
00300      * @param Tx_Extbase_MVC_View_ViewInterface $view
00301      * @return void
00302      */
00303     protected function setViewConfiguration(Tx_Extbase_MVC_View_ViewInterface $view) {
00304             // Template Path Override
00305         $extbaseFrameworkConfiguration = $this->configurationManager->getConfiguration(Tx_Extbase_Configuration_ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK);
00306         if (isset($extbaseFrameworkConfiguration['view']['templateRootPath'])
00307             && strlen($extbaseFrameworkConfiguration['view']['templateRootPath']) > 0
00308             && method_exists($view, 'setTemplateRootPath')) {
00309             $view->setTemplateRootPath(t3lib_div::getFileAbsFileName($extbaseFrameworkConfiguration['view']['templateRootPath']));
00310         }
00311         if (isset($extbaseFrameworkConfiguration['view']['layoutRootPath'])
00312             && strlen($extbaseFrameworkConfiguration['view']['layoutRootPath']) > 0
00313             && method_exists($view, 'setLayoutRootPath')) {
00314             $view->setLayoutRootPath(t3lib_div::getFileAbsFileName($extbaseFrameworkConfiguration['view']['layoutRootPath']));
00315         }
00316         if (isset($extbaseFrameworkConfiguration['view']['partialRootPath'])
00317             && strlen($extbaseFrameworkConfiguration['view']['partialRootPath']) > 0
00318             && method_exists($view, 'setPartialRootPath')) {
00319             $view->setPartialRootPath(t3lib_div::getFileAbsFileName($extbaseFrameworkConfiguration['view']['partialRootPath']));
00320         }
00321     }
00322 
00323     /**
00324      * Determines the fully qualified view object name.
00325      *
00326      * @return mixed The fully qualified view object name or FALSE if no matching view could be found.
00327      * @api
00328      */
00329     protected function resolveViewObjectName() {
00330         $possibleViewName = $this->viewObjectNamePattern;
00331         $extensionName = $this->request->getControllerExtensionName();
00332         $possibleViewName = str_replace('@extension', $extensionName, $possibleViewName);
00333         $possibleViewName = str_replace('@controller', $this->request->getControllerName(), $possibleViewName);
00334         $possibleViewName = str_replace('@action', ucfirst($this->request->getControllerActionName()), $possibleViewName);
00335         $format = $this->request->getFormat();
00336 
00337         $viewObjectName = str_replace('@format', ucfirst($this->request->getFormat()), $possibleViewName);
00338         if (class_exists($viewObjectName) === FALSE) {
00339             $viewObjectName = str_replace('@format', '', $possibleViewName);
00340         }
00341         if (class_exists($viewObjectName) === FALSE && isset($this->viewFormatToObjectNameMap[$format])) {
00342             $viewObjectName = $this->viewFormatToObjectNameMap[$format];
00343         }
00344         return class_exists($viewObjectName) ? $viewObjectName : FALSE;
00345     }
00346 
00347     /**
00348      * Initializes the view before invoking an action method.
00349      *
00350      * Override this method to solve assign variables common for all actions
00351      * or prepare the view in another way before the action is called.
00352      *
00353      * @param Tx_Extbase_View_ViewInterface $view The view to be initialized
00354      * @return void
00355      * @api
00356      */
00357     protected function initializeView(Tx_Extbase_MVC_View_ViewInterface $view) {
00358     }
00359 
00360     /**
00361      * Initializes the controller before invoking an action method.
00362      *
00363      * Override this method to solve tasks which all actions have in
00364      * common.
00365      *
00366      * @return void
00367      * @api
00368      */
00369     protected function initializeAction() {
00370     }
00371 
00372     /**
00373      * A special action which is called if the originally intended action could
00374      * not be called, for example if the arguments were not valid.
00375      *
00376      * The default implementation sets a flash message, request errors and forwards back
00377      * to the originating action. This is suitable for most actions dealing with form input.
00378      *
00379      * We clear the page cache by default on an error as well, as we need to make sure the
00380      * data is re-evaluated when the user changes something.
00381      *
00382      * @return string
00383      * @api
00384      */
00385     protected function errorAction() {
00386         $this->request->setErrors($this->argumentsMappingResults->getErrors());
00387         $this->clearCacheOnError();
00388 
00389         $errorFlashMessage = $this->getErrorFlashMessage();
00390         if ($errorFlashMessage !== FALSE) {
00391             $this->flashMessages->add($errorFlashMessage, '', t3lib_FlashMessage::ERROR);
00392         }
00393 
00394         if ($this->request->hasArgument('__referrer')) {
00395             $referrer = $this->request->getArgument('__referrer');
00396             $this->forward($referrer['actionName'], $referrer['controllerName'], $referrer['extensionName'], $this->request->getArguments());
00397         }
00398 
00399         $message = 'An error occurred while trying to call ' . get_class($this) . '->' . $this->actionMethodName . '().' . PHP_EOL;
00400         foreach ($this->argumentsMappingResults->getErrors() as $error) {
00401             $message .= 'Error:   ' . $error->getMessage() . PHP_EOL;
00402         }
00403         foreach ($this->argumentsMappingResults->getWarnings() as $warning) {
00404             $message .= 'Warning: ' . $warning->getMessage() . PHP_EOL;
00405         }
00406         return $message;
00407     }
00408 
00409     /**
00410      * A template method for displaying custom error flash messages, or to
00411      * display no flash message at all on errors. Override this to customize
00412      * the flash message in your action controller.
00413      *
00414      * @return string|boolean The flash message or FALSE if no flash message should be set
00415      * @api
00416      */
00417     protected function getErrorFlashMessage() {
00418         return 'An error occurred while trying to call ' . get_class($this) . '->' . $this->actionMethodName . '()';
00419     }
00420 
00421     /**
00422      * Checks the request hash (HMAC), if arguments have been touched by the property mapper.
00423      *
00424      * In case the @dontverifyrequesthash-Annotation has been set, this suppresses the exception.
00425      *
00426      * @return void
00427      * @throws Tx_Extbase_MVC_Exception_InvalidOrNoRequestHash In case request hash checking failed
00428      * @author Sebastian Kurfürst <sebastian@typo3.org>
00429      */
00430     protected function checkRequestHash() {
00431         if (!($this->request instanceof Tx_Extbase_MVC_Web_Request)) return; // We only want to check it for now for web requests.
00432         if ($this->request->isHmacVerified()) return; // all good
00433 
00434         $verificationNeeded = FALSE;
00435         foreach ($this->arguments as $argument) {
00436             if ($argument->getOrigin() == Tx_Extbase_MVC_Controller_Argument::ORIGIN_NEWLY_CREATED
00437              || $argument->getOrigin() == Tx_Extbase_MVC_Controller_Argument::ORIGIN_PERSISTENCE_AND_MODIFIED) {
00438                 $verificationNeeded = TRUE;
00439             }
00440         }
00441         if ($verificationNeeded) {
00442             $methodTagsValues = $this->reflectionService->getMethodTagsValues(get_class($this), $this->actionMethodName);
00443             if (!isset($methodTagsValues['dontverifyrequesthash'])) {
00444                 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);
00445             }
00446         }
00447     }
00448 
00449     /**
00450      * Clear cache of current page on error. Needed because we want a re-evaluation of the data.
00451      * Better would be just do delete the cache for the error action, but that is not possible right now.
00452      *
00453      * @return void
00454      */
00455     protected function clearCacheOnError() {
00456         $extbaseSettings = $this->configurationManager->getConfiguration(Tx_Extbase_Configuration_ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK);
00457         if (isset($extbaseSettings['persistence']['enableAutomaticCacheClearing']) && $extbaseSettings['persistence']['enableAutomaticCacheClearing'] === '1') {
00458             if (isset($GLOBALS['TSFE'])) {
00459                 $pageUid = $GLOBALS['TSFE']->id;
00460                 Tx_Extbase_Utility_Cache::clearPageCache(array($pageUid));
00461             }
00462         }
00463     }
00464 
00465 }
00466 ?>