Passed
Push — master ( 92999a...0943a9 )
by
unknown
22:12 queued 10:13
created

ActionController::jsonResponse()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 5
dl 0
loc 8
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
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\PropagateResponseException;
22
use TYPO3\CMS\Core\Http\RedirectResponse;
23
use TYPO3\CMS\Core\Http\Response;
24
use TYPO3\CMS\Core\Http\Stream;
25
use TYPO3\CMS\Core\Messaging\AbstractMessage;
26
use TYPO3\CMS\Core\Messaging\FlashMessage;
27
use TYPO3\CMS\Core\Messaging\FlashMessageQueue;
28
use TYPO3\CMS\Core\Messaging\FlashMessageService;
29
use TYPO3\CMS\Core\Page\PageRenderer;
30
use TYPO3\CMS\Core\Utility\GeneralUtility;
31
use TYPO3\CMS\Core\Utility\MathUtility;
32
use TYPO3\CMS\Core\Utility\StringUtility;
33
use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface;
34
use TYPO3\CMS\Extbase\Event\Mvc\BeforeActionCallEvent;
35
use TYPO3\CMS\Extbase\Http\ForwardResponse;
36
use TYPO3\CMS\Extbase\Mvc\Controller\Exception\RequiredArgumentMissingException;
37
use TYPO3\CMS\Extbase\Mvc\Exception\InvalidArgumentNameException;
38
use TYPO3\CMS\Extbase\Mvc\Exception\InvalidArgumentTypeException;
39
use TYPO3\CMS\Extbase\Mvc\Exception\NoSuchActionException;
40
use TYPO3\CMS\Extbase\Mvc\Exception\StopActionException;
41
use TYPO3\CMS\Extbase\Mvc\RequestInterface;
42
use TYPO3\CMS\Extbase\Mvc\View\GenericViewResolver;
43
use TYPO3\CMS\Extbase\Mvc\View\JsonView;
44
use TYPO3\CMS\Extbase\Mvc\View\NotFoundView;
45
use TYPO3\CMS\Extbase\Mvc\View\ViewInterface;
46
use TYPO3\CMS\Extbase\Mvc\View\ViewResolverInterface;
47
use TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder;
48
use TYPO3\CMS\Extbase\Object\ObjectManagerInterface;
49
use TYPO3\CMS\Extbase\Property\Exception\TargetNotFoundException;
50
use TYPO3\CMS\Extbase\Property\PropertyMapper;
51
use TYPO3\CMS\Extbase\Reflection\ReflectionService;
52
use TYPO3\CMS\Extbase\Security\Cryptography\HashService;
53
use TYPO3\CMS\Extbase\Service\ExtensionService;
54
use TYPO3\CMS\Extbase\SignalSlot\Dispatcher;
55
use TYPO3\CMS\Extbase\Validation\Validator\ConjunctionValidator;
56
use TYPO3\CMS\Extbase\Validation\Validator\ValidatorInterface;
57
use TYPO3\CMS\Extbase\Validation\ValidatorResolver;
58
use TYPO3Fluid\Fluid\View\TemplateView;
59
60
/**
61
 * A multi action controller. This is by far the most common base class for Controllers.
62
 */
63
abstract class ActionController implements ControllerInterface
64
{
65
    /**
66
     * @var ResponseFactoryInterface
67
     */
68
    protected $responseFactory;
69
70
    /**
71
     * @var \TYPO3\CMS\Extbase\Reflection\ReflectionService
72
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
73
     */
74
    protected $reflectionService;
75
76
    /**
77
     * @var HashService
78
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
79
     */
80
    protected $hashService;
81
82
    /**
83
     * @var ViewResolverInterface
84
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
85
     */
86
    private $viewResolver;
87
88
    /**
89
     * The current view, as resolved by resolveView()
90
     *
91
     * @var ViewInterface
92
     */
93
    protected $view;
94
95
    /**
96
     * The default view object to use if none of the resolved views can render
97
     * a response for the current request.
98
     *
99
     * @var string
100
     */
101
    protected $defaultViewObjectName = \TYPO3\CMS\Fluid\View\TemplateView::class;
102
103
    /**
104
     * Name of the action method
105
     *
106
     * @var string
107
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
108
     */
109
    protected $actionMethodName = 'indexAction';
110
111
    /**
112
     * Name of the special error action method which is called in case of errors
113
     *
114
     * @var string
115
     */
116
    protected $errorMethodName = 'errorAction';
117
118
    /**
119
     * @var \TYPO3\CMS\Extbase\Mvc\Controller\MvcPropertyMappingConfigurationService
120
     */
121
    protected $mvcPropertyMappingConfigurationService;
122
123
    /**
124
     * @var EventDispatcherInterface
125
     */
126
    protected $eventDispatcher;
127
128
    /**
129
     * The current request.
130
     *
131
     * @var RequestInterface
132
     */
133
    protected $request;
134
135
    /**
136
     * @var \TYPO3\CMS\Extbase\SignalSlot\Dispatcher
137
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
138
     */
139
    protected $signalSlotDispatcher;
140
141
    /**
142
     * @var \TYPO3\CMS\Extbase\Object\ObjectManagerInterface
143
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
144
     */
145
    protected $objectManager;
146
147
    /**
148
     * @var \TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder
149
     */
150
    protected $uriBuilder;
151
152
    /**
153
     * Contains the settings of the current extension
154
     *
155
     * @var array
156
     */
157
    protected $settings;
158
159
    /**
160
     * @var \TYPO3\CMS\Extbase\Validation\ValidatorResolver
161
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
162
     */
163
    protected $validatorResolver;
164
165
    /**
166
     * @var \TYPO3\CMS\Extbase\Mvc\Controller\Arguments Arguments passed to the controller
167
     */
168
    protected $arguments;
169
170
    /**
171
     * @var \TYPO3\CMS\Extbase\Mvc\Controller\ControllerContext
172
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
173
     */
174
    protected $controllerContext;
175
176
    /**
177
     * @var ConfigurationManagerInterface
178
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
179
     */
180
    protected $configurationManager;
181
182
    /**
183
     * @var PropertyMapper
184
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
185
     */
186
    private $propertyMapper;
187
188
    /**
189
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
190
     */
191
    private FlashMessageService $internalFlashMessageService;
192
193
    /**
194
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
195
     */
196
    private ExtensionService $internalExtensionService;
197
198
    final public function injectResponseFactory(ResponseFactoryInterface $responseFactory)
199
    {
200
        $this->responseFactory = $responseFactory;
201
    }
202
203
    /**
204
     * @param ConfigurationManagerInterface $configurationManager
205
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
206
     */
207
    public function injectConfigurationManager(ConfigurationManagerInterface $configurationManager)
208
    {
209
        $this->configurationManager = $configurationManager;
210
        $this->settings = $this->configurationManager->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_SETTINGS) + ['offlineMode' => false];
211
    }
212
213
    /**
214
     * Injects the object manager
215
     *
216
     * @param \TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager
217
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
218
     */
219
    public function injectObjectManager(ObjectManagerInterface $objectManager)
220
    {
221
        $this->objectManager = $objectManager;
222
        $this->arguments = GeneralUtility::makeInstance(Arguments::class);
223
    }
224
225
    /**
226
     * @param \TYPO3\CMS\Extbase\SignalSlot\Dispatcher $signalSlotDispatcher
227
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
228
     */
229
    public function injectSignalSlotDispatcher(Dispatcher $signalSlotDispatcher)
230
    {
231
        $this->signalSlotDispatcher = $signalSlotDispatcher;
232
    }
233
234
    /**
235
     * @param \TYPO3\CMS\Extbase\Validation\ValidatorResolver $validatorResolver
236
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
237
     */
238
    public function injectValidatorResolver(ValidatorResolver $validatorResolver)
239
    {
240
        $this->validatorResolver = $validatorResolver;
241
    }
242
243
    /**
244
     * @param ViewResolverInterface $viewResolver
245
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
246
     */
247
    public function injectViewResolver(ViewResolverInterface $viewResolver)
248
    {
249
        $this->viewResolver = $viewResolver;
250
    }
251
252
    /**
253
     * @param \TYPO3\CMS\Extbase\Reflection\ReflectionService $reflectionService
254
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
255
     */
256
    public function injectReflectionService(ReflectionService $reflectionService)
257
    {
258
        $this->reflectionService = $reflectionService;
259
    }
260
261
    /**
262
     * @param HashService $hashService
263
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
264
     */
265
    public function injectHashService(HashService $hashService)
266
    {
267
        $this->hashService = $hashService;
268
    }
269
270
    /**
271
     * @param \TYPO3\CMS\Extbase\Mvc\Controller\MvcPropertyMappingConfigurationService $mvcPropertyMappingConfigurationService
272
     */
273
    public function injectMvcPropertyMappingConfigurationService(MvcPropertyMappingConfigurationService $mvcPropertyMappingConfigurationService)
274
    {
275
        $this->mvcPropertyMappingConfigurationService = $mvcPropertyMappingConfigurationService;
276
    }
277
278
    public function injectEventDispatcher(EventDispatcherInterface $eventDispatcher): void
279
    {
280
        $this->eventDispatcher = $eventDispatcher;
281
    }
282
283
    /**
284
     * @param PropertyMapper $propertyMapper
285
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
286
     */
287
    public function injectPropertyMapper(PropertyMapper $propertyMapper): void
288
    {
289
        $this->propertyMapper = $propertyMapper;
290
    }
291
292
    /**
293
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
294
     */
295
    final public function injectInternalFlashMessageService(FlashMessageService $flashMessageService): void
296
    {
297
        $this->internalFlashMessageService = $flashMessageService;
298
    }
299
300
    /**
301
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
302
     */
303
    final public function injectInternalExtensionService(ExtensionService $extensionService): void
304
    {
305
        $this->internalExtensionService = $extensionService;
306
    }
307
308
    /**
309
     * Initializes the view before invoking an action method.
310
     *
311
     * Override this method to solve assign variables common for all actions
312
     * or prepare the view in another way before the action is called.
313
     *
314
     * @param ViewInterface $view The view to be initialized
315
     */
316
    protected function initializeView(ViewInterface $view)
317
    {
318
    }
319
320
    /**
321
     * Initializes the controller before invoking an action method.
322
     *
323
     * Override this method to solve tasks which all actions have in
324
     * common.
325
     */
326
    protected function initializeAction()
327
    {
328
    }
329
330
    /**
331
     * Implementation of the arguments initialization in the action controller:
332
     * Automatically registers arguments of the current action
333
     *
334
     * Don't override this method - use initializeAction() instead.
335
     *
336
     * @throws \TYPO3\CMS\Extbase\Mvc\Exception\InvalidArgumentTypeException
337
     * @see initializeArguments()
338
     *
339
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
340
     */
341
    protected function initializeActionMethodArguments()
342
    {
343
        $methodParameters = $this->reflectionService
344
            ->getClassSchema(static::class)
345
            ->getMethod($this->actionMethodName)->getParameters();
346
347
        foreach ($methodParameters as $parameterName => $parameter) {
348
            $dataType = null;
349
            if ($parameter->getType() !== null) {
350
                $dataType = $parameter->getType();
351
            } elseif ($parameter->isArray()) {
352
                $dataType = 'array';
353
            }
354
            if ($dataType === null) {
355
                throw new InvalidArgumentTypeException('The argument type for parameter $' . $parameterName . ' of method ' . static::class . '->' . $this->actionMethodName . '() could not be detected.', 1253175643);
356
            }
357
            $defaultValue = $parameter->hasDefaultValue() ? $parameter->getDefaultValue() : null;
358
            $this->arguments->addNewArgument($parameterName, $dataType, !$parameter->isOptional(), $defaultValue);
359
        }
360
    }
361
362
    /**
363
     * Adds the needed validators to the Arguments:
364
     *
365
     * - Validators checking the data type from the @param annotation
366
     * - Custom validators specified with validate annotations.
367
     * - Model-based validators (validate annotations in the model)
368
     * - Custom model validator classes
369
     *
370
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
371
     */
372
    protected function initializeActionMethodValidators()
373
    {
374
        if ($this->arguments->count() === 0) {
375
            return;
376
        }
377
378
        $classSchemaMethod = $this->reflectionService->getClassSchema(static::class)
379
            ->getMethod($this->actionMethodName);
380
381
        /** @var \TYPO3\CMS\Extbase\Mvc\Controller\Argument $argument */
382
        foreach ($this->arguments as $argument) {
383
            $classSchemaMethodParameter = $classSchemaMethod->getParameter($argument->getName());
384
            /*
385
             * At this point validation is skipped if there is an IgnoreValidation annotation.
386
             *
387
             * todo: IgnoreValidation annotations could be evaluated in the ClassSchema and result in
388
             * todo: no validators being applied to the method parameter.
389
             */
390
            if ($classSchemaMethodParameter->ignoreValidation()) {
391
                continue;
392
            }
393
394
            // todo: It's quite odd that an instance of ConjunctionValidator is created directly here.
395
            // todo: \TYPO3\CMS\Extbase\Validation\ValidatorResolver::getBaseValidatorConjunction could/should be used
396
            // todo: here, to benefit of the built in 1st level cache of the ValidatorResolver.
397
            $validator = $this->objectManager->get(ConjunctionValidator::class);
398
399
            foreach ($classSchemaMethodParameter->getValidators() as $validatorDefinition) {
400
                /** @var ValidatorInterface $validatorInstance */
401
                $validatorInstance = $this->objectManager->get(
402
                    $validatorDefinition['className'],
403
                    $validatorDefinition['options']
404
                );
405
406
                $validator->addValidator(
407
                    $validatorInstance
408
                );
409
            }
410
411
            $baseValidatorConjunction = $this->validatorResolver->getBaseValidatorConjunction($argument->getDataType());
412
            if ($baseValidatorConjunction->count() > 0) {
413
                $validator->addValidator($baseValidatorConjunction);
414
            }
415
            $argument->setValidator($validator);
416
        }
417
    }
418
419
    /**
420
     * Collects the base validators which were defined for the data type of each
421
     * controller argument and adds them to the argument's validator chain.
422
     *
423
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
424
     */
425
    public function initializeControllerArgumentsBaseValidators()
426
    {
427
        /** @var \TYPO3\CMS\Extbase\Mvc\Controller\Argument $argument */
428
        foreach ($this->arguments as $argument) {
429
            $validator = $this->validatorResolver->getBaseValidatorConjunction($argument->getDataType());
430
            if ($validator !== null) {
431
                $argument->setValidator($validator);
432
            }
433
        }
434
    }
435
436
    /**
437
     * Handles an incoming request and returns a response object
438
     *
439
     * @param \TYPO3\CMS\Extbase\Mvc\RequestInterface $request The request object
440
     * @return ResponseInterface
441
     *
442
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
443
     */
444
    public function processRequest(RequestInterface $request): ResponseInterface
445
    {
446
        $this->request = $request;
447
        // @deprecated since v11, will be removed in v12.
448
        $this->request->setDispatched(true);
0 ignored issues
show
Bug introduced by
The method setDispatched() 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

448
        $this->request->/** @scrutinizer ignore-call */ 
449
                        setDispatched(true);
Loading history...
449
        $this->uriBuilder = $this->objectManager->get(UriBuilder::class);
450
        $this->uriBuilder->setRequest($request);
451
        $this->actionMethodName = $this->resolveActionMethodName();
452
        $this->initializeActionMethodArguments();
453
        $this->initializeActionMethodValidators();
454
        $this->mvcPropertyMappingConfigurationService->initializePropertyMappingConfigurationFromRequest($request, $this->arguments);
455
        $this->initializeAction();
456
        $actionInitializationMethodName = 'initialize' . ucfirst($this->actionMethodName);
457
        /** @var callable $callable */
458
        $callable = [$this, $actionInitializationMethodName];
459
        if (is_callable($callable)) {
460
            $callable();
461
        }
462
        $this->mapRequestArgumentsToControllerArguments();
463
        $this->controllerContext = $this->buildControllerContext();
464
        $this->view = $this->resolveView();
465
        if ($this->view !== null) {
466
            $this->initializeView($this->view);
467
        }
468
        $response = $this->callActionMethod($request);
469
        $this->renderAssetsForRequest($request);
470
471
        return $response;
472
    }
473
474
    /**
475
     * Method which initializes assets that should be attached to the response
476
     * for the given $request, which contains parameters that an override can
477
     * use to determine which assets to add via PageRenderer.
478
     *
479
     * This default implementation will attempt to render the sections "HeaderAssets"
480
     * and "FooterAssets" from the template that is being rendered, inserting the
481
     * rendered content into either page header or footer, as appropriate. Both
482
     * sections are optional and can be used one or both in combination.
483
     *
484
     * You can add assets with this method without worrying about duplicates, if
485
     * for example you do this in a plugin that gets used multiple time on a page.
486
     *
487
     * @param \TYPO3\CMS\Extbase\Mvc\RequestInterface $request
488
     *
489
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
490
     */
491
    protected function renderAssetsForRequest($request)
492
    {
493
        if (!$this->view instanceof TemplateView) {
494
            // Only TemplateView (from Fluid engine, so this includes all TYPO3 Views based
495
            // on TYPO3's AbstractTemplateView) supports renderSection(). The method is not
496
            // declared on ViewInterface - so we must assert a specific class. We silently skip
497
            // asset processing if the View doesn't match, so we don't risk breaking custom Views.
498
            return;
499
        }
500
        $pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
501
        $variables = ['request' => $request, 'arguments' => $this->arguments];
502
        $headerAssets = $this->view->renderSection('HeaderAssets', $variables, true);
503
        $footerAssets = $this->view->renderSection('FooterAssets', $variables, true);
504
        if (!empty(trim($headerAssets))) {
505
            $pageRenderer->addHeaderData($headerAssets);
506
        }
507
        if (!empty(trim($footerAssets))) {
508
            $pageRenderer->addFooterData($footerAssets);
509
        }
510
    }
511
512
    /**
513
     * Resolves and checks the current action method name
514
     *
515
     * @return string Method name of the current action
516
     * @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).
517
     *
518
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
519
     */
520
    protected function resolveActionMethodName()
521
    {
522
        $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

522
        $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...
523
        if (!method_exists($this, $actionMethodName)) {
524
            throw new NoSuchActionException('An action "' . $actionMethodName . '" does not exist in controller "' . static::class . '".', 1186669086);
525
        }
526
        return $actionMethodName;
527
    }
528
529
    /**
530
     * Calls the specified action method and passes the arguments.
531
     *
532
     * If the action returns a string, it is appended to the content in the
533
     * response object. If the action doesn't return anything and a valid
534
     * view exists, the view is rendered automatically.
535
     *
536
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
537
     */
538
    protected function callActionMethod(RequestInterface $request): ResponseInterface
539
    {
540
        // incoming request is not needed yet but can be passed into the action in the future like in symfony
541
        // todo: support this via method-reflection
542
543
        $preparedArguments = [];
544
        /** @var \TYPO3\CMS\Extbase\Mvc\Controller\Argument $argument */
545
        foreach ($this->arguments as $argument) {
546
            $preparedArguments[] = $argument->getValue();
547
        }
548
        $validationResult = $this->arguments->validate();
549
        if (!$validationResult->hasErrors()) {
550
            $this->eventDispatcher->dispatch(new BeforeActionCallEvent(static::class, $this->actionMethodName, $preparedArguments));
551
            $actionResult = $this->{$this->actionMethodName}(...$preparedArguments);
552
        } else {
553
            $actionResult = $this->{$this->errorMethodName}();
554
        }
555
556
        if ($actionResult instanceof ResponseInterface) {
557
            return $actionResult;
558
        }
559
560
        trigger_error(
561
            sprintf(
562
                'Controller action %s does not return an instance of %s which is deprecated.',
563
                static::class . '::' . $this->actionMethodName,
564
                ResponseInterface::class
565
            ),
566
            E_USER_DEPRECATED
567
        );
568
569
        $response = new Response();
570
        $body = new Stream('php://temp', 'rw');
571
        if ($actionResult === null && $this->view instanceof ViewInterface) {
572
            if ($this->view instanceof JsonView) {
573
                // This is just a fallback solution until v12, when Extbase requires PSR-7 responses to be
574
                // returned in their controller actions. The header, added below, may gets overwritten in
575
                // the Extbase bootstrap, depending on the context (FE/BE) and TypoScript configuration.
576
                $response = $response->withHeader('Content-Type', 'application/json; charset=utf-8');
577
            }
578
            $body->write($this->view->render());
579
        } elseif (is_string($actionResult) && $actionResult !== '') {
580
            $body->write($actionResult);
581
        } elseif (is_object($actionResult) && method_exists($actionResult, '__toString')) {
582
            $body->write((string)$actionResult);
583
        }
584
585
        $body->rewind();
586
        return $response->withBody($body);
587
    }
588
589
    /**
590
     * Prepares a view for the current action.
591
     * By default, this method tries to locate a view with a name matching the current action.
592
     *
593
     * @return ViewInterface
594
     *
595
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
596
     */
597
    protected function resolveView()
598
    {
599
        if ($this->viewResolver instanceof GenericViewResolver) {
600
            /*
601
             * This setter is not part of the ViewResolverInterface as it's only necessary to set
602
             * the default view class from this point when using the generic view resolver which
603
             * must respect the possibly overridden property defaultViewObjectName.
604
             */
605
            $this->viewResolver->setDefaultViewClass($this->defaultViewObjectName);
606
        }
607
608
        $view = $this->viewResolver->resolve(
609
            $this->request->getControllerObjectName(),
610
            $this->request->getControllerActionName(),
611
            $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

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

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

867
                $this->request->/** @scrutinizer ignore-call */ 
868
                                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...
868
                $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

868
                $this->request->/** @scrutinizer ignore-call */ 
869
                                getPluginName()
Loading history...
869
            );
870
            $identifier = 'extbase.flashmessages.' . $pluginNamespace;
871
        }
872
873
        return $this->internalFlashMessageService->getMessageQueueByIdentifier($identifier);
874
    }
875
876
    /**
877
     * Initialize the controller context
878
     *
879
     * @return \TYPO3\CMS\Extbase\Mvc\Controller\ControllerContext ControllerContext to be passed to the view
880
     *
881
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
882
     */
883
    protected function buildControllerContext()
884
    {
885
        /** @var \TYPO3\CMS\Extbase\Mvc\Controller\ControllerContext $controllerContext */
886
        $controllerContext = $this->objectManager->get(ControllerContext::class);
887
        $controllerContext->setRequest($this->request);
888
        if ($this->arguments !== null) {
889
            $controllerContext->setArguments($this->arguments);
890
        }
891
        $controllerContext->setUriBuilder($this->uriBuilder);
892
893
        return $controllerContext;
894
    }
895
896
    /**
897
     * Forwards the request to another action and / or controller.
898
     *
899
     * Request is directly transferred to the other action / controller
900
     * without the need for a new request.
901
     *
902
     * @param string $actionName Name of the action to forward to
903
     * @param string|null $controllerName Unqualified object name of the controller to forward to. If not specified, the current controller is used.
904
     * @param string|null $extensionName Name of the extension containing the controller to forward to. If not specified, the current extension is assumed.
905
     * @param array|null $arguments Arguments to pass to the target action
906
     * @throws StopActionException
907
     * @see redirect()
908
     * @deprecated since TYPO3 11.0, will be removed in 12.0
909
     */
910
    public function forward($actionName, $controllerName = null, $extensionName = null, array $arguments = null)
911
    {
912
        trigger_error(
913
            sprintf('Method %s is deprecated. To forward to another action, return a %s instead.', __METHOD__, ForwardResponse::class),
914
            E_USER_DEPRECATED
915
        );
916
917
        $this->request->setDispatched(false);
918
        $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

918
        $this->request->/** @scrutinizer ignore-call */ 
919
                        setControllerActionName($actionName);
Loading history...
919
920
        if ($controllerName !== null) {
921
            $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

921
            $this->request->/** @scrutinizer ignore-call */ 
922
                            setControllerName($controllerName);
Loading history...
922
        }
923
924
        if ($extensionName !== null) {
925
            $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

925
            $this->request->/** @scrutinizer ignore-call */ 
926
                            setControllerExtensionName($extensionName);
Loading history...
926
        }
927
928
        if ($arguments !== null) {
929
            $this->request->setArguments($arguments);
0 ignored issues
show
Bug introduced by
The method setArguments() 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

929
            $this->request->/** @scrutinizer ignore-call */ 
930
                            setArguments($arguments);
Loading history...
930
        }
931
        throw new StopActionException('forward', 1476045801);
932
    }
933
934
    /**
935
     * Redirects the request to another action and / or controller.
936
     *
937
     * Redirect will be sent to the client which then performs another request to the new URI.
938
     *
939
     * @param string $actionName Name of the action to forward to
940
     * @param string|null $controllerName Unqualified object name of the controller to forward to. If not specified, the current controller is used.
941
     * @param string|null $extensionName Name of the extension containing the controller to forward to. If not specified, the current extension is assumed.
942
     * @param array|null $arguments Arguments to pass to the target action
943
     * @param int|null $pageUid Target page uid. If NULL, the current page uid is used
944
     * @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...
945
     * @param int $statusCode (optional) The HTTP status code for the redirect. Default is "303 See Other
946
     * @throws StopActionException deprecated since TYPO3 11.0, method will RETURN a Core\Http\RedirectResponse instead of throwing in v12
947
     * @todo: ': ResponseInterface' (without ?) in v12 as method return type together with redirectToUri() cleanup
948
     */
949
    protected function redirect($actionName, $controllerName = null, $extensionName = null, array $arguments = null, $pageUid = null, $_ = null, $statusCode = 303): void
950
    {
951
        if ($controllerName === null) {
952
            $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

952
            /** @scrutinizer ignore-call */ 
953
            $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...
953
        }
954
        $this->uriBuilder->reset()->setCreateAbsoluteUri(true);
955
        if (MathUtility::canBeInterpretedAsInteger($pageUid)) {
956
            $this->uriBuilder->setTargetPageUid((int)$pageUid);
957
        }
958
        if (GeneralUtility::getIndpEnv('TYPO3_SSL')) {
959
            $this->uriBuilder->setAbsoluteUriScheme('https');
960
        }
961
        $uri = $this->uriBuilder->uriFor($actionName, $arguments, $controllerName, $extensionName);
962
        $this->redirectToUri($uri, null, $statusCode);
963
    }
964
965
    /**
966
     * Redirects the web request to another uri.
967
     *
968
     * @param mixed $uri A string representation of a URI
969
     * @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...
970
     * @param int $statusCode (optional) The HTTP status code for the redirect. Default is "303 See Other"
971
     * @throws StopActionException deprecated since TYPO3 11.0, will be removed in 12.0
972
     * @todo: ': ResponseInterface' (without ?) in v12 as method return type together with redirectToUri() cleanup
973
     */
974
    protected function redirectToUri($uri, $_ = null, $statusCode = 303): void
975
    {
976
        $uri = $this->addBaseUriIfNecessary($uri);
977
        $response = new RedirectResponse($uri, $statusCode);
978
        // @deprecated since v11, will be removed in v12. RETURN the response instead. See Dispatcher class, too.
979
        throw new StopActionException('redirectToUri', 1476045828, null, $response);
980
    }
981
982
    /**
983
     * Adds the base uri if not already in place.
984
     *
985
     * @param string $uri The URI
986
     * @return string
987
     *
988
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
989
     */
990
    protected function addBaseUriIfNecessary($uri)
991
    {
992
        return GeneralUtility::locationHeaderUrl((string)$uri);
993
    }
994
995
    /**
996
     * Sends the specified HTTP status immediately and only stops to run back through the middleware stack.
997
     * Note: If any other plugin or content or hook is used within a frontend request, this is skipped by design.
998
     *
999
     * @param int $statusCode The HTTP status code
1000
     * @param string $statusMessage A custom HTTP status message
1001
     * @param string $content Body content which further explains the status
1002
     * @throws PropagateResponseException
1003
     */
1004
    public function throwStatus($statusCode, $statusMessage = null, $content = null)
1005
    {
1006
        if ($content === null) {
1007
            $content = $statusCode . ' ' . $statusMessage;
1008
        }
1009
        $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

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