Completed
Push — master ( d4fde7...4b3b70 )
by Loz
16:06 queued 11:56
created

Mailer   A

Complexity

Total Complexity 28

Size/Duplication

Total Lines 280
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 7

Test Coverage

Coverage 98.13%

Importance

Changes 0
Metric Value
wmc 28
lcom 1
cbo 7
dl 0
loc 280
ccs 105
cts 107
cp 0.9813
rs 10
c 0
b 0
f 0

11 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 9 1
A setMailgunClient() 0 5 1
A getMailgunClient() 0 4 1
A sendPlain() 0 4 1
A sendHTML() 0 4 1
B sendMessage() 0 39 5
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 Debug;
6
use Exception;
7
use Mailer as SilverstripeMailer;
8
use Mailgun\Mailgun;
9
use Mailgun\Messages\BatchMessage;
10
use Mailgun\Messages\MessageBuilder;
11
use SapphireTest;
12
use SS_Log;
13
14
class Mailer extends SilverstripeMailer
15
{
16
    /**
17
     * @var string
18
     * @config
19
     */
20
    private static $api_domain = '';
21
22
    /**
23
     * @var string
24
     * @config
25
     */
26
    private static $api_key = '';
27
28
    /**
29
     * @var boolean
30
     * @config
31
     */
32
    private static $api_ssl = true;
33
34
    /**
35
     * @var string
36
     * @config
37
     */
38
    private static $api_version = 'v3';
39
40
    /**
41
     * An array of temporary file handles opened to store attachments
42
     * @var array
43
     */
44
    protected $tempFileHandles = [];
45
46
    /**
47
     * @var Mailgun
48
     */
49
    protected $mailgunClient;
50
51
    /**
52
     * {@inheritdoc}
53
     */
54 12
    public function __construct()
55
    {
56 12
        $config = $this->config();
57 12
        $this->setMailgunClient(Mailgun::create($config->api_key));
58
59
        // @todo - Remove, these are deprecated
60 12
        $this->mailgunClient->setApiVersion($config->api_version);
0 ignored issues
show
Deprecated Code introduced by
The method Mailgun\Mailgun::setApiVersion() has been deprecated with message: Will be removed in 3.0

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
61 12
        $this->mailgunClient->setSslEnabled($config->api_ssl);
0 ignored issues
show
Deprecated Code introduced by
The method Mailgun\Mailgun::setSslEnabled() has been deprecated with message: This will be removed in 3.0. Mailgun does not support non-secure connections to their API.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
62 12
    }
63
64
    /**
65
     * @param Mailgun $client
66
     * @return self
67
     */
68 12
    public function setMailgunClient(Mailgun $client)
69
    {
70 12
        $this->mailgunClient = $client;
71 12
        return $this;
72
    }
73
74
    /**
75
     * @return Mailgun
76
     */
77 1
    public function getMailgunClient()
78
    {
79 1
        return $this->mailgunClient;
80
    }
81
82
    /**
83
     * {@inheritdoc}
84
     */
85 1
    public function sendPlain($to, $from, $subject, $plainContent, $attachments = [], $headers = [])
86
    {
87 1
        return $this->sendMessage($to, $from, $subject, $htmlContent = '', $plainContent, $attachments, $headers);
88
    }
89
90
    /**
91
     * {@inheritdoc}
92
     */
93 1
    public function sendHTML($to, $from, $subject, $htmlContent, $attachments = [], $headers = [], $plainContent = '')
94
    {
95 1
        return $this->sendMessage($to, $from, $subject, $htmlContent, $plainContent, $attachments, $headers);
96
    }
97
98
    /**
99
     * @param string $to
100
     * @param string $from
101
     * @param string $subject
102
     * @param string $content
103
     * @param string $plainContent
104
     * @param array $attachments
105
     * @param array $headers
106
     */
107 3
    protected function sendMessage($to, $from, $subject, $content, $plainContent, $attachments, $headers)
108
    {
109 3
        $domain = $this->config()->api_domain;
110 3
        $client = $this->getMailgunClient();
111 3
        $attachments = $this->prepareAttachments($attachments);
112
113 3
        if (isset($headers['X-Mailgunner-Batch-Message'])) {
114 1
            $builder = $client->BatchMessage($domain);
0 ignored issues
show
Deprecated Code introduced by
The method Mailgun\Mailgun::BatchMessage() has been deprecated with message: Will be removed in 3.0

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
115 1
            unset($headers['X-Mailgunner-Batch-Message']);
116 1
        } else {
117 2
            $builder = $client->MessageBuilder();
0 ignored issues
show
Deprecated Code introduced by
The method Mailgun\Mailgun::MessageBuilder() has been deprecated with message: Will be removed in 3.0

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
118
        }
119
120
        try {
121 3
            $this->buildMessage($builder, $to, $from, $subject, $content, $plainContent, $attachments, $headers);
122
123 3
            if ($builder instanceof BatchMessage) {
124 1
                $builder->finalize();
125 1
            } else {
126 2
                $client->sendMessage($domain, $builder->getMessage(), $builder->getFiles());
0 ignored issues
show
Deprecated Code introduced by
The method Mailgun\Mailgun::sendMessage() has been deprecated with message: Use Mailgun->message() instead. Will be removed in 3.0

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
127
            }
128 3
        } catch (Exception $e) {
129
            // Close and remove any temp files created for attachments
130 1
            $this->closeTempFileHandles();
131
            // Throwing the exception would break SilverStripe's Email API expectations, so we log
132
            // errors and show a message (which is hidden in live mode)
133 1
            SS_Log::log('Mailgun error: ' . $e->getMessage(), SS_Log::ERR);
134 1
            if (!SapphireTest::is_running_test()) {
135
                Debug::message('Mailgun error: ' . $e->getMessage());
136
            }
137
138 1
            return false;
139
        }
140
141 2
        $this->closeTempFileHandles();
142
143
        // This is a stupid API :(
144 2
        return array($to, $subject, $content, $headers, '');
145
    }
146
147
    /**
148
     * @param MessageBuilder $builder
149
     * @param string $to
150
     * @param string $from
151
     * @param string $subject
152
     * @param string $content
153
     * @param string $plainContent
154
     * @param array $attachments
155
     * @param array $headers
156
     */
157 2
    protected function buildMessage(
158
        MessageBuilder $builder,
159
        $to,
160
        $from,
161
        $subject,
162
        $content,
163
        $plainContent,
164
        array $attachments,
165
        array $headers
166
    ) {
167
        // Add base info
168 2
        $parsedFrom = $this->parseAddresses($from);
169 2
        foreach ($parsedFrom as $email => $name) {
170 2
            $builder->setFromAddress($email, ['full_name' => $name]);
171 2
        }
172
173 2
        $builder->setSubject($subject);
174 2
        $builder->setHtmlBody($content);
175 2
        $builder->setTextBody($plainContent);
176
177
        // Add attachments
178 2
        foreach ($attachments as $attachment) {
179 2
            $builder->addAttachment($attachment['filePath'], $attachment['remoteName']);
180 2
        }
181
182
        // Parse Cc & Bcc headers out if they're set
183 2
        $ccAddresses = isset($headers['Cc']) ? $headers['Cc'] : '';
184 2
        $bccAddresses = isset($headers['Bcc']) ? $headers['Bcc'] : '';
185
186
        // We handle these ourselves, so can remove them from the list of headers
187 2
        unset($headers['Cc']);
188 2
        unset($headers['Bcc']);
189
190
        // Add remaining custom headers
191 2
        foreach ($headers as $name => $data) {
192 2
            $builder->addCustomHeader($name, $data);
193 2
        }
194
195
        // Add recipients. This is done last as the 'BatchMessage' message builder
196
        // will trigger sends for every 1000 addresses
197 2
        $to = $this->parseAddresses($to);
198 2
        foreach ($to as $email => $name) {
199 2
            $builder->addToRecipient($email, ['full_name' => $name]);
200 2
        }
201
202 2
        $ccAddresses = $this->parseAddresses($ccAddresses);
203 2
        foreach ($ccAddresses as $email => $name) {
204 2
            $builder->addCcRecipient($email, ['full_name' => $name]);
205 2
        }
206
207 2
        $bccAddresses = $this->parseAddresses($bccAddresses);
208 2
        foreach ($bccAddresses as $email => $name) {
209 2
            $builder->addBccRecipient($email, ['full_name' => $name]);
210 2
        }
211 2
    }
212
213
    /**
214
     * @todo This can't deal with mismatched quotes, or commas in names.
215
     *       E.g. "Smith, John" <[email protected]> or "John O'smith" <[email protected]>
216
     * @param string
217
     * @return array
218
     */
219 3
    protected function parseAddresses($addresses)
220
    {
221 3
        $parsed = [];
222
223 3
        $expr = '/\s*["\']?([^><,;"\']+)["\']?\s*((?:<[^><,]+>)?)\s*/';
224 3
        if (preg_match_all($expr, $addresses, $matches, PREG_SET_ORDER) > 0) {
225 3
            foreach ($matches as $result) {
226 3
                if (empty($result[2])) {
227
                    // If we couldn't parse out a name
228 3
                    $parsed[$result[1]] = '';
229 3
                } else {
230 3
                    $email = trim($result[2], '<>');
231 3
                    $parsed[$email] = trim($result[1]);
232
                }
233 3
            }
234 3
        }
235
236 3
        return $parsed;
237
    }
238
239
    /**
240
     * Prepare attachments for sending. SilverStripe extracts the content and
241
     * passes that to the mailer, so to save encoding it we just write them all
242
     * to individual files and let Mailgun deal with the rest.
243
     *
244
     * @todo Can we handle this better?
245
     * @param array $attachments
246
     * @return array
247
     */
248 2
    protected function prepareAttachments(array $attachments)
249
    {
250 2
        $prepared = [];
251
252 2
        foreach ($attachments as $attachment) {
253 2
            $tempFile = $this->writeToTempFile($attachment['contents']);
254
255 2
            $prepared[] = [
256 2
                'filePath' => $tempFile,
257 2
                'remoteName' => $attachment['filename']
258 2
            ];
259 2
        }
260
261 2
        return $prepared;
262
    }
263
264
    /**
265
     * @param string $contents
266
     * @return string
267
     */
268 2
    protected function writeToTempFile($contents)
269
    {
270 2
        $tempFile = tempnam(sys_get_temp_dir(), 'SS_MG_TMP');
271 2
        $fileHandle = fopen($tempFile, 'r+');
272 2
        fwrite($fileHandle, $contents);
273
274 2
        $this->tempFileHandles[] = [
275 2
            'handle' => $fileHandle,
276
            'path' => $tempFile
277 2
        ];
278
279 2
        return $tempFile;
280
    }
281
282
    /**
283
     * @return void
284
     */
285 1
    protected function closeTempFileHandles()
286
    {
287 1
        foreach ($this->tempFileHandles as $key => $data) {
288 1
            fclose($data['handle']);
289 1
            unlink($data['path']);
290 1
            unset($this->tempFileHandles[$key]);
291 1
        }
292 1
    }
293
}
294