Completed
Push — master ( 40184f...590734 )
by Loz
01:01
created

Mailer   A

Complexity

Total Complexity 29

Size/Duplication

Total Lines 284
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 8

Test Coverage

Coverage 96.4%

Importance

Changes 0
Metric Value
wmc 29
lcom 1
cbo 8
dl 0
loc 284
ccs 107
cts 111
cp 0.964
rs 10
c 0
b 0
f 0

11 Methods

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