1<?php
2
3/*
4 * This file is part of the TYPO3 CMS project.
5 *
6 * It is free software; you can redistribute it and/or modify it under
7 * the terms of the GNU General Public License, either version 2
8 * of the License, or any later version.
9 *
10 * For the full copyright and license information, please read the
11 * LICENSE.txt file that was distributed with this source code.
12 *
13 * The TYPO3 project - inspiring people to share!
14 */
15
16namespace TYPO3\CMS\Core\Mail;
17
18use Psr\EventDispatcher\EventDispatcherInterface;
19use Symfony\Component\Mailer\Envelope;
20use Symfony\Component\Mailer\MailerInterface;
21use Symfony\Component\Mailer\SentMessage;
22use Symfony\Component\Mailer\Transport\TransportInterface;
23use Symfony\Component\Mime\Address;
24use Symfony\Component\Mime\Email;
25use Symfony\Component\Mime\RawMessage;
26use TYPO3\CMS\Core\Exception as CoreException;
27use TYPO3\CMS\Core\Mail\Event\AfterMailerInitializationEvent;
28use TYPO3\CMS\Core\Utility\GeneralUtility;
29use TYPO3\CMS\Core\Utility\MailUtility;
30
31/**
32 * Adapter for Symfony/Mailer to be used by TYPO3 extensions.
33 *
34 * This will use the setting in TYPO3_CONF_VARS to choose the correct transport
35 * for it to work out-of-the-box.
36 */
37class Mailer implements MailerInterface
38{
39    /**
40     * @var TransportInterface
41     */
42    protected $transport;
43
44    /**
45     * @var array
46     */
47    protected $mailSettings = [];
48
49    /**
50     * @var SentMessage|null
51     */
52    protected $sentMessage;
53
54    /**
55     * @var string This will be added as X-Mailer to all outgoing mails
56     */
57    protected $mailerHeader = 'TYPO3';
58
59    /**
60     * @var EventDispatcherInterface
61     */
62    protected $eventDispatcher;
63
64    /**
65     * When constructing, also initializes the Symfony Transport like configured
66     *
67     * @param TransportInterface|null $transport optionally pass a transport to the constructor.
68     * @param EventDispatcherInterface|null $eventDispatcher
69     * @throws CoreException
70     */
71    public function __construct(TransportInterface $transport = null, EventDispatcherInterface $eventDispatcher = null)
72    {
73        if ($transport !== null) {
74            $this->transport = $transport;
75        } else {
76            if (empty($this->mailSettings)) {
77                $this->injectMailSettings();
78            }
79            try {
80                $this->initializeTransport();
81            } catch (\Exception $e) {
82                throw new CoreException($e->getMessage(), 1291068569);
83            }
84        }
85        if ($eventDispatcher !== null) {
86            $this->eventDispatcher = $eventDispatcher;
87            $this->eventDispatcher->dispatch(new AfterMailerInitializationEvent($this));
88        }
89    }
90
91    /**
92     * @inheritdoc
93     */
94    public function send(RawMessage $message, Envelope $envelope = null): void
95    {
96        if ($message instanceof Email) {
97            // Ensure to always have a From: header set
98            if (empty($message->getFrom())) {
99                $address = MailUtility::getSystemFromAddress();
100                if ($address) {
101                    $name = MailUtility::getSystemFromName();
102                    if ($name) {
103                        $from = new Address($address, $name);
104                    } else {
105                        $from = new Address($address);
106                    }
107                    $message->from($from);
108                }
109            }
110            if (empty($message->getReplyTo())) {
111                $replyTo = MailUtility::getSystemReplyTo();
112                if (!empty($replyTo)) {
113                    $address = key($replyTo);
114                    if ($address === 0) {
115                        $replyTo = new Address($replyTo[$address]);
116                    } else {
117                        $replyTo = new Address((string)$address, reset($replyTo));
118                    }
119                    $message->replyTo($replyTo);
120                }
121            }
122            $message->getHeaders()->addTextHeader('X-Mailer', $this->mailerHeader);
123        }
124
125        $this->sentMessage = $this->transport->send($message, $envelope);
126    }
127
128    public function getSentMessage(): ?SentMessage
129    {
130        return $this->sentMessage;
131    }
132
133    public function getTransport(): TransportInterface
134    {
135        return $this->transport;
136    }
137
138    /**
139     * Prepares a transport using the TYPO3_CONF_VARS configuration
140     *
141     * Used options:
142     * $TYPO3_CONF_VARS['MAIL']['transport'] = 'smtp' | 'sendmail' | 'null' | 'mbox'
143     *
144     * $TYPO3_CONF_VARS['MAIL']['transport_smtp_server'] = 'smtp.example.org:25';
145     * $TYPO3_CONF_VARS['MAIL']['transport_smtp_encrypt'] = FALSE; # requires openssl in PHP
146     * $TYPO3_CONF_VARS['MAIL']['transport_smtp_username'] = 'username';
147     * $TYPO3_CONF_VARS['MAIL']['transport_smtp_password'] = 'password';
148     *
149     * $TYPO3_CONF_VARS['MAIL']['transport_sendmail_command'] = '/usr/sbin/sendmail -bs'
150     *
151     * @throws CoreException
152     * @throws \RuntimeException
153     */
154    private function initializeTransport()
155    {
156        $this->transport = $this->getTransportFactory()->get($this->mailSettings);
157    }
158
159    /**
160     * This method is only used in unit tests
161     *
162     * @param array $mailSettings
163     * @internal
164     */
165    public function injectMailSettings(array $mailSettings = null)
166    {
167        if (is_array($mailSettings)) {
168            $this->mailSettings = $mailSettings;
169        } else {
170            $this->mailSettings = (array)$GLOBALS['TYPO3_CONF_VARS']['MAIL'];
171        }
172    }
173
174    /**
175     * Returns the real transport (not a spool).
176     *
177     * @return TransportInterface
178     */
179    public function getRealTransport(): TransportInterface
180    {
181        $mailSettings = !empty($this->mailSettings) ? $this->mailSettings : (array)$GLOBALS['TYPO3_CONF_VARS']['MAIL'];
182        unset($mailSettings['transport_spool_type']);
183        return $this->getTransportFactory()->get($mailSettings);
184    }
185
186    /**
187     * @return TransportFactory
188     */
189    protected function getTransportFactory(): TransportFactory
190    {
191        return GeneralUtility::makeInstance(TransportFactory::class);
192    }
193}
194