Test Failed
Push — 1.0.0-dev ( 90e1c2...b109e3 )
by nguereza
02:32
created

Email::getHeaders()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 2
rs 10
c 4
b 0
f 0
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
         * getTo
223
         *
224
         * Return an array of formatted To addresses.
225
         *
226
         * @return array
227
         */
228
        public function getTo() {
229
            return $this->to;
230
        }
231
232
        /**
233
         * setCc
234
         * 
235
         * @param array  $pairs  An array of name => email pairs.
236
         * @example array('John Doe' => '[email protected]')
237
         * @return object
238
         */
239
        public function setCc(array $pairs) {
240
            $this->cc = $pairs;
241
            return $this->addMailHeaders('Cc', $pairs);
242
        }
243
244
        /**
245
         * Return the list of Cc
246
         *
247
         * @return array
248
         */
249
        public function getCc() {
250
            return $this->cc;
251
        }
252
253
        /**
254
         * setBcc
255
         * 
256
         * @param array  $pairs  An array of name => email pairs.
257
         * @example array('John Doe' => '[email protected]')
258
         *
259
         * @return object
260
         */
261
        public function setBcc(array $pairs) {
262
            $this->bcc = $pairs;
263
            return $this->addMailHeaders('Bcc', $pairs);
264
        }
265
266
        /**
267
         * Return the list of Bcc
268
         *
269
         * @return array
270
         */
271
        public function getBcc() {
272
            return $this->bcc;
273
        }
274
275
        /**
276
         * setReplyTo
277
         *
278
         * @param string $email
279
         * @param string $name
280
         *
281
         * @return object
282
         */
283
        public function setReplyTo($email, $name = null) {
284
            $this->replyTo = $this->formatHeader($email, $name);   
285
            return $this->addMailHeader('Reply-To', $email, $name);
286
        }
287
288
        /**
289
         * setHtml
290
         *
291
         * @return object
292
         */
293
        public function setHtml() {
294
            $this->addGenericHeader('Content-Type', 'text/html; charset="utf-8"');           
295
            return $this;
296
        }
297
298
        /**
299
         * setSubject
300
         *
301
         * @param string $subject The email subject
302
         *
303
         * @return object
304
         */
305
        public function setSubject($subject) {
306
            $this->subject = $this->encodeUtf8(
307
            $this->filterOther((string) $subject));
308
            return $this;
309
        }
310
311
        /**
312
         * getSubject function.
313
         *
314
         * @return string
315
         */
316
        public function getSubject() {
317
            return $this->subject;
318
        }
319
320
        /**
321
         * setMessage
322
         *
323
         * @param string $message The message to send.
324
         *
325
         * @return object
326
         */
327
        public function setMessage($message) {
328
            $this->message = str_replace("\n.", "\n..", (string) $message);            
329
            return $this;
330
        }
331
332
        /**
333
         * getMessage
334
         *
335
         * @return string
336
         */
337
        public function getMessage() {
338
            return $this->message;
339
        }
340
341
        /**
342
         * addAttachment
343
         *
344
         * @param string $path The file path to the attachment.
345
         * @param string $filename The filename of the attachment when emailed.
346
         * 
347
         * @return object
348
         */
349
        public function addAttachment($path, $filename = null) {
350
            if (!file_exists($path)) {
351
                show_error('The email attachment file [' . $path . '] does not exists.');
352
                return $this;
353
            }
354
            if (empty($filename)) {
355
                $filename = basename($path);
356
            }
357
            $filename = $this->encodeUtf8($this->filterOther((string) $filename));
358
            $data = $this->getAttachmentData($path);
359
            $this->attachments[] = array(
360
                                        'path' => $path,
361
                                        'file' => $filename,
362
                                        'data' => chunk_split(base64_encode($data))
363
                                    );            
364
            return $this;
365
        }
366
367
        /**
368
         * addMailHeader
369
         *
370
         * @param string $header The header to add.
371
         * @param string $email  The email to add.
372
         * @param string $name   The name to add.
373
         *
374
         * @return object
375
         */
376
        public function addMailHeader($header, $email, $name = null) {
377
            $address = $this->formatHeader((string) $email, (string) $name);
378
            $this->headers[$header] = $address;            
379
            return $this;
380
        }
381
382
        /**
383
         * addMailHeaders
384
         *
385
         * @param string $header The header to add.
386
         * @param array  $pairs  An array of name => email pairs.
387
         *
388
         * @return object
389
         */
390
        public function addMailHeaders($header, array $pairs) {
391
            if (count($pairs) === 0) {
392
                show_error('You must pass at least one name => email pair.');
393
                return $this;
394
            }
395
            $addresses = array();
396
            foreach ($pairs as $name => $email) {
397
                if (is_numeric($name)) {
398
                   $name = null;
399
                }
400
                $addresses[] = $this->formatHeader($email, $name);
401
            }
402
            $this->addGenericHeader($header, implode(',', $addresses));            
403
            return $this;
404
        }
405
406
        /**
407
         * addGenericHeader
408
         *
409
         * @param string $name The generic header to add.
410
         * @param mixed  $value  The value of the header.
411
         *
412
         * @return object
413
         */
414
        public function addGenericHeader($name, $value) {
415
            $this->headers[$name] = $value;
416
            return $this;
417
        }
418
419
        /**
420
         * getHeaders
421
         *
422
         * Return the headers registered so far as an array.
423
         *
424
         * @return array
425
         */
426
        public function getHeaders() {
427
            return $this->headers;
428
        }
429
430
        /**
431
         * setAdditionalParameters
432
         *
433
         * Such as "[email protected]
434
         *
435
         * @param string $additionalParameters The addition mail parameter.
436
         *
437
         * @return object
438
         */
439
        public function setParameters($additionalParameters) {
440
            $this->params = (string) $additionalParameters;            
441
            return $this;
442
        }
443
444
        /**
445
         * getAdditionalParameters
446
         *
447
         * @return string
448
         */
449
        public function getParameters() {
450
            return $this->params;
451
        }
452
453
        /**
454
         * setWrap
455
         *
456
         * @param int $wrap The number of characters at which the message will wrap.
457
         *
458
         * @return object
459
         */
460
        public function setWrap($wrap = 78) {
461
            $wrap = (int) $wrap;
462
            if ($wrap < 1) {
463
                $wrap = 78;
464
            }
465
            $this->wrap = $wrap;            
466
            return $this;
467
        }
468
469
        /**
470
         * getWrap
471
         *
472
         * @return int
473
         */
474
        public function getWrap() {
475
            return $this->wrap;
476
        }
477
478
        /**
479
         * hasAttachments
480
         * 
481
         * Checks if the email has any registered attachments.
482
         *
483
         * @return bool
484
         */
485
        public function hasAttachments() {
486
            return !empty($this->attachments);
487
        }
488
489
        /**
490
         * getWrapMessage
491
         *
492
         * @return string
493
         */
494
        public function getWrapMessage() {
495
            return wordwrap($this->message, $this->wrap);
496
        }
497
    
498
        /**
499
         * Return the send mail protocol
500
         * @return string
501
         */
502
        public function getProtocol() {
503
            return $this->protocol;
504
        }
505
506
        /**
507
         * Set the send mail protocol to "mail"
508
         *
509
         * @return object the current instance
510
         */
511
        public function useMail() {
512
            $this->protocol = 'mail';
513
            return $this;
514
        }
515
516
        /**
517
         * Set the send mail protocol to "smtp"
518
         * 
519
         * @return object the current instance
520
         */
521
        public function useSmtp() {
522
            $this->protocol = 'smtp';
523
            return $this;
524
        }
525
526
        /**
527
         * Return all the smtp configurations
528
         * @return array
529
         */
530
        public function getSmtpConfigs() {
531
            return $this->smtpConfig;
532
        }
533
534
        /**
535
         * Return the smtp configuration value for the given name
536
         *
537
         * @param string $name the configuration name to get value
538
         * @param mixed $default the default value to return if can not find configuration
539
         * 
540
         * @return mixed the configuration value if exist or $default will be returned
541
         * returned
542
         */
543
        public function getSmtpConfig($name, $default = null) {
544
            $value = $default;
545
            if (array_key_exists($name, $this->smtpConfig)) {
546
                $value = $this->smtpConfig[$name];
547
            }
548
            return $value;
549
        }
550
551
        /**
552
         * Set the smtp configuration.
553
         *
554
         * The possible values are:
555
         * array(
556
         *      'transport'          => 'plain', //plain,tls
557
         *      'hostname'           => 'localhost', //smtp server hostname
558
         *      'port'               => 25, //smtp server port
559
         *      'username'           => null, //smtp username if required
560
         *      'password'           => null, //smtp password if required
561
         *      'connection_timeout' => 30, //connection timeout for smtp server socket
562
         *      'response_timeout'   => 10 //response timeout for smtp server socket reply
563
         *   );    
564
         *
565
         * NOTE: the configuration will be merged with the existing one
566
         *
567
         * @param array $config the configuration
568
         *
569
         * @return object the current instance
570
         */
571
        public function setSmtpConfig(array $config = array()) {
572
            $this->smtpConfig = array_merge($this->smtpConfig, $config);
573
            return $this;
574
        }
575
576
        /**
577
         * send the email
578
         *
579
         * @return boolean
580
         */
581
        public function send() {
582
            if (empty($this->to)) {
583
                show_error('Unable to send mail, no destination address has been set.');
584
                return false;
585
            }
586
            if (empty($this->from)) {
587
                show_error('Unable to send mail, no sender address has been set.');
588
                return false;
589
            }
590
            if ($this->protocol == 'mail') {
591
                return $this->sendMail();
592
            } else if ($this->protocol == 'smtp') {
593
                return $this->sendSmtp();
594
            }   
595
            return false;
596
        }
597
598
599
        /**
600
         * Return the last error when sending mail
601
         * @return string|null
602
         */
603
        public function getError() {
604
            return $this->error;
605
        }
606
607
        /**
608
         * Return the sending mail logs content
609
         * @return array
610
         */
611
        public function getLogs() {
612
            return $this->logs;
613
        }
614
615
        /**
616
         * Get attachment data
617
         *
618
         * @param string $path The path to the attachment file.
619
         *
620
         * @return string|boolean
621
         */
622
        protected function getAttachmentData($path) {
623
            $filesize = filesize($path);
624
            $handle = fopen($path, 'r');
625
            $attachment = null;
626
            if (is_resource($handle)) {
627
                $attachment = fread($handle, $filesize);
628
                fclose($handle);
629
            }
630
            return $attachment;
631
        }
632
633
        /**
634
         * assembleAttachment
635
         *
636
         * @return object
637
         */
638
        protected function setAttachmentHeaders() {
639
            $this->headers['MIME-Version'] = '1.0';
640
            $this->headers['Content-Type'] = "multipart/mixed; boundary=\"{$this->uid}\"";
641
            return $this;
642
        }
643
644
        /**
645
         * assembleAttachmentBody
646
         *
647
         * @return string
648
         */
649
        protected function assembleAttachmentBody() {
650
            $body = array();
651
            $body[] = 'This is a multi-part message in MIME format.';
652
            $body[] = "--{$this->uid}";
653
            $body[] = 'Content-Type: text/html; charset="utf-8"';
654
            $body[] = 'Content-Transfer-Encoding: base64';
655
            $body[] = PHP_EOL;
656
            $body[] = chunk_split(base64_encode($this->message));
657
            $body[] = PHP_EOL;
658
            $body[] = "--{$this->uid}";
659
660
            foreach ($this->attachments as $attachment) {
661
                $body[] = $this->getAttachmentMimeTemplate($attachment);
662
            }
663
            return implode(PHP_EOL, $body) . '--';
664
        }
665
666
        /**
667
         * getAttachmentMimeTemplate
668
         *
669
         * @param array  $attachment An array containing 'file' and 'data' keys.
670
         *
671
         * @return string
672
         */
673
        protected function getAttachmentMimeTemplate($attachment) {
674
            $file = $attachment['file'];
675
            $data = $attachment['data'];
676
677
            $head = array();
678
            $head[] = "Content-Type: application/octet-stream; name=\"{$file}\"";
679
            $head[] = 'Content-Transfer-Encoding: base64';
680
            $head[] = "Content-Disposition: attachment; filename=\"{$file}\"";
681
            $head[] = "";
682
            $head[] = $data;
683
            $head[] = "";
684
            $head[] = "--{$this->uid}";
685
686
            return implode(PHP_EOL, $head);
687
        }
688
689
        /**
690
         * formatHeader
691
         *
692
         * Formats a display address for emails according to RFC2822 e.g.
693
         * Name <[email protected]>
694
         *
695
         * @param string $email The email address.
696
         * @param string $name  The display name.
697
         *
698
         * @return string
699
         */
700
        protected function formatHeader($email, $name = null) {
701
            $email = $this->filterEmail((string) $email);
702
            if (empty($name)) {
703
                return $email;
704
            }
705
            $name = $this->encodeUtf8($this->filterName((string) $name));
706
            return sprintf('"%s" <%s>', $name, $email);
707
        }
708
709
        /**
710
         * encodeUtf8
711
         *
712
         * @param string $value The value to encode.
713
         *
714
         * @return string
715
         */
716
        protected function encodeUtf8($value) {
717
            $value = trim($value);
718
            if (preg_match('/(\s)/', $value)) {
719
                return $this->encodeUtf8Words($value);
720
            }
721
            return $this->encodeUtf8Word($value);
722
        }
723
724
        /**
725
         * encodeUtf8Word
726
         *
727
         * @param string $value The word to encode.
728
         *
729
         * @return string
730
         */
731
        protected function encodeUtf8Word($value) {
732
            return sprintf('=?UTF-8?B?%s?=', base64_encode($value));
733
        }
734
735
        /**
736
         * encodeUtf8Words
737
         *
738
         * @param string $value The words to encode.
739
         *
740
         * @return string
741
         */
742
        protected function encodeUtf8Words($value) {
743
            $words = explode(' ', $value);
744
            $encoded = array();
745
            foreach ($words as $word) {
746
                $encoded[] = $this->encodeUtf8Word($word);
747
            }
748
            return join($this->encodeUtf8Word(' '), $encoded);
749
        }
750
751
        /**
752
         * filterEmail
753
         *
754
         * Removes any carriage return, line feed, tab, double quote, comma
755
         * and angle bracket characters before sanitizing the email address.
756
         *
757
         * @param string $email The email to filter.
758
         *
759
         * @return string
760
         */
761
        protected function filterEmail($email) {
762
            $rule = array(
763
                "\r" => '',
764
                "\n" => '',
765
                "\t" => '',
766
                '"'  => '',
767
                ','  => '',
768
                '<'  => '',
769
                '>'  => ''
770
            );
771
            $email = strtr($email, $rule);
772
            $email = filter_var($email, FILTER_SANITIZE_EMAIL);
773
            return $email;
774
        }
775
776
        /**
777
         * filterName
778
         *
779
         * Removes any carriage return, line feed or tab characters. Replaces
780
         * double quotes with single quotes and angle brackets with square
781
         * brackets, before sanitizing the string and stripping out html tags.
782
         *
783
         * @param string $name The name to filter.
784
         *
785
         * @return string
786
         */
787
        protected function filterName($name) {
788
            $rule = array(
789
                "\r" => '',
790
                "\n" => '',
791
                "\t" => '',
792
                '"'  => "'",
793
                '<'  => '[',
794
                '>'  => ']',
795
            );
796
            $filtered = filter_var(
797
                $name,
798
                FILTER_SANITIZE_STRING,
799
                FILTER_FLAG_NO_ENCODE_QUOTES
800
            );
801
            return trim(strtr($filtered, $rule));
802
        }
803
804
        /**
805
         * filterOther
806
         *
807
         * Removes ASCII control characters including any carriage return, line
808
         * feed or tab characters.
809
         *
810
         * @param string $data The data to filter.
811
         *
812
         * @return string
813
         */
814
        protected function filterOther($data) {
815
            return filter_var($data, FILTER_UNSAFE_RAW, FILTER_FLAG_STRIP_LOW);
816
        }
817
818
        /**
819
         * Get destinataire for send
820
         *
821
         * @return string
822
         */
823
        protected function getToForSend() {
824
            return join(', ', $this->to);
825
        }
826
827
        /**
828
         * getHeadersForSend
829
         *
830
         * @return string
831
         */
832
        protected function getHeadersForSend() {
833
            $headers = null;
834
            foreach ($this->headers as $key => $value) {
835
                $headers .= $key . ': ' . $value . PHP_EOL;
836
            }
837
            return $headers;
838
        }
839
840
         /**
841
         * Get the attachment message for send or the simple message
842
         * @return string
843
         */
844
        protected function getMessageWithAttachmentForSend() {
845
            $message = $this->getWrapMessage();
846
            if ($this->hasAttachments()) {
847
                $this->setAttachmentHeaders();
848
                $message  = $this->assembleAttachmentBody();
849
            }
850
            return $message;
851
        }
852
853
        /**
854
         * Send smtp command to server
855
         * @param  string $command the smtp command
856
         * 
857
         * @return object the current instance
858
         */
859
        protected function sendCommand($command) {
860
            if (is_resource($this->smtpSocket)) {
861
                fputs($this->smtpSocket, $command . PHP_EOL);
862
            }
863
            $this->smtpResponse = $this->getSmtpServerResponse();
864
            return $this;
865
        }
866
867
        /**
868
         * Send EHLO or HELO command to smtp server
869
         * @return boolean true if server response is OK otherwise will return false
870
         */
871
        protected function sendHelloCommand() {
872
            $responseCode = $this->sendCommand('EHLO ' . $this->getSmtpClientHostname())
873
                                 ->getSmtpResponseCode();
874
            if ($responseCode !== 250) {
875
                $this->error = $this->smtpResponse;
876
                return false;
877
            }
878
            return true;
879
        }
880
881
        /**
882
         * Get the smtp server response
883
         * @return mixed
884
         */
885
        protected function getSmtpServerResponse() {
886
            $response = '';
887
            if (is_resource($this->smtpSocket)) {
888
                stream_set_timeout($this->smtpSocket, $this->getSmtpConfig('response_timeout', 10));
889
                while (($line = fgets($this->smtpSocket)) !== false) {
890
                    $response .= trim($line) . "\n";
891
                    if (substr($line, 3, 1) == ' ') {
892
                        break;
893
                    }
894
                }
895
                $response = trim($response);
896
            }
897
            return $response;
898
        }
899
900
        /**
901
         * Return the last response code
902
         *
903
         * @param string|null $response the response string if is null 
904
         * will use the last smtp response
905
         * 
906
         * @return integer the 3 digit of response code
907
         */
908
        protected function getSmtpResponseCode($response = null) {
909
            if ($response === null) {
910
                $response = $this->smtpResponse;
911
            }
912
            return (int) substr($response, 0, 3);
913
        }
914
915
        /**
916
         * Establish connection to smtp server
917
         * @return boolean 
918
         */
919
        protected function smtpConnection() {
920
            $this->smtpSocket = fsockopen(
921
                                        $this->getSmtpConfig('hostname'),
922
                                        $this->getSmtpConfig('port', 25),
923
                                        $errorNumber,
924
                                        $errorMessage,
925
                                        $this->getSmtpConfig('connection_timeout', 30)
926
                                    );
927
928
            if (! is_resource($this->smtpSocket)) {
929
                $this->error = $errorNumber . ': ' . $errorMessage;
930
                return false;
931
            }
932
            $response = $this->getSmtpServerResponse();
933
            $code = $this->getSmtpResponseCode($response);
934
            if ($code !== 220) {
935
                $this->error = $response;
936
                return false;
937
            }
938
            $this->logs['CONNECTION'] = $response;
939
            $hello = $this->sendHelloCommand();
940
            $this->logs['HELLO'] = $this->smtpResponse; 
941
            if (!$hello) {
942
                return false;
943
            }
944
945
            //Check if can use TLS connection to server
946
            if (!$this->checkForSmtpConnectionTls()) {
947
                return false;
948
            }
949
950
            //Authentication of the client
951
            if (!$this->smtpAuthentication()) {
952
                return false;
953
            }
954
            return true;
955
        }
956
957
        /**
958
         * Check if server support TLS connection
959
         * @return boolean
960
         */
961
        protected function checkForSmtpConnectionTls() {
962
            if ($this->getSmtpConfig('transport', 'plain') == 'tls') {
963
                $tlsCode = $this->sendCommand('STARTTLS')->getSmtpResponseCode();
964
                $this->logs['STARTTLS'] = $this->smtpResponse;
965
                if ($tlsCode === 220) {
966
                    /**
967
                     * STREAM_CRYPTO_METHOD_TLS_CLIENT is quite the mess ...
968
                     *
969
                     * - On PHP <5.6 it doesn't even mean TLS, but SSL 2.0, and there's no option to use actual TLS
970
                     * - On PHP 5.6.0-5.6.6, >=7.2 it means negotiation with any of TLS 1.0, 1.1, 1.2
971
                     * - On PHP 5.6.7-7.1.* it means only TLS 1.0
972
                     *
973
                     * We want the negotiation, so we'll force it below ...
974
                     */
975
                    if (is_resource($this->smtpSocket)) {
976
                        $method = STREAM_CRYPTO_METHOD_TLS_CLIENT;
977
                        if(version_compare(PHP_VERSION, '5.6', '>=')) {
978
                            $method = STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT | STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT | STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
979
                        }
980
                        stream_socket_enable_crypto($this->smtpSocket, true, $method);
981
                    }
982
                    $hello = $this->sendHelloCommand();
983
                    $this->logs['HELLO_TLS'] = $this->smtpResponse; 
984
                    if (!$hello) {
985
                        return false;
986
                    }
987
                }
988
            }
989
            return true;
990
        }
991
992
        /**
993
         * Client authentication
994
         * @return boolean
995
         */
996
        protected function smtpAuthentication() {
997
            $authCode = $this->sendCommand('AUTH LOGIN')->getSmtpResponseCode();
998
            $this->logs['AUTH_LOGIN'] = $this->smtpResponse;
999
            if ($authCode === 334) {
1000
                $this->sendCommand(base64_encode($this->getSmtpConfig('username')))->getSmtpResponseCode();
1001
                $code = $this->sendCommand(base64_encode($this->getSmtpConfig('password')))->getSmtpResponseCode();
1002
                $this->logs['CLIENT_AUTH'] = $this->smtpResponse;
1003
                if ($code !== 235) {
1004
                    $this->error = $this->smtpResponse;
1005
                    return false;
1006
                }
1007
            }
1008
            return true;
1009
        }
1010
1011
        /**
1012
         * Send mail using "mail" protocol
1013
         * @return boolean
1014
         */
1015
        protected function sendMail() {
1016
            $to = $this->getToForSend();
1017
            $message = $this->getMessageWithAttachmentForSend();
1018
            $headers = $this->getHeadersForSend(); 
1019
            $this->logger->info('Sending new mail using mail protocol, the information are listed below: '
1020
                                  . 'destination: ' . $to . ', headers: ' . $headers . ', message: ' . $message);
1021
            $result = mail($to, $this->subject, $message, $headers, $this->params);
1022
            if (!$result) {
1023
                $this->error = 'Error when sending mail using mail protocol';
1024
            }
1025
            return $result;
1026
        }
1027
1028
         /**
1029
         * Send mail using "smtp" protocol
1030
         * @return boolean
1031
         */
1032
        protected function sendSmtp() {
1033
            if (!$this->smtpConnection()) {
1034
                return false;
1035
            }
1036
            $to = $this->getToForSend();
1037
            $this->setSmtpAdditionnalHeaders();
1038
            $message = $this->getMessageWithAttachmentForSend();
1039
            $headers = $this->getHeadersForSend();
1040
            $this->logger->info('Sending new mail using SMTP protocol, the information are listed below: '
1041
                                  . 'destination: ' . $to . ', headers: ' . $headers . ', message: ' . $message);
1042
            $recipients = array_merge($this->to, $this->cc, $this->bcc);
1043
            $commands = array(
1044
                                'mail_from' => array('MAIL FROM: <' . $this->from . '>', 'MAIL_FROM', 250),
1045
                                'recipients' => array($recipients, 'RECIPIENTS'),
1046
                                'date_1' => array('DATA', 'DATA_1', 354),
1047
                                'date_2' => array($headers . PHP_EOL . $message . PHP_EOL . '.', 'DATA_2', 250),
1048
                            );
1049
            foreach ($commands as $key => $value) {
1050
                if ($key == 'recipients') {
1051
                    foreach ($value[0] as $address) {
1052
                        $code = $this->sendCommand('RCPT TO: <' . $address . '>')->getSmtpResponseCode();
1053
                        $this->logs[$value[1]][] = $this->smtpResponse;
1054
                        if ($code !== 250) {
1055
                            $this->error = $this->smtpResponse;
1056
                            return false;
1057
                        }
1058
                    }
1059
                } else {
1060
                        $code = $this->sendCommand($value[0])->getSmtpResponseCode();
1061
                        $this->logs[$value[1]] = $this->smtpResponse;
1062
                        if ($code !== $value[2]) {
1063
                            $this->error = $this->smtpResponse;
1064
                            return false;
1065
                        }
1066
                }
1067
            }
1068
            $this->sendCommand('QUIT');
1069
            $this->logs['QUIT'] = $this->smtpResponse;
1070
            return empty($this->error);
1071
        }
1072
1073
        /**
1074
         * Set the additionnal headers for SMTP protocol
1075
         */
1076
        protected function setSmtpAdditionnalHeaders() {
1077
            $to = $this->getToForSend();
1078
            $additionalHeaders = array(
1079
                'Date' => date('r'),
1080
                'Subject' => $this->subject,
1081
                'Return-Path' => $this->from,
1082
                'To' => $to
1083
            );
1084
            foreach ($additionalHeaders as $key => $value) {
1085
                if (! isset($this->headers[$key])) {
1086
                    $this->headers[$key] = $value;
1087
                }
1088
            }
1089
        }
1090
1091
1092
         /**
1093
         * Return the client hostname for SMTP
1094
         * 
1095
         * There are only two legal types of hostname - either a fully
1096
         * qualified domain name (eg: "mail.example.com") or an IP literal
1097
         * (eg: "[1.2.3.4]").
1098
         *
1099
         * @link    https://tools.ietf.org/html/rfc5321#section-2.3.5
1100
         * @link    http://cbl.abuseat.org/namingproblems.html
1101
         * @return string
1102
         */
1103
        protected function getSmtpClientHostname() {
1104
            $globals = &class_loader('GlobalVar', 'classes');
1105
            if ($globals->server('SERVER_NAME')) {
1106
                return $globals->server('SERVER_NAME');
1107
            }
1108
            if ($globals->server('SERVER_ADDR')) {
1109
                return $globals->server('SERVER_ADDR');
1110
            }
1111
            return '[127.0.0.1]';
1112
        }
1113
1114
        /**
1115
         * Class desctructor
1116
         * @codeCoverageIgnore
1117
         */
1118
        public function __destruct() {
1119
            if (is_resource($this->smtpSocket)) {
1120
                fclose($this->smtpSocket);
1121
            }
1122
        }
1123
    }
1124