Completed
Push — master ( 47aa14...e68e24 )
by
unknown
28:26
created

ActionController::errorAction()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 4
nc 1
nop 0
dl 0
loc 7
rs 10
c 0
b 0
f 0
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
17
use Psr\EventDispatcher\EventDispatcherInterface;
18
use TYPO3\CMS\Core\Messaging\FlashMessage;
19
use TYPO3\CMS\Core\Page\PageRenderer;
20
use TYPO3\CMS\Core\Utility\GeneralUtility;
21
use TYPO3\CMS\Core\Utility\MathUtility;
22
use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface;
23
use TYPO3\CMS\Extbase\Event\Mvc\BeforeActionCallEvent;
24
use TYPO3\CMS\Extbase\Mvc\Exception\StopActionException;
25
use TYPO3\CMS\Extbase\Mvc\View\ViewInterface;
26
use TYPO3\CMS\Extbase\Mvc\Web\ReferringRequest;
27
use TYPO3\CMS\Extbase\Security\Cryptography\HashService;
28
use TYPO3\CMS\Extbase\Validation\Validator\ConjunctionValidator;
29
use TYPO3\CMS\Extbase\Validation\Validator\ValidatorInterface;
30
use TYPO3Fluid\Fluid\View\TemplateView;
31
32
/**
33
 * A multi action controller. This is by far the most common base class for Controllers.
34
 */
35
class ActionController implements ControllerInterface
36
{
37
    /**
38
     * @var \TYPO3\CMS\Extbase\Reflection\ReflectionService
39
     */
40
    protected $reflectionService;
41
42
    /**
43
     * @var \TYPO3\CMS\Extbase\Service\CacheService
44
     */
45
    protected $cacheService;
46
47
    /**
48
     * @var HashService
49
     */
50
    protected $hashService;
51
52
    /**
53
     * The current view, as resolved by resolveView()
54
     *
55
     * @var ViewInterface
56
     */
57
    protected $view;
58
59
    /**
60
     * The default view object to use if none of the resolved views can render
61
     * a response for the current request.
62
     *
63
     * @var string
64
     */
65
    protected $defaultViewObjectName = \TYPO3\CMS\Fluid\View\TemplateView::class;
66
67
    /**
68
     * Name of the action method
69
     *
70
     * @var string
71
     */
72
    protected $actionMethodName = 'indexAction';
73
74
    /**
75
     * Name of the special error action method which is called in case of errors
76
     *
77
     * @var string
78
     */
79
    protected $errorMethodName = 'errorAction';
80
81
    /**
82
     * @var \TYPO3\CMS\Extbase\Mvc\Controller\MvcPropertyMappingConfigurationService
83
     */
84
    protected $mvcPropertyMappingConfigurationService;
85
86
    /**
87
     * @var EventDispatcherInterface
88
     */
89
    protected $eventDispatcher;
90
91
    /**
92
     * The current request.
93
     *
94
     * @var \TYPO3\CMS\Extbase\Mvc\Request
95
     */
96
    protected $request;
97
98
    /**
99
     * The response which will be returned by this action controller
100
     *
101
     * @var \TYPO3\CMS\Extbase\Mvc\Response
102
     */
103
    protected $response;
104
105
    /**
106
     * @param \TYPO3\CMS\Extbase\Reflection\ReflectionService $reflectionService
107
     */
108
    public function injectReflectionService(\TYPO3\CMS\Extbase\Reflection\ReflectionService $reflectionService)
109
    {
110
        $this->reflectionService = $reflectionService;
111
    }
112
113
    /**
114
     * @param \TYPO3\CMS\Extbase\Service\CacheService $cacheService
115
     */
116
    public function injectCacheService(\TYPO3\CMS\Extbase\Service\CacheService $cacheService)
117
    {
118
        $this->cacheService = $cacheService;
119
    }
120
121
    /**
122
     * @param HashService $hashService
123
     */
124
    public function injectHashService(HashService $hashService)
125
    {
126
        $this->hashService = $hashService;
127
    }
128
129
    /**
130
     * @param \TYPO3\CMS\Extbase\Mvc\Controller\MvcPropertyMappingConfigurationService $mvcPropertyMappingConfigurationService
131
     */
132
    public function injectMvcPropertyMappingConfigurationService(\TYPO3\CMS\Extbase\Mvc\Controller\MvcPropertyMappingConfigurationService $mvcPropertyMappingConfigurationService)
133
    {
134
        $this->mvcPropertyMappingConfigurationService = $mvcPropertyMappingConfigurationService;
135
    }
136
137
    public function injectEventDispatcher(EventDispatcherInterface $eventDispatcher): void
138
    {
139
        $this->eventDispatcher = $eventDispatcher;
140
    }
141
142
    /**
143
     * Handles a request. The result output is returned by altering the given response.
144
     *
145
     * @param \TYPO3\CMS\Extbase\Mvc\RequestInterface $request The request object
146
     * @param \TYPO3\CMS\Extbase\Mvc\ResponseInterface $response The response, modified by this handler
147
     *
148
     * @throws \TYPO3\CMS\Extbase\Mvc\Exception\UnsupportedRequestTypeException
149
     */
150
    public function processRequest(\TYPO3\CMS\Extbase\Mvc\RequestInterface $request, \TYPO3\CMS\Extbase\Mvc\ResponseInterface $response)
151
    {
152
        if (!$this->canProcessRequest($request)) {
153
            throw new \TYPO3\CMS\Extbase\Mvc\Exception\UnsupportedRequestTypeException(static::class . ' does not support requests of type "' . get_class($request) . '". Supported types are: ' . implode(' ', $this->supportedRequestTypes), 1187701131);
154
        }
155
156
        $setRequestCallable = [$response, 'setRequest'];
157
        if (is_callable($setRequestCallable)) {
158
            $setRequestCallable($request);
159
        }
160
        $this->request = $request;
0 ignored issues
show
Documentation Bug introduced by
$request is of type TYPO3\CMS\Extbase\Mvc\RequestInterface, but the property $request was declared to be of type TYPO3\CMS\Extbase\Mvc\Request. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
161
        $this->request->setDispatched(true);
162
        $this->response = $response;
0 ignored issues
show
Documentation Bug introduced by
$response is of type TYPO3\CMS\Extbase\Mvc\ResponseInterface, but the property $response was declared to be of type TYPO3\CMS\Extbase\Mvc\Response. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
163
        $this->uriBuilder = $this->objectManager->get(\TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder::class);
164
        $this->uriBuilder->setRequest($request);
165
        $this->actionMethodName = $this->resolveActionMethodName();
166
        $this->initializeActionMethodArguments();
167
        $this->initializeActionMethodValidators();
168
        $this->mvcPropertyMappingConfigurationService->initializePropertyMappingConfigurationFromRequest($request, $this->arguments);
169
        $this->initializeAction();
170
        $actionInitializationMethodName = 'initialize' . ucfirst($this->actionMethodName);
171
        if (method_exists($this, $actionInitializationMethodName)) {
172
            call_user_func([$this, $actionInitializationMethodName]);
173
        }
174
        $this->mapRequestArgumentsToControllerArguments();
175
        $this->controllerContext = $this->buildControllerContext();
176
        $this->view = $this->resolveView();
177
        if ($this->view !== null) {
178
            $this->initializeView($this->view);
179
        }
180
        $this->callActionMethod();
181
        $this->renderAssetsForRequest($request);
182
    }
183
184
    /**
185
     * Method which initializes assets that should be attached to the response
186
     * for the given $request, which contains parameters that an override can
187
     * use to determine which assets to add via PageRenderer.
188
     *
189
     * This default implementation will attempt to render the sections "HeaderAssets"
190
     * and "FooterAssets" from the template that is being rendered, inserting the
191
     * rendered content into either page header or footer, as appropriate. Both
192
     * sections are optional and can be used one or both in combination.
193
     *
194
     * You can add assets with this method without worrying about duplicates, if
195
     * for example you do this in a plugin that gets used multiple time on a page.
196
     *
197
     * @param \TYPO3\CMS\Extbase\Mvc\RequestInterface $request
198
     */
199
    protected function renderAssetsForRequest($request)
200
    {
201
        if (!$this->view instanceof TemplateView) {
202
            // Only TemplateView (from Fluid engine, so this includes all TYPO3 Views based
203
            // on TYPO3's AbstractTemplateView) supports renderSection(). The method is not
204
            // declared on ViewInterface - so we must assert a specific class. We silently skip
205
            // asset processing if the View doesn't match, so we don't risk breaking custom Views.
206
            return;
207
        }
208
        $pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
209
        $variables = ['request' => $request, 'arguments' => $this->arguments];
210
        $headerAssets = $this->view->renderSection('HeaderAssets', $variables, true);
211
        $footerAssets = $this->view->renderSection('FooterAssets', $variables, true);
212
        if (!empty(trim($headerAssets))) {
213
            $pageRenderer->addHeaderData($headerAssets);
214
        }
215
        if (!empty(trim($footerAssets))) {
216
            $pageRenderer->addFooterData($footerAssets);
217
        }
218
    }
219
220
    /**
221
     * Implementation of the arguments initialization in the action controller:
222
     * Automatically registers arguments of the current action
223
     *
224
     * Don't override this method - use initializeAction() instead.
225
     *
226
     * @throws \TYPO3\CMS\Extbase\Mvc\Exception\InvalidArgumentTypeException
227
     * @see initializeArguments()
228
     */
229
    protected function initializeActionMethodArguments()
230
    {
231
        $methodParameters = $this->reflectionService
232
            ->getClassSchema(static::class)
233
            ->getMethod($this->actionMethodName)->getParameters();
234
235
        foreach ($methodParameters as $parameterName => $parameter) {
236
            $dataType = null;
237
            if ($parameter->getType() !== null) {
238
                $dataType = $parameter->getType();
239
            } elseif ($parameter->isArray()) {
240
                $dataType = 'array';
241
            }
242
            if ($dataType === null) {
243
                throw new \TYPO3\CMS\Extbase\Mvc\Exception\InvalidArgumentTypeException('The argument type for parameter $' . $parameterName . ' of method ' . static::class . '->' . $this->actionMethodName . '() could not be detected.', 1253175643);
244
            }
245
            $defaultValue = $parameter->hasDefaultValue() ? $parameter->getDefaultValue() : null;
246
            $this->arguments->addNewArgument($parameterName, $dataType, !$parameter->isOptional(), $defaultValue);
247
        }
248
    }
249
250
    /**
251
     * Adds the needed validators to the Arguments:
252
     *
253
     * - Validators checking the data type from the @param annotation
254
     * - Custom validators specified with validate annotations.
255
     * - Model-based validators (validate annotations in the model)
256
     * - Custom model validator classes
257
     */
258
    protected function initializeActionMethodValidators()
259
    {
260
        if ($this->arguments->count() === 0) {
261
            return;
262
        }
263
264
        $classSchemaMethod = $this->reflectionService->getClassSchema(static::class)
265
            ->getMethod($this->actionMethodName);
266
267
        /** @var \TYPO3\CMS\Extbase\Mvc\Controller\Argument $argument */
268
        foreach ($this->arguments as $argument) {
269
            $classSchemaMethodParameter = $classSchemaMethod->getParameter($argument->getName());
270
            /*
271
             * At this point validation is skipped if there is an IgnoreValidation annotation.
272
             *
273
             * todo: IgnoreValidation annotations could be evaluated in the ClassSchema and result in
274
             * todo: no validators being applied to the method parameter.
275
             */
276
            if ($classSchemaMethodParameter->ignoreValidation()) {
277
                continue;
278
            }
279
280
            // todo: It's quite odd that an instance of ConjunctionValidator is created directly here.
281
            // todo: \TYPO3\CMS\Extbase\Validation\ValidatorResolver::getBaseValidatorConjunction could/should be used
282
            // todo: here, to benefit of the built in 1st level cache of the ValidatorResolver.
283
            $validator = $this->objectManager->get(ConjunctionValidator::class);
284
285
            foreach ($classSchemaMethodParameter->getValidators() as $validatorDefinition) {
286
                /** @var ValidatorInterface $validatorInstance */
287
                $validatorInstance = $this->objectManager->get(
288
                    $validatorDefinition['className'],
289
                    $validatorDefinition['options']
290
                );
291
292
                $validator->addValidator(
293
                    $validatorInstance
294
                );
295
            }
296
297
            $baseValidatorConjunction = $this->validatorResolver->getBaseValidatorConjunction($argument->getDataType());
298
            if ($baseValidatorConjunction->count() > 0) {
299
                $validator->addValidator($baseValidatorConjunction);
300
            }
301
            $argument->setValidator($validator);
302
        }
303
    }
304
305
    /**
306
     * Resolves and checks the current action method name
307
     *
308
     * @return string Method name of the current action
309
     * @throws \TYPO3\CMS\Extbase\Mvc\Exception\NoSuchActionException if the action specified in the request object does not exist (and if there's no default action either).
310
     */
311
    protected function resolveActionMethodName()
312
    {
313
        $actionMethodName = $this->request->getControllerActionName() . 'Action';
314
        if (!method_exists($this, $actionMethodName)) {
315
            throw new \TYPO3\CMS\Extbase\Mvc\Exception\NoSuchActionException('An action "' . $actionMethodName . '" does not exist in controller "' . static::class . '".', 1186669086);
316
        }
317
        return $actionMethodName;
318
    }
319
320
    /**
321
     * Calls the specified action method and passes the arguments.
322
     *
323
     * If the action returns a string, it is appended to the content in the
324
     * response object. If the action doesn't return anything and a valid
325
     * view exists, the view is rendered automatically.
326
     */
327
    protected function callActionMethod()
328
    {
329
        $preparedArguments = [];
330
        /** @var \TYPO3\CMS\Extbase\Mvc\Controller\Argument $argument */
331
        foreach ($this->arguments as $argument) {
332
            $preparedArguments[] = $argument->getValue();
333
        }
334
        $validationResult = $this->arguments->validate();
335
        if (!$validationResult->hasErrors()) {
336
            $this->eventDispatcher->dispatch(new BeforeActionCallEvent(static::class, $this->actionMethodName, $preparedArguments));
337
            $actionResult = $this->{$this->actionMethodName}(...$preparedArguments);
338
        } else {
339
            $actionResult = $this->{$this->errorMethodName}();
340
        }
341
342
        if ($actionResult === null && $this->view instanceof ViewInterface) {
343
            $this->response->appendContent($this->view->render());
344
        } elseif (is_string($actionResult) && $actionResult !== '') {
345
            $this->response->appendContent($actionResult);
346
        } elseif (is_object($actionResult) && method_exists($actionResult, '__toString')) {
347
            $this->response->appendContent((string)$actionResult);
348
        }
349
    }
350
351
    /**
352
     * Emits a signal before the current action is called
353
     *
354
     * @param array $preparedArguments
355
     */
356
    protected function emitBeforeCallActionMethodSignal(array $preparedArguments)
357
    {
358
        trigger_error(
359
            __METHOD__ . ' is deprecated and will be removed in version 11.0 - use PSR-14 events instead.',
360
            E_USER_DEPRECATED
361
        );
362
        $this->signalSlotDispatcher->dispatch(__CLASS__, 'beforeCallActionMethod', [static::class, $this->actionMethodName, $preparedArguments]);
363
    }
364
365
    /**
366
     * Prepares a view for the current action.
367
     * By default, this method tries to locate a view with a name matching the current action.
368
     *
369
     * @return ViewInterface
370
     */
371
    protected function resolveView()
372
    {
373
        $view = null;
374
        if ($this->defaultViewObjectName != '') {
375
            /** @var ViewInterface $view */
376
            $view = $this->objectManager->get($this->defaultViewObjectName);
377
            $this->setViewConfiguration($view);
378
            if ($view->canRender($this->controllerContext) === false) {
379
                $view = null;
380
            }
381
        }
382
        if (!isset($view)) {
383
            $view = $this->objectManager->get(\TYPO3\CMS\Extbase\Mvc\View\NotFoundView::class);
384
            $view->assign('errorMessage', 'No template was found. View could not be resolved for action "'
385
                . $this->request->getControllerActionName() . '" in class "' . $this->request->getControllerObjectName() . '"');
386
        }
387
        $view->setControllerContext($this->controllerContext);
388
        if (method_exists($view, 'injectSettings')) {
389
            $view->injectSettings($this->settings);
390
        }
391
        $view->initializeView();
392
        // In TYPO3.Flow, solved through Object Lifecycle methods, we need to call it explicitly
393
        $view->assign('settings', $this->settings);
394
        // same with settings injection.
395
        return $view;
396
    }
397
398
    /**
399
     * @param ViewInterface $view
400
     */
401
    protected function setViewConfiguration(ViewInterface $view)
402
    {
403
        // Template Path Override
404
        $extbaseFrameworkConfiguration = $this->configurationManager->getConfiguration(
405
            ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK
406
        );
407
408
        // set TemplateRootPaths
409
        $viewFunctionName = 'setTemplateRootPaths';
410
        if (method_exists($view, $viewFunctionName)) {
411
            $setting = 'templateRootPaths';
412
            $parameter = $this->getViewProperty($extbaseFrameworkConfiguration, $setting);
413
            // no need to bother if there is nothing to set
414
            if ($parameter) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $parameter of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
415
                $view->$viewFunctionName($parameter);
416
            }
417
        }
418
419
        // set LayoutRootPaths
420
        $viewFunctionName = 'setLayoutRootPaths';
421
        if (method_exists($view, $viewFunctionName)) {
422
            $setting = 'layoutRootPaths';
423
            $parameter = $this->getViewProperty($extbaseFrameworkConfiguration, $setting);
424
            // no need to bother if there is nothing to set
425
            if ($parameter) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $parameter of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
426
                $view->$viewFunctionName($parameter);
427
            }
428
        }
429
430
        // set PartialRootPaths
431
        $viewFunctionName = 'setPartialRootPaths';
432
        if (method_exists($view, $viewFunctionName)) {
433
            $setting = 'partialRootPaths';
434
            $parameter = $this->getViewProperty($extbaseFrameworkConfiguration, $setting);
435
            // no need to bother if there is nothing to set
436
            if ($parameter) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $parameter of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
437
                $view->$viewFunctionName($parameter);
438
            }
439
        }
440
    }
441
442
    /**
443
     * Handles the path resolving for *rootPath(s)
444
     *
445
     * numerical arrays get ordered by key ascending
446
     *
447
     * @param array $extbaseFrameworkConfiguration
448
     * @param string $setting parameter name from TypoScript
449
     *
450
     * @return array
451
     */
452
    protected function getViewProperty($extbaseFrameworkConfiguration, $setting)
453
    {
454
        $values = [];
455
        if (
456
            !empty($extbaseFrameworkConfiguration['view'][$setting])
457
            && is_array($extbaseFrameworkConfiguration['view'][$setting])
458
        ) {
459
            $values = $extbaseFrameworkConfiguration['view'][$setting];
460
        }
461
462
        return $values;
463
    }
464
465
    /**
466
     * Initializes the view before invoking an action method.
467
     *
468
     * Override this method to solve assign variables common for all actions
469
     * or prepare the view in another way before the action is called.
470
     *
471
     * @param ViewInterface $view The view to be initialized
472
     */
473
    protected function initializeView(ViewInterface $view)
474
    {
475
    }
476
477
    /**
478
     * Initializes the controller before invoking an action method.
479
     *
480
     * Override this method to solve tasks which all actions have in
481
     * common.
482
     */
483
    protected function initializeAction()
484
    {
485
    }
486
487
    /**
488
     * A special action which is called if the originally intended action could
489
     * not be called, for example if the arguments were not valid.
490
     *
491
     * The default implementation sets a flash message, request errors and forwards back
492
     * to the originating action. This is suitable for most actions dealing with form input.
493
     *
494
     * We clear the page cache by default on an error as well, as we need to make sure the
495
     * data is re-evaluated when the user changes something.
496
     *
497
     * @return string
498
     */
499
    protected function errorAction()
500
    {
501
        $this->clearCacheOnError();
502
        $this->addErrorFlashMessage();
503
        $this->forwardToReferringRequest();
504
505
        return $this->getFlattenedValidationErrorMessage();
506
    }
507
508
    /**
509
     * Clear cache of current page on error. Needed because we want a re-evaluation of the data.
510
     * Better would be just do delete the cache for the error action, but that is not possible right now.
511
     */
512
    protected function clearCacheOnError()
513
    {
514
        $extbaseSettings = $this->configurationManager->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK);
515
        if (isset($extbaseSettings['persistence']['enableAutomaticCacheClearing']) && $extbaseSettings['persistence']['enableAutomaticCacheClearing'] === '1') {
516
            if (isset($GLOBALS['TSFE'])) {
517
                $pageUid = $GLOBALS['TSFE']->id;
518
                $this->cacheService->clearPageCache([$pageUid]);
519
            }
520
        }
521
    }
522
523
    /**
524
     * If an error occurred during this request, this adds a flash message describing the error to the flash
525
     * message container.
526
     */
527
    protected function addErrorFlashMessage()
528
    {
529
        $errorFlashMessage = $this->getErrorFlashMessage();
530
        if ($errorFlashMessage !== false) {
0 ignored issues
show
introduced by
The condition $errorFlashMessage !== false is always true.
Loading history...
531
            $this->addFlashMessage($errorFlashMessage, '', FlashMessage::ERROR);
532
        }
533
    }
534
535
    /**
536
     * A template method for displaying custom error flash messages, or to
537
     * display no flash message at all on errors. Override this to customize
538
     * the flash message in your action controller.
539
     *
540
     * @return string The flash message or FALSE if no flash message should be set
541
     */
542
    protected function getErrorFlashMessage()
543
    {
544
        return 'An error occurred while trying to call ' . static::class . '->' . $this->actionMethodName . '()';
545
    }
546
547
    /**
548
     * If information on the request before the current request was sent, this method forwards back
549
     * to the originating request. This effectively ends processing of the current request, so do not
550
     * call this method before you have finished the necessary business logic!
551
     *
552
     * @throws StopActionException
553
     */
554
    protected function forwardToReferringRequest()
555
    {
556
        $referringRequest = null;
557
        $referringRequestArguments = $this->request->getInternalArguments()['__referrer']['@request'] ?? null;
558
        if (is_string($referringRequestArguments)) {
559
            $referrerArray = json_decode(
560
                $this->hashService->validateAndStripHmac($referringRequestArguments),
561
                true
562
            );
563
            $arguments = [];
564
            $referringRequest = new ReferringRequest();
565
            $referringRequest->setArguments(array_replace_recursive($arguments, $referrerArray));
566
        }
567
568
        if ($referringRequest !== null) {
569
            $originalRequest = clone $this->request;
570
            $this->request->setOriginalRequest($originalRequest);
571
            $this->request->setOriginalRequestMappingResults($this->arguments->validate());
572
            $this->forward(
573
                $referringRequest->getControllerActionName(),
574
                $referringRequest->getControllerName(),
575
                $referringRequest->getControllerExtensionName(),
576
                $referringRequest->getArguments()
577
            );
578
        }
579
    }
580
581
    /**
582
     * Returns a string with a basic error message about validation failure.
583
     * We may add all validation error messages to a log file in the future,
584
     * but for security reasons (@see #54074) we do not return these here.
585
     *
586
     * @return string
587
     */
588
    protected function getFlattenedValidationErrorMessage()
589
    {
590
        $outputMessage = 'Validation failed while trying to call ' . static::class . '->' . $this->actionMethodName . '().' . PHP_EOL;
591
        return $outputMessage;
592
    }
593
594
    /**
595
     * @var \TYPO3\CMS\Extbase\SignalSlot\Dispatcher
596
     */
597
    protected $signalSlotDispatcher;
598
599
    /**
600
     * @var \TYPO3\CMS\Extbase\Object\ObjectManagerInterface
601
     */
602
    protected $objectManager;
603
604
    /**
605
     * @var \TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder
606
     */
607
    protected $uriBuilder;
608
609
    /**
610
     * Contains the settings of the current extension
611
     *
612
     * @var array
613
     */
614
    protected $settings;
615
616
    /**
617
     * @var \TYPO3\CMS\Extbase\Validation\ValidatorResolver
618
     */
619
    protected $validatorResolver;
620
621
    /**
622
     * @var \TYPO3\CMS\Extbase\Mvc\Controller\Arguments Arguments passed to the controller
623
     */
624
    protected $arguments;
625
626
    /**
627
     * @param \TYPO3\CMS\Extbase\SignalSlot\Dispatcher $signalSlotDispatcher
628
     */
629
    public function injectSignalSlotDispatcher(\TYPO3\CMS\Extbase\SignalSlot\Dispatcher $signalSlotDispatcher)
630
    {
631
        $this->signalSlotDispatcher = $signalSlotDispatcher;
632
    }
633
634
    /**
635
     * @param \TYPO3\CMS\Extbase\Validation\ValidatorResolver $validatorResolver
636
     */
637
    public function injectValidatorResolver(\TYPO3\CMS\Extbase\Validation\ValidatorResolver $validatorResolver)
638
    {
639
        $this->validatorResolver = $validatorResolver;
640
    }
641
642
    /**
643
     * An array of supported request types. By default only web requests are supported.
644
     * Modify or replace this array if your specific controller supports certain
645
     * (additional) request types.
646
     *
647
     * @var array
648
     */
649
    protected $supportedRequestTypes = [\TYPO3\CMS\Extbase\Mvc\Request::class];
650
651
    /**
652
     * @var \TYPO3\CMS\Extbase\Mvc\Controller\ControllerContext
653
     */
654
    protected $controllerContext;
655
656
    /**
657
     * @return ControllerContext
658
     */
659
    public function getControllerContext()
660
    {
661
        return $this->controllerContext;
662
    }
663
664
    /**
665
     * @var ConfigurationManagerInterface
666
     */
667
    protected $configurationManager;
668
669
    /**
670
     * @param ConfigurationManagerInterface $configurationManager
671
     */
672
    public function injectConfigurationManager(ConfigurationManagerInterface $configurationManager)
673
    {
674
        $this->configurationManager = $configurationManager;
675
        $this->settings = $this->configurationManager->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_SETTINGS);
676
    }
677
678
    /**
679
     * Injects the object manager
680
     *
681
     * @param \TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager
682
     */
683
    public function injectObjectManager(\TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager)
684
    {
685
        $this->objectManager = $objectManager;
686
        $this->arguments = $this->objectManager->get(\TYPO3\CMS\Extbase\Mvc\Controller\Arguments::class);
687
    }
688
689
    /**
690
     * Creates a Message object and adds it to the FlashMessageQueue.
691
     *
692
     * @param string $messageBody The message
693
     * @param string $messageTitle Optional message title
694
     * @param int $severity Optional severity, must be one of \TYPO3\CMS\Core\Messaging\FlashMessage constants
695
     * @param bool $storeInSession Optional, defines whether the message should be stored in the session (default) or not
696
     * @throws \InvalidArgumentException if the message body is no string
697
     * @see \TYPO3\CMS\Core\Messaging\FlashMessage
698
     */
699
    public function addFlashMessage($messageBody, $messageTitle = '', $severity = \TYPO3\CMS\Core\Messaging\AbstractMessage::OK, $storeInSession = true)
700
    {
701
        if (!is_string($messageBody)) {
0 ignored issues
show
introduced by
The condition is_string($messageBody) is always true.
Loading history...
702
            throw new \InvalidArgumentException('The message body must be of type string, "' . gettype($messageBody) . '" given.', 1243258395);
703
        }
704
        /* @var \TYPO3\CMS\Core\Messaging\FlashMessage $flashMessage */
705
        $flashMessage = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(
706
            \TYPO3\CMS\Core\Messaging\FlashMessage::class,
707
            (string)$messageBody,
708
            (string)$messageTitle,
709
            $severity,
710
            $storeInSession
711
        );
712
        $this->controllerContext->getFlashMessageQueue()->enqueue($flashMessage);
713
    }
714
715
    /**
716
     * Checks if the current request type is supported by the controller.
717
     *
718
     * If your controller only supports certain request types, either
719
     * replace / modify the supportedRequestTypes property or override this
720
     * method.
721
     *
722
     * @param \TYPO3\CMS\Extbase\Mvc\RequestInterface $request The current request
723
     * @return bool TRUE if this request type is supported, otherwise FALSE
724
     */
725
    public function canProcessRequest(\TYPO3\CMS\Extbase\Mvc\RequestInterface $request)
726
    {
727
        foreach ($this->supportedRequestTypes as $supportedRequestType) {
728
            if ($request instanceof $supportedRequestType) {
729
                return true;
730
            }
731
        }
732
        return false;
733
    }
734
735
    /**
736
     * Initialize the controller context
737
     *
738
     * @return \TYPO3\CMS\Extbase\Mvc\Controller\ControllerContext ControllerContext to be passed to the view
739
     */
740
    protected function buildControllerContext()
741
    {
742
        /** @var \TYPO3\CMS\Extbase\Mvc\Controller\ControllerContext $controllerContext */
743
        $controllerContext = $this->objectManager->get(\TYPO3\CMS\Extbase\Mvc\Controller\ControllerContext::class);
744
        $controllerContext->setRequest($this->request);
745
        $controllerContext->setResponse($this->response);
746
        if ($this->arguments !== null) {
747
            $controllerContext->setArguments($this->arguments);
748
        }
749
        $controllerContext->setUriBuilder($this->uriBuilder);
750
751
        return $controllerContext;
752
    }
753
754
    /**
755
     * Forwards the request to another action and / or controller.
756
     *
757
     * Request is directly transferred to the other action / controller
758
     * without the need for a new request.
759
     *
760
     * @param string $actionName Name of the action to forward to
761
     * @param string|null $controllerName Unqualified object name of the controller to forward to. If not specified, the current controller is used.
762
     * @param string|null $extensionName Name of the extension containing the controller to forward to. If not specified, the current extension is assumed.
763
     * @param array|null $arguments Arguments to pass to the target action
764
     * @throws StopActionException
765
     * @see redirect()
766
     */
767
    public function forward($actionName, $controllerName = null, $extensionName = null, array $arguments = null)
768
    {
769
        $this->request->setDispatched(false);
770
        $this->request->setControllerActionName($actionName);
771
772
        if ($controllerName !== null) {
773
            $this->request->setControllerName($controllerName);
774
        }
775
776
        if ($extensionName !== null) {
777
            $this->request->setControllerExtensionName($extensionName);
778
        }
779
780
        if ($arguments !== null) {
781
            $this->request->setArguments($arguments);
782
        }
783
        throw new StopActionException('forward', 1476045801);
784
    }
785
786
    /**
787
     * Redirects the request to another action and / or controller.
788
     *
789
     * Redirect will be sent to the client which then performs another request to the new URI.
790
     *
791
     * NOTE: This method only supports web requests and will thrown an exception
792
     * if used with other request types.
793
     *
794
     * @param string $actionName Name of the action to forward to
795
     * @param string|null $controllerName Unqualified object name of the controller to forward to. If not specified, the current controller is used.
796
     * @param string|null $extensionName Name of the extension containing the controller to forward to. If not specified, the current extension is assumed.
797
     * @param array|null $arguments Arguments to pass to the target action
798
     * @param int|null $pageUid Target page uid. If NULL, the current page uid is used
799
     * @param int $delay (optional) The delay in seconds. Default is no delay.
800
     * @param int $statusCode (optional) The HTTP status code for the redirect. Default is "303 See Other
801
     * @throws StopActionException
802
     * @see forward()
803
     */
804
    protected function redirect($actionName, $controllerName = null, $extensionName = null, array $arguments = null, $pageUid = null, $delay = 0, $statusCode = 303)
805
    {
806
        if ($controllerName === null) {
807
            $controllerName = $this->request->getControllerName();
808
        }
809
        $this->uriBuilder->reset()->setCreateAbsoluteUri(true);
810
        if (MathUtility::canBeInterpretedAsInteger($pageUid)) {
811
            $this->uriBuilder->setTargetPageUid((int)$pageUid);
812
        }
813
        if (\TYPO3\CMS\Core\Utility\GeneralUtility::getIndpEnv('TYPO3_SSL')) {
814
            $this->uriBuilder->setAbsoluteUriScheme('https');
815
        }
816
        $uri = $this->uriBuilder->uriFor($actionName, $arguments, $controllerName, $extensionName);
817
        $this->redirectToUri($uri, $delay, $statusCode);
818
    }
819
820
    /**
821
     * Redirects the web request to another uri.
822
     *
823
     * NOTE: This method only supports web requests and will thrown an exception if used with other request types.
824
     *
825
     * @param mixed $uri A string representation of a URI
826
     * @param int $delay (optional) The delay in seconds. Default is no delay.
827
     * @param int $statusCode (optional) The HTTP status code for the redirect. Default is "303 See Other
828
     * @throws StopActionException
829
     */
830
    protected function redirectToUri($uri, $delay = 0, $statusCode = 303)
831
    {
832
        $this->objectManager->get(\TYPO3\CMS\Extbase\Service\CacheService::class)->clearCachesOfRegisteredPageIds();
833
834
        $uri = $this->addBaseUriIfNecessary($uri);
835
        $escapedUri = htmlentities($uri, ENT_QUOTES, 'utf-8');
836
        $this->response->setContent('<html><head><meta http-equiv="refresh" content="' . (int)$delay . ';url=' . $escapedUri . '"/></head></html>');
837
        $this->response->setStatus($statusCode);
838
        $this->response->setHeader('Location', (string)$uri);
839
840
        // Avoid caching the plugin when we issue a redirect response
841
        // This means that even when an action is configured as cachable
842
        // we avoid the plugin to be cached, but keep the page cache untouched
843
        $contentObject = $this->configurationManager->getContentObject();
844
        if ($contentObject->getUserObjectType() === \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::OBJECTTYPE_USER) {
0 ignored issues
show
introduced by
The condition $contentObject->getUserO...nderer::OBJECTTYPE_USER is always false.
Loading history...
845
            $contentObject->convertToUserIntObject();
846
        }
847
848
        throw new StopActionException('redirectToUri', 1476045828);
849
    }
850
851
    /**
852
     * Adds the base uri if not already in place.
853
     *
854
     * @param string $uri The URI
855
     * @return string
856
     */
857
    protected function addBaseUriIfNecessary($uri)
858
    {
859
        return \TYPO3\CMS\Core\Utility\GeneralUtility::locationHeaderUrl((string)$uri);
860
    }
861
862
    /**
863
     * Sends the specified HTTP status immediately.
864
     *
865
     * NOTE: This method only supports web requests and will thrown an exception if used with other request types.
866
     *
867
     * @param int $statusCode The HTTP status code
868
     * @param string $statusMessage A custom HTTP status message
869
     * @param string $content Body content which further explains the status
870
     * @throws StopActionException
871
     */
872
    public function throwStatus($statusCode, $statusMessage = null, $content = null)
873
    {
874
        $this->response->setStatus($statusCode, $statusMessage);
875
        if ($content === null) {
876
            $content = $this->response->getStatus();
877
        }
878
        $this->response->setContent($content);
879
        throw new StopActionException('throwStatus', 1476045871);
880
    }
881
882
    /**
883
     * Collects the base validators which were defined for the data type of each
884
     * controller argument and adds them to the argument's validator chain.
885
     */
886
    public function initializeControllerArgumentsBaseValidators()
887
    {
888
        /** @var \TYPO3\CMS\Extbase\Mvc\Controller\Argument $argument */
889
        foreach ($this->arguments as $argument) {
890
            $validator = $this->validatorResolver->getBaseValidatorConjunction($argument->getDataType());
891
            if ($validator !== null) {
892
                $argument->setValidator($validator);
893
            }
894
        }
895
    }
896
897
    /**
898
     * Maps arguments delivered by the request object to the local controller arguments.
899
     *
900
     * @throws Exception\RequiredArgumentMissingException
901
     */
902
    protected function mapRequestArgumentsToControllerArguments()
903
    {
904
        /** @var \TYPO3\CMS\Extbase\Mvc\Controller\Argument $argument */
905
        foreach ($this->arguments as $argument) {
906
            $argumentName = $argument->getName();
907
            if ($this->request->hasArgument($argumentName)) {
908
                $argument->setValue($this->request->getArgument($argumentName));
909
            } elseif ($argument->isRequired()) {
910
                throw new \TYPO3\CMS\Extbase\Mvc\Controller\Exception\RequiredArgumentMissingException('Required argument "' . $argumentName . '" is not set for ' . $this->request->getControllerObjectName() . '->' . $this->request->getControllerActionName() . '.', 1298012500);
911
            }
912
        }
913
    }
914
}
915