[FEATURE] Fluid (TemplateView): Made templateRootPath configurable via TypoScript...
[Packages/TYPO3.CMS.git] / typo3 / sysext / extbase / Classes / MVC / Controller / ActionController.php
index 971c7d8..2247a64 100644 (file)
@@ -5,7 +5,7 @@
 *  (c) 2009 Jochen Rau <jochen.rau@typoplanet.de>
 *  All rights reserved
 *
-*  This class is a backport of the corresponding class of FLOW3. 
+*  This class is a backport of the corresponding class of FLOW3.
 *  All credits go to the v5 team.
 *
 *  This script is part of the TYPO3 project. The TYPO3 project is
  * A multi action controller. This is by far the most common base class for Controllers.
  *
  * @package Extbase
- * @subpackage MVC
+ * @subpackage MVC\Controller
  * @version $ID:$
  */
 class Tx_Extbase_MVC_Controller_ActionController extends Tx_Extbase_MVC_Controller_AbstractController {
 
        /**
         * @var Tx_Extbase_Reflection_Service
-        * @internal
         */
        protected $reflectionService;
 
        /**
-        * @var boolean If initializeView() should be called on an action invocation.
-        */
-       protected $initializeView = TRUE;
-
-       /**
-        * By default a view with the same name as the current action is provided. Contains NULL if none was found.
-        * @var Tx_Extbase_MVC_View_AbstractView
+        * By default a Fluid TemplateView is provided, if a template is available,
+        * then a view with the same name as the current action will be looked up.
+        * If none is available the $defaultViewObjectName will be used and finally
+        * an EmptyView will be created.
+        * @var Tx_Extbase_MVC_View_ViewInterface
+        * @api
         */
        protected $view = NULL;
 
        /**
-        * By default $this->viewObjectNamePattern is used to find a matching view object.
-        * If no custom view class can be found, $this->defaultViewObjectName will be used.
+        * Pattern after which the view object name is built if no Fluid template
+        * is found.
         * @var string
+        * @api
         */
-       protected $standardViewObjectName = 'Tx_Fluid_View_TemplateView';
+       protected $viewObjectNamePattern = 'Tx_@extension_View_@controller_@action@format';
 
        /**
-        * Pattern after which the view object name is built
-        *
+        * The default view object to use if neither a Fluid template nor an action
+        * specific view object could be found.
         * @var string
+        * @api
         */
-       protected $viewObjectNamePattern = 'Tx_@extension_View_@controller_@action';
+       protected $defaultViewObjectName = NULL;
 
        /**
         * Name of the action method
         * @var string
+        * @api
         */
        protected $actionMethodName = 'indexAction';
 
        /**
         * Name of the special error action method which is called in case of errors
         * @var string
+        * @api
         */
        protected $errorMethodName = 'errorAction';
 
@@ -82,7 +84,6 @@ class Tx_Extbase_MVC_Controller_ActionController extends Tx_Extbase_MVC_Controll
         *
         * @param Tx_Extbase_Reflection_Service $reflectionService
         * @return void
-        * @internal
         */
        public function injectReflectionService(Tx_Extbase_Reflection_Service $reflectionService) {
                $this->reflectionService = $reflectionService;
@@ -117,8 +118,10 @@ class Tx_Extbase_MVC_Controller_ActionController extends Tx_Extbase_MVC_Controll
                $this->request->setDispatched(TRUE);
                $this->response = $response;
 
+               $this->URIBuilder = t3lib_div::makeInstance('Tx_Extbase_MVC_Web_Routing_URIBuilder');
+               $this->URIBuilder->setRequest($request);
+
                $this->actionMethodName = $this->resolveActionMethodName();
-               if ($this->initializeView) $this->initializeView();
 
                $this->initializeActionMethodArguments();
                $this->initializeControllerArgumentsBaseValidators();
@@ -131,6 +134,8 @@ class Tx_Extbase_MVC_Controller_ActionController extends Tx_Extbase_MVC_Controll
                }
 
                $this->mapRequestArgumentsToControllerArguments();
+               $this->view = $this->resolveView();
+               if ($this->view !== NULL) $this->initializeView($this->view);
                $this->callActionMethod();
        }
 
@@ -138,11 +143,10 @@ class Tx_Extbase_MVC_Controller_ActionController extends Tx_Extbase_MVC_Controll
         * Implementation of the arguments initilization in the action controller:
         * Automatically registers arguments of the current action
         *
-        * Don't override this method - use initializeArguments() instead.
+        * Don't override this method - use initializeAction() instead.
         *
         * @return void
         * @see initializeArguments()
-        * @internal
         */
        protected function initializeActionMethodArguments() {
                $methodParameters = $this->reflectionService->getMethodParameters(get_class($this), $this->actionMethodName);
@@ -164,13 +168,36 @@ class Tx_Extbase_MVC_Controller_ActionController extends Tx_Extbase_MVC_Controll
         * specified in the @validate annotations of an action method
         *
         * @return void
-        * @internal
         */
        protected function initializeActionMethodValidators() {
-               $validatorChains = $this->validatorResolver->buildMethodArgumentsValidatorChains(get_class($this), $this->actionMethodName);
-               foreach ($validatorChains as $argumentName => $validatorChain) {
+               $validatorConjunctions = $this->validatorResolver->buildMethodArgumentsValidatorConjunctions(get_class($this), $this->actionMethodName);
+               foreach ($validatorConjunctions as $argumentName => $validatorConjunction) {
                        if (!isset($this->arguments[$argumentName])) throw new Tx_Extbase_MVC_Exception_NoSuchArgument('Found custom validation rule for non existing argument "' . $argumentName . '" in ' . get_class($this) . '->' . $this->actionMethodName . '().', 1239853108);
-                       $this->arguments[$argumentName]->setValidator($validatorChain);
+                       $argument = $this->arguments[$argumentName];
+                       $existingValidator = $argument->getValidator();
+                       if ($existingValidator !== NULL) {
+                               $validatorConjunction->addValidator($existingValidator);
+                       }
+                       $argument->setValidator($validatorConjunction);
+               }
+
+               $this->evaluateDontValidateAnnotations();
+       }
+
+       /**
+        * Parses @dontvalidate annotations of an action method an disables validation for
+        * the specified arguments.
+        *
+        * @return void
+        */
+       protected function evaluateDontValidateAnnotations() {
+               $methodTagsValues = $this->reflectionService->getMethodTagsValues(get_class($this), $this->actionMethodName);
+               if (isset($methodTagsValues['dontvalidate'])) {
+                       foreach ($methodTagsValues['dontvalidate'] as $dontValidateValue) {
+                               $argumentName = substr($dontValidateValue, 1);
+                               if (!isset($this->arguments[$argumentName])) throw new Tx_Extbase_MVC_Exception_NoSuchArgument('Found @dontvalidate annotation for non existing argument "$' . $argumentName . '" in ' . get_class($this) . '->' . $this->actionMethodName . '().', 1249484908);
+                               $this->arguments[$argumentName]->disableValidation();
+                       }
                }
        }
 
@@ -178,12 +205,11 @@ class Tx_Extbase_MVC_Controller_ActionController extends Tx_Extbase_MVC_Controll
         * Determines the action method and assures that the method exists.
         *
         * @return string The action method name
-        * @throws Tx_Extbase_Exception_NoSuchAction if the action specified in the request object does not exist (and if there's no default action either).
-        * @internal
+        * @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).
         */
        protected function resolveActionMethodName() {
                $actionMethodName = $this->request->getControllerActionName() . 'Action';
-               if (!method_exists($this, $actionMethodName)) throw new Tx_Extbase_Exception_NoSuchAction('An action "' . $actionMethodName . '" does not exist in controller "' . get_class($this) . '".', 1186669086);
+               if (!method_exists($this, $actionMethodName)) throw new Tx_Extbase_MVC_Exception_NoSuchAction('An action "' . $actionMethodName . '" does not exist in controller "' . get_class($this) . '".', 1186669086);
                return $actionMethodName;
        }
 
@@ -196,7 +222,7 @@ class Tx_Extbase_MVC_Controller_ActionController extends Tx_Extbase_MVC_Controll
         *
         * @param string $actionMethodName Name of the action method to call
         * @return void
-        * @internal
+        * @api
         */
        protected function callActionMethod() {
                $argumentsAreValid = TRUE;
@@ -223,39 +249,81 @@ class Tx_Extbase_MVC_Controller_ActionController extends Tx_Extbase_MVC_Controll
         * the current action.
         *
         * @return void
-        * @internal
+        * @api
         */
-       protected function initializeView() {
-               $this->view = t3lib_div::makeInstance($this->resolveViewObjectName());
-               $this->view->setRequest($this->request);
+       protected function resolveView() {
+               $view = $this->objectManager->getObject('Tx_Fluid_View_TemplateView');
+               $controllerContext = $this->buildControllerContext();
+               $view->setControllerContext($controllerContext);
+               
+               // Template Path Override
+               $extbaseFrameworkConfiguration = Tx_Extbase_Dispatcher::getExtbaseFrameworkConfiguration();
+               if (isset($extbaseFrameworkConfiguration['view']['templateRootPath']) && $extbaseFrameworkConfiguration['view']['templateRootPath']) {
+                       $view->setTemplateRootPath($extbaseFrameworkConfiguration['view']['templateRootPath']);
+               }
+
+               if ($view->hasTemplate() === FALSE) {
+                       $viewObjectName = $this->resolveViewObjectName();
+                       if (class_exists($viewObjectName) === FALSE) $viewObjectName = 'Tx_Extbase_MVC_View_EmptyView';
+                       $view = $this->objectManager->getObject($viewObjectName);
+                       $view->setControllerContext($controllerContext);
+               }
+               if (method_exists($view, 'injectSettings')) {
+                       $view->injectSettings($this->settings);
+               }
+               $view->initializeView(); // In FLOW3, solved through Object Lifecycle methods, we need to call it explicitely.
+               // $view->assign('flashMessages', $this->popFlashMessages());
+               return $view;
        }
 
        /**
         * Determines the fully qualified view object name.
         *
-        * @return string The fully qualified view object name
+        * @return mixed The fully qualified view object name or FALSE if no matching view could be found.
+        * @api
         */
        protected function resolveViewObjectName() {
-               $viewObjectName = str_replace('@extension', $this->request->getControllerExtensionName(), $this->viewObjectNamePattern);
-               $viewObjectName = str_replace('@controller', $this->request->getControllerName(), $viewObjectName);
-               $viewObjectName = str_replace('@action', ucfirst($this->request->getControllerActionName()), $viewObjectName);
-               if (!class_exists($viewObjectName)) {
-                       if (class_exists($this->standardViewObjectName)) {
-                               $viewObjectName = $this->standardViewObjectName;
-                       } else {
-                               $viewObjectName = 'Tx_Extbase_View_EmptyView';
-                       }
+               $possibleViewName = $this->viewObjectNamePattern;
+               $extensionName = $this->request->getControllerExtensionName();
+               $subextensionName = $this->request->getControllerSubextensionName();
+               if ($subextensionName !== NULL && $subextensionName !== '') {
+                       $extensionName.= '_' . $subextensionName;
+               }
+               $possibleViewName = str_replace('@extension', $extensionName, $possibleViewName);
+               $possibleViewName = str_replace('@controller', $this->request->getControllerName(), $possibleViewName);
+               $possibleViewName = str_replace('@action', ucfirst($this->request->getControllerActionName()), $possibleViewName);
+
+               $viewObjectName = str_replace('@format', ucfirst($this->request->getFormat()), $possibleViewName);              
+               if (class_exists($viewObjectName) === FALSE) {
+                       $viewObjectName = str_replace('@format', '', $possibleViewName);
+               }
+               if (class_exists($viewObjectName) === FALSE && $this->defaultViewObjectName !== NULL) {
+                       $viewObjectName = $this->defaultViewObjectName;
                }
                return $viewObjectName;
        }
 
        /**
+        * Initializes the view before invoking an action method.
+        *
+        * Override this method to solve assign variables common for all actions
+        * or prepare the view in another way before the action is called.
+        *
+        * @param Tx_Extbase_View_ViewInterface $view The view to be initialized
+        * @return void
+        * @api
+        */
+       protected function initializeView(Tx_Extbase_MVC_View_ViewInterface $view) {
+       }
+
+       /**
         * Initializes the controller before invoking an action method.
         *
         * Override this method to solve tasks which all actions have in
         * common.
         *
         * @return void
+        * @api
         */
        protected function initializeAction() {
        }
@@ -264,17 +332,49 @@ class Tx_Extbase_MVC_Controller_ActionController extends Tx_Extbase_MVC_Controll
         * A special action which is called if the originally intended action could
         * not be called, for example if the arguments were not valid.
         *
+        * The default implementation sets a flash message, request errors and forwards back
+        * to the originating action. This is suitable for most actions dealing with form input.
+        *
+        * We clear the page cache by default on an error as well, as we need to make sure the
+        * data is re-evaluated when the user changes something.
+        *
         * @return string
+        * @api
         */
        protected function errorAction() {
+               $this->request->setErrors($this->argumentsMappingResults->getErrors());
+               $this->clearCacheOnError();
+
+               if ($this->request->hasArgument('__referrer')) {
+                       $referrer = $this->request->getArgument('__referrer');
+                       $this->forward($referrer['actionName'], $referrer['controllerName'], $referrer['extensionName'], $this->request->getArguments());
+               }
+
                $message = 'An error occurred while trying to call ' . get_class($this) . '->' . $this->actionMethodName . '().' . PHP_EOL;
                foreach ($this->argumentsMappingResults->getErrors() as $error) {
-                       $message .= 'Error:   ' . $error . PHP_EOL;
+                       $message .= 'Error:   ' . $error->getMessage() . PHP_EOL;
                }
                foreach ($this->argumentsMappingResults->getWarnings() as $warning) {
-                       $message .= 'Warning: ' . $warning . PHP_EOL;
+                       $message .= 'Warning: ' . $warning->getMessage() . PHP_EOL;
                }
                return $message;
        }
+
+       /**
+        * Clear cache of current page on error. Needed because we want a re-evaluation of the data.
+        * Better would be just do delete the cache for the error action, but that is not possible right now.
+        *
+        * @return void
+        */
+       protected function clearCacheOnError() {
+               $extbaseSettings = Tx_Extbase_Dispatcher::getExtbaseFrameworkConfiguration();
+               if (isset($extbaseSettings['persistence']['enableAutomaticCacheClearing']) && $extbaseSettings['persistence']['enableAutomaticCacheClearing'] === '1') {
+                       if (isset($GLOBALS['TSFE'])) {
+                               $pageUid = $GLOBALS['TSFE']->id;
+                               Tx_Extbase_Utility_Cache::clearPageCache(array($pageUid));
+                       }
+               }
+       }
+
 }
 ?>
\ No newline at end of file