Skip to content
Snippets Groups Projects
MailTemplateService.php 21.4 KiB
Newer Older
<?php

namespace SGalinski\SgMail\Service;

/***************************************************************
 *  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!
 ***************************************************************/
use SGalinski\SgMail\Domain\Model\Mail;
use SGalinski\SgMail\Domain\Model\Template;
use SGalinski\SgMail\Domain\Repository\MailRepository;
use SGalinski\SgMail\Domain\Repository\TemplateRepository;
use Swift_Attachment;
use Swift_OutputByteStream;
use TYPO3\CMS\Core\Cache\CacheManager;
use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
use TYPO3\CMS\Core\Mail\MailMessage;
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Object\ObjectManager;
use TYPO3\CMS\Extbase\Persistence\Generic\PersistenceManager;
use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
use TYPO3\CMS\Fluid\View\StandaloneView;
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
class MailTemplateService {
	const MARKER_TYPE_STRING = 'String';
	const MARKER_TYPE_ARRAY = 'Array';
	const MARKER_TYPE_OBJECT = 'Object';
	const DEFAULT_TEMPLATE_PATH = 'Resources/Private/Templates/SgMail/';
	const CACHE_NAME = 'sg_mail_registerArrayCache';
	const CACHE_LIFETIME_IN_SECONDS = 86400;
	 * @var array $toAddresses
	private $toAddresses = [];
	 * @var string $fromAddress
	private $fromAddress;
	 * @var array $ccAddresses
	private $ccAddresses;
	 * @var string $replyToAddress
	private $replyToAddress;
	 * @var string $language
	private $language = 'default';
	 * @var boolean $ignoreMailQueue
	private $ignoreMailQueue = FALSE;

	/**
	 * @var \TYPO3\CMS\Core\Mail\MailMessage $mailMessage
	 */
	private $mailMessage;

	/**
	 * @var string $templateName
	 */
	private $templateName;

	/**
	 * @var string $extensionKey
	 */
	private $extensionKey;

	/**
	 * @var array $markers
Torsten Oppermann's avatar
Torsten Oppermann committed
	private $markers;
	/**
	 * @var array $bccAddresses
	 */
	private $bccAddresses;
	/**
	 * @var int
	 */
	private $priority = Mail::PRIORITY_LOWEST;

	/**
	 * @var string
	 */
	private $fromName = '';

	/**
	 * @var \SGalinski\SgMail\Domain\Repository\TemplateRepository
	 */
	protected $templateRepository;

	/**
	 * @var \TYPO3\CMS\Extbase\Persistence\Generic\PersistenceManager
	 */
	protected $persistenceManager;
	/**
	 * @var \TYPO3\CMS\Extbase\Object\ObjectManager
	 */
	protected $objectManager;

	/**
	 * MailTemplateService constructor.
	 * @param string $templateName
	 * @param string $extensionKey
	 * @param string $markers
	 * @throws \InvalidArgumentException
	public function __construct($templateName = '', $extensionKey = '', $markers = '') {
		$this->templateName = $templateName;
		$this->extensionKey = $extensionKey;
		$this->markers = $markers;

		/** @var ObjectManager objectManager */
		$this->objectManager = GeneralUtility::makeInstance(ObjectManager::class);
		/** @var MailMessage mailMessage */
		$this->mailMessage = $this->objectManager->get(MailMessage::class);
		/** @var TypoScriptSettingsService $typoScriptSettingsService */
		$typoScriptSettingsService = $this->objectManager->get(TypoScriptSettingsService::class);
		$tsSettings = $typoScriptSettingsService->getSettings(0, 'tx_sgmail');
		/** @var TemplateRepository templateRepository */
		$this->templateRepository = $this->objectManager->get(TemplateRepository::class);
		/** @var PersistenceManager persistenceManager */
		$this->persistenceManager = $this->objectManager->get(PersistenceManager::class);
		// use defaultMailFromAddress if it is provided in LocalConfiguration.php; use the sg_mail TS setting as fallback
		if (!filter_var($GLOBALS['TYPO3_CONF_VARS']['MAIL']['defaultMailFromAddress'], FILTER_VALIDATE_EMAIL)) {
			$this->fromAddress = $GLOBALS['TYPO3_CONF_VARS']['MAIL']['defaultMailFromAddress'];
		} else {
			$this->fromAddress = $tsSettings['mail']['default']['from'];
			if (!filter_var($tsSettings['mail']['default']['from'], FILTER_VALIDATE_EMAIL)) {
				$this->fromAddress = 'noreply@example.org';
			} else {
				$this->fromAddress = $tsSettings['mail']['default']['from'];
		$this->mailMessage->setFrom($this->fromAddress);
		$this->bccAddresses = GeneralUtility::trimExplode(',', $tsSettings['mail']['default']['bcc']);
		$this->ccAddresses = GeneralUtility::trimExplode(',', $tsSettings['mail']['default']['cc']);

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

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

		if (count($this->bccAddresses) > 0) {
		if (count($this->ccAddresses) > 0) {
			$this->mailMessage->setCc($this->ccAddresses);
		}
	/**
	 * Return default markers for sg_mail
	 *
	 * @param string $translationKey
	 * @param array $marker
	 * @param string $extensionKey
	 * @return array
	 */
	public static function getDefaultTemplateMarker($translationKey, array $marker, $extensionKey = 'sg_mail') {
		$languagePath = 'LLL:EXT:' . $extensionKey . '/Resources/Private/Language/locallang.xlf:' . $translationKey;

		// Need the key for translations
		if (trim($extensionKey) === '') {
			return [];
		}

		$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
			];
		}

		return $generatedMarker;
	}

	 * @param boolean $isPreview
	 * @return boolean email was sent or added to mail queue successfully?
	 * @throws \TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException
	 * @throws \InvalidArgumentException
	 * @throws \BadFunctionCallException
	 * @throws \TYPO3\CMS\Extbase\Persistence\Exception\IllegalObjectTypeException
	public function sendEmail($isPreview = FALSE): bool {
		if (TYPO3_MODE === 'FE') {
			/** @var TypoScriptFrontendController $tsfe */
			$tsfe = $GLOBALS['TSFE'];
			$pageUid = $tsfe->id;
		} else {
			$pageUid = (int) GeneralUtility::_GP('id');
		}

		if ($this->pid) {
			$pageUid = $this->pid;
		}
		$siteRootId = BackendService::getSiteRoot($pageUid);
		$template = $this->templateRepository->findOneByTemplate(
			$this->extensionKey, $this->templateName, $this->language, $siteRootId
		if ($template === NULL) {
			$template = $this->templateRepository->findOneByTemplate(
				$this->extensionKey, $this->templateName, 'default', $siteRootId
			);
		}

		// if there is a template, prefer those values
		if ($template) {
			$this->loadTemplateValues($template);
		}

		$defaultTemplateContent = NULL;
		// If there is no template for this language, use the default template
		if ($template === NULL) {
			$templatePath = self::getRegisterArray()[$this->extensionKey][$this->templateName]['templatePath'];
			// only standard template file is considered since version 4.1
			$defaultTemplateFile = $templatePath . 'template.html';
			if (file_exists($defaultTemplateFile)) {
				$defaultTemplateContent = file_get_contents($defaultTemplateFile);
			} else {
				return FALSE;
		} elseif (filter_var($template->getToAddress(), FILTER_VALIDATE_EMAIL)) {
			$this->setToAddresses(trim($template->getToAddress()));
		if ($isPreview) {
			$previewMarker = [];
			/** @var array $markerArray */
			$markerArray = self::getRegisterArray()[$this->extensionKey][$this->templateName]['marker'];
			foreach ($markerArray as $marker) {
				$markerPath = GeneralUtility::trimExplode('.', $marker['marker']);
				$temporaryMarkerArray = [];
				foreach (array_reverse($markerPath) as $index => $markerPathSegment) {
					if ($index === 0) {
						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];
				$previewMarker = array_merge_recursive($previewMarker, $temporaryMarkerArray);
			$this->setIgnoreMailQueue(TRUE);
			$this->setMarkers($previewMarker);
		}

		/** @var StandaloneView $emailView */
		$emailView = $this->objectManager->get(StandaloneView::class);
			$emailView->setTemplateSource($template->getContent());
			$subject = $template->getSubject();
		} else {
			$emailView->setTemplateSource($defaultTemplateContent);
			$subject = self::getRegisterArray()[$this->extensionKey][$this->templateName]['subject'];
				$subject = self::getRegisterArray(
				)[$this->extensionKey][$this->templateName]['subject'][$this->language];
		$this->mailMessage->setSubject($subject);
		$emailView->assignMultiple($this->markers);
		$emailBody = $emailView->render();
		// insert <br /> tags, but replace every instance of three or more successive breaks with just two.
		$emailBody = nl2br($emailBody);
		$emailBody = preg_replace('/(<br[\s]?[\/]?>[\s]*){3,}/', '<br /><br />', $emailBody);
		if ($this->ignoreMailQueue) {
			$this->mailMessage->setBody($emailBody, 'text/html');
			$this->mailMessage->send();

			if (!$isPreview) {
				$this->addMailToMailQueue(
					$this->extensionKey, $this->templateName, $subject, $emailBody, $this->priority,
					$currentTimestamp, $currentTimestamp, $this->language, $siteRootId
			if (!$isPreview) {
				$this->addMailToMailQueue(
					$this->extensionKey, $this->templateName, $subject, $emailBody, $this->priority, 0, 0,
					$this->language, $siteRootId
				);
			}
	 * Adds a new mail to the mail queue.
	 * @param string $extensionKey
	 * @param string $templateName
	 * @param string $subject
	 * @param string $emailBody
	 * @param int $priority
	 * @param string $language
	 * @throws \TYPO3\CMS\Extbase\Persistence\Exception\IllegalObjectTypeException
		$extensionKey, $templateName, $subject, $emailBody, $priority, $sendingTime = 0,
		$lastSendingTime = 0, $language = self::DEFAULT_LANGUAGE, $pid = 0
		$mail = $this->objectManager->get(Mail::class);
		$mail->setExtensionKey($extensionKey);
		$mail->setTemplateName($templateName);
		$mail->setLanguage($language);
		$mail->setFromAddress($this->fromAddress);
		$mail->setFromName($this->fromName);
		$mail->setToAddress($this->toAddresses);
		$mail->setMailSubject($subject);
		$mail->setMailBody($emailBody);
		$mail->setPriority($priority);
		$mail->setBccAddresses($this->bccAddresses);
		$mail->setCcAddresses($this->ccAddresses);
		$mail->setReplyTo($this->replyToAddress);
		$mailRepository = $this->objectManager->get(MailRepository::class);
		$this->persistenceManager->persistAll();
	/**
	 * Send a Mail from the queue, identified by its id
	 *
	 * @param int $uid
	 * @throws \TYPO3\CMS\Extbase\Persistence\Exception\IllegalObjectTypeException
	 * @throws \TYPO3\CMS\Extbase\Persistence\Exception\UnknownObjectException
	 */
	public function sendMailFromQueue($uid) {
		$mailRepository = $this->objectManager->get(MailRepository::class);
		/** @var Mail $mailToSend */
		$mailToSend = $mailRepository->findOneByUid($uid);

		if ($mailToSend) {
			$this->mailMessage->setBody($mailToSend->getMailBody(), 'text/html');
			$this->mailMessage->setTo(trim($mailToSend->getToAddress()));
			$this->mailMessage->setFrom($mailToSend->getFromAddress(), $mailToSend->getFromName());
			$this->mailMessage->setSubject($mailToSend->getMailSubject());
			if ($mailToSend->getBccAddresses()) {
				$this->mailMessage->setBcc(GeneralUtility::trimExplode(',', $mailToSend->getBccAddresses()));
			}

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

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

			$dateTime = new DateTime();
			$mailToSend->setLastSendingTime($dateTime->getTimestamp());
			$this->mailMessage->send();
	 * @param string $toAddresses
	 * @return MailTemplateService
	 */
	public function setToAddresses($toAddresses) {
		$toAddresses = preg_replace('~\x{00a0}~siu', ' ', $toAddresses);
		$this->toAddresses = trim($toAddresses);
		$this->mailMessage->setTo(trim($toAddresses));
	 * @param string $fromAddress
	 * @return MailTemplateService
	 */
	public function setFromAddress($fromAddress, $fromName = '') {
		if ($fromAddress) {
			$this->fromAddress = $fromAddress;
			$this->mailMessage->setFrom($fromAddress, $fromName);
		}

	 * @return MailTemplateService
	 */
	public function setCcAddresses($ccAddresses) {
		if ($ccAddresses) {
			$this->ccAddresses = $ccAddresses;
			$this->mailMessage->setCc(GeneralUtility::trimExplode(',', $this->ccAddresses));
	 * @param string $replyToAddress
	 * @return MailTemplateService
	 */
	public function setReplyToAddress($replyToAddress) {
		if ($replyToAddress) {
			$this->replyToAddress = $replyToAddress;
			$this->mailMessage->setReplyTo($replyToAddress);
		}

		return $this;
	}

	/**
	 * @param string $language
	 * @return MailTemplateService
	 */
	public function setLanguage($language) {
		$this->language = $language;
		return $this;
	}

	/**
	 * @param boolean $ignoreMailQueue
	 * @return MailTemplateService
	 */
	public function setIgnoreMailQueue($ignoreMailQueue) {
		$this->ignoreMailQueue = $ignoreMailQueue;
		return $this;
	}

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

	/**
	 * @param string $extensionKey
	 * @return MailTemplateService
	 */
	public function setExtensionKey($extensionKey) {
		$this->extensionKey = $extensionKey;
		return $this;
	}

	public function setMarkers(array $markers) {
		$this->markers = $markers;
	 * @return MailTemplateService
	 */
		if ($bccAddresses) {
			$this->bccAddresses = $bccAddresses;
			$this->mailMessage->setBcc(GeneralUtility::trimExplode(',', $this->bccAddresses));
	/**
	 * @param int $priority
	 * @return MailTemplateService
	 */
	public function setPriority($priority) {
		$this->priority = $priority;
		return $this;
	}

	/**
	 * @param Swift_OutputByteStream $data
	 * @param string $filename
	 * @param string $contentType
	 * @return MailTemplateService
	 */
	public function addAttachment($data, $filename, $contentType) {
		$attachment = Swift_Attachment::newInstance()
			->setFilename($filename)
			->setContentType($contentType)
			->setBody($data);
		$this->mailMessage->attach($attachment);
		return $this;
	}

	/**
	 * @return MailMessage
	 */
	public function getMailMessage() {
		return $this->mailMessage;
	}

	/**
	 * use all values from the given template
	 *
	 * @param Template $template
	 */
	private function loadTemplateValues($template) {
		$fromName = $template->getFromName();
		if ($fromName === '' && $GLOBALS['TYPO3_CONF_VARS']['MAIL']['defaultMailFromName']) {
			$fromName = $GLOBALS['TYPO3_CONF_VARS']['MAIL']['defaultMailFromName'];
		}

		$fromMail = $template->getFromMail();
		if (!filter_var($fromMail, FILTER_VALIDATE_EMAIL)) {
			$fromMail = $GLOBALS['TYPO3_CONF_VARS']['MAIL']['defaultMailFromAddress'];
			if (!filter_var($GLOBALS['TYPO3_CONF_VARS']['MAIL']['defaultMailFromAddress'], FILTER_VALIDATE_EMAIL)) {
				$fromMail = 'noreply@example.com';
			}
		}

		$this->setFromAddress($fromMail, $fromName);
		$this->setCcAddresses($template->getCc());
		$this->setBccAddresses($template->getBcc());
		$this->setReplyToAddress($template->getReplyTo());
		$this->setFromName($fromName);
		$this->setReplyToAddress($template->getReplyTo());
	}

	/**
	 * @param string $fromName
	 */
	public function setFromName($fromName) {
		$this->fromName = $fromName;

	/**
	 * Provides translation for the marker data type
	 *
	 * @param string $markerType
	 */
	public static function getReadableMarkerType($markerType) {
		switch ($markerType) {
			case self::MARKER_TYPE_STRING :
				LocalizationUtility::translate('backend.marker.type.string', 'sg_mail');
				break;
			case self::MARKER_TYPE_ARRAY :
				LocalizationUtility::translate('backend.marker.type.array', 'sg_mail');
				break;
			case self::MARKER_TYPE_OBJECT :
				LocalizationUtility::translate('backend.marker.type.object', 'sg_mail');
				break;
			default:
				LocalizationUtility::translate('backend.marker.type.mixed', 'sg_mail');
		}
	}

	/**
	 * set the page id from which this was called
	 *
	 * @param int $pid
	 * @return MailTemplateService
	 */
	public function setPid($pid) {
		$this->pid = (int) $pid;
		return $this;
	}
	/**
	 * Get all registered templates
	 *
	 * @return array
	 * @throws \TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException
	 * @throws \BadFunctionCallException
	 * @throws \InvalidArgumentException
	 */
	public static function getRegisterArray() {
		/** @var CacheManager $cacheManager */
		$cacheManager = GeneralUtility::makeInstance(CacheManager::class);
		/** @var FrontendInterface $cache */
		$cache = $cacheManager->getCache(self::CACHE_NAME);
		$cacheId = md5('sg_mail');
		/** @var array entry */
		if (($entry = $cache->get($cacheId)) === FALSE) {
			$entry = self::registerExtensions();

			if ($entry === NULL) {
				$entry = [];
			}

			$cache->set($cacheId, $entry, [], self::CACHE_LIFETIME_IN_SECONDS);
		}

		return $entry;
	}

	/**
	 * Iterate over all installed extensions and look for sg_mail configuration files
	 * If found, register the template(s)
	 *
	 * @throws \BadFunctionCallException
	public static function registerExtensions(): array {
		// clear registerArray

		$extensionList = ExtensionManagementUtility::getLoadedExtensionListArray();

		foreach ($extensionList as $extensionName) {
			$extensionConfigDirectory = ExtensionManagementUtility::extPath($extensionName);
			$extensionConfigDirectory .= '/Configuration/MailTemplates';
			$configFiles = GeneralUtility::getFilesInDir($extensionConfigDirectory);

			foreach ($configFiles as $configFile) {
				$configArray = (include $extensionConfigDirectory . '/' . $configFile);
				$extensionKey = $configArray['extension_key'];
				$templateKey = $configArray['template_key'];

				// transform template directory name: your_templates => YourTemplates/
				$templateDirectoryParts = GeneralUtility::trimExplode('_', $configArray['template_key']);
				$templateDirectory = '';
				foreach ($templateDirectoryParts as $part) {
					$templateDirectory .= ucfirst($part);
				}
				$templateDirectory .= '/';
				$templatePath = ExtensionManagementUtility::extPath(
						$extensionName
					) . self::DEFAULT_TEMPLATE_PATH . $templateDirectory;
				if ($configArray['template_path']) {
					$templatePath = $configArray['template_key'];
				}
				$description = $configArray['description'];
				$subject = $configArray['subject'];
				$marker = $configArray['markers'];

				$registerArray[$extensionKey][$templateKey] = [
					'templatePath' => $templatePath,
					'description' => $description,
					'marker' => $marker,
					'extension' => $extensionKey,
					'templateName' => $templateKey,
					'subject' => $subject
				];
			}

	/**
	 * Checks if a template is blacklisted for a given siterootId
	 *
	 * @param string $extensionKey
	 * @param string $templateName
	 * @param int $siteRootId
	 * @return boolean
	 * @throws \InvalidArgumentException
	 * @throws \BadFunctionCallException
	 * @throws \TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException
	 */
	public static function isTemplateBlacklisted($extensionKey, $templateName, $siteRootId): bool {
		$whitelistedTemplates = BackendService::getWhitelistedTemplates($siteRootId);

		if ($whitelistedTemplates[$extensionKey]) {
			return $whitelistedTemplates[$extensionKey][$templateName] ? FALSE : TRUE;
		}

		return TRUE;
	}