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\Extbase\Mvc\Web\Routing; 19 20use TYPO3\CMS\Backend\Routing\Exception\ResourceNotFoundException; 21use TYPO3\CMS\Backend\Routing\Exception\RouteNotFoundException; 22use TYPO3\CMS\Core\Utility\ArrayUtility; 23use TYPO3\CMS\Core\Utility\GeneralUtility; 24use TYPO3\CMS\Core\Utility\HttpUtility; 25use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface; 26use TYPO3\CMS\Extbase\DomainObject\AbstractDomainObject; 27use TYPO3\CMS\Extbase\DomainObject\AbstractValueObject; 28use TYPO3\CMS\Extbase\Mvc\Exception\InvalidArgumentValueException; 29use TYPO3\CMS\Extbase\Mvc\Request; 30use TYPO3\CMS\Extbase\Persistence\Generic\LazyLoadingProxy; 31use TYPO3\CMS\Extbase\Service\EnvironmentService; 32use TYPO3\CMS\Extbase\Service\ExtensionService; 33use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer; 34 35/** 36 * An URI Builder 37 */ 38class UriBuilder 39{ 40 /** 41 * @var \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface 42 */ 43 protected $configurationManager; 44 45 /** 46 * @var \TYPO3\CMS\Extbase\Service\ExtensionService 47 */ 48 protected $extensionService; 49 50 /** 51 * An instance of \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer 52 * 53 * @var \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer 54 */ 55 protected $contentObject; 56 57 /** 58 * @var Request|null 59 */ 60 protected $request; 61 62 /** 63 * @var array 64 */ 65 protected $arguments = []; 66 67 /** 68 * Arguments which have been used for building the last URI 69 * 70 * @var array 71 */ 72 protected $lastArguments = []; 73 74 /** 75 * @var string 76 */ 77 protected $section = ''; 78 79 /** 80 * @var bool 81 */ 82 protected $createAbsoluteUri = false; 83 84 /** 85 * @var string 86 */ 87 protected $absoluteUriScheme; 88 89 /** 90 * @var bool 91 */ 92 protected $addQueryString = false; 93 94 /** 95 * @var string 96 */ 97 protected $addQueryStringMethod = ''; 98 99 /** 100 * @var array 101 */ 102 protected $argumentsToBeExcludedFromQueryString = []; 103 104 /** 105 * @var bool 106 */ 107 protected $linkAccessRestrictedPages = false; 108 109 /** 110 * @var int|null 111 */ 112 protected $targetPageUid; 113 114 /** 115 * @var int 116 */ 117 protected $targetPageType = 0; 118 119 /** 120 * @var string 121 */ 122 protected $language; 123 124 /** 125 * @var bool 126 */ 127 protected $noCache = false; 128 129 /** 130 * @var string 131 */ 132 protected $format = ''; 133 134 /** 135 * @var string|null 136 */ 137 protected $argumentPrefix; 138 139 /** 140 * @var \TYPO3\CMS\Extbase\Service\EnvironmentService 141 */ 142 protected $environmentService; 143 144 /** 145 * @param \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface $configurationManager 146 * @internal only to be used within Extbase, not part of TYPO3 Core API. 147 */ 148 public function injectConfigurationManager(ConfigurationManagerInterface $configurationManager): void 149 { 150 $this->configurationManager = $configurationManager; 151 } 152 153 /** 154 * @param \TYPO3\CMS\Extbase\Service\ExtensionService $extensionService 155 * @internal only to be used within Extbase, not part of TYPO3 Core API. 156 */ 157 public function injectExtensionService(ExtensionService $extensionService): void 158 { 159 $this->extensionService = $extensionService; 160 } 161 162 /** 163 * @param \TYPO3\CMS\Extbase\Service\EnvironmentService $environmentService 164 * @internal only to be used within Extbase, not part of TYPO3 Core API. 165 */ 166 public function injectEnvironmentService(EnvironmentService $environmentService): void 167 { 168 $this->environmentService = $environmentService; 169 } 170 171 /** 172 * Life-cycle method that is called by the DI container as soon as this object is completely built 173 * @internal only to be used within Extbase, not part of TYPO3 Core API. 174 */ 175 public function initializeObject(): void 176 { 177 $this->contentObject = $this->configurationManager->getContentObject() 178 ?? GeneralUtility::makeInstance(ContentObjectRenderer::class); 179 } 180 181 /** 182 * Sets the current request 183 * 184 * @param Request $request 185 * @return static the current UriBuilder to allow method chaining 186 * @internal only to be used within Extbase, not part of TYPO3 Core API. 187 */ 188 public function setRequest(Request $request): UriBuilder 189 { 190 $this->request = $request; 191 return $this; 192 } 193 194 /** 195 * @return Request|null 196 * @internal only to be used within Extbase, not part of TYPO3 Core API. 197 */ 198 public function getRequest(): ?Request 199 { 200 return $this->request; 201 } 202 203 /** 204 * Additional query parameters. 205 * If you want to "prefix" arguments, you can pass in multidimensional arrays: 206 * array('prefix1' => array('foo' => 'bar')) gets "&prefix1[foo]=bar" 207 * 208 * @param array $arguments 209 * @return static the current UriBuilder to allow method chaining 210 */ 211 public function setArguments(array $arguments): UriBuilder 212 { 213 $this->arguments = $arguments; 214 return $this; 215 } 216 217 /** 218 * @return array 219 * @internal 220 */ 221 public function getArguments(): array 222 { 223 return $this->arguments; 224 } 225 226 /** 227 * If specified, adds a given HTML anchor to the URI (#...) 228 * 229 * @param string $section 230 * @return static the current UriBuilder to allow method chaining 231 */ 232 public function setSection(string $section): UriBuilder 233 { 234 $this->section = $section; 235 return $this; 236 } 237 238 /** 239 * @return string 240 * @internal 241 */ 242 public function getSection(): string 243 { 244 return $this->section; 245 } 246 247 /** 248 * Specifies the format of the target (e.g. "html" or "xml") 249 * 250 * @param string $format 251 * @return static the current UriBuilder to allow method chaining 252 */ 253 public function setFormat(string $format): UriBuilder 254 { 255 $this->format = $format; 256 return $this; 257 } 258 259 /** 260 * @return string 261 * @internal 262 */ 263 public function getFormat(): string 264 { 265 return $this->format; 266 } 267 268 /** 269 * If set, the URI is prepended with the current base URI. Defaults to FALSE. 270 * 271 * @param bool $createAbsoluteUri 272 * @return static the current UriBuilder to allow method chaining 273 */ 274 public function setCreateAbsoluteUri(bool $createAbsoluteUri): UriBuilder 275 { 276 $this->createAbsoluteUri = $createAbsoluteUri; 277 return $this; 278 } 279 280 /** 281 * @return bool 282 * @internal 283 */ 284 public function getCreateAbsoluteUri(): bool 285 { 286 return $this->createAbsoluteUri; 287 } 288 289 /** 290 * @return string|null 291 * @internal only to be used within Extbase, not part of TYPO3 Core API. 292 */ 293 public function getAbsoluteUriScheme(): ?string 294 { 295 return $this->absoluteUriScheme; 296 } 297 298 /** 299 * Sets the scheme that should be used for absolute URIs in FE mode 300 * 301 * @param string $absoluteUriScheme the scheme to be used for absolute URIs 302 * @return static the current UriBuilder to allow method chaining 303 */ 304 public function setAbsoluteUriScheme(string $absoluteUriScheme): UriBuilder 305 { 306 $this->absoluteUriScheme = $absoluteUriScheme; 307 return $this; 308 } 309 310 /** 311 * Enforces a URI / link to a page to a specific language (or use "current") 312 * @param string|null $language 313 * @return UriBuilder 314 */ 315 public function setLanguage(?string $language): UriBuilder 316 { 317 $this->language = $language; 318 return $this; 319 } 320 321 public function getLanguage(): ?string 322 { 323 return $this->language; 324 } 325 326 /** 327 * If set, the current query parameters will be merged with $this->arguments. Defaults to FALSE. 328 * 329 * @param bool $addQueryString 330 * @return static the current UriBuilder to allow method chaining 331 * @see https://docs.typo3.org/m/typo3/reference-typoscript/master/en-us/Functions/Typolink.html#addquerystring 332 */ 333 public function setAddQueryString(bool $addQueryString): UriBuilder 334 { 335 $this->addQueryString = $addQueryString; 336 return $this; 337 } 338 339 /** 340 * @return bool 341 * @internal 342 */ 343 public function getAddQueryString(): bool 344 { 345 return $this->addQueryString; 346 } 347 348 /** 349 * Sets the method to get the addQueryString parameters. Defaults to an empty string 350 * which results in using GeneralUtility::_GET(). Possible values are 351 * 352 * + '' -> uses GeneralUtility::_GET() 353 * + '0' -> uses GeneralUtility::_GET() 354 * + 'GET' -> uses GeneralUtility::_GET() 355 * + '<any>' -> uses parse_str(GeneralUtility::getIndpEnv('QUERY_STRING')) 356 * (<any> refers to literally everything else than previously mentioned values) 357 * 358 * @param string $addQueryStringMethod 359 * @return static the current UriBuilder to allow method chaining 360 * @see https://docs.typo3.org/m/typo3/reference-typoscript/master/en-us/Functions/Typolink.html#addquerystring 361 */ 362 public function setAddQueryStringMethod(string $addQueryStringMethod): UriBuilder 363 { 364 if ($addQueryStringMethod === 'POST') { 365 trigger_error('Assigning addQueryStringMethod = POST is not supported anymore since TYPO3 v10.0.', E_USER_WARNING); 366 $addQueryStringMethod = null; 367 } elseif ($addQueryStringMethod === 'GET,POST' || $addQueryStringMethod === 'POST,GET') { 368 trigger_error('Assigning addQueryStringMethod = GET,POST or POST,GET is not supported anymore since TYPO3 v10.0 - falling back to GET.', E_USER_WARNING); 369 $addQueryStringMethod = 'GET'; 370 } 371 $this->addQueryStringMethod = $addQueryStringMethod; 372 return $this; 373 } 374 375 /** 376 * @return string 377 * @internal 378 */ 379 public function getAddQueryStringMethod(): string 380 { 381 return $this->addQueryStringMethod; 382 } 383 384 /** 385 * A list of arguments to be excluded from the query parameters 386 * Only active if addQueryString is set 387 * 388 * @param array $argumentsToBeExcludedFromQueryString 389 * @return static the current UriBuilder to allow method chaining 390 * @see https://docs.typo3.org/m/typo3/reference-typoscript/master/en-us/Functions/Typolink.html#addquerystring 391 * @see setAddQueryString() 392 */ 393 public function setArgumentsToBeExcludedFromQueryString(array $argumentsToBeExcludedFromQueryString): UriBuilder 394 { 395 $this->argumentsToBeExcludedFromQueryString = $argumentsToBeExcludedFromQueryString; 396 return $this; 397 } 398 399 /** 400 * @return array 401 * @internal 402 */ 403 public function getArgumentsToBeExcludedFromQueryString(): array 404 { 405 return $this->argumentsToBeExcludedFromQueryString; 406 } 407 408 /** 409 * Specifies the prefix to be used for all arguments. 410 * 411 * @param string $argumentPrefix 412 * @return static the current UriBuilder to allow method chaining 413 */ 414 public function setArgumentPrefix(string $argumentPrefix): UriBuilder 415 { 416 $this->argumentPrefix = $argumentPrefix; 417 return $this; 418 } 419 420 /** 421 * @return string|null 422 * @internal only to be used within Extbase, not part of TYPO3 Core API. 423 */ 424 public function getArgumentPrefix(): ?string 425 { 426 return $this->argumentPrefix; 427 } 428 429 /** 430 * If set, URIs for pages without access permissions will be created 431 * 432 * @param bool $linkAccessRestrictedPages 433 * @return static the current UriBuilder to allow method chaining 434 */ 435 public function setLinkAccessRestrictedPages(bool $linkAccessRestrictedPages): UriBuilder 436 { 437 $this->linkAccessRestrictedPages = $linkAccessRestrictedPages; 438 return $this; 439 } 440 441 /** 442 * @return bool 443 * @internal 444 */ 445 public function getLinkAccessRestrictedPages(): bool 446 { 447 return $this->linkAccessRestrictedPages; 448 } 449 450 /** 451 * Uid of the target page 452 * 453 * @param int $targetPageUid 454 * @return static the current UriBuilder to allow method chaining 455 */ 456 public function setTargetPageUid(int $targetPageUid): UriBuilder 457 { 458 $this->targetPageUid = $targetPageUid; 459 return $this; 460 } 461 462 /** 463 * returns $this->targetPageUid. 464 * 465 * @return int|null 466 * @internal 467 */ 468 public function getTargetPageUid(): ?int 469 { 470 return $this->targetPageUid; 471 } 472 473 /** 474 * Sets the page type of the target URI. Defaults to 0 475 * 476 * @param int $targetPageType 477 * @return static the current UriBuilder to allow method chaining 478 */ 479 public function setTargetPageType(int $targetPageType): UriBuilder 480 { 481 $this->targetPageType = $targetPageType; 482 return $this; 483 } 484 485 /** 486 * @return int 487 * @internal only to be used within Extbase, not part of TYPO3 Core API. 488 */ 489 public function getTargetPageType(): int 490 { 491 return $this->targetPageType; 492 } 493 494 /** 495 * by default FALSE; if TRUE, &no_cache=1 will be appended to the URI 496 * 497 * @param bool $noCache 498 * @return static the current UriBuilder to allow method chaining 499 */ 500 public function setNoCache(bool $noCache): UriBuilder 501 { 502 $this->noCache = $noCache; 503 return $this; 504 } 505 506 /** 507 * @return bool 508 * @internal 509 */ 510 public function getNoCache(): bool 511 { 512 return $this->noCache; 513 } 514 515 /** 516 * by default TRUE; if FALSE, no cHash parameter will be appended to the URI 517 * If noCache is set, this setting will be ignored. 518 * 519 * @return static the current UriBuilder to allow method chaining 520 */ 521 public function setUseCacheHash(): UriBuilder 522 { 523 trigger_error('Calling UriBuilder->setUseCacheHash() will be removed in TYPO3 v11.0. TYPO3 Core routing is taking care of handling this argument. Simply remove the line to avoid the notice.', E_USER_DEPRECATED); 524 return $this; 525 } 526 527 /** 528 * @return bool 529 * @internal 530 */ 531 public function getUseCacheHash(): bool 532 { 533 trigger_error('Calling UriBuilder->getUseCacheHash() will be removed in TYPO3 v11.0. TYPO3 Core routing is taking care of handling this argument. Simply remove the line to avoid the notice.', E_USER_DEPRECATED); 534 return true; 535 } 536 537 /** 538 * Returns the arguments being used for the last URI being built. 539 * This is only set after build() / uriFor() has been called. 540 * 541 * @return array The last arguments 542 * @internal only to be used within Extbase, not part of TYPO3 Core API. 543 */ 544 public function getLastArguments(): array 545 { 546 return $this->lastArguments; 547 } 548 549 /** 550 * Resets all UriBuilder options to their default value 551 * 552 * @return static the current UriBuilder to allow method chaining 553 */ 554 public function reset(): UriBuilder 555 { 556 $this->arguments = []; 557 $this->section = ''; 558 $this->format = ''; 559 $this->language = null; 560 $this->createAbsoluteUri = false; 561 $this->addQueryString = false; 562 $this->addQueryStringMethod = ''; 563 $this->argumentsToBeExcludedFromQueryString = []; 564 $this->linkAccessRestrictedPages = false; 565 $this->targetPageUid = null; 566 $this->targetPageType = 0; 567 $this->noCache = false; 568 $this->argumentPrefix = null; 569 $this->absoluteUriScheme = null; 570 /* 571 * $this->request MUST NOT be reset here because the request is actually a hard dependency and not part 572 * of the internal state of this object. 573 * todo: consider making the request a constructor dependency or get rid of it's usage 574 */ 575 return $this; 576 } 577 578 /** 579 * Creates an URI used for linking to an Extbase action. 580 * Works in Frontend and Backend mode of TYPO3. 581 * 582 * @param string|null $actionName Name of the action to be called 583 * @param array|null $controllerArguments Additional query parameters. Will be "namespaced" and merged with $this->arguments. 584 * @param string|null $controllerName Name of the target controller. If not set, current ControllerName is used. 585 * @param string|null $extensionName Name of the target extension, without underscores. If not set, current ExtensionName is used. 586 * @param string|null $pluginName Name of the target plugin. If not set, current PluginName is used. 587 * @return string the rendered URI 588 * @see build() 589 */ 590 public function uriFor( 591 ?string $actionName = null, 592 ?array $controllerArguments = null, 593 ?string $controllerName = null, 594 ?string $extensionName = null, 595 ?string $pluginName = null 596 ): string { 597 $controllerArguments = $controllerArguments ?? []; 598 599 if ($actionName !== null) { 600 $controllerArguments['action'] = $actionName; 601 } 602 if ($controllerName !== null) { 603 $controllerArguments['controller'] = $controllerName; 604 } else { 605 $controllerArguments['controller'] = $this->request->getControllerName(); 606 } 607 if ($extensionName === null) { 608 $extensionName = $this->request->getControllerExtensionName(); 609 } 610 if ($pluginName === null && $this->environmentService->isEnvironmentInFrontendMode()) { 611 $pluginName = $this->extensionService->getPluginNameByAction($extensionName, $controllerArguments['controller'], $controllerArguments['action'] ?? null); 612 } 613 if ($pluginName === null) { 614 $pluginName = $this->request->getPluginName(); 615 } 616 if ($this->environmentService->isEnvironmentInFrontendMode() && $this->configurationManager->isFeatureEnabled('skipDefaultArguments')) { 617 $controllerArguments = $this->removeDefaultControllerAndAction($controllerArguments, $extensionName, $pluginName); 618 } 619 if ($this->targetPageUid === null && $this->environmentService->isEnvironmentInFrontendMode()) { 620 $this->targetPageUid = $this->extensionService->getTargetPidByPlugin($extensionName, $pluginName); 621 } 622 if ($this->format !== '') { 623 $controllerArguments['format'] = $this->format; 624 } 625 if ($this->argumentPrefix !== null) { 626 $prefixedControllerArguments = [$this->argumentPrefix => $controllerArguments]; 627 } else { 628 $pluginNamespace = $this->extensionService->getPluginNamespace($extensionName, $pluginName); 629 $prefixedControllerArguments = [$pluginNamespace => $controllerArguments]; 630 } 631 ArrayUtility::mergeRecursiveWithOverrule($this->arguments, $prefixedControllerArguments); 632 return $this->build(); 633 } 634 635 /** 636 * This removes controller and/or action arguments from given controllerArguments 637 * if they are equal to the default controller/action of the target plugin. 638 * Note: This is only active in FE mode and if feature "skipDefaultArguments" is enabled 639 * 640 * @see \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface::isFeatureEnabled() 641 * @param array $controllerArguments the current controller arguments to be modified 642 * @param string $extensionName target extension name 643 * @param string $pluginName target plugin name 644 * @return array 645 */ 646 protected function removeDefaultControllerAndAction(array $controllerArguments, string $extensionName, string $pluginName): array 647 { 648 $defaultControllerName = $this->extensionService->getDefaultControllerNameByPlugin($extensionName, $pluginName); 649 if (isset($controllerArguments['action'])) { 650 $defaultActionName = $this->extensionService->getDefaultActionNameByPluginAndController($extensionName, $pluginName, $controllerArguments['controller']); 651 if ($controllerArguments['action'] === $defaultActionName) { 652 unset($controllerArguments['action']); 653 } 654 } 655 if ($controllerArguments['controller'] === $defaultControllerName) { 656 unset($controllerArguments['controller']); 657 } 658 return $controllerArguments; 659 } 660 661 /** 662 * Builds the URI 663 * Depending on the current context this calls buildBackendUri() or buildFrontendUri() 664 * 665 * @return string The URI 666 * @see buildBackendUri() 667 * @see buildFrontendUri() 668 */ 669 public function build(): string 670 { 671 if ($this->environmentService->isEnvironmentInBackendMode()) { 672 return $this->buildBackendUri(); 673 } 674 return $this->buildFrontendUri(); 675 } 676 677 /** 678 * Builds the URI, backend flavour 679 * The resulting URI is relative and starts with "index.php". 680 * The settings pageUid, pageType, noCache & linkAccessRestrictedPages 681 * will be ignored in the backend. 682 * 683 * @return string The URI 684 * @internal only to be used within Extbase, not part of TYPO3 Core API. 685 * @see \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::getQueryArguments 686 */ 687 public function buildBackendUri(): string 688 { 689 $arguments = []; 690 if ($this->addQueryString === true) { 691 if ($this->addQueryStringMethod === '' || $this->addQueryStringMethod === '0' || $this->addQueryStringMethod === 'GET') { 692 $arguments = GeneralUtility::_GET(); 693 } else { 694 // Explode GET vars recursively 695 parse_str(GeneralUtility::getIndpEnv('QUERY_STRING'), $arguments); 696 } 697 foreach ($this->argumentsToBeExcludedFromQueryString as $argumentToBeExcluded) { 698 $argumentArrayToBeExcluded = []; 699 parse_str($argumentToBeExcluded, $argumentArrayToBeExcluded); 700 $arguments = ArrayUtility::arrayDiffKeyRecursive($arguments, $argumentArrayToBeExcluded); 701 } 702 } else { 703 $id = GeneralUtility::_GP('id'); 704 $route = GeneralUtility::_GP('route'); 705 if ($id !== null) { 706 $arguments['id'] = $id; 707 } 708 if ($route !== null) { 709 $arguments['route'] = $route; 710 } 711 } 712 ArrayUtility::mergeRecursiveWithOverrule($arguments, $this->arguments); 713 $arguments = $this->convertDomainObjectsToIdentityArrays($arguments); 714 $this->lastArguments = $arguments; 715 $routeName = $arguments['route'] ?? null; 716 unset($arguments['route'], $arguments['token']); 717 $backendUriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class); 718 try { 719 if ($this->createAbsoluteUri) { 720 $uri = (string)$backendUriBuilder->buildUriFromRoutePath($routeName, $arguments, \TYPO3\CMS\Backend\Routing\UriBuilder::ABSOLUTE_URL); 721 } else { 722 $uri = (string)$backendUriBuilder->buildUriFromRoutePath($routeName, $arguments, \TYPO3\CMS\Backend\Routing\UriBuilder::ABSOLUTE_PATH); 723 } 724 } catch (ResourceNotFoundException $e) { 725 try { 726 if ($this->createAbsoluteUri) { 727 $uri = (string)$backendUriBuilder->buildUriFromRoute($routeName, $arguments, \TYPO3\CMS\Backend\Routing\UriBuilder::ABSOLUTE_URL); 728 } else { 729 $uri = (string)$backendUriBuilder->buildUriFromRoute($routeName, $arguments, \TYPO3\CMS\Backend\Routing\UriBuilder::ABSOLUTE_PATH); 730 } 731 } catch (RouteNotFoundException $e) { 732 $uri = ''; 733 } 734 } 735 if ($this->section !== '') { 736 $uri .= '#' . $this->section; 737 } 738 return $uri; 739 } 740 741 /** 742 * Builds the URI, frontend flavour 743 * 744 * @return string The URI 745 * @see buildTypolinkConfiguration() 746 * @internal only to be used within Extbase, not part of TYPO3 Core API. 747 */ 748 public function buildFrontendUri(): string 749 { 750 $typolinkConfiguration = $this->buildTypolinkConfiguration(); 751 if ($this->createAbsoluteUri === true) { 752 $typolinkConfiguration['forceAbsoluteUrl'] = true; 753 if ($this->absoluteUriScheme !== null) { 754 $typolinkConfiguration['forceAbsoluteUrl.']['scheme'] = $this->absoluteUriScheme; 755 } 756 } 757 // Other than stated in the doc block, typoLink_URL does not always return a string 758 // Thus, we explicitly cast to string here. 759 $uri = (string)$this->contentObject->typoLink_URL($typolinkConfiguration); 760 return $uri; 761 } 762 763 /** 764 * Builds a TypoLink configuration array from the current settings 765 * 766 * @return array typolink configuration array 767 * @see https://docs.typo3.org/m/typo3/reference-typoscript/master/en-us/Functions/Typolink.html 768 */ 769 protected function buildTypolinkConfiguration(): array 770 { 771 $typolinkConfiguration = []; 772 $typolinkConfiguration['parameter'] = $this->targetPageUid ?? $GLOBALS['TSFE']->id; 773 if ($this->targetPageType !== 0) { 774 $typolinkConfiguration['parameter'] .= ',' . $this->targetPageType; 775 } elseif ($this->format !== '') { 776 $targetPageType = $this->extensionService->getTargetPageTypeByFormat($this->request->getControllerExtensionName(), $this->format); 777 $typolinkConfiguration['parameter'] .= ',' . $targetPageType; 778 } 779 if (!empty($this->arguments)) { 780 $arguments = $this->convertDomainObjectsToIdentityArrays($this->arguments); 781 $this->lastArguments = $arguments; 782 $typolinkConfiguration['additionalParams'] = HttpUtility::buildQueryString($arguments, '&'); 783 } 784 if ($this->addQueryString === true) { 785 $typolinkConfiguration['addQueryString'] = 1; 786 if (!empty($this->argumentsToBeExcludedFromQueryString)) { 787 $typolinkConfiguration['addQueryString.'] = [ 788 'exclude' => implode(',', $this->argumentsToBeExcludedFromQueryString) 789 ]; 790 } 791 if ($this->addQueryStringMethod !== '') { 792 $typolinkConfiguration['addQueryString.']['method'] = $this->addQueryStringMethod; 793 } 794 } 795 if ($this->language !== null) { 796 $typolinkConfiguration['language'] = $this->language; 797 } 798 799 if ($this->noCache === true) { 800 $typolinkConfiguration['no_cache'] = 1; 801 } 802 if ($this->section !== '') { 803 $typolinkConfiguration['section'] = $this->section; 804 } 805 if ($this->linkAccessRestrictedPages === true) { 806 $typolinkConfiguration['linkAccessRestrictedPages'] = 1; 807 } 808 return $typolinkConfiguration; 809 } 810 811 /** 812 * Recursively iterates through the specified arguments and turns instances of type \TYPO3\CMS\Extbase\DomainObject\AbstractEntity 813 * into an arrays containing the uid of the domain object. 814 * 815 * @param array $arguments The arguments to be iterated 816 * @throws \TYPO3\CMS\Extbase\Mvc\Exception\InvalidArgumentValueException 817 * @return array The modified arguments array 818 */ 819 protected function convertDomainObjectsToIdentityArrays(array $arguments): array 820 { 821 foreach ($arguments as $argumentKey => $argumentValue) { 822 // if we have a LazyLoadingProxy here, make sure to get the real instance for further processing 823 if ($argumentValue instanceof LazyLoadingProxy) { 824 $argumentValue = $argumentValue->_loadRealInstance(); 825 // also update the value in the arguments array, because the lazyLoaded object could be 826 // hidden and thus the $argumentValue would be NULL. 827 $arguments[$argumentKey] = $argumentValue; 828 } 829 if ($argumentValue instanceof \Iterator) { 830 $argumentValue = $this->convertIteratorToArray($argumentValue); 831 } 832 if ($argumentValue instanceof AbstractDomainObject) { 833 if ($argumentValue->getUid() !== null) { 834 $arguments[$argumentKey] = $argumentValue->getUid(); 835 } elseif ($argumentValue instanceof AbstractValueObject) { 836 $arguments[$argumentKey] = $this->convertTransientObjectToArray($argumentValue); 837 } else { 838 throw new InvalidArgumentValueException('Could not serialize Domain Object ' . get_class($argumentValue) . '. It is neither an Entity with identity properties set, nor a Value Object.', 1260881688); 839 } 840 } elseif (is_array($argumentValue)) { 841 $arguments[$argumentKey] = $this->convertDomainObjectsToIdentityArrays($argumentValue); 842 } 843 } 844 return $arguments; 845 } 846 847 /** 848 * @param \Iterator $iterator 849 * @return array 850 */ 851 protected function convertIteratorToArray(\Iterator $iterator): array 852 { 853 if (method_exists($iterator, 'toArray')) { 854 $array = $iterator->toArray(); 855 } else { 856 $array = iterator_to_array($iterator); 857 } 858 return $array; 859 } 860 861 /** 862 * Converts a given object recursively into an array. 863 * 864 * @param \TYPO3\CMS\Extbase\DomainObject\AbstractDomainObject $object 865 * @return array 866 * @todo Refactor this into convertDomainObjectsToIdentityArrays() 867 * @internal only to be used within Extbase, not part of TYPO3 Core API. 868 */ 869 public function convertTransientObjectToArray(AbstractDomainObject $object): array 870 { 871 $result = []; 872 foreach ($object->_getProperties() as $propertyName => $propertyValue) { 873 if ($propertyValue instanceof \Iterator) { 874 $propertyValue = $this->convertIteratorToArray($propertyValue); 875 } 876 if ($propertyValue instanceof AbstractDomainObject) { 877 if ($propertyValue->getUid() !== null) { 878 $result[$propertyName] = $propertyValue->getUid(); 879 } else { 880 $result[$propertyName] = $this->convertTransientObjectToArray($propertyValue); 881 } 882 } elseif (is_array($propertyValue)) { 883 $result[$propertyName] = $this->convertDomainObjectsToIdentityArrays($propertyValue); 884 } else { 885 $result[$propertyName] = $propertyValue; 886 } 887 } 888 return $result; 889 } 890} 891