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