[TASK] Make extbase/Tests/Unit/Mvc/Web/RequestBuilderTest.php notice free
[Packages/TYPO3.CMS.git] / typo3 / sysext / extbase / Classes / Mvc / Web / RequestBuilder.php
1 <?php
2 namespace TYPO3\CMS\Extbase\Mvc\Web;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use TYPO3\CMS\Core\Utility\ArrayUtility;
18 use TYPO3\CMS\Core\Utility\Exception\MissingArrayPathException;
19 use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface;
20 use TYPO3\CMS\Extbase\Mvc\Exception as MvcException;
21
22 /**
23 * Builds a web request.
24 */
25 class RequestBuilder implements \TYPO3\CMS\Core\SingletonInterface
26 {
27 /**
28 * @var \TYPO3\CMS\Extbase\Object\ObjectManagerInterface
29 */
30 protected $objectManager;
31
32 /**
33 * This is the vendor name of the extension
34 *
35 * @var string
36 */
37 protected $vendorName;
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 default controller name
55 *
56 * @var string
57 */
58 protected $defaultControllerName = '';
59
60 /**
61 * The default format of the response object
62 *
63 * @var string
64 */
65 protected $defaultFormat = 'html';
66
67 /**
68 * The allowed actions of the controller. This actions can be called via $_GET and $_POST.
69 *
70 * @var array
71 */
72 protected $allowedControllerActions = [];
73
74 /**
75 * @var \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface
76 */
77 protected $configurationManager;
78
79 /**
80 * @var \TYPO3\CMS\Extbase\Service\ExtensionService
81 */
82 protected $extensionService;
83
84 /**
85 * @var \TYPO3\CMS\Extbase\Service\EnvironmentService
86 */
87 protected $environmentService;
88
89 /**
90 * @param \TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager
91 */
92 public function injectObjectManager(\TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager)
93 {
94 $this->objectManager = $objectManager;
95 }
96
97 /**
98 * @param \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface $configurationManager
99 */
100 public function injectConfigurationManager(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface $configurationManager)
101 {
102 $this->configurationManager = $configurationManager;
103 }
104
105 /**
106 * @param \TYPO3\CMS\Extbase\Service\ExtensionService $extensionService
107 */
108 public function injectExtensionService(\TYPO3\CMS\Extbase\Service\ExtensionService $extensionService)
109 {
110 $this->extensionService = $extensionService;
111 }
112
113 /**
114 * @param \TYPO3\CMS\Extbase\Service\EnvironmentService $environmentService
115 */
116 public function injectEnvironmentService(\TYPO3\CMS\Extbase\Service\EnvironmentService $environmentService)
117 {
118 $this->environmentService = $environmentService;
119 }
120
121 /**
122 * @throws MvcException
123 */
124 protected function loadDefaultValues()
125 {
126 $configuration = $this->configurationManager->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK);
127 if (empty($configuration['extensionName'])) {
128 throw new MvcException('"extensionName" is not properly configured. Request can\'t be dispatched!', 1289843275);
129 }
130 if (empty($configuration['pluginName'])) {
131 throw new MvcException('"pluginName" is not properly configured. Request can\'t be dispatched!', 1289843277);
132 }
133 if (!empty($configuration['vendorName'])) {
134 $this->vendorName = $configuration['vendorName'];
135 } else {
136 $this->vendorName = null;
137 }
138 $this->extensionName = $configuration['extensionName'];
139 $this->pluginName = $configuration['pluginName'];
140 $this->defaultControllerName = (string)current(array_keys($configuration['controllerConfiguration']));
141 $this->allowedControllerActions = [];
142 foreach ($configuration['controllerConfiguration'] as $controllerName => $controllerActions) {
143 $this->allowedControllerActions[$controllerName] = $controllerActions['actions'] ?? null;
144 }
145 if (!empty($configuration['format'])) {
146 $this->defaultFormat = $configuration['format'];
147 }
148 }
149
150 /**
151 * Builds a web request object from the raw HTTP information and the configuration
152 *
153 * @return \TYPO3\CMS\Extbase\Mvc\Web\Request The web request as an object
154 */
155 public function build()
156 {
157 $this->loadDefaultValues();
158 $pluginNamespace = $this->extensionService->getPluginNamespace($this->extensionName, $this->pluginName);
159 $parameters = \TYPO3\CMS\Core\Utility\GeneralUtility::_GPmerged($pluginNamespace);
160 $files = $this->untangleFilesArray($_FILES);
161 if (isset($files[$pluginNamespace]) && is_array($files[$pluginNamespace])) {
162 $parameters = array_replace_recursive($parameters, $files[$pluginNamespace]);
163 }
164 $controllerName = $this->resolveControllerName($parameters);
165 $actionName = $this->resolveActionName($controllerName, $parameters);
166 /** @var $request \TYPO3\CMS\Extbase\Mvc\Web\Request */
167 $request = $this->objectManager->get(\TYPO3\CMS\Extbase\Mvc\Web\Request::class);
168 if ($this->vendorName !== null) {
169 $request->setControllerVendorName($this->vendorName);
170 }
171 $request->setPluginName($this->pluginName);
172 $request->setControllerExtensionName($this->extensionName);
173 $request->setControllerName($controllerName);
174 $request->setControllerActionName($actionName);
175 $request->setRequestUri(\TYPO3\CMS\Core\Utility\GeneralUtility::getIndpEnv('TYPO3_REQUEST_URL'));
176 $request->setBaseUri(\TYPO3\CMS\Core\Utility\GeneralUtility::getIndpEnv('TYPO3_SITE_URL'));
177 $request->setMethod($this->environmentService->getServerRequestMethod());
178 if (isset($parameters['format']) && is_string($parameters['format']) && $parameters['format'] !== '') {
179 $request->setFormat(filter_var($parameters['format'], FILTER_SANITIZE_STRING));
180 } else {
181 $request->setFormat($this->defaultFormat);
182 }
183 foreach ($parameters as $argumentName => $argumentValue) {
184 $request->setArgument($argumentName, $argumentValue);
185 }
186 return $request;
187 }
188
189 /**
190 * Returns the current ControllerName extracted from given $parameters.
191 * If no controller is specified, the defaultControllerName will be returned.
192 * If that's not available, an exception is thrown.
193 *
194 * @param array $parameters
195 * @throws \TYPO3\CMS\Extbase\Mvc\Exception\InvalidControllerNameException
196 * @throws MvcException if the controller could not be resolved
197 * @throws \TYPO3\CMS\Core\Error\Http\PageNotFoundException
198 * @return string
199 */
200 protected function resolveControllerName(array $parameters)
201 {
202 if (!isset($parameters['controller']) || $parameters['controller'] === '') {
203 if (empty($this->defaultControllerName)) {
204 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);
205 }
206 return $this->defaultControllerName;
207 }
208 $allowedControllerNames = array_keys($this->allowedControllerActions);
209 if (!in_array($parameters['controller'], $allowedControllerNames)) {
210 $configuration = $this->configurationManager->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK);
211 if (isset($configuration['mvc']['throwPageNotFoundExceptionIfActionCantBeResolved']) && (bool)$configuration['mvc']['throwPageNotFoundExceptionIfActionCantBeResolved']) {
212 throw new \TYPO3\CMS\Core\Error\Http\PageNotFoundException('The requested resource was not found', 1313857897);
213 }
214 if (isset($configuration['mvc']['callDefaultActionIfActionCantBeResolved']) && (bool)$configuration['mvc']['callDefaultActionIfActionCantBeResolved']) {
215 return $this->defaultControllerName;
216 }
217 throw new \TYPO3\CMS\Extbase\Mvc\Exception\InvalidControllerNameException(
218 '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.',
219 1313855173
220 );
221 }
222 return filter_var($parameters['controller'], FILTER_SANITIZE_STRING);
223 }
224
225 /**
226 * Returns the current actionName extracted from given $parameters.
227 * If no action is specified, the defaultActionName will be returned.
228 * If that's not available or the specified action is not defined in the current plugin, an exception is thrown.
229 *
230 * @param string $controllerName
231 * @param array $parameters
232 * @throws \TYPO3\CMS\Extbase\Mvc\Exception\InvalidActionNameException
233 * @throws MvcException
234 * @throws \TYPO3\CMS\Core\Error\Http\PageNotFoundException
235 * @return string
236 */
237 protected function resolveActionName($controllerName, array $parameters)
238 {
239 $defaultActionName = is_array($this->allowedControllerActions[$controllerName]) ? current($this->allowedControllerActions[$controllerName]) : '';
240 if (!isset($parameters['action']) || $parameters['action'] === '') {
241 if ($defaultActionName === '') {
242 throw new MvcException('The default action can not be determined for controller "' . $controllerName . '". Please check TYPO3\\CMS\\Extbase\\Utility\\ExtensionUtility::configurePlugin() in your ext_localconf.php.', 1295479651);
243 }
244 return $defaultActionName;
245 }
246 $actionName = $parameters['action'];
247 $allowedActionNames = $this->allowedControllerActions[$controllerName];
248 if (!in_array($actionName, $allowedActionNames)) {
249 $configuration = $this->configurationManager->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK);
250 if (isset($configuration['mvc']['throwPageNotFoundExceptionIfActionCantBeResolved']) && (bool)$configuration['mvc']['throwPageNotFoundExceptionIfActionCantBeResolved']) {
251 throw new \TYPO3\CMS\Core\Error\Http\PageNotFoundException('The requested resource was not found', 1313857898);
252 }
253 if (isset($configuration['mvc']['callDefaultActionIfActionCantBeResolved']) && (bool)$configuration['mvc']['callDefaultActionIfActionCantBeResolved']) {
254 return $defaultActionName;
255 }
256 throw new \TYPO3\CMS\Extbase\Mvc\Exception\InvalidActionNameException('The action "' . $actionName . '" (controller "' . $controllerName . '") is not allowed by this plugin. Please check TYPO3\\CMS\\Extbase\\Utility\\ExtensionUtility::configurePlugin() in your ext_localconf.php.', 1313855175);
257 }
258 return filter_var($actionName, FILTER_SANITIZE_STRING);
259 }
260
261 /**
262 * Transforms the convoluted _FILES superglobal into a manageable form.
263 *
264 * @param array $convolutedFiles The _FILES superglobal
265 * @return array Untangled files
266 */
267 protected function untangleFilesArray(array $convolutedFiles)
268 {
269 $untangledFiles = [];
270 $fieldPaths = [];
271 foreach ($convolutedFiles as $firstLevelFieldName => $fieldInformation) {
272 if (!is_array($fieldInformation['error'])) {
273 $fieldPaths[] = [$firstLevelFieldName];
274 } else {
275 $newFieldPaths = $this->calculateFieldPaths($fieldInformation['error'], $firstLevelFieldName);
276 array_walk($newFieldPaths, function (&$value, $key) {
277 $value = explode('/', $value);
278 });
279 $fieldPaths = array_merge($fieldPaths, $newFieldPaths);
280 }
281 }
282 foreach ($fieldPaths as $fieldPath) {
283 if (count($fieldPath) === 1) {
284 $fileInformation = $convolutedFiles[$fieldPath[0]];
285 } else {
286 $fileInformation = [];
287 foreach ($convolutedFiles[$fieldPath[0]] as $key => $subStructure) {
288 try {
289 $fileInformation[$key] = ArrayUtility::getValueByPath($subStructure, array_slice($fieldPath, 1));
290 } catch (MissingArrayPathException $e) {
291 // do nothing if the path is invalid
292 }
293 }
294 }
295 $untangledFiles = ArrayUtility::setValueByPath($untangledFiles, $fieldPath, $fileInformation);
296 }
297 return $untangledFiles;
298 }
299
300 /**
301 * Returns an array of all possibles "field paths" for the given array.
302 *
303 * @param array $structure The array to walk through
304 * @param string $firstLevelFieldName
305 * @return array An array of paths (as strings) in the format "key1/key2/key3" ...
306 */
307 protected function calculateFieldPaths(array $structure, $firstLevelFieldName = null)
308 {
309 $fieldPaths = [];
310 if (is_array($structure)) {
311 foreach ($structure as $key => $subStructure) {
312 $fieldPath = ($firstLevelFieldName !== null ? $firstLevelFieldName . '/' : '') . $key;
313 if (is_array($subStructure)) {
314 foreach ($this->calculateFieldPaths($subStructure) as $subFieldPath) {
315 $fieldPaths[] = $fieldPath . '/' . $subFieldPath;
316 }
317 } else {
318 $fieldPaths[] = $fieldPath;
319 }
320 }
321 }
322 return $fieldPaths;
323 }
324 }