Completed
Push — master ( 987613...8e6cf9 )
by Jacob
02:35
created

Archangel::send()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 5
Bugs 1 Features 0
Metric Value
c 5
b 1
f 0
dl 0
loc 13
rs 9.4285
cc 2
eloc 8
nc 2
nop 0
1
<?php
2
3
namespace Jacobemerick\Archangel;
4
5
use Psr\Log\LoggerAwareInterface;
6
use Psr\Log\LoggerInterface;
7
use Psr\Log\NullLogger;
8
9
/**
10
 * This is the main class for Archangel mailer
11
 * For licensing and examples:
12
 * @see https://github.com/jacobemerick/archangel
13
 *
14
 * @author jacobemerick (http://home.jacobemerick.com/)
15
 */
16
class Archangel implements LoggerAwareInterface
17
{
18
19
    /** @var string $subject */
20
    protected $subject;
21
22
    /** @var array $toAddresses */
23
    protected $toAddresses = array();
24
25
    /** @var array $ccAddresses */
26
    protected $ccAddresses = array();
27
28
    /** @var array $bccAddresses */
29
    protected $bccAddresses = array();
30
31
    /** @var array $headers */
32
    protected $headers = array();
33
34
    /** @var string $plainMessage */
35
    protected $plainMessage;
36
37
    /** @var string $htmlMessage */
38
    protected $htmlMessage;
39
40
    /** @var array $attachments */
41
    protected $attachments = array();
42
43
    /** @var string $boundary */
44
    protected $boundary;
45
46
    /** @var string $alternativeBoundary */
47
    protected $alternativeBoundary;
48
49
    /** @var LoggerInterface */
50
    protected $logger;
51
52
    /** @var string LINE_BREAK */
53
    const LINE_BREAK = "\r\n";
54
55
    /**
56
     * @param string $mailer
57
     */
58
    public function __construct($mailer = null)
59
    {
60
        if (is_null($mailer)) {
61
            $mailer = sprintf('PHP/%s', phpversion());
62
        }
63
        $this->headers['X-Mailer'] = $mailer;
64
65
        $this->logger = new NullLogger();
66
    }
67
68
    /**
69
     * @param LoggerInterface $logger
70
     *
71
     * @return $this;
0 ignored issues
show
Documentation introduced by
The doc-type $this; could not be parsed: Expected "|" or "end of type", but got ";" at position 5. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
72
     */
73
    public function setLogger(LoggerInterface $logger)
74
    {
75
        $this->logger = $logger;
76
77
        return $this;
78
    }
79
80
    /**
81
     * Setter method for adding recipients
82
     *
83
     * @param string $address email address for the recipient
84
     * @param string $title   name of the recipient (optional)
85
86
     * @return object instantiated $this
87
     */
88 View Code Duplication
    public function addTo($address, $title = '')
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
89
    {
90
        if (!empty($title)) {
91
            $address = sprintf('"%s" <%s>', $title, $address);
92
        }
93
        array_push($this->toAddresses, $address);
94
95
        return $this;
96
    }
97
98
    /**
99
     * Setter method for adding cc recipients
100
     *
101
     * @param string $address email address for the cc recipient
102
     * @param string $title   name of the cc recipient (optional)
103
     *
104
     * @return object instantiated $this
105
     */
106 View Code Duplication
    public function addCC($address, $title = '')
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
107
    {
108
        if (!empty($title)) {
109
            $address = sprintf('"%s" <%s>', $title, $address);
110
        }
111
        array_push($this->ccAddresses, $address);
112
113
        return $this;
114
    }
115
116
    /**
117
     * Setter method for adding bcc recipients
118
     *
119
     * @param string $address email address for the bcc recipient
120
     * @param string $title   name of the bcc recipient (optional)
121
     *
122
     * @return object instantiated $this
123
     */
124 View Code Duplication
    public function addBCC($address, $title = '')
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
125
    {
126
        if (!empty($title)) {
127
            $address = sprintf('"%s" <%s>', $title, $address);
128
        }
129
        array_push($this->bccAddresses, $address);
130
131
        return $this;
132
    }
133
134
    /**
135
     * Setter method for setting the single 'from' field
136
     *
137
     * @param string $address email address for the sender
138
     * @param string $title   name of the sender (optional)
139
     *
140
     * @return object instantiated $this
141
     */
142 View Code Duplication
    public function setFrom($address, $title = '')
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
143
    {
144
        if (!empty($title)) {
145
            $address = sprintf('"%s" <%s>', $title, $address);
146
        }
147
        $this->headers['From'] = $address;
148
149
        return $this;
150
    }
151
152
    /**
153
     * Setter method for setting the single 'reply-to' field
154
     *
155
     * @param string $address email address for the reply-to
156
     * @param string $title   name of the reply-to (optional)
157
     *
158
     * @return object instantiated $this
159
     */
160 View Code Duplication
    public function setReplyTo($address, $title = '')
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
161
    {
162
        if (!empty($title)) {
163
            $address = sprintf('"%s" <%s>', $title, $address);
164
        }
165
        $this->headers['Reply-To'] = $address;
166
167
        return $this;
168
    }
169
170
    /**
171
     * Setter method for setting a subject
172
     *
173
     * @param string $subject subject for the email
174
     *
175
     * @return object instantiated $this
176
     */
177
    public function setSubject($subject)
178
    {
179
        $this->subject = $subject;
180
181
        return $this;
182
    }
183
184
    /**
185
     * Setter method for the plain text message
186
     *
187
     * @param string $message the plain-text message
188
     *
189
     * @return object instantiated $this
190
     */
191
    public function setPlainMessage($message)
192
    {
193
        $this->plainMessage = $message;
194
195
        return $this;
196
    }
197
198
    /**
199
     * Setter method for the html message
200
     *
201
     * @param string $message the html message
202
     *
203
     * @return object instantiated $this
204
     */
205
    public function setHTMLMessage($message)
206
    {
207
        $this->htmlMessage = $message;
208
209
        return $this;
210
    }
211
212
    /**
213
     * Setter method for adding attachments
214
     *
215
     * @param string $path  the full path of the attachment
216
     * @param string $type  mime type of the file
217
     * @param string $title the title of the attachment (optional)
218
     *
219
     * @return object instantiated $this
220
     */
221
    public function addAttachment($path, $type, $title = '')
222
    {
223
        array_push($this->attachments, array(
224
          'path' => $path,
225
          'type' => $type,
226
          'title' => $title,
227
        ));
228
229
        return $this;
230
    }
231
232
    /**
233
     * The executing step, the actual sending of the email
234
     * First checks to make sure the minimum fields are set (returns false if they are not)
235
     * Second it attempts to send the mail with php's mail() (returns false if it fails)
236
     *
237
     * return boolean whether or not the email was valid & sent
238
     */
239
    public function send()
240
    {
241
        if (!$this->checkRequiredFields()) {
242
            return false;
243
        }
244
245
        $recipients = $this->buildTo();
246
        $subject = $this->subject;
247
        $message = $this->buildMessage();
248
        $headers = $this->buildHeaders();
249
250
        return mail($recipients, $subject, $message, $headers);
251
    }
252
253
    /**
254
     * Call to check the minimum required fields
255
     *
256
     * @return boolean whether or not the email meets the minimum required fields
257
     */
258
    protected function checkRequiredFields()
259
    {
260
        if (empty($this->toAddresses)) {
261
            return false;
262
        }
263
        if (empty($this->subject)) {
264
            return false;
265
        }
266
267
        if (empty($this->plainMessage) && empty($this->htmlMessage) && empty($this->attachments)) {
268
            return false;
269
        }
270
271
        return true;
272
    }
273
274
    /**
275
     * Build the recipients from 'to'
276
     *
277
     * @return string comma-separated lit of recipients
278
     */
279
    protected function buildTo()
280
    {
281
        return implode(', ', $this->toAddresses);
282
    }
283
284
    /**
285
     * Long, nasty creater of the actual message, with all the multipart logic you'd never want to see
286
     *
287
     * @return string email message
288
     */
289
    protected function buildMessage()
290
    {
291
        $messageString = '';
292
        
293
        if (!empty($this->attachments)) {
294
            $messageString .= "--{$this->getBoundary()}" . self::LINE_BREAK;
295
        }
296
        if (!empty($this->plainMessage) && !empty($this->htmlMessage)) {
297 View Code Duplication
            if (!empty($this->attachments)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
298
                $messageString .= "Content-Type: multipart/alternative; boundary={$this->getAlternativeBoundary()}" . self::LINE_BREAK;
299
                $messageString .= self::LINE_BREAK;
300
            }
301
            $messageString .= "--{$this->getAlternativeBoundary()}" . self::LINE_BREAK;
302
            $messageString .= 'Content-Type: text/plain; charset="iso-8859"' . self::LINE_BREAK;
303
            $messageString .= 'Content-Transfer-Encoding: 7bit' . self::LINE_BREAK;
304
            $messageString .= self::LINE_BREAK;
305
            $messageString .= $this->plainMessage;
306
            $messageString .= self::LINE_BREAK;
307
            $messageString .= "--{$this->getAlternativeBoundary()}" . self::LINE_BREAK;
308
            $messageString .= 'Content-Type: text/html; charset="iso-8859-1"' . self::LINE_BREAK;
309
            $messageString .= 'Content-Transfer-Encoding: 7bit' . self::LINE_BREAK;
310
            $messageString .= self::LINE_BREAK;
311
            $messageString .= $this->htmlMessage;
312
            $messageString .= self::LINE_BREAK;
313
            $messageString .= "--{$this->getAlternativeBoundary()}--" . self::LINE_BREAK;
314
            $messageString .= self::LINE_BREAK;
315
        } elseif (!empty($this->plainMessage)) {
316 View Code Duplication
            if (!empty($this->attachments)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
317
                $messageString .= 'Content-Type: text/plain; charset="iso-8859"' . self::LINE_BREAK;
318
                $messageString .= 'Content-Transfer-Encoding: 7bit' . self::LINE_BREAK;
319
                $messageString .= self::LINE_BREAK;
320
            }
321
            $messageString .= $this->plainMessage;
322
            $messageString .= self::LINE_BREAK;
323
        } elseif (!empty($this->htmlMessage)) {
324 View Code Duplication
            if (!empty($this->attachments)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
325
                $messageString .= 'Content-Type: text/html; charset="iso-8859-1"' . self::LINE_BREAK;
326
                $messageString .= 'Content-Transfer-Encoding: 7bit' . self::LINE_BREAK;
327
                $messageString .= self::LINE_BREAK;
328
            }
329
            $messageString .= $this->htmlMessage;
330
            $messageString .= self::LINE_BREAK;
331
        }
332
        if (!empty($this->attachments)) {
333
            foreach ($this->attachments as $attachment) {
334
                $messageString .= "--{$this->getBoundary()}" . self::LINE_BREAK;
335
                $messageString .= "Content-Type: {$attachment['type']}; name=\"{$attachment['title']}\"" . self::LINE_BREAK;
336
                $messageString .= 'Content-Transfer-Encoding: base64' . self::LINE_BREAK;
337
                $messageString .= 'Content-Disposition: attachment' . self::LINE_BREAK;
338
                $messageString .= self::LINE_BREAK;
339
                $messageString .= $this->buildAttachmentContent($attachment);
340
                $messageString .= self::LINE_BREAK;
341
            }
342
            $messageString .= "--{$this->getBoundary()}--" . self::LINE_BREAK;
343
        }
344
        return $messageString;
345
    }
346
347
348
    /**
349
     * Builder for the additional headers needed for multipart emails
350
     *
351
     * @return string headers needed for multipart
352
     */
353
    protected function buildHeaders()
354
    {
355
        $headerString = '';
356
        foreach ($this->headers as $key => $value) {
357
            $headerString .= sprintf('%s: %s', $key, $value) . self::LINE_BREAK;
358
        }
359
360
        if (!empty($this->ccAddresses)) {
361
            $headerString .= 'CC: ' . implode(', ', $this->ccAddresses) . self::LINE_BREAK;
362
        }
363
        if (!empty($this->bccAddresses)) {
364
            $headerString .= 'BCC: ' . implode(', ', $this->bccAddresses) . self::LINE_BREAK;
365
        }
366
        
367
        if (!empty($this->attachments)) {
368
            $headerString .= "Content-Type: multipart/mixed; boundary=\"{$this->getBoundary()}\"";
369
        } elseif (!empty($this->plainMessage) && !empty($this->htmlMessage)) {
370
            $headerString .= "Content-Type: multipart/alternative; boundary=\"{$this->getAlternativeBoundary()}\"";
371
        } elseif (!empty($this->htmlMessage)) {
372
            $headerString .= 'Content-type: text/html; charset="iso-8859-1"';
373
        }
374
375
        return $headerString;
376
    }
377
378
    /**
379
     * File reader for attachments
380
     *
381
     * @return string binary representation of file, base64'd
382
     */
383
    protected function buildAttachmentContent($attachment)
384
    {
385
        if (!file_exists($attachment['path'])) {
386
            return ''; // todo log error
387
        }
388
389
        $handle = fopen($attachment['path'], 'r');
390
        $contents = fread($handle, filesize($attachment['path']));
391
        fclose($handle);
392
393
        $contents = base64_encode($contents);
394
        $contents = chunk_split($contents);
395
        return $contents;
396
    }
397
398
    /**
399
     * Holder for the boundry logic
400
     * Not called/created unless it's needed
401
     *
402
     * @return  string  boundary
403
     */
404
    protected function getBoundary()
405
    {
406
        if (!isset($this->boundary)) {
407
            $this->boundary = sprintf('PHP-mixed-%s', uniqid());
408
        }
409
        return $this->boundary;
410
    }
411
412
    /**
413
     * Holder to create the alternative boundry logic
414
     * Not called/created unless it's needed
415
     *
416
     * @return string alternative boundary
417
     */
418
    protected function getAlternativeBoundary()
419
    {
420
        if (!isset($this->alternativeBoundary)) {
421
            $this->alternativeBoundary = sprintf('PHP-alternative-%s', uniqid());
422
        }
423
        return $this->alternativeBoundary;
424
    }
425
}
426