MailTemplateService.php 34.8 KB
Newer Older
1
<?php
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/***************************************************************
 *  Copyright notice
 *
 *  (c) sgalinski Internet Services (https://www.sgalinski.de)
 *
 *  All rights reserved
 *
 *  This script is part of the TYPO3 project. The TYPO3 project is
 *  free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  The GNU General Public License can be found at
 *  http://www.gnu.org/copyleft/gpl.html.
 *
 *  This script is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  This copyright notice MUST APPEAR in all copies of the script!
 ***************************************************************/
25
namespace SGalinski\SgMail\Service;
26

27
use DateTime;
28
29
use SGalinski\SgMail\Domain\Model\Mail;
use SGalinski\SgMail\Domain\Model\Template;
Paul Ilea's avatar
Paul Ilea committed
30
use SGalinski\SgMail\Domain\Repository\LayoutRepository;
31
use SGalinski\SgMail\Domain\Repository\MailRepository;
32
use SGalinski\SgMail\Domain\Repository\TemplateRepository;
33
use Swift_Attachment;
34
use Symfony\Component\Mailer\Exception\TransportException;
35
use TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException;
36
use TYPO3\CMS\Core\Mail\MailMessage;
37
use TYPO3\CMS\Core\Resource\Exception\ResourceDoesNotExistException;
38
use TYPO3\CMS\Core\Resource\File;
39
40
use TYPO3\CMS\Core\Resource\FileInterface;
use TYPO3\CMS\Core\Resource\FileReference as CoreFileReference;
41
use TYPO3\CMS\Core\Resource\ResourceFactory;
42
use TYPO3\CMS\Core\SingletonInterface;
43
44
use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
use TYPO3\CMS\Core\Site\SiteFinder;
45
use TYPO3\CMS\Core\Utility\GeneralUtility;
46
use TYPO3\CMS\Core\Utility\VersionNumberUtility;
47
use TYPO3\CMS\Extbase\Domain\Model\FileReference;
48
use TYPO3\CMS\Extbase\Object\Exception;
49
use TYPO3\CMS\Extbase\Object\ObjectManager;
50
51
use TYPO3\CMS\Extbase\Persistence\Exception\IllegalObjectTypeException;
use TYPO3\CMS\Extbase\Persistence\Exception\UnknownObjectException;
52
use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
53
use TYPO3\CMS\Fluid\View\StandaloneView;
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
use function array_key_exists;
use function array_merge;
use function array_merge_recursive;
use function array_reverse;
use function count;
use function file_exists;
use function file_get_contents;
use function filter_var;
use function is_array;
use function is_bool;
use function is_object;
use function is_string;
use function method_exists;
use function nl2br;
use function preg_replace;
use function str_replace;
use function trim;
71

72
73
74
/**
 * MailTemplateService
 */
75
class MailTemplateService implements SingletonInterface {
76
77
78
79
80
81
82
83
84
85
	public const MARKER_TYPE_STRING = 'String';
	public const MARKER_TYPE_ARRAY = 'Array';
	public const MARKER_TYPE_OBJECT = 'Object';
	public const MARKER_TYPE_FILE = 'File';
	public const DEFAULT_LANGUAGE = 'default';
	public const DEFAULT_TEMPLATE_PATH = 'Resources/Private/Templates/SgMail/';
	public const CACHE_NAME = 'sg_mail_registerArrayCache';
	public const CACHE_LIFETIME_IN_SECONDS = 86400;
	public const REGISTER_FILE = 'Register.php';
	public const CONFIG_PATH = 'Configuration/MailTemplates';
86

87
	/**
88
	 * @var array
89
	 */
90
	private static $templateObjectCache = [];
91
92
93
94
95

	/**
	 * @var array
	 */
	private static $mailObjectCache = [];
96

97
	/**
98
	 * @var TemplateRepository
99
100
	 */
	protected $templateRepository;
101

102
	/**
103
	 * @var LayoutRepository
104
105
	 */
	protected $layoutRepository;
106
107
108
109
110
111

	/**
	 * @var MailRepository
	 */
	protected $mailRepository;

112
	/**
113
	 * @var ObjectManager
114
	 * @deprecated Not used since TYPO3 10
115
116
	 */
	protected $objectManager;
117
118
119
120
121
122

	/**
	 * @var RegisterService
	 */
	protected $registerService;

123
	/**
124
	 * @var string $toAddresses
125
	 */
126
	private $toAddresses = '';
127

128
	/**
129
	 * @var string $fromAddress
130
	 */
131
	private $fromAddress = '';
132

133
	/**
134
	 * @var string $ccAddresses
135
	 */
136
	private $ccAddresses = '';
137

138
	/**
139
	 * @var string $replyToAddress
140
	 */
141
	private $replyToAddress = '';
142

143
	/**
144
	 * @var SiteLanguage
145
	 */
146
147
	private $siteLanguage;

148
	/**
149
	 * @var boolean $ignoreMailQueue
150
	 */
151
	private $ignoreMailQueue = TRUE;
152

153
	/**
154
	 * @var string $templateName
155
156
	 */
	private $templateName;
157

158
159
160
161
	/**
	 * @var string $subject
	 */
	private $subject;
162

163
164
165
166
	/**
	 * @var string $overwrittenEmailBody
	 */
	private $overwrittenEmailBody = '';
167

168
	/**
169
	 * @var string $extensionKey
170
171
	 */
	private $extensionKey;
172

173
	/**
174
	 * @var array $markers
175
	 */
Torsten Oppermann's avatar
Torsten Oppermann committed
176
	private $markers;
177

178
179
180
181
	/**
	 * @var array $markerLabels
	 */
	private $markerLabels;
182

183
	/**
184
	 * @var string $bccAddresses
185
	 */
186
	private $bccAddresses = '';
187

188
189
190
191
	/**
	 * @var int
	 */
	private $priority = Mail::PRIORITY_LOWEST;
192

193
194
195
196
	/**
	 * @var int
	 */
	private $pid;
197

198
199
200
	/**
	 * @var string
	 */
201
	private $fromName = '';
202

203
	/**
204
205
	 * @var string
	 */
206
	private $mailBodyToSend = '';
207
208
209
210

	/**
	 * @var string
	 */
211
	private $subjectToSend;
212

213
214
	/**
	 * MailTemplateService constructor.
Paul Ilea's avatar
Paul Ilea committed
215
	 *
216
217
	 * @param string $templateName
	 * @param string $extensionKey
218
	 * @param array $markers
219
	 * @param array $markerLabels
220
	 * @throws Exception
221
	 */
222
	public function __construct(string $templateName = '', string $extensionKey = '', array $markers = [], array $markerLabels = []) {
223
224
225
		$this->templateName = $templateName;
		$this->extensionKey = $extensionKey;
		$this->markers = $markers;
226
		$this->markerLabels = $markerLabels;
227

228
		$this->registerService = GeneralUtility::makeInstance(RegisterService::class);
229
230
231
232
233
234
235
		if (version_compare(VersionNumberUtility::getCurrentTypo3Version(), '10.4.0')) {
			$this->objectManager = GeneralUtility::makeInstance(ObjectManager::class);
			/** @var TypoScriptSettingsService $typoScriptSettingsService */
			$typoScriptSettingsService = $this->objectManager->get(TypoScriptSettingsService::class);
		} else {
			$typoScriptSettingsService = GeneralUtility::makeInstance(TypoScriptSettingsService::class);
		}
236

237
		$tsSettings = $typoScriptSettingsService->getSettings(0, 'tx_sgmail');
238
		// use defaultMailFromAddress if it is provided in LocalConfiguration.php; use the sg_mail TS setting as fallback
239
		if (filter_var($GLOBALS['TYPO3_CONF_VARS']['MAIL']['defaultMailFromAddress'], FILTER_VALIDATE_EMAIL)) {
240
			$this->fromAddress = $GLOBALS['TYPO3_CONF_VARS']['MAIL']['defaultMailFromAddress'];
241
242
		} else if (!filter_var($tsSettings['mail']['default']['from'], FILTER_VALIDATE_EMAIL)) {
			$this->fromAddress = 'noreply@example.org';
243
		} else {
244
			$this->fromAddress = $tsSettings['mail']['default']['from'];
245
246
		}

247
248
249
250
		if ($GLOBALS['TYPO3_CONF_VARS']['MAIL']['defaultMailFromName']) {
			$this->fromName = $GLOBALS['TYPO3_CONF_VARS']['MAIL']['defaultMailFromName'];
		}

251
252
253
		$bccAddresses = GeneralUtility::trimExplode(',', $tsSettings['mail']['default']['bcc']);
		$ccAddresses = GeneralUtility::trimExplode(',', $tsSettings['mail']['default']['cc']);
		foreach ($bccAddresses as $index => $email) {
254
			if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
255
				unset($bccAddresses[$index]);
256
257
258
			}
		}

259
		foreach ($ccAddresses as $index => $email) {
260
			if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
261
				unset($ccAddresses[$index]);
262
263
			}
		}
264
265
266

		$this->bccAddresses = implode(',', $bccAddresses);
		$this->ccAddresses = implode(',', $ccAddresses);
267
268
269
	}

	/**
270
271
272
273
274
275
	 * Return default markers for sg_mail
	 *
	 * @param string $translationKey
	 * @param array $marker
	 * @param string $extensionKey
	 * @return array
276
	 */
277
	public static function getDefaultTemplateMarker(string $translationKey, array $marker, $extensionKey = 'sg_mail'): array {
278
279
		$languagePath = 'LLL:EXT:' . $extensionKey . '/Resources/Private/Language/locallang.xlf:' . $translationKey;
		// Need the key for translations
280
		if (trim($extensionKey) === '') {
281
			return [];
282
283
		}

284
285
286
287
288
289
290
291
292
		$generatedMarker = [];
		foreach ($marker as $markerName) {
			$generatedMarker[] = [
				'marker' => $markerName,
				'value' => $languagePath . '.example.' . $markerName,
				'description' => $languagePath . '.description.' . $markerName,
				'backend_translation_key' => $translationKey . '.example.' . $markerName,
				'extension_key' => $extensionKey
			];
293
294
		}

295
		return $generatedMarker;
296
297
298
	}

	/**
299
300
	 * Adds a file resource as attachment
	 *
301
302
	 * @param FileReference|null $fileReference
	 * @param File|null $file
303
304
305
	 *
	 * @return MailTemplateService
	 */
306
	public function addFileResourceAttachment(
307
		FileReference $fileReference = NULL, FileInterface $file = NULL
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
	): MailTemplateService {
		if (!$file) {
			if (!$fileReference) {
				return $this;
			}

			$originalResource = $fileReference->getOriginalResource();
			if (!$originalResource) {
				return $this;
			}

			$file = $originalResource->getOriginalFile();
			if (!$file) {
				return $this;
			}
323
324
		}

325
326
		$coreFileReferenceMailFile = GeneralUtility::makeInstance(ResourceFactory::class)
			->createFileReferenceObject(
327
			[
328
				'uid_local' => $file->getUid(),
329
330
331
332
333
334
335
336
337
				'table_local' => 'sys_file',
				'uid' => uniqid('NEW_MAIL', TRUE)
			]
		);

		$newFileReference = GeneralUtility::makeInstance(FileReference::class);
		$newFileReference->setOriginalResource($coreFileReferenceMailFile);

		$this->markers[] = $newFileReference;
338
339
340
		return $this;
	}

341
342
343
	/**
	 * Set preview markers for the template editor
	 *
344
	 * @throws NoSuchCacheException
345
	 */
346
347
	public function setPreviewMarkers(): void {
		$previewMarkers = [];
348
349

		/** @var array $markerArray */
350
		$markerArray = $this->registerService->getRegisterArray()[$this->extensionKey][$this->templateName]['marker'];
351
352
353
		foreach ($markerArray as $marker) {
			$markerPath = GeneralUtility::trimExplode('.', $marker['marker']);
			$temporaryMarkerArray = [];
354
			foreach (array_reverse($markerPath) as $index => $markerPathSegment) {
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
				if ($index === 0) {
					if ($marker['markerLabel']) {
						$markerPathSegment = $marker['markerLabel'];
					}

					if ($marker['backend_translation_key']) {
						$temporaryMarkerArray[$markerPathSegment] = LocalizationUtility::translate(
							$marker['backend_translation_key'], $marker['extension_key']
						);
					} else {
						$temporaryMarkerArray[$markerPathSegment] = $marker['value'];
					}
				} else {
					$temporaryMarkerArray = [$markerPathSegment => $temporaryMarkerArray];
				}
			}
371
372

			$previewMarkers[] = $temporaryMarkerArray;
373
		}
374
375

		$this->setMarkers(array_merge_recursive(...$previewMarkers));
376
377
	}

378
	/**
379
	 * Send the Email
380
	 *
381
	 * @param bool $isPreview
382
	 * @param bool $isNewsletter
383
	 * @return bool email was sent or added to mail queue successfully?
384
385
386
387
388
389
	 * @throws NoSuchCacheException
	 * @throws ResourceDoesNotExistException
	 * @throws IllegalObjectTypeException
	 * @throws UnknownObjectException
	 */
	public function sendEmail(bool $isPreview = FALSE, bool $isNewsletter = FALSE): bool {
390
		try {
391
			$template = $this->getTemplate();
392
393
394
395
396
397
398
399
400
401
		} catch (\Exception $e) {
			return FALSE;
		}

		// Load the values from the template if they are set
		if (!$isNewsletter && $template !== NULL) {
			$this->loadTemplateValues($template);
		}

		// get default template content from register array
402
		$defaultTemplateContent = $this->getDefaultTemplateContent($template);
403

404
		$this->parseValuesForMail($template, $defaultTemplateContent);
405

406
		$mail = $this->addMailToMailQueue();
407
408
		self::$mailObjectCache[$mail->getUid()] = $mail; // add it to cache to avoid extra DB queries
		if ($this->ignoreMailQueue) {
409
			return $this->sendMailFromQueue($mail->getUid());
410
411
		}

412
		return TRUE;
413
414
	}

415
416
417
	/**
	 * Get the template object
	 *
418
	 * @return null|Template
419
420
	 * @throws \Exception
	 */
421
422
423
424
425
426
	private function getTemplate(): ?Template {
		$isTemplateBlacklisted = $this->registerService->isTemplateBlacklisted(
			$this->extensionKey,
			$this->templateName,
			$this->pid
		);
427
		if ($isTemplateBlacklisted) {
428
			throw new \Exception('The template is blacklisted');
429
430
		}

431
432
433
434
435
		$templateHash = $this->getTemplateHash();
		if (
			isset(self::$templateObjectCache[$templateHash])
			&& self::$templateObjectCache[$templateHash] instanceof Template
		) {
436
437
			return self::$templateObjectCache[$templateHash];
		}
438

439
		/** @var Template $template */
440
441
442
		$template = $this->getTemplateRepository()->findByTemplateProperties(
			$this->extensionKey, $this->templateName, [$this->siteLanguage], $this->pid
		)->getFirst();
443

444
		if ($template === NULL) {
445
446
447
448
449
450
451
452
453
			$site = GeneralUtility::makeInstance(SiteFinder::class)->getSiteByPageId($this->pid);
			$fallbackLanguages = [];
			foreach ($this->siteLanguage->getFallbackLanguageIds() as $fallbackLanguageId) {
				$fallbackLanguages[] = $site->getLanguageById($fallbackLanguageId);
			}

			$template = $this->getTemplateRepository()->findByTemplateProperties(
				$this->extensionKey, $this->templateName, $fallbackLanguages, $this->pid
			)->getFirst();
454
		}
455

456
		self::$templateObjectCache[$templateHash] = $template;
457
458
		return $template;
	}
459

460
461
462
463
464
	/**
	 * Get the hash for the object cache
	 *
	 * @return string
	 */
465
466
467
468
469
470
471
	private function getTemplateHash(): string {
		return md5(
			$this->extensionKey . '_' .
			$this->templateName . '_' .
			$this->pid . '_' .
			$this->siteLanguage->getLanguageId()
		);
472
473
474
475
476
477
478
	}

	/**
	 * use all values from the given template
	 *
	 * @param Template $template
	 */
479
480
	public function loadTemplateValues(Template $template): void {
		$this->pid = $template->getPid();
481
482
		$this->extensionKey = $template->getExtensionKey();
		$this->templateName = $template->getTemplateName();
483
		$this->siteLanguage = $template->getSiteLanguage();
484
485

		$fromName = trim($template->getFromName());
486
487
488
		if ($fromName === '') {
			$fromName = $this->fromName;
		}
489

490
491
492
493
		if ($fromName === '' && $GLOBALS['TYPO3_CONF_VARS']['MAIL']['defaultMailFromName']) {
			$fromName = $GLOBALS['TYPO3_CONF_VARS']['MAIL']['defaultMailFromName'];
		}

494
		$fromMail = $this->getValidFromMail(trim($template->getFromMail()));
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
		// The setters check if the value is empty or not
		$this->setFromAddress($fromMail, $fromName);
		$this->setCcAddresses($template->getCc());
		$this->setBccAddresses($template->getBcc());
		$this->setReplyToAddress($template->getReplyTo());
		$this->setFromName($fromName);
	}

	/**
	 * Sets the fromMail property of the mailTemplateService.
	 * Checks validity and uses all available fallbacks
	 *
	 * @param string $fromMail
	 * @return string
	 */
510
511
512
	private function getValidFromMail(string $fromMail): string {
		$fromMail = trim($fromMail);
		if (!filter_var($fromMail, FILTER_VALIDATE_EMAIL)) {
513
514
			$fromMail = $this->fromAddress;
		}
515
516

		if (!filter_var($fromMail, FILTER_VALIDATE_EMAIL)) {
517
			$fromMail = $GLOBALS['TYPO3_CONF_VARS']['MAIL']['defaultMailFromAddress'];
518
			if (!filter_var($GLOBALS['TYPO3_CONF_VARS']['MAIL']['defaultMailFromAddress'], FILTER_VALIDATE_EMAIL)) {
519
520
521
522
523
524
525
526
527
				$fromMail = 'noreply@example.com';
			}
		}

		return $fromMail;
	}



528
529
530
	/**
	 * Get the default content for this template
	 *
531
	 * @param Template|null $template
532
533
534
	 * @return bool|string
	 * @throws Exception
	 * @throws NoSuchCacheException
535
	 */
536
	protected function getDefaultTemplateContent(Template $template = NULL) {
537
		$defaultTemplateContent =
538
			$this->registerService->getRegisterArray()[$this->extensionKey][$this->templateName]['templateContent'];
539

540
		// If there is no template for this language, use the default template
541
		if ($template === NULL && $defaultTemplateContent === NULL) {
542
			$templatePath =
543
				$this->registerService->getRegisterArray()[$this->extensionKey][$this->templateName]['templatePath'];
544
545
546

			// only standard template file is considered since version 4.1
			$defaultTemplateFile = $templatePath . 'template.html';
547
548
			if (file_exists($defaultTemplateFile)) {
				$defaultTemplateContent = file_get_contents($defaultTemplateFile);
549
550
			} else {
				// use configured default html template
551
552
553
554
555
556
557
				if (version_compare(VersionNumberUtility::getCurrentTypo3Version(), '10.4.0', '<')) {
					/** @var TypoScriptSettingsService $typoScriptSettingsService */
					$typoScriptSettingsService = $this->objectManager->get(TypoScriptSettingsService::class);
				} else {
					$typoScriptSettingsService = GeneralUtility::makeInstance(TypoScriptSettingsService::class);
				}

558
559
560
561
				$tsSettings = $typoScriptSettingsService->getSettings(0, 'tx_sgmail');
				$defaultTemplateFile = GeneralUtility::getFileAbsFileName(
					$tsSettings['mail']['defaultHtmlTemplate']
				);
562

563
564
				if (file_exists($defaultTemplateFile)) {
					$defaultTemplateContent = file_get_contents($defaultTemplateFile);
565
				} else {
566
					return FALSE;
567
				}
568
			}
Torsten Oppermann's avatar
Torsten Oppermann committed
569
		}
570

571
572
		return $defaultTemplateContent;
	}
Torsten Oppermann's avatar
Torsten Oppermann committed
573

574
575
576
577
	/**
	 * @param string $toAddresses
	 * @return MailTemplateService
	 */
578
	public function setToAddresses(string $toAddresses): MailTemplateService {
579
580
581
582
583
		$normalizedToAddresses = trim(preg_replace('~\x{00a0}~iu', ' ', $toAddresses));
		$this->toAddresses = $normalizedToAddresses;
		return $this;
	}

584
	/**
585
586
587
	 * Sets the values to send the mail with from the template or register service
	 *
	 * @param Template|null $template
588
	 * @param string $defaultTemplateContent
589
590
	 * @throws Exception
	 * @throws NoSuchCacheException
591
	 */
592
	protected function parseValuesForMail(
593
594
		Template $template = NULL, string $defaultTemplateContent = ''
	): void {
595
596
597
598
599
600
601
		if (version_compare(VersionNumberUtility::getCurrentTypo3Version(), '10.4.0', '<')) {
			/** @var StandaloneView $emailView */
			$emailView = $this->objectManager->get(StandaloneView::class);
		} else {
			$emailView = GeneralUtility::makeInstance(StandaloneView::class);
		}

602
		$emailView->assignMultiple($this->markers);
Torsten Oppermann's avatar
Torsten Oppermann committed
603
		$emailView->assign('all_fields', $this->getAllMarker($this->markers));
604
		$emailView->assign('all_fields_html', $this->getAllMarkerHTML($this->markers));
605

606
		//TODO: make this as the lines below the next block
607
608
609
610
611
612
		$overwrittenEmailBody = $this->getOverwrittenEmailBody();
		$overwrittenSubject = '';
		if ($this->subject !== '' && $this->subject !== NULL) {
			$overwrittenSubject = $this->subject;
		}

613
		// parse markers
Paul Ilea's avatar
Paul Ilea committed
614
		if ($template !== NULL) {
615
616
617
			$subject = $this->parseMarkers(
				trim(empty($overwrittenSubject) ? $template->getSubject() : $overwrittenSubject),
				$emailView
618
			);
619
620
			$layoutId = $template->getLayout();
			$templateContent = $template->getContent();
621
			$this->setSubjectToSend($subject);
622
		} else {
623
			$subject = $this->registerService->getRegisterArray()[$this->extensionKey][$this->templateName]['subject'];
624
625
			if (is_array($subject)) {
				$subject = trim(
626
					$this->registerService->getRegisterArray()
627
					[$this->extensionKey][$this->templateName]['subject'][$this->siteLanguage->getTypo3Language()]
628
				);
629
			}
630

631
632
633
634
			$subject = $this->parseMarkers(
				(empty($overwrittenSubject) ? $subject : $overwrittenSubject),
				$emailView
			);
635

636
637
638
			$layoutId = 0;
			$templateContent = $defaultTemplateContent;
		}
639

640
		$this->setSubjectToSend($subject);
641

642
643
		// Parse the markers
		if ($this->fromName) {
644
			$this->setFromName($this->parseMarkers($this->fromName, $emailView));
645
646
		}

647
		if ($this->fromAddress) {
648
			$this->setFromAddress($this->parseMarkers($this->fromAddress, $emailView));
649
650
		}

651
		if ($this->replyToAddress) {
652
			$this->setReplyToAddress($this->parseMarkers($this->replyToAddress, $emailView));
653
654
		}

655
656
		if ($this->ccAddresses) {
			$this->setCcAddresses($this->parseMarkers($this->ccAddresses, $emailView));
657
658
		}

659
		if ($this->bccAddresses) {
660
			$this->setBccAddresses($this->parseMarkers($this->bccAddresses, $emailView));
661
662
		}

663
		if ($this->toAddresses) {
664
			$this->setToAddresses($this->parseMarkers($this->toAddresses, $emailView));
665
666
667
668
		}

		// reset template source back to default
		$emailView->setTemplateSource(
669
			empty($overwrittenEmailBody) ? $templateContent : $overwrittenEmailBody
670
		);
671

672
		// insert <br> tags, but replace every instance of three or more successive breaks with just two.
673
		$emailBody = $emailView->render();
674
675
		$emailBody = nl2br($emailBody);
		$emailBody = preg_replace('/(<br[\s]?[\/]?>[\s]*){3,}/', '<br><br>', $emailBody);
676

677
678
679
680
681
		$layout = $this->getLayoutRepository()->findByUidOrDefault(
			$layoutId,
			$this->pid,
			$this->siteLanguage->getLanguageId()
		);
682
683
684
		$emailHTMLHead = '';
		if ($layout) {
			$emailHTMLHead = $layout->getHeadContent();
685
			$emailBody = str_replace('###CONTENT###', $emailBody, $layout->getContent());
686
		}
687

688
		$this->mailBodyToSend = '<html><head>' . $emailHTMLHead . '</head><body>' . $emailBody . '</body></html>';
689
690
691
	}

	/**
692
	 * Get a single variable containing a list of all markers
693
	 *
694
695
	 * @param array $markers
	 * @return string
696
	 */
697
698
699
	private function getAllMarker(array $markers): string {
		$allMarker = '';
		foreach ($markers as $key => $value) {
700
			if (array_key_exists($key, $this->markerLabels) && $this->markerLabels[$key] !== NULL) {
701
702
				$key = $this->markerLabels[$key];
			}
703

704
			if (is_string($value)) {
705
				$allMarker .= $key . ': ' . $value . PHP_EOL;
706
			} elseif (is_array($value)) {
707
708
709
				foreach ($value as $innerKey => $innerValue) {
					$allMarker .= $key . '.' . $innerKey . ': ' . $innerValue . PHP_EOL;
				}
710
			} elseif (is_bool($value)) {
711
712
				$valueAsString = $value ? 'true' : 'false';
				$allMarker .= $key . ': ' . $valueAsString . PHP_EOL;
713
714
			} elseif (is_object($value)) {
				if (method_exists($value, '__toString')) {
715
716
717
					$allMarker .= $key . ': ' . $value->__toString() . PHP_EOL;
				}
			}
718
719
		}

720
721
		return $allMarker;
	}
722

723
724
725
726
727
728
729
730
731
732
	/**
	 * Get a single variable containing a list of all markers in table markup
	 *
	 * @param array $markers
	 * @return string
	 */
	private function getAllMarkerHTML(array $markers): string {
		$allMarker = '<table>';
		$allMarker .= '<style> table { text-align: left; } table tr th, table tr td { border-bottom: 1px solid rgba(0,0,0,0.2); padding: 2px 6px 4px;} </style>';
		foreach ($markers as $key => $value) {
733
			if (array_key_exists($key, $this->markerLabels) && $this->markerLabels[$key] !== NULL) {
734
735
736
				$key = $this->markerLabels[$key];
			}

737
			if (is_string($value)) {
738
				$allMarker .= '<tr><th>' . $key . ' </th><td> ' . $value . '</td></tr>';
739
			} elseif (is_array($value)) {
740
				foreach ($value as $innerKey => $innerValue) {
741
					$allMarker .= '<tr><th>' . $key . '.' . $innerKey . ' </th><td> ' . $innerValue . '</td></tr>';
742
				}
743
			} elseif (is_bool($value)) {
744
				$valueAsString = $value ? 'true' : 'false';
745
				$allMarker .= '<tr><th>' . $key . ' </th><td> ' . $valueAsString . '</td></tr>';
746
747
			} elseif (is_object($value)) {
				if (method_exists($value, '__toString')) {
748
					$allMarker .= '<tr><th>' . $key . ' </th><td> ' . $value->__toString() . '</td></tr>';
749
750
751
752
				}
			}
		}

753
		return $allMarker . '</table>';
754
755
	}

756
	/**
757
758
759
760
761
	 * Parses markers in an email View.
	 * !!! CHANGES THE SOURCE PATH AND IT SHOULD BE RESET BACK TO THE ORIGINAL!!!
	 *
	 * @param string $text
	 * @param StandaloneView $emailView
762
763
	 * @return string
	 */
764
	protected function parseMarkers(string $text, StandaloneView $emailView): string {
765
766
767
		if (strpos($text, '{') !== FALSE) {
			$emailView->setTemplateSource($text);
			return $emailView->render();
768
		}
769

770
		return $text;
771
772
773
	}

	/**
774
	 * Adds a new mail to the mail queue.
775
	 *
776
	 * @return Mail
777
778
779
780
	 * @throws Exception
	 * @throws IllegalObjectTypeException
	 * @throws NoSuchCacheException
	 */
781
	public function addMailToMailQueue(): Mail {
782
783
784
785
		$mail = GeneralUtility::makeInstance(Mail::class);
		$mail->setPid($this->pid);
		$mail->setExtensionKey($this->extensionKey);
		$mail->setTemplateName($this->templateName);
786
		$mail->setSiteLanguage($this->siteLanguage);
787
788
789
790
791
792
793
		$mail->setBlacklisted(
			$this->registerService->isTemplateBlacklisted(
				$this->extensionKey,
				$this->templateName,
				$this->pid
			)
		);
794
		$mail->setFromAddress($this->fromAddress);
795
		$mail->setFromName($this->fromName);
796
		$mail->setToAddress($this->toAddresses);
797
798
799
		$mail->setMailSubject($this->getSubjectToSend());
		$mail->setMailBody($this->getMailBodyToSend());
		$mail->setPriority($this->priority);
800
801
		$mail->setBccAddresses($this->bccAddresses);
		$mail->setCcAddresses($this->ccAddresses);
802
803
		$mail->setSendingTime(0);
		$mail->setLastSendingTime(0);
804
		$mail->setReplyTo($this->replyToAddress);
805
806
		foreach ($this->markers as $marker) {
			if ($marker instanceof FileReference) {
807
808
809
				// we need to create proper copies of the attachment so that the original file reference does not get
				// moved over to the mail model and worst case, the original model loses the reference because of this
				$originalResource = $marker->getOriginalResource();
810
811
812
				if ($originalResource instanceof CoreFileReference) {
					$coreFileReferenceMailFile = GeneralUtility::makeInstance(ResourceFactory::class)
						->createFileReferenceObject(
813
814
815
816
817
818
						[
							'uid_local' => $originalResource->getOriginalFile()->getUid(),
							'table_local' => 'sys_file',
							'uid' => uniqid('NEW_MAIL', TRUE)
						]
					);
819
					$newFileReference = GeneralUtility::makeInstance(FileReference::class);
820
821
822
					$newFileReference->setOriginalResource($coreFileReferenceMailFile);
					$mail->addAttachment($newFileReference);
				}
823
824
			}
		}
825

826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
		$mailRepository = $this->getMailRepository();
		$mailRepository->add($mail);
		$mailRepository->persist();
		return $mail;
	}

	/**
	 * Send a Mail from the queue, identified by its id
	 *
	 * @param int $uid
	 * @return bool
	 * @throws Exception
	 */
	public function sendMailFromQueue(int $uid): bool {
		if (!isset(self::$mailObjectCache[$uid]) || !self::$mailObjectCache[$uid] instanceof Mail) {
			$mailToSend = $this->getMailObjectByUid($uid);
			if ($mailToSend === FALSE) {
				return FALSE;
			}
		} else {
			$mailToSend = self::$mailObjectCache[$uid];
		}

		$this->sendMailsFromQueue([$mailToSend]);
		$success = FALSE;
		if ($mailToSend->getStatus() === Mail::STATUS_SENT) {
			$success = TRUE;
		}

		unset(self::$mailObjectCache[$uid]); // free the memory
		return $success;
	}

	/**
	 * Get the mail object by uid and check if it's blacklisted
	 *
	 * @param int $uid
	 * @return bool|object
	 * @throws Exception
	 */
	protected function getMailObjectByUid(int $uid): ?Mail {
867
868
869
870
871
872
873
		if (version_compare(VersionNumberUtility::getCurrentTypo3Version(), '10.4.0', '<')) {
			/** @var MailRepository $mailRepository */
			$mailRepository = $this->objectManager->get(MailRepository::class);
		} else {
			$mailRepository = GeneralUtility::makeInstance(MailRepository::class);
		}

874
875
876
877
878
879
		$mailObject = $mailRepository->findOneByUid($uid);
		if (!$mailObject || $mailObject->getBlacklisted()) {
			return NULL;
		}

		return $mailObject;
880
	}
881

882
	/**
883
884
885
886
887
	 * Add html and plain body text to the mail message object
	 *
	 * @param MailMessage $mailMessage
	 * @param string $htmlBody
	 * @param string $plainBody
888
	 */
889
890
891
892
893
894
895
896
	protected static function addBodyToMailMessage(MailMessage $mailMessage, string $htmlBody = '', string $plainBody = ''): void {
		if (version_compare(VersionNumberUtility::getCurrentTypo3Version(), '10.4.0', '<')) {
			$mailMessage->setBody($htmlBody, 'text/html');
			$mailMessage->addPart($plainBody, 'text/plain');
		} else {
			$mailMessage->html($htmlBody);
			$mailMessage->text($plainBody);
		}
897
898
899
	}

	/**
900
901
902
	 * Attach a file
	 * @param MailMessage $mailMessage
	 * @param FileInterface $file
903
	 */
904
905
906
907
908
909
910
911
912
913
	protected static function attachToMailMessage(MailMessage $mailMessage, FileInterface $file): void {
		if (version_compare(VersionNumberUtility::getCurrentTypo3Version(), '10.4.0', '<')) {
			$mailMessage->attach(
				Swift_Attachment::newInstance($file->getContents(), $file->getName(), $file->getMimeType())
			);
		} else {
			$mailMessage->attach(
				$file->getContents(), $file->getName(), $file->getMimeType()
			);
		}
914
915
916
	}

	/**
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
	 *
	 * @param Mail[] $mails
	 * @throws Exception
	 * @throws IllegalObjectTypeException
	 * @throws UnknownObjectException
	 */
	public function sendMailsFromQueue(array $mails): void {
		foreach ($mails as $mail) {
			$mailMessage = GeneralUtility::makeInstance(MailMessage::class);
			$toAddresses = trim($mail->getToAddress());
			$addressesArray = GeneralUtility::trimExplode(',', $mail->getToAddress(), TRUE);
			if (count($addressesArray) > 1) {
				$toAddresses = $addressesArray;
			}

			$mailMessage->setTo($toAddresses);
			$mailMessage->setFrom($mail->getFromAddress(), $mail->getFromName());
			$mailMessage->setCc(
				GeneralUtility::trimExplode(',', $mail->getCcAddresses(), TRUE)
			);
			$mailMessage->setBcc(
				GeneralUtility::trimExplode(',', $mail->getBccAddresses(), TRUE)
			);
			$mailMessage->setSubject($mail->getMailSubject());
			$plaintextService = GeneralUtility::makeInstance(PlaintextService::class);
			$plaintextBody = $plaintextService->makePlain($mail->getMailBody());
			self::addBodyToMailMessage($mailMessage, $mail->getMailBody(), $plaintextBody);

			if ($mail->getBccAddresses()) {
				$mailMessage->setBcc(GeneralUtility::trimExplode(',', $mail->getBccAddresses()));
			}

			if ($mail->getCcAddresses()) {
				$mailMessage->setCc(GeneralUtility::trimExplode(',', $mail->getCcAddresses()));
			}

			if ($mail->getReplyTo()) {
				$mailMessage->setReplyTo($mail->getReplyTo());
			}

			$attachments = $mail->getAttachments();
			if ($attachments->count() > 0) {
				foreach ($attachments as $attachment) {
					/**
					 * @var FileReference $attachment
					 */
					$originalResource = $attachment->getOriginalResource();
					if ($originalResource === NULL) {
						continue;
					}

					$file = $originalResource->getOriginalFile();
					self::attachToMailMessage($mailMessage, $file);
				}
			}

			$dateTime = new DateTime();
			if ($mail->getSendingTime() === 0) {
				$mail->setSendingTime($dateTime->getTimestamp());
			}

			$mail->setLastSendingTime($dateTime->getTimestamp());
			try {
				$success = $mailMessage->send();
				if (!$success) {
					$mail->setStatus(Mail::STATUS_ERROR);
					$mail->setErrorMessage(LocalizationUtility::translate('backend.success_mail_queue', 'sg_mail'));
				}
				$mail->setStatus(Mail::STATUS_SENT);
			} catch (TransportException $exception) {
				$mail->setStatus(Mail::STATUS_ERROR);
				$mail->setErrorMessage($exception->getMessage());
			}

			$this->getMailRepository()->update($mail);
		}

		$this->getMailRepository()->persist();
	}

	/**
	 * @param SiteLanguage $siteLanguage
	 * @return $this
1000