[+BUGFIX] Extbase (Reflection): Fixed buildClassSchema() to prevent a PHP warning.
[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 */
35 class Tx_Extbase_MVC_Controller_ActionController extends Tx_Extbase_MVC_Controller_AbstractController {
36
37 /**
38 * @var Tx_Extbase_Reflection_Service
39 */
40 protected $reflectionService;
41
42 /**
43 * By default a Fluid TemplateView is provided, if a template is available,
44 * then a view with the same name as the current action will be looked up.
45 * If none is available the $defaultViewObjectName will be used and finally
46 * an EmptyView will be created.
47 * @var Tx_Extbase_MVC_View_ViewInterface
48 * @api
49 */
50 protected $view = NULL;
51
52 /**
53 * Pattern after which the view object name is built if no Fluid template
54 * is found.
55 * @var string
56 * @api
57 */
58 protected $viewObjectNamePattern = 'Tx_@extension_View_@controller_@action@format';
59
60 /**
61 * The default view object to use if neither a Fluid template nor an action
62 * specific view object could be found.
63 * @var string
64 * @api
65 */
66 protected $defaultViewObjectName = NULL;
67
68 /**
69 * Name of the action method
70 * @var string
71 * @api
72 */
73 protected $actionMethodName = 'indexAction';
74
75 /**
76 * Name of the special error action method which is called in case of errors
77 * @var string
78 * @api
79 */
80 protected $errorMethodName = 'errorAction';
81
82 /**
83 * Injects the reflection service
84 *
85 * @param Tx_Extbase_Reflection_Service $reflectionService
86 * @return void
87 */
88 public function injectReflectionService(Tx_Extbase_Reflection_Service $reflectionService) {
89 $this->reflectionService = $reflectionService;
90 }
91
92 /**
93 * Checks if the current request type is supported by the controller.
94 *
95 * If your controller only supports certain request types, either
96 * replace / modify the supporteRequestTypes property or override this
97 * method.
98 *
99 * @param Tx_Extbase_MVC_Request $request The current request
100 * @return boolean TRUE if this request type is supported, otherwise FALSE
101 */
102 public function canProcessRequest(Tx_Extbase_MVC_Request $request) {
103 return parent::canProcessRequest($request);
104
105 }
106
107 /**
108 * Handles a request. The result output is returned by altering the given response.
109 *
110 * @param Tx_Extbase_MVC_Request $request The request object
111 * @param Tx_Extbase_MVC_Response $response The response, modified by this handler
112 * @return void
113 */
114 public function processRequest(Tx_Extbase_MVC_Request $request, Tx_Extbase_MVC_Response $response) {
115 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);
116
117 $this->request = $request;
118 $this->request->setDispatched(TRUE);
119 $this->response = $response;
120
121 $this->uriBuilder = t3lib_div::makeInstance('Tx_Extbase_MVC_Web_Routing_UriBuilder');
122 $this->uriBuilder->setRequest($request);
123
124 $this->actionMethodName = $this->resolveActionMethodName();
125
126 $this->initializeActionMethodArguments();
127 $this->initializeControllerArgumentsBaseValidators();
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->view = $this->resolveView();
138 if ($this->view !== NULL) $this->initializeView($this->view);
139 $this->callActionMethod();
140 }
141
142 /**
143 * Implementation of the arguments initilization in the action controller:
144 * Automatically registers arguments of the current action
145 *
146 * Don't override this method - use initializeAction() instead.
147 *
148 * @return void
149 * @see initializeArguments()
150 */
151 protected function initializeActionMethodArguments() {
152 $methodParameters = $this->reflectionService->getMethodParameters(get_class($this), $this->actionMethodName);
153 foreach ($methodParameters as $parameterName => $parameterInfo) {
154 $dataType = 'Text';
155 if (isset($parameterInfo['type'])) {
156 $dataType = $parameterInfo['type'];
157 } elseif ($parameterInfo['array']) {
158 $dataType = 'array';
159 }
160 // TODO Exception here if type has not been determined!
161 $defaultValue = (isset($parameterInfo['defaultValue']) ? $parameterInfo['defaultValue'] : NULL);
162
163 $this->arguments->addNewArgument($parameterName, $dataType, ($parameterInfo['optional'] === FALSE), $defaultValue);
164 }
165 }
166
167 /**
168 * Detects and registers any additional validators for arguments which were
169 * specified in the @validate annotations of an action method
170 *
171 * @return void
172 */
173 protected function initializeActionMethodValidators() {
174 $validatorConjunctions = $this->validatorResolver->buildMethodArgumentsValidatorConjunctions(get_class($this), $this->actionMethodName);
175 foreach ($validatorConjunctions as $argumentName => $validatorConjunction) {
176 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);
177 $argument = $this->arguments[$argumentName];
178 $existingValidator = $argument->getValidator();
179 if ($existingValidator !== NULL) {
180 $validatorConjunction->addValidator($existingValidator);
181 }
182 $argument->setValidator($validatorConjunction);
183 }
184
185 $this->evaluateDontValidateAnnotations();
186 }
187
188 /**
189 * Parses @dontvalidate annotations of an action method an disables validation for
190 * the specified arguments.
191 *
192 * @return void
193 */
194 protected function evaluateDontValidateAnnotations() {
195 $methodTagsValues = $this->reflectionService->getMethodTagsValues(get_class($this), $this->actionMethodName);
196 if (isset($methodTagsValues['dontvalidate'])) {
197 foreach ($methodTagsValues['dontvalidate'] as $dontValidateValue) {
198 $argumentName = substr($dontValidateValue, 1);
199 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);
200 $this->arguments[$argumentName]->disableValidation();
201 }
202 }
203 }
204
205 /**
206 * Determines the action method and assures that the method exists.
207 *
208 * @return string The action method name
209 * @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).
210 */
211 protected function resolveActionMethodName() {
212 $actionMethodName = $this->request->getControllerActionName() . 'Action';
213 if (!method_exists($this, $actionMethodName)) throw new Tx_Extbase_MVC_Exception_NoSuchAction('An action "' . $actionMethodName . '" does not exist in controller "' . get_class($this) . '".', 1186669086);
214 return $actionMethodName;
215 }
216
217 /**
218 * Calls the specified action method and passes the arguments.
219 *
220 * If the action returns a string, it is appended to the content in the
221 * response object. If the action doesn't return anything and a valid
222 * view exists, the view is rendered automatically.
223 *
224 * @param string $actionMethodName Name of the action method to call
225 * @return void
226 * @api
227 */
228 protected function callActionMethod() {
229 $argumentsAreValid = TRUE;
230 $preparedArguments = array();
231 foreach ($this->arguments as $argument) {
232 $preparedArguments[] = $argument->getValue();
233 }
234
235 if ($this->argumentsMappingResults->hasErrors()) {
236 $actionResult = call_user_func(array($this, $this->errorMethodName));
237 } else {
238 $actionResult = call_user_func_array(array($this, $this->actionMethodName), $preparedArguments);
239 }
240 if ($actionResult === NULL && $this->view instanceof Tx_Extbase_MVC_View_ViewInterface) {
241 $this->response->appendContent($this->view->render());
242 } elseif (is_string($actionResult) && strlen($actionResult) > 0) {
243 $this->response->appendContent($actionResult);
244 }
245 }
246
247 /**
248 * Prepares a view for the current action and stores it in $this->view.
249 * By default, this method tries to locate a view with a name matching
250 * the current action.
251 *
252 * @return void
253 * @api
254 */
255 protected function resolveView() {
256 $view = $this->objectManager->getObject('Tx_Fluid_View_TemplateView');
257 $controllerContext = $this->buildControllerContext();
258 $view->setControllerContext($controllerContext);
259
260 // Template Path Override
261 $extbaseFrameworkConfiguration = Tx_Extbase_Dispatcher::getExtbaseFrameworkConfiguration();
262 if (isset($extbaseFrameworkConfiguration['view']['templateRootPath']) && $extbaseFrameworkConfiguration['view']['templateRootPath']) {
263 $view->setTemplateRootPath($extbaseFrameworkConfiguration['view']['templateRootPath']);
264 }
265
266 if ($view->hasTemplate() === FALSE) {
267 $viewObjectName = $this->resolveViewObjectName();
268 if (class_exists($viewObjectName) === FALSE) $viewObjectName = 'Tx_Extbase_MVC_View_EmptyView';
269 $view = $this->objectManager->getObject($viewObjectName);
270 $view->setControllerContext($controllerContext);
271 }
272 if (method_exists($view, 'injectSettings')) {
273 $view->injectSettings($this->settings);
274 }
275 $view->initializeView(); // In FLOW3, solved through Object Lifecycle methods, we need to call it explicitely.
276 $view->assign('flashMessages', $this->popFlashMessages());
277 return $view;
278 }
279
280 /**
281 * Determines the fully qualified view object name.
282 *
283 * @return mixed The fully qualified view object name or FALSE if no matching view could be found.
284 * @api
285 */
286 protected function resolveViewObjectName() {
287 $possibleViewName = $this->viewObjectNamePattern;
288 $extensionName = $this->request->getControllerExtensionName();
289 $possibleViewName = str_replace('@extension', $extensionName, $possibleViewName);
290 $possibleViewName = str_replace('@controller', $this->request->getControllerName(), $possibleViewName);
291 $possibleViewName = str_replace('@action', ucfirst($this->request->getControllerActionName()), $possibleViewName);
292
293 $viewObjectName = str_replace('@format', ucfirst($this->request->getFormat()), $possibleViewName);
294 if (class_exists($viewObjectName) === FALSE) {
295 $viewObjectName = str_replace('@format', '', $possibleViewName);
296 }
297 if (class_exists($viewObjectName) === FALSE && $this->defaultViewObjectName !== NULL) {
298 $viewObjectName = $this->defaultViewObjectName;
299 }
300 return $viewObjectName;
301 }
302
303 /**
304 * Initializes the view before invoking an action method.
305 *
306 * Override this method to solve assign variables common for all actions
307 * or prepare the view in another way before the action is called.
308 *
309 * @param Tx_Extbase_View_ViewInterface $view The view to be initialized
310 * @return void
311 * @api
312 */
313 protected function initializeView(Tx_Extbase_MVC_View_ViewInterface $view) {
314 }
315
316 /**
317 * Initializes the controller before invoking an action method.
318 *
319 * Override this method to solve tasks which all actions have in
320 * common.
321 *
322 * @return void
323 * @api
324 */
325 protected function initializeAction() {
326 }
327
328 /**
329 * A special action which is called if the originally intended action could
330 * not be called, for example if the arguments were not valid.
331 *
332 * The default implementation sets a flash message, request errors and forwards back
333 * to the originating action. This is suitable for most actions dealing with form input.
334 *
335 * We clear the page cache by default on an error as well, as we need to make sure the
336 * data is re-evaluated when the user changes something.
337 *
338 * @return string
339 * @api
340 */
341 protected function errorAction() {
342 $this->request->setErrors($this->argumentsMappingResults->getErrors());
343 $this->clearCacheOnError();
344
345 if ($this->request->hasArgument('__referrer')) {
346 $referrer = $this->request->getArgument('__referrer');
347 $this->forward($referrer['actionName'], $referrer['controllerName'], $referrer['extensionName'], $this->request->getArguments());
348 }
349
350 $message = 'An error occurred while trying to call ' . get_class($this) . '->' . $this->actionMethodName . '().' . PHP_EOL;
351 foreach ($this->argumentsMappingResults->getErrors() as $error) {
352 $message .= 'Error: ' . $error->getMessage() . PHP_EOL;
353 }
354 foreach ($this->argumentsMappingResults->getWarnings() as $warning) {
355 $message .= 'Warning: ' . $warning->getMessage() . PHP_EOL;
356 }
357 return $message;
358 }
359
360 /**
361 * A template method for displaying custom error flash messages, or to
362 * display no flash message at all on errors. Override this to customize
363 * the flash message in your action controller.
364 *
365 * @return string|boolean The flash message or FALSE if no flash message should be set
366 * @api
367 */
368 protected function getErrorFlashMessage() {
369 return 'An error occurred while trying to call ' . get_class($this) . '->' . $this->actionMethodName . '()';
370 }
371
372 /**
373 * Clear cache of current page on error. Needed because we want a re-evaluation of the data.
374 * Better would be just do delete the cache for the error action, but that is not possible right now.
375 *
376 * @return void
377 */
378 protected function clearCacheOnError() {
379 $extbaseSettings = Tx_Extbase_Dispatcher::getExtbaseFrameworkConfiguration();
380 if (isset($extbaseSettings['persistence']['enableAutomaticCacheClearing']) && $extbaseSettings['persistence']['enableAutomaticCacheClearing'] === '1') {
381 if (isset($GLOBALS['TSFE'])) {
382 $pageUid = $GLOBALS['TSFE']->id;
383 Tx_Extbase_Utility_Cache::clearPageCache(array($pageUid));
384 }
385 }
386 }
387
388 }
389 ?>