4c1c80c700c88045e6dc6083e33a6f01254ee625
[Packages/TYPO3.CMS.git] / typo3 / sysext / linkvalidator / Classes / Linktype / InternalLinktype.php
1 <?php
2 namespace TYPO3\CMS\Linkvalidator\Linktype;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use TYPO3\CMS\Backend\Utility\BackendUtility;
18 use TYPO3\CMS\Core\Database\ConnectionPool;
19 use TYPO3\CMS\Core\Utility\GeneralUtility;
20
21 /**
22 * This class provides Check Internal Links plugin implementation
23 */
24 class InternalLinktype extends AbstractLinktype
25 {
26 /**
27 * @var string
28 */
29 const DELETED = 'deleted';
30
31 /**
32 * @var string
33 */
34 const HIDDEN = 'hidden';
35
36 /**
37 * @var string
38 */
39 const MOVED = 'moved';
40
41 /**
42 * @var string
43 */
44 const NOTEXISTING = 'notExisting';
45
46 /**
47 * Result of the check, if the current page uid is valid or not
48 *
49 * @var bool
50 */
51 protected $responsePage = true;
52
53 /**
54 * Result of the check, if the current content uid is valid or not
55 *
56 * @var bool
57 */
58 protected $responseContent = true;
59
60 /**
61 * Checks a given URL + /path/filename.ext for validity
62 *
63 * @param string $url Url to check as page-id or page-id#anchor (if anchor is present)
64 * @param array $softRefEntry: The soft reference entry which builds the context of that url
65 * @param \TYPO3\CMS\Linkvalidator\LinkAnalyzer $reference Parent instance
66 * @return bool TRUE on success or FALSE on error
67 */
68 public function checkLink($url, $softRefEntry, $reference)
69 {
70 $anchor = '';
71 $this->responseContent = true;
72 // Might already contain values - empty it
73 unset($this->errorParams);
74 // Only check pages records. Content elements will also be checked
75 // as we extract the anchor in the next step.
76 [$table, $uid] = explode(':', $softRefEntry['substr']['recordRef']);
77 if (!in_array($table, ['pages', 'tt_content'], true)) {
78 return true;
79 }
80 // Defines the linked page and anchor (if any).
81 if (strpos($url, '#c') !== false) {
82 $parts = explode('#c', $url);
83 $page = $parts[0];
84 $anchor = $parts[1];
85 } elseif (
86 $table === 'tt_content'
87 && strpos($softRefEntry['row'][$softRefEntry['field']], 't3://') === 0
88 ) {
89 $parsedTypoLinkUrl = @parse_url($softRefEntry['row'][$softRefEntry['field']]);
90 if ($parsedTypoLinkUrl['host'] === 'page') {
91 parse_str($parsedTypoLinkUrl['query'], $query);
92 if (isset($query['uid'])) {
93 $page = (int)$query['uid'];
94 $anchor = (int)$url;
95 }
96 }
97 } else {
98 $page = $url;
99 }
100 // Check if the linked page is OK
101 $this->responsePage = $this->checkPage($page);
102 // Check if the linked content element is OK
103 if ($anchor) {
104 // Check if the content element is OK
105 $this->responseContent = $this->checkContent($page, $anchor);
106 }
107 if (
108 is_array($this->errorParams['page']) && !$this->responsePage
109 || is_array($this->errorParams['content']) && !$this->responseContent
110 ) {
111 $this->setErrorParams($this->errorParams);
112 }
113
114 return $this->responsePage && $this->responseContent;
115 }
116
117 /**
118 * Checks a given page uid for validity
119 *
120 * @param string $page Page uid to check
121 * @return bool TRUE on success or FALSE on error
122 */
123 protected function checkPage($page)
124 {
125 // Get page ID on which the content element in fact is located
126 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
127 $queryBuilder->getRestrictions()->removeAll();
128 $row = $queryBuilder
129 ->select('uid', 'title', 'deleted', 'hidden', 'starttime', 'endtime')
130 ->from('pages')
131 ->where(
132 $queryBuilder->expr()->eq(
133 'uid',
134 $queryBuilder->createNamedParameter($page, \PDO::PARAM_INT)
135 )
136 )
137 ->execute()
138 ->fetch();
139 $this->responsePage = true;
140 if ($row) {
141 if ($row['deleted'] == '1') {
142 $this->errorParams['errorType']['page'] = self::DELETED;
143 $this->errorParams['page']['title'] = $row['title'];
144 $this->errorParams['page']['uid'] = $row['uid'];
145 $this->responsePage = false;
146 } elseif ($row['hidden'] == '1'
147 || $GLOBALS['EXEC_TIME'] < (int)$row['starttime']
148 || $row['endtime'] && (int)$row['endtime'] < $GLOBALS['EXEC_TIME']
149 ) {
150 $this->errorParams['errorType']['page'] = self::HIDDEN;
151 $this->errorParams['page']['title'] = $row['title'];
152 $this->errorParams['page']['uid'] = $row['uid'];
153 $this->responsePage = false;
154 }
155 } else {
156 $this->errorParams['errorType']['page'] = self::NOTEXISTING;
157 $this->errorParams['page']['uid'] = (int)$page;
158 $this->responsePage = false;
159 }
160 return $this->responsePage;
161 }
162
163 /**
164 * Checks a given content uid for validity
165 *
166 * @param string $page Uid of the page to which the link is pointing
167 * @param string $anchor Uid of the content element to check
168 * @return bool TRUE on success or FALSE on error
169 */
170 protected function checkContent($page, $anchor)
171 {
172 // Get page ID on which the content element in fact is located
173 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('tt_content');
174 $queryBuilder->getRestrictions()->removeAll();
175 $row = $queryBuilder
176 ->select('uid', 'pid', 'header', 'deleted', 'hidden', 'starttime', 'endtime')
177 ->from('tt_content')
178 ->where(
179 $queryBuilder->expr()->eq(
180 'uid',
181 $queryBuilder->createNamedParameter($anchor, \PDO::PARAM_INT)
182 )
183 )
184 ->execute()
185 ->fetch();
186 $this->responseContent = true;
187 // this content element exists
188 if ($row) {
189 $page = (int)$page;
190 // page ID on which this CE is in fact located.
191 $correctPageID = (int)$row['pid'];
192 // Check if the element is on the linked page
193 // (The element might have been moved to another page)
194 if ($correctPageID !== $page) {
195 $this->errorParams['errorType']['content'] = self::MOVED;
196 $this->errorParams['content']['uid'] = (int)$anchor;
197 $this->errorParams['content']['wrongPage'] = $page;
198 $this->errorParams['content']['rightPage'] = $correctPageID;
199 $this->responseContent = false;
200 } else {
201 // The element is located on the page to which the link is pointing
202 if ($row['deleted'] == '1') {
203 $this->errorParams['errorType']['content'] = self::DELETED;
204 $this->errorParams['content']['title'] = $row['header'];
205 $this->errorParams['content']['uid'] = $row['uid'];
206 $this->responseContent = false;
207 } elseif ($row['hidden'] == '1' || $GLOBALS['EXEC_TIME'] < (int)$row['starttime'] || $row['endtime'] && (int)$row['endtime'] < $GLOBALS['EXEC_TIME']) {
208 $this->errorParams['errorType']['content'] = self::HIDDEN;
209 $this->errorParams['content']['title'] = $row['header'];
210 $this->errorParams['content']['uid'] = $row['uid'];
211 $this->responseContent = false;
212 }
213 }
214 } else {
215 // The content element does not exist
216 $this->errorParams['errorType']['content'] = self::NOTEXISTING;
217 $this->errorParams['content']['uid'] = (int)$anchor;
218 $this->responseContent = false;
219 }
220 return $this->responseContent;
221 }
222
223 /**
224 * Generates the localized error message from the error params saved from the parsing
225 *
226 * @param array $errorParams All parameters needed for the rendering of the error message
227 * @return string Validation error message
228 */
229 public function getErrorMessage($errorParams)
230 {
231 $lang = $this->getLanguageService();
232 $errorType = $errorParams['errorType'];
233 if (is_array($errorParams['page'])) {
234 switch ($errorType['page']) {
235 case self::DELETED:
236 $errorPage = str_replace(
237 [
238 '###title###',
239 '###uid###'
240 ],
241 [
242 $errorParams['page']['title'],
243 $errorParams['page']['uid']
244 ],
245 $lang->getLL('list.report.pagedeleted')
246 );
247 break;
248 case self::HIDDEN:
249 $errorPage = str_replace(
250 [
251 '###title###',
252 '###uid###'
253 ],
254 [
255 $errorParams['page']['title'],
256 $errorParams['page']['uid']
257 ],
258 $lang->getLL('list.report.pagenotvisible')
259 );
260 break;
261 default:
262 $errorPage = str_replace(
263 '###uid###',
264 $errorParams['page']['uid'],
265 $lang->getLL('list.report.pagenotexisting')
266 );
267 }
268 }
269 if (is_array($errorParams['content'])) {
270 switch ($errorType['content']) {
271 case self::DELETED:
272 $errorContent = str_replace(
273 [
274 '###title###',
275 '###uid###'
276 ],
277 [
278 $errorParams['content']['title'],
279 $errorParams['content']['uid']
280 ],
281 $lang->getLL('list.report.contentdeleted')
282 );
283 break;
284 case self::HIDDEN:
285 $errorContent = str_replace(
286 [
287 '###title###',
288 '###uid###'
289 ],
290 [
291 $errorParams['content']['title'],
292 $errorParams['content']['uid']
293 ],
294 $lang->getLL('list.report.contentnotvisible')
295 );
296 break;
297 case self::MOVED:
298 $errorContent = str_replace(
299 [
300 '###title###',
301 '###uid###',
302 '###wrongpage###',
303 '###rightpage###'
304 ],
305 [
306 $errorParams['content']['title'],
307 $errorParams['content']['uid'],
308 $errorParams['content']['wrongPage'],
309 $errorParams['content']['rightPage']
310 ],
311 $lang->getLL('list.report.contentmoved')
312 );
313 break;
314 default:
315 $errorContent = str_replace('###uid###', $errorParams['content']['uid'], $lang->getLL('list.report.contentnotexisting'));
316 }
317 }
318 if (isset($errorPage) && isset($errorContent)) {
319 $response = $errorPage . LF . $errorContent;
320 } elseif (isset($errorPage)) {
321 $response = $errorPage;
322 } elseif (isset($errorContent)) {
323 $response = $errorContent;
324 } else {
325 // This should not happen
326 $response = $lang->getLL('list.report.noinformation');
327 }
328 return $response;
329 }
330
331 /**
332 * Constructs a valid Url for browser output
333 *
334 * @param array $row Broken link record
335 * @return string Parsed broken url
336 */
337 public function getBrokenUrl($row)
338 {
339 $domain = rtrim(GeneralUtility::getIndpEnv('TYPO3_SITE_URL'), '/');
340 $rootLine = BackendUtility::BEgetRootLine($row['record_pid']);
341 // checks alternate domains
342 if (!empty($rootLine)) {
343 $protocol = GeneralUtility::getIndpEnv('TYPO3_SSL') ? 'https://' : 'http://';
344 $domainRecord = BackendUtility::firstDomainRecord($rootLine);
345 if (!empty($domainRecord)) {
346 $domain = $protocol . $domainRecord;
347 }
348 }
349 return $domain . '/index.php?id=' . $row['url'];
350 }
351 }