MailTemplateService.php 34.9 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
43
use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
use TYPO3\CMS\Core\Site\SiteFinder;
44
use TYPO3\CMS\Core\Utility\GeneralUtility;
45
use TYPO3\CMS\Core\Utility\VersionNumberUtility;
46
use TYPO3\CMS\Extbase\Domain\Model\FileReference;
47
use TYPO3\CMS\Extbase\Object\Exception;
48
use TYPO3\CMS\Extbase\Object\ObjectManager;
49
50
use TYPO3\CMS\Extbase\Persistence\Exception\IllegalObjectTypeException;
use TYPO3\CMS\Extbase\Persistence\Exception\UnknownObjectException;
51
use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
52
use TYPO3\CMS\Fluid\View\StandaloneView;
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
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;
70

71
72
73
/**
 * MailTemplateService
 */
74
class MailTemplateService {
75
76
77
78
79
80
81
82
83
84
	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';
85

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

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

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

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

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

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

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

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

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

132
	/**
133
	 * @var string $ccAddresses
134
	 */
Paul Ilea's avatar
Paul Ilea committed
135
	private $ccAddresses;
136

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

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

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

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

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

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

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

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

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

182
	/**
183
	 * @var string $bccAddresses
184
	 */
Paul Ilea's avatar
Paul Ilea committed
185
	private $bccAddresses;
186

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

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

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

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

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

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

227
		$this->registerService = GeneralUtility::makeInstance(RegisterService::class);
228
229
230
231
232
233
234
		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);
		}
235

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

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

Torsten Oppermann's avatar
Torsten Oppermann committed
250
251
		$this->bccAddresses = GeneralUtility::trimExplode(',', $tsSettings['mail']['default']['bcc']);
		$this->ccAddresses = GeneralUtility::trimExplode(',', $tsSettings['mail']['default']['cc']);
252
		foreach ($this->bccAddresses as $index => $email) {
253
			if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
254
255
256
257
258
259
260
261
262
				unset($this->bccAddresses[$index]);
			}
		}

		foreach ($this->ccAddresses as $index => $email) {
			if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
				unset($this->ccAddresses[$index]);
			}
		}
263
264
265
	}

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

280
281
282
283
284
285
286
287
288
		$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
			];
289
290
		}

291
		return $generatedMarker;
292
293
294
	}

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

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

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

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

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

		$this->markers[] = $newFileReference;
334
335
336
		return $this;
	}

337
338
339
	/**
	 * Set preview markers for the template editor
	 *
340
	 * @throws NoSuchCacheException
341
	 */
342
343
	public function setPreviewMarkers(): void {
		$previewMarkers = [];
344
345

		/** @var array $markerArray */
346
		$markerArray = $this->registerService->getRegisterArray()[$this->extensionKey][$this->templateName]['marker'];
347
348
349
		foreach ($markerArray as $marker) {
			$markerPath = GeneralUtility::trimExplode('.', $marker['marker']);
			$temporaryMarkerArray = [];
350
			foreach (array_reverse($markerPath) as $index => $markerPathSegment) {
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
				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];
				}
			}
367
368

			$previewMarkers[] = $temporaryMarkerArray;
369
		}
370
371

		$this->setMarkers(array_merge_recursive(...$previewMarkers));
372
373
	}

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

		// set the ToAddress if there are no placeholders in it
		// TODO: does this belong here?
402
403
		if ($template !== NULL && filter_var($template->getToAddress(), FILTER_VALIDATE_EMAIL)) {
			$this->setToAddresses(trim($template->getToAddress()));
404
405
		}

406
		$this->parseValuesForMail($template, $defaultTemplateContent);
407

408
		$mail = $this->addMailToMailQueue();
409
		self::$mailObjectCache[$mail->getUid()] = $mail; // add it to cache to avoid extra DB queries
410
		$success = FALSE;
411
412
413
414
415
416
417
		if ($this->ignoreMailQueue) {
			$success = $this->sendMailFromQueue($mail->getUid());
		}

		return $success;
	}

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

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

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

447
		if ($template === NULL) {
448
449
450
451
452
453
454
455
456
			$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();
457
		}
458

459
		self::$templateObjectCache[$templateHash] = $template;
460
461
		return $template;
	}
462

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

	/**
	 * use all values from the given template
	 *
	 * @param Template $template
	 */
482
483
484
485
486
487
	private function loadTemplateValues(Template $template): void {
		$this->resetTemplate();
		$this->extensionKey = $template->getExtensionKey();
		$this->templateName = $template->getTemplateName();

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

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

496
		$fromMail = $this->getValidFromMail(trim($template->getFromMail()));
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
		// 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
	 */
512
513
514
	private function getValidFromMail(string $fromMail): string {
		$fromMail = trim($fromMail);
		if (!filter_var($fromMail, FILTER_VALIDATE_EMAIL)) {
515
516
			$fromMail = $this->fromAddress;
		}
517
518

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

		return $fromMail;
	}



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

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

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

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

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

573
574
		return $defaultTemplateContent;
	}
Torsten Oppermann's avatar
Torsten Oppermann committed
575

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

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

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

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

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

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

638
639
640
			$layoutId = 0;
			$templateContent = $defaultTemplateContent;
		}
641

642
		$this->setSubjectToSend($subject);
643

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

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

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

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

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

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

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

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

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

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

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

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

722
723
		return $allMarker;
	}
724

725
726
727
728
729
730
731
732
733
734
	/**
	 * 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) {
735
			if (array_key_exists($key, $this->markerLabels) && $this->markerLabels[$key] !== NULL) {
736
737
738
				$key = $this->markerLabels[$key];
			}

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

755
		return $allMarker . '</table>';
756
757
	}

758
	/**
759
760
761
762
763
	 * 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
764
765
	 * @return string
	 */
766
	protected function parseMarkers(string $text, StandaloneView $emailView): string {
767
768
769
		if (strpos($text, '{') !== FALSE) {
			$emailView->setTemplateSource($text);
			return $emailView->render();
770
		}
771

772
		return $text;
773
774
775
	}

	/**
776
	 * Adds a new mail to the mail queue.
777
	 *
778
	 * @return Mail
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
	 * @throws Exception
	 * @throws IllegalObjectTypeException
	 * @throws NoSuchCacheException
	 */
	private function addMailToMailQueue(): Mail {
		$mail = GeneralUtility::makeInstance(Mail::class);
		$mail->setPid($this->pid);
		$mail->setExtensionKey($this->extensionKey);
		$mail->setTemplateName($this->templateName);
		$mail->setLanguage(explode('.', $this->siteLanguage->getLocale())[0]);
		$mail->setBlacklisted(
			$this->registerService->isTemplateBlacklisted(
				$this->extensionKey,
				$this->templateName,
				$this->pid
			)
		);
796
		$mail->setFromAddress($this->fromAddress);
797
		$mail->setFromName($this->fromName);
798
		$mail->setToAddress($this->toAddresses);
799
800
801
		$mail->setMailSubject($this->getSubjectToSend());
		$mail->setMailBody($this->getMailBodyToSend());
		$mail->setPriority($this->priority);
802
803
		$mail->setBccAddresses($this->bccAddresses);
		$mail->setCcAddresses($this->ccAddresses);
804
805
		$mail->setSendingTime(0);
		$mail->setLastSendingTime(0);
806
		$mail->setReplyTo($this->replyToAddress);
807
808
		foreach ($this->markers as $marker) {
			if ($marker instanceof FileReference) {
809
810
811
				// 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();
812
813
814
				if ($originalResource instanceof CoreFileReference) {
					$coreFileReferenceMailFile = GeneralUtility::makeInstance(ResourceFactory::class)
						->createFileReferenceObject(
815
816
817
818
819
820
						[
							'uid_local' => $originalResource->getOriginalFile()->getUid(),
							'table_local' => 'sys_file',
							'uid' => uniqid('NEW_MAIL', TRUE)
						]
					);
821
					$newFileReference = GeneralUtility::makeInstance(FileReference::class);
822
823
824
					$newFileReference->setOriginalResource($coreFileReferenceMailFile);
					$mail->addAttachment($newFileReference);
				}
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
867
868
		$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 {
869
870
871
872
873
874
875
		if (version_compare(VersionNumberUtility::getCurrentTypo3Version(), '10.4.0', '<')) {
			/** @var MailRepository $mailRepository */
			$mailRepository = $this->objectManager->get(MailRepository::class);
		} else {
			$mailRepository = GeneralUtility::makeInstance(MailRepository::class);
		}

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

		return $mailObject;
882
	}
883

884
	/**
885
886
887
888
889
	 * Add html and plain body text to the mail message object
	 *
	 * @param MailMessage $mailMessage
	 * @param string $htmlBody
	 * @param string $plainBody
890
	 */
891
892
893
894
895
896
897
898
	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);
		}
899
900
901
	}

	/**
902
903
904
	 * Attach a file
	 * @param MailMessage $mailMessage
	 * @param FileInterface $file
905
	 */
906
907
908
909
910
911
912
913
914
915
	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()
			);
		}
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
1000
1001
	 *
	 * @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
1002
	 */
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
	public function setSiteLanguage(SiteLanguage $siteLanguage): MailTemplateService {
		$this->siteLanguage = $siteLanguage;
		return $this;
	}

	/**
	 * @param string $templateName
	 * @return MailTemplateService
	 */
	public function setTemplateName(string $templateName): MailTemplateService {
		$this->templateName = $templateName;
		return $this;
1015
1016
1017
	}

	/**
1018
1019
	 * @param string $extensionKey
	 * @return MailTemplateService
1020
	 */
1021
1022
1023
	public function setExtensionKey(string $extensionKey): MailTemplateService {
		$this->extensionKey = $extensionKey;
		return $this;
1024
1025
	}

1026
	/**