Extbase:
[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
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 * @internal
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 */
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 */
57 protected $viewObjectNamePattern = 'Tx_@extension_View_@controller_@action';
58
59 /**
60 * The default view object to use if neither a Fluid template nor an action
61 * specific view object could be found.
62 * @var string
63 */
64 protected $defaultViewObjectName = NULL;
65
66 /**
67 * Name of the action method
68 * @var string
69 */
70 protected $actionMethodName = 'indexAction';
71
72 /**
73 * Name of the special error action method which is called in case of errors
74 * @var string
75 */
76 protected $errorMethodName = 'errorAction';
77
78 /**
79 * Injects the reflection service
80 *
81 * @param Tx_Extbase_Reflection_Service $reflectionService
82 * @return void
83 * @internal
84 */
85 public function injectReflectionService(Tx_Extbase_Reflection_Service $reflectionService) {
86 $this->reflectionService = $reflectionService;
87 }
88
89 /**
90 * Checks if the current request type is supported by the controller.
91 *
92 * If your controller only supports certain request types, either
93 * replace / modify the supporteRequestTypes property or override this
94 * method.
95 *
96 * @param Tx_Extbase_MVC_Request $request The current request
97 * @return boolean TRUE if this request type is supported, otherwise FALSE
98 */
99 public function canProcessRequest(Tx_Extbase_MVC_Request $request) {
100 return parent::canProcessRequest($request);
101
102 }
103
104 /**
105 * Handles a request. The result output is returned by altering the given response.
106 *
107 * @param Tx_Extbase_MVC_Request $request The request object
108 * @param Tx_Extbase_MVC_Response $response The response, modified by this handler
109 * @return void
110 */
111 public function processRequest(Tx_Extbase_MVC_Request $request, Tx_Extbase_MVC_Response $response) {
112 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);
113
114 $this->request = $request;
115 $this->request->setDispatched(TRUE);
116 $this->response = $response;
117
118 $this->URIBuilder = t3lib_div::makeInstance('Tx_Extbase_MVC_Web_Routing_URIBuilder');
119 $this->URIBuilder->setRequest($request);
120
121 $this->actionMethodName = $this->resolveActionMethodName();
122
123 $this->initializeActionMethodArguments();
124 $this->initializeControllerArgumentsBaseValidators();
125 $this->initializeActionMethodValidators();
126
127 $this->initializeAction();
128 $actionInitializationMethodName = 'initialize' . ucfirst($this->actionMethodName);
129 if (method_exists($this, $actionInitializationMethodName)) {
130 call_user_func(array($this, $actionInitializationMethodName));
131 }
132
133 $this->mapRequestArgumentsToControllerArguments();
134 $this->view = $this->resolveView();
135 if ($this->view !== NULL) $this->initializeView($this->view);
136 $this->callActionMethod();
137 }
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 * @internal
148 */
149 protected function initializeActionMethodArguments() {
150 $methodParameters = $this->reflectionService->getMethodParameters(get_class($this), $this->actionMethodName);
151 foreach ($methodParameters as $parameterName => $parameterInfo) {
152 $dataType = 'Text';
153 if (isset($parameterInfo['type'])) {
154 $dataType = $parameterInfo['type'];
155 } elseif ($parameterInfo['array']) {
156 $dataType = 'array';
157 }
158 $defaultValue = (isset($parameterInfo['defaultValue']) ? $parameterInfo['defaultValue'] : NULL);
159
160 $this->arguments->addNewArgument($parameterName, $dataType, ($parameterInfo['optional'] === FALSE), $defaultValue);
161 }
162 }
163
164 /**
165 * Detects and registers any additional validators for arguments which were
166 * specified in the @validate annotations of an action method
167 *
168 * @return void
169 * @internal
170 */
171 protected function initializeActionMethodValidators() {
172 $validatorConjunctions = $this->validatorResolver->buildMethodArgumentsValidatorConjunctions(get_class($this), $this->actionMethodName);
173 foreach ($validatorConjunctions as $argumentName => $validatorConjunction) {
174 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);
175 $this->arguments[$argumentName]->setValidator($validatorConjunction);
176 }
177 }
178
179 /**
180 * Determines the action method and assures that the method exists.
181 *
182 * @return string The action method name
183 * @throws Tx_Extbase_Exception_NoSuchAction if the action specified in the request object does not exist (and if there's no default action either).
184 * @internal
185 */
186 protected function resolveActionMethodName() {
187 $actionMethodName = $this->request->getControllerActionName() . 'Action';
188 if (!method_exists($this, $actionMethodName)) throw new Tx_Extbase_Exception_NoSuchAction('An action "' . $actionMethodName . '" does not exist in controller "' . get_class($this) . '".', 1186669086);
189 return $actionMethodName;
190 }
191
192 /**
193 * Calls the specified action method and passes the arguments.
194 *
195 * If the action returns a string, it is appended to the content in the
196 * response object. If the action doesn't return anything and a valid
197 * view exists, the view is rendered automatically.
198 *
199 * @param string $actionMethodName Name of the action method to call
200 * @return void
201 * @internal
202 */
203 protected function callActionMethod() {
204 $argumentsAreValid = TRUE;
205 $preparedArguments = array();
206 foreach ($this->arguments as $argument) {
207 $preparedArguments[] = $argument->getValue();
208 }
209
210 if ($this->argumentsMappingResults->hasErrors()) {
211 $actionResult = call_user_func(array($this, $this->errorMethodName));
212 } else {
213 $actionResult = call_user_func_array(array($this, $this->actionMethodName), $preparedArguments);
214 }
215 if ($actionResult === NULL && $this->view instanceof Tx_Extbase_MVC_View_ViewInterface) {
216 $this->response->appendContent($this->view->render());
217 } elseif (is_string($actionResult) && strlen($actionResult) > 0) {
218 $this->response->appendContent($actionResult);
219 }
220 }
221
222 /**
223 * Prepares a view for the current action and stores it in $this->view.
224 * By default, this method tries to locate a view with a name matching
225 * the current action.
226 *
227 * @return void
228 */
229 protected function resolveView() {
230 $view = t3lib_div::makeInstance('Tx_Fluid_View_TemplateView');
231 $controllerContext = $this->buildControllerContext();
232 $view->setControllerContext($controllerContext);
233 if ($view->hasTemplate() === FALSE) {
234 $viewObjectName = $this->resolveViewObjectName();
235 if ($viewObjectName === FALSE) $viewObjectName = 'Tx_Extbase_MVC_View_EmptyView';
236 $view = t3lib_div::makeInstance($viewObjectName);
237 $view->setControllerContext($controllerContext);
238 }
239 $view->initializeView(); // In FLOW3, solved through Object Lifecycle methods, we need to call it explicitely.
240 // $view->assign('flashMessages', $this->popFlashMessages());
241 return $view;
242 }
243
244 /**
245 * Determines the fully qualified view object name.
246 *
247 * @return mixed The fully qualified view object name or FALSE if no matching view could be found.
248 */
249 protected function resolveViewObjectName() {
250 $possibleViewName = $this->viewObjectNamePattern;
251 $possibleViewName = str_replace('@extension', $this->request->getControllerExtensionName(), $possibleViewName);
252 $possibleViewName = str_replace('@controller', $this->request->getControllerName(), $$possibleViewName);
253 $possibleViewName = str_replace('@action', ucfirst($this->request->getControllerActionName()), $possibleViewName);
254
255 if (class_exists($possibleViewName)) {
256 return $possibleViewName;
257 }
258
259 if ($this->defaultViewObjectName !== NULL && class_exists($this->defaultViewObjectName)) {
260 return $this->defaultViewObjectName;
261 }
262 return FALSE;
263 }
264
265 /**
266 * Initializes the view before invoking an action method.
267 *
268 * Override this method to solve assign variables common for all actions
269 * or prepare the view in another way before the action is called.
270 *
271 * @param Tx_Extbase_View_ViewInterface $view The view to be initialized
272 * @return void
273 */
274 protected function initializeView(Tx_Extbase_MVC_View_ViewInterface $view) {
275 }
276
277 /**
278 * Initializes the controller before invoking an action method.
279 *
280 * Override this method to solve tasks which all actions have in
281 * common.
282 *
283 * @return void
284 */
285 protected function initializeAction() {
286 }
287
288 /**
289 * A special action which is called if the originally intended action could
290 * not be called, for example if the arguments were not valid.
291 *
292 * @return string
293 */
294 protected function errorAction() {
295 $message = 'An error occurred while trying to call ' . get_class($this) . '->' . $this->actionMethodName . '().' . PHP_EOL;
296 foreach ($this->argumentsMappingResults->getErrors() as $error) {
297 $message .= 'Error: ' . $error . PHP_EOL;
298 }
299 foreach ($this->argumentsMappingResults->getWarnings() as $warning) {
300 $message .= 'Warning: ' . $warning . PHP_EOL;
301 }
302 return $message;
303 }
304 }
305 ?>