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