[BUGFIX] Fix linktype selection in Linkvalidator report
[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::getModTSconfig($this->pObj->id, 'mod.linkvalidator');
157 $this->modTS = $this->modTS['properties'];
158 }
159 if (isset($this->searchLevel[$prefix])) {
160 $this->pObj->MOD_SETTINGS[$prefix . '_searchlevel'] = $this->searchLevel[$prefix];
161 } else {
162 $this->searchLevel[$prefix] = $this->pObj->MOD_SETTINGS[$prefix . '_searchlevel'];
163 }
164 if (isset($this->pObj->MOD_SETTINGS[$other . '_searchlevel'])) {
165 $this->searchLevel[$other] = $this->pObj->MOD_SETTINGS[$other . '_searchlevel'];
166 }
167
168 // which linkTypes to check (internal, file, external, ...)
169 $set = GeneralUtility::_GP($prefix . '_SET');
170 $this->pObj->handleExternalFunctionValue();
171
172 foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['linkvalidator']['checkLinks'] ?? [] as $linkType => $value) {
173 // Compile list of all available types. Used for checking with button "Check Links".
174 if (strpos($this->modTS['linktypes'], $linkType) !== false) {
175 $this->availableOptions[$linkType] = 1;
176 }
177
178 // 1) if "$prefix_values" = "1" : use POST variables
179 // 2) if not set, use stored configuration in $this->>pObj->MOD_SETTINGS
180 // 3) if not set, use default
181 unset($this->checkOpt[$prefix][$linkType]);
182 if (!empty(GeneralUtility::_GP($prefix . '_values'))) {
183 if (isset($set[$linkType])) {
184 $this->checkOpt[$prefix][$linkType] = $set[$linkType];
185 } else {
186 $this->checkOpt[$prefix][$linkType] = '0';
187 }
188 $this->pObj->MOD_SETTINGS[$prefix . '_' . $linkType] = $this->checkOpt[$prefix][$linkType];
189 } elseif (isset($this->pObj->MOD_SETTINGS[$prefix . '_' . $linkType])) {
190 $this->checkOpt[$prefix][$linkType] = $this->pObj->MOD_SETTINGS[$prefix . '_' . $linkType];
191 } else {
192 // use default
193 $this->checkOpt[$prefix][$linkType] = '0';
194 $this->pObj->MOD_SETTINGS[$prefix . '_' . $linkType] = $this->checkOpt[$prefix][$linkType];
195 }
196 if (isset($this->pObj->MOD_SETTINGS[$other . '_' . $linkType])) {
197 $this->checkOpt[$other][$linkType] = $this->pObj->MOD_SETTINGS[$other . '_' . $linkType];
198 }
199 }
200
201 // save settings
202 $this->getBackendUser()->pushModuleData('web_info', $this->pObj->MOD_SETTINGS);
203 $this->initialize();
204
205 // Localization
206 $this->getPageRenderer()->addInlineLanguageLabelFile('EXT:linkvalidator/Resources/Private/Language/Module/locallang.xlf');
207
208 if ($this->modTS['showCheckLinkTab'] == 1) {
209 $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')) . '"/>';
210 }
211 $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')) . '"/>';
212 $this->linkAnalyzer = GeneralUtility::makeInstance(LinkAnalyzer::class);
213 $this->updateBrokenLinks();
214
215 $brokenLinkOverView = $this->linkAnalyzer->getLinkCounts($this->pObj->id);
216 $this->checkOptionsHtml['report'] = $this->getCheckOptions($brokenLinkOverView, 'report');
217 $this->checkOptionsHtml['check'] = $this->getCheckOptions($brokenLinkOverView, 'check');
218 $this->render();
219
220 $pageTile = '';
221 if ($this->pObj->id) {
222 $pageRecord = BackendUtility::getRecord('pages', $this->pObj->id);
223 $pageTile = '<h1>' . htmlspecialchars(BackendUtility::getRecordTitle('pages', $pageRecord)) . '</h1>';
224 }
225
226 return '<div id="linkvalidator-modfuncreport">' . $pageTile . $this->createTabs() . '</div>';
227 }
228
229 /**
230 * Create tabs to split the report and the checkLink functions
231 *
232 * @return string
233 */
234 protected function createTabs()
235 {
236 $menuItems = [
237 0 => [
238 'label' => $this->getLanguageService()->getLL('Report'),
239 'content' => $this->flush(true)
240 ],
241 ];
242
243 if ((bool)$this->modTS['showCheckLinkTab']) {
244 $menuItems[1] = [
245 'label' => $this->getLanguageService()->getLL('CheckLink'),
246 'content' => $this->flush()
247 ];
248 }
249
250 $moduleTemplate = GeneralUtility::makeInstance(ModuleTemplate::class);
251 return $moduleTemplate->getDynamicTabMenu($menuItems, 'report-linkvalidator');
252 }
253
254 /**
255 * Initializes the Module
256 */
257 protected function initialize()
258 {
259 foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['linkvalidator']['checkLinks'] ?? [] as $linkType => $className) {
260 $this->hookObjectsArr[$linkType] = GeneralUtility::makeInstance($className);
261 }
262
263 $this->doc = GeneralUtility::makeInstance(DocumentTemplate::class);
264 $this->doc->setModuleTemplate('EXT:linkvalidator/Resources/Private/Templates/mod_template.html');
265
266 $this->pageRecord = BackendUtility::readPageAccess($this->pObj->id, $this->getBackendUser()->getPagePermsClause(Permission::PAGE_SHOW));
267 if ($this->pObj->id && is_array($this->pageRecord) || !$this->pObj->id && $this->isCurrentUserAdmin()) {
268 $this->isAccessibleForCurrentUser = true;
269 }
270
271 $this->getPageRenderer()->addCssFile('EXT:linkvalidator/Resources/Public/Css/linkvalidator.css', 'stylesheet', 'screen');
272 $this->getPageRenderer()->loadJquery();
273 $this->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Linkvalidator/Linkvalidator');
274
275 $this->templateService = GeneralUtility::makeInstance(MarkerBasedTemplateService::class);
276
277 // Don't access in workspace
278 if ($this->getBackendUser()->workspace !== 0) {
279 $this->isAccessibleForCurrentUser = false;
280 }
281 }
282
283 /**
284 * Updates the table of stored broken links
285 */
286 protected function updateBrokenLinks()
287 {
288 $searchFields = [];
289 // Get the searchFields from TypoScript
290 foreach ($this->modTS['searchFields.'] as $table => $fieldList) {
291 $fields = GeneralUtility::trimExplode(',', $fieldList, true);
292 foreach ($fields as $field) {
293 if (!$searchFields || !is_array($searchFields[$table]) || array_search($field, $searchFields[$table]) === false) {
294 $searchFields[$table][] = $field;
295 }
296 }
297 }
298 $rootLineHidden = $this->linkAnalyzer->getRootLineIsHidden($this->pObj->pageinfo);
299 if (!$rootLineHidden || $this->modTS['checkhidden'] == 1) {
300 // Get children pages
301 $pageList = $this->linkAnalyzer->extGetTreeList(
302 $this->pObj->id,
303 $this->searchLevel['check'],
304 0,
305 $this->getBackendUser()->getPagePermsClause(Permission::PAGE_SHOW),
306 $this->modTS['checkhidden']
307 );
308 if ($this->pObj->pageinfo['hidden'] == 0 || $this->modTS['checkhidden']) {
309 $pageList .= $this->pObj->id;
310 }
311
312 $this->linkAnalyzer->init($searchFields, $pageList, $this->modTS);
313
314 // Check if button press
315 $update = GeneralUtility::_GP('updateLinkList');
316 if (!empty($update)) {
317 $this->linkAnalyzer->getLinkStatistics($this->checkOpt['check'], $this->modTS['checkhidden']);
318 }
319 }
320 }
321
322 /**
323 * Renders the content of the module
324 */
325 protected function render()
326 {
327 if ($this->isAccessibleForCurrentUser) {
328 $this->content = $this->renderBrokenLinksTable();
329 } else {
330 // If no access or if ID == zero
331 /** @var FlashMessage $message */
332 $message = GeneralUtility::makeInstance(
333 FlashMessage::class,
334 $this->getLanguageService()->getLL('no.access'),
335 $this->getLanguageService()->getLL('no.access.title'),
336 FlashMessage::ERROR
337 );
338 /** @var $flashMessageService \TYPO3\CMS\Core\Messaging\FlashMessageService */
339 $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
340 /** @var $defaultFlashMessageQueue \TYPO3\CMS\Core\Messaging\FlashMessageQueue */
341 $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
342 $defaultFlashMessageQueue->enqueue($message);
343 }
344 }
345
346 /**
347 * Flushes the rendered content to the browser
348 *
349 * @param bool $form
350 * @return string $content
351 */
352 protected function flush($form = false)
353 {
354 return $this->doc->moduleBody(
355 $this->pageRecord,
356 $this->getDocHeaderButtons(),
357 $form ? $this->getTemplateMarkers() : $this->getTemplateMarkersCheck()
358 );
359 }
360
361 /**
362 * Builds the selector for the level of pages to search
363 *
364 * @param string $prefix Indicating if the selector is build for the "report" or "check" tab
365 *
366 * @return string Html code of that selector
367 */
368 protected function getLevelSelector($prefix = 'report')
369 {
370 // Build level selector
371 $options = [];
372 $availableOptions = [
373 0 => $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.depth_0'),
374 1 => $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.depth_1'),
375 2 => $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.depth_2'),
376 3 => $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.depth_3'),
377 4 => $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.depth_4'),
378 999 => $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.depth_infi')
379 ];
380 foreach ($availableOptions as $optionValue => $optionLabel) {
381 $options[] = '<option value="' . $optionValue . '"' . ($optionValue === (int)$this->searchLevel[$prefix] ? ' selected="selected"' : '') . '>' . htmlspecialchars($optionLabel) . '</option>';
382 }
383 return '<select name="' . $prefix . '_search_levels" class="form-control">' . implode('', $options) . '</select>';
384 }
385
386 /**
387 * Displays the table of broken links or a note if there were no broken links
388 *
389 * @return string Content of the table or of the note
390 */
391 protected function renderBrokenLinksTable()
392 {
393 $brokenLinkItems = '';
394 $brokenLinksTemplate = $this->templateService->getSubpart(
395 $this->doc->moduleTemplate,
396 '###NOBROKENLINKS_CONTENT###'
397 );
398
399 $linkTypes = [];
400 if (is_array($this->checkOpt['report'])) {
401 $linkTypes = array_keys($this->checkOpt['report'], '1');
402 }
403
404 // Table header
405 $brokenLinksMarker = $this->startTable();
406
407 $rootLineHidden = $this->linkAnalyzer->getRootLineIsHidden($this->pObj->pageinfo);
408 if (!$rootLineHidden || (bool)$this->modTS['checkhidden']) {
409 $pageList = $this->getPageList($this->pObj->id);
410 $result = false;
411 if (!empty($linkTypes)) {
412 $result = $this->getLinkValidatorBrokenLinks($pageList, $linkTypes);
413 }
414
415 if ($result && $result->rowCount()) {
416 // Display table with broken links
417 $brokenLinksTemplate = $this->templateService->getSubpart(
418 $this->doc->moduleTemplate,
419 '###BROKENLINKS_CONTENT###'
420 );
421 $brokenLinksItemTemplate = $this->templateService->getSubpart(
422 $this->doc->moduleTemplate,
423 '###BROKENLINKS_ITEM###'
424 );
425
426 // Table rows containing the broken links
427 $items = [];
428 while ($row = $result->fetch()) {
429 $items[] = $this->renderTableRow($row['table_name'], $row, $brokenLinksItemTemplate);
430 }
431 $brokenLinkItems = implode(LF, $items);
432 } else {
433 $brokenLinksMarker = $this->getNoBrokenLinkMessage($brokenLinksMarker);
434 }
435 } else {
436 $brokenLinksMarker = $this->getNoBrokenLinkMessage($brokenLinksMarker);
437 }
438
439 $brokenLinksTemplate = $this->templateService->substituteMarkerArray(
440 $brokenLinksTemplate,
441 $brokenLinksMarker,
442 '###|###',
443 true
444 );
445
446 return $this->templateService->substituteSubpart($brokenLinksTemplate, '###BROKENLINKS_ITEM', $brokenLinkItems);
447 }
448
449 /**
450 * Generates an array of page uids from current pageUid.
451 * List does include pageUid itself.
452 *
453 * @param int $currentPageUid
454 * @return array
455 */
456 protected function getPageList(int $currentPageUid): array
457 {
458 $pageList = $this->linkAnalyzer->extGetTreeList(
459 $currentPageUid,
460 $this->searchLevel['report'],
461 0,
462 $this->getBackendUser()->getPagePermsClause(Permission::PAGE_SHOW),
463 $this->modTS['checkhidden']
464 );
465 // Always add the current page, because we are just displaying the results
466 $pageList .= $currentPageUid;
467
468 return GeneralUtility::intExplode(',', $pageList, true);
469 }
470
471 /**
472 * Prepare database query with pageList and keyOpt data.
473 *
474 * @param int[] $pageList Pages to check for broken links
475 * @param string[] $linkTypes Link types to validate
476 * @return Statement
477 */
478 protected function getLinkValidatorBrokenLinks(array $pageList, array $linkTypes): Statement
479 {
480 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
481 ->getQueryBuilderForTable('tx_linkvalidator_link');
482 $queryBuilder
483 ->select('*')
484 ->from('tx_linkvalidator_link')
485 ->where(
486 $queryBuilder->expr()->in(
487 'record_pid',
488 $queryBuilder->createNamedParameter($pageList, Connection::PARAM_INT_ARRAY)
489 )
490 )
491 ->orderBy('record_uid')
492 ->addOrderBy('uid');
493
494 if (!empty($linkTypes)) {
495 $queryBuilder->andWhere(
496 $queryBuilder->expr()->in(
497 'link_type',
498 $queryBuilder->createNamedParameter($linkTypes, Connection::PARAM_STR_ARRAY)
499 )
500 );
501 }
502
503 return $queryBuilder->execute();
504 }
505
506 /**
507 * Replace $brokenLinksMarker['NO_BROKEN_LINKS] with localized flashmessage
508 *
509 * @param array $brokenLinksMarker
510 * @return array $brokenLinksMarker['NO_BROKEN_LINKS] replaced with flashmessage
511 */
512 protected function getNoBrokenLinkMessage(array $brokenLinksMarker)
513 {
514 $brokenLinksMarker['LIST_HEADER'] = '<h3>' . htmlspecialchars($this->getLanguageService()->getLL('list.header')) . '</h3>';
515 /** @var $message FlashMessage */
516 $message = GeneralUtility::makeInstance(
517 FlashMessage::class,
518 $this->getLanguageService()->getLL('list.no.broken.links'),
519 $this->getLanguageService()->getLL('list.no.broken.links.title'),
520 FlashMessage::OK
521 );
522 /** @var $flashMessageService \TYPO3\CMS\Core\Messaging\FlashMessageService */
523 $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
524 /** @var $defaultFlashMessageQueue \TYPO3\CMS\Core\Messaging\FlashMessageQueue */
525 $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
526 $defaultFlashMessageQueue->enqueue($message);
527 $brokenLinksMarker['NO_BROKEN_LINKS'] = $defaultFlashMessageQueue->renderFlashMessages();
528 return $brokenLinksMarker;
529 }
530
531 /**
532 * Displays the table header of the table with the broken links
533 *
534 * @return string Code of content
535 */
536 protected function startTable()
537 {
538 // Listing head
539 $makerTableHead = [
540 'tablehead_path' => $this->getLanguageService()->getLL('list.tableHead.path'),
541 'tablehead_element' => $this->getLanguageService()->getLL('list.tableHead.element'),
542 'tablehead_headlink' => $this->getLanguageService()->getLL('list.tableHead.headlink'),
543 'tablehead_linktarget' => $this->getLanguageService()->getLL('list.tableHead.linktarget'),
544 'tablehead_linkmessage' => $this->getLanguageService()->getLL('list.tableHead.linkmessage'),
545 'tablehead_lastcheck' => $this->getLanguageService()->getLL('list.tableHead.lastCheck'),
546 ];
547
548 // Add CSH to the header of each column
549 foreach ($makerTableHead as $column => $label) {
550 $makerTableHead[$column] = BackendUtility::wrapInHelp('linkvalidator', $column, $label);
551 }
552 // Add section header
553 $makerTableHead['list_header'] = '<h3>' . htmlspecialchars($this->getLanguageService()->getLL('list.header')) . '</h3>';
554 return $makerTableHead;
555 }
556
557 /**
558 * Displays one line of the broken links table
559 *
560 * @param string $table Name of database table
561 * @param array $row Record row to be processed
562 * @param array $brokenLinksItemTemplate Markup of the template to be used
563 * @return string HTML of the rendered row
564 */
565 protected function renderTableRow($table, array $row, $brokenLinksItemTemplate)
566 {
567 $markerArray = [];
568 $fieldName = '';
569 // Restore the linktype object
570 $hookObj = $this->hookObjectsArr[$row['link_type']];
571
572 // Construct link to edit the content element
573 $requestUri = GeneralUtility::getIndpEnv('REQUEST_URI') .
574 '&id=' . $this->pObj->id .
575 '&search_levels=' . $this->searchLevel['report'];
576 /** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */
577 $uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
578 $url = (string)$uriBuilder->buildUriFromRoute('record_edit', [
579 'edit' => [
580 $table => [
581 $row['record_uid'] => 'edit'
582 ]
583 ],
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 }