Completed
Push — master ( d3e6fb...d363b9 )
by Eoghan
03:52
created

SimpleMail   C

Complexity

Total Complexity 56

Size/Duplication

Total Lines 684
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 0

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 56
lcom 1
cbo 0
dl 0
loc 684
ccs 181
cts 181
cp 1
rs 5.3488
c 0
b 0
f 0

42 Methods

Rating   Name   Duplication   Size   Complexity  
A make() 0 4 1
A __construct() 0 4 1
A reset() 0 12 1
A setTo() 0 5 1
A getTo() 0 4 1
A setFrom() 0 5 1
A setCc() 0 4 1
A setBcc() 0 4 1
A setReplyTo() 0 4 1
A setHtml() 0 6 1
A setSubject() 0 7 1
A getSubject() 0 4 1
A setMessage() 0 5 1
A getMessage() 0 4 1
A addAttachment() 0 11 3
A getAttachmentData() 0 8 1
A addMailHeader() 0 6 1
A addMailHeaders() 0 18 4
A addGenericHeader() 0 9 1
A getHeaders() 0 4 1
A setParameters() 0 5 1
A getParameters() 0 4 1
A setWrap() 0 9 2
A getWrap() 0 4 1
A hasAttachments() 0 4 1
A assembleAttachmentHeaders() 0 8 1
A assembleAttachmentBody() 0 18 2
A getAttachmentMimeTemplate() 0 16 1
A send() 0 20 3
A debug() 0 4 1
A __toString() 0 4 1
A formatHeader() 0 9 2
A encodeUtf8() 0 8 2
A encodeUtf8Word() 0 4 1
A encodeUtf8Words() 0 9 2
A filterEmail() 0 15 1
A filterName() 0 17 1
A filterOther() 0 4 1
A getHeadersForSend() 0 7 2
A getToForSend() 0 7 2
A getUniqueId() 0 4 1
A getWrapMessage() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like SimpleMail 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 SimpleMail, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Simple Mail
4
 *
5
 * A simple PHP wrapper class for sending email using the mail() method.
6
 *
7
 * PHP version > 5.2
8
 *
9
 * LICENSE: This source file is subject to the MIT license, which is
10
 * available through the world-wide-web at the following URI:
11
 * http://github.com/eoghanobrien/php-simple-mail/LICENCE.txt
12
 *
13
 * @category  SimpleMail
14
 * @package   SimpleMail
15
 * @author    Eoghan O'Brien <[email protected]>
16
 * @copyright 2009 - 2017 Eoghan O'Brien
17
 * @license   http://github.com/eoghanobrien/php-simple-mail/LICENCE.txt MIT
18
 * @version   1.7
19
 * @link      http://github.com/eoghanobrien/php-simple-mail
20
 */
21
22
/**
23
 * Simple Mail class.
24
 *
25
 * @category  SimpleMail
26
 * @package   SimpleMail
27
 * @author    Eoghan O'Brien <[email protected]>
28
 * @copyright 2009 - 2017 Eoghan O'Brien
29
 * @license   http://github.com/eoghanobrien/php-simple-mail/LICENCE.txt MIT
30
 * @version   1.7
31
 * @link      http://github.com/eoghanobrien/php-simple-mail
32
 */
33
class SimpleMail
34
{
35
    /**
36
     * @var int $_wrap
37
     */
38
    protected $_wrap = 78;
39
40
    /**
41
     * @var array $_to
42
     */
43
    protected $_to = array();
44
45
    /**
46
     * @var string $_subject
47
     */
48
    protected $_subject;
49
50
    /**
51
     * @var string $_message
52
     */
53
    protected $_message;
54
55
    /**
56
     * @var array $_headers
57
     */
58
    protected $_headers = array();
59
60
    /**
61
     * @var string $_parameters
62
     */
63
    protected $_params;
64
65
    /**
66
     * @var array $_attachments
67
     */
68
    protected $_attachments = array();
69
70
    /**
71
     * @var string $_uid
72
     */
73
    protected $_uid;
74
75
    /**
76
     * Named constructor.
77
     *
78
     * @return static
79
     */
80
    public static function make()
81 38
    {
82
        return new SimpleMail();
83 38
    }
84 38
85
    /**
86
     * __construct
87
     *
88
     * Resets the class properties.
89
     */
90
    public function __construct()
91
    {
92
        $this->reset();
93 38
    }
94
95 38
    /**
96 38
     * reset
97 38
     *
98 38
     * Resets all properties to initial state.
99 38
     *
100 38
     * @return self
101 38
     */
102 38
    public function reset()
103 38
    {
104
        $this->_to = array();
105
        $this->_headers = array();
106
        $this->_subject = null;
107
        $this->_message = null;
108
        $this->_wrap = 78;
109
        $this->_params = null;
110
        $this->_attachments = array();
111
        $this->_uid = $this->getUniqueId();
112
        return $this;
113
    }
114 4
115
    /**
116 4
     * setTo
117 4
     *
118
     * @param string $email The email address to send to.
119
     * @param string $name  The name of the person to send to.
120
     *
121
     * @return self
122
     */
123
    public function setTo($email, $name)
124
    {
125
        $this->_to[] = $this->formatHeader((string) $email, (string) $name);
126
        return $this;
127 2
    }
128
129 2
    /**
130
     * getTo
131
     *
132
     * Return an array of formatted To addresses.
133
     *
134
     * @return array
135
     */
136
    public function getTo()
137
    {
138
        return $this->_to;
139 3
    }
140
141 3
    /**
142 3
     * setFrom
143 3
     *
144 3
     * @param string $email The email to send as from.
145
     * @param string $name  The name to send as from.
146
     *
147
     * @return self
148
     */
149
    public function setFrom($email, $name)
150
    {
151
        $this->addMailHeader('From', (string) $email, (string) $name);
152 1
        return $this;
153
    }
154 1
155
    /**
156
     * setCc
157
     *
158
     * @param array  $pairs  An array of name => email pairs.
159
     *
160
     * @return self
161
     */
162
    public function setCc(array $pairs)
163
    {
164 3
        return $this->addMailHeaders('Cc', $pairs);
165
    }
166 3
167 3
    /**
168
     * setBcc
169
     *
170
     * @param array  $pairs  An array of name => email pairs.
171
     *
172
     * @return self
173
     */
174
    public function setBcc(array $pairs)
175 1
    {
176
        return $this->addMailHeaders('Bcc', $pairs);
177 1
    }
178
179
    /**
180
     * setReplyTo
181
     *
182
     * @param string $email
183
     * @param string $name
184
     *
185
     * @return self
186
     */
187
    public function setReplyTo($email, $name = null)
188
    {
189 3
        return $this->addMailHeader('Reply-To', $email, $name);
190
    }
191 3
192 3
    /**
193 3
     * setHtml
194 3
     *
195 3
     * @return self
196 3
     */
197 3
    public function setHtml()
198 3
    {
199
        return $this->addGenericHeader(
200
            'Content-Type', 'text/html; charset="utf-8"'
201
        );
202
    }
203
204
    /**
205
     * setSubject
206
     *
207
     * @param string $subject The email subject
208 3
     *
209
     * @return self
210 3
     */
211 3
    public function setSubject($subject)
212 3
    {
213 3
        $this->_subject = $this->encodeUtf8(
214 3
            $this->filterOther((string) $subject)
215
        );
216
        return $this;
217
    }
218
219
    /**
220
     * getSubject function.
221
     *
222
     * @return string
223
     */
224
    public function getSubject()
225 3
    {
226
        return $this->_subject;
227 3
    }
228 3
229
    /**
230
     * setMessage
231
     *
232
     * @param string $message The message to send.
233
     *
234
     * @return self
235
     */
236
    public function setMessage($message)
237
    {
238
        $this->_message = str_replace("\n.", "\n..", (string) $message);
239
        return $this;
240 3
    }
241
242 3
    /**
243 3
     * getMessage
244 3
     *
245
     * @return string
246
     */
247
    public function getMessage()
248
    {
249
        return $this->_message;
250
    }
251
252
    /**
253
     * addAttachment
254
     *
255 1
     * @param string $path The file path to the attachment.
256
     * @param string $filename The filename of the attachment when emailed.
257 1
     * @param null $data
258 1
     * 
259 1
     * @return self
260
     */
261 1
    public function addAttachment($path, $filename = null, $data = null)
262 1
    {
263
        $filename = empty($filename) ? basename($path) : $filename;
264
        $data = empty($data) ? $this->getAttachmentData($path) : $data;
265
        $this->_attachments[] = array(
266
            'path' => $path,
267
            'file' => $filename,
268
            'data' => chunk_split(base64_encode($data))
269
        );
270
        return $this;
271
    }
272 2
273
    /**
274 2
     * getAttachmentData
275
     *
276
     * @param string $path The path to the attachment file.
277
     *
278
     * @return string
279
     */
280
    public function getAttachmentData($path)
281
    {
282
        $filesize = filesize($path);
283
        $handle = fopen($path, "r");
284
        $attachment = fread($handle, $filesize);
285
        fclose($handle);
286 1
        return $attachment;
287
    }
288 1
289 1
    /**
290
     * addMailHeader
291
     *
292
     * @param string $header The header to add.
293
     * @param string $email  The email to add.
294
     * @param string $name   The name to add.
295
     *
296
     * @return self
297 1
     */
298
    public function addMailHeader($header, $email, $name = null)
299 1
    {
300
        $address = $this->formatHeader((string) $email, (string) $name);
301
        $this->_headers[] = sprintf('%s: %s', (string) $header, $address);
302
        return $this;
303
    }
304
305
    /**
306
     * addMailHeaders
307
     *
308
     * @param string $header The header to add.
309 2
     * @param array  $pairs  An array of name => email pairs.
310
     *
311 2
     * @return self
312 2
     */
313 1
    public function addMailHeaders($header, array $pairs)
314 1
    {
315 2
        if (count($pairs) === 0) {
316 2
            throw new InvalidArgumentException(
317
                'You must pass at least one name => email pair.'
318
            );
319
        }
320
        foreach ($pairs as $name => $email) {
321
            if (is_numeric($name)) {
322
                $addresses[] = $this->formatHeader($email);
0 ignored issues
show
Coding Style Comprehensibility introduced by
$addresses was never initialized. Although not strictly required by PHP, it is generally a good practice to add $addresses = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
323
            } else {
324 3
                $addresses[] = $this->formatHeader($email, $name);
0 ignored issues
show
Bug introduced by
The variable $addresses does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
325
            }
326 3
        }
327
        $addresses = implode(',', $addresses);
328
        $this->addGenericHeader($header, $addresses);
329
        return $this;
330
    }
331
332
    /**
333
     * addGenericHeader
334
     *
335
     * @param string $header The generic header to add.
336 4
     * @param mixed  $value  The value of the header.
337
     *
338 4
     * @return self
339
     */
340
    public function addGenericHeader($header, $value)
341
    {
342
        $this->_headers[] = sprintf(
343
            '%s: %s',
344
            (string) $header,
345
            (string) $value
346 2
        );
347
        return $this;
348 2
    }
349 2
350 2
    /**
351
     * getHeaders
352 2
     *
353
     * Return the headers registered so far as an array.
354
     *
355
     * @return array
356
     */
357
    public function getHeaders()
358
    {
359
        return $this->_headers;
360 1
    }
361
362 1
    /**
363 1
     * setAdditionalParameters
364 1
     *
365 1
     * Such as "[email protected]
366 1
     *
367 1
     * @param string $additionalParameters The addition mail parameter.
368 1
     *
369 1
     * @return self
370 1
     */
371
    public function setParameters($additionalParameters)
372 1
    {
373 1
        $this->_params = (string) $additionalParameters;
374 1
        return $this;
375
    }
376 1
377
    /**
378
     * getAdditionalParameters
379
     *
380
     * @return string
381
     */
382
    public function getParameters()
383
    {
384
        return $this->_params;
385
    }
386 1
387
    /**
388 1
     * setWrap
389 1
     *
390
     * @param int $wrap The number of characters at which the message will wrap.
391 1
     *
392 1
     * @return self
393 1
     */
394 1
    public function setWrap($wrap = 78)
395 1
    {
396 1
        $wrap = (int) $wrap;
397 1
        if ($wrap < 1) {
398 1
            $wrap = 78;
399
        }
400 1
        $this->_wrap = $wrap;
401
        return $this;
402
    }
403
404
    /**
405
     * getWrap
406
     *
407
     * @return int
408
     */
409 3
    public function getWrap()
410
    {
411 3
        return $this->_wrap;
412 3
    }
413
414 3
    /**
415 1
     * hasAttachments
416
     * 
417 1
     * Checks if the email has any registered attachments.
418
     *
419
     * @return bool
420 2
     */
421 1
    public function hasAttachments()
422 1
    {
423 1
        return !empty($this->_attachments);
424 1
    }
425
426
    /**
427 2
     * assembleAttachment
428
     *
429
     * @return string
430
     */
431
    public function assembleAttachmentHeaders()
432
    {
433
        $head = array();
434
        $head[] = "MIME-Version: 1.0";
435 1
        $head[] = "Content-Type: multipart/mixed; boundary=\"{$this->_uid}\"";
436
437 1
        return join(PHP_EOL, $head);
438
    }
439
440
    /**
441
     * assembleAttachmentBody
442
     *
443
     * @return string
444
     */
445 1
    public function assembleAttachmentBody()
446
    {
447 1
        $body = array();
448
        $body[] = "This is a multi-part message in MIME format.";
449
        $body[] = "--{$this->_uid}";
450
        $body[] = "Content-type:text/html; charset=\"utf-8\"";
451
        $body[] = "Content-Transfer-Encoding: 7bit";
452
        $body[] = "";
453
        $body[] = $this->_message;
454
        $body[] = "";
455
        $body[] = "--{$this->_uid}";
456
457
        foreach ($this->_attachments as $attachment) {
458
            $body[] = $this->getAttachmentMimeTemplate($attachment);
459
        }
460
461 6
        return implode(PHP_EOL, $body) . '--';
462
    }
463 6
464 6
    /**
465 1
     * getAttachmentMimeTemplate
466
     *
467 5
     * @param array  $attachment An array containing 'file' and 'data' keys.
468 5
     *
469
     * @return string
470
     */
471
    public function getAttachmentMimeTemplate($attachment)
472
    {
473
        $file = $attachment['file'];
474
        $data = $attachment['data'];
475
476
        $head = array();
477
        $head[] = "Content-Type: application/octet-stream; name=\"{$file}\"";
478 8
        $head[] = "Content-Transfer-Encoding: base64";
479
        $head[] = "Content-Disposition: attachment; filename=\"{$file}\"";
480 8
        $head[] = "";
481 8
        $head[] = $data;
482 4
        $head[] = "";
483
        $head[] = "--{$this->_uid}";
484 6
485
        return implode(PHP_EOL, $head);
486
    }
487
488
    /**
489
     * send
490
     *
491
     * @return boolean
492
     * @throws \RuntimeException on no 'To: ' address to send to.
493
     */
494 8
    public function send()
495
    {
496 8
        $to = $this->getToForSend();
497
        $headers = $this->getHeadersForSend();
498
499
        if (empty($to)) {
500
            throw new \RuntimeException(
501
                'Unable to send, no To address has been set.'
502
            );
503
        }
504
505
        if ($this->hasAttachments()) {
506 4
            $message  = $this->assembleAttachmentBody();
507
            $headers .= PHP_EOL . $this->assembleAttachmentHeaders();
508 4
        } else {
509 4
            $message = $this->getWrapMessage();
510 4
        }
511 4
512 4
        return mail($to, $this->_subject, $message, $headers, $this->_params);
513 4
    }
514
515
    /**
516
     * debug
517
     *
518
     * @return string
519
     */
520
    public function debug()
521
    {
522
        return '<pre>' . print_r($this, true) . '</pre>';
523
    }
524
525
    /**
526 12
     * magic __toString function
527
     *
528
     * @return string
529 12
     */
530 12
    public function __toString()
531 12
    {
532 12
        return print_r($this, true);
533 12
    }
534 12
535
    /**
536 12
     * formatHeader
537 12
     *
538 12
     * Formats a display address for emails according to RFC2822 e.g.
539 12
     * Name <[email protected]>
540
     *
541
     * @param string $email The email address.
542
     * @param string $name  The display name.
543
     *
544
     * @return string
545
     */
546
    public function formatHeader($email, $name = null)
547
    {
548
        $email = $this->filterEmail((string) $email);
549
        if (empty(trim($name))) {
550
            return $email;
551
        }
552
        $name = $this->encodeUtf8($this->filterName((string) $name));
553 10
        return sprintf('"%s" <%s>', $name, $email);
554
    }
555
556 10
    /**
557 10
     * encodeUtf8
558 10
     *
559 10
     * @param string $value The value to encode.
560 10
     *
561 10
     * @return string
562 10
     */
563 10
    public function encodeUtf8($value)
564 10
    {
565 10
        $value = trim($value);
566
        if (preg_match('/(\s)/', $value)) {
567 10
            return $this->encodeUtf8Words($value);
568 10
        }
569
        return $this->encodeUtf8Word($value);
570
    }
571
572
    /**
573
     * encodeUtf8Word
574
     *
575
     * @param string $value The word to encode.
576
     *
577
     * @return string
578
     */
579
    public function encodeUtf8Word($value)
580
    {
581 9
        return sprintf('=?UTF-8?B?%s?=', base64_encode($value));
582
    }
583 9
584
    /**
585
     * encodeUtf8Words
586
     *
587
     * @param string $value The words to encode.
588
     *
589
     * @return string
590
     */
591 3
    public function encodeUtf8Words($value)
592
    {
593 3
        $words = explode(' ', $value);
594 1
        $encoded = array();
595
        foreach ($words as $word) {
596 2
            $encoded[] = $this->encodeUtf8Word($word);
597
        }
598
        return join($this->encodeUtf8Word(' '), $encoded);
599
    }
600
601
    /**
602
     * filterEmail
603
     *
604 3
     * Removes any carriage return, line feed, tab, double quote, comma
605
     * and angle bracket characters before sanitizing the email address.
606 3
     *
607 1
     * @param string $email The email to filter.
608
     *
609 2
     * @return string
610
     */
611
    public function filterEmail($email)
612
    {
613
        $rule = array(
614
            "\r" => '',
615
            "\n" => '',
616
            "\t" => '',
617 38
            '"'  => '',
618
            ','  => '',
619 38
            '<'  => '',
620
            '>'  => ''
621
        );
622
        $email = strtr($email, $rule);
623
        $email = filter_var($email, FILTER_SANITIZE_EMAIL);
624
        return $email;
625
    }
626
627 1
    /**
628
     * filterName
629 1
     *
630
     * Removes any carriage return, line feed or tab characters. Replaces
631
     * double quotes with single quotes and angle brackets with square
632
     * brackets, before sanitizing the string and stripping out html tags.
633
     *
634
     * @param string $name The name to filter.
635
     *
636
     * @return string
637
     */
638
    public function filterName($name)
639
    {
640
        $rule = array(
641
            "\r" => '',
642
            "\n" => '',
643
            "\t" => '',
644
            '"'  => "'",
645
            '<'  => '[',
646
            '>'  => ']',
647
        );
648
        $filtered = filter_var(
649
            $name,
650
            FILTER_SANITIZE_STRING,
651
            FILTER_FLAG_NO_ENCODE_QUOTES
652
        );
653
        return trim(strtr($filtered, $rule));
654
    }
655
656
    /**
657
     * filterOther
658
     *
659
     * Removes ASCII control characters including any carriage return, line
660
     * feed or tab characters.
661
     *
662
     * @param string $data The data to filter.
663
     *
664
     * @return string
665
     */
666
    public function filterOther($data)
667
    {
668
        return filter_var($data, FILTER_UNSAFE_RAW, FILTER_FLAG_STRIP_LOW);
669
    }
670
671
    /**
672
     * getHeadersForSend
673
     *
674
     * @return string
675
     */
676
    public function getHeadersForSend()
677
    {
678
        if (empty($this->_headers)) {
679
            return '';
680
        }
681
        return join(PHP_EOL, $this->_headers);
682
    }
683
684
    /**
685
     * getToForSend
686
     *
687
     * @return string
688
     */
689
    public function getToForSend()
690
    {
691
        if (empty($this->_to)) {
692
            return '';
693
        }
694
        return join(', ', $this->_to);
695
    }
696
697
    /**
698
     * getUniqueId
699
     *
700
     * @return string
701
     */
702
    public function getUniqueId()
703
    {
704
        return md5(uniqid(time()));
705
    }
706
707
    /**
708
     * getWrapMessage
709
     *
710
     * @return string
711
     */
712
    public function getWrapMessage()
713
    {
714
        return wordwrap($this->_message, $this->_wrap);
715
    }
716
}
717