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