Completed
Push — master ( dc17eb...040d62 )
by Jacob
05:42
created

Archangel::formatEmailAddress()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 7
rs 9.4285
cc 2
eloc 4
nc 2
nop 2
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
    public function addTo($address, $title = '')
89
    {
90
        array_push(
91
            $this->toAddresses,
92
            $this->formatEmailAddress($address, $title)
93
        );
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
    public function addCC($address, $title = '')
107
    {
108
        array_push(
109
            $this->ccAddresses,
110
            $this->formatEmailAddress($address, $title)
111
        );
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
    public function addBCC($address, $title = '')
125
    {
126
        array_push(
127
            $this->bccAddresses,
128
            $this->formatEmailAddress($address, $title)
129
        );
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
    public function setFrom($address, $title = '')
143
    {
144
        $this->headers['From'] = $this->formatEmailAddress($address, $title);
145
146
        return $this;
147
    }
148
149
    /**
150
     * Setter method for setting the single 'reply-to' field
151
     *
152
     * @param string $address email address for the reply-to
153
     * @param string $title   name of the reply-to (optional)
154
     *
155
     * @return object instantiated $this
156
     */
157
    public function setReplyTo($address, $title = '')
158
    {
159
        $this->headers['Reply-To'] = $this->formatEmailAddress($address, $title);
160
161
        return $this;
162
    }
163
164
    /**
165
     * @param string $address
166
     * @param string $title
167
     *
168
     * @return string
169
     */
170
    protected function formatEmailAddress($address, $title)
171
    {
172
        if (!empty($title)) {
173
            $address = sprintf('"%s" <%s>', $title, $address);
174
        }
175
        return $address;
176
    }
177
178
    /**
179
     * Setter method for setting a subject
180
     *
181
     * @param string $subject subject for the email
182
     *
183
     * @return object instantiated $this
184
     */
185
    public function setSubject($subject)
186
    {
187
        $this->subject = $subject;
188
189
        return $this;
190
    }
191
192
    /**
193
     * Setter method for the plain text message
194
     *
195
     * @param string $message the plain-text message
196
     *
197
     * @return object instantiated $this
198
     */
199
    public function setPlainMessage($message)
200
    {
201
        $this->plainMessage = $message;
202
203
        return $this;
204
    }
205
206
    /**
207
     * Setter method for the html message
208
     *
209
     * @param string $message the html message
210
     *
211
     * @return object instantiated $this
212
     */
213
    public function setHTMLMessage($message)
214
    {
215
        $this->htmlMessage = $message;
216
217
        return $this;
218
    }
219
220
    /**
221
     * Setter method for adding attachments
222
     *
223
     * @param string $path  the full path of the attachment
224
     * @param string $type  mime type of the file
225
     * @param string $title the title of the attachment (optional)
226
     *
227
     * @return object instantiated $this
228
     */
229
    public function addAttachment($path, $type, $title = '')
230
    {
231
        array_push($this->attachments, array(
232
          'path' => $path,
233
          'type' => $type,
234
          'title' => $title,
235
        ));
236
237
        return $this;
238
    }
239
240
    /**
241
     * The executing step, the actual sending of the email
242
     * First checks to make sure the minimum fields are set (returns false if they are not)
243
     * Second it attempts to send the mail with php's mail() (returns false if it fails)
244
     *
245
     * return boolean whether or not the email was valid & sent
246
     */
247
    public function send()
248
    {
249
        if (!$this->checkRequiredFields()) {
250
            return false;
251
        }
252
253
        $recipients = $this->buildTo();
254
        $subject = $this->subject;
255
        $message = $this->buildMessage();
256
        $headers = $this->buildHeaders();
257
258
        return mail($recipients, $subject, $message, $headers);
259
    }
260
261
    /**
262
     * Call to check the minimum required fields
263
     *
264
     * @return boolean whether or not the email meets the minimum required fields
265
     */
266
    protected function checkRequiredFields()
267
    {
268
        if (empty($this->toAddresses)) {
269
            return false;
270
        }
271
        if (empty($this->subject)) {
272
            return false;
273
        }
274
275
        if (empty($this->plainMessage) && empty($this->htmlMessage) && empty($this->attachments)) {
276
            return false;
277
        }
278
279
        return true;
280
    }
281
282
    /**
283
     * Build the recipients from 'to'
284
     *
285
     * @return string comma-separated lit of recipients
286
     */
287
    protected function buildTo()
288
    {
289
        return implode(', ', $this->toAddresses);
290
    }
291
292
    /**
293
     * Long, nasty creater of the actual message, with all the multipart logic you'd never want to see
294
     *
295
     * @return string email message
296
     */
297
    protected function buildMessage()
298
    {
299
        $messageString = '';
300
        
301
        if (!empty($this->attachments)) {
302
            $messageString .= "--{$this->getBoundary()}" . self::LINE_BREAK;
303
        }
304
        if (!empty($this->plainMessage) && !empty($this->htmlMessage)) {
305 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...
306
                $messageString .= "Content-Type: multipart/alternative; boundary={$this->getAlternativeBoundary()}" . self::LINE_BREAK;
307
                $messageString .= self::LINE_BREAK;
308
            }
309
            $messageString .= "--{$this->getAlternativeBoundary()}" . self::LINE_BREAK;
310
            $messageString .= 'Content-Type: text/plain; charset="iso-8859"' . self::LINE_BREAK;
311
            $messageString .= 'Content-Transfer-Encoding: 7bit' . self::LINE_BREAK;
312
            $messageString .= self::LINE_BREAK;
313
            $messageString .= $this->plainMessage;
314
            $messageString .= self::LINE_BREAK;
315
            $messageString .= "--{$this->getAlternativeBoundary()}" . self::LINE_BREAK;
316
            $messageString .= 'Content-Type: text/html; charset="iso-8859-1"' . self::LINE_BREAK;
317
            $messageString .= 'Content-Transfer-Encoding: 7bit' . self::LINE_BREAK;
318
            $messageString .= self::LINE_BREAK;
319
            $messageString .= $this->htmlMessage;
320
            $messageString .= self::LINE_BREAK;
321
            $messageString .= "--{$this->getAlternativeBoundary()}--" . self::LINE_BREAK;
322
            $messageString .= self::LINE_BREAK;
323
        } elseif (!empty($this->plainMessage)) {
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/plain; charset="iso-8859"' . self::LINE_BREAK;
326
                $messageString .= 'Content-Transfer-Encoding: 7bit' . self::LINE_BREAK;
327
                $messageString .= self::LINE_BREAK;
328
            }
329
            $messageString .= $this->plainMessage;
330
            $messageString .= self::LINE_BREAK;
331
        } elseif (!empty($this->htmlMessage)) {
332 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...
333
                $messageString .= 'Content-Type: text/html; charset="iso-8859-1"' . self::LINE_BREAK;
334
                $messageString .= 'Content-Transfer-Encoding: 7bit' . self::LINE_BREAK;
335
                $messageString .= self::LINE_BREAK;
336
            }
337
            $messageString .= $this->htmlMessage;
338
            $messageString .= self::LINE_BREAK;
339
        }
340
        if (!empty($this->attachments)) {
341
            foreach ($this->attachments as $attachment) {
342
                $messageString .= "--{$this->getBoundary()}" . self::LINE_BREAK;
343
                $messageString .= "Content-Type: {$attachment['type']}; name=\"{$attachment['title']}\"" . self::LINE_BREAK;
344
                $messageString .= 'Content-Transfer-Encoding: base64' . self::LINE_BREAK;
345
                $messageString .= 'Content-Disposition: attachment' . self::LINE_BREAK;
346
                $messageString .= self::LINE_BREAK;
347
                $messageString .= $this->buildAttachmentContent($attachment);
348
                $messageString .= self::LINE_BREAK;
349
            }
350
            $messageString .= "--{$this->getBoundary()}--" . self::LINE_BREAK;
351
        }
352
        return $messageString;
353
    }
354
355
356
    /**
357
     * Builder for the additional headers needed for multipart emails
358
     *
359
     * @return string headers needed for multipart
360
     */
361
    protected function buildHeaders()
362
    {
363
        $headerString = '';
364
        foreach ($this->headers as $key => $value) {
365
            $headerString .= sprintf('%s: %s', $key, $value) . self::LINE_BREAK;
366
        }
367
368
        if (!empty($this->ccAddresses)) {
369
            $headerString .= 'CC: ' . implode(', ', $this->ccAddresses) . self::LINE_BREAK;
370
        }
371
        if (!empty($this->bccAddresses)) {
372
            $headerString .= 'BCC: ' . implode(', ', $this->bccAddresses) . self::LINE_BREAK;
373
        }
374
        
375
        if (!empty($this->attachments)) {
376
            $headerString .= "Content-Type: multipart/mixed; boundary=\"{$this->getBoundary()}\"";
377
        } elseif (!empty($this->plainMessage) && !empty($this->htmlMessage)) {
378
            $headerString .= "Content-Type: multipart/alternative; boundary=\"{$this->getAlternativeBoundary()}\"";
379
        } elseif (!empty($this->htmlMessage)) {
380
            $headerString .= 'Content-type: text/html; charset="iso-8859-1"';
381
        }
382
383
        return $headerString;
384
    }
385
386
    /**
387
     * File reader for attachments
388
     *
389
     * @return string binary representation of file, base64'd
390
     */
391
    protected function buildAttachmentContent($attachment)
392
    {
393
        if (!file_exists($attachment['path'])) {
394
            return ''; // todo log error
395
        }
396
397
        $handle = fopen($attachment['path'], 'r');
398
        $contents = fread($handle, filesize($attachment['path']));
399
        fclose($handle);
400
401
        $contents = base64_encode($contents);
402
        $contents = chunk_split($contents);
403
        return $contents;
404
    }
405
406
    /**
407
     * Holder for the boundry logic
408
     * Not called/created unless it's needed
409
     *
410
     * @return  string  boundary
411
     */
412
    protected function getBoundary()
413
    {
414
        if (!isset($this->boundary)) {
415
            $this->boundary = sprintf('PHP-mixed-%s', uniqid());
416
        }
417
        return $this->boundary;
418
    }
419
420
    /**
421
     * Holder to create the alternative boundry logic
422
     * Not called/created unless it's needed
423
     *
424
     * @return string alternative boundary
425
     */
426
    protected function getAlternativeBoundary()
427
    {
428
        if (!isset($this->alternativeBoundary)) {
429
            $this->alternativeBoundary = sprintf('PHP-alternative-%s', uniqid());
430
        }
431
        return $this->alternativeBoundary;
432
    }
433
}
434