3476174820c1e18e10c4b578bf6c439f77cca87d
[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->loadJquery();
364 $pageRenderer->loadRequireJsModule('TYPO3/CMS/Linkvalidator/Linkvalidator');
365
366 $this->templateService = GeneralUtility::makeInstance(MarkerBasedTemplateService::class);
367
368 // Don't access in workspace
369 if ($this->getBackendUser()->workspace !== 0) {
370 $this->isAccessibleForCurrentUser = false;
371 }
372 }
373
374 /**
375 * Updates the table of stored broken links
376 */
377 protected function updateBrokenLinks()
378 {
379 $searchFields = [];
380 // Get the searchFields from TypoScript
381 foreach ($this->modTS['searchFields.'] as $table => $fieldList) {
382 $fields = GeneralUtility::trimExplode(',', $fieldList, true);
383 foreach ($fields as $field) {
384 if (!$searchFields || !is_array($searchFields[$table]) || !in_array($field, $searchFields[$table], true)) {
385 $searchFields[$table][] = $field;
386 }
387 }
388 }
389 $rootLineHidden = $this->linkAnalyzer->getRootLineIsHidden($this->pObj->pageinfo);
390 if (!$rootLineHidden || $this->modTS['checkhidden'] == 1) {
391 // Get children pages
392 $pageList = $this->linkAnalyzer->extGetTreeList(
393 $this->id,
394 $this->searchLevel['check'],
395 0,
396 $this->getBackendUser()->getPagePermsClause(Permission::PAGE_SHOW),
397 $this->modTS['checkhidden']
398 );
399 if ($this->pObj->pageinfo['hidden'] == 0 || $this->modTS['checkhidden']) {
400 $pageList .= $this->id;
401 }
402
403 $this->linkAnalyzer->init($searchFields, $pageList, $this->modTS);
404
405 // Check if button press
406 $update = GeneralUtility::_GP('updateLinkList');
407 if (!empty($update)) {
408 $this->linkAnalyzer->getLinkStatistics($this->checkOpt['check'], $this->modTS['checkhidden']);
409 }
410 }
411 }
412
413 /**
414 * Renders the content of the module
415 */
416 protected function render()
417 {
418 if ($this->isAccessibleForCurrentUser) {
419 $this->content = $this->renderBrokenLinksTable();
420 } else {
421 $languageService = $this->getLanguageService();
422 // If no access or if ID == zero
423 $message = GeneralUtility::makeInstance(
424 FlashMessage::class,
425 $languageService->getLL('no.access'),
426 $languageService->getLL('no.access.title'),
427 FlashMessage::ERROR
428 );
429 /** @var \TYPO3\CMS\Core\Messaging\FlashMessageService $flashMessageService */
430 $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
431 /** @var \TYPO3\CMS\Core\Messaging\FlashMessageQueue $defaultFlashMessageQueue */
432 $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
433 $defaultFlashMessageQueue->enqueue($message);
434 }
435 }
436
437 /**
438 * Flushes the rendered content to the browser
439 *
440 * @param bool $form
441 * @return string $content
442 */
443 protected function flush($form = false)
444 {
445 return $this->doc->moduleBody(
446 $this->pageRecord,
447 $this->getDocHeaderButtons(),
448 $form ? $this->getTemplateMarkers() : $this->getTemplateMarkersCheck()
449 );
450 }
451
452 /**
453 * Builds the selector for the level of pages to search
454 *
455 * @param string $prefix Indicating if the selector is build for the "report" or "check" tab
456 *
457 * @return string Html code of that selector
458 */
459 protected function getLevelSelector($prefix = 'report')
460 {
461 $languageService = $this->getLanguageService();
462 // Build level selector
463 $options = [];
464 $availableOptions = [
465 0 => $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_0'),
466 1 => $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_1'),
467 2 => $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_2'),
468 3 => $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_3'),
469 4 => $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_4'),
470 999 => $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_infi')
471 ];
472 foreach ($availableOptions as $optionValue => $optionLabel) {
473 $options[] = '<option value="' . $optionValue . '"' . ($optionValue === (int)$this->searchLevel[$prefix] ? ' selected="selected"' : '') . '>' . htmlspecialchars($optionLabel) . '</option>';
474 }
475 return '<select name="' . $prefix . '_search_levels" class="form-control">' . implode('', $options) . '</select>';
476 }
477
478 /**
479 * Displays the table of broken links or a note if there were no broken links
480 *
481 * @return string Content of the table or of the note
482 */
483 protected function renderBrokenLinksTable()
484 {
485 $brokenLinkItems = '';
486 $brokenLinksTemplate = $this->templateService->getSubpart(
487 $this->doc->moduleTemplate,
488 '###NOBROKENLINKS_CONTENT###'
489 );
490
491 $linkTypes = [];
492 if (is_array($this->checkOpt['report'])) {
493 $linkTypes = array_keys($this->checkOpt['report'], '1');
494 }
495
496 // Table header
497 $brokenLinksMarker = $this->startTable();
498
499 $rootLineHidden = $this->linkAnalyzer->getRootLineIsHidden($this->pObj->pageinfo);
500 if (!$rootLineHidden || (bool)$this->modTS['checkhidden']) {
501 $pageList = $this->getPageList($this->id);
502 $result = false;
503 if (!empty($linkTypes)) {
504 $result = $this->getLinkValidatorBrokenLinks($pageList, $linkTypes);
505 }
506
507 if ($result && $result->rowCount()) {
508 // Display table with broken links
509 $brokenLinksTemplate = $this->templateService->getSubpart(
510 $this->doc->moduleTemplate,
511 '###BROKENLINKS_CONTENT###'
512 );
513 $brokenLinksItemTemplate = $this->templateService->getSubpart(
514 $this->doc->moduleTemplate,
515 '###BROKENLINKS_ITEM###'
516 );
517
518 // Table rows containing the broken links
519 $items = [];
520 while ($row = $result->fetch()) {
521 $items[] = $this->renderTableRow($row['table_name'], $row, $brokenLinksItemTemplate);
522 }
523 $brokenLinkItems = implode(LF, $items);
524 } else {
525 $brokenLinksMarker = $this->getNoBrokenLinkMessage($brokenLinksMarker);
526 }
527 } else {
528 $brokenLinksMarker = $this->getNoBrokenLinkMessage($brokenLinksMarker);
529 }
530
531 $brokenLinksTemplate = $this->templateService->substituteMarkerArray(
532 $brokenLinksTemplate,
533 $brokenLinksMarker,
534 '###|###',
535 true
536 );
537
538 return $this->templateService->substituteSubpart($brokenLinksTemplate, '###BROKENLINKS_ITEM', $brokenLinkItems);
539 }
540
541 /**
542 * Generates an array of page uids from current pageUid.
543 * List does include pageUid itself.
544 *
545 * @param int $currentPageUid
546 * @return array
547 */
548 protected function getPageList(int $currentPageUid): array
549 {
550 $pageList = $this->linkAnalyzer->extGetTreeList(
551 $currentPageUid,
552 $this->searchLevel['report'],
553 0,
554 $this->getBackendUser()->getPagePermsClause(Permission::PAGE_SHOW),
555 $this->modTS['checkhidden']
556 );
557 // Always add the current page, because we are just displaying the results
558 $pageList .= $currentPageUid;
559
560 return GeneralUtility::intExplode(',', $pageList, true);
561 }
562
563 /**
564 * Prepare database query with pageList and keyOpt data.
565 *
566 * @param int[] $pageList Pages to check for broken links
567 * @param string[] $linkTypes Link types to validate
568 * @return Statement
569 */
570 protected function getLinkValidatorBrokenLinks(array $pageList, array $linkTypes): Statement
571 {
572 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
573 ->getQueryBuilderForTable('tx_linkvalidator_link');
574 $queryBuilder
575 ->select('*')
576 ->from('tx_linkvalidator_link')
577 ->where(
578 $queryBuilder->expr()->in(
579 'record_pid',
580 $queryBuilder->createNamedParameter($pageList, Connection::PARAM_INT_ARRAY)
581 )
582 )
583 ->orderBy('record_uid')
584 ->addOrderBy('uid');
585
586 if (!empty($linkTypes)) {
587 $queryBuilder->andWhere(
588 $queryBuilder->expr()->in(
589 'link_type',
590 $queryBuilder->createNamedParameter($linkTypes, Connection::PARAM_STR_ARRAY)
591 )
592 );
593 }
594
595 return $queryBuilder->execute();
596 }
597
598 /**
599 * Replace $brokenLinksMarker['NO_BROKEN_LINKS] with localized flashmessage
600 *
601 * @param array $brokenLinksMarker
602 * @return array $brokenLinksMarker['NO_BROKEN_LINKS] replaced with flashmessage
603 */
604 protected function getNoBrokenLinkMessage(array $brokenLinksMarker)
605 {
606 $languageService = $this->getLanguageService();
607 $brokenLinksMarker['LIST_HEADER'] = '<h3>' . htmlspecialchars($languageService->getLL('list.header')) . '</h3>';
608 /** @var FlashMessage $message */
609 $message = GeneralUtility::makeInstance(
610 FlashMessage::class,
611 $languageService->getLL('list.no.broken.links'),
612 $languageService->getLL('list.no.broken.links.title'),
613 FlashMessage::OK
614 );
615 $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
616 /** @var \TYPO3\CMS\Core\Messaging\FlashMessageQueue $defaultFlashMessageQueue */
617 $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
618 $defaultFlashMessageQueue->enqueue($message);
619 $brokenLinksMarker['NO_BROKEN_LINKS'] = $defaultFlashMessageQueue->renderFlashMessages();
620 return $brokenLinksMarker;
621 }
622
623 /**
624 * Displays the table header of the table with the broken links
625 *
626 * @return array Code of content
627 */
628 protected function startTable()
629 {
630 $languageService = $this->getLanguageService();
631 // Listing head
632 $makerTableHead = [
633 'tablehead_path' => $languageService->getLL('list.tableHead.path'),
634 'tablehead_element' => $languageService->getLL('list.tableHead.element'),
635 'tablehead_headlink' => $languageService->getLL('list.tableHead.headlink'),
636 'tablehead_linktarget' => $languageService->getLL('list.tableHead.linktarget'),
637 'tablehead_linkmessage' => $languageService->getLL('list.tableHead.linkmessage'),
638 'tablehead_lastcheck' => $languageService->getLL('list.tableHead.lastCheck'),
639 ];
640
641 // Add CSH to the header of each column
642 foreach ($makerTableHead as $column => $label) {
643 $makerTableHead[$column] = BackendUtility::wrapInHelp('linkvalidator', $column, $label);
644 }
645 // Add section header
646 $makerTableHead['list_header'] = '<h3>' . htmlspecialchars($languageService->getLL('list.header')) . '</h3>';
647 return $makerTableHead;
648 }
649
650 /**
651 * Displays one line of the broken links table
652 *
653 * @param string $table Name of database table
654 * @param array $row Record row to be processed
655 * @param array $brokenLinksItemTemplate Markup of the template to be used
656 * @return string HTML of the rendered row
657 */
658 protected function renderTableRow($table, array $row, $brokenLinksItemTemplate)
659 {
660 $languageService = $this->getLanguageService();
661 $markerArray = [];
662 $fieldName = '';
663 // Restore the linktype object
664 $hookObj = $this->hookObjectsArr[$row['link_type']];
665
666 // Construct link to edit the content element
667 $requestUri = GeneralUtility::getIndpEnv('REQUEST_URI') .
668 '&id=' . $this->id .
669 '&search_levels=' . $this->searchLevel['report'];
670 /** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */
671 $uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
672 $url = (string)$uriBuilder->buildUriFromRoute('record_edit', [
673 'edit' => [
674 $table => [
675 $row['record_uid'] => 'edit'
676 ]
677 ],
678 'columnsOnly' => $row['field'],
679 'returnUrl' => $requestUri
680 ]);
681 $actionLinkOpen = '<a href="' . htmlspecialchars($url);
682 $actionLinkOpen .= '" title="' . htmlspecialchars($languageService->getLL('list.edit')) . '">';
683 $actionLinkClose = '</a>';
684 $elementHeadline = $row['headline'];
685 // Get the language label for the field from TCA
686 if ($GLOBALS['TCA'][$table]['columns'][$row['field']]['label']) {
687 $fieldName = $languageService->sL($GLOBALS['TCA'][$table]['columns'][$row['field']]['label']);
688 // Crop colon from end if present
689 if (substr($fieldName, '-1', '1') === ':') {
690 $fieldName = substr($fieldName, '0', strlen($fieldName) - 1);
691 }
692 }
693 // Fallback, if there is no label
694 $fieldName = !empty($fieldName) ? $fieldName : $row['field'];
695 // column "Element"
696 $element = '<span title="' . htmlspecialchars($table . ':' . $row['record_uid']) . '">' . $this->iconFactory->getIconForRecord($table, $row, Icon::SIZE_SMALL)->render() . '</span>';
697 if (empty($elementHeadline)) {
698 $element .= '<i>' . htmlspecialchars($languageService->getLL('list.no.headline')) . '</i>';
699 } else {
700 $element .= htmlspecialchars($elementHeadline);
701 }
702 $element .= ' ' . htmlspecialchars(sprintf($languageService->getLL('list.field'), $fieldName));
703 $markerArray['actionlinkOpen'] = $actionLinkOpen;
704 $markerArray['actionlinkClose'] = $actionLinkClose;
705 $markerArray['actionlinkIcon'] = $this->iconFactory->getIcon('actions-open', Icon::SIZE_SMALL)->render();
706 $markerArray['path'] = BackendUtility::getRecordPath($row['record_pid'], '', 0, 0);
707 $markerArray['element'] = $element;
708 $markerArray['headlink'] = htmlspecialchars($row['link_title']);
709 $markerArray['linktarget'] = htmlspecialchars($hookObj->getBrokenUrl($row));
710 $response = unserialize($row['url_response']);
711 if ($response['valid']) {
712 $linkMessage = '<span class="valid">' . htmlspecialchars($languageService->getLL('list.msg.ok')) . '</span>';
713 } else {
714 $linkMessage = '<span class="error">'
715 . nl2br(
716 // Encode for output
717 htmlspecialchars(
718 $hookObj->getErrorMessage($response['errorParams']),
719 ENT_QUOTES,
720 'UTF-8',
721 false
722 )
723 )
724 . '</span>';
725 }
726 $markerArray['linkmessage'] = $linkMessage;
727
728 $lastRunDate = date($GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'], $row['last_check']);
729 $lastRunTime = date($GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'], $row['last_check']);
730 $markerArray['lastcheck'] = htmlspecialchars(sprintf($languageService->getLL('list.msg.lastRun'), $lastRunDate, $lastRunTime));
731
732 // Return the table html code as string
733 return $this->templateService->substituteMarkerArray($brokenLinksItemTemplate, $markerArray, '###|###', true, true);
734 }
735
736 /**
737 * Builds the checkboxes out of the hooks array
738 *
739 * @param array $brokenLinkOverView Array of broken links information
740 * @param string $prefix "report" or "check" for "Report" and "Check links" tab
741 * @return string code content
742 */
743 protected function getCheckOptions(array $brokenLinkOverView, $prefix = 'report')
744 {
745 $languageService = $this->getLanguageService();
746 $markerArray = [];
747 if (!empty($prefix)) {
748 $additionalAttr = ' class="' . $prefix . '"';
749 } else {
750 $additionalAttr = ' class="refresh"';
751 }
752 $checkOptionsTemplate = $this->templateService->getSubpart($this->doc->moduleTemplate, '###CHECKOPTIONS_SECTION###');
753 $hookSectionTemplate = $this->templateService->getSubpart($checkOptionsTemplate, '###HOOK_SECTION###');
754 $markerArray['statistics_header'] = '<h3>' . htmlspecialchars($languageService->getLL('report.statistics.header')) . '</h3>';
755 $markerArray['total_count_label'] = BackendUtility::wrapInHelp('linkvalidator', 'checkboxes', $languageService->getLL('overviews.nbtotal'));
756 $markerArray['total_count'] = $brokenLinkOverView['brokenlinkCount'] ?: '0';
757
758 $linktypes = GeneralUtility::trimExplode(',', $this->modTS['linktypes'], true);
759 $hookSectionContent = '';
760 if (is_array($linktypes)) {
761 if (
762 !empty($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['linkvalidator']['checkLinks'])
763 && is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['linkvalidator']['checkLinks'])
764 ) {
765 foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['linkvalidator']['checkLinks'] as $type => $value) {
766 if (in_array($type, $linktypes)) {
767 $hookSectionMarker = [
768 'count' => $brokenLinkOverView[$type] ?: '0',
769 ];
770
771 $translation = $languageService->getLL('hooks.' . $type) ?: $type;
772
773 $checked = $this->checkOpt[$prefix][$type] ? 'checked="checked"' : '';
774
775 $hookSectionMarker['option'] = '<input type="checkbox"' . $additionalAttr
776 . ' id="' . $prefix . '_SET_' . $type
777 . '" name="' . $prefix . '_SET[' . $type . ']" value="1"'
778 . ' ' . $checked . '/>' . '<label for="'
779 . $prefix . '_SET_' . $type . '">&nbsp;' . htmlspecialchars($translation) . '</label>';
780
781 $hookSectionContent .= $this->templateService->substituteMarkerArray(
782 $hookSectionTemplate,
783 $hookSectionMarker,
784 '###|###',
785 true,
786 true
787 );
788 }
789 }
790 }
791 }
792 $checkOptionsTemplate = $this->templateService->substituteSubpart(
793 $checkOptionsTemplate,
794 '###HOOK_SECTION###',
795 $hookSectionContent
796 );
797
798 // set this to signal that $prefix_SET variables should be used
799 $checkOptionsTemplate .= '<input type="hidden" name="' . $prefix . '_values" value="1">';
800
801 return $this->templateService->substituteMarkerArray($checkOptionsTemplate, $markerArray, '###|###', true, true);
802 }
803
804 /**
805 * Gets the buttons that shall be rendered in the docHeader
806 *
807 * @return array Available buttons for the docHeader
808 */
809 protected function getDocHeaderButtons()
810 {
811 return [
812 'csh' => BackendUtility::cshItem('_MOD_web_func', ''),
813 'shortcut' => $this->getShortcutButton(),
814 'save' => ''
815 ];
816 }
817
818 /**
819 * Gets the button to set a new shortcut in the backend (if current user is allowed to).
820 *
821 * @return string HTML representation of the shortcut button
822 */
823 protected function getShortcutButton()
824 {
825 $result = '';
826 if ($this->getBackendUser()->mayMakeShortcut()) {
827 $result = $this->doc->makeShortcutIcon('', 'function', 'web_info');
828 }
829 return $result;
830 }
831
832 /**
833 * Gets the filled markers that are used in the HTML template
834 * Reports tab
835 *
836 * @return array The filled marker array
837 */
838 protected function getTemplateMarkers()
839 {
840 $languageService = $this->getLanguageService();
841 return [
842 'FUNC_TITLE' => $languageService->getLL('report.func.title'),
843 'CHECKOPTIONS_TITLE' => $languageService->getLL('report.statistics.header'),
844 'FUNC_MENU' => $this->getLevelSelector('report'),
845 'CONTENT' => $this->content,
846 'CHECKOPTIONS' => $this->checkOptionsHtml['report'],
847 'ID' => '<input type="hidden" name="id" value="' . $this->id . '" />',
848 'REFRESH' => '<input type="submit" class="btn btn-default t3js-update-button" name="refreshLinkList" id="refreshLinkList" value="'
849 . htmlspecialchars($languageService->getLL('label_refresh'))
850 . '" data-notification-message="'
851 . htmlspecialchars($languageService->getLL('label_refresh-link-list')) . '" />',
852 'UPDATE' => '',
853 ];
854 }
855
856 /**
857 * Gets the filled markers that are used in the HTML template
858 * Check Links tab
859 *
860 * @return array The filled marker array
861 */
862 protected function getTemplateMarkersCheck()
863 {
864 $languageService = $this->getLanguageService();
865 return [
866 'FUNC_TITLE' => $languageService->getLL('checklinks.func.title'),
867 'CHECKOPTIONS_TITLE' => $languageService->getLL('checklinks.statistics.header'),
868 'FUNC_MENU' => $this->getLevelSelector('check'),
869 'CONTENT' => '',
870 'CHECKOPTIONS' => $this->checkOptionsHtml['check'],
871 'ID' => '<input type="hidden" name="id" value="' . $this->id . '" />',
872 'REFRESH' => '',
873 'UPDATE' => '<input type="submit" class="btn btn-default t3js-update-button" name="updateLinkList" id="updateLinkList" value="'
874 . htmlspecialchars($languageService->getLL('label_update'))
875 . '" data-notification-message="'
876 . htmlspecialchars($languageService->getLL('label_update-link-list'))
877 . '"/>',
878 ];
879 }
880
881 /**
882 * Determines whether the current user is an admin
883 *
884 * @return bool Whether the current user is admin
885 */
886 protected function isCurrentUserAdmin()
887 {
888 return $this->getBackendUser()->isAdmin();
889 }
890
891 /**
892 * Called from InfoModuleController until deprecation removal in v10
893 *
894 * @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0.
895 */
896 public function checkExtObj()
897 {
898 if (is_array($this->extClassConf) && $this->extClassConf['name']) {
899 $this->extObj = GeneralUtility::makeInstance($this->extClassConf['name']);
900 $this->extObj->init($this->pObj, $this->extClassConf);
901 // Re-write:
902 $this->pObj->MOD_SETTINGS = BackendUtility::getModuleData($this->pObj->MOD_MENU, GeneralUtility::_GP('SET'), 'web_info');
903 }
904 }
905
906 /**
907 * Calls the main function inside ANOTHER sub-submodule which might exist.
908 *
909 * @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0.
910 */
911 protected function extObjContent()
912 {
913 if (is_object($this->extObj)) {
914 return $this->extObj->main();
915 }
916 }
917
918 /**
919 * @return LanguageService
920 */
921 protected function getLanguageService(): LanguageService
922 {
923 return $GLOBALS['LANG'];
924 }
925
926 /**
927 * @return BackendUserAuthentication
928 */
929 protected function getBackendUser(): BackendUserAuthentication
930 {
931 return $GLOBALS['BE_USER'];
932 }
933
934 /**
935 * @return PageRenderer
936 */
937 protected function getPageRenderer(): PageRenderer
938 {
939 return GeneralUtility::makeInstance(PageRenderer::class);
940 }
941 }