Completed
Push — master ( 12b3bb...db2dee )
by Loz
05:12
created

Mailer   A

Complexity

Total Complexity 27

Size/Duplication

Total Lines 277
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Test Coverage

Coverage 100%

Importance

Changes 8
Bugs 2 Features 3
Metric Value
wmc 27
c 8
b 2
f 3
lcom 1
cbo 4
dl 0
loc 277
ccs 108
cts 108
cp 1
rs 10

11 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 10 1
A setMailgunClient() 0 5 1
A getMailgunClient() 0 4 1
A sendPlain() 0 4 1
A sendHTML() 0 4 1
B sendMessage() 0 29 4
C buildMessage() 0 55 9
A parseAddresses() 0 19 4
A prepareAttachments() 0 15 2
A writeToTempFile() 0 13 1
A closeTempFileHandles() 0 8 2
1
<?php
2
3
namespace Kinglozzer\SilverStripeMailgunner;
4
5
use Mailer as SilverstripeMailer;
6
use Mailgun\Mailgun;
7
use Mailgun\Messages\MessageBuilder;
8
9
class Mailer extends SilverstripeMailer
10
{
11
    /**
12
     * @var string
13
     * @config
14
     */
15
    private static $api_domain = '';
16
17
    /**
18
     * @var string
19
     * @config
20
     */
21
    private static $api_endpoint = 'api.mailgun.net';
22
23
    /**
24
     * @var string
25
     * @config
26
     */
27
    private static $api_key = '';
28
29
    /**
30
     * @var boolean
31
     * @config
32
     */
33
    private static $api_ssl = true;
34
35
    /**
36
     * @var string
37
     * @config
38
     */
39
    private static $api_version = 'v3';
40
41
    /**
42
     * An array of temporary file handles opened to store attachments
43
     * @var array
44
     */
45
    protected $tempFileHandles = [];
46
47
    /**
48
     * @var Mailgun
49
     */
50
    protected $mailgunClient;
51
52
    /**
53
     * {@inheritdoc}
54
     */
55 12
    public function __construct()
56
    {
57 12
        $config = $this->config();
58 12
        $this->setMailgunClient(new Mailgun(
59 12
            $config->api_key,
60 12
            $config->api_endpoint,
61 12
            $config->api_version,
62 12
            $config->api_ssl
63 12
        ));
64 12
    }
65
66
    /**
67
     * @param Mailgun $client
68
     * @return self
69
     */
70 12
    public function setMailgunClient(Mailgun $client)
71
    {
72 12
        $this->mailgunClient = $client;
73 12
        return $this;
74
    }
75
76
    /**
77
     * @return Mailgun
78
     */
79 1
    public function getMailgunClient()
80
    {
81 1
        return $this->mailgunClient;
82
    }
83
84
    /**
85
     * {@inheritdoc}
86
     */
87 1
    public function sendPlain($to, $from, $subject, $plainContent, $attachments = [], $headers = [])
88
    {
89 1
        $this->sendMessage($to, $from, $subject, $htmlContent = '', $plainContent, $attachments, $headers);
90 1
    }
91
92
    /**
93
     * {@inheritdoc}
94
     */
95 1
    public function sendHTML($to, $from, $subject, $htmlContent, $attachments = [], $headers = [], $plainContent = '')
96
    {
97 1
        $this->sendMessage($to, $from, $subject, $htmlContent, $plainContent, $attachments, $headers);
98 1
    }
99
100
    /**
101
     * @param string $to
102
     * @param string $from
103
     * @param string $subject
104
     * @param string $content
105
     * @param string $plainContent
106
     * @param array $attachments
107
     * @param array $headers
108
     */
109 3
    protected function sendMessage($to, $from, $subject, $content, $plainContent, $attachments, $headers)
110
    {
111 3
        $domain = $this->config()->api_domain;
112 3
        $client = $this->getMailgunClient();
113 3
        $attachments = $this->prepareAttachments($attachments);
114
115 3
        if (isset($headers['X-Mailgunner-Batch-Message'])) {
116 1
            $builder = $client->BatchMessage($domain);
117 1
            unset($headers['X-Mailgunner-Batch-Message']);
118 1
        } else {
119 2
            $builder = $client->MessageBuilder();
120
        }
121
122
        try {
123 3
            $this->buildMessage($builder, $to, $from, $subject, $content, $plainContent, $attachments, $headers);
124
125 3
            if ($builder instanceof \Mailgun\Messages\BatchMessage) {
126 1
                $builder->finalize();
127 1
            } else {
128 2
                $client->sendMessage($domain, $builder->getMessage(), $builder->getFiles());
129
            }
130 3
        } catch (\Exception $e) {
131
            // Close and remove any temp files created for attachments, then let the exception bubble up
132 1
            $this->closeTempFileHandles();
133 1
            throw $e;
134
        }
135
136 2
        $this->closeTempFileHandles();
137 2
    }
138
139
    /**
140
     * @param MessageBuilder $builder
141
     * @param string $to
142
     * @param string $from
143
     * @param string $subject
144
     * @param string $content
145
     * @param string $plainContent
146
     * @param array $attachments
147
     * @param array $headers
148
     */
149 2
    protected function buildMessage(
150
        MessageBuilder $builder,
151
        $to,
152
        $from,
153
        $subject,
154
        $content,
155
        $plainContent,
156
        array $attachments,
157
        array $headers
158
    ) {
159
        // Add base info
160 2
        $parsedFrom = $this->parseAddresses($from);
161 2
        foreach ($parsedFrom as $email => $name) {
162 2
            $builder->setFromAddress($email, ['full_name' => $name]);
163 2
        }
164
        
165 2
        $builder->setSubject($subject);
166 2
        $builder->setHtmlBody($content);
167 2
        $builder->setTextBody($plainContent);
168
169
        // Add attachments
170 2
        foreach ($attachments as $attachment) {
171 2
            $builder->addAttachment($attachment['filePath'], $attachment['remoteName']);
172 2
        }
173
174
        // Parse Cc & Bcc headers out if they're set
175 2
        $ccAddresses = isset($headers['Cc']) ? $headers['Cc'] : '';
176 2
        $bccAddresses = isset($headers['Bcc']) ? $headers['Bcc'] : '';
177
178
        // We handle these ourselves, so can remove them from the list of headers
179 2
        unset($headers['Cc']);
180 2
        unset($headers['Bcc']);
181
182
        // Add remaining custom headers
183 2
        foreach ($headers as $name => $data) {
184 2
            $builder->addCustomHeader($name, $data);
185 2
        }
186
187
        // Add recipients. This is done last as the 'BatchMessage' message builder
188
        // will trigger sends for every 1000 addresses
189 2
        $to = $this->parseAddresses($to);
190 2
        foreach ($to as $email => $name) {
191 2
            $builder->addToRecipient($email, ['full_name' => $name]);
192 2
        }
193
194 2
        $ccAddresses = $this->parseAddresses($ccAddresses);
195 2
        foreach ($ccAddresses as $email => $name) {
196 2
            $builder->addCcRecipient($email, ['full_name' => $name]);
197 2
        }
198
199 2
        $bccAddresses = $this->parseAddresses($bccAddresses);
200 2
        foreach ($bccAddresses as $email => $name) {
201 2
            $builder->addBccRecipient($email, ['full_name' => $name]);
202 2
        }
203 2
    }
204
205
    /**
206
     * @todo This can't deal with mismatched quotes, or commas in names.
207
     *       E.g. "Smith, John" <[email protected]> or "John O'smith" <[email protected]>
208
     * @param string
209
     * @return array
210
     */
211 3
    protected function parseAddresses($addresses)
212
    {
213 3
        $parsed = [];
214
215 3
        $expr = '/\s*["\']?([^><,;"\']+)["\']?\s*((?:<[^><,]+>)?)\s*/';
216 3
        if (preg_match_all($expr, $addresses, $matches, PREG_SET_ORDER) > 0) {
217 3
            foreach ($matches as $result) {
218 3
                if (empty($result[2])) {
219
                    // If we couldn't parse out a name
220 3
                    $parsed[$result[1]] = '';
221 3
                } else {
222 3
                    $email = trim($result[2], '<>');
223 3
                    $parsed[$email] = trim($result[1]);
224
                }
225 3
            }
226 3
        }
227
228 3
        return $parsed;
229
    }
230
231
    /**
232
     * Prepare attachments for sending. SilverStripe extracts the content and
233
     * passes that to the mailer, so to save encoding it we just write them all
234
     * to individual files and let Mailgun deal with the rest.
235
     *
236
     * @todo Can we handle this better?
237
     * @param array $attachments
238
     * @return array
239
     */
240 2
    protected function prepareAttachments(array $attachments)
241
    {
242 2
        $prepared = [];
243
            
244 2
        foreach ($attachments as $attachment) {
245 2
            $tempFile = $this->writeToTempFile($attachment['contents']);
246
            
247 2
            $prepared[] = [
248 2
                'filePath' => $tempFile,
249 2
                'remoteName' => $attachment['filename']
250 2
            ];
251 2
        }
252
253 2
        return $prepared;
254
    }
255
256
    /**
257
     * @param string $contents
258
     * @return string
259
     */
260 2
    protected function writeToTempFile($contents)
261
    {
262 2
        $tempFile = tempnam(sys_get_temp_dir(), 'SS_MG_TMP');
263 2
        $fileHandle = fopen($tempFile, 'r+');
264 2
        fwrite($fileHandle, $contents);
265
266 2
        $this->tempFileHandles[] = [
267 2
            'handle' => $fileHandle,
268
            'path' => $tempFile
269 2
        ];
270
271 2
        return $tempFile;
272
    }
273
274
    /**
275
     * @return void
276
     */
277 1
    protected function closeTempFileHandles()
278
    {
279 1
        foreach ($this->tempFileHandles as $key => $data) {
280 1
            fclose($data['handle']);
281 1
            unlink($data['path']);
282 1
            unset($this->tempFileHandles[$key]);
283 1
        }
284 1
    }
285
}
286