Completed
Push — master ( 006b54...a554da )
by
unknown
12:54
created

RequestBuilder::build()   B

Complexity

Conditions 9
Paths 48

Size

Total Lines 53
Code Lines 38

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
eloc 38
c 0
b 0
f 0
nc 48
nop 0
dl 0
loc 53
rs 7.7564

How to fix   Long Method   

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