ffd47dbe9f13f0784e8dd88a785270b87187155f
[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 if (strpos($softRefEntry['substr']['recordRef'], 'pages:') !== 0) {
77 return true;
78 }
79 // Defines the linked page and anchor (if any).
80 if (strpos($url, '#c') !== false) {
81 $parts = explode('#c', $url);
82 $page = $parts[0];
83 $anchor = $parts[1];
84 } else {
85 $page = $url;
86 }
87 // Check if the linked page is OK
88 $this->responsePage = $this->checkPage($page);
89 // Check if the linked content element is OK
90 if ($anchor) {
91 // Check if the content element is OK
92 $this->responseContent = $this->checkContent($page, $anchor);
93 }
94 if (
95 is_array($this->errorParams['page']) && !$this->responsePage
96 || is_array($this->errorParams['content']) && !$this->responseContent
97 ) {
98 $this->setErrorParams($this->errorParams);
99 }
100
101 return $this->responsePage && $this->responseContent;
102 }
103
104 /**
105 * Checks a given page uid for validity
106 *
107 * @param string $page Page uid to check
108 * @return bool TRUE on success or FALSE on error
109 */
110 protected function checkPage($page)
111 {
112 // Get page ID on which the content element in fact is located
113 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
114 $queryBuilder->getRestrictions()->removeAll();
115 $row = $queryBuilder
116 ->select('uid', 'title', 'deleted', 'hidden', 'starttime', 'endtime')
117 ->from('pages')
118 ->where(
119 $queryBuilder->expr()->eq(
120 'uid',
121 $queryBuilder->createNamedParameter($page, \PDO::PARAM_INT)
122 )
123 )
124 ->execute()
125 ->fetch();
126 $this->responsePage = true;
127 if ($row) {
128 if ($row['deleted'] == '1') {
129 $this->errorParams['errorType']['page'] = self::DELETED;
130 $this->errorParams['page']['title'] = $row['title'];
131 $this->errorParams['page']['uid'] = $row['uid'];
132 $this->responsePage = false;
133 } elseif ($row['hidden'] == '1'
134 || $GLOBALS['EXEC_TIME'] < (int)$row['starttime']
135 || $row['endtime'] && (int)$row['endtime'] < $GLOBALS['EXEC_TIME']
136 ) {
137 $this->errorParams['errorType']['page'] = self::HIDDEN;
138 $this->errorParams['page']['title'] = $row['title'];
139 $this->errorParams['page']['uid'] = $row['uid'];
140 $this->responsePage = false;
141 }
142 } else {
143 $this->errorParams['errorType']['page'] = self::NOTEXISTING;
144 $this->errorParams['page']['uid'] = (int)$page;
145 $this->responsePage = false;
146 }
147 return $this->responsePage;
148 }
149
150 /**
151 * Checks a given content uid for validity
152 *
153 * @param string $page Uid of the page to which the link is pointing
154 * @param string $anchor Uid of the content element to check
155 * @return bool TRUE on success or FALSE on error
156 */
157 protected function checkContent($page, $anchor)
158 {
159 // Get page ID on which the content element in fact is located
160 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('tt_content');
161 $queryBuilder->getRestrictions()->removeAll();
162 $row = $queryBuilder
163 ->select('uid', 'pid', 'header', 'deleted', 'hidden', 'starttime', 'endtime')
164 ->from('tt_content')
165 ->where(
166 $queryBuilder->expr()->eq(
167 'uid',
168 $queryBuilder->createNamedParameter($anchor, \PDO::PARAM_INT)
169 )
170 )
171 ->execute()
172 ->fetch();
173 $this->responseContent = true;
174 // this content element exists
175 if ($row) {
176 $page = (int)$page;
177 // page ID on which this CE is in fact located.
178 $correctPageID = (int)$row['pid'];
179 // Check if the element is on the linked page
180 // (The element might have been moved to another page)
181 if ($correctPageID !== $page) {
182 $this->errorParams['errorType']['content'] = self::MOVED;
183 $this->errorParams['content']['uid'] = (int)$anchor;
184 $this->errorParams['content']['wrongPage'] = $page;
185 $this->errorParams['content']['rightPage'] = $correctPageID;
186 $this->responseContent = false;
187 } else {
188 // The element is located on the page to which the link is pointing
189 if ($row['deleted'] == '1') {
190 $this->errorParams['errorType']['content'] = self::DELETED;
191 $this->errorParams['content']['title'] = $row['header'];
192 $this->errorParams['content']['uid'] = $row['uid'];
193 $this->responseContent = false;
194 } elseif ($row['hidden'] == '1' || $GLOBALS['EXEC_TIME'] < (int)$row['starttime'] || $row['endtime'] && (int)$row['endtime'] < $GLOBALS['EXEC_TIME']) {
195 $this->errorParams['errorType']['content'] = self::HIDDEN;
196 $this->errorParams['content']['title'] = $row['header'];
197 $this->errorParams['content']['uid'] = $row['uid'];
198 $this->responseContent = false;
199 }
200 }
201 } else {
202 // The content element does not exist
203 $this->errorParams['errorType']['content'] = self::NOTEXISTING;
204 $this->errorParams['content']['uid'] = (int)$anchor;
205 $this->responseContent = false;
206 }
207 return $this->responseContent;
208 }
209
210 /**
211 * Generates the localized error message from the error params saved from the parsing
212 *
213 * @param array $errorParams All parameters needed for the rendering of the error message
214 * @return string Validation error message
215 */
216 public function getErrorMessage($errorParams)
217 {
218 $lang = $this->getLanguageService();
219 $errorType = $errorParams['errorType'];
220 if (is_array($errorParams['page'])) {
221 switch ($errorType['page']) {
222 case self::DELETED:
223 $errorPage = str_replace(
224 [
225 '###title###',
226 '###uid###'
227 ],
228 [
229 $errorParams['page']['title'],
230 $errorParams['page']['uid']
231 ],
232 $lang->getLL('list.report.pagedeleted')
233 );
234 break;
235 case self::HIDDEN:
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.pagenotvisible')
246 );
247 break;
248 default:
249 $errorPage = str_replace(
250 '###uid###',
251 $errorParams['page']['uid'],
252 $lang->getLL('list.report.pagenotexisting')
253 );
254 }
255 }
256 if (is_array($errorParams['content'])) {
257 switch ($errorType['content']) {
258 case self::DELETED:
259 $errorContent = str_replace(
260 [
261 '###title###',
262 '###uid###'
263 ],
264 [
265 $errorParams['content']['title'],
266 $errorParams['content']['uid']
267 ],
268 $lang->getLL('list.report.contentdeleted')
269 );
270 break;
271 case self::HIDDEN:
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.contentnotvisible')
282 );
283 break;
284 case self::MOVED:
285 $errorContent = str_replace(
286 [
287 '###title###',
288 '###uid###',
289 '###wrongpage###',
290 '###rightpage###'
291 ],
292 [
293 $errorParams['content']['title'],
294 $errorParams['content']['uid'],
295 $errorParams['content']['wrongPage'],
296 $errorParams['content']['rightPage']
297 ],
298 $lang->getLL('list.report.contentmoved')
299 );
300 break;
301 default:
302 $errorContent = str_replace('###uid###', $errorParams['content']['uid'], $lang->getLL('list.report.contentnotexisting'));
303 }
304 }
305 if (isset($errorPage) && isset($errorContent)) {
306 $response = $errorPage . LF . $errorContent;
307 } elseif (isset($errorPage)) {
308 $response = $errorPage;
309 } elseif (isset($errorContent)) {
310 $response = $errorContent;
311 } else {
312 // This should not happen
313 $response = $lang->getLL('list.report.noinformation');
314 }
315 return $response;
316 }
317
318 /**
319 * Constructs a valid Url for browser output
320 *
321 * @param array $row Broken link record
322 * @return string Parsed broken url
323 */
324 public function getBrokenUrl($row)
325 {
326 $domain = rtrim(GeneralUtility::getIndpEnv('TYPO3_SITE_URL'), '/');
327 $rootLine = BackendUtility::BEgetRootLine($row['record_pid']);
328 // checks alternate domains
329 if (!empty($rootLine)) {
330 $protocol = GeneralUtility::getIndpEnv('TYPO3_SSL') ? 'https://' : 'http://';
331 $domainRecord = BackendUtility::firstDomainRecord($rootLine);
332 if (!empty($domainRecord)) {
333 $domain = $protocol . $domainRecord;
334 }
335 }
336 return $domain . '/index.php?id=' . $row['url'];
337 }
338 }