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