TYPO3  7.6
extbase/Classes/Mvc/Controller/ActionController.php
Go to the documentation of this file.
1 <?php
2 namespace TYPO3\CMS\Extbase\Mvc\Controller;
3 
4 /*
5  * This file is part of the TYPO3 CMS project.
6  *
7  * It is free software; you can redistribute it and/or modify it under
8  * the terms of the GNU General Public License, either version 2
9  * of the License, or any later version.
10  *
11  * For the full copyright and license information, please read the
12  * LICENSE.txt file that was distributed with this source code.
13  *
14  * The TYPO3 project - inspiring people to share!
15  */
16 
19 use TYPO3\CMS\Extbase\Mvc\Exception\StopActionException;
21 use TYPO3\CMS\Extbase\Mvc\Web\Request as WebRequest;
24 
31 {
35  protected $reflectionService;
36 
40  protected $cacheService;
41 
48  protected $view = null;
49 
54  protected $namespacesViewObjectNamePattern = '@vendor\@extension\View\@controller\@action@format';
55 
65  protected $viewFormatToObjectNameMap = array();
66 
74  protected $defaultViewObjectName = \TYPO3\CMS\Fluid\View\TemplateView::class;
75 
82  protected $actionMethodName = 'indexAction';
83 
90  protected $errorMethodName = 'errorAction';
91 
97 
104  protected $request;
105 
112  protected $response;
113 
117  public function injectReflectionService(\TYPO3\CMS\Extbase\Reflection\ReflectionService $reflectionService)
118  {
119  $this->reflectionService = $reflectionService;
120  }
121 
125  public function injectCacheService(\TYPO3\CMS\Extbase\Service\CacheService $cacheService)
126  {
127  $this->cacheService = $cacheService;
128  }
129 
134  {
135  $this->mvcPropertyMappingConfigurationService = $mvcPropertyMappingConfigurationService;
136  }
137 
147  public function processRequest(\TYPO3\CMS\Extbase\Mvc\RequestInterface $request, \TYPO3\CMS\Extbase\Mvc\ResponseInterface $response)
148  {
149  if (!$this->canProcessRequest($request)) {
150  throw new \TYPO3\CMS\Extbase\Mvc\Exception\UnsupportedRequestTypeException(get_class($this) . ' does not support requests of type "' . get_class($request) . '". Supported types are: ' . implode(' ', $this->supportedRequestTypes), 1187701131);
151  }
152 
153  if ($response instanceof \TYPO3\CMS\Extbase\Mvc\Web\Response && $request instanceof WebRequest) {
154  $response->setRequest($request);
155  }
156  $this->request = $request;
157  $this->request->setDispatched(true);
158  $this->response = $response;
159  $this->uriBuilder = $this->objectManager->get(\TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder::class);
160  $this->uriBuilder->setRequest($request);
161  $this->actionMethodName = $this->resolveActionMethodName();
163  $this->initializeActionMethodValidators();
164  $this->mvcPropertyMappingConfigurationService->initializePropertyMappingConfigurationFromRequest($request, $this->arguments);
165  $this->initializeAction();
166  $actionInitializationMethodName = 'initialize' . ucfirst($this->actionMethodName);
167  if (method_exists($this, $actionInitializationMethodName)) {
168  call_user_func(array($this, $actionInitializationMethodName));
169  }
170  $this->mapRequestArgumentsToControllerArguments();
171  $this->controllerContext = $this->buildControllerContext();
172  $this->view = $this->resolveView();
173  if ($this->view !== null) {
174  $this->initializeView($this->view);
175  }
176  $this->callActionMethod();
177  }
178 
189  protected function initializeActionMethodArguments()
190  {
191  $methodParameters = $this->reflectionService->getMethodParameters(get_class($this), $this->actionMethodName);
192  foreach ($methodParameters as $parameterName => $parameterInfo) {
193  $dataType = null;
194  if (isset($parameterInfo['type'])) {
195  $dataType = $parameterInfo['type'];
196  } elseif ($parameterInfo['array']) {
197  $dataType = 'array';
198  }
199  if ($dataType === null) {
200  throw new \TYPO3\CMS\Extbase\Mvc\Exception\InvalidArgumentTypeException('The argument type for parameter $' . $parameterName . ' of method ' . get_class($this) . '->' . $this->actionMethodName . '() could not be detected.', 1253175643);
201  }
202  $defaultValue = isset($parameterInfo['defaultValue']) ? $parameterInfo['defaultValue'] : null;
203  $this->arguments->addNewArgument($parameterName, $dataType, $parameterInfo['optional'] === false, $defaultValue);
204  }
205  }
206 
217  protected function initializeActionMethodValidators()
218  {
219 
225  $actionMethodParameters = static::getActionMethodParameters($this->objectManager);
226  if (isset($actionMethodParameters[$this->actionMethodName])) {
227  $methodParameters = $actionMethodParameters[$this->actionMethodName];
228  } else {
229  $methodParameters = array();
230  }
231 
237  $parameterValidators = $this->validatorResolver->buildMethodArgumentsValidatorConjunctions(get_class($this), $this->actionMethodName, $methodParameters);
239  foreach ($this->arguments as $argument) {
240  $validator = $parameterValidators[$argument->getName()];
241 
242  $baseValidatorConjunction = $this->validatorResolver->getBaseValidatorConjunction($argument->getDataType());
243  if (!empty($baseValidatorConjunction) && $validator instanceof AbstractCompositeValidator) {
244  $validator->addValidator($baseValidatorConjunction);
245  }
246  $argument->setValidator($validator);
247  }
248  }
249 
256  protected function resolveActionMethodName()
257  {
258  $actionMethodName = $this->request->getControllerActionName() . 'Action';
259  if (!method_exists($this, $actionMethodName)) {
260  throw new \TYPO3\CMS\Extbase\Mvc\Exception\NoSuchActionException('An action "' . $actionMethodName . '" does not exist in controller "' . get_class($this) . '".', 1186669086);
261  }
262  return $actionMethodName;
263  }
264 
275  protected function callActionMethod()
276  {
277  $preparedArguments = array();
279  foreach ($this->arguments as $argument) {
280  $preparedArguments[] = $argument->getValue();
281  }
282  $validationResult = $this->arguments->getValidationResults();
283  if (!$validationResult->hasErrors()) {
284  $this->emitBeforeCallActionMethodSignal($preparedArguments);
285  $actionResult = call_user_func_array(array($this, $this->actionMethodName), $preparedArguments);
286  } else {
287  $methodTagsValues = $this->reflectionService->getMethodTagsValues(get_class($this), $this->actionMethodName);
288  $ignoreValidationAnnotations = array();
289  if (isset($methodTagsValues['ignorevalidation'])) {
290  $ignoreValidationAnnotations = $methodTagsValues['ignorevalidation'];
291  }
292  // if there exist errors which are not ignored with @ignorevalidation => call error method
293  // else => call action method
294  $shouldCallActionMethod = true;
295  foreach ($validationResult->getSubResults() as $argumentName => $subValidationResult) {
296  if (!$subValidationResult->hasErrors()) {
297  continue;
298  }
299  if (array_search('$' . $argumentName, $ignoreValidationAnnotations) !== false) {
300  continue;
301  }
302  $shouldCallActionMethod = false;
303  break;
304  }
305  if ($shouldCallActionMethod) {
306  $this->emitBeforeCallActionMethodSignal($preparedArguments);
307  $actionResult = call_user_func_array(array($this, $this->actionMethodName), $preparedArguments);
308  } else {
309  $actionResult = call_user_func(array($this, $this->errorMethodName));
310  }
311  }
312 
313  if ($actionResult === null && $this->view instanceof ViewInterface) {
314  $this->response->appendContent($this->view->render());
315  } elseif (is_string($actionResult) && $actionResult !== '') {
316  $this->response->appendContent($actionResult);
317  } elseif (is_object($actionResult) && method_exists($actionResult, '__toString')) {
318  $this->response->appendContent((string)$actionResult);
319  }
320  }
321 
327  protected function emitBeforeCallActionMethodSignal(array $preparedArguments)
328  {
329  $this->signalSlotDispatcher->dispatch(__CLASS__, 'beforeCallActionMethod', array(get_class($this), $this->actionMethodName, $preparedArguments));
330  }
331 
339  protected function resolveView()
340  {
341  $viewObjectName = $this->resolveViewObjectName();
342  if ($viewObjectName !== false) {
344  $view = $this->objectManager->get($viewObjectName);
345  $this->setViewConfiguration($view);
346  if ($view->canRender($this->controllerContext) === false) {
347  unset($view);
348  }
349  }
350  if (!isset($view) && $this->defaultViewObjectName != '') {
352  $view = $this->objectManager->get($this->defaultViewObjectName);
353  $this->setViewConfiguration($view);
354  if ($view->canRender($this->controllerContext) === false) {
355  unset($view);
356  }
357  }
358  if (!isset($view)) {
359  $view = $this->objectManager->get(\TYPO3\CMS\Extbase\Mvc\View\NotFoundView::class);
360  $view->assign('errorMessage', 'No template was found. View could not be resolved for action "'
361  . $this->request->getControllerActionName() . '" in class "' . $this->request->getControllerObjectName() . '"');
362  }
363  $view->setControllerContext($this->controllerContext);
364  if (method_exists($view, 'injectSettings')) {
365  $view->injectSettings($this->settings);
366  }
367  $view->initializeView();
368  // In TYPO3.Flow, solved through Object Lifecycle methods, we need to call it explicitly
369  $view->assign('settings', $this->settings);
370  // same with settings injection.
371  return $view;
372  }
373 
379  protected function setViewConfiguration(ViewInterface $view)
380  {
381  // Template Path Override
382  $extbaseFrameworkConfiguration = $this->configurationManager->getConfiguration(
384  );
385 
386  // set TemplateRootPaths
387  $viewFunctionName = 'setTemplateRootPaths';
388  if (method_exists($view, $viewFunctionName)) {
389  $setting = 'templateRootPaths';
390  $parameter = $this->getViewProperty($extbaseFrameworkConfiguration, $setting);
391  // no need to bother if there is nothing to set
392  if ($parameter) {
393  $view->$viewFunctionName($parameter);
394  }
395  }
396 
397  // set LayoutRootPaths
398  $viewFunctionName = 'setLayoutRootPaths';
399  if (method_exists($view, $viewFunctionName)) {
400  $setting = 'layoutRootPaths';
401  $parameter = $this->getViewProperty($extbaseFrameworkConfiguration, $setting);
402  // no need to bother if there is nothing to set
403  if ($parameter) {
404  $view->$viewFunctionName($parameter);
405  }
406  }
407 
408  // set PartialRootPaths
409  $viewFunctionName = 'setPartialRootPaths';
410  if (method_exists($view, $viewFunctionName)) {
411  $setting = 'partialRootPaths';
412  $parameter = $this->getViewProperty($extbaseFrameworkConfiguration, $setting);
413  // no need to bother if there is nothing to set
414  if ($parameter) {
415  $view->$viewFunctionName($parameter);
416  }
417  }
418  }
419 
430  protected function getViewProperty($extbaseFrameworkConfiguration, $setting)
431  {
432  $values = array();
433  if (
434  !empty($extbaseFrameworkConfiguration['view'][$setting])
435  && is_array($extbaseFrameworkConfiguration['view'][$setting])
436  ) {
437  $values = ArrayUtility::sortArrayWithIntegerKeys($extbaseFrameworkConfiguration['view'][$setting]);
438  $values = array_reverse($values, true);
439  }
440 
441  return $values;
442  }
443 
450  protected function resolveViewObjectName()
451  {
452  $vendorName = $this->request->getControllerVendorName();
453  if ($vendorName === null) {
454  return false;
455  }
456 
457  $possibleViewName = str_replace(
458  array(
459  '@vendor',
460  '@extension',
461  '@controller',
462  '@action'
463  ),
464  array(
465  $vendorName,
466  $this->request->getControllerExtensionName(),
467  $this->request->getControllerName(),
468  ucfirst($this->request->getControllerActionName())
469  ),
471  );
472  $format = $this->request->getFormat();
473  $viewObjectName = str_replace('@format', ucfirst($format), $possibleViewName);
474  if (class_exists($viewObjectName) === false) {
475  $viewObjectName = str_replace('@format', '', $possibleViewName);
476  }
477  if (isset($this->viewFormatToObjectNameMap[$format]) && class_exists($viewObjectName) === false) {
478  $viewObjectName = $this->viewFormatToObjectNameMap[$format];
479  }
480  return class_exists($viewObjectName) ? $viewObjectName : false;
481  }
482 
494  protected function initializeView(ViewInterface $view)
495  {
496  }
497 
507  protected function initializeAction()
508  {
509  }
510 
524  protected function errorAction()
525  {
526  $this->clearCacheOnError();
527  $this->addErrorFlashMessage();
528  $this->forwardToReferringRequest();
529 
530  return $this->getFlattenedValidationErrorMessage();
531  }
532 
539  protected function clearCacheOnError()
540  {
541  $extbaseSettings = $this->configurationManager->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK);
542  if (isset($extbaseSettings['persistence']['enableAutomaticCacheClearing']) && $extbaseSettings['persistence']['enableAutomaticCacheClearing'] === '1') {
543  if (isset($GLOBALS['TSFE'])) {
544  $pageUid = $GLOBALS['TSFE']->id;
545  $this->cacheService->clearPageCache(array($pageUid));
546  }
547  }
548  }
549 
556  protected function addErrorFlashMessage()
557  {
558  $errorFlashMessage = $this->getErrorFlashMessage();
559  if ($errorFlashMessage !== false) {
560  $this->addFlashMessage($errorFlashMessage, '', FlashMessage::ERROR);
561  }
562  }
563 
572  protected function getErrorFlashMessage()
573  {
574  return 'An error occurred while trying to call ' . get_class($this) . '->' . $this->actionMethodName . '()';
575  }
576 
585  protected function forwardToReferringRequest()
586  {
587  $referringRequest = $this->request->getReferringRequest();
588  if ($referringRequest !== null) {
589  $originalRequest = clone $this->request;
590  $this->request->setOriginalRequest($originalRequest);
591  $this->request->setOriginalRequestMappingResults($this->arguments->getValidationResults());
592  $this->forward(
593  $referringRequest->getControllerActionName(),
594  $referringRequest->getControllerName(),
595  $referringRequest->getControllerExtensionName(),
596  $referringRequest->getArguments()
597  );
598  }
599  }
600 
609  {
610  $outputMessage = 'Validation failed while trying to call ' . get_class($this) . '->' . $this->actionMethodName . '().' . PHP_EOL;
611  return $outputMessage;
612  }
613 
622  {
623  $reflectionService = $objectManager->get(\TYPO3\CMS\Extbase\Reflection\ReflectionService::class);
624 
625  $result = array();
626 
627  $className = get_called_class();
628  $methodNames = get_class_methods($className);
629  foreach ($methodNames as $methodName) {
630  if (strlen($methodName) > 6 && strpos($methodName, 'Action', strlen($methodName) - 6) !== false) {
631  $result[$methodName] = $reflectionService->getMethodParameters($className, $methodName);
632  }
633  }
634 
635  return $result;
636  }
637 }