PageRenderTime 45ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 1ms

/typo3/sysext/extbase/Classes/MVC/Controller/ActionController.php

https://github.com/andreaswolf/typo3-tceforms
PHP | 466 lines | 212 code | 45 blank | 209 comment | 47 complexity | 96c8faf592e83ed01223bf97ee21e58d MD5 | raw file
Possible License(s): Apache-2.0, BSD-2-Clause, LGPL-3.0
  1. <?php
  2. /***************************************************************
  3. * Copyright notice
  4. *
  5. * (c) 2009 Jochen Rau <jochen.rau@typoplanet.de>
  6. * All rights reserved
  7. *
  8. * This class is a backport of the corresponding class of FLOW3.
  9. * All credits go to the v5 team.
  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. *
  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.
  24. *
  25. * This copyright notice MUST APPEAR in all copies of the script!
  26. ***************************************************************/
  27. /**
  28. * A multi action controller. This is by far the most common base class for Controllers.
  29. *
  30. * @package Extbase
  31. * @subpackage MVC\Controller
  32. * @version $ID:$
  33. * @api
  34. */
  35. class Tx_Extbase_MVC_Controller_ActionController extends Tx_Extbase_MVC_Controller_AbstractController {
  36. /**
  37. * @var Tx_Extbase_Reflection_Service
  38. */
  39. protected $reflectionService;
  40. /**
  41. * The current view, as resolved by resolveView()
  42. *
  43. * @var Tx_Extbase_MVC_View_ViewInterface
  44. * @api
  45. */
  46. protected $view = NULL;
  47. /**
  48. * Pattern after which the view object name is built if no Fluid template
  49. * is found.
  50. * @var string
  51. * @api
  52. */
  53. protected $viewObjectNamePattern = 'Tx_@extension_View_@controller_@action@format';
  54. /**
  55. * A list of formats and object names of the views which should render them.
  56. *
  57. * Example:
  58. *
  59. * array('html' => 'Tx_MyExtension_View_MyHtmlView', 'json' => 'F3...
  60. *
  61. * @var array
  62. */
  63. protected $viewFormatToObjectNameMap = array();
  64. /**
  65. * The default view object to use if none of the resolved views can render
  66. * a response for the current request.
  67. *
  68. * @var string
  69. * @api
  70. */
  71. protected $defaultViewObjectName = 'Tx_Fluid_View_TemplateView';
  72. /**
  73. * Name of the action method
  74. * @var string
  75. * @api
  76. */
  77. protected $actionMethodName = 'indexAction';
  78. /**
  79. * Name of the special error action method which is called in case of errors
  80. * @var string
  81. * @api
  82. */
  83. protected $errorMethodName = 'errorAction';
  84. /**
  85. * Injects the reflection service
  86. *
  87. * @param Tx_Extbase_Reflection_Service $reflectionService
  88. * @return void
  89. */
  90. public function injectReflectionService(Tx_Extbase_Reflection_Service $reflectionService) {
  91. $this->reflectionService = $reflectionService;
  92. }
  93. /**
  94. * Checks if the current request type is supported by the controller.
  95. *
  96. * If your controller only supports certain request types, either
  97. * replace / modify the supporteRequestTypes property or override this
  98. * method.
  99. *
  100. * @param Tx_Extbase_MVC_Request $request The current request
  101. * @return boolean TRUE if this request type is supported, otherwise FALSE
  102. */
  103. public function canProcessRequest(Tx_Extbase_MVC_RequestInterface $request) {
  104. return parent::canProcessRequest($request);
  105. }
  106. /**
  107. * Handles a request. The result output is returned by altering the given response.
  108. *
  109. * @param Tx_Extbase_MVC_Request $request The request object
  110. * @param Tx_Extbase_MVC_Response $response The response, modified by this handler
  111. * @return void
  112. */
  113. public function processRequest(Tx_Extbase_MVC_RequestInterface $request, Tx_Extbase_MVC_ResponseInterface $response) {
  114. if (!$this->canProcessRequest($request)) {
  115. 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);
  116. }
  117. $this->request = $request;
  118. $this->request->setDispatched(TRUE);
  119. $this->response = $response;
  120. $this->uriBuilder = $this->objectManager->create('Tx_Extbase_MVC_Web_Routing_UriBuilder');
  121. $this->uriBuilder->setRequest($request);
  122. $this->actionMethodName = $this->resolveActionMethodName();
  123. $this->initializeActionMethodArguments();
  124. $this->initializeActionMethodValidators();
  125. $this->initializeAction();
  126. $actionInitializationMethodName = 'initialize' . ucfirst($this->actionMethodName);
  127. if (method_exists($this, $actionInitializationMethodName)) {
  128. call_user_func(array($this, $actionInitializationMethodName));
  129. }
  130. $this->mapRequestArgumentsToControllerArguments();
  131. $this->checkRequestHash();
  132. $this->controllerContext = $this->buildControllerContext();
  133. $this->view = $this->resolveView();
  134. if ($this->view !== NULL) {
  135. $this->initializeView($this->view);
  136. }
  137. $this->callActionMethod();
  138. }
  139. /**
  140. * Implementation of the arguments initilization in the action controller:
  141. * Automatically registers arguments of the current action
  142. *
  143. * Don't override this method - use initializeAction() instead.
  144. *
  145. * @return void
  146. * @see initializeArguments()
  147. */
  148. protected function initializeActionMethodArguments() {
  149. $methodParameters = $this->reflectionService->getMethodParameters(get_class($this), $this->actionMethodName);
  150. foreach ($methodParameters as $parameterName => $parameterInfo) {
  151. $dataType = NULL;
  152. if (isset($parameterInfo['type'])) {
  153. $dataType = $parameterInfo['type'];
  154. } elseif ($parameterInfo['array']) {
  155. $dataType = 'array';
  156. }
  157. if ($dataType === NULL) throw new Tx_Extbase_MVC_Exception_InvalidArgumentType('The argument type for parameter "' . $parameterName . '" could not be detected.', 1253175643);
  158. $defaultValue = (isset($parameterInfo['defaultValue']) ? $parameterInfo['defaultValue'] : NULL);
  159. $this->arguments->addNewArgument($parameterName, $dataType, ($parameterInfo['optional'] === FALSE), $defaultValue);
  160. }
  161. }
  162. /**
  163. * Adds the needed valiators to the Arguments:
  164. * - Validators checking the data type from the @param annotation
  165. * - Custom validators specified with @validate.
  166. *
  167. * In case @dontvalidate is NOT set for an argument, the following two
  168. * validators are also added:
  169. * - Model-based validators (@validate annotations in the model)
  170. * - Custom model validator classes
  171. *
  172. * @return void
  173. */
  174. protected function initializeActionMethodValidators() {
  175. $parameterValidators = $this->validatorResolver->buildMethodArgumentsValidatorConjunctions(get_class($this), $this->actionMethodName);
  176. $dontValidateAnnotations = array();
  177. $methodTagsValues = $this->reflectionService->getMethodTagsValues(get_class($this), $this->actionMethodName);
  178. if (isset($methodTagsValues['dontvalidate'])) {
  179. $dontValidateAnnotations = $methodTagsValues['dontvalidate'];
  180. }
  181. foreach ($this->arguments as $argument) {
  182. $validator = $parameterValidators[$argument->getName()];
  183. if (array_search('$' . $argument->getName(), $dontValidateAnnotations) === FALSE) {
  184. $baseValidatorConjunction = $this->validatorResolver->getBaseValidatorConjunction($argument->getDataType());
  185. if ($baseValidatorConjunction !== NULL) {
  186. $validator->addValidator($baseValidatorConjunction);
  187. }
  188. }
  189. $argument->setValidator($validator);
  190. }
  191. }
  192. /**
  193. * Resolves and checks the current action method name
  194. *
  195. * @return string Method name of the current action
  196. * @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).
  197. */
  198. protected function resolveActionMethodName() {
  199. $actionMethodName = $this->request->getControllerActionName() . 'Action';
  200. if (!method_exists($this, $actionMethodName)) throw new Tx_Extbase_MVC_Exception_NoSuchAction('An action "' . $actionMethodName . '" does not exist in controller "' . get_class($this) . '".', 1186669086);
  201. return $actionMethodName;
  202. }
  203. /**
  204. * Calls the specified action method and passes the arguments.
  205. *
  206. * If the action returns a string, it is appended to the content in the
  207. * response object. If the action doesn't return anything and a valid
  208. * view exists, the view is rendered automatically.
  209. *
  210. * @param string $actionMethodName Name of the action method to call
  211. * @return void
  212. * @api
  213. */
  214. protected function callActionMethod() {
  215. $argumentsAreValid = TRUE;
  216. $preparedArguments = array();
  217. foreach ($this->arguments as $argument) {
  218. $preparedArguments[] = $argument->getValue();
  219. }
  220. if ($this->argumentsMappingResults->hasErrors()) {
  221. $actionResult = call_user_func(array($this, $this->errorMethodName));
  222. } else {
  223. $actionResult = call_user_func_array(array($this, $this->actionMethodName), $preparedArguments);
  224. }
  225. if ($actionResult === NULL && $this->view instanceof Tx_Extbase_MVC_View_ViewInterface) {
  226. $this->response->appendContent($this->view->render());
  227. } elseif (is_string($actionResult) && strlen($actionResult) > 0) {
  228. $this->response->appendContent($actionResult);
  229. }
  230. }
  231. /**
  232. * Prepares a view for the current action and stores it in $this->view.
  233. * By default, this method tries to locate a view with a name matching
  234. * the current action.
  235. *
  236. * @return void
  237. * @api
  238. */
  239. protected function resolveView() {
  240. $viewObjectName = $this->resolveViewObjectName();
  241. if ($viewObjectName !== FALSE) {
  242. $view = $this->objectManager->create($viewObjectName);
  243. $this->setViewConfiguration($view);
  244. if ($view->canRender($this->controllerContext) === FALSE) {
  245. unset($view);
  246. }
  247. }
  248. if (!isset($view) && $this->defaultViewObjectName != '') {
  249. $view = $this->objectManager->create($this->defaultViewObjectName);
  250. $this->setViewConfiguration($view);
  251. if ($view->canRender($this->controllerContext) === FALSE) {
  252. unset($view);
  253. }
  254. }
  255. if (!isset($view)) {
  256. $view = $this->objectManager->create('Tx_Extbase_MVC_View_NotFoundView');
  257. $view->assign('errorMessage', 'No template was found. View could not be resolved for action "' . $this->request->getControllerActionName() . '"');
  258. }
  259. $view->setControllerContext($this->controllerContext);
  260. if (method_exists($view, 'injectSettings')) {
  261. $view->injectSettings($this->settings);
  262. }
  263. $view->initializeView(); // In FLOW3, solved through Object Lifecycle methods, we need to call it explicitely
  264. $view->assign('settings', $this->settings); // same with settings injection.
  265. return $view;
  266. }
  267. /**
  268. * @param Tx_Extbase_MVC_View_ViewInterface $view
  269. * @return void
  270. */
  271. protected function setViewConfiguration(Tx_Extbase_MVC_View_ViewInterface $view) {
  272. // Template Path Override
  273. $extbaseFrameworkConfiguration = $this->configurationManager->getConfiguration(Tx_Extbase_Configuration_ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK);
  274. if (isset($extbaseFrameworkConfiguration['view']['templateRootPath'])
  275. && strlen($extbaseFrameworkConfiguration['view']['templateRootPath']) > 0
  276. && method_exists($view, 'setTemplateRootPath')) {
  277. $view->setTemplateRootPath(t3lib_div::getFileAbsFileName($extbaseFrameworkConfiguration['view']['templateRootPath']));
  278. }
  279. if (isset($extbaseFrameworkConfiguration['view']['layoutRootPath'])
  280. && strlen($extbaseFrameworkConfiguration['view']['layoutRootPath']) > 0
  281. && method_exists($view, 'setLayoutRootPath')) {
  282. $view->setLayoutRootPath(t3lib_div::getFileAbsFileName($extbaseFrameworkConfiguration['view']['layoutRootPath']));
  283. }
  284. if (isset($extbaseFrameworkConfiguration['view']['partialRootPath'])
  285. && strlen($extbaseFrameworkConfiguration['view']['partialRootPath']) > 0
  286. && method_exists($view, 'setPartialRootPath')) {
  287. $view->setPartialRootPath(t3lib_div::getFileAbsFileName($extbaseFrameworkConfiguration['view']['partialRootPath']));
  288. }
  289. }
  290. /**
  291. * Determines the fully qualified view object name.
  292. *
  293. * @return mixed The fully qualified view object name or FALSE if no matching view could be found.
  294. * @api
  295. */
  296. protected function resolveViewObjectName() {
  297. $possibleViewName = $this->viewObjectNamePattern;
  298. $extensionName = $this->request->getControllerExtensionName();
  299. $possibleViewName = str_replace('@extension', $extensionName, $possibleViewName);
  300. $possibleViewName = str_replace('@controller', $this->request->getControllerName(), $possibleViewName);
  301. $possibleViewName = str_replace('@action', ucfirst($this->request->getControllerActionName()), $possibleViewName);
  302. $format = $this->request->getFormat();
  303. $viewObjectName = str_replace('@format', ucfirst($this->request->getFormat()), $possibleViewName);
  304. if (class_exists($viewObjectName) === FALSE) {
  305. $viewObjectName = str_replace('@format', '', $possibleViewName);
  306. }
  307. if (class_exists($viewObjectName) === FALSE && isset($this->viewFormatToObjectNameMap[$format])) {
  308. $viewObjectName = $this->viewFormatToObjectNameMap[$format];
  309. }
  310. return class_exists($viewObjectName) ? $viewObjectName : FALSE;
  311. }
  312. /**
  313. * Initializes the view before invoking an action method.
  314. *
  315. * Override this method to solve assign variables common for all actions
  316. * or prepare the view in another way before the action is called.
  317. *
  318. * @param Tx_Extbase_View_ViewInterface $view The view to be initialized
  319. * @return void
  320. * @api
  321. */
  322. protected function initializeView(Tx_Extbase_MVC_View_ViewInterface $view) {
  323. }
  324. /**
  325. * Initializes the controller before invoking an action method.
  326. *
  327. * Override this method to solve tasks which all actions have in
  328. * common.
  329. *
  330. * @return void
  331. * @api
  332. */
  333. protected function initializeAction() {
  334. }
  335. /**
  336. * A special action which is called if the originally intended action could
  337. * not be called, for example if the arguments were not valid.
  338. *
  339. * The default implementation sets a flash message, request errors and forwards back
  340. * to the originating action. This is suitable for most actions dealing with form input.
  341. *
  342. * We clear the page cache by default on an error as well, as we need to make sure the
  343. * data is re-evaluated when the user changes something.
  344. *
  345. * @return string
  346. * @api
  347. */
  348. protected function errorAction() {
  349. $this->request->setErrors($this->argumentsMappingResults->getErrors());
  350. $this->clearCacheOnError();
  351. $errorFlashMessage = $this->getErrorFlashMessage();
  352. if ($errorFlashMessage !== FALSE) {
  353. $this->flashMessages->add($errorFlashMessage, '', t3lib_FlashMessage::ERROR);
  354. }
  355. if ($this->request->hasArgument('__referrer')) {
  356. $referrer = $this->request->getArgument('__referrer');
  357. $this->forward($referrer['actionName'], $referrer['controllerName'], $referrer['extensionName'], $this->request->getArguments());
  358. }
  359. $message = 'An error occurred while trying to call ' . get_class($this) . '->' . $this->actionMethodName . '().' . PHP_EOL;
  360. foreach ($this->argumentsMappingResults->getErrors() as $error) {
  361. $message .= 'Error: ' . $error->getMessage() . PHP_EOL;
  362. }
  363. foreach ($this->argumentsMappingResults->getWarnings() as $warning) {
  364. $message .= 'Warning: ' . $warning->getMessage() . PHP_EOL;
  365. }
  366. return $message;
  367. }
  368. /**
  369. * A template method for displaying custom error flash messages, or to
  370. * display no flash message at all on errors. Override this to customize
  371. * the flash message in your action controller.
  372. *
  373. * @return string|boolean The flash message or FALSE if no flash message should be set
  374. * @api
  375. */
  376. protected function getErrorFlashMessage() {
  377. return 'An error occurred while trying to call ' . get_class($this) . '->' . $this->actionMethodName . '()';
  378. }
  379. /**
  380. * Checks the request hash (HMAC), if arguments have been touched by the property mapper.
  381. *
  382. * In case the @dontverifyrequesthash-Annotation has been set, this suppresses the exception.
  383. *
  384. * @return void
  385. * @throws Tx_Extbase_MVC_Exception_InvalidOrNoRequestHash In case request hash checking failed
  386. * @author Sebastian Kurfürst <sebastian@typo3.org>
  387. */
  388. protected function checkRequestHash() {
  389. if (!($this->request instanceof Tx_Extbase_MVC_Web_Request)) return; // We only want to check it for now for web requests.
  390. if ($this->request->isHmacVerified()) return; // all good
  391. $verificationNeeded = FALSE;
  392. foreach ($this->arguments as $argument) {
  393. if ($argument->getOrigin() == Tx_Extbase_MVC_Controller_Argument::ORIGIN_NEWLY_CREATED
  394. || $argument->getOrigin() == Tx_Extbase_MVC_Controller_Argument::ORIGIN_PERSISTENCE_AND_MODIFIED) {
  395. $verificationNeeded = TRUE;
  396. }
  397. }
  398. if ($verificationNeeded) {
  399. $methodTagsValues = $this->reflectionService->getMethodTagsValues(get_class($this), $this->actionMethodName);
  400. if (!isset($methodTagsValues['dontverifyrequesthash'])) {
  401. 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);
  402. }
  403. }
  404. }
  405. /**
  406. * Clear cache of current page on error. Needed because we want a re-evaluation of the data.
  407. * Better would be just do delete the cache for the error action, but that is not possible right now.
  408. *
  409. * @return void
  410. */
  411. protected function clearCacheOnError() {
  412. $extbaseSettings = $this->configurationManager->getConfiguration(Tx_Extbase_Configuration_ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK);
  413. if (isset($extbaseSettings['persistence']['enableAutomaticCacheClearing']) && $extbaseSettings['persistence']['enableAutomaticCacheClearing'] === '1') {
  414. if (isset($GLOBALS['TSFE'])) {
  415. $pageUid = $GLOBALS['TSFE']->id;
  416. Tx_Extbase_Utility_Cache::clearPageCache(array($pageUid));
  417. }
  418. }
  419. }
  420. }
  421. ?>