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