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