Passed
Push — master ( f2de56...1dcac8 )
by Greg
05:22
created

MailService::mailSslOptions()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 4
nc 1
nop 0
dl 0
loc 8
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * webtrees: online genealogy
4
 * Copyright (C) 2019 webtrees development team
5
 * This program is free software: you can redistribute it and/or modify
6
 * it under the terms of the GNU General Public License as published by
7
 * the Free Software Foundation, either version 3 of the License, or
8
 * (at your option) any later version.
9
 * This program is distributed in the hope that it will be useful,
10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
 * GNU General Public License for more details.
13
 * You should have received a copy of the GNU General Public License
14
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15
 */
16
declare(strict_types=1);
17
18
namespace Fisharebest\Webtrees\Services;
19
20
use Exception;
21
use Fisharebest\Webtrees\Contracts\UserInterface;
22
use Fisharebest\Webtrees\I18N;
23
use Fisharebest\Webtrees\Log;
24
use Fisharebest\Webtrees\Site;
25
use Swift_Mailer;
26
use Swift_Message;
27
use Swift_NullTransport;
28
use Swift_SendmailTransport;
29
use Swift_Signers_DKIMSigner;
30
use Swift_SmtpTransport;
31
use Swift_Transport;
32
use Throwable;
33
use function filter_var;
34
use function function_exists;
35
use function gethostbyaddr;
36
use function gethostbyname;
37
use function gethostname;
38
use function getmxrr;
39
use function str_replace;
40
use function strrchr;
41
use function substr;
42
use const FILTER_VALIDATE_DOMAIN;
43
use const FILTER_VALIDATE_EMAIL;
44
45
/**
46
 * Send mail messages.
47
 */
48
class MailService
49
{
50
    /**
51
     * Send an external email message
52
     * Caution! gmail may rewrite the "From" header unless you have added the address to your account.
53
     *
54
     * @param UserInterface $from
55
     * @param UserInterface $to
56
     * @param UserInterface $reply_to
57
     * @param string        $subject
58
     * @param string        $message_text
59
     * @param string        $message_html
60
     *
61
     * @return bool
62
     */
63
    public function send(UserInterface $from, UserInterface $to, UserInterface $reply_to, string $subject, string $message_text, string $message_html): bool
64
    {
65
        // Mail needs MSDOS line endings
66
        $message_text = str_replace("\n", "\r\n", $message_text);
67
        $message_html = str_replace("\n", "\r\n", $message_html);
68
69
        $message = (new Swift_Message())
70
            ->setSubject($subject)
71
            ->setFrom($from->email(), $from->realName())
72
            ->setTo($to->email(), $to->realName())
73
            ->setBody($message_html, 'text/html');
74
75
        if ($from->email() !== $reply_to->email()) {
76
            $message->setReplyTo($reply_to->email(), $reply_to->realName());
77
        }
78
79
        $dkim_domain   = Site::getPreference('DKIM_DOMAIN');
80
        $dkim_selector = Site::getPreference('DKIM_SELECTOR');
81
        $dkim_key      = Site::getPreference('DKIM_KEY');
82
83
        if ($dkim_domain !== '' && $dkim_selector !== '' && $dkim_key !== '') {
84
            $signer = new Swift_Signers_DKIMSigner($dkim_key, $dkim_domain, $dkim_selector);
85
            $signer
86
                ->setHeaderCanon('relaxed')
87
                ->setBodyCanon('relaxed');
88
89
            $message->attachSigner($signer);
90
        } else {
91
            // DKIM body hashes don't work with multipart/alternative content.
92
            $message->addPart($message_text, 'text/plain');
93
        }
94
95
        $mailer = new Swift_Mailer($this->transport());
96
97
        try {
98
            $mailer->send($message);
99
        } catch (Exception $ex) {
100
            Log::addErrorLog('MailService: ' . $ex->getMessage());
101
102
            return false;
103
        }
104
105
        return true;
106
    }
107
108
    /**
109
     * Create a transport mechanism for sending mail
110
     *
111
     * @return Swift_Transport
112
     */
113
    private function transport(): Swift_Transport
114
    {
115
        switch (Site::getPreference('SMTP_ACTIVE')) {
116
            case 'sendmail':
117
                // Local sendmail (requires PHP proc_* functions)
118
                return new Swift_SendmailTransport();
119
120
            case 'external':
121
                // SMTP
122
                $smtp_host = Site::getPreference('SMTP_HOST');
123
                $smtp_port = (int) Site::getPreference('SMTP_PORT', '25');
124
                $smtp_auth = (bool) Site::getPreference('SMTP_AUTH');
125
                $smtp_user = Site::getPreference('SMTP_AUTH_USER');
126
                $smtp_pass = Site::getPreference('SMTP_AUTH_PASS');
127
                $smtp_encr = Site::getPreference('SMTP_SSL');
128
129
                $transport = new Swift_SmtpTransport($smtp_host, $smtp_port, $smtp_encr);
130
131
                $transport->setLocalDomain($this->localDomain());
132
133
                if ($smtp_auth) {
134
                    $transport
135
                        ->setUsername($smtp_user)
136
                        ->setPassword($smtp_pass);
137
                }
138
139
                return $transport;
140
141
            default:
142
                // For testing
143
                return new Swift_NullTransport();
144
        }
145
    }
146
147
    /**
148
     * Where are we sending mail from?
149
     *
150
     * @return string
151
     */
152
    public function localDomain(): string {
153
        $local_domain = Site::getPreference('SMTP_HELO');
154
155
        try {
156
            // Look ourself up using DNS.
157
            $default = gethostbyaddr(gethostbyname(gethostname()));
158
        } catch (Throwable $ex) {
159
            $default = 'localhost';
160
        }
161
162
        return $local_domain ?: $default;
163
    }
164
165
    /**
166
     * Where are we sending mail from?
167
     *
168
     * @return string
169
     */
170
    public function senderEmail(): string {
171
        $sender  = Site::getPreference('SMTP_FROM_NAME');
172
        $default = 'no-reply@' . $this->localDomain();
173
174
        return $sender ?: $default;
175
    }
176
177
    /**
178
     * Many mail relays require a valid sender email.
179
     *
180
     * @param string $email
181
     *
182
     * @return bool
183
     */
184
    public function isValidEmail(string $email): bool
185
    {
186
        if (filter_var($email, FILTER_VALIDATE_EMAIL) === false) {
187
            return false;
188
        }
189
190
        $domain = substr(strrchr($email, '@'), 1);
191
192
        if (filter_var($domain, FILTER_VALIDATE_DOMAIN) === false) {
193
            return false;
194
        }
195
196
        return getmxrr($domain, $mxhosts);
197
    }
198
199
    /**
200
     * A list SSL modes (e.g. for an edit control).
201
     *
202
     * @return string[]
203
     */
204
    public function mailSslOptions(): array
205
    {
206
        return [
207
            'none' => I18N::translate('none'),
208
            /* I18N: Secure Sockets Layer - a secure communications protocol*/
209
            'ssl'  => I18N::translate('ssl'),
210
            /* I18N: Transport Layer Security - a secure communications protocol */
211
            'tls'  => I18N::translate('tls'),
212
        ];
213
    }
214
215
    /**
216
     * A list SSL modes (e.g. for an edit control).
217
     *
218
     * @return string[]
219
     */
220
    public function mailTransportOptions(): array
221
    {
222
        $options = [
223
            /* I18N: "sendmail" is the name of some mail software */
224
            'sendmail' => I18N::translate('Use sendmail to send messages'),
225
            'external' => I18N::translate('Use SMTP to send messages'),
226
        ];
227
228
        if (!function_exists('proc_open')) {
229
            unset($options['sendmail']);
230
        }
231
232
        return $options;
233
    }
234
}
235