58e12f5ec2ef38980070c825e0c93202e5067f46
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Routing / PageArguments.php
1 <?php
2 declare(strict_types = 1);
3
4 namespace TYPO3\CMS\Core\Routing;
5
6 /*
7 * This file is part of the TYPO3 CMS project.
8 *
9 * It is free software; you can redistribute it and/or modify it under
10 * the terms of the GNU General Public License, either version 2
11 * of the License, or any later version.
12 *
13 * For the full copyright and license information, please read the
14 * LICENSE.txt file that was distributed with this source code.
15 *
16 * The TYPO3 project - inspiring people to share!
17 */
18
19 use TYPO3\CMS\Core\Utility\ArrayUtility;
20
21 /**
22 * Contains all resolved parameters when a page is resolved from a page path segment plus all fragments.
23 */
24 class PageArguments implements RouteResultInterface
25 {
26 /**
27 * @var int
28 */
29 protected $pageId;
30
31 /**
32 * @var array
33 */
34 protected $arguments;
35
36 /**
37 * @var array
38 */
39 protected $staticArguments;
40
41 /**
42 * @var array
43 */
44 protected $dynamicArguments;
45
46 /**
47 * @var array
48 */
49 protected $routeArguments;
50
51 /**
52 * @var array
53 */
54 protected $queryArguments = [];
55
56 /**
57 * @var bool
58 */
59 protected $dirty = false;
60
61 /**
62 * @param int $pageId
63 * @param array $routeArguments
64 * @param array $staticArguments
65 * @param array $remainingArguments
66 */
67 public function __construct(int $pageId, array $routeArguments, array $staticArguments = [], array $remainingArguments = [])
68 {
69 $this->pageId = $pageId;
70 $this->routeArguments = $this->sort($routeArguments);
71 $this->staticArguments = $this->sort($staticArguments);
72 $this->arguments = $this->routeArguments;
73 $this->updateDynamicArguments();
74 if (!empty($remainingArguments)) {
75 $this->updateQueryArguments($remainingArguments);
76 }
77 }
78
79 /**
80 * @return bool
81 */
82 public function areDirty(): bool
83 {
84 return $this->dirty;
85 }
86
87 /**
88 * @return array
89 */
90 public function getRouteArguments(): array
91 {
92 return $this->routeArguments;
93 }
94
95 /**
96 * @return int
97 */
98 public function getPageId(): int
99 {
100 return $this->pageId;
101 }
102
103 /**
104 * @param string $name
105 * @return mixed|null
106 */
107 public function get(string $name)
108 {
109 return $this->arguments[$name] ?? null;
110 }
111
112 /**
113 * @return array
114 */
115 public function getArguments(): array
116 {
117 return $this->arguments;
118 }
119
120 /**
121 * @return array
122 */
123 public function getStaticArguments(): array
124 {
125 return $this->staticArguments;
126 }
127
128 /**
129 * @return array
130 */
131 public function getDynamicArguments(): array
132 {
133 return $this->dynamicArguments;
134 }
135
136 /**
137 * @return array
138 */
139 public function getQueryArguments(): array
140 {
141 return $this->queryArguments;
142 }
143
144 /**
145 * @param array $queryArguments
146 * @return static
147 */
148 public function withQueryArguments(array $queryArguments): self
149 {
150 $queryArguments = $this->sort($queryArguments);
151 if ($this->queryArguments === $queryArguments) {
152 return $this;
153 }
154 // in case query arguments would override route arguments,
155 // the state is considered as dirty (since it's not distinct)
156 // thus, route arguments take precedence over query arguments
157 $additionalQueryArguments = $this->diff($queryArguments, $this->routeArguments);
158 $dirty = $additionalQueryArguments !== $queryArguments;
159 // apply changes
160 $target = clone $this;
161 $target->dirty = $this->dirty || $dirty;
162 $target->queryArguments = $queryArguments;
163 $target->arguments = array_replace_recursive($target->arguments, $additionalQueryArguments);
164 $target->updateDynamicArguments();
165 return $target;
166 }
167
168 /**
169 * @param array $queryArguments
170 */
171 protected function updateQueryArguments(array $queryArguments)
172 {
173 $queryArguments = $this->sort($queryArguments);
174 if ($this->queryArguments === $queryArguments) {
175 return;
176 }
177 // in case query arguments would override route arguments,
178 // the state is considered as dirty (since it's not distinct)
179 // thus, route arguments take precedence over query arguments
180 $additionalQueryArguments = $this->diff($queryArguments, $this->routeArguments);
181 $dirty = $additionalQueryArguments !== $queryArguments;
182 $this->dirty = $this->dirty || $dirty;
183 $this->queryArguments = $queryArguments;
184 $this->arguments = array_replace_recursive($this->arguments, $additionalQueryArguments);
185 $this->updateDynamicArguments();
186 }
187
188 /**
189 * Updates dynamic arguments based on definitions for static arguments.
190 */
191 protected function updateDynamicArguments(): void
192 {
193 $this->dynamicArguments = $this->diff(
194 $this->arguments,
195 $this->staticArguments
196 );
197 }
198
199 /**
200 * Cleans empty array recursively.
201 *
202 * @param array $array
203 * @return array
204 */
205 protected function clean(array $array): array
206 {
207 foreach ($array as $key => &$item) {
208 if (!is_array($item)) {
209 continue;
210 }
211 if (!empty($item)) {
212 $item = $this->clean($item);
213 }
214 if (empty($item)) {
215 unset($array[$key]);
216 }
217 }
218 return $array;
219 }
220
221 /**
222 * Sorts array keys recursively.
223 *
224 * @param array $array
225 * @return array
226 */
227 protected function sort(array $array): array
228 {
229 $array = $this->clean($array);
230 ArrayUtility::naturalKeySortRecursive($array);
231 return $array;
232 }
233
234 /**
235 * Removes keys that are defined in $second from $first recursively.
236 *
237 * @param array $first
238 * @param array $second
239 * @return array
240 */
241 protected function diff(array $first, array $second): array
242 {
243 return ArrayUtility::arrayDiffAssocRecursive($first, $second);
244 }
245
246 /**
247 * @param mixed $offset
248 * @return bool
249 */
250 public function offsetExists($offset): bool
251 {
252 return $offset === 'pageId' || isset($this->arguments[$offset]);
253 }
254
255 /**
256 * @param mixed $offset
257 * @return mixed
258 */
259 public function offsetGet($offset)
260 {
261 if ($offset === 'pageId') {
262 return $this->getPageId();
263 }
264 return $this->arguments[$offset] ?? null;
265 }
266
267 /**
268 * @param mixed $offset
269 * @param mixed $value
270 */
271 public function offsetSet($offset, $value)
272 {
273 throw new \InvalidArgumentException('PageArguments cannot be modified.', 1538152266);
274 }
275
276 /**
277 * @param mixed $offset
278 */
279 public function offsetUnset($offset)
280 {
281 throw new \InvalidArgumentException('PageArguments cannot be modified.', 1538152269);
282 }
283 }