pluswerk/mail_logger

View on GitHub
Classes/Domain/Model/TemplateBasedMailMessage.php

Summary

Maintainability
B
4 hrs
Test Coverage
<?php

declare(strict_types=1);

namespace Pluswerk\MailLogger\Domain\Model;

use Exception;
use Pluswerk\MailLogger\Utility\ConfigurationUtility;
use Symfony\Component\Mime\Crypto\DkimSigner;
use TYPO3\CMS\Core\Mail\MailMessage;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Fluid\View\StandaloneView;

class TemplateBasedMailMessage extends MailMessage
{
    protected MailTemplate $mailTemplate;

    /**
     * @var array<array-key, mixed>
     */
    protected array $viewParameters = [];

    protected string $typoScriptKey = '';

    public function __construct(
        protected StandaloneView $messageView,
        protected StandaloneView $subjectView
    ) {
        parent::__construct();
    }

    public function getMailTemplate(): MailTemplate
    {
        return $this->mailTemplate;
    }

    /**
     * @param array<array-key, mixed> $viewParameters This is necessary if you use Fluid for your mail fields
     * @return $this
     */
    public function setMailTemplate(MailTemplate $mailTemplate, bool $assignMailTemplate = true, array $viewParameters = []): self
    {
        $this->mailTemplate = $mailTemplate;
        if ($viewParameters !== []) {
            $this->setViewParameters($viewParameters);
        }

        if ($assignMailTemplate) {
            $this->assignMailTemplate();
        }

        return $this;
    }

    public function getMessageView(): StandaloneView
    {
        return $this->messageView;
    }

    public function setMessageView(StandaloneView $messageView): self
    {
        $this->messageView = $messageView;
        return $this;
    }

    public function getSubjectView(): StandaloneView
    {
        return $this->subjectView;
    }

    public function setSubjectView(StandaloneView $subjectView): self
    {
        $this->subjectView = $subjectView;
        return $this;
    }

    /**
     * @return array<array-key, mixed>
     */
    public function getViewParameters(): array
    {
        return $this->viewParameters;
    }

    /**
     * @param array<array-key, mixed> $viewParameters
     */
    public function setViewParameters(array $viewParameters): self
    {
        $this->viewParameters = $viewParameters;
        return $this;
    }

    public function assignMailTemplate(): void
    {
        $this->assignMailTemplateValues($this->mailTemplate->_getProperties());
    }

    public function assignDefaultsFromTypoScript(string $typoScriptKey, string $templatePathKey): void
    {
        if ($typoScriptKey !== '') {
            $this->setTypoScriptKey($typoScriptKey);
            $settings = ConfigurationUtility::getCurrentModuleConfiguration('settings');
            $concreteSettings = $settings['mailTemplates'][$typoScriptKey];
            $concreteSettings['templatePaths'] = $settings['templateOverrides'][$templatePathKey ?: 'default'];
            $concreteSettings['defaultTemplatePaths'] = $settings['templateOverrides']['default'];
            $this->assignMailTemplateValues($concreteSettings);
        }
    }

    public function send(): bool
    {
        try {
            $body = $this->renderView($this->messageView);
            $this->html($body);
        } catch (Exception $exception) {
            throw new Exception('Error while setting mail body template: ' . $exception->getMessage(), 1449133006, $exception);
        }

        try {
            $this->setSubject($this->renderView($this->subjectView));
        } catch (Exception $exception) {
            throw new Exception('Error while setting mail subject template: ' . $exception->getMessage(), 1449133007, $exception);
        }

        $this->signMail();
        return parent::send();
    }

    private function signMail(): void
    {
        $settings = ConfigurationUtility::getCurrentModuleConfiguration('settings');
        if (isset($settings['dkim']) && isset($settings['dkim'][$this->mailTemplate->getDkimKey()])) {
            $conf = $settings['dkim'][$this->mailTemplate->getDkimKey()];

            // needs testing:
            $signer = new DkimSigner($this->formPrivateKey($conf['key']), $conf['domain'], $conf['selector'], [
                'headers_to_ignore' => [
                    'Return-Path',
                ],
            ]);
            $signedMail = $signer->sign($this);
            $this->setHeaders($signedMail->getHeaders());
            $this->setBody($signedMail->getBody());
        }
    }


    private function formPrivateKey(string $key): string
    {
        $begin = '-----BEGIN RSA PRIVATE KEY-----';
        $ending = '-----END RSA PRIVATE KEY-----';
        return $begin . PHP_EOL . trim($key) . PHP_EOL . $ending;
    }

    /**
     * @param array<array-key, mixed> $values
     */
    private function assignMailTemplateValues(array $values): void
    {
        if (!empty($values['typoScriptKey'])) {
            $this->assignDefaultsFromTypoScript($values['typoScriptKey'], $this->mailTemplate->getTemplatePathKey());
        }

        // set From
        $fromAddress = $this->getRenderedValue($values['mailFromAddress'] ?? '');
        if ($fromAddress !== '') {
            $fromName = $this->getRenderedValue($values['mailFromName'] ?? '');
            $fromName = $fromName ?: $fromAddress;
            $this->setFrom($this->cleanUpMailAddressesAndNames([$fromAddress => $fromName]));
        }

        // set To
        $toAddresses = GeneralUtility::trimExplode(',', $this->getRenderedValue($values['mailToAddresses'] ?? ''), true);
        if ($toAddresses !== []) {
            $toNames = GeneralUtility::trimExplode(',', $this->getRenderedValue($values['mailToNames'] ?? ''));
            $combinedTo = [];
            foreach ($toAddresses as $key => $toAddress) {
                $combinedTo[$toAddress] = empty($toNames[$key]) ? '' : $toNames[$key];
            }

            $this->setTo($this->cleanUpMailAddressesAndNames($combinedTo));
        }

        // set CC and BCC
        if (!empty($values['mailCopyAddresses'])) {
            $this->setCc(GeneralUtility::trimExplode(',', $this->getRenderedValue($values['mailCopyAddresses']), true));
        }

        if (!empty($values['mailBlindCopyAddresses'])) {
            $this->setBcc(GeneralUtility::trimExplode(',', $this->getRenderedValue($values['mailBlindCopyAddresses']), true));
        }

        $this->assignMailTemplatePaths($values);

        // set subject and message
        if (!empty($values['subject'])) {
            $this->subjectView->setTemplateSource($values['subject']);
        }

        if (!empty($values['message'])) {
            $mailView = GeneralUtility::makeInstance(StandaloneView::class);
            $mailView->setTemplateSource($values['message']);
            $mailView->assignMultiple($this->viewParameters);
            $this->messageView->assign('message', $mailView->render());
        }

        $this->messageView->assign('mailTemplate', $this->mailTemplate);
    }

    /**
     * @param array<string, string|null> $addressesAndNames
     * @return array<array-key, string>
     */
    private function cleanUpMailAddressesAndNames(array $addressesAndNames): array
    {
        foreach ($addressesAndNames as $mailAddress => $name) {
            if (!$name && is_string($mailAddress)) {
                unset($addressesAndNames[$mailAddress]);
                $addressesAndNames[] = $mailAddress;
            }
        }

        return $addressesAndNames;
    }

    /**
     * Short method to render a standalone fluid template
     */
    private function getRenderedValue(string $value): string
    {
        // Check if the string is not empty and contains any Fluid stuff
        if ($value !== '' && (str_contains($value, '{') || str_contains($value, '<'))) {
            /** @var StandaloneView $valueView */
            $valueView = GeneralUtility::makeInstance(StandaloneView::class);
            $valueView->setTemplateSource($value);
            $value = $this->renderView($valueView);
        }

        return $value;
    }

    /**
     * Render view with all parameters
     */
    private function renderView(StandaloneView $view): string
    {
        return $view->assignMultiple($this->viewParameters)->render();
    }

    /**
     * @param array<array-key, mixed> $values
     */
    private function assignMailTemplatePaths(array $values): void
    {
        if (!$this->messageView->getTemplatePathAndFilename()) {
            $this->messageView->setTemplatePathAndFilename(
                GeneralUtility::getFileAbsFileName($values['templatePaths']['templatePath'] ?: $values['defaultTemplatePaths']['templatePath'])
            );

            $this->messageView->setPartialRootPaths(
                array_filter(
                    [
                        $values['defaultTemplatePaths']['partialRootPaths'],
                        $values['templatePaths']['partialRootPaths'] ?? [],
                    ]
                )
            );
            $this->messageView->setLayoutRootPaths(
                array_filter(
                    [
                        $values['defaultTemplatePaths']['layoutRootPaths'],
                        $values['templatePaths']['layoutRootPaths'] ?? [],
                    ]
                )
            );

            if (isset($values['templatePaths']['settings']) && !empty($values['templatePaths']['settings'])) {
                $this->messageView->assignMultiple(['settings' => $values['templatePaths']['settings']]);
            }
        }
    }

    public function getTypoScriptKey(): string
    {
        return $this->typoScriptKey;
    }

    private function setTypoScriptKey(string $typoScriptKey): void
    {
        $this->typoScriptKey = $typoScriptKey;
    }
}