MailTemplateService.php 36.4 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\Context\Context;
37
use TYPO3\CMS\Core\Mail\MailMessage;
38
use TYPO3\CMS\Core\Resource\Exception\ResourceDoesNotExistException;
39
use TYPO3\CMS\Core\Resource\File;
40 41
use TYPO3\CMS\Core\Resource\FileInterface;
use TYPO3\CMS\Core\Resource\FileReference as CoreFileReference;
42
use TYPO3\CMS\Core\Resource\ResourceFactory;
43
use TYPO3\CMS\Core\SingletonInterface;
44 45
use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
use TYPO3\CMS\Core\Site\SiteFinder;
46
use TYPO3\CMS\Core\Utility\GeneralUtility;
47
use TYPO3\CMS\Core\Utility\VersionNumberUtility;
48
use TYPO3\CMS\Extbase\Domain\Model\FileReference;
49
use TYPO3\CMS\Extbase\Object\Exception;
50
use TYPO3\CMS\Extbase\Object\ObjectManager;
51 52
use TYPO3\CMS\Extbase\Persistence\Exception\IllegalObjectTypeException;
use TYPO3\CMS\Extbase\Persistence\Exception\UnknownObjectException;
53
use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
54
use TYPO3\CMS\Fluid\View\StandaloneView;
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
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;
72

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

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

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

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

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

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

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

119
	/**
120
	 * @var RegisterService
121
	 */
122 123
	protected $registerService;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

285 286 287 288 289 290 291 292 293
		$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
			];
294 295
		}

296
		return $generatedMarker;
297 298 299
	}

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

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

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

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

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

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

342
	/**
343 344 345 346
	 * Allows adding a file from filePath,
	 * YOU MUST USE setIgnoreMailQueue(TRUE)
	 * because we CAN NOT guarantee access to the added files
	 * when sending mails from the Queue, since this allows temporary files etc.
347 348 349 350 351
	 *
	 * @param string $path
	 * @param string|null $name
	 * @param string|null $contentType
	 */
Matthias Adrowski's avatar
Matthias Adrowski committed
352
	public function addFileFromFilePathForDirectSending(string $path, string $name = NULL, string $contentType = NULL) {
353 354 355
		$this->mailMessage->attachFromPath($path, $name, $contentType);
	}

356 357 358
	/**
	 * Set preview markers for the template editor
	 *
359
	 * @throws NoSuchCacheException
360
	 */
361 362
	public function setPreviewMarkers(): void {
		$previewMarkers = [];
363 364

		/** @var array $markerArray */
365
		$markerArray = $this->registerService->getRegisterArray()[$this->extensionKey][$this->templateName]['marker'];
366 367 368
		foreach ($markerArray as $marker) {
			$markerPath = GeneralUtility::trimExplode('.', $marker['marker']);
			$temporaryMarkerArray = [];
369
			foreach (array_reverse($markerPath) as $index => $markerPathSegment) {
370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385
				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];
				}
			}
386 387

			$previewMarkers[] = $temporaryMarkerArray;
388
		}
389 390

		$this->setMarkers(array_merge_recursive(...$previewMarkers));
391 392
	}

393
	/**
394
	 * Send the Email
395
	 *
396
	 * @param bool $isPreview
397
	 * @param bool $isNewsletter
398
	 * @return bool email was sent or added to mail queue successfully?
399 400 401 402 403 404
	 * @throws NoSuchCacheException
	 * @throws ResourceDoesNotExistException
	 * @throws IllegalObjectTypeException
	 * @throws UnknownObjectException
	 */
	public function sendEmail(bool $isPreview = FALSE, bool $isNewsletter = FALSE): bool {
405
		try {
406
			$template = $this->getTemplate();
407 408 409 410 411 412 413 414 415 416
		} 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
417
		$defaultTemplateContent = $this->getDefaultTemplateContent($template);
418

419
		$this->parseValuesForMail($template, $defaultTemplateContent);
420

421
		$mail = $this->addMailToMailQueue();
422 423
		self::$mailObjectCache[$mail->getUid()] = $mail; // add it to cache to avoid extra DB queries
		if ($this->ignoreMailQueue) {
424
			return $this->sendMailFromQueue($mail->getUid());
425
		}
426

427
		return TRUE;
428
	}
429

430 431 432
	/**
	 * Get the template object
	 *
433
	 * @return null|Template
434 435
	 * @throws \Exception
	 */
436 437 438 439 440 441
	private function getTemplate(): ?Template {
		$isTemplateBlacklisted = $this->registerService->isTemplateBlacklisted(
			$this->extensionKey,
			$this->templateName,
			$this->pid
		);
442
		if ($isTemplateBlacklisted) {
443
			throw new \Exception('The template is blacklisted');
444 445
		}

446 447 448 449 450
		$templateHash = $this->getTemplateHash();
		if (
			isset(self::$templateObjectCache[$templateHash])
			&& self::$templateObjectCache[$templateHash] instanceof Template
		) {
451 452
			return self::$templateObjectCache[$templateHash];
		}
453

454
		/** @var Template $template */
455 456 457
		$template = $this->getTemplateRepository()->findByTemplateProperties(
			$this->extensionKey, $this->templateName, [$this->siteLanguage], $this->pid
		)->getFirst();
458

459
		if ($template === NULL && !empty($this->siteLanguage->getFallbackLanguageIds())) {
460 461 462 463 464
			$site = GeneralUtility::makeInstance(SiteFinder::class)->getSiteByPageId($this->pid);
			$fallbackLanguages = [];
			foreach ($this->siteLanguage->getFallbackLanguageIds() as $fallbackLanguageId) {
				$fallbackLanguages[] = $site->getLanguageById($fallbackLanguageId);
			}
465

466 467 468
			$template = $this->getTemplateRepository()->findByTemplateProperties(
				$this->extensionKey, $this->templateName, $fallbackLanguages, $this->pid
			)->getFirst();
469 470 471 472 473
		} elseif($template === NULL) {
			$templateArray = $this->registerService->findTemplate(
				$this->extensionKey,
				$this->templateName,
				$this->siteLanguage
474
			);
475
			$templateArray['pid'] = $this->pid;
476
			$template = $this->getTemplateRepository()->create($templateArray);
477
		}
478

479
		self::$templateObjectCache[$templateHash] = $template;
480 481
		return $template;
	}
482

483 484 485 486 487
	/**
	 * Get the hash for the object cache
	 *
	 * @return string
	 */
488 489 490 491 492 493 494
	private function getTemplateHash(): string {
		return md5(
			$this->extensionKey . '_' .
			$this->templateName . '_' .
			$this->pid . '_' .
			$this->siteLanguage->getLanguageId()
		);
495 496 497 498 499 500 501
	}

	/**
	 * use all values from the given template
	 *
	 * @param Template $template
	 */
502 503
	public function loadTemplateValues(Template $template): void {
		$this->pid = $template->getPid();
504 505
		$this->extensionKey = $template->getExtensionKey();
		$this->templateName = $template->getTemplateName();
506
		$this->siteLanguage = $template->getSiteLanguage();
507 508

		$fromName = trim($template->getFromName());
509 510 511
		if ($fromName === '') {
			$fromName = $this->fromName;
		}
512

513 514 515 516
		if ($fromName === '' && $GLOBALS['TYPO3_CONF_VARS']['MAIL']['defaultMailFromName']) {
			$fromName = $GLOBALS['TYPO3_CONF_VARS']['MAIL']['defaultMailFromName'];
		}

517
		$fromMail = $this->getValidFromMail(trim($template->getFromMail()));
518 519 520 521 522 523 524 525 526 527 528 529 530 531 532
		// 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
	 */
533 534 535
	private function getValidFromMail(string $fromMail): string {
		$fromMail = trim($fromMail);
		if (!filter_var($fromMail, FILTER_VALIDATE_EMAIL)) {
536 537
			$fromMail = $this->fromAddress;
		}
538 539

		if (!filter_var($fromMail, FILTER_VALIDATE_EMAIL)) {
540
			$fromMail = $GLOBALS['TYPO3_CONF_VARS']['MAIL']['defaultMailFromAddress'];
541
			if (!filter_var($GLOBALS['TYPO3_CONF_VARS']['MAIL']['defaultMailFromAddress'], FILTER_VALIDATE_EMAIL)) {
542 543 544 545 546 547 548 549 550
				$fromMail = 'noreply@example.com';
			}
		}

		return $fromMail;
	}



551 552 553
	/**
	 * Get the default content for this template
	 *
554
	 * @param Template|null $template
555 556 557
	 * @return bool|string
	 * @throws Exception
	 * @throws NoSuchCacheException
558
	 */
559
	protected function getDefaultTemplateContent(Template $template = NULL) {
560
		$defaultTemplateContent =
561
			$this->registerService->getRegisterArray()[$this->extensionKey][$this->templateName]['templateContent'];
562

563
		// If there is no template for this language, use the default template
564
		if ($template === NULL && $defaultTemplateContent === NULL) {
565
			$templatePath =
566
				$this->registerService->getRegisterArray()[$this->extensionKey][$this->templateName]['templatePath'];
567 568 569

			// only standard template file is considered since version 4.1
			$defaultTemplateFile = $templatePath . 'template.html';
570 571
			if (file_exists($defaultTemplateFile)) {
				$defaultTemplateContent = file_get_contents($defaultTemplateFile);
572 573
			} else {
				// use configured default html template
574 575 576 577 578 579 580
				if (version_compare(VersionNumberUtility::getCurrentTypo3Version(), '10.4.0', '<')) {
					/** @var TypoScriptSettingsService $typoScriptSettingsService */
					$typoScriptSettingsService = $this->objectManager->get(TypoScriptSettingsService::class);
				} else {
					$typoScriptSettingsService = GeneralUtility::makeInstance(TypoScriptSettingsService::class);
				}

581 582 583 584
				$tsSettings = $typoScriptSettingsService->getSettings(0, 'tx_sgmail');
				$defaultTemplateFile = GeneralUtility::getFileAbsFileName(
					$tsSettings['mail']['defaultHtmlTemplate']
				);
585

586 587
				if (file_exists($defaultTemplateFile)) {
					$defaultTemplateContent = file_get_contents($defaultTemplateFile);
588
				} else {
589
					return FALSE;
590
				}
591
			}
592 593 594 595
		} elseif ($template !== NULL) {
			$defaultTemplateContent = $template->getContent();
		} else {
			$defaultTemplateContent = '';
Torsten Oppermann's avatar
Torsten Oppermann committed
596
		}
597

598 599
		return $defaultTemplateContent;
	}
Torsten Oppermann's avatar
Torsten Oppermann committed
600

601 602 603 604
	/**
	 * @param string $toAddresses
	 * @return MailTemplateService
	 */
605
	public function setToAddresses(string $toAddresses): MailTemplateService {
606 607 608 609 610
		$normalizedToAddresses = trim(preg_replace('~\x{00a0}~iu', ' ', $toAddresses));
		$this->toAddresses = $normalizedToAddresses;
		return $this;
	}

611
	/**
612 613 614
	 * Sets the values to send the mail with from the template or register service
	 *
	 * @param Template|null $template
615
	 * @param string $defaultTemplateContent
616 617
	 * @throws Exception
	 * @throws NoSuchCacheException
618
	 */
619
	protected function parseValuesForMail(
620 621
		Template $template = NULL, string $defaultTemplateContent = ''
	): void {
622 623 624 625 626 627 628
		if (version_compare(VersionNumberUtility::getCurrentTypo3Version(), '10.4.0', '<')) {
			/** @var StandaloneView $emailView */
			$emailView = $this->objectManager->get(StandaloneView::class);
		} else {
			$emailView = GeneralUtility::makeInstance(StandaloneView::class);
		}

629
		$emailView->assignMultiple($this->markers);
Torsten Oppermann's avatar
Torsten Oppermann committed
630
		$emailView->assign('all_fields', $this->getAllMarker($this->markers));
631
		$emailView->assign('all_fields_html', $this->getAllMarkerHTML($this->markers));
632

633
		//TODO: make this as the lines below the next block
634 635 636 637 638 639
		$overwrittenEmailBody = $this->getOverwrittenEmailBody();
		$overwrittenSubject = '';
		if ($this->subject !== '' && $this->subject !== NULL) {
			$overwrittenSubject = $this->subject;
		}

640
		// parse markers
Paul Ilea's avatar
Paul Ilea committed
641
		if ($template !== NULL) {
642 643 644
			$subject = $this->parseMarkers(
				trim(empty($overwrittenSubject) ? $template->getSubject() : $overwrittenSubject),
				$emailView
645
			);
646 647
			$layoutId = $template->getLayout();
			$templateContent = $template->getContent();
648
			$this->setSubjectToSend($subject);
649
		} else {
650
			$subject = $this->registerService->getRegisterArray()[$this->extensionKey][$this->templateName]['subject'];
651 652
			if (is_array($subject)) {
				$subject = trim(
653
					$this->registerService->getRegisterArray()
654
					[$this->extensionKey][$this->templateName]['subject'][$this->siteLanguage->getTypo3Language()]
655
				);
656
			}
657

658 659 660 661
			$subject = $this->parseMarkers(
				(empty($overwrittenSubject) ? $subject : $overwrittenSubject),
				$emailView
			);
662

663 664 665
			$layoutId = 0;
			$templateContent = $defaultTemplateContent;
		}
666

667
		$this->setSubjectToSend($subject);
668

669 670
		// Parse the markers
		if ($this->fromName) {
671
			$this->setFromName($this->parseMarkers($this->fromName, $emailView));
672 673
		}

674
		if ($this->fromAddress) {
675
			$this->setFromAddress($this->parseMarkers($this->fromAddress, $emailView));
676 677
		}

678
		if ($this->replyToAddress) {
679
			$this->setReplyToAddress($this->parseMarkers($this->replyToAddress, $emailView));
680 681
		}

682 683
		if ($this->ccAddresses) {
			$this->setCcAddresses($this->parseMarkers($this->ccAddresses, $emailView));
684 685
		}

686
		if ($this->bccAddresses) {
687
			$this->setBccAddresses($this->parseMarkers($this->bccAddresses, $emailView));
688 689
		}

690
		if ($this->toAddresses) {
691
			$this->setToAddresses($this->parseMarkers($this->toAddresses, $emailView));
692 693 694 695
		}

		// reset template source back to default
		$emailView->setTemplateSource(
696
			empty($overwrittenEmailBody) ? $templateContent : $overwrittenEmailBody
697
		);
698

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

704 705 706 707 708
		$layout = $this->getLayoutRepository()->findByUidOrDefault(
			$layoutId,
			$this->pid,
			$this->siteLanguage->getLanguageId()
		);
709 710 711
		$emailHTMLHead = '';
		if ($layout) {
			$emailHTMLHead = $layout->getHeadContent();
712
			$emailBody = str_replace('###CONTENT###', $emailBody, $layout->getContent());
713
		}
714

715
		$this->mailBodyToSend = '<html><head>' . $emailHTMLHead . '</head><body>' . $emailBody . '</body></html>';
716 717 718
	}

	/**
719
	 * Get a single variable containing a list of all markers
720
	 *
721 722
	 * @param array $markers
	 * @return string
723
	 */
724 725 726
	private function getAllMarker(array $markers): string {
		$allMarker = '';
		foreach ($markers as $key => $value) {
727
			if (array_key_exists($key, $this->markerLabels) && $this->markerLabels[$key] !== NULL) {
728 729
				$key = $this->markerLabels[$key];
			}
730

731
			if (is_string($value)) {
732
				$allMarker .= $key . ': ' . $value . PHP_EOL;
733
			} elseif (is_array($value)) {
734 735 736
				foreach ($value as $innerKey => $innerValue) {
					$allMarker .= $key . '.' . $innerKey . ': ' . $innerValue . PHP_EOL;
				}
737
			} elseif (is_bool($value)) {
738 739
				$valueAsString = $value ? 'true' : 'false';
				$allMarker .= $key . ': ' . $valueAsString . PHP_EOL;
740 741
			} elseif ($value instanceof DateTime) {
				$allMarker .= $key . ': ' . $value->format('d.m.Y') . PHP_EOL;
742 743
			} elseif (is_object($value)) {
				if (method_exists($value, '__toString')) {
744 745 746
					$allMarker .= $key . ': ' . $value->__toString() . PHP_EOL;
				}
			}
747 748
		}

749 750
		return $allMarker;
	}
751

752 753 754 755 756 757 758 759 760 761
	/**
	 * 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) {
762
			if (array_key_exists($key, $this->markerLabels) && $this->markerLabels[$key] !== NULL) {
763 764 765
				$key = $this->markerLabels[$key];
			}

766
			if (is_string($value)) {
767
				$allMarker .= '<tr><th>' . $key . ' </th><td> ' . $value . '</td></tr>';
768
			} elseif (is_array($value)) {
769
				foreach ($value as $innerKey => $innerValue) {
770
					$allMarker .= '<tr><th>' . $key . '.' . $innerKey . ' </th><td> ' . $innerValue . '</td></tr>';
771
				}
772
			} elseif (is_bool($value)) {
773
				$valueAsString = $value ? 'true' : 'false';
774
				$allMarker .= '<tr><th>' . $key . ' </th><td> ' . $valueAsString . '</td></tr>';
775 776
			} elseif ($value instanceof DateTime) {
				$allMarker .= '<tr><th>' . $key . ' </th><td> ' . $value->format('d.m.Y') . '</td></tr>';
777 778
			} elseif (is_object($value)) {
				if (method_exists($value, '__toString')) {
779
					$allMarker .= '<tr><th>' . $key . ' </th><td> ' . $value->__toString() . '</td></tr>';
780 781 782 783
				}
			}
		}

784
		return $allMarker . '</table>';
785
	}
786

787 788 789 790 791 792 793 794
	/**
	 * 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
	 * @return string
	 */
795
	protected function parseMarkers(string $text, StandaloneView $emailView): string {
796 797 798
		if (strpos($text, '{') !== FALSE) {
			$emailView->setTemplateSource($text);
			return $emailView->render();
799
		}