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
16namespace TYPO3\CMS\Extbase\Mvc;
17
18use TYPO3\CMS\Core\Utility\ClassNamingUtility;
19use TYPO3\CMS\Core\Utility\GeneralUtility;
20use TYPO3\CMS\Extbase\Error\Result;
21use TYPO3\CMS\Extbase\Mvc\Exception\InvalidActionNameException;
22use TYPO3\CMS\Extbase\Mvc\Exception\InvalidArgumentNameException;
23use TYPO3\CMS\Extbase\Mvc\Exception\InvalidControllerNameException;
24use TYPO3\CMS\Extbase\Mvc\Exception\InvalidRequestMethodException;
25use TYPO3\CMS\Extbase\Mvc\Exception\NoSuchArgumentException;
26
27/**
28 * Represents a generic request.
29 */
30class Request implements RequestInterface
31{
32    const PATTERN_MATCH_FORMAT = '/^[a-z0-9]{1,5}$/';
33
34    /**
35     * @var string Key of the plugin which identifies the plugin. It must be a string containing [a-z0-9]
36     */
37    protected $pluginName = '';
38
39    /**
40     * @var string Name of the extension which is supposed to handle this request. This is the extension name converted to UpperCamelCase
41     */
42    protected $controllerExtensionName;
43
44    /**
45     * Subpackage key of the controller which is supposed to handle this request.
46     *
47     * @var string
48     */
49    protected $controllerSubpackageKey;
50
51    /**
52     * @var string
53     */
54    protected $controllerObjectName;
55
56    /**
57     * @var string Object name of the controller which is supposed to handle this request.
58     */
59    protected $controllerName = 'Standard';
60
61    /**
62     * @var string Name of the action the controller is supposed to take.
63     */
64    protected $controllerActionName = 'index';
65
66    /**
67     * @var array The arguments for this request
68     */
69    protected $arguments = [];
70
71    /**
72     * Framework-internal arguments for this request, such as __referrer.
73     * All framework-internal arguments start with double underscore (__),
74     * and are only used from within the framework. Not for user consumption.
75     * Internal Arguments can be objects, in contrast to public arguments
76     *
77     * @var array
78     */
79    protected $internalArguments = [];
80
81    /**
82     * @var string The requested representation format
83     */
84    protected $format = 'txt';
85
86    /**
87     * @var bool If this request has been changed and needs to be dispatched again
88     */
89    protected $dispatched = false;
90
91    /**
92     * If this request is a forward because of an error, the original request gets filled.
93     *
94     * @var \TYPO3\CMS\Extbase\Mvc\Request
95     */
96    protected $originalRequest;
97
98    /**
99     * If the request is a forward because of an error, these mapping results get filled here.
100     *
101     * @var \TYPO3\CMS\Extbase\Error\Result
102     */
103    protected $originalRequestMappingResults;
104
105    /**
106     * @var string Contains the request method
107     */
108    protected $method = 'GET';
109
110    /**
111     * @var string
112     */
113    protected $requestUri;
114
115    /**
116     * @var string The base URI for this request - ie. the host and path leading to the index.php
117     */
118    protected $baseUri;
119
120    /**
121     * @var bool TRUE if the current request is cached, false otherwise.
122     */
123    protected $isCached = false;
124
125    /**
126     * Sets the dispatched flag
127     *
128     * @param bool $flag If this request has been dispatched
129     */
130    public function setDispatched($flag)
131    {
132        $this->dispatched = (bool)$flag;
133    }
134
135    /**
136     * If this request has been dispatched and addressed by the responsible
137     * controller and the response is ready to be sent.
138     *
139     * The dispatcher will try to dispatch the request again if it has not been
140     * addressed yet.
141     *
142     * @return bool TRUE if this request has been dispatched successfully
143     */
144    public function isDispatched()
145    {
146        return $this->dispatched;
147    }
148
149    /**
150     * @param string $controllerClassName
151     */
152    public function __construct(string $controllerClassName = '')
153    {
154        $this->controllerObjectName = $controllerClassName;
155    }
156
157    /**
158     * @return string
159     */
160    public function getControllerObjectName(): string
161    {
162        return $this->controllerObjectName;
163    }
164
165    /**
166     * Explicitly sets the object name of the controller
167     *
168     * @param string $controllerObjectName The fully qualified controller object name
169     * @internal only to be used within Extbase, not part of TYPO3 Core API.
170     */
171    public function setControllerObjectName($controllerObjectName)
172    {
173        $nameParts = ClassNamingUtility::explodeObjectControllerName($controllerObjectName);
174        $this->controllerExtensionName = $nameParts['extensionName'];
175        $this->controllerSubpackageKey = $nameParts['subpackageKey'] ?? null;
176        $this->controllerName = $nameParts['controllerName'];
177    }
178
179    /**
180     * Sets the plugin name.
181     *
182     * @param string|null $pluginName
183     * @internal only to be used within Extbase, not part of TYPO3 Core API.
184     */
185    public function setPluginName($pluginName = null)
186    {
187        if ($pluginName !== null) {
188            $this->pluginName = $pluginName;
189        }
190    }
191
192    /**
193     * Returns the plugin key.
194     *
195     * @return string The plugin key
196     */
197    public function getPluginName()
198    {
199        return $this->pluginName;
200    }
201
202    /**
203     * Sets the extension name of the controller.
204     *
205     * @param string $controllerExtensionName The extension name.
206     * @throws \TYPO3\CMS\Extbase\Mvc\Exception\InvalidExtensionNameException if the extension name is not valid
207     * @internal only to be used within Extbase, not part of TYPO3 Core API.
208     */
209    public function setControllerExtensionName($controllerExtensionName)
210    {
211        if ($controllerExtensionName !== null) {
212            $this->controllerExtensionName = $controllerExtensionName;
213        }
214    }
215
216    /**
217     * Returns the extension name of the specified controller.
218     *
219     * @return string The extension name
220     */
221    public function getControllerExtensionName()
222    {
223        return $this->controllerExtensionName;
224    }
225
226    /**
227     * Returns the extension name of the specified controller.
228     *
229     * @return string The extension key
230     */
231    public function getControllerExtensionKey()
232    {
233        return GeneralUtility::camelCaseToLowerCaseUnderscored($this->controllerExtensionName);
234    }
235
236    /**
237     * Sets the subpackage key of the controller.
238     *
239     * @param string $subpackageKey The subpackage key.
240     * @internal only to be used within Extbase, not part of TYPO3 Core API.
241     */
242    public function setControllerSubpackageKey($subpackageKey)
243    {
244        $this->controllerSubpackageKey = $subpackageKey;
245    }
246
247    /**
248     * Returns the subpackage key of the specified controller.
249     * If there is no subpackage key set, the method returns NULL
250     *
251     * @return string The subpackage key
252     * @internal only to be used within Extbase, not part of TYPO3 Core API.
253     */
254    public function getControllerSubpackageKey()
255    {
256        return $this->controllerSubpackageKey;
257    }
258
259    /**
260     * @var array
261     */
262    protected $controllerAliasToClassNameMapping = [];
263
264    /**
265     * @param array $controllerAliasToClassNameMapping
266     */
267    public function setControllerAliasToClassNameMapping(array $controllerAliasToClassNameMapping)
268    {
269        // this is only needed as long as forwarded requests are altered and unless there
270        // is no new request object created by the request builder.
271        $this->controllerAliasToClassNameMapping = $controllerAliasToClassNameMapping;
272    }
273
274    /**
275     * Sets the name of the controller which is supposed to handle the request.
276     * Note: This is not the object name of the controller!
277     *
278     * @param string $controllerName Name of the controller
279     * @throws Exception\InvalidControllerNameException
280     * @internal only to be used within Extbase, not part of TYPO3 Core API.
281     */
282    public function setControllerName($controllerName)
283    {
284        if (!is_string($controllerName) && $controllerName !== null) {
285            throw new InvalidControllerNameException('The controller name must be a valid string, ' . gettype($controllerName) . ' given.', 1187176358);
286        }
287        if ($controllerName !== null) {
288            $this->controllerName = $controllerName;
289            $this->controllerObjectName = $this->controllerAliasToClassNameMapping[$controllerName] ?? '';
290            // There might be no Controller Class, for example for Fluid Templates.
291        }
292    }
293
294    /**
295     * Returns the object name of the controller supposed to handle this request, if one
296     * was set already (if not, the name of the default controller is returned)
297     *
298     * @return string Object name of the controller
299     */
300    public function getControllerName()
301    {
302        return $this->controllerName;
303    }
304
305    /**
306     * Sets the name of the action contained in this request.
307     *
308     * Note that the action name must start with a lower case letter and is case sensitive.
309     *
310     * @param string $actionName Name of the action to execute by the controller
311     * @throws \TYPO3\CMS\Extbase\Mvc\Exception\InvalidActionNameException if the action name is not valid
312     * @internal only to be used within Extbase, not part of TYPO3 Core API.
313     */
314    public function setControllerActionName($actionName)
315    {
316        if (!is_string($actionName) && $actionName !== null) {
317            throw new InvalidActionNameException('The action name must be a valid string, ' . gettype($actionName) . ' given (' . $actionName . ').', 1187176359);
318        }
319        if ($actionName[0] !== strtolower($actionName[0]) && $actionName !== null) {
320            throw new InvalidActionNameException('The action name must start with a lower case letter, "' . $actionName . '" does not match this criteria.', 1218473352);
321        }
322        if ($actionName !== null) {
323            $this->controllerActionName = $actionName;
324        }
325    }
326
327    /**
328     * Returns the name of the action the controller is supposed to execute.
329     *
330     * @return string Action name
331     */
332    public function getControllerActionName()
333    {
334        $controllerObjectName = $this->getControllerObjectName();
335        if ($controllerObjectName !== '' && $this->controllerActionName === strtolower($this->controllerActionName)) {
336            // todo: this is nonsense! We can detect a non existing method in
337            // todo: \TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin, if necessary.
338            // todo: At this point, we want to have a getter for a fixed value.
339            $actionMethodName = $this->controllerActionName . 'Action';
340            $classMethods = get_class_methods($controllerObjectName);
341            if (is_array($classMethods)) {
342                foreach ($classMethods as $existingMethodName) {
343                    if (strtolower($existingMethodName) === strtolower($actionMethodName)) {
344                        $this->controllerActionName = substr($existingMethodName, 0, -6);
345                        break;
346                    }
347                }
348            }
349        }
350        return $this->controllerActionName;
351    }
352
353    /**
354     * Sets the value of the specified argument
355     *
356     * @param string $argumentName Name of the argument to set
357     * @param mixed $value The new value
358     * @throws Exception\InvalidArgumentNameException
359     * @internal only to be used within Extbase, not part of TYPO3 Core API.
360     */
361    public function setArgument($argumentName, $value)
362    {
363        if (!is_string($argumentName) || $argumentName === '') {
364            throw new InvalidArgumentNameException('Invalid argument name.', 1210858767);
365        }
366        if ($argumentName[0] === '_' && $argumentName[1] === '_') {
367            $this->internalArguments[$argumentName] = $value;
368            return;
369        }
370        if (!in_array($argumentName, ['@extension', '@subpackage', '@controller', '@action', '@format'], true)) {
371            $this->arguments[$argumentName] = $value;
372        }
373    }
374
375    /**
376     * Sets the whole arguments array and therefore replaces any arguments
377     * which existed before.
378     *
379     * @param array $arguments An array of argument names and their values
380     * @internal only to be used within Extbase, not part of TYPO3 Core API.
381     */
382    public function setArguments(array $arguments)
383    {
384        $this->arguments = [];
385        foreach ($arguments as $argumentName => $argumentValue) {
386            $this->setArgument($argumentName, $argumentValue);
387        }
388    }
389
390    /**
391     * Returns an array of arguments and their values
392     *
393     * @return array Associative array of arguments and their values (which may be arguments and values as well)
394     */
395    public function getArguments()
396    {
397        return $this->arguments;
398    }
399
400    /**
401     * Returns the value of the specified argument
402     *
403     * @param string $argumentName Name of the argument
404     *
405     * @return string|array Value of the argument
406     * @throws \TYPO3\CMS\Extbase\Mvc\Exception\NoSuchArgumentException if such an argument does not exist
407     */
408    public function getArgument($argumentName)
409    {
410        if (!isset($this->arguments[$argumentName])) {
411            throw new NoSuchArgumentException('An argument "' . $argumentName . '" does not exist for this request.', 1176558158);
412        }
413        return $this->arguments[$argumentName];
414    }
415
416    /**
417     * Checks if an argument of the given name exists (is set)
418     *
419     * @param string $argumentName Name of the argument to check
420     *
421     * @return bool TRUE if the argument is set, otherwise FALSE
422     */
423    public function hasArgument($argumentName)
424    {
425        return isset($this->arguments[$argumentName]);
426    }
427
428    /**
429     * Sets the requested representation format
430     *
431     * @param string $format The desired format, something like "html", "xml", "png", "json" or the like. Can even be something like "rss.xml".
432     * @internal only to be used within Extbase, not part of TYPO3 Core API.
433     */
434    public function setFormat($format)
435    {
436        $this->format = $format;
437    }
438
439    /**
440     * Returns the requested representation format
441     *
442     * @return string The desired format, something like "html", "xml", "png", "json" or the like.
443     */
444    public function getFormat()
445    {
446        return $this->format;
447    }
448
449    /**
450     * Returns the original request. Filled only if a property mapping error occurred.
451     *
452     * @return \TYPO3\CMS\Extbase\Mvc\Request the original request.
453     * @internal only to be used within Extbase, not part of TYPO3 Core API.
454     */
455    public function getOriginalRequest()
456    {
457        return $this->originalRequest;
458    }
459
460    /**
461     * @param \TYPO3\CMS\Extbase\Mvc\Request $originalRequest
462     * @internal only to be used within Extbase, not part of TYPO3 Core API.
463     */
464    public function setOriginalRequest(\TYPO3\CMS\Extbase\Mvc\Request $originalRequest)
465    {
466        $this->originalRequest = $originalRequest;
467    }
468
469    /**
470     * Get the request mapping results for the original request.
471     *
472     * @return \TYPO3\CMS\Extbase\Error\Result
473     * @internal only to be used within Extbase, not part of TYPO3 Core API.
474     */
475    public function getOriginalRequestMappingResults()
476    {
477        if ($this->originalRequestMappingResults === null) {
478            return new Result();
479        }
480        return $this->originalRequestMappingResults;
481    }
482
483    /**
484     * @param \TYPO3\CMS\Extbase\Error\Result $originalRequestMappingResults
485     * @internal only to be used within Extbase, not part of TYPO3 Core API.
486     */
487    public function setOriginalRequestMappingResults(Result $originalRequestMappingResults)
488    {
489        $this->originalRequestMappingResults = $originalRequestMappingResults;
490    }
491
492    /**
493     * Get the internal arguments of the request, i.e. every argument starting
494     * with two underscores.
495     *
496     * @return array
497     * @internal only to be used within Extbase, not part of TYPO3 Core API.
498     */
499    public function getInternalArguments()
500    {
501        return $this->internalArguments;
502    }
503
504    /**
505     * Returns the value of the specified argument
506     *
507     * @param string $argumentName Name of the argument
508     * @return string Value of the argument, or NULL if not set.
509     * @internal only to be used within Extbase, not part of TYPO3 Core API.
510     */
511    public function getInternalArgument($argumentName)
512    {
513        if (!isset($this->internalArguments[$argumentName])) {
514            return null;
515        }
516        return $this->internalArguments[$argumentName];
517    }
518
519    /**
520     * Sets the request method
521     *
522     * @param string $method Name of the request method
523     * @throws \TYPO3\CMS\Extbase\Mvc\Exception\InvalidRequestMethodException if the request method is not supported
524     * @internal only to be used within Extbase, not part of TYPO3 Core API.
525     */
526    public function setMethod($method)
527    {
528        if ($method === '' || strtoupper($method) !== $method) {
529            throw new InvalidRequestMethodException('The request method "' . $method . '" is not supported.', 1217778382);
530        }
531        $this->method = $method;
532    }
533
534    /**
535     * Returns the name of the request method
536     *
537     * @return string Name of the request method
538     */
539    public function getMethod()
540    {
541        return $this->method;
542    }
543
544    /**
545     * Sets the request URI
546     *
547     * @param string $requestUri URI of this web request
548     * @internal only to be used within Extbase, not part of TYPO3 Core API.
549     */
550    public function setRequestUri($requestUri)
551    {
552        $this->requestUri = $requestUri;
553    }
554
555    /**
556     * Returns the request URI
557     *
558     * @return string URI of this web request
559     */
560    public function getRequestUri()
561    {
562        return $this->requestUri;
563    }
564
565    /**
566     * Sets the base URI for this request.
567     *
568     * @param string $baseUri New base URI
569     * @internal only to be used within Extbase, not part of TYPO3 Core API.
570     */
571    public function setBaseUri($baseUri)
572    {
573        $this->baseUri = $baseUri;
574    }
575
576    /**
577     * Returns the base URI
578     *
579     * @return string Base URI of this web request
580     */
581    public function getBaseUri()
582    {
583        return $this->baseUri;
584    }
585
586    /**
587     * Set if the current request is cached.
588     *
589     * @param bool $isCached
590     * @internal only to be used within Extbase, not part of TYPO3 Core API.
591     */
592    public function setIsCached($isCached)
593    {
594        $this->isCached = (bool)$isCached;
595    }
596
597    /**
598     * Return whether the current request is a cached request or not.
599     *
600     * @return bool the caching status.
601     * @internal only to be used within Extbase, not part of TYPO3 Core API.
602     */
603    public function isCached()
604    {
605        return $this->isCached;
606    }
607}
608