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