Passed
Push — master ( 14805b...8eb528 )
by
unknown
14:18
created

ActionController::forwardToReferringRequest()   B

Complexity

Conditions 8
Paths 9

Size

Total Lines 37
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 25
dl 0
loc 37
rs 8.4444
c 0
b 0
f 0
cc 8
nc 9
nop 0
1
<?php
2
3
/*
4
 * This file is part of the TYPO3 CMS project.
5
 *
6
 * It is free software; you can redistribute it and/or modify it under
7
 * the terms of the GNU General Public License, either version 2
8
 * of the License, or any later version.
9
 *
10
 * For the full copyright and license information, please read the
11
 * LICENSE.txt file that was distributed with this source code.
12
 *
13
 * The TYPO3 project - inspiring people to share!
14
 */
15
16
namespace TYPO3\CMS\Extbase\Mvc\Controller;
17
18
use Psr\EventDispatcher\EventDispatcherInterface;
19
use Psr\Http\Message\ResponseFactoryInterface;
20
use Psr\Http\Message\ResponseInterface;
21
use TYPO3\CMS\Core\Http\HtmlResponse;
22
use TYPO3\CMS\Core\Http\PropagateResponseException;
23
use TYPO3\CMS\Core\Http\RedirectResponse;
24
use TYPO3\CMS\Core\Http\Response;
25
use TYPO3\CMS\Core\Http\Stream;
26
use TYPO3\CMS\Core\Messaging\AbstractMessage;
27
use TYPO3\CMS\Core\Messaging\FlashMessage;
28
use TYPO3\CMS\Core\Messaging\FlashMessageQueue;
29
use TYPO3\CMS\Core\Messaging\FlashMessageService;
30
use TYPO3\CMS\Core\Page\PageRenderer;
31
use TYPO3\CMS\Core\Utility\GeneralUtility;
32
use TYPO3\CMS\Core\Utility\MathUtility;
33
use TYPO3\CMS\Core\Utility\StringUtility;
34
use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface;
35
use TYPO3\CMS\Extbase\Event\Mvc\BeforeActionCallEvent;
36
use TYPO3\CMS\Extbase\Http\ForwardResponse;
37
use TYPO3\CMS\Extbase\Mvc\Controller\Exception\RequiredArgumentMissingException;
38
use TYPO3\CMS\Extbase\Mvc\Exception\InvalidArgumentNameException;
39
use TYPO3\CMS\Extbase\Mvc\Exception\InvalidArgumentTypeException;
40
use TYPO3\CMS\Extbase\Mvc\Exception\NoSuchActionException;
41
use TYPO3\CMS\Extbase\Mvc\Exception\StopActionException;
42
use TYPO3\CMS\Extbase\Mvc\RequestInterface;
43
use TYPO3\CMS\Extbase\Mvc\View\GenericViewResolver;
44
use TYPO3\CMS\Extbase\Mvc\View\JsonView;
45
use TYPO3\CMS\Extbase\Mvc\View\NotFoundView;
46
use TYPO3\CMS\Extbase\Mvc\View\ViewInterface;
47
use TYPO3\CMS\Extbase\Mvc\View\ViewResolverInterface;
48
use TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder;
49
use TYPO3\CMS\Extbase\Object\ObjectManagerInterface;
50
use TYPO3\CMS\Extbase\Property\Exception\TargetNotFoundException;
51
use TYPO3\CMS\Extbase\Property\PropertyMapper;
52
use TYPO3\CMS\Extbase\Reflection\ReflectionService;
53
use TYPO3\CMS\Extbase\Security\Cryptography\HashService;
54
use TYPO3\CMS\Extbase\Service\ExtensionService;
55
use TYPO3\CMS\Extbase\SignalSlot\Dispatcher;
56
use TYPO3\CMS\Extbase\Validation\Validator\ConjunctionValidator;
57
use TYPO3\CMS\Extbase\Validation\Validator\ValidatorInterface;
58
use TYPO3\CMS\Extbase\Validation\ValidatorResolver;
59
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
60
use TYPO3Fluid\Fluid\View\TemplateView;
61
62
/**
63
 * A multi action controller. This is by far the most common base class for Controllers.
64
 */
65
abstract class ActionController implements ControllerInterface
66
{
67
    /**
68
     * @var ResponseFactoryInterface
69
     */
70
    protected $responseFactory;
71
72
    /**
73
     * @var \TYPO3\CMS\Extbase\Reflection\ReflectionService
74
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
75
     */
76
    protected $reflectionService;
77
78
    /**
79
     * @var HashService
80
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
81
     */
82
    protected $hashService;
83
84
    /**
85
     * @var ViewResolverInterface
86
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
87
     */
88
    private $viewResolver;
89
90
    /**
91
     * The current view, as resolved by resolveView()
92
     *
93
     * @var ViewInterface
94
     */
95
    protected $view;
96
97
    /**
98
     * The default view object to use if none of the resolved views can render
99
     * a response for the current request.
100
     *
101
     * @var string
102
     */
103
    protected $defaultViewObjectName = \TYPO3\CMS\Fluid\View\TemplateView::class;
104
105
    /**
106
     * Name of the action method
107
     *
108
     * @var string
109
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
110
     */
111
    protected $actionMethodName = 'indexAction';
112
113
    /**
114
     * Name of the special error action method which is called in case of errors
115
     *
116
     * @var string
117
     */
118
    protected $errorMethodName = 'errorAction';
119
120
    /**
121
     * @var \TYPO3\CMS\Extbase\Mvc\Controller\MvcPropertyMappingConfigurationService
122
     */
123
    protected $mvcPropertyMappingConfigurationService;
124
125
    /**
126
     * @var EventDispatcherInterface
127
     */
128
    protected $eventDispatcher;
129
130
    /**
131
     * The current request.
132
     *
133
     * @var RequestInterface
134
     */
135
    protected $request;
136
137
    /**
138
     * @var \TYPO3\CMS\Extbase\SignalSlot\Dispatcher
139
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
140
     */
141
    protected $signalSlotDispatcher;
142
143
    /**
144
     * @var \TYPO3\CMS\Extbase\Object\ObjectManagerInterface
145
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
146
     */
147
    protected $objectManager;
148
149
    /**
150
     * @var \TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder
151
     */
152
    protected $uriBuilder;
153
154
    /**
155
     * Contains the settings of the current extension
156
     *
157
     * @var array
158
     */
159
    protected $settings;
160
161
    /**
162
     * @var \TYPO3\CMS\Extbase\Validation\ValidatorResolver
163
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
164
     */
165
    protected $validatorResolver;
166
167
    /**
168
     * @var \TYPO3\CMS\Extbase\Mvc\Controller\Arguments Arguments passed to the controller
169
     */
170
    protected $arguments;
171
172
    /**
173
     * @var \TYPO3\CMS\Extbase\Mvc\Controller\ControllerContext
174
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
175
     */
176
    protected $controllerContext;
177
178
    /**
179
     * @var ConfigurationManagerInterface
180
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
181
     */
182
    protected $configurationManager;
183
184
    /**
185
     * @var PropertyMapper
186
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
187
     */
188
    private $propertyMapper;
189
190
    /**
191
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
192
     */
193
    private FlashMessageService $internalFlashMessageService;
194
195
    /**
196
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
197
     */
198
    private ExtensionService $internalExtensionService;
199
200
    final public function injectResponseFactory(ResponseFactoryInterface $responseFactory)
201
    {
202
        $this->responseFactory = $responseFactory;
203
    }
204
205
    /**
206
     * @param ConfigurationManagerInterface $configurationManager
207
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
208
     */
209
    public function injectConfigurationManager(ConfigurationManagerInterface $configurationManager)
210
    {
211
        $this->configurationManager = $configurationManager;
212
        $this->settings = $this->configurationManager->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_SETTINGS) + ['offlineMode' => false];
213
    }
214
215
    /**
216
     * Injects the object manager
217
     *
218
     * @param \TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager
219
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
220
     */
221
    public function injectObjectManager(ObjectManagerInterface $objectManager)
222
    {
223
        $this->objectManager = $objectManager;
224
        $this->arguments = GeneralUtility::makeInstance(Arguments::class);
225
    }
226
227
    /**
228
     * @param \TYPO3\CMS\Extbase\SignalSlot\Dispatcher $signalSlotDispatcher
229
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
230
     */
231
    public function injectSignalSlotDispatcher(Dispatcher $signalSlotDispatcher)
232
    {
233
        $this->signalSlotDispatcher = $signalSlotDispatcher;
234
    }
235
236
    /**
237
     * @param \TYPO3\CMS\Extbase\Validation\ValidatorResolver $validatorResolver
238
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
239
     */
240
    public function injectValidatorResolver(ValidatorResolver $validatorResolver)
241
    {
242
        $this->validatorResolver = $validatorResolver;
243
    }
244
245
    /**
246
     * @param ViewResolverInterface $viewResolver
247
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
248
     */
249
    public function injectViewResolver(ViewResolverInterface $viewResolver)
250
    {
251
        $this->viewResolver = $viewResolver;
252
    }
253
254
    /**
255
     * @param \TYPO3\CMS\Extbase\Reflection\ReflectionService $reflectionService
256
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
257
     */
258
    public function injectReflectionService(ReflectionService $reflectionService)
259
    {
260
        $this->reflectionService = $reflectionService;
261
    }
262
263
    /**
264
     * @param HashService $hashService
265
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
266
     */
267
    public function injectHashService(HashService $hashService)
268
    {
269
        $this->hashService = $hashService;
270
    }
271
272
    /**
273
     * @param \TYPO3\CMS\Extbase\Mvc\Controller\MvcPropertyMappingConfigurationService $mvcPropertyMappingConfigurationService
274
     */
275
    public function injectMvcPropertyMappingConfigurationService(MvcPropertyMappingConfigurationService $mvcPropertyMappingConfigurationService)
276
    {
277
        $this->mvcPropertyMappingConfigurationService = $mvcPropertyMappingConfigurationService;
278
    }
279
280
    public function injectEventDispatcher(EventDispatcherInterface $eventDispatcher): void
281
    {
282
        $this->eventDispatcher = $eventDispatcher;
283
    }
284
285
    /**
286
     * @param PropertyMapper $propertyMapper
287
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
288
     */
289
    public function injectPropertyMapper(PropertyMapper $propertyMapper): void
290
    {
291
        $this->propertyMapper = $propertyMapper;
292
    }
293
294
    /**
295
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
296
     */
297
    final public function injectInternalFlashMessageService(FlashMessageService $flashMessageService): void
298
    {
299
        $this->internalFlashMessageService = $flashMessageService;
300
    }
301
302
    /**
303
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
304
     */
305
    final public function injectInternalExtensionService(ExtensionService $extensionService): void
306
    {
307
        $this->internalExtensionService = $extensionService;
308
    }
309
310
    /**
311
     * Initializes the view before invoking an action method.
312
     *
313
     * Override this method to solve assign variables common for all actions
314
     * or prepare the view in another way before the action is called.
315
     *
316
     * @param ViewInterface $view The view to be initialized
317
     */
318
    protected function initializeView(ViewInterface $view)
319
    {
320
    }
321
322
    /**
323
     * Initializes the controller before invoking an action method.
324
     *
325
     * Override this method to solve tasks which all actions have in
326
     * common.
327
     */
328
    protected function initializeAction()
329
    {
330
    }
331
332
    /**
333
     * Implementation of the arguments initialization in the action controller:
334
     * Automatically registers arguments of the current action
335
     *
336
     * Don't override this method - use initializeAction() instead.
337
     *
338
     * @throws \TYPO3\CMS\Extbase\Mvc\Exception\InvalidArgumentTypeException
339
     * @see initializeArguments()
340
     *
341
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
342
     */
343
    protected function initializeActionMethodArguments()
344
    {
345
        $methodParameters = $this->reflectionService
346
            ->getClassSchema(static::class)
347
            ->getMethod($this->actionMethodName)->getParameters();
348
349
        foreach ($methodParameters as $parameterName => $parameter) {
350
            $dataType = null;
351
            if ($parameter->getType() !== null) {
352
                $dataType = $parameter->getType();
353
            } elseif ($parameter->isArray()) {
354
                $dataType = 'array';
355
            }
356
            if ($dataType === null) {
357
                throw new InvalidArgumentTypeException('The argument type for parameter $' . $parameterName . ' of method ' . static::class . '->' . $this->actionMethodName . '() could not be detected.', 1253175643);
358
            }
359
            $defaultValue = $parameter->hasDefaultValue() ? $parameter->getDefaultValue() : null;
360
            $this->arguments->addNewArgument($parameterName, $dataType, !$parameter->isOptional(), $defaultValue);
361
        }
362
    }
363
364
    /**
365
     * Adds the needed validators to the Arguments:
366
     *
367
     * - Validators checking the data type from the @param annotation
368
     * - Custom validators specified with validate annotations.
369
     * - Model-based validators (validate annotations in the model)
370
     * - Custom model validator classes
371
     *
372
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
373
     */
374
    protected function initializeActionMethodValidators()
375
    {
376
        if ($this->arguments->count() === 0) {
377
            return;
378
        }
379
380
        $classSchemaMethod = $this->reflectionService->getClassSchema(static::class)
381
            ->getMethod($this->actionMethodName);
382
383
        /** @var \TYPO3\CMS\Extbase\Mvc\Controller\Argument $argument */
384
        foreach ($this->arguments as $argument) {
385
            $classSchemaMethodParameter = $classSchemaMethod->getParameter($argument->getName());
386
            /*
387
             * At this point validation is skipped if there is an IgnoreValidation annotation.
388
             *
389
             * todo: IgnoreValidation annotations could be evaluated in the ClassSchema and result in
390
             * todo: no validators being applied to the method parameter.
391
             */
392
            if ($classSchemaMethodParameter->ignoreValidation()) {
393
                continue;
394
            }
395
396
            // todo: It's quite odd that an instance of ConjunctionValidator is created directly here.
397
            // todo: \TYPO3\CMS\Extbase\Validation\ValidatorResolver::getBaseValidatorConjunction could/should be used
398
            // todo: here, to benefit of the built in 1st level cache of the ValidatorResolver.
399
            $validator = $this->objectManager->get(ConjunctionValidator::class);
400
401
            foreach ($classSchemaMethodParameter->getValidators() as $validatorDefinition) {
402
                /** @var ValidatorInterface $validatorInstance */
403
                $validatorInstance = $this->objectManager->get(
404
                    $validatorDefinition['className'],
405
                    $validatorDefinition['options']
406
                );
407
408
                $validator->addValidator(
409
                    $validatorInstance
410
                );
411
            }
412
413
            $baseValidatorConjunction = $this->validatorResolver->getBaseValidatorConjunction($argument->getDataType());
414
            if ($baseValidatorConjunction->count() > 0) {
415
                $validator->addValidator($baseValidatorConjunction);
416
            }
417
            $argument->setValidator($validator);
418
        }
419
    }
420
421
    /**
422
     * Collects the base validators which were defined for the data type of each
423
     * controller argument and adds them to the argument's validator chain.
424
     *
425
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
426
     */
427
    public function initializeControllerArgumentsBaseValidators()
428
    {
429
        /** @var \TYPO3\CMS\Extbase\Mvc\Controller\Argument $argument */
430
        foreach ($this->arguments as $argument) {
431
            $validator = $this->validatorResolver->getBaseValidatorConjunction($argument->getDataType());
432
            if ($validator !== null) {
433
                $argument->setValidator($validator);
434
            }
435
        }
436
    }
437
438
    /**
439
     * Handles an incoming request and returns a response object
440
     *
441
     * @param \TYPO3\CMS\Extbase\Mvc\RequestInterface $request The request object
442
     * @return ResponseInterface
443
     *
444
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
445
     */
446
    public function processRequest(RequestInterface $request): ResponseInterface
447
    {
448
        $this->request = $request;
449
        $this->request->setDispatched(true);
450
        $this->uriBuilder = $this->objectManager->get(UriBuilder::class);
451
        $this->uriBuilder->setRequest($request);
452
        $this->actionMethodName = $this->resolveActionMethodName();
453
        $this->initializeActionMethodArguments();
454
        $this->initializeActionMethodValidators();
455
        $this->mvcPropertyMappingConfigurationService->initializePropertyMappingConfigurationFromRequest($request, $this->arguments);
456
        $this->initializeAction();
457
        $actionInitializationMethodName = 'initialize' . ucfirst($this->actionMethodName);
458
        /** @var callable $callable */
459
        $callable = [$this, $actionInitializationMethodName];
460
        if (is_callable($callable)) {
461
            $callable();
462
        }
463
        $this->mapRequestArgumentsToControllerArguments();
464
        $this->controllerContext = $this->buildControllerContext();
465
        $this->view = $this->resolveView();
466
        if ($this->view !== null) {
467
            $this->initializeView($this->view);
468
        }
469
        $response = $this->callActionMethod($request);
470
        $this->renderAssetsForRequest($request);
471
472
        return $response;
473
    }
474
475
    /**
476
     * Method which initializes assets that should be attached to the response
477
     * for the given $request, which contains parameters that an override can
478
     * use to determine which assets to add via PageRenderer.
479
     *
480
     * This default implementation will attempt to render the sections "HeaderAssets"
481
     * and "FooterAssets" from the template that is being rendered, inserting the
482
     * rendered content into either page header or footer, as appropriate. Both
483
     * sections are optional and can be used one or both in combination.
484
     *
485
     * You can add assets with this method without worrying about duplicates, if
486
     * for example you do this in a plugin that gets used multiple time on a page.
487
     *
488
     * @param \TYPO3\CMS\Extbase\Mvc\RequestInterface $request
489
     *
490
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
491
     */
492
    protected function renderAssetsForRequest($request)
493
    {
494
        if (!$this->view instanceof TemplateView) {
495
            // Only TemplateView (from Fluid engine, so this includes all TYPO3 Views based
496
            // on TYPO3's AbstractTemplateView) supports renderSection(). The method is not
497
            // declared on ViewInterface - so we must assert a specific class. We silently skip
498
            // asset processing if the View doesn't match, so we don't risk breaking custom Views.
499
            return;
500
        }
501
        $pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
502
        $variables = ['request' => $request, 'arguments' => $this->arguments];
503
        $headerAssets = $this->view->renderSection('HeaderAssets', $variables, true);
504
        $footerAssets = $this->view->renderSection('FooterAssets', $variables, true);
505
        if (!empty(trim($headerAssets))) {
506
            $pageRenderer->addHeaderData($headerAssets);
507
        }
508
        if (!empty(trim($footerAssets))) {
509
            $pageRenderer->addFooterData($footerAssets);
510
        }
511
    }
512
513
    /**
514
     * Resolves and checks the current action method name
515
     *
516
     * @return string Method name of the current action
517
     * @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).
518
     *
519
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
520
     */
521
    protected function resolveActionMethodName()
522
    {
523
        $actionMethodName = $this->request->getControllerActionName() . 'Action';
0 ignored issues
show
Bug introduced by
The method getControllerActionName() does not exist on TYPO3\CMS\Extbase\Mvc\RequestInterface. Did you maybe mean getControllerObjectName()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

523
        $actionMethodName = $this->request->/** @scrutinizer ignore-call */ getControllerActionName() . 'Action';

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
524
        if (!method_exists($this, $actionMethodName)) {
525
            throw new NoSuchActionException('An action "' . $actionMethodName . '" does not exist in controller "' . static::class . '".', 1186669086);
526
        }
527
        return $actionMethodName;
528
    }
529
530
    /**
531
     * Calls the specified action method and passes the arguments.
532
     *
533
     * If the action returns a string, it is appended to the content in the
534
     * response object. If the action doesn't return anything and a valid
535
     * view exists, the view is rendered automatically.
536
     *
537
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
538
     */
539
    protected function callActionMethod(RequestInterface $request): ResponseInterface
540
    {
541
        // incoming request is not needed yet but can be passed into the action in the future like in symfony
542
        // todo: support this via method-reflection
543
544
        $preparedArguments = [];
545
        /** @var \TYPO3\CMS\Extbase\Mvc\Controller\Argument $argument */
546
        foreach ($this->arguments as $argument) {
547
            $preparedArguments[] = $argument->getValue();
548
        }
549
        $validationResult = $this->arguments->validate();
550
        if (!$validationResult->hasErrors()) {
551
            $this->eventDispatcher->dispatch(new BeforeActionCallEvent(static::class, $this->actionMethodName, $preparedArguments));
552
            $actionResult = $this->{$this->actionMethodName}(...$preparedArguments);
553
        } else {
554
            $actionResult = $this->{$this->errorMethodName}();
555
        }
556
557
        if ($actionResult instanceof ResponseInterface) {
558
            return $actionResult;
559
        }
560
561
        trigger_error(
562
            sprintf(
563
                'Controller action %s does not return an instance of %s which is deprecated.',
564
                static::class . '::' . $this->actionMethodName,
565
                ResponseInterface::class
566
            ),
567
            E_USER_DEPRECATED
568
        );
569
570
        $response = new Response();
571
        $body = new Stream('php://temp', 'rw');
572
        if ($actionResult === null && $this->view instanceof ViewInterface) {
573
            if ($this->view instanceof JsonView) {
574
                // this is just a temporary solution until Extbase uses PSR-7 responses and users are forced to return a
575
                // response object in their controller actions.
576
577
                if (!empty($GLOBALS['TSFE']) && $GLOBALS['TSFE'] instanceof TypoScriptFrontendController) {
578
                    /** @var TypoScriptFrontendController $typoScriptFrontendController */
579
                    $typoScriptFrontendController = $GLOBALS['TSFE'];
580
                    if (empty($typoScriptFrontendController->config['config']['disableCharsetHeader'])) {
581
                        // If the charset header is *not* disabled in configuration,
582
                        // TypoScriptFrontendController will send the header later with the Content-Type which we set here.
583
                        $typoScriptFrontendController->setContentType('application/json');
584
                    } else {
585
                        // Although the charset header is disabled in configuration, we *must* send a Content-Type header here.
586
                        // Content-Type headers optionally carry charset information at the same time.
587
                        // Since we have the information about the charset, there is no reason to not include the charset information although disabled in TypoScript.
588
                        $response = $response->withHeader('Content-Type', 'application/json; charset=' . trim($typoScriptFrontendController->metaCharset));
589
                    }
590
                } else {
591
                    $response = $response->withHeader('Content-Type', 'application/json');
592
                }
593
            }
594
595
            $body->write($this->view->render());
596
        } elseif (is_string($actionResult) && $actionResult !== '') {
597
            $body->write($actionResult);
598
        } elseif (is_object($actionResult) && method_exists($actionResult, '__toString')) {
599
            $body->write((string)$actionResult);
600
        }
601
602
        $body->rewind();
603
        return $response->withBody($body);
604
    }
605
606
    /**
607
     * Prepares a view for the current action.
608
     * By default, this method tries to locate a view with a name matching the current action.
609
     *
610
     * @return ViewInterface
611
     *
612
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
613
     */
614
    protected function resolveView()
615
    {
616
        if ($this->viewResolver instanceof GenericViewResolver) {
617
            /*
618
             * This setter is not part of the ViewResolverInterface as it's only necessary to set
619
             * the default view class from this point when using the generic view resolver which
620
             * must respect the possibly overridden property defaultViewObjectName.
621
             */
622
            $this->viewResolver->setDefaultViewClass($this->defaultViewObjectName);
623
        }
624
625
        $view = $this->viewResolver->resolve(
626
            $this->request->getControllerObjectName(),
627
            $this->request->getControllerActionName(),
628
            $this->request->getFormat()
0 ignored issues
show
Bug introduced by
The method getFormat() does not exist on TYPO3\CMS\Extbase\Mvc\RequestInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to TYPO3\CMS\Extbase\Mvc\RequestInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

628
            $this->request->/** @scrutinizer ignore-call */ 
629
                            getFormat()
Loading history...
629
        );
630
631
        if ($view instanceof ViewInterface) {
0 ignored issues
show
introduced by
$view is always a sub-type of TYPO3\CMS\Extbase\Mvc\View\ViewInterface.
Loading history...
632
            $this->setViewConfiguration($view);
633
            if ($view->canRender($this->controllerContext) === false) {
634
                $view = null;
635
            }
636
        }
637
        if (!isset($view)) {
638
            $view = $this->objectManager->get(NotFoundView::class);
639
            $view->assign('errorMessage', 'No template was found. View could not be resolved for action "'
640
                . $this->request->getControllerActionName() . '" in class "' . $this->request->getControllerObjectName() . '"');
641
        }
642
        $view->setControllerContext($this->controllerContext);
643
        if (method_exists($view, 'injectSettings')) {
644
            $view->injectSettings($this->settings);
645
        }
646
        $view->initializeView();
647
        // In TYPO3.Flow, solved through Object Lifecycle methods, we need to call it explicitly
648
        $view->assign('settings', $this->settings);
649
        // same with settings injection.
650
        return $view;
651
    }
652
653
    /**
654
     * @param ViewInterface $view
655
     *
656
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
657
     */
658
    protected function setViewConfiguration(ViewInterface $view)
659
    {
660
        // Template Path Override
661
        $extbaseFrameworkConfiguration = $this->configurationManager->getConfiguration(
662
            ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK
663
        );
664
665
        // set TemplateRootPaths
666
        $viewFunctionName = 'setTemplateRootPaths';
667
        if (method_exists($view, $viewFunctionName)) {
668
            $setting = 'templateRootPaths';
669
            $parameter = $this->getViewProperty($extbaseFrameworkConfiguration, $setting);
670
            // no need to bother if there is nothing to set
671
            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...
672
                $view->$viewFunctionName($parameter);
673
            }
674
        }
675
676
        // set LayoutRootPaths
677
        $viewFunctionName = 'setLayoutRootPaths';
678
        if (method_exists($view, $viewFunctionName)) {
679
            $setting = 'layoutRootPaths';
680
            $parameter = $this->getViewProperty($extbaseFrameworkConfiguration, $setting);
681
            // no need to bother if there is nothing to set
682
            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...
683
                $view->$viewFunctionName($parameter);
684
            }
685
        }
686
687
        // set PartialRootPaths
688
        $viewFunctionName = 'setPartialRootPaths';
689
        if (method_exists($view, $viewFunctionName)) {
690
            $setting = 'partialRootPaths';
691
            $parameter = $this->getViewProperty($extbaseFrameworkConfiguration, $setting);
692
            // no need to bother if there is nothing to set
693
            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...
694
                $view->$viewFunctionName($parameter);
695
            }
696
        }
697
    }
698
699
    /**
700
     * Handles the path resolving for *rootPath(s)
701
     *
702
     * numerical arrays get ordered by key ascending
703
     *
704
     * @param array $extbaseFrameworkConfiguration
705
     * @param string $setting parameter name from TypoScript
706
     *
707
     * @return array
708
     *
709
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
710
     */
711
    protected function getViewProperty($extbaseFrameworkConfiguration, $setting)
712
    {
713
        $values = [];
714
        if (
715
            !empty($extbaseFrameworkConfiguration['view'][$setting])
716
            && is_array($extbaseFrameworkConfiguration['view'][$setting])
717
        ) {
718
            $values = $extbaseFrameworkConfiguration['view'][$setting];
719
        }
720
721
        return $values;
722
    }
723
724
    /**
725
     * A special action which is called if the originally intended action could
726
     * not be called, for example if the arguments were not valid.
727
     *
728
     * The default implementation sets a flash message, request errors and forwards back
729
     * to the originating action. This is suitable for most actions dealing with form input.
730
     *
731
     * We clear the page cache by default on an error as well, as we need to make sure the
732
     * data is re-evaluated when the user changes something.
733
     *
734
     * @return ResponseInterface
735
     */
736
    protected function errorAction()
737
    {
738
        $this->addErrorFlashMessage();
739
        if (($response = $this->forwardToReferringRequest()) !== null) {
740
            return $response->withStatus(400);
741
        }
742
743
        $response = $this->htmlResponse($this->getFlattenedValidationErrorMessage());
744
        return $response->withStatus(400);
745
    }
746
747
    /**
748
     * If an error occurred during this request, this adds a flash message describing the error to the flash
749
     * message container.
750
     *
751
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
752
     */
753
    protected function addErrorFlashMessage()
754
    {
755
        $errorFlashMessage = $this->getErrorFlashMessage();
756
        if ($errorFlashMessage !== false) {
0 ignored issues
show
introduced by
The condition $errorFlashMessage !== false is always true.
Loading history...
757
            $this->addFlashMessage($errorFlashMessage, '', FlashMessage::ERROR);
758
        }
759
    }
760
761
    /**
762
     * A template method for displaying custom error flash messages, or to
763
     * display no flash message at all on errors. Override this to customize
764
     * the flash message in your action controller.
765
     *
766
     * @return string The flash message or FALSE if no flash message should be set
767
     *
768
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
769
     */
770
    protected function getErrorFlashMessage()
771
    {
772
        return 'An error occurred while trying to call ' . static::class . '->' . $this->actionMethodName . '()';
773
    }
774
775
    /**
776
     * If information on the request before the current request was sent, this method forwards back
777
     * to the originating request. This effectively ends processing of the current request, so do not
778
     * call this method before you have finished the necessary business logic!
779
     *
780
     * @return ResponseInterface|null
781
     *
782
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
783
     */
784
    protected function forwardToReferringRequest(): ?ResponseInterface
785
    {
786
        $referringRequest = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $referringRequest is dead and can be removed.
Loading history...
787
        $referringRequestArguments = $this->request->getInternalArguments()['__referrer'] ?? null;
0 ignored issues
show
Bug introduced by
The method getInternalArguments() does not exist on TYPO3\CMS\Extbase\Mvc\RequestInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to TYPO3\CMS\Extbase\Mvc\RequestInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

787
        $referringRequestArguments = $this->request->/** @scrutinizer ignore-call */ getInternalArguments()['__referrer'] ?? null;
Loading history...
788
        if (is_string($referringRequestArguments['@request'] ?? null)) {
789
            $referrerArray = json_decode(
790
                $this->hashService->validateAndStripHmac($referringRequestArguments['@request']),
791
                true
792
            );
793
            $arguments = [];
794
            if (is_string($referringRequestArguments['arguments'] ?? null)) {
795
                $arguments = unserialize(
796
                    base64_decode($this->hashService->validateAndStripHmac($referringRequestArguments['arguments']))
797
                );
798
            }
799
            $replacedArguments = array_replace_recursive($arguments, $referrerArray);
800
            $nonExtbaseBaseArguments = [];
801
            foreach ($replacedArguments as $argumentName => $argumentValue) {
802
                if (!is_string($argumentName) || $argumentName === '') {
803
                    throw new InvalidArgumentNameException('Invalid argument name.', 1623940985);
804
                }
805
                if (StringUtility::beginsWith($argumentName, '__')
806
                    || in_array($argumentName, ['@extension', '@subpackage', '@controller', '@action', '@format'], true)
807
                ) {
808
                    // Don't handle internalArguments here, not needed for forwardResponse()
809
                    continue;
810
                }
811
                $nonExtbaseBaseArguments[$argumentName] = $argumentValue;
812
            }
813
            return (new ForwardResponse((string)($replacedArguments['@action'] ?? 'index')))
814
                ->withControllerName((string)($replacedArguments['@controller'] ?? 'Standard'))
815
                ->withExtensionName((string)($replacedArguments['@extension'] ?? ''))
816
                ->withArguments($nonExtbaseBaseArguments)
817
                ->withArgumentsValidationResult($this->arguments->validate());
818
        }
819
820
        return null;
821
    }
822
823
    /**
824
     * Returns a string with a basic error message about validation failure.
825
     * We may add all validation error messages to a log file in the future,
826
     * but for security reasons (@see #54074) we do not return these here.
827
     *
828
     * @return string
829
     *
830
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
831
     */
832
    protected function getFlattenedValidationErrorMessage()
833
    {
834
        $outputMessage = 'Validation failed while trying to call ' . static::class . '->' . $this->actionMethodName . '().' . PHP_EOL;
835
        return $outputMessage;
836
    }
837
838
    /**
839
     * @return ControllerContext
840
     */
841
    public function getControllerContext()
842
    {
843
        return $this->controllerContext;
844
    }
845
846
    /**
847
     * Creates a Message object and adds it to the FlashMessageQueue.
848
     *
849
     * @param string $messageBody The message
850
     * @param string $messageTitle Optional message title
851
     * @param int $severity Optional severity, must be one of \TYPO3\CMS\Core\Messaging\FlashMessage constants
852
     * @param bool $storeInSession Optional, defines whether the message should be stored in the session (default) or not
853
     * @throws \InvalidArgumentException if the message body is no string
854
     * @see \TYPO3\CMS\Core\Messaging\FlashMessage
855
     */
856
    public function addFlashMessage($messageBody, $messageTitle = '', $severity = AbstractMessage::OK, $storeInSession = true)
857
    {
858
        if (!is_string($messageBody)) {
0 ignored issues
show
introduced by
The condition is_string($messageBody) is always true.
Loading history...
859
            throw new \InvalidArgumentException('The message body must be of type string, "' . gettype($messageBody) . '" given.', 1243258395);
860
        }
861
        /* @var \TYPO3\CMS\Core\Messaging\FlashMessage $flashMessage */
862
        $flashMessage = GeneralUtility::makeInstance(
863
            FlashMessage::class,
864
            (string)$messageBody,
865
            (string)$messageTitle,
866
            $severity,
867
            $storeInSession
868
        );
869
870
        $this->getFlashMessageQueue()->enqueue($flashMessage);
871
    }
872
873
    /**
874
     * todo: As soon as the incoming request contains the compiled plugin namespace, extbase will offer a trait to
875
     *       create a flash message identifier from the current request. Users then should inject the flash message
876
     *       service themselves if needed.
877
     *
878
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
879
     */
880
    protected function getFlashMessageQueue(string $identifier = null): FlashMessageQueue
881
    {
882
        if ($identifier === null) {
883
            $pluginNamespace = $this->internalExtensionService->getPluginNamespace(
884
                $this->request->getControllerExtensionName(),
0 ignored issues
show
Bug introduced by
The method getControllerExtensionName() does not exist on TYPO3\CMS\Extbase\Mvc\RequestInterface. Did you maybe mean getControllerObjectName()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

884
                $this->request->/** @scrutinizer ignore-call */ 
885
                                getControllerExtensionName(),

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
885
                $this->request->getPluginName()
0 ignored issues
show
Bug introduced by
The method getPluginName() does not exist on TYPO3\CMS\Extbase\Mvc\RequestInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to TYPO3\CMS\Extbase\Mvc\RequestInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

885
                $this->request->/** @scrutinizer ignore-call */ 
886
                                getPluginName()
Loading history...
886
            );
887
            $identifier = 'extbase.flashmessages.' . $pluginNamespace;
888
        }
889
890
        return $this->internalFlashMessageService->getMessageQueueByIdentifier($identifier);
891
    }
892
893
    /**
894
     * Initialize the controller context
895
     *
896
     * @return \TYPO3\CMS\Extbase\Mvc\Controller\ControllerContext ControllerContext to be passed to the view
897
     *
898
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
899
     */
900
    protected function buildControllerContext()
901
    {
902
        /** @var \TYPO3\CMS\Extbase\Mvc\Controller\ControllerContext $controllerContext */
903
        $controllerContext = $this->objectManager->get(ControllerContext::class);
904
        $controllerContext->setRequest($this->request);
905
        if ($this->arguments !== null) {
906
            $controllerContext->setArguments($this->arguments);
907
        }
908
        $controllerContext->setUriBuilder($this->uriBuilder);
909
910
        return $controllerContext;
911
    }
912
913
    /**
914
     * Forwards the request to another action and / or controller.
915
     *
916
     * Request is directly transferred to the other action / controller
917
     * without the need for a new request.
918
     *
919
     * @param string $actionName Name of the action to forward to
920
     * @param string|null $controllerName Unqualified object name of the controller to forward to. If not specified, the current controller is used.
921
     * @param string|null $extensionName Name of the extension containing the controller to forward to. If not specified, the current extension is assumed.
922
     * @param array|null $arguments Arguments to pass to the target action
923
     * @throws StopActionException
924
     * @see redirect()
925
     * @deprecated since TYPO3 11.0, will be removed in 12.0
926
     */
927
    public function forward($actionName, $controllerName = null, $extensionName = null, array $arguments = null)
928
    {
929
        trigger_error(
930
            sprintf('Method %s is deprecated. To forward to another action, return a %s instead.', __METHOD__, ForwardResponse::class),
931
            E_USER_DEPRECATED
932
        );
933
934
        $this->request->setDispatched(false);
935
        $this->request->setControllerActionName($actionName);
0 ignored issues
show
Bug introduced by
The method setControllerActionName() does not exist on TYPO3\CMS\Extbase\Mvc\RequestInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to TYPO3\CMS\Extbase\Mvc\RequestInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

935
        $this->request->/** @scrutinizer ignore-call */ 
936
                        setControllerActionName($actionName);
Loading history...
936
937
        if ($controllerName !== null) {
938
            $this->request->setControllerName($controllerName);
0 ignored issues
show
Bug introduced by
The method setControllerName() does not exist on TYPO3\CMS\Extbase\Mvc\RequestInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to TYPO3\CMS\Extbase\Mvc\RequestInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

938
            $this->request->/** @scrutinizer ignore-call */ 
939
                            setControllerName($controllerName);
Loading history...
939
        }
940
941
        if ($extensionName !== null) {
942
            $this->request->setControllerExtensionName($extensionName);
0 ignored issues
show
Bug introduced by
The method setControllerExtensionName() does not exist on TYPO3\CMS\Extbase\Mvc\RequestInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to TYPO3\CMS\Extbase\Mvc\RequestInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

942
            $this->request->/** @scrutinizer ignore-call */ 
943
                            setControllerExtensionName($extensionName);
Loading history...
943
        }
944
945
        if ($arguments !== null) {
946
            $this->request->setArguments($arguments);
947
        }
948
        throw new StopActionException('forward', 1476045801);
949
    }
950
951
    /**
952
     * Redirects the request to another action and / or controller.
953
     *
954
     * Redirect will be sent to the client which then performs another request to the new URI.
955
     *
956
     * @param string $actionName Name of the action to forward to
957
     * @param string|null $controllerName Unqualified object name of the controller to forward to. If not specified, the current controller is used.
958
     * @param string|null $extensionName Name of the extension containing the controller to forward to. If not specified, the current extension is assumed.
959
     * @param array|null $arguments Arguments to pass to the target action
960
     * @param int|null $pageUid Target page uid. If NULL, the current page uid is used
961
     * @param null $_ (optional) Unused
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $_ is correct as it would always require null to be passed?
Loading history...
962
     * @param int $statusCode (optional) The HTTP status code for the redirect. Default is "303 See Other
963
     * @throws StopActionException deprecated since TYPO3 11.0, method will RETURN a Core\Http\RedirectResponse instead of throwing in v12
964
     * @todo: ': ResponseInterface' (without ?) in v12 as method return type together with redirectToUri() cleanup
965
     */
966
    protected function redirect($actionName, $controllerName = null, $extensionName = null, array $arguments = null, $pageUid = null, $_ = null, $statusCode = 303): void
967
    {
968
        if ($controllerName === null) {
969
            $controllerName = $this->request->getControllerName();
0 ignored issues
show
Bug introduced by
The method getControllerName() does not exist on TYPO3\CMS\Extbase\Mvc\RequestInterface. Did you maybe mean getControllerObjectName()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

969
            /** @scrutinizer ignore-call */ 
970
            $controllerName = $this->request->getControllerName();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
970
        }
971
        $this->uriBuilder->reset()->setCreateAbsoluteUri(true);
972
        if (MathUtility::canBeInterpretedAsInteger($pageUid)) {
973
            $this->uriBuilder->setTargetPageUid((int)$pageUid);
974
        }
975
        if (GeneralUtility::getIndpEnv('TYPO3_SSL')) {
976
            $this->uriBuilder->setAbsoluteUriScheme('https');
977
        }
978
        $uri = $this->uriBuilder->uriFor($actionName, $arguments, $controllerName, $extensionName);
979
        $this->redirectToUri($uri, null, $statusCode);
980
    }
981
982
    /**
983
     * Redirects the web request to another uri.
984
     *
985
     * @param mixed $uri A string representation of a URI
986
     * @param null $_ (optional) Unused
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $_ is correct as it would always require null to be passed?
Loading history...
987
     * @param int $statusCode (optional) The HTTP status code for the redirect. Default is "303 See Other"
988
     * @throws StopActionException deprecated since TYPO3 11.0, will be removed in 12.0
989
     * @todo: ': ResponseInterface' (without ?) in v12 as method return type together with redirectToUri() cleanup
990
     */
991
    protected function redirectToUri($uri, $_ = null, $statusCode = 303): void
992
    {
993
        $uri = $this->addBaseUriIfNecessary($uri);
994
        $response = new RedirectResponse($uri, $statusCode);
995
        // @deprecated since v11, will be removed in v12. RETURN the response instead. See Dispatcher class, too.
996
        throw new StopActionException('redirectToUri', 1476045828, null, $response);
997
    }
998
999
    /**
1000
     * Adds the base uri if not already in place.
1001
     *
1002
     * @param string $uri The URI
1003
     * @return string
1004
     *
1005
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
1006
     */
1007
    protected function addBaseUriIfNecessary($uri)
1008
    {
1009
        return GeneralUtility::locationHeaderUrl((string)$uri);
1010
    }
1011
1012
    /**
1013
     * Sends the specified HTTP status immediately and only stops to run back through the middleware stack.
1014
     * Note: If any other plugin or content or hook is used within a frontend request, this is skipped by design.
1015
     *
1016
     * @param int $statusCode The HTTP status code
1017
     * @param string $statusMessage A custom HTTP status message
1018
     * @param string $content Body content which further explains the status
1019
     * @throws PropagateResponseException
1020
     */
1021
    public function throwStatus($statusCode, $statusMessage = null, $content = null)
1022
    {
1023
        if ($content === null) {
1024
            $content = $statusCode . ' ' . $statusMessage;
1025
        }
1026
        $response = $this->responseFactory->createResponse((int)$statusCode, $statusMessage);
0 ignored issues
show
Bug introduced by
It seems like $statusMessage can also be of type null; however, parameter $reasonPhrase of Psr\Http\Message\Respons...rface::createResponse() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1026
        $response = $this->responseFactory->createResponse((int)$statusCode, /** @scrutinizer ignore-type */ $statusMessage);
Loading history...
1027
        $response->getBody()->write($content);
1028
        throw new PropagateResponseException($response, 1476045871);
1029
    }
1030
1031
    /**
1032
     * Maps arguments delivered by the request object to the local controller arguments.
1033
     *
1034
     * @throws Exception\RequiredArgumentMissingException
1035
     *
1036
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
1037
     */
1038
    protected function mapRequestArgumentsToControllerArguments()
1039
    {
1040
        /** @var \TYPO3\CMS\Extbase\Mvc\Controller\Argument $argument */
1041
        foreach ($this->arguments as $argument) {
1042
            $argumentName = $argument->getName();
1043
            if ($this->request->hasArgument($argumentName)) {
1044
                $this->setArgumentValue($argument, $this->request->getArgument($argumentName));
1045
            } elseif ($argument->isRequired()) {
1046
                throw new RequiredArgumentMissingException('Required argument "' . $argumentName . '" is not set for ' . $this->request->getControllerObjectName() . '->' . $this->request->getControllerActionName() . '.', 1298012500);
1047
            }
1048
        }
1049
    }
1050
1051
    /**
1052
     * @param Argument $argument
1053
     * @param mixed $rawValue
1054
     */
1055
    private function setArgumentValue(Argument $argument, $rawValue): void
1056
    {
1057
        if ($rawValue === null) {
1058
            $argument->setValue(null);
1059
            return;
1060
        }
1061
        $dataType = $argument->getDataType();
1062
        if (is_object($rawValue) && $rawValue instanceof $dataType) {
1063
            $argument->setValue($rawValue);
1064
            return;
1065
        }
1066
        $this->propertyMapper->resetMessages();
1067
        try {
1068
            $argument->setValue(
1069
                $this->propertyMapper->convert(
1070
                    $rawValue,
1071
                    $dataType,
1072
                    $argument->getPropertyMappingConfiguration()
1073
                )
1074
            );
1075
        } catch (TargetNotFoundException $e) {
1076
            // for optional arguments no exception is thrown.
1077
            if ($argument->isRequired()) {
1078
                throw $e;
1079
            }
1080
        }
1081
        $argument->getValidationResults()->merge($this->propertyMapper->getMessages());
1082
    }
1083
1084
    /**
1085
     * Returns a response object with either the given html string or the current rendered view as content.
1086
     *
1087
     * @param string|null $html
1088
     * @return ResponseInterface
1089
     */
1090
    protected function htmlResponse(string $html = null): ResponseInterface
1091
    {
1092
        $response = $this->responseFactory->createResponse()
1093
            ->withHeader('Content-Type', 'text/html; charset=utf-8');
1094
        $response->getBody()->write($html ?? $this->view->render());
1095
        return $response;
1096
    }
1097
}
1098