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