Passed
Push — master ( a7d46b...0a8eff )
by
unknown
14:11
created

UriBuilder::injectEnvironmentService()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(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
18
namespace TYPO3\CMS\Extbase\Mvc\Web\Routing;
19
20
use Psr\Http\Message\ServerRequestInterface;
21
use TYPO3\CMS\Backend\Routing\Exception\ResourceNotFoundException;
22
use TYPO3\CMS\Backend\Routing\Exception\RouteNotFoundException;
23
use TYPO3\CMS\Backend\Routing\Route;
24
use TYPO3\CMS\Core\Http\ApplicationType;
25
use TYPO3\CMS\Core\Utility\ArrayUtility;
26
use TYPO3\CMS\Core\Utility\GeneralUtility;
27
use TYPO3\CMS\Core\Utility\HttpUtility;
28
use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface;
29
use TYPO3\CMS\Extbase\DomainObject\AbstractDomainObject;
30
use TYPO3\CMS\Extbase\DomainObject\AbstractValueObject;
31
use TYPO3\CMS\Extbase\Mvc\Exception\InvalidArgumentValueException;
32
use TYPO3\CMS\Extbase\Mvc\Request;
33
use TYPO3\CMS\Extbase\Persistence\Generic\LazyLoadingProxy;
34
use TYPO3\CMS\Extbase\Service\ExtensionService;
35
36
/**
37
 * An URI Builder
38
 */
39
class UriBuilder
40
{
41
    /**
42
     * @var \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface
43
     */
44
    protected $configurationManager;
45
46
    /**
47
     * @var \TYPO3\CMS\Extbase\Service\ExtensionService
48
     */
49
    protected $extensionService;
50
51
    /**
52
     * An instance of \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer
53
     *
54
     * @var \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer
55
     */
56
    protected $contentObject;
57
58
    /**
59
     * @var Request|null
60
     */
61
    protected $request;
62
63
    /**
64
     * @var array
65
     */
66
    protected $arguments = [];
67
68
    /**
69
     * Arguments which have been used for building the last URI
70
     *
71
     * @var array
72
     */
73
    protected $lastArguments = [];
74
75
    /**
76
     * @var string
77
     */
78
    protected $section = '';
79
80
    /**
81
     * @var bool
82
     */
83
    protected $createAbsoluteUri = false;
84
85
    /**
86
     * @var string
87
     */
88
    protected $absoluteUriScheme;
89
90
    /**
91
     * @var bool
92
     */
93
    protected $addQueryString = false;
94
95
    /**
96
     * @var array
97
     */
98
    protected $argumentsToBeExcludedFromQueryString = [];
99
100
    /**
101
     * @var bool
102
     */
103
    protected $linkAccessRestrictedPages = false;
104
105
    /**
106
     * @var int|null
107
     */
108
    protected $targetPageUid;
109
110
    /**
111
     * @var int
112
     */
113
    protected $targetPageType = 0;
114
115
    /**
116
     * @var string
117
     */
118
    protected $language;
119
120
    /**
121
     * @var bool
122
     */
123
    protected $noCache = false;
124
125
    /**
126
     * @var string
127
     */
128
    protected $format = '';
129
130
    /**
131
     * @var string|null
132
     */
133
    protected $argumentPrefix;
134
135
    /**
136
     * @param \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface $configurationManager
137
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
138
     */
139
    public function injectConfigurationManager(ConfigurationManagerInterface $configurationManager): void
140
    {
141
        $this->configurationManager = $configurationManager;
142
    }
143
144
    /**
145
     * @param \TYPO3\CMS\Extbase\Service\ExtensionService $extensionService
146
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
147
     */
148
    public function injectExtensionService(ExtensionService $extensionService): void
149
    {
150
        $this->extensionService = $extensionService;
151
    }
152
153
    /**
154
     * Life-cycle method that is called by the DI container as soon as this object is completely built
155
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
156
     */
157
    public function initializeObject(): void
158
    {
159
        $this->contentObject = $this->configurationManager->getContentObject();
160
    }
161
162
    /**
163
     * Sets the current request
164
     *
165
     * @param Request $request
166
     * @return static the current UriBuilder to allow method chaining
167
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
168
     */
169
    public function setRequest(Request $request): UriBuilder
170
    {
171
        $this->request = $request;
172
        return $this;
173
    }
174
175
    /**
176
     * @return Request|null
177
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
178
     */
179
    public function getRequest(): ?Request
180
    {
181
        return $this->request;
182
    }
183
184
    /**
185
     * Additional query parameters.
186
     * If you want to "prefix" arguments, you can pass in multidimensional arrays:
187
     * array('prefix1' => array('foo' => 'bar')) gets "&prefix1[foo]=bar"
188
     *
189
     * @param array $arguments
190
     * @return static the current UriBuilder to allow method chaining
191
     */
192
    public function setArguments(array $arguments): UriBuilder
193
    {
194
        $this->arguments = $arguments;
195
        return $this;
196
    }
197
198
    /**
199
     * @return array
200
     * @internal
201
     */
202
    public function getArguments(): array
203
    {
204
        return $this->arguments;
205
    }
206
207
    /**
208
     * If specified, adds a given HTML anchor to the URI (#...)
209
     *
210
     * @param string $section
211
     * @return static the current UriBuilder to allow method chaining
212
     */
213
    public function setSection(string $section): UriBuilder
214
    {
215
        $this->section = $section;
216
        return $this;
217
    }
218
219
    /**
220
     * @return string
221
     * @internal
222
     */
223
    public function getSection(): string
224
    {
225
        return $this->section;
226
    }
227
228
    /**
229
     * Specifies the format of the target (e.g. "html" or "xml")
230
     *
231
     * @param string $format
232
     * @return static the current UriBuilder to allow method chaining
233
     */
234
    public function setFormat(string $format): UriBuilder
235
    {
236
        $this->format = $format;
237
        return $this;
238
    }
239
240
    /**
241
     * @return string
242
     * @internal
243
     */
244
    public function getFormat(): string
245
    {
246
        return $this->format;
247
    }
248
249
    /**
250
     * If set, the URI is prepended with the current base URI. Defaults to FALSE.
251
     *
252
     * @param bool $createAbsoluteUri
253
     * @return static the current UriBuilder to allow method chaining
254
     */
255
    public function setCreateAbsoluteUri(bool $createAbsoluteUri): UriBuilder
256
    {
257
        $this->createAbsoluteUri = $createAbsoluteUri;
258
        return $this;
259
    }
260
261
    /**
262
     * @return bool
263
     * @internal
264
     */
265
    public function getCreateAbsoluteUri(): bool
266
    {
267
        return $this->createAbsoluteUri;
268
    }
269
270
    /**
271
     * @return string|null
272
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
273
     */
274
    public function getAbsoluteUriScheme(): ?string
275
    {
276
        return $this->absoluteUriScheme;
277
    }
278
279
    /**
280
     * Sets the scheme that should be used for absolute URIs in FE mode
281
     *
282
     * @param string $absoluteUriScheme the scheme to be used for absolute URIs
283
     * @return static the current UriBuilder to allow method chaining
284
     */
285
    public function setAbsoluteUriScheme(string $absoluteUriScheme): UriBuilder
286
    {
287
        $this->absoluteUriScheme = $absoluteUriScheme;
288
        return $this;
289
    }
290
291
    /**
292
     * Enforces a URI / link to a page to a specific language (or use "current")
293
     * @param string|null $language
294
     * @return UriBuilder
295
     */
296
    public function setLanguage(?string $language): UriBuilder
297
    {
298
        $this->language = $language;
299
        return $this;
300
    }
301
302
    public function getLanguage(): ?string
303
    {
304
        return $this->language;
305
    }
306
307
    /**
308
     * If set, the current query parameters will be merged with $this->arguments. Defaults to FALSE.
309
     *
310
     * @param bool $addQueryString
311
     * @return static the current UriBuilder to allow method chaining
312
     * @see https://docs.typo3.org/m/typo3/reference-typoscript/master/en-us/Functions/Typolink.html#addquerystring
313
     */
314
    public function setAddQueryString(bool $addQueryString): UriBuilder
315
    {
316
        $this->addQueryString = $addQueryString;
317
        return $this;
318
    }
319
320
    /**
321
     * @return bool
322
     * @internal
323
     */
324
    public function getAddQueryString(): bool
325
    {
326
        return $this->addQueryString;
327
    }
328
329
    /**
330
     * Sets the method to get the addQueryString parameters. Defaults to an empty string
331
     * which results in using GeneralUtility::_GET(). Possible values are
332
     *
333
     * + ''      -> uses GeneralUtility::_GET()
334
     * + '0'     -> uses GeneralUtility::_GET()
335
     * + 'GET'   -> uses GeneralUtility::_GET()
336
     * + '<any>' -> uses parse_str(GeneralUtility::getIndpEnv('QUERY_STRING'))
337
     *              (<any> refers to literally everything else than previously mentioned values)
338
     *
339
     * @param string $addQueryStringMethod
340
     * @return static the current UriBuilder to allow method chaining
341
     * @see https://docs.typo3.org/m/typo3/reference-typoscript/master/en-us/Functions/Typolink.html#addquerystring
342
     */
343
    public function setAddQueryStringMethod(string $addQueryStringMethod): UriBuilder
344
    {
345
        trigger_error('Calling UriBuilder->setAddQueryStringMethod() has no effect anymore and will be removed in TYPO3 v12.  Remove any call in your custom code, as it will result in a fatal error.', E_USER_DEPRECATED);
346
        return $this;
347
    }
348
349
    /**
350
     * A list of arguments to be excluded from the query parameters
351
     * Only active if addQueryString is set
352
     *
353
     * @param array $argumentsToBeExcludedFromQueryString
354
     * @return static the current UriBuilder to allow method chaining
355
     * @see https://docs.typo3.org/m/typo3/reference-typoscript/master/en-us/Functions/Typolink.html#addquerystring
356
     * @see setAddQueryString()
357
     */
358
    public function setArgumentsToBeExcludedFromQueryString(array $argumentsToBeExcludedFromQueryString): UriBuilder
359
    {
360
        $this->argumentsToBeExcludedFromQueryString = $argumentsToBeExcludedFromQueryString;
361
        return $this;
362
    }
363
364
    /**
365
     * @return array
366
     * @internal
367
     */
368
    public function getArgumentsToBeExcludedFromQueryString(): array
369
    {
370
        return $this->argumentsToBeExcludedFromQueryString;
371
    }
372
373
    /**
374
     * Specifies the prefix to be used for all arguments.
375
     *
376
     * @param string $argumentPrefix
377
     * @return static the current UriBuilder to allow method chaining
378
     */
379
    public function setArgumentPrefix(string $argumentPrefix): UriBuilder
380
    {
381
        $this->argumentPrefix = $argumentPrefix;
382
        return $this;
383
    }
384
385
    /**
386
     * @return string|null
387
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
388
     */
389
    public function getArgumentPrefix(): ?string
390
    {
391
        return $this->argumentPrefix;
392
    }
393
394
    /**
395
     * If set, URIs for pages without access permissions will be created
396
     *
397
     * @param bool $linkAccessRestrictedPages
398
     * @return static the current UriBuilder to allow method chaining
399
     */
400
    public function setLinkAccessRestrictedPages(bool $linkAccessRestrictedPages): UriBuilder
401
    {
402
        $this->linkAccessRestrictedPages = $linkAccessRestrictedPages;
403
        return $this;
404
    }
405
406
    /**
407
     * @return bool
408
     * @internal
409
     */
410
    public function getLinkAccessRestrictedPages(): bool
411
    {
412
        return $this->linkAccessRestrictedPages;
413
    }
414
415
    /**
416
     * Uid of the target page
417
     *
418
     * @param int $targetPageUid
419
     * @return static the current UriBuilder to allow method chaining
420
     */
421
    public function setTargetPageUid(int $targetPageUid): UriBuilder
422
    {
423
        $this->targetPageUid = $targetPageUid;
424
        return $this;
425
    }
426
427
    /**
428
     * returns $this->targetPageUid.
429
     *
430
     * @return int|null
431
     * @internal
432
     */
433
    public function getTargetPageUid(): ?int
434
    {
435
        return $this->targetPageUid;
436
    }
437
438
    /**
439
     * Sets the page type of the target URI. Defaults to 0
440
     *
441
     * @param int $targetPageType
442
     * @return static the current UriBuilder to allow method chaining
443
     */
444
    public function setTargetPageType(int $targetPageType): UriBuilder
445
    {
446
        $this->targetPageType = $targetPageType;
447
        return $this;
448
    }
449
450
    /**
451
     * @return int
452
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
453
     */
454
    public function getTargetPageType(): int
455
    {
456
        return $this->targetPageType;
457
    }
458
459
    /**
460
     * by default FALSE; if TRUE, &no_cache=1 will be appended to the URI
461
     *
462
     * @param bool $noCache
463
     * @return static the current UriBuilder to allow method chaining
464
     */
465
    public function setNoCache(bool $noCache): UriBuilder
466
    {
467
        $this->noCache = $noCache;
468
        return $this;
469
    }
470
471
    /**
472
     * @return bool
473
     * @internal
474
     */
475
    public function getNoCache(): bool
476
    {
477
        return $this->noCache;
478
    }
479
480
    /**
481
     * Returns the arguments being used for the last URI being built.
482
     * This is only set after build() / uriFor() has been called.
483
     *
484
     * @return array The last arguments
485
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
486
     */
487
    public function getLastArguments(): array
488
    {
489
        return $this->lastArguments;
490
    }
491
492
    /**
493
     * Resets all UriBuilder options to their default value
494
     *
495
     * @return static the current UriBuilder to allow method chaining
496
     */
497
    public function reset(): UriBuilder
498
    {
499
        $this->arguments = [];
500
        $this->section = '';
501
        $this->format = '';
502
        $this->language = null;
503
        $this->createAbsoluteUri = false;
504
        $this->addQueryString = false;
505
        $this->argumentsToBeExcludedFromQueryString = [];
506
        $this->linkAccessRestrictedPages = false;
507
        $this->targetPageUid = null;
508
        $this->targetPageType = 0;
509
        $this->noCache = false;
510
        $this->argumentPrefix = null;
511
        $this->absoluteUriScheme = null;
512
        /*
513
         * $this->request MUST NOT be reset here because the request is actually a hard dependency and not part
514
         * of the internal state of this object.
515
         * todo: consider making the request a constructor dependency or get rid of it's usage
516
         */
517
        return $this;
518
    }
519
520
    /**
521
     * Creates an URI used for linking to an Extbase action.
522
     * Works in Frontend and Backend mode of TYPO3.
523
     *
524
     * @param string|null $actionName Name of the action to be called
525
     * @param array|null $controllerArguments Additional query parameters. Will be "namespaced" and merged with $this->arguments.
526
     * @param string|null $controllerName Name of the target controller. If not set, current ControllerName is used.
527
     * @param string|null $extensionName Name of the target extension, without underscores. If not set, current ExtensionName is used.
528
     * @param string|null $pluginName Name of the target plugin. If not set, current PluginName is used.
529
     * @return string the rendered URI
530
     * @see build()
531
     */
532
    public function uriFor(
533
        ?string $actionName = null,
534
        ?array $controllerArguments = null,
535
        ?string $controllerName = null,
536
        ?string $extensionName = null,
537
        ?string $pluginName = null
538
    ): string {
539
        $controllerArguments = $controllerArguments ?? [];
540
541
        if ($actionName !== null) {
542
            $controllerArguments['action'] = $actionName;
543
        }
544
        if ($controllerName !== null) {
545
            $controllerArguments['controller'] = $controllerName;
546
        } else {
547
            $controllerArguments['controller'] = $this->request->getControllerName();
0 ignored issues
show
Bug introduced by
The method getControllerName() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

547
            /** @scrutinizer ignore-call */ 
548
            $controllerArguments['controller'] = $this->request->getControllerName();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
548
        }
549
        if ($extensionName === null) {
550
            $extensionName = $this->request->getControllerExtensionName();
551
        }
552
        $isFrontend = ($GLOBALS['TYPO3_REQUEST'] ?? null) instanceof ServerRequestInterface
553
            && ApplicationType::fromRequest($GLOBALS['TYPO3_REQUEST'])->isFrontend();
554
        if ($pluginName === null && $isFrontend) {
555
            $pluginName = $this->extensionService->getPluginNameByAction($extensionName, $controllerArguments['controller'], $controllerArguments['action'] ?? null);
556
        }
557
        if ($pluginName === null) {
558
            $pluginName = $this->request->getPluginName();
559
        }
560
        if ($isFrontend && $this->configurationManager->isFeatureEnabled('skipDefaultArguments')) {
561
            $controllerArguments = $this->removeDefaultControllerAndAction($controllerArguments, $extensionName, $pluginName);
562
        }
563
        if ($this->targetPageUid === null && $isFrontend) {
564
            $this->targetPageUid = $this->extensionService->getTargetPidByPlugin($extensionName, $pluginName);
565
        }
566
        if ($this->format !== '') {
567
            $controllerArguments['format'] = $this->format;
568
        }
569
        if ($this->argumentPrefix !== null) {
570
            $prefixedControllerArguments = [$this->argumentPrefix => $controllerArguments];
571
        } else {
572
            $pluginNamespace = $this->extensionService->getPluginNamespace($extensionName, $pluginName);
573
            $prefixedControllerArguments = [$pluginNamespace => $controllerArguments];
574
        }
575
        ArrayUtility::mergeRecursiveWithOverrule($this->arguments, $prefixedControllerArguments);
576
        return $this->build();
577
    }
578
579
    /**
580
     * This removes controller and/or action arguments from given controllerArguments
581
     * if they are equal to the default controller/action of the target plugin.
582
     * Note: This is only active in FE mode and if feature "skipDefaultArguments" is enabled
583
     *
584
     * @see \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface::isFeatureEnabled()
585
     * @param array $controllerArguments the current controller arguments to be modified
586
     * @param string $extensionName target extension name
587
     * @param string $pluginName target plugin name
588
     * @return array
589
     */
590
    protected function removeDefaultControllerAndAction(array $controllerArguments, string $extensionName, string $pluginName): array
591
    {
592
        $defaultControllerName = $this->extensionService->getDefaultControllerNameByPlugin($extensionName, $pluginName);
593
        if (isset($controllerArguments['action'])) {
594
            $defaultActionName = $this->extensionService->getDefaultActionNameByPluginAndController($extensionName, $pluginName, $controllerArguments['controller']);
595
            if ($controllerArguments['action'] === $defaultActionName) {
596
                unset($controllerArguments['action']);
597
            }
598
        }
599
        if ($controllerArguments['controller'] === $defaultControllerName) {
600
            unset($controllerArguments['controller']);
601
        }
602
        return $controllerArguments;
603
    }
604
605
    /**
606
     * Builds the URI
607
     * Depending on the current context this calls buildBackendUri() or buildFrontendUri()
608
     *
609
     * @return string The URI
610
     * @see buildBackendUri()
611
     * @see buildFrontendUri()
612
     */
613
    public function build(): string
614
    {
615
        if (($GLOBALS['TYPO3_REQUEST'] ?? null) instanceof ServerRequestInterface
616
            && ApplicationType::fromRequest($GLOBALS['TYPO3_REQUEST'])->isBackend()
617
        ) {
618
            return $this->buildBackendUri();
619
        }
620
        return $this->buildFrontendUri();
621
    }
622
623
    /**
624
     * Builds the URI, backend flavour
625
     * The resulting URI is relative and starts with "index.php".
626
     * The settings pageUid, pageType, noCache & linkAccessRestrictedPages
627
     * will be ignored in the backend.
628
     *
629
     * @return string The URI
630
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
631
     * @see \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::getQueryArguments
632
     */
633
    public function buildBackendUri(): string
634
    {
635
        $arguments = [];
636
        if ($this->addQueryString === true) {
637
            $arguments = GeneralUtility::_GET();
638
            foreach ($this->argumentsToBeExcludedFromQueryString as $argumentToBeExcluded) {
639
                $argumentArrayToBeExcluded = [];
640
                parse_str($argumentToBeExcluded, $argumentArrayToBeExcluded);
641
                $arguments = ArrayUtility::arrayDiffAssocRecursive($arguments, $argumentArrayToBeExcluded);
642
            }
643
        } else {
644
            $id = GeneralUtility::_GP('id');
645
            if ($id !== null) {
646
                $arguments['id'] = $id;
647
            }
648
        }
649
        // @todo Should be replaced as soon as we have a PSR-7 object here
650
        if (($GLOBALS['TYPO3_REQUEST'] ?? null) instanceof ServerRequestInterface
651
            && ($route = $GLOBALS['TYPO3_REQUEST']->getAttribute('route')) instanceof Route
652
        ) {
653
            $arguments['route'] = $route->getPath();
654
        }
655
        ArrayUtility::mergeRecursiveWithOverrule($arguments, $this->arguments);
656
        $arguments = $this->convertDomainObjectsToIdentityArrays($arguments);
657
        $this->lastArguments = $arguments;
658
        $routeName = $arguments['route'] ?? null;
659
        unset($arguments['route'], $arguments['token']);
660
        $backendUriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
661
        try {
662
            if ($this->createAbsoluteUri) {
663
                $uri = (string)$backendUriBuilder->buildUriFromRoutePath($routeName, $arguments, \TYPO3\CMS\Backend\Routing\UriBuilder::ABSOLUTE_URL);
664
            } else {
665
                $uri = (string)$backendUriBuilder->buildUriFromRoutePath($routeName, $arguments, \TYPO3\CMS\Backend\Routing\UriBuilder::ABSOLUTE_PATH);
666
            }
667
        } catch (ResourceNotFoundException $e) {
668
            try {
669
                if ($this->createAbsoluteUri) {
670
                    $uri = (string)$backendUriBuilder->buildUriFromRoute($routeName, $arguments, \TYPO3\CMS\Backend\Routing\UriBuilder::ABSOLUTE_URL);
671
                } else {
672
                    $uri = (string)$backendUriBuilder->buildUriFromRoute($routeName, $arguments, \TYPO3\CMS\Backend\Routing\UriBuilder::ABSOLUTE_PATH);
673
                }
674
            } catch (RouteNotFoundException $e) {
675
                $uri = '';
676
            }
677
        }
678
        if ($this->section !== '') {
679
            $uri .= '#' . $this->section;
680
        }
681
        return $uri;
682
    }
683
684
    /**
685
     * Builds the URI, frontend flavour
686
     *
687
     * @return string The URI
688
     * @see buildTypolinkConfiguration()
689
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
690
     */
691
    public function buildFrontendUri(): string
692
    {
693
        $typolinkConfiguration = $this->buildTypolinkConfiguration();
694
        if ($this->createAbsoluteUri === true) {
695
            $typolinkConfiguration['forceAbsoluteUrl'] = true;
696
            if ($this->absoluteUriScheme !== null) {
697
                $typolinkConfiguration['forceAbsoluteUrl.']['scheme'] = $this->absoluteUriScheme;
698
            }
699
        }
700
        // Other than stated in the doc block, typoLink_URL does not always return a string
701
        // Thus, we explicitly cast to string here.
702
        $uri = (string)$this->contentObject->typoLink_URL($typolinkConfiguration);
703
        return $uri;
704
    }
705
706
    /**
707
     * Builds a TypoLink configuration array from the current settings
708
     *
709
     * @return array typolink configuration array
710
     * @see https://docs.typo3.org/m/typo3/reference-typoscript/master/en-us/Functions/Typolink.html
711
     */
712
    protected function buildTypolinkConfiguration(): array
713
    {
714
        $typolinkConfiguration = [];
715
        $typolinkConfiguration['parameter'] = $this->targetPageUid ?? $GLOBALS['TSFE']->id;
716
        if ($this->targetPageType !== 0) {
717
            $typolinkConfiguration['parameter'] .= ',' . $this->targetPageType;
718
        } elseif ($this->format !== '') {
719
            $targetPageType = $this->extensionService->getTargetPageTypeByFormat($this->request->getControllerExtensionName(), $this->format);
720
            $typolinkConfiguration['parameter'] .= ',' . $targetPageType;
721
        }
722
        if (!empty($this->arguments)) {
723
            $arguments = $this->convertDomainObjectsToIdentityArrays($this->arguments);
724
            $this->lastArguments = $arguments;
725
            $typolinkConfiguration['additionalParams'] = HttpUtility::buildQueryString($arguments, '&');
726
        }
727
        if ($this->addQueryString === true) {
728
            $typolinkConfiguration['addQueryString'] = 1;
729
            if (!empty($this->argumentsToBeExcludedFromQueryString)) {
730
                $typolinkConfiguration['addQueryString.'] = [
731
                    'exclude' => implode(',', $this->argumentsToBeExcludedFromQueryString)
732
                ];
733
            }
734
        }
735
        if ($this->language !== null) {
736
            $typolinkConfiguration['language'] = $this->language;
737
        }
738
739
        if ($this->noCache === true) {
740
            $typolinkConfiguration['no_cache'] = 1;
741
        }
742
        if ($this->section !== '') {
743
            $typolinkConfiguration['section'] = $this->section;
744
        }
745
        if ($this->linkAccessRestrictedPages === true) {
746
            $typolinkConfiguration['linkAccessRestrictedPages'] = 1;
747
        }
748
        return $typolinkConfiguration;
749
    }
750
751
    /**
752
     * Recursively iterates through the specified arguments and turns instances of type \TYPO3\CMS\Extbase\DomainObject\AbstractEntity
753
     * into an arrays containing the uid of the domain object.
754
     *
755
     * @param array $arguments The arguments to be iterated
756
     * @throws \TYPO3\CMS\Extbase\Mvc\Exception\InvalidArgumentValueException
757
     * @return array The modified arguments array
758
     */
759
    protected function convertDomainObjectsToIdentityArrays(array $arguments): array
760
    {
761
        foreach ($arguments as $argumentKey => $argumentValue) {
762
            // if we have a LazyLoadingProxy here, make sure to get the real instance for further processing
763
            if ($argumentValue instanceof LazyLoadingProxy) {
764
                $argumentValue = $argumentValue->_loadRealInstance();
765
                // also update the value in the arguments array, because the lazyLoaded object could be
766
                // hidden and thus the $argumentValue would be NULL.
767
                $arguments[$argumentKey] = $argumentValue;
768
            }
769
            if ($argumentValue instanceof \Iterator) {
770
                $argumentValue = $this->convertIteratorToArray($argumentValue);
771
            }
772
            if ($argumentValue instanceof AbstractDomainObject) {
773
                if ($argumentValue->getUid() !== null) {
774
                    $arguments[$argumentKey] = $argumentValue->getUid();
775
                } elseif ($argumentValue instanceof AbstractValueObject) {
776
                    $arguments[$argumentKey] = $this->convertTransientObjectToArray($argumentValue);
777
                } else {
778
                    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);
779
                }
780
            } elseif (is_array($argumentValue)) {
781
                $arguments[$argumentKey] = $this->convertDomainObjectsToIdentityArrays($argumentValue);
782
            }
783
        }
784
        return $arguments;
785
    }
786
787
    /**
788
     * @param \Iterator $iterator
789
     * @return array
790
     */
791
    protected function convertIteratorToArray(\Iterator $iterator): array
792
    {
793
        if (method_exists($iterator, 'toArray')) {
794
            $array = $iterator->toArray();
795
        } else {
796
            $array = iterator_to_array($iterator);
797
        }
798
        return $array;
799
    }
800
801
    /**
802
     * Converts a given object recursively into an array.
803
     *
804
     * @param \TYPO3\CMS\Extbase\DomainObject\AbstractDomainObject $object
805
     * @return array
806
     * @todo Refactor this into convertDomainObjectsToIdentityArrays()
807
     * @internal only to be used within Extbase, not part of TYPO3 Core API.
808
     */
809
    public function convertTransientObjectToArray(AbstractDomainObject $object): array
810
    {
811
        $result = [];
812
        foreach ($object->_getProperties() as $propertyName => $propertyValue) {
813
            if ($propertyValue instanceof \Iterator) {
814
                $propertyValue = $this->convertIteratorToArray($propertyValue);
815
            }
816
            if ($propertyValue instanceof AbstractDomainObject) {
817
                if ($propertyValue->getUid() !== null) {
818
                    $result[$propertyName] = $propertyValue->getUid();
819
                } else {
820
                    $result[$propertyName] = $this->convertTransientObjectToArray($propertyValue);
821
                }
822
            } elseif (is_array($propertyValue)) {
823
                $result[$propertyName] = $this->convertDomainObjectsToIdentityArrays($propertyValue);
824
            } else {
825
                $result[$propertyName] = $propertyValue;
826
            }
827
        }
828
        return $result;
829
    }
830
}
831