[BUGFIX] Use mod.linkvalidator settings in scheduler
[Packages/TYPO3.CMS.git] / typo3 / sysext / linkvalidator / Classes / Task / ValidatorTask.php
1 <?php
2 namespace TYPO3\CMS\Linkvalidator\Task;
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 TYPO3\CMS\Backend\Utility\BackendUtility;
18 use TYPO3\CMS\Core\Mail\MailMessage;
19 use TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser;
20 use TYPO3\CMS\Core\Utility\ArrayUtility;
21 use TYPO3\CMS\Core\Utility\GeneralUtility;
22 use TYPO3\CMS\Core\Html\HtmlParser;
23 use TYPO3\CMS\Core\Utility\MailUtility;
24 use TYPO3\CMS\Lang\LanguageService;
25 use TYPO3\CMS\Linkvalidator\LinkAnalyzer;
26
27 /**
28 * This class provides Scheduler plugin implementation
29 *
30 * @author Michael Miousse <michael.miousse@infoglobe.ca>
31 */
32 class ValidatorTask extends \TYPO3\CMS\Scheduler\Task\AbstractTask {
33
34 /**
35 * @var int
36 */
37 protected $sleepTime;
38
39 /**
40 * @var int
41 */
42 protected $sleepAfterFinish;
43
44 /**
45 * @var int
46 */
47 protected $countInARun;
48
49 /**
50 * Total number of broken links
51 *
52 * @var int
53 */
54 protected $totalBrokenLink = 0;
55
56 /**
57 * Total number of broken links from the last run
58 *
59 * @var int
60 */
61 protected $oldTotalBrokenLink = 0;
62
63 /**
64 * Mail template fetched from the given template file
65 *
66 * @var string
67 */
68 protected $templateMail;
69
70 /**
71 * specific TSconfig for this task.
72 *
73 * @var array
74 */
75 protected $configuration = array();
76
77 /**
78 * Shows if number of result was different from the result of the last check
79 *
80 * @var bool
81 */
82 protected $isDifferentToLastRun;
83
84 /**
85 * Template to be used for the email
86 *
87 * @var string
88 */
89 protected $emailTemplateFile;
90
91 /**
92 * Level of pages the task should check
93 *
94 * @var int
95 */
96 protected $depth;
97
98 /**
99 * UID of the start page for this task
100 *
101 * @var int
102 */
103 protected $page;
104
105 /**
106 * Email address to which an email report is sent
107 *
108 * @var string
109 */
110 protected $email;
111
112 /**
113 * Only send an email, if new broken links were found
114 *
115 * @var bool
116 */
117 protected $emailOnBrokenLinkOnly;
118
119 /**
120 * Get the value of the protected property email
121 *
122 * @return string Email address to which an email report is sent
123 */
124 public function getEmail() {
125 return $this->email;
126 }
127
128 /**
129 * Set the value of the private property email.
130 *
131 * @param string $email Email address to which an email report is sent
132 * @return void
133 */
134 public function setEmail($email) {
135 $this->email = $email;
136 }
137
138 /**
139 * Get the value of the protected property emailOnBrokenLinkOnly
140 *
141 * @return bool Whether to send an email, if new broken links were found
142 */
143 public function getEmailOnBrokenLinkOnly() {
144 return $this->emailOnBrokenLinkOnly;
145 }
146
147 /**
148 * Set the value of the private property emailOnBrokenLinkOnly
149 *
150 * @param bool $emailOnBrokenLinkOnly Only send an email, if new broken links were found
151 * @return void
152 */
153 public function setEmailOnBrokenLinkOnly($emailOnBrokenLinkOnly) {
154 $this->emailOnBrokenLinkOnly = $emailOnBrokenLinkOnly;
155 }
156
157 /**
158 * Get the value of the protected property page
159 *
160 * @return int UID of the start page for this task
161 */
162 public function getPage() {
163 return $this->page;
164 }
165
166 /**
167 * Set the value of the private property page
168 *
169 * @param int $page UID of the start page for this task.
170 * @return void
171 */
172 public function setPage($page) {
173 $this->page = $page;
174 }
175
176 /**
177 * Get the value of the protected property depth
178 *
179 * @return int Level of pages the task should check
180 */
181 public function getDepth() {
182 return $this->depth;
183 }
184
185 /**
186 * Set the value of the private property depth
187 *
188 * @param int $depth Level of pages the task should check
189 * @return void
190 */
191 public function setDepth($depth) {
192 $this->depth = $depth;
193 }
194
195 /**
196 * Get the value of the protected property emailTemplateFile
197 *
198 * @return string Template to be used for the email
199 */
200 public function getEmailTemplateFile() {
201 return $this->emailTemplateFile;
202 }
203
204 /**
205 * Set the value of the private property emailTemplateFile
206 *
207 * @param string $emailTemplateFile Template to be used for the email
208 * @return void
209 */
210 public function setEmailTemplateFile($emailTemplateFile) {
211 $this->emailTemplateFile = $emailTemplateFile;
212 }
213
214 /**
215 * Get the value of the protected property configuration
216 *
217 * @return array specific TSconfig for this task
218 */
219 public function getConfiguration() {
220 return $this->configuration;
221 }
222
223 /**
224 * Set the value of the private property configuration
225 *
226 * @param array $configuration specific TSconfig for this task
227 * @return void
228 */
229 public function setConfiguration($configuration) {
230 $this->configuration = $configuration;
231 }
232
233 /**
234 * Function execute from the Scheduler
235 *
236 * @return bool TRUE on successful execution, FALSE on error
237 * @throws \InvalidArgumentException if the email template file can not be read
238 */
239 public function execute() {
240 $this->setCliArguments();
241 $successfullyExecuted = TRUE;
242 if (
243 !file_exists(($file = GeneralUtility::getFileAbsFileName($this->emailTemplateFile)))
244 && !empty($this->email)
245 ) {
246 if ($this->emailTemplateFile === 'EXT:linkvalidator/res/mailtemplate.html') {
247 // Update the default email template file path
248 $this->emailTemplateFile = 'EXT:linkvalidator/Resources/Private/Templates/mailtemplate.html';
249 $this->save();
250 } else {
251 throw new \InvalidArgumentException(
252 $this->getLanguageService()->sL('LLL:EXT:linkvalidator/Resources/Private/Language/locallang.xlf:tasks.error.invalidEmailTemplateFile'),
253 '1295476972'
254 );
255 }
256 }
257 $htmlFile = GeneralUtility::getURL($file);
258 $this->templateMail = HtmlParser::getSubpart($htmlFile, '###REPORT_TEMPLATE###');
259 // The array to put the content into
260 $pageSections = '';
261 $this->isDifferentToLastRun = FALSE;
262 $pageList = GeneralUtility::trimExplode(',', $this->page, TRUE);
263 $modTs = $this->loadModTsConfig($this->page);
264 if (is_array($pageList)) {
265 foreach ($pageList as $page) {
266 $pageSections .= $this->checkPageLinks($page);
267 }
268 }
269 if ($this->totalBrokenLink != $this->oldTotalBrokenLink) {
270 $this->isDifferentToLastRun = TRUE;
271 }
272 if ($this->totalBrokenLink > 0 && (!$this->emailOnBrokenLinkOnly || $this->isDifferentToLastRun) && !empty($this->email)) {
273 $successfullyExecuted = $this->reportEmail($pageSections, $modTs);
274 }
275 return $successfullyExecuted;
276 }
277
278 /**
279 * Validate all links for a page based on the task configuration
280 *
281 * @param int $page Uid of the page to parse
282 * @return string $pageSections Content of page section
283 */
284 protected function checkPageLinks($page) {
285 $page = (int)$page;
286 $pageSections = '';
287 $pageIds = '';
288 $oldLinkCounts = array();
289 $modTs = $this->loadModTsConfig($page);
290 $searchFields = $this->getSearchField($modTs);
291 $linkTypes = $this->getLinkTypes($modTs);
292 /** @var $processor LinkAnalyzer */
293 $processor = GeneralUtility::makeInstance(LinkAnalyzer::class);
294 if ($page === 0) {
295 $rootLineHidden = FALSE;
296 } else {
297 $pageRow = $this->getDatabaseConnection()->exec_SELECTgetSingleRow('*', 'pages', 'uid=' . $page);
298 $rootLineHidden = $processor->getRootLineIsHidden($pageRow);
299 }
300 if (!$rootLineHidden || $modTs['checkhidden'] == 1) {
301 $pageIds = $processor->extGetTreeList($page, $this->depth, 0, '1=1', $modTs['checkhidden']);
302 if (isset($pageRow) && $pageRow['hidden'] == 0 || $modTs['checkhidden'] == 1) {
303 // \TYPO3\CMS\Linkvalidator\LinkAnalyzer->extGetTreeList() always adds trailing comma
304 $pageIds .= $page;
305 }
306 }
307 if (!empty($pageIds)) {
308 $processor->init($searchFields, $pageIds, $modTs);
309 if (!empty($this->email)) {
310 $oldLinkCounts = $processor->getLinkCounts($page);
311 $this->oldTotalBrokenLink += $oldLinkCounts['brokenlinkCount'];
312 }
313 $processor->getLinkStatistics($linkTypes, $modTs['checkhidden']);
314 if (!empty($this->email)) {
315 $linkCounts = $processor->getLinkCounts($page);
316 $this->totalBrokenLink += $linkCounts['brokenlinkCount'];
317 $pageSections = $this->buildMail($page, $pageIds, $linkCounts, $oldLinkCounts);
318 }
319 }
320 return $pageSections;
321 }
322
323 /**
324 * Get the linkvalidator modTSconfig for a page
325 *
326 * @param int $page Uid of the page
327 * @return array $modTsConfig mod.linkvalidator TSconfig array
328 * @throws \Exception
329 */
330 protected function loadModTsConfig($page) {
331 $modTs = BackendUtility::getModTSconfig($page, 'mod.linkvalidator');
332 $parseObj = GeneralUtility::makeInstance(TypoScriptParser::class);
333 $parseObj->parse($this->configuration);
334 if (count($parseObj->errors) > 0) {
335 $parseErrorMessage = $this->getLanguageService()->sL('LLL:EXT:linkvalidator/Resources/Private/Language/locallang.xlf:tasks.error.invalidTSconfig') . '<br />';
336 foreach ($parseObj->errors as $errorInfo) {
337 $parseErrorMessage .= $errorInfo[0] . '<br />';
338 }
339 throw new \Exception($parseErrorMessage, '1295476989');
340 }
341 $tsConfig = $parseObj->setup;
342 $modTs = $modTs['properties'];
343 $overrideTs = $tsConfig['mod.']['linkvalidator.'];
344 if (is_array($overrideTs)) {
345 ArrayUtility::mergeRecursiveWithOverrule($modTs, $overrideTs);
346 } else {
347 $deprecatedOverrideTs = $tsConfig['mod.']['tx_linkvalidator.'];
348 if (is_array($deprecatedOverrideTs)) {
349 GeneralUtility::deprecationLog('Using mod.tx_linkvalidator in the scheduler TSConfig setting is deprecated since TYPO3 CMS 7 and will be removed in TYPO3 CMS 8. Please use mod.linkvalidator instead.');
350 ArrayUtility::mergeRecursiveWithOverrule($modTs, $deprecatedOverrideTs);
351 }
352 }
353 return $modTs;
354 }
355
356 /**
357 * Get the list of fields to parse in modTSconfig
358 *
359 * @param array $modTS mod.linkvalidator TSconfig array
360 * @return array $searchFields List of fields
361 */
362 protected function getSearchField(array $modTS) {
363 // Get the searchFields from TypoScript
364 foreach ($modTS['searchFields.'] as $table => $fieldList) {
365 $fields = GeneralUtility::trimExplode(',', $fieldList);
366 foreach ($fields as $field) {
367 $searchFields[$table][] = $field;
368 }
369 }
370 return isset($searchFields) ? $searchFields : array();
371 }
372
373 /**
374 * Get the list of linkTypes to parse in modTSconfig
375 *
376 * @param array $modTS mod.linkvalidator TSconfig array
377 * @return array $linkTypes list of link types
378 */
379 protected function getLinkTypes(array $modTS) {
380 $linkTypes = array();
381 $typesTmp = GeneralUtility::trimExplode(',', $modTS['linktypes'], TRUE);
382 if (is_array($typesTmp)) {
383 if (!empty($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['linkvalidator']['checkLinks']) && is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['linkvalidator']['checkLinks'])) {
384 foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['linkvalidator']['checkLinks'] as $type => $value) {
385 if (in_array($type, $typesTmp)) {
386 $linkTypes[$type] = 1;
387 }
388 }
389 }
390 }
391 return $linkTypes;
392 }
393
394 /**
395 * Build and send warning email when new broken links were found
396 *
397 * @param string $pageSections Content of page section
398 * @param array $modTsConfig TSconfig array
399 * @return bool TRUE if mail was sent, FALSE if or not
400 * @throws \Exception if required modTsConfig settings are missing
401 */
402 protected function reportEmail($pageSections, array $modTsConfig) {
403 $content = HtmlParser::substituteSubpart($this->templateMail, '###PAGE_SECTION###', $pageSections);
404 /** @var array $markerArray */
405 $markerArray = array();
406 /** @var array $validEmailList */
407 $validEmailList = array();
408 /** @var bool $sendEmail */
409 $sendEmail = TRUE;
410 $markerArray['totalBrokenLink'] = $this->totalBrokenLink;
411 $markerArray['totalBrokenLink_old'] = $this->oldTotalBrokenLink;
412 // Hook
413 if (is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['linkvalidator']['reportEmailMarkers'])) {
414 foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['linkvalidator']['reportEmailMarkers'] as $userFunc) {
415 $params = array(
416 'pObj' => &$this,
417 'markerArray' => $markerArray
418 );
419 $newMarkers = GeneralUtility::callUserFunction($userFunc, $params, $this);
420 if (is_array($newMarkers)) {
421 $markerArray = $newMarkers + $markerArray;
422 }
423 unset($params);
424 }
425 }
426 $content = HtmlParser::substituteMarkerArray($content, $markerArray, '###|###', TRUE, TRUE);
427 /** @var $mail MailMessage */
428 $mail = GeneralUtility::makeInstance(MailMessage::class);
429 if (empty($modTsConfig['mail.']['fromemail'])) {
430 $modTsConfig['mail.']['fromemail'] = MailUtility::getSystemFromAddress();
431 }
432 if (empty($modTsConfig['mail.']['fromname'])) {
433 $modTsConfig['mail.']['fromname'] = MailUtility::getSystemFromName();
434 }
435 if (GeneralUtility::validEmail($modTsConfig['mail.']['fromemail'])) {
436 $mail->setFrom(array($modTsConfig['mail.']['fromemail'] => $modTsConfig['mail.']['fromname']));
437 } else {
438 throw new \Exception($this->getLanguageService()->sL('LLL:EXT:linkvalidator/Resources/Private/Language/locallang.xlf:tasks.error.invalidFromEmail'), '1295476760');
439 }
440 if (GeneralUtility::validEmail($modTsConfig['mail.']['replytoemail'])) {
441 $mail->setReplyTo(array($modTsConfig['mail.']['replytoemail'] => $modTsConfig['mail.']['replytoname']));
442 }
443 if (!empty($modTsConfig['mail.']['subject'])) {
444 $mail->setSubject($modTsConfig['mail.']['subject']);
445 } else {
446 throw new \Exception($this->getLanguageService()->sL('LLL:EXT:linkvalidator/Resources/Private/Language/locallang.xlf:tasks.error.noSubject'), '1295476808');
447 }
448 if (!empty($this->email)) {
449 $emailList = GeneralUtility::trimExplode(',', $this->email);
450 foreach ($emailList as $emailAdd) {
451 if (!GeneralUtility::validEmail($emailAdd)) {
452 throw new \Exception($this->getLanguageService()->sL('LLL:EXT:linkvalidator/Resources/Private/Language/locallang.xlf:tasks.error.invalidToEmail'), '1295476821');
453 } else {
454 $validEmailList[] = $emailAdd;
455 }
456 }
457 }
458 if (is_array($validEmailList) && !empty($validEmailList)) {
459 $mail->setTo($validEmailList);
460 } else {
461 $sendEmail = FALSE;
462 }
463 if ($sendEmail) {
464 $mail->setBody($content, 'text/html');
465 $mail->send();
466 }
467 return $sendEmail;
468 }
469
470 /**
471 * Build the mail content
472 *
473 * @param int $curPage Id of the current page
474 * @param string $pageList List of pages id
475 * @param array $markerArray Array of markers
476 * @param array $oldBrokenLink Marker array with the number of link found
477 * @return string Content of the mail
478 */
479 protected function buildMail($curPage, $pageList, array $markerArray, array $oldBrokenLink) {
480 $pageSectionHtml = HtmlParser::getSubpart($this->templateMail, '###PAGE_SECTION###');
481 // Hook
482 if (is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['linkvalidator']['buildMailMarkers'])) {
483 foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['linkvalidator']['buildMailMarkers'] as $userFunc) {
484 $params = array(
485 'curPage' => $curPage,
486 'pageList' => $pageList,
487 'markerArray' => $markerArray,
488 'oldBrokenLink' => $oldBrokenLink,
489 'pObj' => &$this
490 );
491 $newMarkers = GeneralUtility::callUserFunction($userFunc, $params, $this);
492 if (is_array($newMarkers)) {
493 $markerArray = $newMarkers + $markerArray;
494 }
495 unset($params);
496 }
497 }
498 if (is_array($markerArray)) {
499 foreach ($markerArray as $markerKey => $markerValue) {
500 if (empty($oldBrokenLink[$markerKey])) {
501 $oldBrokenLink[$markerKey] = 0;
502 }
503 if ($markerValue != $oldBrokenLink[$markerKey]) {
504 $this->isDifferentToLastRun = TRUE;
505 }
506 $markerArray[$markerKey . '_old'] = $oldBrokenLink[$markerKey];
507 }
508 }
509 $markerArray['title'] = BackendUtility::getRecordTitle(
510 'pages',
511 BackendUtility::getRecord('pages', $curPage)
512 );
513 $content = '';
514 if ($markerArray['brokenlinkCount'] > 0) {
515 $content = HtmlParser::substituteMarkerArray($pageSectionHtml, $markerArray, '###|###', TRUE, TRUE);
516 }
517 return $content;
518 }
519
520 /**
521 * Returns the most important properties of the link validator task as a
522 * comma separated string that will be displayed in the scheduler module.
523 *
524 * @return string
525 */
526 public function getAdditionalInformation() {
527 $additionalInformation = array();
528
529 $page = (int)$this->getPage();
530 $pageLabel = $page;
531 if ($page !== 0) {
532 $pageData = BackendUtility::getRecord('pages', $page);
533 if (!empty($pageData)) {
534 $pageTitle = BackendUtility::getRecordTitle('pages', $pageData);
535 $pageLabel = $pageTitle . ' (' . $page . ')';
536 }
537 }
538 $lang = $this->getLanguageService();
539 $additionalInformation[] = $lang->sL('LLL:EXT:linkvalidator/Resources/Private/Language/locallang.xlf:tasks.validate.page') . ': ' . $pageLabel;
540
541 $depth = (int)$this->getDepth();
542 $additionalInformation[] = $lang->sL('LLL:EXT:linkvalidator/Resources/Private/Language/locallang.xlf:tasks.validate.depth') . ': ' . $lang->sL('LLL:EXT:lang/locallang_core.xlf:labels.depth_' . ($depth === 999 ? 'infi' : $depth));
543
544 $additionalInformation[] = $lang->sL('LLL:EXT:linkvalidator/Resources/Private/Language/locallang.xlf:tasks.validate.email') . ': ' . $this->getEmail();
545
546 return implode(', ', $additionalInformation);
547 }
548
549 /**
550 * Simulate cli call with setting the required options to the $_SERVER['argv']
551 *
552 * @return void
553 */
554 protected function setCliArguments() {
555 $_SERVER['argv'] = array(
556 $_SERVER['argv'][0],
557 'tx_link_scheduler_link',
558 '0',
559 '-ss',
560 '--sleepTime',
561 $this->sleepTime,
562 '--sleepAfterFinish',
563 $this->sleepAfterFinish,
564 '--countInARun',
565 $this->countInARun
566 );
567 }
568
569 /**
570 * @return LanguageService
571 */
572 protected function getLanguageService() {
573 return $GLOBALS['LANG'];
574 }
575
576 /**
577 * @return \TYPO3\CMS\Core\Database\DatabaseConnection
578 */
579 protected function getDatabaseConnection() {
580 return $GLOBALS['TYPO3_DB'];
581 }
582 }