Email   F
last analyzed

Complexity

Total Complexity 101

Size/Duplication

Total Lines 991
Duplicated Lines 0 %

Importance

Changes 8
Bugs 0 Features 0
Metric Value
eloc 333
c 8
b 0
f 0
dl 0
loc 991
rs 2
wmc 101

52 Methods

Rating   Name   Duplication   Size   Complexity  
A setFrom() 0 4 1
A __construct() 0 3 1
A reset() 0 26 1
A setTos() 0 9 3
A setTo() 0 3 1
A setSmtpAdditionnalHeaders() 0 11 3
A formatHeader() 0 7 2
A getToForSend() 0 2 1
A send() 0 15 5
A setSmtpConfig() 0 3 1
A addMailHeader() 0 4 1
A setMessage() 0 3 1
B smtpConnection() 0 36 6
A setParameters() 0 3 1
A encodeUtf8() 0 6 2
A useMail() 0 3 1
A filterName() 0 15 1
A encodeUtf8Words() 0 7 2
A setSubject() 0 3 1
A setWrap() 0 7 2
A addMailHeaders() 0 14 4
A getSmtpServerResponse() 0 13 4
A getParameters() 0 2 1
B sendSmtp() 0 39 7
A addAttachment() 0 16 3
A getAttachmentData() 0 9 2
A useSmtp() 0 3 1
A getAttachmentMimeTemplate() 0 14 1
A addGenericHeader() 0 3 1
A setAttachmentHeaders() 0 4 1
A checkForSmtpConnectionTls() 0 29 6
A setHtml() 0 3 1
A getHeaders() 0 2 1
A sendCommand() 0 6 2
A setReplyTo() 0 3 1
A setBcc() 0 3 1
A getSmtpClientHostname() 0 9 3
A sendHelloCommand() 0 8 2
A hasAttachments() 0 2 1
A getHeadersForSend() 0 6 2
A filterEmail() 0 13 1
A setCc() 0 3 1
A getSmtpResponseCode() 0 5 2
A getError() 0 2 1
A assembleAttachmentBody() 0 15 2
A encodeUtf8Word() 0 2 1
A getMessageWithAttachmentForSend() 0 7 2
A getLogs() 0 2 1
A filterOther() 0 2 1
A smtpAuthentication() 0 13 3
A __destruct() 0 3 2
A sendMail() 0 11 2

How to fix   Complexity   

Complex Class

Complex classes like Email 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.

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 Email, and based on these observations, apply Extract Interface, too.

1
<?php
2
    defined('ROOT_PATH') || exit('Access denied');
3
    /**
4
     * TNH Framework
5
     *
6
     * A simple PHP framework using HMVC architecture
7
     *
8
     * This content is released under the MIT License (MIT)
9
     *
10
     * Copyright (c) 2017 TNH Framework
11
     *
12
     * Permission is hereby granted, free of charge, to any person obtaining a copy
13
     * of this software and associated documentation files (the "Software"), to deal
14
     * in the Software without restriction, including without limitation the rights
15
     * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16
     * copies of the Software, and to permit persons to whom the Software is
17
     * furnished to do so, subject to the following conditions:
18
     *
19
     * The above copyright notice and this permission notice shall be included in all
20
     * copies or substantial portions of the Software.
21
     *
22
     * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23
     * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24
     * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25
     * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26
     * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27
     * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28
     * SOFTWARE.
29
     */
30
31
    class Email extends BaseClass{
32
        /**
33
         * @var int $wrap
34
         */
35
        protected $wrap = 78;
36
37
        /**
38
         * @var string 
39
         */
40
        protected $from;
41
42
        /**
43
         * @var array $to
44
         */
45
        protected $to = array();
46
47
        /**
48
         * @var array $cc
49
         */
50
        protected $cc = array();
51
52
        /**
53
         * @var array $bcc
54
         */
55
        protected $bcc = array();
56
57
        /**
58
         * @var string 
59
         */
60
        protected $replyTo;
61
62
        /**
63
         * @var string $subject
64
         */
65
        protected $subject;
66
67
        /**
68
         * @var string $message
69
         */
70
        protected $message;
71
72
        /**
73
         * @var array $headers
74
         */
75
        protected $headers = array();
76
77
        /**
78
         * @var string $parameters
79
         */
80
        protected $params;
81
82
        /**
83
         * @var array $attachments
84
         */
85
        protected $attachments = array();
86
87
        /**
88
         * @var string $uid
89
         */
90
        protected $uid;
91
92
        /**
93
         * Send mail protocol, current supported values are:
94
         * "mail", "smtp"
95
         * @var string
96
         */
97
        protected $protocol = 'mail';
98
99
        /**
100
         * SMTP configuration
101
         * @var array
102
         */
103
        protected $smtpConfig = array();
104
105
        /**
106
         * SMTP connection socket
107
         * @var resource|boolean
108
         */
109
        protected $smtpSocket;
110
111
        /**
112
         * The last SMTP server response string
113
         * @var string
114
         */
115
        protected $smtpResponse;
116
117
        /**
118
         * The last sending mail error
119
         * @var string|null
120
         */
121
        protected $error = null;
122
123
        /**
124
         * The log for sending mail
125
         * @var array
126
         */
127
        protected $logs = array();
128
        
129
130
        /**
131
         * __construct
132
         *
133
         * Resets the class properties.
134
         */
135
        public function __construct() {
136
            parent::__construct();
137
            $this->reset();
138
        }
139
140
        /**
141
         * reset
142
         *
143
         * Resets all properties to initial state.
144
         *
145
         * @return object
146
         */
147
        public function reset() {
148
            $this->from = null;
149
            $this->to = array();
150
            $this->cc = array();
151
            $this->bcc = array();
152
            $this->replyTo = null;
153
            $this->headers = array();
154
            $this->subject = null;
155
            $this->message = null;
156
            $this->wrap = 78;
157
            $this->params = null;
158
            $this->attachments = array();
159
            $this->logs = array();
160
            $this->error = null;
161
            $this->uid = md5(uniqid(time())); 
162
            $this->smtpResponse = null; 
163
            $this->smtpConfig = array(
164
                'transport'          => 'plain', //plain,tls
165
                'hostname'           => 'localhost',
166
                'port'               => 25,
167
                'username'           => null,
168
                'password'           => null,
169
                'connection_timeout' => 30,
170
                'response_timeout'   => 10
171
            );         
172
            return $this;
173
        }
174
        
175
        /**
176
         * setFrom
177
         *
178
         * @param string $email The email to send as from.
179
         * @param string $name  The name to send as from.
180
         *
181
         * @return object
182
         */
183
        public function setFrom($email, $name = null) {
184
            $this->addMailHeader('From', (string) $email, (string) $name);  
185
            $this->from = $this->formatHeader($email, $name);          
186
            return $this;
187
        }
188
189
        /**
190
         * setTo
191
         *
192
         * @param string $email The email address to send to.
193
         * @param string $name  The name of the person to send to.
194
         *
195
         * @return object
196
         */
197
        public function setTo($email, $name = null) {
198
            $this->to[] = $this->formatHeader((string) $email, (string) $name);            
199
            return $this;
200
        }
201
        
202
        /**
203
         * Set destination using array
204
         * @params array $emails the list of recipient. This is an 
205
         * associative array name => email
206
         * @example array('John Doe' => '[email protected]')
207
         * 
208
         * @return object the current instance
209
         */
210
        public function setTos(array $emails) {
211
            foreach ($emails as $name => $email) {
212
                if (is_numeric($name)) {
213
                    $this->setTo($email);
214
                } else {
215
                    $this->setTo($email, $name);
216
                }
217
            }            
218
            return $this;
219
        }
220
221
        /**
222
         * setCc
223
         * 
224
         * @param array  $pairs  An array of name => email pairs.
225
         * @example array('John Doe' => '[email protected]')
226
         * @return object
227
         */
228
        public function setCc(array $pairs) {
229
            $this->cc = $pairs;
230
            return $this->addMailHeaders('Cc', $pairs);
231
        }
232
233
        /**
234
         * setBcc
235
         * 
236
         * @param array  $pairs  An array of name => email pairs.
237
         * @example array('John Doe' => '[email protected]')
238
         *
239
         * @return object
240
         */
241
        public function setBcc(array $pairs) {
242
            $this->bcc = $pairs;
243
            return $this->addMailHeaders('Bcc', $pairs);
244
        }
245
246
        /**
247
         * setReplyTo
248
         *
249
         * @param string $email
250
         * @param string $name
251
         *
252
         * @return object
253
         */
254
        public function setReplyTo($email, $name = null) {
255
            $this->replyTo = $this->formatHeader($email, $name);   
256
            return $this->addMailHeader('Reply-To', $email, $name);
257
        }
258
259
        /**
260
         * setHtml
261
         *
262
         * @return object
263
         */
264
        public function setHtml() {
265
            $this->addGenericHeader('Content-Type', 'text/html; charset="utf-8"');           
266
            return $this;
267
        }
268
269
        /**
270
         * setSubject
271
         *
272
         * @param string $subject The email subject
273
         *
274
         * @return object
275
         */
276
        public function setSubject($subject) {
277
            $this->subject = $this->encodeUtf8($this->filterOther((string) $subject));
278
            return $this;
279
        }
280
281
        /**
282
         * setMessage
283
         *
284
         * @param string $message The message to send.
285
         *
286
         * @return object
287
         */
288
        public function setMessage($message) {
289
            $this->message = str_replace("\n.", "\n..", (string) $message);            
290
            return $this;
291
        }
292
293
        /**
294
         * addAttachment
295
         *
296
         * @param string $path The file path to the attachment.
297
         * @param string $filename The filename of the attachment when emailed.
298
         * 
299
         * @return object
300
         */
301
        public function addAttachment($path, $filename = null) {
302
            if (!file_exists($path)) {
303
                show_error('The email attachment file [' . $path . '] does not exists.');
304
                return $this;
305
            }
306
            if (empty($filename)) {
307
                $filename = basename($path);
308
            }
309
            $filename = $this->encodeUtf8($this->filterOther((string) $filename));
310
            $data = $this->getAttachmentData($path);
311
            $this->attachments[] = array(
312
                                        'path' => $path,
313
                                        'file' => $filename,
314
                                        'data' => chunk_split(base64_encode($data))
315
                                    );            
316
            return $this;
317
        }
318
319
        /**
320
         * addMailHeader
321
         *
322
         * @param string $header The header to add.
323
         * @param string $email  The email to add.
324
         * @param string $name   The name to add.
325
         *
326
         * @return object
327
         */
328
        public function addMailHeader($header, $email, $name = null) {
329
            $address = $this->formatHeader((string) $email, (string) $name);
330
            $this->headers[$header] = $address;            
331
            return $this;
332
        }
333
334
        /**
335
         * addMailHeaders
336
         *
337
         * @param string $header The header to add.
338
         * @param array  $pairs  An array of name => email pairs.
339
         *
340
         * @return object
341
         */
342
        public function addMailHeaders($header, array $pairs) {
343
            if (count($pairs) === 0) {
344
                show_error('You must pass at least one name => email pair.');
345
                return $this;
346
            }
347
            $addresses = array();
348
            foreach ($pairs as $name => $email) {
349
                if (is_numeric($name)) {
350
                   $name = null;
351
                }
352
                $addresses[] = $this->formatHeader($email, $name);
353
            }
354
            $this->addGenericHeader($header, implode(',', $addresses));            
355
            return $this;
356
        }
357
358
        /**
359
         * addGenericHeader
360
         *
361
         * @param string $name The generic header to add.
362
         * @param mixed  $value  The value of the header.
363
         *
364
         * @return object
365
         */
366
        public function addGenericHeader($name, $value) {
367
            $this->headers[$name] = $value;
368
            return $this;
369
        }
370
371
        /**
372
         * getHeaders
373
         *
374
         * Return the headers registered so far as an array.
375
         *
376
         * @return array
377
         */
378
        public function getHeaders() {
379
            return $this->headers;
380
        }
381
382
        /**
383
         * setAdditionalParameters
384
         *
385
         * Such as "[email protected]
386
         *
387
         * @param string $additionalParameters The addition mail parameter.
388
         *
389
         * @return object
390
         */
391
        public function setParameters($additionalParameters) {
392
            $this->params = (string) $additionalParameters;            
393
            return $this;
394
        }
395
396
        /**
397
         * getAdditionalParameters
398
         *
399
         * @return string
400
         */
401
        public function getParameters() {
402
            return $this->params;
403
        }
404
405
        /**
406
         * setWrap
407
         *
408
         * @param int $wrap The number of characters at which the message will wrap.
409
         *
410
         * @return object
411
         */
412
        public function setWrap($wrap = 78) {
413
            $wrap = (int) $wrap;
414
            if ($wrap < 1) {
415
                $wrap = 78;
416
            }
417
            $this->wrap = $wrap;            
418
            return $this;
419
        }
420
421
        /**
422
         * hasAttachments
423
         * 
424
         * Checks if the email has any registered attachments.
425
         *
426
         * @return bool
427
         */
428
        public function hasAttachments() {
429
            return !empty($this->attachments);
430
        }
431
432
        /**
433
         * Set the send mail protocol to "mail"
434
         *
435
         * @return object the current instance
436
         */
437
        public function useMail() {
438
            $this->protocol = 'mail';
439
            return $this;
440
        }
441
442
        /**
443
         * Set the send mail protocol to "smtp"
444
         * 
445
         * @return object the current instance
446
         */
447
        public function useSmtp() {
448
            $this->protocol = 'smtp';
449
            return $this;
450
        }
451
452
        /**
453
         * Set the smtp configuration.
454
         *
455
         * The possible values are:
456
         * array(
457
         *      'transport'          => 'plain', //plain,tls
458
         *      'hostname'           => 'localhost', //smtp server hostname
459
         *      'port'               => 25, //smtp server port
460
         *      'username'           => null, //smtp username if required
461
         *      'password'           => null, //smtp password if required
462
         *      'connection_timeout' => 30, //connection timeout for smtp server socket
463
         *      'response_timeout'   => 10 //response timeout for smtp server socket reply
464
         *   );    
465
         *
466
         * NOTE: the configuration will be merged with the existing one
467
         *
468
         * @param array $config the configuration
469
         *
470
         * @return object the current instance
471
         */
472
        public function setSmtpConfig(array $config = array()) {
473
            $this->smtpConfig = array_merge($this->smtpConfig, $config);
474
            return $this;
475
        }
476
477
        /**
478
         * send the email
479
         *
480
         * @return boolean
481
         */
482
        public function send() {
483
            if (empty($this->to)) {
484
                show_error('Unable to send mail, no destination address has been set.');
485
                return false;
486
            }
487
            if (empty($this->from)) {
488
                show_error('Unable to send mail, no sender address has been set.');
489
                return false;
490
            }
491
            if ($this->protocol == 'mail') {
492
                return $this->sendMail();
493
            } else if ($this->protocol == 'smtp') {
494
                return $this->sendSmtp();
495
            }   
496
            return false;
497
        }
498
499
500
        /**
501
         * Return the last error when sending mail
502
         * @return string|null
503
         */
504
        public function getError() {
505
            return $this->error;
506
        }
507
508
        /**
509
         * Return the sending mail logs content
510
         * @return array
511
         */
512
        public function getLogs() {
513
            return $this->logs;
514
        }
515
516
        /**
517
         * Get attachment data
518
         *
519
         * @param string $path The path to the attachment file.
520
         *
521
         * @return string|boolean
522
         */
523
        protected function getAttachmentData($path) {
524
            $filesize = filesize($path);
525
            $handle = fopen($path, 'r');
526
            $attachment = null;
527
            if (is_resource($handle)) {
528
                $attachment = fread($handle, $filesize);
529
                fclose($handle);
530
            }
531
            return $attachment;
532
        }
533
534
        /**
535
         * assembleAttachment
536
         *
537
         * @return object
538
         */
539
        protected function setAttachmentHeaders() {
540
            $this->headers['MIME-Version'] = '1.0';
541
            $this->headers['Content-Type'] = "multipart/mixed; boundary=\"{$this->uid}\"";
542
            return $this;
543
        }
544
545
        /**
546
         * assembleAttachmentBody
547
         *
548
         * @return string
549
         */
550
        protected function assembleAttachmentBody() {
551
            $body = array();
552
            $body[] = 'This is a multi-part message in MIME format.';
553
            $body[] = "--{$this->uid}";
554
            $body[] = 'Content-Type: text/html; charset="utf-8"';
555
            $body[] = 'Content-Transfer-Encoding: base64';
556
            $body[] = PHP_EOL;
557
            $body[] = chunk_split(base64_encode($this->message));
558
            $body[] = PHP_EOL;
559
            $body[] = "--{$this->uid}";
560
561
            foreach ($this->attachments as $attachment) {
562
                $body[] = $this->getAttachmentMimeTemplate($attachment);
563
            }
564
            return implode(PHP_EOL, $body) . '--';
565
        }
566
567
        /**
568
         * getAttachmentMimeTemplate
569
         *
570
         * @param array  $attachment An array containing 'file' and 'data' keys.
571
         *
572
         * @return string
573
         */
574
        protected function getAttachmentMimeTemplate($attachment) {
575
            $file = $attachment['file'];
576
            $data = $attachment['data'];
577
578
            $head = array();
579
            $head[] = "Content-Type: application/octet-stream; name=\"{$file}\"";
580
            $head[] = 'Content-Transfer-Encoding: base64';
581
            $head[] = "Content-Disposition: attachment; filename=\"{$file}\"";
582
            $head[] = "";
583
            $head[] = $data;
584
            $head[] = "";
585
            $head[] = "--{$this->uid}";
586
587
            return implode(PHP_EOL, $head);
588
        }
589
590
        /**
591
         * formatHeader
592
         *
593
         * Formats a display address for emails according to RFC2822 e.g.
594
         * Name <[email protected]>
595
         *
596
         * @param string $email The email address.
597
         * @param string $name  The display name.
598
         *
599
         * @return string
600
         */
601
        protected function formatHeader($email, $name = null) {
602
            $email = $this->filterEmail((string) $email);
603
            if (empty($name)) {
604
                return $email;
605
            }
606
            $name = $this->encodeUtf8($this->filterName((string) $name));
607
            return sprintf('"%s" <%s>', $name, $email);
608
        }
609
610
        /**
611
         * encodeUtf8
612
         *
613
         * @param string $value The value to encode.
614
         *
615
         * @return string
616
         */
617
        protected function encodeUtf8($value) {
618
            $value = trim($value);
619
            if (preg_match('/(\s)/', $value)) {
620
                return $this->encodeUtf8Words($value);
621
            }
622
            return $this->encodeUtf8Word($value);
623
        }
624
625
        /**
626
         * encodeUtf8Word
627
         *
628
         * @param string $value The word to encode.
629
         *
630
         * @return string
631
         */
632
        protected function encodeUtf8Word($value) {
633
            return sprintf('=?UTF-8?B?%s?=', base64_encode($value));
634
        }
635
636
        /**
637
         * encodeUtf8Words
638
         *
639
         * @param string $value The words to encode.
640
         *
641
         * @return string
642
         */
643
        protected function encodeUtf8Words($value) {
644
            $words = explode(' ', $value);
645
            $encoded = array();
646
            foreach ($words as $word) {
647
                $encoded[] = $this->encodeUtf8Word($word);
648
            }
649
            return join($this->encodeUtf8Word(' '), $encoded);
650
        }
651
652
        /**
653
         * filterEmail
654
         *
655
         * Removes any carriage return, line feed, tab, double quote, comma
656
         * and angle bracket characters before sanitizing the email address.
657
         *
658
         * @param string $email The email to filter.
659
         *
660
         * @return string
661
         */
662
        protected function filterEmail($email) {
663
            $rule = array(
664
                "\r" => '',
665
                "\n" => '',
666
                "\t" => '',
667
                '"'  => '',
668
                ','  => '',
669
                '<'  => '',
670
                '>'  => ''
671
            );
672
            $email = strtr($email, $rule);
673
            $email = filter_var($email, FILTER_SANITIZE_EMAIL);
674
            return $email;
675
        }
676
677
        /**
678
         * filterName
679
         *
680
         * Removes any carriage return, line feed or tab characters. Replaces
681
         * double quotes with single quotes and angle brackets with square
682
         * brackets, before sanitizing the string and stripping out html tags.
683
         *
684
         * @param string $name The name to filter.
685
         *
686
         * @return string
687
         */
688
        protected function filterName($name) {
689
            $rule = array(
690
                "\r" => '',
691
                "\n" => '',
692
                "\t" => '',
693
                '"'  => "'",
694
                '<'  => '[',
695
                '>'  => ']',
696
            );
697
            $filtered = filter_var(
698
                $name,
699
                FILTER_SANITIZE_STRING,
700
                FILTER_FLAG_NO_ENCODE_QUOTES
701
            );
702
            return trim(strtr($filtered, $rule));
703
        }
704
705
        /**
706
         * filterOther
707
         *
708
         * Removes ASCII control characters including any carriage return, line
709
         * feed or tab characters.
710
         *
711
         * @param string $data The data to filter.
712
         *
713
         * @return string
714
         */
715
        protected function filterOther($data) {
716
            return filter_var($data, FILTER_UNSAFE_RAW, FILTER_FLAG_STRIP_LOW);
717
        }
718
719
        /**
720
         * Get destinataire for send
721
         *
722
         * @return string
723
         */
724
        protected function getToForSend() {
725
            return join(', ', $this->to);
726
        }
727
728
        /**
729
         * getHeadersForSend
730
         *
731
         * @return string
732
         */
733
        protected function getHeadersForSend() {
734
            $headers = null;
735
            foreach ($this->headers as $key => $value) {
736
                $headers .= $key . ': ' . $value . PHP_EOL;
737
            }
738
            return $headers;
739
        }
740
741
         /**
742
         * Get the attachment message for send or the simple message
743
         * @return string
744
         */
745
        protected function getMessageWithAttachmentForSend() {
746
            $message = wordwrap($this->message, $this->wrap);
747
            if ($this->hasAttachments()) {
748
                $this->setAttachmentHeaders();
749
                $message  = $this->assembleAttachmentBody();
750
            }
751
            return $message;
752
        }
753
754
        /**
755
         * Send smtp command to server
756
         * @param  string $command the smtp command
757
         * 
758
         * @return object the current instance
759
         */
760
        protected function sendCommand($command) {
761
            if (is_resource($this->smtpSocket)) {
762
                fputs($this->smtpSocket, $command . PHP_EOL);
763
            }
764
            $this->smtpResponse = $this->getSmtpServerResponse();
765
            return $this;
766
        }
767
768
        /**
769
         * Send EHLO or HELO command to smtp server
770
         * @return boolean true if server response is OK otherwise will return false
771
         */
772
        protected function sendHelloCommand() {
773
            $responseCode = $this->sendCommand('EHLO ' . $this->getSmtpClientHostname())
774
                                 ->getSmtpResponseCode();
775
            if ($responseCode !== 250) {
776
                $this->error = $this->smtpResponse;
777
                return false;
778
            }
779
            return true;
780
        }
781
782
        /**
783
         * Get the smtp server response
784
         * @return mixed
785
         */
786
        protected function getSmtpServerResponse() {
787
            $response = '';
788
            if (is_resource($this->smtpSocket)) {
789
                stream_set_timeout($this->smtpSocket, $this->smtpConfig['response_timeout']);
790
                while (($line = fgets($this->smtpSocket)) !== false) {
791
                    $response .= trim($line) . "\n";
792
                    if (substr($line, 3, 1) == ' ') {
793
                        break;
794
                    }
795
                }
796
                $response = trim($response);
797
            }
798
            return $response;
799
        }
800
801
        /**
802
         * Return the last response code
803
         *
804
         * @param string|null $response the response string if is null 
805
         * will use the last smtp response
806
         * 
807
         * @return integer the 3 digit of response code
808
         */
809
        protected function getSmtpResponseCode($response = null) {
810
            if ($response === null) {
811
                $response = $this->smtpResponse;
812
            }
813
            return (int) substr($response, 0, 3);
814
        }
815
816
        /**
817
         * Establish connection to smtp server
818
         * @return boolean 
819
         */
820
        protected function smtpConnection() {
821
            $this->smtpSocket = fsockopen(
822
                                        $this->smtpConfig['hostname'],
823
                                        $this->smtpConfig['port'],
824
                                        $errorNumber,
825
                                        $errorMessage,
826
                                        $this->smtpConfig['connection_timeout']
827
                                    );
828
829
            if (! is_resource($this->smtpSocket)) {
830
                $this->error = $errorNumber . ': ' . $errorMessage;
831
                return false;
832
            }
833
            $response = $this->getSmtpServerResponse();
834
            $code = $this->getSmtpResponseCode($response);
835
            if ($code !== 220) {
836
                $this->error = $response;
837
                return false;
838
            }
839
            $this->logs['CONNECTION'] = $response;
840
            $hello = $this->sendHelloCommand();
841
            $this->logs['HELLO'] = $this->smtpResponse; 
842
            if (!$hello) {
843
                return false;
844
            }
845
846
            //Check if can use TLS connection to server
847
            if (!$this->checkForSmtpConnectionTls()) {
848
                return false;
849
            }
850
851
            //Authentication of the client
852
            if (!$this->smtpAuthentication()) {
853
                return false;
854
            }
855
            return true;
856
        }
857
858
        /**
859
         * Check if server support TLS connection
860
         * @return boolean
861
         */
862
        protected function checkForSmtpConnectionTls() {
863
            if ($this->smtpConfig['transport'] == 'tls') {
864
                $tlsCode = $this->sendCommand('STARTTLS')->getSmtpResponseCode();
865
                $this->logs['STARTTLS'] = $this->smtpResponse;
866
                if ($tlsCode === 220) {
867
                    /**
868
                     * STREAM_CRYPTO_METHOD_TLS_CLIENT is quite the mess ...
869
                     *
870
                     * - On PHP <5.6 it doesn't even mean TLS, but SSL 2.0, and there's no option to use actual TLS
871
                     * - On PHP 5.6.0-5.6.6, >=7.2 it means negotiation with any of TLS 1.0, 1.1, 1.2
872
                     * - On PHP 5.6.7-7.1.* it means only TLS 1.0
873
                     *
874
                     * We want the negotiation, so we'll force it below ...
875
                     */
876
                    if (is_resource($this->smtpSocket)) {
877
                        $method = STREAM_CRYPTO_METHOD_TLS_CLIENT;
878
                        if(version_compare(PHP_VERSION, '5.6', '>=')) {
879
                            $method = STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT | STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT | STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
880
                        }
881
                        stream_socket_enable_crypto($this->smtpSocket, true, $method);
882
                    }
883
                    $hello = $this->sendHelloCommand();
884
                    $this->logs['HELLO_TLS'] = $this->smtpResponse; 
885
                    if (!$hello) {
886
                        return false;
887
                    }
888
                }
889
            }
890
            return true;
891
        }
892
893
        /**
894
         * Client authentication
895
         * @return boolean
896
         */
897
        protected function smtpAuthentication() {
898
            $authCode = $this->sendCommand('AUTH LOGIN')->getSmtpResponseCode();
899
            $this->logs['AUTH_LOGIN'] = $this->smtpResponse;
900
            if ($authCode === 334) {
901
                $this->sendCommand(base64_encode($this->smtpConfig['username']))->getSmtpResponseCode();
902
                $code = $this->sendCommand(base64_encode($this->smtpConfig['password']))->getSmtpResponseCode();
903
                $this->logs['CLIENT_AUTH'] = $this->smtpResponse;
904
                if ($code !== 235) {
905
                    $this->error = $this->smtpResponse;
906
                    return false;
907
                }
908
            }
909
            return true;
910
        }
911
912
        /**
913
         * Send mail using "mail" protocol
914
         * @return boolean
915
         */
916
        protected function sendMail() {
917
            $to = $this->getToForSend();
918
            $message = $this->getMessageWithAttachmentForSend();
919
            $headers = $this->getHeadersForSend(); 
920
            $this->logger->info('Sending new mail using mail protocol, the information are listed below: '
921
                                  . 'destination: ' . $to . ', headers: ' . $headers . ', message: ' . $message);
922
            $result = mail($to, $this->subject, $message, $headers, $this->params);
923
            if (!$result) {
924
                $this->error = 'Error when sending mail using mail protocol';
925
            }
926
            return $result;
927
        }
928
929
         /**
930
         * Send mail using "smtp" protocol
931
         * @return boolean
932
         */
933
        protected function sendSmtp() {
934
            if (!$this->smtpConnection()) {
935
                return false;
936
            }
937
            $to = $this->getToForSend();
938
            $this->setSmtpAdditionnalHeaders();
939
            $message = $this->getMessageWithAttachmentForSend();
940
            $headers = $this->getHeadersForSend();
941
            $this->logger->info('Sending new mail using SMTP protocol, the information are listed below: '
942
                                  . 'destination: ' . $to . ', headers: ' . $headers . ', message: ' . $message);
943
            $recipients = array_merge($this->to, $this->cc, $this->bcc);
944
            $commands = array(
945
                                'mail_from' => array('MAIL FROM: <' . $this->from . '>', 'MAIL_FROM', 250),
946
                                'recipients' => array($recipients, 'RECIPIENTS'),
947
                                'date_1' => array('DATA', 'DATA_1', 354),
948
                                'date_2' => array($headers . PHP_EOL . $message . PHP_EOL . '.', 'DATA_2', 250),
949
                            );
950
            foreach ($commands as $key => $value) {
951
                if ($key == 'recipients') {
952
                    foreach ($value[0] as $address) {
953
                        $code = $this->sendCommand('RCPT TO: <' . $address . '>')->getSmtpResponseCode();
954
                        $this->logs[$value[1]][] = $this->smtpResponse;
955
                        if ($code !== 250) {
956
                            $this->error = $this->smtpResponse;
957
                            return false;
958
                        }
959
                    }
960
                } else {
961
                        $code = $this->sendCommand($value[0])->getSmtpResponseCode();
962
                        $this->logs[$value[1]] = $this->smtpResponse;
963
                        if ($code !== $value[2]) {
964
                            $this->error = $this->smtpResponse;
965
                            return false;
966
                        }
967
                }
968
            }
969
            $this->sendCommand('QUIT');
970
            $this->logs['QUIT'] = $this->smtpResponse;
971
            return empty($this->error);
972
        }
973
974
        /**
975
         * Set the additionnal headers for SMTP protocol
976
         */
977
        protected function setSmtpAdditionnalHeaders() {
978
            $to = $this->getToForSend();
979
            $additionalHeaders = array(
980
                'Date' => date('r'),
981
                'Subject' => $this->subject,
982
                'Return-Path' => $this->from,
983
                'To' => $to
984
            );
985
            foreach ($additionalHeaders as $key => $value) {
986
                if (! isset($this->headers[$key])) {
987
                    $this->headers[$key] = $value;
988
                }
989
            }
990
        }
991
992
993
         /**
994
         * Return the client hostname for SMTP
995
         * 
996
         * There are only two legal types of hostname - either a fully
997
         * qualified domain name (eg: "mail.example.com") or an IP literal
998
         * (eg: "[1.2.3.4]").
999
         *
1000
         * @link    https://tools.ietf.org/html/rfc5321#section-2.3.5
1001
         * @link    http://cbl.abuseat.org/namingproblems.html
1002
         * @return string
1003
         */
1004
        protected function getSmtpClientHostname() {
1005
            $globals = &class_loader('GlobalVar', 'classes');
1006
            if ($globals->server('SERVER_NAME')) {
1007
                return $globals->server('SERVER_NAME');
1008
            }
1009
            if ($globals->server('SERVER_ADDR')) {
1010
                return $globals->server('SERVER_ADDR');
1011
            }
1012
            return '[127.0.0.1]';
1013
        }
1014
1015
        /**
1016
         * Class desctructor
1017
         * @codeCoverageIgnore
1018
         */
1019
        public function __destruct() {
1020
            if (is_resource($this->smtpSocket)) {
1021
                fclose($this->smtpSocket);
1022
            }
1023
        }
1024
    }
1025