[BUGFIX] Respect GET parameters when generating canonicalized URLs
[Packages/TYPO3.CMS.git] / typo3 / sysext / seo / Classes / Canonical / CanonicalGenerator.php
1 <?php
2 declare(strict_types = 1);
3
4 namespace TYPO3\CMS\Seo\Canonical;
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\GeneralUtility;
20 use TYPO3\CMS\Extbase\SignalSlot\Dispatcher;
21 use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
22 use TYPO3\CMS\Frontend\Page\PageRepository;
23 use TYPO3\CMS\Frontend\Utility\CanonicalizationUtility;
24
25 /**
26 * Class to add the canonical tag to the page
27 *
28 * @internal this class is not part of TYPO3's Core API.
29 */
30 class CanonicalGenerator
31 {
32 /**
33 * @var TypoScriptFrontendController
34 */
35 protected $typoScriptFrontendController;
36
37 /**
38 * @var PageRepository
39 */
40 protected $pageRepository;
41
42 /**
43 * @var Dispatcher
44 */
45 protected $signalSlotDispatcher;
46
47 /**
48 * CanonicalGenerator constructor
49 *
50 * @param TypoScriptFrontendController $typoScriptFrontendController
51 * @param Dispatcher $signalSlotDispatcher
52 */
53 public function __construct(TypoScriptFrontendController $typoScriptFrontendController = null, Dispatcher $signalSlotDispatcher = null)
54 {
55 if ($typoScriptFrontendController === null) {
56 $typoScriptFrontendController = $this->getTypoScriptFrontendController();
57 }
58 if ($signalSlotDispatcher === null) {
59 $signalSlotDispatcher = GeneralUtility::makeInstance(Dispatcher::class);
60 }
61 $this->typoScriptFrontendController = $typoScriptFrontendController;
62 $this->signalSlotDispatcher = $signalSlotDispatcher;
63 $this->pageRepository = GeneralUtility::makeInstance(PageRepository::class);
64 }
65
66 /**
67 * @return string
68 * @throws \TYPO3\CMS\Extbase\SignalSlot\Exception\InvalidSlotException
69 * @throws \TYPO3\CMS\Extbase\SignalSlot\Exception\InvalidSlotReturnException
70 */
71 public function generate(): string
72 {
73 $href = '';
74 $this->signalSlotDispatcher->dispatch(self::class, 'beforeGeneratingCanonical', [&$href]);
75
76 if (empty($href) && (int)$this->typoScriptFrontendController->page['no_index'] === 1) {
77 return '';
78 }
79
80 if (empty($href)) {
81 // 1) Check if page show content from other page
82 $href = $this->checkContentFromPid();
83 }
84 if (empty($href)) {
85 // 2) Check if page has canonical URL set
86 $href = $this->checkForCanonicalLink();
87 }
88 if (empty($href)) {
89 // 3) Fallback, create canonical URL
90 $href = $this->checkDefaultCanonical();
91 }
92
93 if (!empty($href)) {
94 $canonical = '<link ' . GeneralUtility::implodeAttributes([
95 'rel' => 'canonical',
96 'href' => $href
97 ], true) . '/>' . LF;
98 $this->typoScriptFrontendController->additionalHeaderData[] = $canonical;
99 return $canonical;
100 }
101 return '';
102 }
103
104 /**
105 * @return string
106 */
107 protected function checkForCanonicalLink(): string
108 {
109 if (!empty($this->typoScriptFrontendController->page['canonical_link'])) {
110 return $this->typoScriptFrontendController->cObj->typoLink_URL([
111 'parameter' => $this->typoScriptFrontendController->page['canonical_link'],
112 'forceAbsoluteUrl' => true,
113 ]);
114 }
115 return '';
116 }
117
118 /**
119 * @return string
120 */
121 protected function checkContentFromPid(): string
122 {
123 if (!empty($this->typoScriptFrontendController->page['content_from_pid'])) {
124 $parameter = (int)$this->typoScriptFrontendController->page['content_from_pid'];
125 if ($parameter > 0) {
126 $targetPage = $this->pageRepository->getPage($parameter, true);
127 if (!empty($targetPage['canonical_link'])) {
128 $parameter = $targetPage['canonical_link'];
129 }
130 return $this->typoScriptFrontendController->cObj->typoLink_URL([
131 'parameter' => $parameter,
132 'forceAbsoluteUrl' => true,
133 ]);
134 }
135 }
136 return '';
137 }
138
139 /**
140 * @return string
141 */
142 protected function checkDefaultCanonical(): string
143 {
144 return $this->typoScriptFrontendController->cObj->typoLink_URL([
145 'parameter' => $this->typoScriptFrontendController->id . ',' . $this->typoScriptFrontendController->type,
146 'forceAbsoluteUrl' => true,
147 'addQueryString' => true,
148 'addQueryString.' => [
149 'method' => 'GET',
150 'exclude' => implode(
151 ',',
152 CanonicalizationUtility::getParamsToExcludeForCanonicalizedUrl(
153 (int)$this->typoScriptFrontendController->id,
154 (array)$GLOBALS['TYPO3_CONF_VARS']['FE']['additionalCanonicalizedUrlParameters']
155 )
156 )
157 ]
158 ]);
159 }
160
161 /**
162 * @return TypoScriptFrontendController
163 */
164 protected function getTypoScriptFrontendController(): TypoScriptFrontendController
165 {
166 return $GLOBALS['TSFE'];
167 }
168 }