[DOCS] Remove confusing "cron command" ambiguity.
[Packages/TYPO3.CMS.git] / typo3 / sysext / linkvalidator / Classes / Report / LinkValidatorReport.php
1 <?php
2 namespace TYPO3\CMS\Linkvalidator\Report;
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 Doctrine\DBAL\Driver\Statement;
18 use TYPO3\CMS\Backend\Template\DocumentTemplate;
19 use TYPO3\CMS\Backend\Template\ModuleTemplate;
20 use TYPO3\CMS\Backend\Utility\BackendUtility;
21 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
22 use TYPO3\CMS\Core\Database\Connection;
23 use TYPO3\CMS\Core\Database\ConnectionPool;
24 use TYPO3\CMS\Core\Database\Query\QueryHelper;
25 use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
26 use TYPO3\CMS\Core\Imaging\Icon;
27 use TYPO3\CMS\Core\Imaging\IconFactory;
28 use TYPO3\CMS\Core\Localization\LanguageService;
29 use TYPO3\CMS\Core\Messaging\FlashMessage;
30 use TYPO3\CMS\Core\Messaging\FlashMessageService;
31 use TYPO3\CMS\Core\Page\PageRenderer;
32 use TYPO3\CMS\Core\Service\MarkerBasedTemplateService;
33 use TYPO3\CMS\Core\Type\Bitmask\Permission;
34 use TYPO3\CMS\Core\Utility\GeneralUtility;
35 use TYPO3\CMS\Info\Controller\InfoModuleController;
36 use TYPO3\CMS\Linkvalidator\LinkAnalyzer;
37
38 /**
39 * Module 'Link validator' as sub module of Web -> Info
40 * @internal This class is a specific Backend controller implementation and is not part of the TYPO3's Core API.
41 */
42 class LinkValidatorReport
43 {
44
45 /**
46 * @var DocumentTemplate
47 */
48 protected $doc;
49
50 /**
51 * Information about the current page record
52 *
53 * @var array
54 */
55 protected $pageRecord = [];
56
57 /**
58 * Information, if the module is accessible for the current user or not
59 *
60 * @var bool
61 */
62 protected $isAccessibleForCurrentUser = false;
63
64 /**
65 * Link validation class
66 *
67 * @var LinkAnalyzer
68 */
69 protected $linkAnalyzer;
70
71 /**
72 * TSconfig of the current module
73 *
74 * @var array
75 */
76 protected $modTS = [];
77
78 /**
79 * List of available link types to check defined in the TSconfig
80 *
81 * @var array
82 */
83 protected $availableOptions = [];
84
85 /**
86 * Depth for the recursive traversal of pages for the link validation
87 * For "Report" and "Check link" tab.
88 *
89 * @var array
90 */
91 protected $searchLevel = ['report' => 0, 'check' => 0];
92
93 /**
94 * List of link types currently chosen in the statistics table
95 * Used to show broken links of these types only
96 * For "Report" and "Check link" tab
97 *
98 * @var array
99 */
100 protected $checkOpt = ['report' => [], 'check' => []];
101
102 /**
103 * Html for the statistics table with the checkboxes of the link types
104 * and the numbers of broken links
105 * For "Report" and "Check link" tab
106 *
107 * @var array
108 */
109 protected $checkOptionsHtml = ['report' => [], 'check' => []];
110
111 /**
112 * Complete content (html) to be displayed
113 *
114 * @var string
115 */
116 protected $content;
117
118 /**
119 * @var \TYPO3\CMS\Linkvalidator\Linktype\LinktypeInterface[]
120 */
121 protected $hookObjectsArr = [];
122
123 /**
124 * @var string
125 */
126 protected $updateListHtml = '';
127
128 /**
129 * @var string
130 */
131 protected $refreshListHtml = '';
132
133 /**
134 * @var MarkerBasedTemplateService
135 */
136 protected $templateService;
137
138 /**
139 * @var IconFactory
140 */
141 protected $iconFactory;
142
143 /**
144 * @var int Value of the GET/POST var 'id'
145 */
146 protected $id;
147
148 /**
149 * @var InfoModuleController Contains a reference to the parent calling object
150 */
151 protected $pObj;
152
153 /**
154 * Init, called from parent object
155 *
156 * @param InfoModuleController $pObj A reference to the parent (calling) object
157 */
158 public function init($pObj)
159 {
160 $this->pObj = $pObj;
161 $this->id = (int)GeneralUtility::_GP('id');
162 }
163
164 /**
165 * Main, called from parent object
166 *
167 * @return string Module content
168 */
169 public function main()
170 {
171 $this->getLanguageService()->includeLLFile('EXT:linkvalidator/Resources/Private/Language/Module/locallang.xlf');
172 $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
173 $update = GeneralUtility::_GP('updateLinkList');
174 $prefix = 'check';
175 $other = 'report';
176
177 if (empty($update)) {
178 $prefix = 'report';
179 $other = 'check';
180 }
181
182 // get searchLevel (number of levels of pages to check / show results)
183 $this->searchLevel[$prefix] = GeneralUtility::_GP($prefix . '_search_levels');
184 if (isset($this->id)) {
185 $this->modTS = BackendUtility::getPagesTSconfig($this->id)['mod.']['linkvalidator.'] ?? [];
186 }
187 if (isset($this->searchLevel[$prefix])) {
188 $this->pObj->MOD_SETTINGS[$prefix . '_searchlevel'] = $this->searchLevel[$prefix];
189 } else {
190 $this->searchLevel[$prefix] = $this->pObj->MOD_SETTINGS[$prefix . '_searchlevel'];
191 }
192 if (isset($this->pObj->MOD_SETTINGS[$other . '_searchlevel'])) {
193 $this->searchLevel[$other] = $this->pObj->MOD_SETTINGS[$other . '_searchlevel'];
194 }
195
196 // which linkTypes to check (internal, file, external, ...)
197 $set = GeneralUtility::_GP($prefix . '_SET');
198
199 foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['linkvalidator']['checkLinks'] ?? [] as $linkType => $value) {
200 // Compile list of all available types. Used for checking with button "Check Links".
201 if (strpos($this->modTS['linktypes'], $linkType) !== false) {
202 $this->availableOptions[$linkType] = 1;
203 }
204
205 // 1) if "$prefix_values" = "1" : use POST variables
206 // 2) if not set, use stored configuration in $this->>pObj->MOD_SETTINGS
207 // 3) if not set, use default
208 unset($this->checkOpt[$prefix][$linkType]);
209 if (!empty(GeneralUtility::_GP($prefix . '_values'))) {
210 if (isset($set[$linkType])) {
211 $this->checkOpt[$prefix][$linkType] = $set[$linkType];
212 } else {
213 $this->checkOpt[$prefix][$linkType] = '0';
214 }
215 $this->pObj->MOD_SETTINGS[$prefix . '_' . $linkType] = $this->checkOpt[$prefix][$linkType];
216 } elseif (isset($this->pObj->MOD_SETTINGS[$prefix . '_' . $linkType])) {
217 $this->checkOpt[$prefix][$linkType] = $this->pObj->MOD_SETTINGS[$prefix . '_' . $linkType];
218 } else {
219 // use default
220 $this->checkOpt[$prefix][$linkType] = '0';
221 $this->pObj->MOD_SETTINGS[$prefix . '_' . $linkType] = $this->checkOpt[$prefix][$linkType];
222 }
223 if (isset($this->pObj->MOD_SETTINGS[$other . '_' . $linkType])) {
224 $this->checkOpt[$other][$linkType] = $this->pObj->MOD_SETTINGS[$other . '_' . $linkType];
225 }
226 }
227
228 // save settings
229 $this->getBackendUser()->pushModuleData('web_info', $this->pObj->MOD_SETTINGS);
230 $this->initialize();
231
232 // Localization
233 $this->getPageRenderer()->addInlineLanguageLabelFile('EXT:linkvalidator/Resources/Private/Language/Module/locallang.xlf');
234
235 if ($this->modTS['showCheckLinkTab'] == 1) {
236 $this->updateListHtml = '<input class="btn btn-default t3js-update-button" type="submit" name="updateLinkList" id="updateLinkList" value="'
237 . htmlspecialchars($this->getLanguageService()->getLL('label_update'))
238 . '" data-notification-message="'
239 . htmlspecialchars($this->getLanguageService()->getLL('label_update-link-list'))
240 . '"/>';
241 }
242 $this->refreshListHtml = '<input class="btn btn-default t3js-update-button" type="submit" name="refreshLinkList" id="refreshLinkList" value="'
243 . htmlspecialchars($this->getLanguageService()->getLL('label_refresh'))
244 . '" data-notification-message="'
245 . htmlspecialchars($this->getLanguageService()->getLL('label_refresh-link-list'))
246 . '"/>';
247 $this->linkAnalyzer = GeneralUtility::makeInstance(LinkAnalyzer::class);
248 $this->updateBrokenLinks();
249
250 $brokenLinkOverView = $this->linkAnalyzer->getLinkCounts($this->id);
251 $this->checkOptionsHtml['report'] = $this->getCheckOptions($brokenLinkOverView, 'report');
252 $this->checkOptionsHtml['check'] = $this->getCheckOptions($brokenLinkOverView, 'check');
253 $this->render();
254
255 $pageTile = '';
256 if ($this->id) {
257 $pageRecord = BackendUtility::getRecord('pages', $this->id);
258 $pageTile = '<h1>' . htmlspecialchars(BackendUtility::getRecordTitle('pages', $pageRecord)) . '</h1>';
259 }
260
261 return '<div id="linkvalidator-modfuncreport">' . $pageTile . $this->createTabs() . '</div>';
262 }
263
264 /**
265 * Create tabs to split the report and the checkLink functions
266 *
267 * @return string
268 */
269 protected function createTabs()
270 {
271 $languageService = $this->getLanguageService();
272 $menuItems = [
273 0 => [
274 'label' => $languageService->getLL('Report'),
275 'content' => $this->flush(true)
276 ],
277 ];
278
279 if ((bool)$this->modTS['showCheckLinkTab']) {
280 $menuItems[1] = [
281 'label' => $languageService->getLL('CheckLink'),
282 'content' => $this->flush()
283 ];
284 }
285
286 $moduleTemplate = GeneralUtility::makeInstance(ModuleTemplate::class);
287 return $moduleTemplate->getDynamicTabMenu($menuItems, 'report-linkvalidator');
288 }
289
290 /**
291 * Initializes the Module
292 */
293 protected function initialize()
294 {
295 foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['linkvalidator']['checkLinks'] ?? [] as $linkType => $className) {
296 $this->hookObjectsArr[$linkType] = GeneralUtility::makeInstance($className);
297 }
298
299 $this->doc = GeneralUtility::makeInstance(DocumentTemplate::class);
300 $this->doc->setModuleTemplate('EXT:linkvalidator/Resources/Private/Templates/mod_template.html');
301
302 $this->pageRecord = BackendUtility::readPageAccess($this->id, $this->getBackendUser()->getPagePermsClause(Permission::PAGE_SHOW));
303 if ($this->id && is_array($this->pageRecord) || !$this->id && $this->isCurrentUserAdmin()) {
304 $this->isAccessibleForCurrentUser = true;
305 }
306
307 $pageRenderer = $this->getPageRenderer();
308 $pageRenderer->addCssFile('EXT:linkvalidator/Resources/Public/Css/linkvalidator.css', 'stylesheet', 'screen');
309 $pageRenderer->loadRequireJsModule('TYPO3/CMS/Linkvalidator/Linkvalidator');
310
311 $this->templateService = GeneralUtility::makeInstance(MarkerBasedTemplateService::class);
312
313 // Don't access in workspace
314 if ($this->getBackendUser()->workspace !== 0) {
315 $this->isAccessibleForCurrentUser = false;
316 }
317 }
318
319 /**
320 * Updates the table of stored broken links
321 */
322 protected function updateBrokenLinks()
323 {
324 $searchFields = [];
325 // Get the searchFields from TypoScript
326 foreach ($this->modTS['searchFields.'] as $table => $fieldList) {
327 $fields = GeneralUtility::trimExplode(',', $fieldList, true);
328 foreach ($fields as $field) {
329 if (!$searchFields || !is_array($searchFields[$table]) || !in_array($field, $searchFields[$table], true)) {
330 $searchFields[$table][] = $field;
331 }
332 }
333 }
334 $rootLineHidden = $this->linkAnalyzer->getRootLineIsHidden($this->pObj->pageinfo);
335 if (!$rootLineHidden || $this->modTS['checkhidden'] == 1) {
336 $permsClause = $this->getBackendUser()->getPagePermsClause(Permission::PAGE_SHOW);
337 // Get children pages
338 $pageList = $this->linkAnalyzer->extGetTreeList(
339 $this->id,
340 $this->searchLevel['check'],
341 0,
342 $permsClause,
343 $this->modTS['checkhidden']
344 );
345 if ($this->pObj->pageinfo['hidden'] == 0 || $this->modTS['checkhidden']) {
346 $pageList .= $this->id;
347 $pageList = $this->addPageTranslationsToPageList($pageList, $permsClause);
348 }
349
350 $this->linkAnalyzer->init($searchFields, $pageList, $this->modTS);
351
352 // Check if button press
353 $update = GeneralUtility::_GP('updateLinkList');
354 if (!empty($update)) {
355 $this->linkAnalyzer->getLinkStatistics($this->checkOpt['check'], $this->modTS['checkhidden']);
356 }
357 }
358 }
359
360 /**
361 * Renders the content of the module
362 */
363 protected function render()
364 {
365 if ($this->isAccessibleForCurrentUser) {
366 $this->content = $this->renderBrokenLinksTable();
367 } else {
368 $languageService = $this->getLanguageService();
369 // If no access or if ID == zero
370 $message = GeneralUtility::makeInstance(
371 FlashMessage::class,
372 $languageService->getLL('no.access'),
373 $languageService->getLL('no.access.title'),
374 FlashMessage::ERROR
375 );
376 /** @var \TYPO3\CMS\Core\Messaging\FlashMessageService $flashMessageService */
377 $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
378 /** @var \TYPO3\CMS\Core\Messaging\FlashMessageQueue $defaultFlashMessageQueue */
379 $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
380 $defaultFlashMessageQueue->enqueue($message);
381 }
382 }
383
384 /**
385 * Flushes the rendered content to the browser
386 *
387 * @param bool $form
388 * @return string $content
389 */
390 protected function flush($form = false)
391 {
392 return $this->doc->moduleBody(
393 $this->pageRecord,
394 $this->getDocHeaderButtons(),
395 $form ? $this->getTemplateMarkers() : $this->getTemplateMarkersCheck()
396 );
397 }
398
399 /**
400 * Builds the selector for the level of pages to search
401 *
402 * @param string $prefix Indicating if the selector is build for the "report" or "check" tab
403 *
404 * @return string Html code of that selector
405 */
406 protected function getLevelSelector($prefix = 'report')
407 {
408 $languageService = $this->getLanguageService();
409 // Build level selector
410 $options = [];
411 $availableOptions = [
412 0 => $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_0'),
413 1 => $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_1'),
414 2 => $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_2'),
415 3 => $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_3'),
416 4 => $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_4'),
417 999 => $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_infi')
418 ];
419 foreach ($availableOptions as $optionValue => $optionLabel) {
420 $options[] = '<option value="' . $optionValue . '"' . ($optionValue === (int)$this->searchLevel[$prefix] ? ' selected="selected"' : '') . '>' . htmlspecialchars($optionLabel) . '</option>';
421 }
422 return '<select name="' . $prefix . '_search_levels" class="form-control">' . implode('', $options) . '</select>';
423 }
424
425 /**
426 * Displays the table of broken links or a note if there were no broken links
427 *
428 * @return string Content of the table or of the note
429 */
430 protected function renderBrokenLinksTable()
431 {
432 $brokenLinkItems = '';
433 $brokenLinksTemplate = $this->templateService->getSubpart(
434 $this->doc->moduleTemplate,
435 '###NOBROKENLINKS_CONTENT###'
436 );
437
438 $linkTypes = [];
439 if (is_array($this->checkOpt['report'])) {
440 $linkTypes = array_keys($this->checkOpt['report'], '1');
441 }
442
443 // Table header
444 $brokenLinksMarker = $this->startTable();
445
446 $rootLineHidden = $this->linkAnalyzer->getRootLineIsHidden($this->pObj->pageinfo);
447 if (!$rootLineHidden || (bool)$this->modTS['checkhidden']) {
448 $pageList = $this->getPageList();
449 $result = false;
450 if (!empty($linkTypes)) {
451 $result = $this->getLinkValidatorBrokenLinks($pageList, $linkTypes);
452 }
453
454 if ($result && $result->rowCount()) {
455 // Display table with broken links
456 $brokenLinksTemplate = $this->templateService->getSubpart(
457 $this->doc->moduleTemplate,
458 '###BROKENLINKS_CONTENT###'
459 );
460 $brokenLinksItemTemplate = $this->templateService->getSubpart(
461 $this->doc->moduleTemplate,
462 '###BROKENLINKS_ITEM###'
463 );
464
465 // Table rows containing the broken links
466 $items = [];
467 while ($row = $result->fetch()) {
468 $items[] = $this->renderTableRow($row['table_name'], $row, $brokenLinksItemTemplate);
469 }
470 $brokenLinkItems = implode(LF, $items);
471 } else {
472 $brokenLinksMarker = $this->getNoBrokenLinkMessage($brokenLinksMarker);
473 }
474 } else {
475 $brokenLinksMarker = $this->getNoBrokenLinkMessage($brokenLinksMarker);
476 }
477
478 $brokenLinksTemplate = $this->templateService->substituteMarkerArray(
479 $brokenLinksTemplate,
480 $brokenLinksMarker,
481 '###|###',
482 true
483 );
484
485 return $this->templateService->substituteSubpart($brokenLinksTemplate, '###BROKENLINKS_ITEM', $brokenLinkItems);
486 }
487
488 /**
489 * Generates an array of page uids from current pageUid.
490 * List does include pageUid itself.
491 *
492 * @return array
493 */
494 protected function getPageList(): array
495 {
496 $permsClause = $this->getBackendUser()->getPagePermsClause(Permission::PAGE_SHOW);
497 $pageList = $this->linkAnalyzer->extGetTreeList(
498 $this->id,
499 $this->searchLevel['report'],
500 0,
501 $permsClause,
502 $this->modTS['checkhidden']
503 );
504 // Always add the current page, because we are just displaying the results
505 $pageList .= $this->id;
506 $pageList = $this->addPageTranslationsToPageList($pageList, $permsClause);
507
508 return GeneralUtility::intExplode(',', $pageList, true);
509 }
510
511 /**
512 * Prepare database query with pageList and keyOpt data.
513 *
514 * @param int[] $pageList Pages to check for broken links
515 * @param string[] $linkTypes Link types to validate
516 * @return Statement
517 */
518 protected function getLinkValidatorBrokenLinks(array $pageList, array $linkTypes): Statement
519 {
520 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
521 ->getQueryBuilderForTable('tx_linkvalidator_link');
522 $queryBuilder
523 ->select('*')
524 ->from('tx_linkvalidator_link')
525 ->where(
526 $queryBuilder->expr()->orX(
527 $queryBuilder->expr()->andX(
528 $queryBuilder->expr()->in(
529 'record_uid',
530 $queryBuilder->createNamedParameter($pageList, Connection::PARAM_INT_ARRAY)
531 ),
532 $queryBuilder->expr()->eq('table_name', $queryBuilder->createNamedParameter('pages'))
533 ),
534 $queryBuilder->expr()->andX(
535 $queryBuilder->expr()->in(
536 'record_pid',
537 $queryBuilder->createNamedParameter($pageList, Connection::PARAM_INT_ARRAY)
538 ),
539 $queryBuilder->expr()->neq('table_name', $queryBuilder->createNamedParameter('pages'))
540 )
541 )
542 )
543 ->orderBy('record_uid')
544 ->addOrderBy('uid');
545
546 if (!empty($linkTypes)) {
547 $queryBuilder->andWhere(
548 $queryBuilder->expr()->in(
549 'link_type',
550 $queryBuilder->createNamedParameter($linkTypes, Connection::PARAM_STR_ARRAY)
551 )
552 );
553 }
554
555 return $queryBuilder->execute();
556 }
557
558 /**
559 * Replace $brokenLinksMarker['NO_BROKEN_LINKS] with localized flashmessage
560 *
561 * @param array $brokenLinksMarker
562 * @return array $brokenLinksMarker['NO_BROKEN_LINKS] replaced with flashmessage
563 */
564 protected function getNoBrokenLinkMessage(array $brokenLinksMarker)
565 {
566 $languageService = $this->getLanguageService();
567 $brokenLinksMarker['LIST_HEADER'] = '<h3>' . htmlspecialchars($languageService->getLL('list.header')) . '</h3>';
568 /** @var FlashMessage $message */
569 $message = GeneralUtility::makeInstance(
570 FlashMessage::class,
571 $languageService->getLL('list.no.broken.links'),
572 $languageService->getLL('list.no.broken.links.title'),
573 FlashMessage::OK
574 );
575 $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
576 /** @var \TYPO3\CMS\Core\Messaging\FlashMessageQueue $defaultFlashMessageQueue */
577 $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
578 $defaultFlashMessageQueue->enqueue($message);
579 $brokenLinksMarker['NO_BROKEN_LINKS'] = $defaultFlashMessageQueue->renderFlashMessages();
580 return $brokenLinksMarker;
581 }
582
583 /**
584 * Displays the table header of the table with the broken links
585 *
586 * @return array Code of content
587 */
588 protected function startTable()
589 {
590 $languageService = $this->getLanguageService();
591 // Listing head
592 $makerTableHead = [
593 'tablehead_path' => $languageService->getLL('list.tableHead.path'),
594 'tablehead_element' => $languageService->getLL('list.tableHead.element'),
595 'tablehead_headlink' => $languageService->getLL('list.tableHead.headlink'),
596 'tablehead_linktarget' => $languageService->getLL('list.tableHead.linktarget'),
597 'tablehead_linkmessage' => $languageService->getLL('list.tableHead.linkmessage'),
598 'tablehead_lastcheck' => $languageService->getLL('list.tableHead.lastCheck'),
599 ];
600
601 // Add CSH to the header of each column
602 foreach ($makerTableHead as $column => $label) {
603 $makerTableHead[$column] = BackendUtility::wrapInHelp('linkvalidator', $column, $label);
604 }
605 // Add section header
606 $makerTableHead['list_header'] = '<h3>' . htmlspecialchars($languageService->getLL('list.header')) . '</h3>';
607 return $makerTableHead;
608 }
609
610 /**
611 * Displays one line of the broken links table
612 *
613 * @param string $table Name of database table
614 * @param array $row Record row to be processed
615 * @param array $brokenLinksItemTemplate Markup of the template to be used
616 * @return string HTML of the rendered row
617 */
618 protected function renderTableRow($table, array $row, $brokenLinksItemTemplate)
619 {
620 $languageService = $this->getLanguageService();
621 $markerArray = [];
622 $fieldName = '';
623 // Restore the linktype object
624 $hookObj = $this->hookObjectsArr[$row['link_type']];
625
626 // Construct link to edit the content element
627 $requestUri = GeneralUtility::getIndpEnv('REQUEST_URI') .
628 '&id=' . $this->id .
629 '&search_levels=' . $this->searchLevel['report'];
630 /** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */
631 $uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
632 $url = (string)$uriBuilder->buildUriFromRoute('record_edit', [
633 'edit' => [
634 $table => [
635 $row['record_uid'] => 'edit'
636 ]
637 ],
638 'columnsOnly' => $row['field'],
639 'returnUrl' => $requestUri
640 ]);
641 $actionLinkOpen = '<a href="' . htmlspecialchars($url);
642 $actionLinkOpen .= '" title="' . htmlspecialchars($languageService->getLL('list.edit')) . '">';
643 $actionLinkClose = '</a>';
644 $elementHeadline = $row['headline'];
645 // Get the language label for the field from TCA
646 if ($GLOBALS['TCA'][$table]['columns'][$row['field']]['label']) {
647 $fieldName = $languageService->sL($GLOBALS['TCA'][$table]['columns'][$row['field']]['label']);
648 // Crop colon from end if present
649 if (substr($fieldName, '-1', '1') === ':') {
650 $fieldName = substr($fieldName, '0', strlen($fieldName) - 1);
651 }
652 }
653 // Fallback, if there is no label
654 $fieldName = !empty($fieldName) ? $fieldName : $row['field'];
655 // column "Element"
656 $element = '<span title="' . htmlspecialchars($table . ':' . $row['record_uid']) . '">' . $this->iconFactory->getIconForRecord($table, $row, Icon::SIZE_SMALL)->render() . '</span>';
657 if (empty($elementHeadline)) {
658 $element .= '<i>' . htmlspecialchars($languageService->getLL('list.no.headline')) . '</i>';
659 } else {
660 $element .= htmlspecialchars($elementHeadline);
661 }
662 $element .= ' ' . htmlspecialchars(sprintf($languageService->getLL('list.field'), $fieldName));
663 $markerArray['actionlinkOpen'] = $actionLinkOpen;
664 $markerArray['actionlinkClose'] = $actionLinkClose;
665 $markerArray['actionlinkIcon'] = $this->iconFactory->getIcon('actions-open', Icon::SIZE_SMALL)->render();
666 $markerArray['path'] = BackendUtility::getRecordPath($row['record_pid'], '', 0, 0);
667 $markerArray['element'] = $element;
668 $markerArray['headlink'] = htmlspecialchars($row['link_title']);
669 $markerArray['linktarget'] = htmlspecialchars($hookObj->getBrokenUrl($row));
670 $response = unserialize($row['url_response']);
671 if ($response['valid']) {
672 $linkMessage = '<span class="valid">' . htmlspecialchars($languageService->getLL('list.msg.ok')) . '</span>';
673 } else {
674 $linkMessage = '<span class="error">'
675 . nl2br(
676 // Encode for output
677 htmlspecialchars(
678 $hookObj->getErrorMessage($response['errorParams']),
679 ENT_QUOTES,
680 'UTF-8',
681 false
682 )
683 )
684 . '</span>';
685 }
686 $markerArray['linkmessage'] = $linkMessage;
687
688 $lastRunDate = date($GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'], $row['last_check']);
689 $lastRunTime = date($GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'], $row['last_check']);
690 $markerArray['lastcheck'] = htmlspecialchars(sprintf($languageService->getLL('list.msg.lastRun'), $lastRunDate, $lastRunTime));
691
692 // Return the table html code as string
693 return $this->templateService->substituteMarkerArray($brokenLinksItemTemplate, $markerArray, '###|###', true, true);
694 }
695
696 /**
697 * Builds the checkboxes out of the hooks array
698 *
699 * @param array $brokenLinkOverView Array of broken links information
700 * @param string $prefix "report" or "check" for "Report" and "Check links" tab
701 * @return string code content
702 */
703 protected function getCheckOptions(array $brokenLinkOverView, $prefix = 'report')
704 {
705 $languageService = $this->getLanguageService();
706 $markerArray = [];
707 if (!empty($prefix)) {
708 $additionalAttr = ' class="' . $prefix . '"';
709 } else {
710 $additionalAttr = ' class="refresh"';
711 }
712 $checkOptionsTemplate = $this->templateService->getSubpart($this->doc->moduleTemplate, '###CHECKOPTIONS_SECTION###');
713 $hookSectionTemplate = $this->templateService->getSubpart($checkOptionsTemplate, '###HOOK_SECTION###');
714 $markerArray['statistics_header'] = '<h3>' . htmlspecialchars($languageService->getLL('report.statistics.header')) . '</h3>';
715 $markerArray['total_count_label'] = BackendUtility::wrapInHelp('linkvalidator', 'checkboxes', $languageService->getLL('overviews.nbtotal'));
716 $markerArray['total_count'] = $brokenLinkOverView['brokenlinkCount'] ?: '0';
717
718 $linktypes = GeneralUtility::trimExplode(',', $this->modTS['linktypes'], true);
719 $hookSectionContent = '';
720 if (is_array($linktypes)) {
721 if (
722 !empty($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['linkvalidator']['checkLinks'])
723 && is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['linkvalidator']['checkLinks'])
724 ) {
725 foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['linkvalidator']['checkLinks'] as $type => $value) {
726 if (in_array($type, $linktypes)) {
727 $hookSectionMarker = [
728 'count' => $brokenLinkOverView[$type] ?: '0',
729 ];
730
731 $translation = $languageService->getLL('hooks.' . $type) ?: $type;
732
733 $checked = $this->checkOpt[$prefix][$type] ? 'checked="checked"' : '';
734
735 $hookSectionMarker['option'] = '<input type="checkbox"' . $additionalAttr
736 . ' id="' . $prefix . '_SET_' . $type
737 . '" name="' . $prefix . '_SET[' . $type . ']" value="1"'
738 . ' ' . $checked . '/><label for="'
739 . $prefix . '_SET_' . $type . '">&nbsp;' . htmlspecialchars($translation) . '</label>';
740
741 $hookSectionContent .= $this->templateService->substituteMarkerArray(
742 $hookSectionTemplate,
743 $hookSectionMarker,
744 '###|###',
745 true,
746 true
747 );
748 }
749 }
750 }
751 }
752 $checkOptionsTemplate = $this->templateService->substituteSubpart(
753 $checkOptionsTemplate,
754 '###HOOK_SECTION###',
755 $hookSectionContent
756 );
757
758 // set this to signal that $prefix_SET variables should be used
759 $checkOptionsTemplate .= '<input type="hidden" name="' . $prefix . '_values" value="1">';
760
761 return $this->templateService->substituteMarkerArray($checkOptionsTemplate, $markerArray, '###|###', true, true);
762 }
763
764 /**
765 * Gets the buttons that shall be rendered in the docHeader
766 *
767 * @return array Available buttons for the docHeader
768 */
769 protected function getDocHeaderButtons()
770 {
771 return [
772 'csh' => BackendUtility::cshItem('_MOD_web_func', ''),
773 'shortcut' => $this->getShortcutButton(),
774 'save' => ''
775 ];
776 }
777
778 /**
779 * Gets the button to set a new shortcut in the backend (if current user is allowed to).
780 *
781 * @return string HTML representation of the shortcut button
782 */
783 protected function getShortcutButton()
784 {
785 $result = '';
786 if ($this->getBackendUser()->mayMakeShortcut()) {
787 $result = $this->doc->makeShortcutIcon('', 'function', 'web_info');
788 }
789 return $result;
790 }
791
792 /**
793 * Gets the filled markers that are used in the HTML template
794 * Reports tab
795 *
796 * @return array The filled marker array
797 */
798 protected function getTemplateMarkers()
799 {
800 $languageService = $this->getLanguageService();
801 return [
802 'FUNC_TITLE' => $languageService->getLL('report.func.title'),
803 'CHECKOPTIONS_TITLE' => $languageService->getLL('report.statistics.header'),
804 'FUNC_MENU' => $this->getLevelSelector('report'),
805 'CONTENT' => $this->content,
806 'CHECKOPTIONS' => $this->checkOptionsHtml['report'],
807 'ID' => '<input type="hidden" name="id" value="' . $this->id . '" />',
808 'REFRESH' => '<input type="submit" class="btn btn-default t3js-update-button" name="refreshLinkList" id="refreshLinkList" value="'
809 . htmlspecialchars($languageService->getLL('label_refresh'))
810 . '" data-notification-message="'
811 . htmlspecialchars($languageService->getLL('label_refresh-link-list')) . '" />',
812 'UPDATE' => '',
813 ];
814 }
815
816 /**
817 * Gets the filled markers that are used in the HTML template
818 * Check Links tab
819 *
820 * @return array The filled marker array
821 */
822 protected function getTemplateMarkersCheck()
823 {
824 $languageService = $this->getLanguageService();
825 return [
826 'FUNC_TITLE' => $languageService->getLL('checklinks.func.title'),
827 'CHECKOPTIONS_TITLE' => $languageService->getLL('checklinks.statistics.header'),
828 'FUNC_MENU' => $this->getLevelSelector('check'),
829 'CONTENT' => '',
830 'CHECKOPTIONS' => $this->checkOptionsHtml['check'],
831 'ID' => '<input type="hidden" name="id" value="' . $this->id . '" />',
832 'REFRESH' => '',
833 'UPDATE' => '<input type="submit" class="btn btn-default t3js-update-button" name="updateLinkList" id="updateLinkList" value="'
834 . htmlspecialchars($languageService->getLL('label_update'))
835 . '" data-notification-message="'
836 . htmlspecialchars($languageService->getLL('label_update-link-list'))
837 . '"/>',
838 ];
839 }
840
841 /**
842 * Determines whether the current user is an admin
843 *
844 * @return bool Whether the current user is admin
845 */
846 protected function isCurrentUserAdmin()
847 {
848 return $this->getBackendUser()->isAdmin();
849 }
850
851 /**
852 * @return LanguageService
853 */
854 protected function getLanguageService(): LanguageService
855 {
856 return $GLOBALS['LANG'];
857 }
858
859 /**
860 * @return BackendUserAuthentication
861 */
862 protected function getBackendUser(): BackendUserAuthentication
863 {
864 return $GLOBALS['BE_USER'];
865 }
866
867 /**
868 * @return PageRenderer
869 */
870 protected function getPageRenderer(): PageRenderer
871 {
872 return GeneralUtility::makeInstance(PageRenderer::class);
873 }
874
875 /**
876 * @param string $theList
877 * @param string $permsClause
878 * @return string
879 */
880 protected function addPageTranslationsToPageList(string $theList, string $permsClause): string
881 {
882 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
883 $queryBuilder->getRestrictions()
884 ->removeAll()
885 ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
886
887 $result = $queryBuilder
888 ->select('uid', 'title', 'hidden')
889 ->from('pages')
890 ->where(
891 $queryBuilder->expr()->eq(
892 'l10n_parent',
893 $queryBuilder->createNamedParameter($this->id, \PDO::PARAM_INT)
894 ),
895 QueryHelper::stripLogicalOperatorPrefix($permsClause)
896 )
897 ->execute();
898
899 while ($row = $result->fetch()) {
900 if ($row['hidden'] === 0 || $this->modTS['checkhidden']) {
901 $theList .= ',' . $row['uid'];
902 }
903 }
904
905 return $theList;
906 }
907 }