Completed
Push — master ( e18dd8...85dea1 )
by Jacob
02:06
created

Archangel   B

Complexity

Total Complexity 53

Size/Duplication

Total Lines 444
Duplicated Lines 7.88 %

Coupling/Cohesion

Components 1
Dependencies 2

Importance

Changes 14
Bugs 1 Features 2
Metric Value
wmc 53
c 14
b 1
f 2
lcom 1
cbo 2
dl 35
loc 444
rs 7.4757

21 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 11 2
A setLogger() 0 6 1
A addTo() 0 9 1
A addCC() 13 13 2
A addBCC() 13 13 2
A setFrom() 0 6 1
A setReplyTo() 0 6 1
A formatEmailAddress() 0 7 2
A setSubject() 0 6 1
A setPlainMessage() 0 6 1
A setHTMLMessage() 0 6 1
A addAttachment() 0 10 1
A send() 0 13 3
B checkRequiredFields() 0 19 6
A buildTo() 0 4 1
C buildMessage() 0 23 7
C buildMessageWithAttachments() 3 38 8
A getPlainMessageHeader() 0 8 1
A getHtmlMessageHeader() 0 8 1
C buildHeaders() 6 29 8
A buildAttachmentContent() 0 15 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Archangel often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Archangel, and based on these observations, apply Extract Interface, too.

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 $headers */
26
    protected $headers = array();
27
28
    /** @var string $plainMessage */
29
    protected $plainMessage;
30
31
    /** @var string $htmlMessage */
32
    protected $htmlMessage;
33
34
    /** @var array $attachments */
35
    protected $attachments = array();
36
37
    /** @var string $boundaryMixed */
38
    protected $boundaryMixed;
39
40
    /** @var string $boundaryAlternative */
41
    protected $boundaryAlternative;
42
43
    /** @var LoggerInterface */
44
    protected $logger;
45
46
    /** @var string LINE_BREAK */
47
    const LINE_BREAK = "\r\n";
48
49
    /**
50
     * @param string $mailer
51
     */
52
    public function __construct($mailer = null)
53
    {
54
        if (is_null($mailer)) {
55
            $mailer = sprintf('PHP/%s', phpversion());
56
        }
57
        $this->headers['X-Mailer'] = $mailer;
58
59
        $this->logger = new NullLogger();
60
        $this->boundaryMixed = sprintf('PHP-mixed-%s', uniqid());
61
        $this->boundaryAlternative = sprintf('PHP-alternative-%s', uniqid());
62
    }
63
64
    /**
65
     * @param LoggerInterface $logger
66
     *
67
     * @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...
68
     */
69
    public function setLogger(LoggerInterface $logger)
70
    {
71
        $this->logger = $logger;
72
73
        return $this;
74
    }
75
76
    /**
77
     * Setter method for adding recipients
78
     *
79
     * @param string $address email address for the recipient
80
     * @param string $title   name of the recipient (optional)
81
82
     * @return object instantiated $this
83
     */
84
    public function addTo($address, $title = '')
85
    {
86
        array_push(
87
            $this->toAddresses,
88
            $this->formatEmailAddress($address, $title)
89
        );
90
91
        return $this;
92
    }
93
94
    /**
95
     * Setter method for adding cc recipients
96
     *
97
     * @param string $address email address for the cc recipient
98
     * @param string $title   name of the cc recipient (optional)
99
     *
100
     * @return object instantiated $this
101
     */
102 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...
103
    {
104
        if (!isset($this->headers['CC'])) {
105
            $this->headers['CC'] = array();
106
        }
107
108
        array_push(
109
            $this->headers['CC'],
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 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 (!isset($this->headers['BCC'])) {
127
            $this->headers['BCC'] = array();
128
        }
129
130
        array_push(
131
            $this->headers['BCC'],
132
            $this->formatEmailAddress($address, $title)
133
        );
134
135
        return $this;
136
    }
137
138
    /**
139
     * Setter method for setting the single 'from' field
140
     *
141
     * @param string $address email address for the sender
142
     * @param string $title   name of the sender (optional)
143
     *
144
     * @return object instantiated $this
145
     */
146
    public function setFrom($address, $title = '')
147
    {
148
        $this->headers['From'] = $this->formatEmailAddress($address, $title);
149
150
        return $this;
151
    }
152
153
    /**
154
     * Setter method for setting the single 'reply-to' field
155
     *
156
     * @param string $address email address for the reply-to
157
     * @param string $title   name of the reply-to (optional)
158
     *
159
     * @return object instantiated $this
160
     */
161
    public function setReplyTo($address, $title = '')
162
    {
163
        $this->headers['Reply-To'] = $this->formatEmailAddress($address, $title);
164
165
        return $this;
166
    }
167
168
    /**
169
     * @param string $address
170
     * @param string $title
171
     *
172
     * @return string
173
     */
174
    protected function formatEmailAddress($address, $title)
175
    {
176
        if (!empty($title)) {
177
            $address = sprintf('"%s" <%s>', $title, $address);
178
        }
179
        return $address;
180
    }
181
182
    /**
183
     * Setter method for setting a subject
184
     *
185
     * @param string $subject subject for the email
186
     *
187
     * @return object instantiated $this
188
     */
189
    public function setSubject($subject)
190
    {
191
        $this->subject = $subject;
192
193
        return $this;
194
    }
195
196
    /**
197
     * Setter method for the plain text message
198
     *
199
     * @param string $message the plain-text message
200
     *
201
     * @return object instantiated $this
202
     */
203
    public function setPlainMessage($message)
204
    {
205
        $this->plainMessage = $message;
206
207
        return $this;
208
    }
209
210
    /**
211
     * Setter method for the html message
212
     *
213
     * @param string $message the html message
214
     *
215
     * @return object instantiated $this
216
     */
217
    public function setHTMLMessage($message)
218
    {
219
        $this->htmlMessage = $message;
220
221
        return $this;
222
    }
223
224
    /**
225
     * Setter method for adding attachments
226
     *
227
     * @param string $path  the full path of the attachment
228
     * @param string $type  mime type of the file
229
     * @param string $title the title of the attachment (optional)
230
     *
231
     * @return object instantiated $this
232
     */
233
    public function addAttachment($path, $type, $title = '')
234
    {
235
        array_push($this->attachments, array(
236
          'path' => $path,
237
          'type' => $type,
238
          'title' => $title,
239
        ));
240
241
        return $this;
242
    }
243
244
    /**
245
     * The executing step, the actual sending of the email
246
     * First checks to make sure the minimum fields are set (returns false if they are not)
247
     * Second it attempts to send the mail with php's mail() (returns false if it fails)
248
     *
249
     * return boolean whether or not the email was valid & sent
250
     */
251
    public function send()
252
    {
253
        if (!$this->checkRequiredFields()) {
254
            return false;
255
        }
256
257
        $recipients = $this->buildTo();
258
        $subject = $this->subject;
259
        $message = (empty($this->attachments)) ? $this->buildMessage() : $this->buildHtmlMessage();
0 ignored issues
show
Bug introduced by
The method buildHtmlMessage() does not seem to exist on object<Jacobemerick\Archangel\Archangel>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
260
        $headers = $this->buildHeaders();
261
262
        return mail($recipients, $subject, $message, $headers);
263
    }
264
265
    /**
266
     * Call to check the minimum required fields
267
     *
268
     * @return boolean whether or not the email meets the minimum required fields
269
     */
270
    protected function checkRequiredFields()
271
    {
272
        if (empty($this->toAddresses)) {
273
            return false;
274
        }
275
        if (empty($this->subject)) {
276
            return false;
277
        }
278
279
        if (
280
            empty($this->plainMessage) &&
281
            empty($this->htmlMessage) &&
282
            empty($this->attachments)
283
        ) {
284
            return false;
285
        }
286
287
        return true;
288
    }
289
290
    /**
291
     * Build the recipients from 'to'
292
     *
293
     * @return string comma-separated lit of recipients
294
     */
295
    protected function buildTo()
296
    {
297
        return implode(', ', $this->toAddresses);
298
    }
299
300
    /**
301
     * Returns a simple email message without attachments
302
     *
303
     * @return string email message
304
     */
305
    protected function buildMessage()
306
    {
307
        if (empty($this->plainMessage) && empty($this->htmlMessage)) {
308
            return '';
309
        }
310
        if (!empty($this->plainMessage) && empty($this->htmlMessage)) {
311
            return $this->plainMessage;
312
        }
313
        if (empty($this->plainMessage) && !empty($this->htmlMessage)) {
314
            return $this->htmlMessage;
315
        }
316
317
        $message = array();
318
        array_push($message, "--{$this->boundaryAlternative}");
319
        $message += $this->buildPlainMessageHeaders();
0 ignored issues
show
Bug introduced by
The method buildPlainMessageHeaders() does not seem to exist on object<Jacobemerick\Archangel\Archangel>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
320
        array_push($message, $this->plainMessage);
321
        array_push($message, "--{$this->boundaryAlternative}");
322
        $message += $this->buildHtmlMessageHeaders();
0 ignored issues
show
Bug introduced by
The method buildHtmlMessageHeaders() does not seem to exist on object<Jacobemerick\Archangel\Archangel>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
323
        array_push($message, $this->htmlMessage);
324
        array_push($message, "--{$this->boundaryAlternative}--");
325
326
        return implode(self::LINE_BREAK, $message);
327
    }
328
329
    /**
330
     * Build multi-part message with attachments
331
     *
332
     * @return string email message
333
     */
334
    protected function buildMessageWithAttachments()
335
    {
336
        $message = array();
337
338 View Code Duplication
        if (!empty($this->plainMessage) || !empty($this->htmlMessage)) {
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...
339
            array_push($message, "--{$this->boundaryMixed}");
340
        }
341
342
        if (!empty($this->plainMessage) && !empty($this->htmlMessage)) {
343
            array_push($message, "Content-Type: multipart/alternative; boundary={$this->boundaryAlternative}");
344
            array_push($message, '');
345
            array_push($message, "--{$this->boundaryAlternative}");
346
            $message += $this->buildPlainMessageHeaders();
0 ignored issues
show
Bug introduced by
The method buildPlainMessageHeaders() does not seem to exist on object<Jacobemerick\Archangel\Archangel>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
347
            array_push($message, $this->plainMessage);
348
            array_push($message, "--{$this->boundaryAlternative}");
349
            $message += $this->buildHtmlMessageHeaders();
0 ignored issues
show
Bug introduced by
The method buildHtmlMessageHeaders() does not seem to exist on object<Jacobemerick\Archangel\Archangel>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
350
            array_push($message, $this->htmlMessage);
351
            array_push($message, "--{$this->boundaryAlternative}--");
352
            array_push($message, '');
353
        } elseif (!empty($this->plainMessage)) {
354
            $message += $this->buildPlainMessageHeaders();
0 ignored issues
show
Bug introduced by
The method buildPlainMessageHeaders() does not seem to exist on object<Jacobemerick\Archangel\Archangel>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
355
            array_push($message, $this->plainMessage);
356
        } elseif (!empty($this->htmlMessage)) {
357
            $message += $this->buildHtmlMessageHeaders();
0 ignored issues
show
Bug introduced by
The method buildHtmlMessageHeaders() does not seem to exist on object<Jacobemerick\Archangel\Archangel>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
358
            array_push($message, $this->htmlMessage);
359
        }
360
        foreach ($this->attachments as $attachment) {
361
            array_push($message, "--{$this->boundaryMixed}");
362
            array_push($message, "Content-Type: {$attachment['type']}; name=\"{$attachment['title']}\"");
363
            array_push($message, 'Content-Transfer-Encoding: base64');
364
            array_push($message, 'Content-Disposition: attachment');
365
            array_push($message, '');
366
            array_push($message, $this->buildAttachmentContent($attachment['path']));
367
        }
368
        array_push($message, "--{$this->boundaryMixed}--");
369
370
        return implode(self::LINE_BREAK, $message);
371
    }
372
373
374
    /**
375
     * Shared holder for the plain message header
376
     *
377
     * @return array
378
     */
379
    protected function getPlainMessageHeader()
380
    {
381
        return array(
382
            'Content-Type: text/plain; charset="iso-8859"',
383
            'Content-Transfer-Encoding: 7bit',
384
            '',
385
        );
386
    }
387
388
    /**
389
     * Shared holder for the html message header
390
     *
391
     * @return array
392
     */
393
    protected function getHtmlMessageHeader()
394
    {
395
        return array(
396
            'Content-Type: text/html; charset="iso-8859-1"',
397
            'Content-Transfer-Encoding: 7bit',
398
            '',
399
        );
400
    }
401
402
    /**
403
     * Builder for the additional headers needed for multipart emails
404
     *
405
     * @return string headers needed for multipart
406
     */
407
    protected function buildHeaders()
408
    {
409
        $headers = array();
410
        foreach ($this->headers as $key => $value) {
411
            if ($key == 'CC' || $key == 'BCC') {
412
                $value = implode(', ', $value);
413
            }
414
            array_push($headers, sprintf('%s: %s', $key, $value));
415
        }
416
417
        if (!empty($this->attachments)) {
418
            array_push(
419
                $headers,
420
                "Content-Type: multipart/mixed; boundary=\"{$this->boundaryMixed}\""
421
            );
422 View Code Duplication
        } elseif (!empty($this->plainMessage) && !empty($this->htmlMessage)) {
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...
423
            array_push(
424
                $headers,
425
                "Content-Type: multipart/alternative; boundary=\"{$this->boundaryAlternative}\""
426
            );
427
        } elseif (!empty($this->htmlMessage)) {
428
            array_push(
429
                $headers,
430
                'Content-type: text/html; charset="iso-8859-1"'
431
            );
432
        }
433
434
        return implode(self::LINE_BREAK, $headers);
435
    }
436
437
    /**
438
     * File reader for attachments
439
     *
440
     * @param string $path filepath of the attachment
441
     *
442
     * @return string binary representation of file, base64'd
443
     */
444
    protected function buildAttachmentContent($path)
445
    {
446
        if (!file_exists($path)) {
447
            $this->logger->error("Could not find file {$path} for attaching to Archangel mail.");
448
            return '';
449
        }
450
451
        $handle = fopen($path, 'r');
452
        $contents = fread($handle, filesize($path));
453
        fclose($handle);
454
455
        $contents = base64_encode($contents);
456
        $contents = chunk_split($contents);
457
        return $contents;
458
    }
459
}
460