<?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 DateTime; use SGalinski\SgMail\Domain\Model\Layout; use SGalinski\SgMail\Domain\Model\Mail; use SGalinski\SgMail\Domain\Model\Template; use SGalinski\SgMail\Domain\Repository\LayoutRepository; use SGalinski\SgMail\Domain\Repository\MailRepository; use SGalinski\SgMail\Domain\Repository\TemplateRepository; use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Mail\MailMessage; use TYPO3\CMS\Core\Resource\File; use TYPO3\CMS\Core\Resource\ResourceFactory; use TYPO3\CMS\Core\Routing\SiteMatcher; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\VersionNumberUtility; use TYPO3\CMS\Extbase\Domain\Model\FileReference; use TYPO3\CMS\Extbase\Object\ObjectManager; use TYPO3\CMS\Extbase\Utility\LocalizationUtility; use TYPO3\CMS\Fluid\View\StandaloneView; use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController; /** * MailTemplateService */ class MailTemplateService { const MARKER_TYPE_STRING = 'String'; const MARKER_TYPE_ARRAY = 'Array'; const MARKER_TYPE_OBJECT = 'Object'; const MARKER_TYPE_FILE = 'File'; const DEFAULT_LANGUAGE = 'default'; const DEFAULT_TEMPLATE_PATH = 'Resources/Private/Templates/SgMail/'; const CACHE_NAME = 'sg_mail_registerArrayCache'; const CACHE_LIFETIME_IN_SECONDS = 86400; const REGISTER_FILE = 'Register.php'; const CONFIG_PATH = 'Configuration/MailTemplates'; /** * @var array */ private static $templateObjectCache = []; /** * @var array */ private static $mailObjectCache = []; /** * @var \SGalinski\SgMail\Domain\Repository\TemplateRepository */ protected $templateRepository; /** * @var \SGalinski\SgMail\Domain\Repository\LayoutRepository */ protected $layoutRepository; /** * @var \TYPO3\CMS\Extbase\Object\ObjectManager * @deprecated Not used since TYPO3 10 */ protected $objectManager; /** * @var \TYPO3\CMS\Core\Resource\ResourceFactory */ protected $resourceFactory; /** * @var string $toAddresses */ private $toAddresses = ''; /** * @var string $fromAddress */ private $fromAddress = ''; /** * @var string $ccAddresses */ private $ccAddresses; /** * @var string $replyToAddress */ private $replyToAddress = ''; /** * @var string $language */ private $language = 'default'; /** * @var boolean $ignoreMailQueue */ private $ignoreMailQueue = TRUE; /** * @var \TYPO3\CMS\Core\Mail\MailMessage $mailMessage */ private $mailMessage; /** * @var string $templateName */ private $templateName; /** * @var string $subject */ private $subject; /** * @var string $overwrittenEmailBody */ private $overwrittenEmailBody = ''; /** * @var string $extensionKey */ private $extensionKey; /** * @var array $markers */ private $markers; /** * @var array $markerLabels */ private $markerLabels; /** * @var string $bccAddresses */ private $bccAddresses; /** * @var int */ private $priority = Mail::PRIORITY_LOWEST; /** * @var int */ private $pid; /** * @var string */ private $fromName = ''; /** * @var string */ private $mailBodyToSend; /** * @var string */ private $subjectToSend; /** * @var string */ private $defaultFromAddress; /** * @var string */ private $defaultFromName; /** * MailTemplateService constructor. * * @param string $templateName * @param string $extensionKey * @param array $markers * @param array $markerLabels * @throws \InvalidArgumentException */ public function __construct($templateName = '', $extensionKey = '', $markers = [], $markerLabels = []) { $this->templateName = $templateName; $this->extensionKey = $extensionKey; $this->markers = $markers; $this->markerLabels = $markerLabels; $this->resourceFactory = GeneralUtility::makeInstance(ResourceFactory::class); $this->mailMessage = GeneralUtility::makeInstance(MailMessage::class); if (version_compare(VersionNumberUtility::getCurrentTypo3Version(), '10.4.0')) { $this->objectManager = GeneralUtility::makeInstance(ObjectManager::class); /** @var TypoScriptSettingsService $typoScriptSettingsService */ $typoScriptSettingsService = $this->objectManager->get(TypoScriptSettingsService::class); /** @var TemplateRepository templateRepository */ $this->templateRepository = $this->objectManager->get(TemplateRepository::class); /** @var LayoutRepository layoutRepository */ $this->layoutRepository = $this->objectManager->get(LayoutRepository::class); } else { $typoScriptSettingsService = GeneralUtility::makeInstance(TypoScriptSettingsService::class); $this->templateRepository = GeneralUtility::makeInstance(TemplateRepository::class); $this->layoutRepository = GeneralUtility::makeInstance(LayoutRepository::class); } $tsSettings = $typoScriptSettingsService->getSettings(0, 'tx_sgmail'); // 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']; $this->defaultFromAddress = $GLOBALS['TYPO3_CONF_VARS']['MAIL']['defaultMailFromAddress']; } else { if (!\filter_var($tsSettings['mail']['default']['from'], FILTER_VALIDATE_EMAIL)) { $this->fromAddress = 'noreply@example.org'; $this->defaultFromAddress = 'noreply@example.org'; } else { $this->fromAddress = $tsSettings['mail']['default']['from']; $this->defaultFromAddress = $tsSettings['mail']['default']['from']; } } if ($GLOBALS['TYPO3_CONF_VARS']['MAIL']['defaultMailFromName']) { $this->fromName = $GLOBALS['TYPO3_CONF_VARS']['MAIL']['defaultMailFromName']; $this->defaultFromName = $GLOBALS['TYPO3_CONF_VARS']['MAIL']['defaultMailFromName']; } $this->mailMessage->setFrom($this->fromAddress, $this->fromName); $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) { $this->mailMessage->setBcc($this->bccAddresses); } if (\count($this->ccAddresses) > 0) { $this->mailMessage->setCc($this->ccAddresses); } } /** * 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; case self::MARKER_TYPE_FILE: LocalizationUtility::translate('backend.marker.type.file', 'sg_mail'); break; default: LocalizationUtility::translate('backend.marker.type.mixed', 'sg_mail'); } } /** * 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'): array { $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 string $language * @return MailTemplateService */ public function setLanguage($language): MailTemplateService { $this->language = $language; return $this; } /** * @param string $templateName * @return MailTemplateService */ public function setTemplateName($templateName): MailTemplateService { $this->templateName = $templateName; return $this; } /** * @param string $extensionKey * @return MailTemplateService */ public function setExtensionKey($extensionKey): MailTemplateService { $this->extensionKey = $extensionKey; return $this; } /** * @param array $markers * @return MailTemplateService */ public function addMarkers(array $markers): MailTemplateService { $this->setMarkers(\array_merge($this->markers, $markers)); return $this; } /** * @param array $markers * @return MailTemplateService */ public function setMarkers(array $markers): MailTemplateService { $this->markers = $markers; foreach ($markers as $key => $currentMarker) { if (!\is_array($currentMarker) || !isset($currentMarker['markerLabel'])) { continue; } $this->markerLabels[$key] = $currentMarker['markerLabel']; } return $this; } /** * @param int $priority * @return MailTemplateService */ public function setPriority($priority): MailTemplateService { $this->priority = $priority; return $this; } /** * Adds a file resource as attachment * * @param FileReference $fileReference * @param File $file * * @return MailTemplateService * @throws \TYPO3\CMS\Core\Resource\Exception\ResourceDoesNotExistException */ public function addFileResourceAttachment( FileReference $fileReference = NULL, File $file = NULL ): MailTemplateService { if (!$file) { if (!$fileReference) { return $this; } $originalResource = $fileReference->getOriginalResource(); if (!$originalResource) { return $this; } $file = $originalResource->getOriginalFile(); if (!$file) { return $this; } } $coreFileReferenceMailFile = $this->resourceFactory->createFileReferenceObject( [ 'uid_local' => $file->getUid(), 'table_local' => 'sys_file', 'uid' => uniqid('NEW_MAIL', TRUE) ] ); $newFileReference = GeneralUtility::makeInstance(FileReference::class); $newFileReference->setOriginalResource($coreFileReferenceMailFile); $this->markers[] = $newFileReference; return $this; } /** * @return MailMessage */ public function getMailMessage(): MailMessage { return $this->mailMessage; } /** * set the page id from which this was called * * @param int $pid * @return MailTemplateService */ public function setPid($pid): MailTemplateService { $this->pid = (int) $pid; return $this; } /** * @return string */ public function getSubject(): string { return $this->subject; } /** * @param string $subject */ public function setSubject(string $subject) { $this->subject = $subject; } /** * Set preview markers for the template editor * * @throws \TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException */ public function setPreviewMarkers() { $previewMarker = []; // get default template content from register array $registerService = GeneralUtility::makeInstance(RegisterService::class); /** @var array $markerArray */ $markerArray = $registerService->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['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]; } } /** @noinspection SlowArrayOperationsInLoopInspection */ $previewMarker = \array_merge_recursive($previewMarker, $temporaryMarkerArray); } $this->setMarkers($previewMarker); } /** * Send the Email * * @param bool $isPreview * @param Template|null $template * @return bool email was sent or added to mail queue successfully? * @throws \TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException * @throws \TYPO3\CMS\Core\Resource\Exception\ResourceDoesNotExistException * @throws \TYPO3\CMS\Extbase\Persistence\Exception\IllegalObjectTypeException * @throws \TYPO3\CMS\Extbase\Persistence\Exception\UnknownObjectException */ public function sendEmail($isPreview = FALSE, $isNewsletter = FALSE): bool { if ($isPreview) { //TODO: remove this from here $this->setIgnoreMailQueue(TRUE); } $success = FALSE; // Get page ID // TODO: this doesn't belong here. The API user needs to provide the UID $siteRootId = $this->getSiteRootId(); try { $template = $this->getTemplate($siteRootId); } catch (\Exception $e) { return FALSE; } $registerService = GeneralUtility::makeInstance(RegisterService::class); // Load the values from the template if they are set if (!$isNewsletter && $template !== NULL) { $this->loadTemplateValues($template); } // get default template content from register array $defaultTemplateContent = $this->getDefaultTemplateContent($template, $registerService); // set the ToAddress if there are no placeholders in it // TODO: does this belong here? if ($template !== NULL && \filter_var($template->getToAddress(), FILTER_VALIDATE_EMAIL)) { $this->setToAddresses(\trim($template->getToAddress())); } $this->parseValuesForMail($template, $registerService, $defaultTemplateContent, $siteRootId); $mail = $this->addMailToMailQueue( $this->extensionKey, $this->templateName, $this->getSubjectToSend(), $this->getMailBodyToSend(), $this->priority, 0, 0, $this->language, $siteRootId ); self::$mailObjectCache[$mail->getUid()] = $mail; // add it to cache to avoid extra DB queries if ($this->ignoreMailQueue) { $success = $this->sendMailFromQueue($mail->getUid()); } //TODO: this can be avoided if the sending logic is decoupled from this function if ($isPreview) { if (version_compare(VersionNumberUtility::getCurrentTypo3Version(), '10.4.0', '<')) { /** @var MailRepository $mailRepository */ $mailRepository = $this->objectManager->get(MailRepository::class); } else { $mailRepository = GeneralUtility::makeInstance(MailRepository::class); } $mailRepository->remove($mail); $mailRepository->persist(); } return $success; } /** * @param boolean $ignoreMailQueue * @return MailTemplateService */ public function setIgnoreMailQueue($ignoreMailQueue): MailTemplateService { $this->ignoreMailQueue = $ignoreMailQueue; return $this; } /** * Get site root ID * * @return int */ public function getSiteRootId(): int { $pageUid = $this->getPageUid(); return BackendService::getSiteRoot($pageUid); } /** * Gets the current PageUid * * @return int */ protected function getPageUid(): int { if (TYPO3_MODE === 'FE') { $pageUid = (int) $GLOBALS['TSFE']->id; } else { $pageUid = (int) GeneralUtility::_GP('id'); } if ($this->pid) { $pageUid = $this->pid; } if ($pageUid === 0) { $pageUid = $this->getAnyRootPageUid(); } return $pageUid; } /** * Gets the page uid of any root page in the page tree * * @return int */ private function getAnyRootPageUid() { $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable( 'pages' ); $rootPageRows = $queryBuilder->select('*') ->from('pages') ->where( $queryBuilder->expr()->eq( 'is_siteroot', 1 ) ) ->andWhere( $queryBuilder->expr()->eq( 'hidden', 0 ) ) ->execute()->fetchAll(); if ($rootPageRows && \count($rootPageRows)) { $pageUid = (int) $rootPageRows[0]['uid']; } return $pageUid; } /** * Get the template object * * @param int $siteRootId * @return null|object|Template|FALSE * @throws \Exception */ private function getTemplate($siteRootId) { $isTemplateBlacklisted = self::isTemplateBlacklisted($this->extensionKey, $this->templateName, $siteRootId); if ($isTemplateBlacklisted) { throw new \Exception('The template is blacklisted'); } $templateHash = $this->getTemplateHash($this->extensionKey, $this->templateName, $siteRootId, $this->language); if (isset(self::$templateObjectCache[$templateHash]) && self::$templateObjectCache[$templateHash] instanceof Template) { return self::$templateObjectCache[$templateHash]; } /** @var Template $template */ $template = $this->templateRepository->findOneByTemplate( $this->extensionKey, $this->templateName, $this->language, $siteRootId ); if ($template === NULL) { $template = $this->templateRepository->findOneByTemplate( $this->extensionKey, $this->templateName, 'default', $siteRootId ); } self::$templateObjectCache[$templateHash] = $template; return $template; } /** * Checks if a template is blacklisted for a given siteroot id * * @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 { $nonBlacklistedTemplates = BackendService::getNonBlacklistedTemplates($siteRootId); if ($nonBlacklistedTemplates[$extensionKey]) { return $nonBlacklistedTemplates[$extensionKey][$templateName] ? FALSE : TRUE; } return TRUE; } /** * Get the hash for the object cache * * @param $extensionKey * @param $templateName * @param $siteRootId * @param $languageId * @return string */ private function getTemplateHash($extensionKey, $templateName, $siteRootId, $languageId) { return md5($extensionKey . '_' . $templateName . '_' . $siteRootId . '_' . $languageId); } /** * use all values from the given template * * @param Template $template */ private function loadTemplateValues(Template $template) { $fromName = \trim($template->getFromName()); if ($fromName === '') { $fromName = $this->fromName; } if ($fromName === '' && $GLOBALS['TYPO3_CONF_VARS']['MAIL']['defaultMailFromName']) { $fromName = $GLOBALS['TYPO3_CONF_VARS']['MAIL']['defaultMailFromName']; } $fromMail = $this->getValidFromMail(\trim($template->getFromMail())); // 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 */ private function getValidFromMail($fromMail): string { $fromMail = \trim($fromMail); if (!\filter_var($fromMail, FILTER_VALIDATE_EMAIL)) { $fromMail = $this->fromAddress; } 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'; } } return $fromMail; } /** * @param string $fromAddress * @param string $fromName * @return MailTemplateService */ public function setFromAddress($fromAddress, $fromName = ''): MailTemplateService { if ($fromAddress) { $this->fromAddress = $fromAddress; $this->mailMessage->setFrom($fromAddress, $fromName); } return $this; } /** * @param string $ccAddresses * @return MailTemplateService */ public function setCcAddresses($ccAddresses): MailTemplateService { if ($ccAddresses) { $this->ccAddresses = $ccAddresses; $this->mailMessage->setCc(GeneralUtility::trimExplode(',', $this->ccAddresses)); } return $this; } /** * @param string $bccAddresses * @return MailTemplateService */ public function setBccAddresses($bccAddresses): MailTemplateService { if ($bccAddresses) { $this->bccAddresses = $bccAddresses; $this->mailMessage->setBcc(GeneralUtility::trimExplode(',', $this->bccAddresses)); } return $this; } /** * @param string $replyToAddress * @return MailTemplateService */ public function setReplyToAddress($replyToAddress): MailTemplateService { if ($replyToAddress) { $this->replyToAddress = $replyToAddress; $this->mailMessage->setReplyTo($replyToAddress); } return $this; } /** * @param string $fromName */ public function setFromName($fromName) { $this->fromName = $fromName; } /** * Get the default content for this template * * @param Template|null $template * @param RegisterService $registerService * @return bool|false|string * @throws \TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException */ protected function getDefaultTemplateContent($template, $registerService) { $defaultTemplateContent = $registerService->getRegisterArray()[$this->extensionKey][$this->templateName]['templateContent']; // If there is no template for this language, use the default template if ($template === NULL && $defaultTemplateContent === NULL) { $templatePath = $registerService->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 { // use configured default html template if (version_compare(VersionNumberUtility::getCurrentTypo3Version(), '10.4.0', '<')) { /** @var TypoScriptSettingsService $typoScriptSettingsService */ $typoScriptSettingsService = $this->objectManager->get(TypoScriptSettingsService::class); } else { $typoScriptSettingsService = GeneralUtility::makeInstance(TypoScriptSettingsService::class); } $tsSettings = $typoScriptSettingsService->getSettings(0, 'tx_sgmail'); $defaultTemplateFile = GeneralUtility::getFileAbsFileName( $tsSettings['mail']['defaultHtmlTemplate'] ); if (\file_exists($defaultTemplateFile)) { $defaultTemplateContent = \file_get_contents($defaultTemplateFile); } else { return FALSE; } } } return $defaultTemplateContent; } /** * @param string $toAddresses * @return MailTemplateService */ public function setToAddresses($toAddresses): MailTemplateService { $normalizedToAddresses = trim(preg_replace('~\x{00a0}~iu', ' ', $toAddresses)); $this->toAddresses = $normalizedToAddresses; $addressesArray = GeneralUtility::trimExplode(',', $normalizedToAddresses, TRUE); if (\count($addressesArray) > 1) { $normalizedToAddresses = $addressesArray; } $this->mailMessage->setTo($normalizedToAddresses); return $this; } /** * Sets the values to send the mail with from the template or register service * * @param Template|null $template * @param RegisterService $registerService * @param string $defaultTemplateContent * @param int $siteRootId * @param boolean $isNewsletter * @throws \TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException */ protected function parseValuesForMail( $template, RegisterService $registerService, $defaultTemplateContent, $siteRootId ) { if (version_compare(VersionNumberUtility::getCurrentTypo3Version(), '10.4.0', '<')) { /** @var StandaloneView $emailView */ $emailView = $this->objectManager->get(StandaloneView::class); } else { $emailView = GeneralUtility::makeInstance(StandaloneView::class); } $emailView->assignMultiple($this->markers); $emailView->assign('all_fields', $this->getAllMarker($this->markers)); //TODO: make this as the lines below the next block $overwrittenEmailBody = $this->getOverwrittenEmailBody(); $overwrittenSubject = ''; if ($this->subject !== '' && $this->subject !== NULL) { $overwrittenSubject = $this->subject; } // parse markers if ($template !== NULL) { $subject = $this->parseMarkers( trim(empty($overwrittenSubject) ? $template->getSubject() : $overwrittenSubject), $emailView ); $layoutId = $template->getLayout(); $templateContent = $template->getContent(); $this->setSubjectToSend($subject); } else { $subject = $registerService->getRegisterArray()[$this->extensionKey][$this->templateName]['subject']; if (\is_array($subject)) { $subject = \trim( $registerService->getRegisterArray() [$this->extensionKey][$this->templateName]['subject'][$this->language] ); } $subject = $this->parseMarkers( (empty($overwrittenSubject) ? $subject : $overwrittenSubject), $emailView ); $layoutId = 0; $templateContent = $defaultTemplateContent; } $this->setSubjectToSend($subject); $this->mailMessage->setSubject($subject); // Parse the markers if ($this->fromName) { $this->setFromName($this->parseMarkers($this->fromName, $emailView)); } if ($this->fromAddress) { $this->setFromAddress($this->parseMarkers($this->fromAddress, $emailView)); } if ($this->replyToAddress) { $this->setReplyToAddress($this->parseMarkers($this->replyToAddress, $emailView)); } if ($this->ccAddresses) { $this->setCcAddresses($this->parseMarkers($this->ccAddresses, $emailView)); } if ($this->bccAddresses) { $this->setBccAddresses($this->parseMarkers($this->bccAddresses, $emailView)); } if ($this->toAddresses) { $this->setToAddresses($this->parseMarkers($this->toAddresses, $emailView)); } // reset template source back to default $emailView->setTemplateSource( empty($overwrittenEmailBody) ? $templateContent : $overwrittenEmailBody ); // insert <br> tags, but replace every instance of three or more successive breaks with just two. $emailBody = $emailView->render(); $emailBody = \nl2br($emailBody); $emailBody = \preg_replace('/(<br[\s]?[\/]?>[\s]*){3,}/', '<br><br>', $emailBody); $layout = $this->getLayoutSource($layoutId, $siteRootId); $emailHTMLHead = ''; if ($layout) { $emailHTMLHead = $layout->getHeadContent(); $emailBody = \str_replace('###CONTENT###', $emailBody, $layout->getContent()); } $this->mailBodyToSend = '<html><head>' . $emailHTMLHead . '</head><body>' . $emailBody . '</body></html>'; } /** * Get a single variable containing a list of all markers * * @param array $markers * @return string */ private function getAllMarker(array $markers): string { $allMarker = ''; foreach ($markers as $key => $value) { if (\array_key_exists($key, $this->markerLabels) && $this->markerLabels[$key] !== NULL) { $key = $this->markerLabels[$key]; } if (\is_string($value)) { $allMarker .= $key . ': ' . $value . PHP_EOL; } elseif (\is_array($value)) { foreach ($value as $innerKey => $innerValue) { $allMarker .= $key . '.' . $innerKey . ': ' . $innerValue . PHP_EOL; } } elseif (\is_bool($value)) { $valueAsString = $value ? 'true' : 'false'; $allMarker .= $key . ': ' . $valueAsString . PHP_EOL; } elseif (\is_object($value)) { if (\method_exists($value, '__toString')) { $allMarker .= $key . ': ' . $value->__toString() . PHP_EOL; } } } return $allMarker; } /** * @return string */ public function getOverwrittenEmailBody(): string { return $this->overwrittenEmailBody; } /** * @param string $overwrittenEmailBody */ public function setOverwrittenEmailBody(string $overwrittenEmailBody) { $this->overwrittenEmailBody = $overwrittenEmailBody; } /** * 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 mixed */ protected function parseMarkers($text, $emailView) { $text = (string) $text; if (strpos($text, '{') !== FALSE) { $emailView->setTemplateSource($text); return $emailView->render(); } return $text; } /** * Returns the layout. * * @param int $layoutUid * @param int $siteRootId * @return Layout|NULL */ private function getLayoutSource(int $layoutUid, int $siteRootId) { $languageUid = 0; if ($this->language !== self::DEFAULT_LANGUAGE) { $languageUid = (int) array_search($this->language, $this->getAvailableLanguages(), TRUE); } return $this->layoutRepository->findByUidOrDefault($layoutUid, $siteRootId, $languageUid); } /** * Returns the list of available translation languages * * @return array */ private function getAvailableLanguages(): array { $out = [0 => '']; try { $site = GeneralUtility::makeInstance(SiteMatcher::class)->matchByPageId(0); } catch (\Exception $exception) { return [0 => '']; } $availableLanguages = $site->getLanguages(); $out = []; foreach ($availableLanguages as $language) { $languageId = $language->getLanguageId(); if ($languageId < 0) { continue; } $out[$language->getLanguageId()] = strtolower($language->getTwoLetterIsoCode()); } return $out; } /** * Adds a new mail to the mail queue. * * @param string $extensionKey * @param string $templateName * @param string $subject * @param string $emailBody * @param int $sendingTime * @param int $priority * @param int $lastSendingTime * @param string $language * @param int $pid * @return Mail * @throws \InvalidArgumentException * @throws \BadFunctionCallException * @throws \TYPO3\CMS\Extbase\Persistence\Exception\IllegalObjectTypeException * @throws \TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException */ private function addMailToMailQueue( $extensionKey, $templateName, $subject, $emailBody, $priority, $sendingTime = 0, $lastSendingTime = 0, $language = self::DEFAULT_LANGUAGE, $pid = 0 ): Mail { $mail = new Mail(); $mail->setPid($pid); $mail->setExtensionKey($extensionKey); $mail->setTemplateName($templateName); $mail->setLanguage($language); $mail->setBlacklisted(self::isTemplateBlacklisted($extensionKey, $templateName, $pid)); $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->setSendingTime($sendingTime); $mail->setLastSendingTime($lastSendingTime); $mail->setReplyTo($this->replyToAddress); foreach ($this->markers as $marker) { if ($marker instanceof FileReference) { // 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(); if ($originalResource instanceof \TYPO3\CMS\Core\Resource\FileReference) { $coreFileReferenceMailFile = $this->resourceFactory->createFileReferenceObject( [ 'uid_local' => $originalResource->getOriginalFile()->getUid(), 'table_local' => 'sys_file', 'uid' => uniqid('NEW_MAIL', TRUE) ] ); $newFileReference = new FileReference(); $newFileReference->setOriginalResource($coreFileReferenceMailFile); $mail->addAttachment($newFileReference); } } } if (version_compare(VersionNumberUtility::getCurrentTypo3Version(), '10.4.0', '<')) { /** @var MailRepository $mailRepository */ $mailRepository = $this->objectManager->get(MailRepository::class); } else { $mailRepository = GeneralUtility::makeInstance(MailRepository::class); } $mailRepository->add($mail); $mailRepository->persist(); return $mail; } /** * @return string */ public function getSubjectToSend(): string { return $this->subjectToSend; } /** * @param string $subjectToSend */ public function setSubjectToSend(string $subjectToSend) { $this->subjectToSend = $subjectToSend; } /** * @return string|string[]|null */ public function getMailBodyToSend() { return $this->mailBodyToSend; } /** * @param string|string[]|null $mailBodyToSend */ public function setMailBodyToSend($mailBodyToSend) { $this->mailBodyToSend = $mailBodyToSend; } /** * Send a Mail from the queue, identified by its id * * @param int $uid * @return bool|NULL * @throws \TYPO3\CMS\Core\Resource\Exception\ResourceDoesNotExistException * @throws \TYPO3\CMS\Extbase\Persistence\Exception\IllegalObjectTypeException * @throws \TYPO3\CMS\Extbase\Persistence\Exception\UnknownObjectException */ public function sendMailFromQueue($uid): bool { if (version_compare(VersionNumberUtility::getCurrentTypo3Version(), '10.4.0', '<')) { /** @var MailRepository $mailRepository */ $mailRepository = $this->objectManager->get(MailRepository::class); } else { $mailRepository = GeneralUtility::makeInstance(MailRepository::class); } /** @var Mail $mailToSend */ if (!isset(self::$mailObjectCache[$uid]) || !self::$mailObjectCache[$uid] instanceof Mail) { $mailToSend = $this->getMailObjectByUid($uid); if ($mailToSend === FALSE) { return FALSE; } } else { $mailToSend = self::$mailObjectCache[$uid]; } $plaintextService = GeneralUtility::makeInstance(PlaintextService::class); $plaintextBody = $plaintextService->makePlain($mailToSend->getMailBody()); if (version_compare(VersionNumberUtility::getCurrentTypo3Version(), '10.4.0', '<')) { $this->mailMessage->setBody($mailToSend->getMailBody(), 'text/html'); $this->mailMessage->addPart($plaintextBody, 'text/plain'); } else { $this->mailMessage->html($mailToSend->getMailBody()); $this->mailMessage->text($plaintextBody); } $toAddresses = \trim($mailToSend->getToAddress()); $addressesArray = GeneralUtility::trimExplode(',', $toAddresses, TRUE); if (\count($addressesArray) > 1) { $toAddresses = $addressesArray; } $this->mailMessage->setTo($toAddresses); $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()); } $attachments = $mailToSend->getAttachments(); if ($attachments->count() > 0) { foreach ($attachments as $attachment) { /** * @var FileReference $attachment */ $file = $attachment->getOriginalResource()->getOriginalFile(); if (version_compare(VersionNumberUtility::getCurrentTypo3Version(), '10.4.0', '<')) { $this->mailMessage->attach( \Swift_Attachment::newInstance($file->getContents(), $file->getName(), $file->getMimeType()) ); } else { $this->mailMessage->attach( $file->getContents(), $file->getName(), $file->getMimeType() ); } } } $dateTime = new DateTime(); if ((int) $mailToSend->getSendingTime() === 0) { $mailToSend->setSendingTime($dateTime->getTimestamp()); } $mailToSend->setLastSendingTime($dateTime->getTimestamp()); $success = $this->mailMessage->send(); if ($success) { $mailRepository->update($mailToSend); $mailRepository->persist(); } else { $this->mailMessage->getFailedRecipients(); } 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 */ public function getMailObjectByUid($uid) { if (version_compare(VersionNumberUtility::getCurrentTypo3Version(), '10.4.0', '<')) { /** @var MailRepository $mailRepository */ $mailRepository = $this->objectManager->get(MailRepository::class); } else { $mailRepository = GeneralUtility::makeInstance(MailRepository::class); } $mailObject = $mailRepository->findOneByUid($uid); if (!$mailObject || $mailObject->getBlacklisted()) { return FALSE; } return $mailObject; } }