Passed
Push — master ( 90fa47...0ecca8 )
by
unknown
17:40
created

RequestBuilder::resolveActionName()   B

Complexity

Conditions 10
Paths 12

Size

Total Lines 22
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 10
eloc 15
nc 12
nop 2
dl 0
loc 22
rs 7.6666
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\Web;
17
18
use Psr\Http\Message\ServerRequestInterface;
19
use TYPO3\CMS\Core\Error\Http\PageNotFoundException;
20
use TYPO3\CMS\Core\Http\NormalizedParams;
21
use TYPO3\CMS\Core\Routing\PageArguments;
22
use TYPO3\CMS\Core\SingletonInterface;
23
use TYPO3\CMS\Core\Utility\ArrayUtility;
24
use TYPO3\CMS\Core\Utility\Exception\MissingArrayPathException;
25
use TYPO3\CMS\Core\Utility\GeneralUtility;
26
use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface;
27
use TYPO3\CMS\Extbase\Mvc\Exception as MvcException;
28
use TYPO3\CMS\Extbase\Mvc\Exception\InvalidActionNameException;
29
use TYPO3\CMS\Extbase\Mvc\Exception\InvalidControllerNameException;
30
use TYPO3\CMS\Extbase\Mvc\Request;
31
use TYPO3\CMS\Extbase\Service\EnvironmentService;
32
use TYPO3\CMS\Extbase\Service\ExtensionService;
33
34
/**
35
 * Builds a web request.
36
 * @internal only to be used within Extbase, not part of TYPO3 Core API.
37
 */
38
class RequestBuilder implements SingletonInterface
39
{
40
    /**
41
     * This is a unique key for a plugin (not the extension key!)
42
     *
43
     * @var string
44
     */
45
    protected $pluginName = 'plugin';
46
47
    /**
48
     * The name of the extension (in UpperCamelCase)
49
     *
50
     * @var string
51
     */
52
    protected $extensionName;
53
54
    /**
55
     * The class name of the default controller
56
     *
57
     * @var string
58
     */
59
    private $defaultControllerClassName;
60
61
    /**
62
     * The default controller name
63
     *
64
     * @var string
65
     */
66
    protected $defaultControllerName = '';
67
68
    /**
69
     * The default format of the response object
70
     *
71
     * @var string
72
     */
73
    protected $defaultFormat = 'html';
74
75
    /**
76
     * The allowed actions of the controller. This actions can be called via $_GET and $_POST.
77
     *
78
     * @var array
79
     */
80
    protected $allowedControllerActions = [];
81
82
    /**
83
     * @var ConfigurationManagerInterface
84
     */
85
    protected $configurationManager;
86
87
    /**
88
     * @var ExtensionService
89
     */
90
    protected $extensionService;
91
92
    /**
93
     * @var EnvironmentService
94
     */
95
    protected $environmentService;
96
97
    /**
98
     * @var array
99
     */
100
    private $controllerAliasToClassMapping = [];
101
102
    /**
103
     * @var array
104
     */
105
    private $controllerClassToAliasMapping = [];
106
107
    /**
108
     * @var array|string[]
109
     */
110
    private $allowedControllerAliases = [];
111
112
    /**
113
     * @param ConfigurationManagerInterface $configurationManager
114
     */
115
    public function injectConfigurationManager(ConfigurationManagerInterface $configurationManager)
116
    {
117
        $this->configurationManager = $configurationManager;
118
    }
119
120
    /**
121
     * @param ExtensionService $extensionService
122
     */
123
    public function injectExtensionService(ExtensionService $extensionService)
124
    {
125
        $this->extensionService = $extensionService;
126
    }
127
128
    /**
129
     * @param EnvironmentService $environmentService
130
     */
131
    public function injectEnvironmentService(EnvironmentService $environmentService)
132
    {
133
        $this->environmentService = $environmentService;
134
    }
135
136
    /**
137
     * @throws MvcException
138
     * @see \TYPO3\CMS\Extbase\Core\Bootstrap::initializeConfiguration
139
     */
140
    protected function loadDefaultValues()
141
    {
142
        // todo: See comment in \TYPO3\CMS\Extbase\Core\Bootstrap::initializeConfiguration for further explanation
143
        // todo: on why we shouldn't use the configuration manager here.
144
        $configuration = $this->configurationManager->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK);
145
        if (empty($configuration['extensionName'])) {
146
            throw new MvcException('"extensionName" is not properly configured. Request can\'t be dispatched!', 1289843275);
147
        }
148
        if (empty($configuration['pluginName'])) {
149
            throw new MvcException('"pluginName" is not properly configured. Request can\'t be dispatched!', 1289843277);
150
        }
151
        $this->extensionName = $configuration['extensionName'];
152
        $this->pluginName = $configuration['pluginName'];
153
        $defaultControllerConfiguration = reset($configuration['controllerConfiguration']) ?? [];
154
        $this->defaultControllerClassName = $defaultControllerConfiguration['className'] ?? null;
155
        $this->defaultControllerName = $defaultControllerConfiguration['alias'] ?? null;
156
        $this->allowedControllerActions = [];
157
        foreach ($configuration['controllerConfiguration'] as $controllerClassName => $controllerConfiguration) {
158
            $this->allowedControllerActions[$controllerClassName] = $controllerConfiguration['actions'] ?? null;
159
            $this->controllerAliasToClassMapping[$controllerConfiguration['alias']] = $controllerConfiguration['className'];
160
            $this->controllerClassToAliasMapping[$controllerConfiguration['className']] = $controllerConfiguration['alias'];
161
            $this->allowedControllerAliases[] = $controllerConfiguration['alias'];
162
        }
163
        if (!empty($configuration['format'])) {
164
            $this->defaultFormat = $configuration['format'];
165
        }
166
    }
167
168
    /**
169
     * Builds a web request object from the raw HTTP information and the configuration
170
     *
171
     * @param ServerRequestInterface $mainRequest
172
     * @return Request The web request as an object
173
     */
174
    public function build(ServerRequestInterface $mainRequest)
175
    {
176
        $this->loadDefaultValues();
177
        $pluginNamespace = $this->extensionService->getPluginNamespace($this->extensionName, $this->pluginName);
178
        /** @var NormalizedParams $normalizedParams */
179
        $normalizedParams = $mainRequest->getAttribute('normalizedParams');
180
        $queryArguments = $mainRequest->getAttribute('routing');
181
        if ($queryArguments instanceof PageArguments) {
182
            $parameters = $queryArguments->get($pluginNamespace) ?? [];
183
        } else {
184
            $parameters = $mainRequest->getQueryParams()[$pluginNamespace] ?? [];
185
        }
186
        if ($mainRequest->getMethod() === 'POST') {
187
            $postParameters = $mainRequest->getParsedBody()[$pluginNamespace] ?? [];
188
            ArrayUtility::mergeRecursiveWithOverrule($parameters, $postParameters);
189
        }
190
191
        $files = $this->untangleFilesArray($_FILES);
192
        if (is_array($files[$pluginNamespace] ?? null)) {
193
            $parameters = array_replace_recursive($parameters, $files[$pluginNamespace]);
194
        }
195
196
        $controllerClassName = $this->resolveControllerClassName($parameters);
197
        $actionName = $this->resolveActionName($controllerClassName, $parameters);
198
199
        $baseUri = $normalizedParams->getSiteUrl();
200
        if ($this->environmentService->isEnvironmentInBackendMode()) {
201
            $baseUri .= TYPO3_mainDir;
202
        }
203
204
        $request = GeneralUtility::makeInstance(Request::class);
205
        $request->setPluginName($this->pluginName);
206
        $request->setControllerExtensionName($this->extensionName);
207
        $request->setControllerAliasToClassNameMapping($this->controllerAliasToClassMapping);
208
        $request->setControllerName($this->controllerClassToAliasMapping[$controllerClassName]);
209
        $request->setControllerActionName($actionName);
210
        $request->setRequestUri($normalizedParams->getRequestUrl());
211
        $request->setBaseUri($baseUri);
212
        $request->setMethod($mainRequest->getMethod());
213
        if (isset($parameters['format']) && is_string($parameters['format']) && $parameters['format'] !== '') {
214
            $request->setFormat(filter_var($parameters['format'], FILTER_SANITIZE_STRING));
215
        } else {
216
            $request->setFormat($this->defaultFormat);
217
        }
218
        foreach ($parameters as $argumentName => $argumentValue) {
219
            $request->setArgument($argumentName, $argumentValue);
220
        }
221
        return $request;
222
    }
223
224
    /**
225
     * Returns the current ControllerName extracted from given $parameters.
226
     * If no controller is specified, the defaultControllerName will be returned.
227
     * If that's not available, an exception is thrown.
228
     *
229
     * @param array $parameters
230
     * @throws \TYPO3\CMS\Extbase\Mvc\Exception\InvalidControllerNameException
231
     * @throws MvcException if the controller could not be resolved
232
     * @throws \TYPO3\CMS\Core\Error\Http\PageNotFoundException
233
     * @return string
234
     */
235
    protected function resolveControllerClassName(array $parameters)
236
    {
237
        if (!isset($parameters['controller']) || $parameters['controller'] === '') {
238
            if (empty($this->defaultControllerClassName)) {
239
                throw new MvcException('The default controller for extension "' . $this->extensionName . '" and plugin "' . $this->pluginName . '" can not be determined. Please check for TYPO3\\CMS\\Extbase\\Utility\\ExtensionUtility::configurePlugin() in your ext_localconf.php.', 1316104317);
240
            }
241
            return $this->defaultControllerClassName;
242
        }
243
        $controllerClassName = $this->controllerAliasToClassMapping[$parameters['controller']] ?? '';
244
        if (!in_array($controllerClassName, array_keys($this->allowedControllerActions))) {
245
            $configuration = $this->configurationManager->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK);
246
            if (isset($configuration['mvc']['throwPageNotFoundExceptionIfActionCantBeResolved']) && (bool)$configuration['mvc']['throwPageNotFoundExceptionIfActionCantBeResolved']) {
247
                throw new PageNotFoundException('The requested resource was not found', 1313857897);
248
            }
249
            if (isset($configuration['mvc']['callDefaultActionIfActionCantBeResolved']) && (bool)$configuration['mvc']['callDefaultActionIfActionCantBeResolved']) {
250
                return $this->defaultControllerClassName;
251
            }
252
            throw new InvalidControllerNameException(
253
                'The controller "' . $parameters['controller'] . '" is not allowed by plugin "' . $this->pluginName . '". Please check for TYPO3\\CMS\\Extbase\\Utility\\ExtensionUtility::configurePlugin() in your ext_localconf.php.',
254
                1313855173
255
            );
256
        }
257
        return filter_var($controllerClassName, FILTER_SANITIZE_STRING);
258
    }
259
260
    /**
261
     * Returns the current actionName extracted from given $parameters.
262
     * If no action is specified, the defaultActionName will be returned.
263
     * If that's not available or the specified action is not defined in the current plugin, an exception is thrown.
264
     *
265
     * @param string $controllerClassName
266
     * @param array $parameters
267
     * @throws \TYPO3\CMS\Extbase\Mvc\Exception\InvalidActionNameException
268
     * @throws MvcException
269
     * @throws \TYPO3\CMS\Core\Error\Http\PageNotFoundException
270
     * @return string
271
     */
272
    protected function resolveActionName($controllerClassName, array $parameters)
273
    {
274
        $defaultActionName = is_array($this->allowedControllerActions[$controllerClassName]) ? current($this->allowedControllerActions[$controllerClassName]) : '';
275
        if (!isset($parameters['action']) || $parameters['action'] === '') {
276
            if ($defaultActionName === '') {
277
                throw new MvcException('The default action can not be determined for controller "' . $controllerClassName . '". Please check TYPO3\\CMS\\Extbase\\Utility\\ExtensionUtility::configurePlugin() in your ext_localconf.php.', 1295479651);
278
            }
279
            return $defaultActionName;
280
        }
281
        $actionName = $parameters['action'];
282
        $allowedActionNames = $this->allowedControllerActions[$controllerClassName];
283
        if (!in_array($actionName, $allowedActionNames)) {
284
            $configuration = $this->configurationManager->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK);
285
            if (isset($configuration['mvc']['throwPageNotFoundExceptionIfActionCantBeResolved']) && (bool)$configuration['mvc']['throwPageNotFoundExceptionIfActionCantBeResolved']) {
286
                throw new PageNotFoundException('The requested resource was not found', 1313857898);
287
            }
288
            if (isset($configuration['mvc']['callDefaultActionIfActionCantBeResolved']) && (bool)$configuration['mvc']['callDefaultActionIfActionCantBeResolved']) {
289
                return $defaultActionName;
290
            }
291
            throw new InvalidActionNameException('The action "' . $actionName . '" (controller "' . $controllerClassName . '") is not allowed by this plugin / module. Please check TYPO3\\CMS\\Extbase\\Utility\\ExtensionUtility::configurePlugin() in your ext_localconf.php / TYPO3\\CMS\\Extbase\\Utility\\ExtensionUtility::configureModule() in your ext_tables.php.', 1313855175);
292
        }
293
        return filter_var($actionName, FILTER_SANITIZE_STRING);
294
    }
295
296
    /**
297
     * Transforms the convoluted _FILES superglobal into a manageable form.
298
     *
299
     * @param array $convolutedFiles The _FILES superglobal
300
     * @return array Untangled files
301
     */
302
    protected function untangleFilesArray(array $convolutedFiles)
303
    {
304
        $untangledFiles = [];
305
        $fieldPaths = [];
306
        foreach ($convolutedFiles as $firstLevelFieldName => $fieldInformation) {
307
            if (!is_array($fieldInformation['error'])) {
308
                $fieldPaths[] = [$firstLevelFieldName];
309
            } else {
310
                $newFieldPaths = $this->calculateFieldPaths($fieldInformation['error'], $firstLevelFieldName);
311
                array_walk($newFieldPaths, function (&$value, $key) {
312
                    $value = explode('/', $value);
313
                });
314
                $fieldPaths = array_merge($fieldPaths, $newFieldPaths);
315
            }
316
        }
317
        foreach ($fieldPaths as $fieldPath) {
318
            if (count($fieldPath) === 1) {
319
                $fileInformation = $convolutedFiles[$fieldPath[0]];
320
            } else {
321
                $fileInformation = [];
322
                foreach ($convolutedFiles[$fieldPath[0]] as $key => $subStructure) {
323
                    try {
324
                        $fileInformation[$key] = ArrayUtility::getValueByPath($subStructure, array_slice($fieldPath, 1));
325
                    } catch (MissingArrayPathException $e) {
326
                        // do nothing if the path is invalid
327
                    }
328
                }
329
            }
330
            $untangledFiles = ArrayUtility::setValueByPath($untangledFiles, $fieldPath, $fileInformation);
331
        }
332
        return $untangledFiles;
333
    }
334
335
    /**
336
     * Returns an array of all possibles "field paths" for the given array.
337
     *
338
     * @param array $structure The array to walk through
339
     * @param string $firstLevelFieldName
340
     * @return array An array of paths (as strings) in the format "key1/key2/key3" ...
341
     */
342
    protected function calculateFieldPaths(array $structure, $firstLevelFieldName = null)
343
    {
344
        $fieldPaths = [];
345
        if (is_array($structure)) {
0 ignored issues
show
introduced by
The condition is_array($structure) is always true.
Loading history...
346
            foreach ($structure as $key => $subStructure) {
347
                $fieldPath = ($firstLevelFieldName !== null ? $firstLevelFieldName . '/' : '') . $key;
348
                if (is_array($subStructure)) {
349
                    foreach ($this->calculateFieldPaths($subStructure) as $subFieldPath) {
350
                        $fieldPaths[] = $fieldPath . '/' . $subFieldPath;
351
                    }
352
                } else {
353
                    $fieldPaths[] = $fieldPath;
354
                }
355
            }
356
        }
357
        return $fieldPaths;
358
    }
359
}
360