Test Failed
Push — 1.0.0-dev ( a13fd7...49bbbd )
by nguereza
02:47
created

Email::setSmtpConnectionTimeout()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 3
rs 10
c 0
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
         * Send mail transport, current supported values are:
101
         * "tls", "plain"
102
         * @var string
103
         */
104
        protected $transport = 'plain';
105
106
        /**
107
         * SMTP connection socket
108
         * @var resource
109
         */
110
        protected $smtpSocket;
111
112
        /**
113
         * SMTP server hostname
114
         * @var string
115
         */
116
        protected $smtpHostname = 'localhost';
117
118
        /**
119
         * SMTP server port
120
         * @var integer
121
         */
122
        protected $smtpPort = 25;
123
124
        /**
125
         * SMTP authentication username
126
         * @var string
127
         */
128
        protected $smtpUsername;
129
130
        /**
131
         * SMTP authentication password
132
         * @var string
133
         */
134
        protected $smtpPassword;
135
136
        /**
137
         * SMTP server connection timeout (second)
138
         * @var integer
139
         */
140
        protected $smtpConnectionTimeout = 30;
141
142
        /**
143
         * SMTP server response timeout (second)
144
         * @var integer
145
         */
146
        protected $smtpResponseTimeout = 10;
147
148
        /**
149
         * The last SMTP response string
150
         * @var string
151
         */
152
        protected $smtpResponse;
153
154
        /**
155
         * The last sending mail error
156
         * @var string|null
157
         */
158
        protected $error = null;
159
160
        /**
161
         * The log for sending mail
162
         * @var array
163
         */
164
        protected $logs = array();
165
        
166
167
        /**
168
         * __construct
169
         *
170
         * Resets the class properties.
171
         */
172
        public function __construct() {
173
            parent::__construct();
174
            $this->reset();
175
        }
176
177
        /**
178
         * reset
179
         *
180
         * Resets all properties to initial state.
181
         *
182
         * @return object
183
         */
184
        public function reset() {
185
            $this->from = null;
186
            $this->to = array();
187
            $this->cc = array();
188
            $this->bcc = array();
189
            $this->replyTo = null;
190
            $this->headers = array();
191
            $this->subject = null;
192
            $this->message = null;
193
            $this->wrap = 78;
194
            $this->params = null;
195
            $this->attachments = array();
196
            $this->logs = array();
197
            $this->error = null;
198
            $this->uid = $this->getUniqueId();  
199
            $this->smtpResponse = null;          
200
            return $this;
201
        }
202
        
203
        /**
204
         * setFrom
205
         *
206
         * @param string $email The email to send as from.
207
         * @param string $name  The name to send as from.
208
         *
209
         * @return object
210
         */
211
        public function setFrom($email, $name = null) {
212
            $this->addMailHeader('From', (string) $email, (string) $name);  
213
            $this->from = $this->formatHeader($email, $name);          
214
            return $this;
215
        }
216
217
        /**
218
         * setTo
219
         *
220
         * @param string $email The email address to send to.
221
         * @param string $name  The name of the person to send to.
222
         *
223
         * @return object
224
         */
225
        public function setTo($email, $name = null) {
226
            $this->to[] = $this->formatHeader((string) $email, (string) $name);            
227
            return $this;
228
        }
229
        
230
        /**
231
         * Set destination using array
232
         * @params array $emails the list of recipient. This is an 
233
         * associative array name => email
234
         * @example array('John Doe' => '[email protected]')
235
         * 
236
         * @return object the current instance
237
         */
238
        public function setTos(array $emails) {
239
            foreach ($emails as $name => $email) {
240
                if (is_numeric($name)) {
241
                    $this->setTo($email);
242
                } else {
243
                    $this->setTo($email, $name);
244
                }
245
            }            
246
            return $this;
247
        }
248
249
        /**
250
         * getTo
251
         *
252
         * Return an array of formatted To addresses.
253
         *
254
         * @return array
255
         */
256
        public function getTo() {
257
            return $this->to;
258
        }
259
260
        /**
261
         * setCc
262
         * 
263
         * @param array  $pairs  An array of name => email pairs.
264
         * @example array('John Doe' => '[email protected]')
265
         * @return object
266
         */
267
        public function setCc(array $pairs) {
268
            $this->cc = $pairs;
269
            return $this->addMailHeaders('Cc', $pairs);
270
        }
271
272
        /**
273
         * Return the list of Cc
274
         *
275
         * @return array
276
         */
277
        public function getCc() {
278
            return $this->cc;
279
        }
280
281
        /**
282
         * setBcc
283
         * 
284
         * @param array  $pairs  An array of name => email pairs.
285
         * @example array('John Doe' => '[email protected]')
286
         *
287
         * @return object
288
         */
289
        public function setBcc(array $pairs) {
290
            $this->bcc = $pairs;
291
            return $this->addMailHeaders('Bcc', $pairs);
292
        }
293
294
        /**
295
         * Return the list of Bcc
296
         *
297
         * @return array
298
         */
299
        public function getBcc() {
300
            return $this->bcc;
301
        }
302
303
        /**
304
         * setReplyTo
305
         *
306
         * @param string $email
307
         * @param string $name
308
         *
309
         * @return object
310
         */
311
        public function setReplyTo($email, $name = null) {
312
            $this->replyTo = $this->formatHeader($email, $name);   
313
            return $this->addMailHeader('Reply-To', $email, $name);
314
        }
315
316
        /**
317
         * setHtml
318
         *
319
         * @return object
320
         */
321
        public function setHtml() {
322
            $this->addGenericHeader('Content-Type', 'text/html; charset="utf-8"');           
323
            return $this;
324
        }
325
326
        /**
327
         * setSubject
328
         *
329
         * @param string $subject The email subject
330
         *
331
         * @return object
332
         */
333
        public function setSubject($subject) {
334
            $this->subject = $this->encodeUtf8(
335
            $this->filterOther((string) $subject));
336
            return $this;
337
        }
338
339
        /**
340
         * getSubject function.
341
         *
342
         * @return string
343
         */
344
        public function getSubject() {
345
            return $this->subject;
346
        }
347
348
        /**
349
         * setMessage
350
         *
351
         * @param string $message The message to send.
352
         *
353
         * @return object
354
         */
355
        public function setMessage($message) {
356
            $this->message = str_replace("\n.", "\n..", (string) $message);            
357
            return $this;
358
        }
359
360
        /**
361
         * getMessage
362
         *
363
         * @return string
364
         */
365
        public function getMessage() {
366
            return $this->message;
367
        }
368
369
        /**
370
         * addAttachment
371
         *
372
         * @param string $path The file path to the attachment.
373
         * @param string $filename The filename of the attachment when emailed.
374
         * @param string $data
375
         * 
376
         * @return object
377
         */
378
        public function addAttachment($path, $filename = null, $data = null) {
379
            if (!file_exists($path)) {
380
                show_error('The file [' . $path . '] does not exists.');
381
                return $this;
382
            }
383
            if (empty($filename)) {
384
                $filename = basename($path);
385
            }
386
            $filename = $this->encodeUtf8($this->filterOther((string) $filename));
387
            if (empty($data)) {
388
               $data = $this->getAttachmentData($path);
389
            }
390
            $this->attachments[] = array(
391
                'path' => $path,
392
                'file' => $filename,
393
                'data' => chunk_split(base64_encode($data))
394
            );            
395
            return $this;
396
        }
397
398
        /**
399
         * addMailHeader
400
         *
401
         * @param string $header The header to add.
402
         * @param string $email  The email to add.
403
         * @param string $name   The name to add.
404
         *
405
         * @return object
406
         */
407
        public function addMailHeader($header, $email, $name = null) {
408
            $address = $this->formatHeader((string) $email, (string) $name);
409
            $this->headers[$header] = $address;            
410
            return $this;
411
        }
412
413
        /**
414
         * addMailHeaders
415
         *
416
         * @param string $header The header to add.
417
         * @param array  $pairs  An array of name => email pairs.
418
         *
419
         * @return object
420
         */
421
        public function addMailHeaders($header, array $pairs) {
422
            if (count($pairs) === 0) {
423
                show_error('You must pass at least one name => email pair.');
424
                return $this;
425
            }
426
            $addresses = array();
427
            foreach ($pairs as $name => $email) {
428
                if (is_numeric($name)) {
429
                   $name = null;
430
                }
431
                $addresses[] = $this->formatHeader($email, $name);
432
            }
433
            $this->addGenericHeader($header, implode(',', $addresses));            
434
            return $this;
435
        }
436
437
        /**
438
         * addGenericHeader
439
         *
440
         * @param string $name The generic header to add.
441
         * @param mixed  $value  The value of the header.
442
         *
443
         * @return object
444
         */
445
        public function addGenericHeader($name, $value) {
446
            $this->headers[$name] = $value;
447
            return $this;
448
        }
449
450
        /**
451
         * getHeaders
452
         *
453
         * Return the headers registered so far as an array.
454
         *
455
         * @return array
456
         */
457
        public function getHeaders() {
458
            return $this->headers;
459
        }
460
461
        /**
462
         * setAdditionalParameters
463
         *
464
         * Such as "[email protected]
465
         *
466
         * @param string $additionalParameters The addition mail parameter.
467
         *
468
         * @return object
469
         */
470
        public function setParameters($additionalParameters) {
471
            $this->params = (string) $additionalParameters;            
472
            return $this;
473
        }
474
475
        /**
476
         * getAdditionalParameters
477
         *
478
         * @return string
479
         */
480
        public function getParameters() {
481
            return $this->params;
482
        }
483
484
        /**
485
         * setWrap
486
         *
487
         * @param int $wrap The number of characters at which the message will wrap.
488
         *
489
         * @return object
490
         */
491
        public function setWrap($wrap = 78) {
492
            $wrap = (int) $wrap;
493
            if ($wrap < 1) {
494
                $wrap = 78;
495
            }
496
            $this->wrap = $wrap;            
497
            return $this;
498
        }
499
500
        /**
501
         * getWrap
502
         *
503
         * @return int
504
         */
505
        public function getWrap() {
506
            return $this->wrap;
507
        }
508
509
        /**
510
         * hasAttachments
511
         * 
512
         * Checks if the email has any registered attachments.
513
         *
514
         * @return bool
515
         */
516
        public function hasAttachments() {
517
            return !empty($this->attachments);
518
        }
519
520
        /**
521
         * getWrapMessage
522
         *
523
         * @return string
524
         */
525
        public function getWrapMessage() {
526
            return wordwrap($this->message, $this->wrap);
527
        }
528
    
529
        /**
530
         * Return the send mail protocol
531
         * @return string
532
         */
533
        public function getProtocol() {
534
            return $this->protocol;
535
        }
536
537
        /**
538
         * Set the send mail protocol to "mail"
539
         *
540
         * @return object the current instance
541
         */
542
        public function setProtocolMail() {
543
            $this->protocol = 'mail';
544
            return $this;
545
        }
546
547
        /**
548
         * Set the send mail protocol to "smtp"
549
         * 
550
         * @return object the current instance
551
         */
552
        public function setProtocolSmtp() {
553
            $this->protocol = 'smtp';
554
            return $this;
555
        }
556
557
        /**
558
         * Return the mail transport
559
         * @return string
560
         */
561
        public function getTransport() {
562
            return $this->transport;
563
        }
564
565
        /**
566
         * Set the send mail transport to "tls"
567
         *
568
         * @return object the current instance
569
         */
570
        public function setTransportTls() {
571
            $this->transport = 'tls';
572
            return $this;
573
        }
574
575
        /**
576
         * Set the send mail transport to "plain"
577
         *
578
         * @return object the current instance
579
         */
580
        public function setTransportPlain() {
581
            $this->transport = 'plain';
582
            return $this;
583
        }
584
585
        /**
586
         * Return the smtp server hostname
587
         * @return string
588
         */
589
        public function getSmtpHostname() {
590
            return $this->smtpHostname;
591
        }
592
593
        /**
594
         * Set the smtp server hostname
595
         * @param string $smtpHostname
596
         *
597
         * @return object the current instance
598
         */
599
        public function setSmtpHostname($smtpHostname) {
600
            $this->smtpHostname = $smtpHostname;
601
            return $this;
602
        }
603
604
        /**
605
         * Return the smtp server port
606
         * @return integer
607
         */
608
        public function getSmtpPort() {
609
            return $this->smtpPort;
610
        }
611
612
        /**
613
         * Set the smtp server port
614
         * @param integer $smtpPort
615
         *
616
         * @return object the current instance
617
         */
618
        public function setSmtpPort($smtpPort) {
619
            $this->smtpPort = $smtpPort;
620
            return $this;
621
        }
622
623
        /**
624
         * Return the smtp username
625
         * @return string
626
         */
627
        public function getSmtpUsername() {
628
            return $this->smtpUsername;
629
        }
630
631
        /**
632
         * Set the smtp username
633
         * @param string $smtpUsername
634
         *
635
         * @return object the current instance
636
         */
637
        public function setSmtpUsername($smtpUsername) {
638
            $this->smtpUsername = $smtpUsername;
639
            return $this;
640
        }
641
642
        /**
643
         * Return the smtp password
644
         * @return string
645
         */
646
        public function getSmtpPassword() {
647
            return $this->smtpPassword;
648
        }
649
650
        /**
651
         * Set the smtp password
652
         * @param string $smtpPassword
653
         *
654
         * @return object the current instance
655
         */
656
        public function setSmtpPassword($smtpPassword) {
657
            $this->smtpPassword = $smtpPassword;
658
            return $this;
659
        }
660
661
        /**
662
         * Return the smtp server connection timeout
663
         * @return integer
664
         */
665
        public function getSmtpConnectionTimeout() {
666
            return $this->smtpConnectionTimeout;
667
        }
668
669
        /**
670
         * Set the smtp server connection timeout
671
         * @param integer $timeout
672
         *
673
         * @return object the current instance
674
         */
675
        public function setSmtpConnectionTimeout($timeout) {
676
            $this->smtpConnectionTimeout = $timeout;
677
            return $this;
678
        }
679
680
        /**
681
         * Return the smtp server response timeout
682
         * @return integer
683
         */
684
        public function getSmtpResponseTimeout() {
685
            return $this->smtpResponseTimeout;
686
        }
687
688
        /**
689
         * Set the smtp server response timeout
690
         * @param integer $timeout
691
         *
692
         * @return object the current instance
693
         */
694
        public function setSmtpResponseTimeout($timeout) {
695
            $this->smtpResponseTimeout = $timeout;
696
            return $this;
697
        }
698
699
        /**
700
         * send the email
701
         *
702
         * @return boolean
703
         */
704
        public function send() {
705
            if (empty($this->to)) {
706
                show_error('Unable to send mail, no destination address has been set.');
707
                return false;
708
            }
709
            if (empty($this->from)) {
710
                show_error('Unable to send mail, no sender address has been set.');
711
                return false;
712
            }
713
            if ($this->protocol == 'mail') {
714
                return $this->sendMail();
715
            } else if ($this->protocol == 'smtp') {
716
                return $this->sendSmtp();
717
            }   
718
            return false;
719
        }
720
721
722
        /**
723
         * Return the last error when sending mail
724
         * @return string|null
725
         */
726
        public function getError() {
727
            return $this->error;
728
        }
729
730
        /**
731
         * Return the sending mail logs content
732
         * @return array
733
         */
734
        public function getLogs() {
735
            return $this->logs;
736
        }
737
738
        /**
739
         * Debug
740
         * @codeCoverageIgnore
741
         * 
742
         * @return string
743
         */
744
        public function debug() {
745
            return '<pre>' . print_r($this->logs, true) . '</pre>';
746
        }
747
748
        /**
749
         * Get attachment data
750
         *
751
         * @param string $path The path to the attachment file.
752
         *
753
         * @return string|boolean
754
         */
755
        protected function getAttachmentData($path) {
756
            $filesize = filesize($path);
757
            $handle = fopen($path, "r");
758
            $attachment = null;
759
            if (is_resource($handle)) {
760
                $attachment = fread($handle, $filesize);
761
                fclose($handle);
762
            }
763
            return $attachment;
764
        }
765
766
        /**
767
         * assembleAttachment
768
         *
769
         * @return object
770
         */
771
        protected function setAttachmentHeaders() {
772
            $this->headers['MIME-Version'] = '1.0';
773
            $this->headers['Content-Type'] = "multipart/mixed; boundary=\"{$this->uid}\"";
774
            return $this;
775
        }
776
777
        /**
778
         * assembleAttachmentBody
779
         *
780
         * @return string
781
         */
782
        protected function assembleAttachmentBody() {
783
            $body = array();
784
            $body[] = "This is a multi-part message in MIME format.";
785
            $body[] = "--{$this->uid}";
786
            $body[] = "Content-Type: text/html; charset=\"utf-8\"";
787
            $body[] = "Content-Transfer-Encoding: base64";
788
            $body[] = PHP_EOL;
789
            $body[] = chunk_split(base64_encode($this->message));
790
            $body[] = PHP_EOL;
791
            $body[] = "--{$this->uid}";
792
793
            foreach ($this->attachments as $attachment) {
794
                $body[] = $this->getAttachmentMimeTemplate($attachment);
795
            }
796
            return implode(PHP_EOL, $body) . '--';
797
        }
798
799
        /**
800
         * getAttachmentMimeTemplate
801
         *
802
         * @param array  $attachment An array containing 'file' and 'data' keys.
803
         *
804
         * @return string
805
         */
806
        protected function getAttachmentMimeTemplate($attachment) {
807
            $file = $attachment['file'];
808
            $data = $attachment['data'];
809
810
            $head = array();
811
            $head[] = "Content-Type: application/octet-stream; name=\"{$file}\"";
812
            $head[] = "Content-Transfer-Encoding: base64";
813
            $head[] = "Content-Disposition: attachment; filename=\"{$file}\"";
814
            $head[] = "";
815
            $head[] = $data;
816
            $head[] = "";
817
            $head[] = "--{$this->uid}";
818
819
            return implode(PHP_EOL, $head);
820
        }
821
822
        /**
823
         * formatHeader
824
         *
825
         * Formats a display address for emails according to RFC2822 e.g.
826
         * Name <[email protected]>
827
         *
828
         * @param string $email The email address.
829
         * @param string $name  The display name.
830
         *
831
         * @return string
832
         */
833
        protected function formatHeader($email, $name = null) {
834
            $email = $this->filterEmail((string) $email);
835
            if (empty($name)) {
836
                return $email;
837
            }
838
            $name = $this->encodeUtf8($this->filterName((string) $name));
839
            return sprintf('"%s" <%s>', $name, $email);
840
        }
841
842
        /**
843
         * encodeUtf8
844
         *
845
         * @param string $value The value to encode.
846
         *
847
         * @return string
848
         */
849
        protected function encodeUtf8($value) {
850
            $value = trim($value);
851
            if (preg_match('/(\s)/', $value)) {
852
                return $this->encodeUtf8Words($value);
853
            }
854
            return $this->encodeUtf8Word($value);
855
        }
856
857
        /**
858
         * encodeUtf8Word
859
         *
860
         * @param string $value The word to encode.
861
         *
862
         * @return string
863
         */
864
        protected function encodeUtf8Word($value) {
865
            return sprintf('=?UTF-8?B?%s?=', base64_encode($value));
866
        }
867
868
        /**
869
         * encodeUtf8Words
870
         *
871
         * @param string $value The words to encode.
872
         *
873
         * @return string
874
         */
875
        protected function encodeUtf8Words($value) {
876
            $words = explode(' ', $value);
877
            $encoded = array();
878
            foreach ($words as $word) {
879
                $encoded[] = $this->encodeUtf8Word($word);
880
            }
881
            return join($this->encodeUtf8Word(' '), $encoded);
882
        }
883
884
        /**
885
         * filterEmail
886
         *
887
         * Removes any carriage return, line feed, tab, double quote, comma
888
         * and angle bracket characters before sanitizing the email address.
889
         *
890
         * @param string $email The email to filter.
891
         *
892
         * @return string
893
         */
894
        protected function filterEmail($email) {
895
            $rule = array(
896
                "\r" => '',
897
                "\n" => '',
898
                "\t" => '',
899
                '"'  => '',
900
                ','  => '',
901
                '<'  => '',
902
                '>'  => ''
903
            );
904
            $email = strtr($email, $rule);
905
            $email = filter_var($email, FILTER_SANITIZE_EMAIL);
906
            return $email;
907
        }
908
909
        /**
910
         * filterName
911
         *
912
         * Removes any carriage return, line feed or tab characters. Replaces
913
         * double quotes with single quotes and angle brackets with square
914
         * brackets, before sanitizing the string and stripping out html tags.
915
         *
916
         * @param string $name The name to filter.
917
         *
918
         * @return string
919
         */
920
        protected function filterName($name) {
921
            $rule = array(
922
                "\r" => '',
923
                "\n" => '',
924
                "\t" => '',
925
                '"'  => "'",
926
                '<'  => '[',
927
                '>'  => ']',
928
            );
929
            $filtered = filter_var(
930
                $name,
931
                FILTER_SANITIZE_STRING,
932
                FILTER_FLAG_NO_ENCODE_QUOTES
933
            );
934
            return trim(strtr($filtered, $rule));
935
        }
936
937
        /**
938
         * filterOther
939
         *
940
         * Removes ASCII control characters including any carriage return, line
941
         * feed or tab characters.
942
         *
943
         * @param string $data The data to filter.
944
         *
945
         * @return string
946
         */
947
        protected function filterOther($data) {
948
            return filter_var($data, FILTER_UNSAFE_RAW, FILTER_FLAG_STRIP_LOW);
949
        }
950
951
        /**
952
         * Get destinataire for send
953
         *
954
         * @return string
955
         */
956
        protected function getToForSend() {
957
            return join(', ', $this->to);
958
        }
959
960
        /**
961
         * getHeadersForSend
962
         *
963
         * @return string
964
         */
965
        protected function getHeadersForSend() {
966
            $headers = null;
967
            foreach ($this->headers as $key => $value) {
968
                $headers .= $key . ': ' . $value . PHP_EOL;
969
            }
970
            return $headers;
971
        }
972
973
         /**
974
         * Get the attachment message for send or the simple message
975
         * @return string
976
         */
977
        protected function getMessageWithAttachmentForSend() {
978
            $message = $this->getWrapMessage();
979
            if ($this->hasAttachments()) {
980
                $this->setAttachmentHeaders();
981
                $message  = $this->assembleAttachmentBody();
982
            }
983
            return $message;
984
        }
985
986
        /**
987
         * getUniqueId
988
         *
989
         * @return string
990
         */
991
        protected function getUniqueId() {
992
            return md5(uniqid(time()));
993
        }
994
995
        /**
996
         * Send smtp command to server
997
         * @param  string $command the smtp command
998
         * 
999
         * @return object
1000
         */
1001
        protected function sendCommand($command) {
1002
            fputs($this->smtpSocket, $command . PHP_EOL);
1003
            $this->smtpResponse = $this->getSmtpServerResponse();
1004
            return $this;
1005
        }
1006
1007
        /**
1008
         * Send EHLO or HELO command to smtp server
1009
         * @return boolean true if server response is OK otherwise will return false
1010
         */
1011
        protected function sendHelloCommand() {
1012
            $responseCode = $this->sendCommand('EHLO ' . $this->getSmtpClientHostname())
1013
                                 ->getSmtpResponseCode();
1014
            if ($responseCode !== 250) {
1015
                //May be try with "HELO"
1016
                $responseCode = $this->sendCommand('HELO ' . $this->getSmtpClientHostname())
1017
                                     ->getSmtpResponseCode();
1018
                if ($responseCode !== 250) {
1019
                    $this->error = $this->smtpResponse;
1020
                    return false;
1021
                }
1022
            }
1023
            return true;
1024
        }
1025
1026
        /**
1027
         * Get the smtp server response
1028
         * @return mixed
1029
         */
1030
        protected function getSmtpServerResponse() {
1031
            $response = '';
1032
            stream_set_timeout($this->smtpSocket, $this->smtpResponseTimeout);
1033
            while (($line = fgets($this->smtpSocket)) !== false) {
1034
                $response .= trim($line) . "\n";
1035
                if (substr($line, 3, 1) == ' ') {
1036
                    break;
1037
                }
1038
            }
1039
            $response = trim($response);
1040
            return $response;
1041
        }
1042
1043
        /**
1044
         * Return the last response code
1045
         *
1046
         * @param string|null $response the response string if is null 
1047
         * will use the last smtp response
1048
         * 
1049
         * @return integer the 3 digit of response code
1050
         */
1051
        protected function getSmtpResponseCode($response = null) {
1052
            if ($response === null) {
1053
                $response = $this->smtpResponse;
1054
            }
1055
            return (int) substr($response, 0, 3);
1056
        }
1057
1058
        /**
1059
         * Establish connection to smtp server
1060
         * @return boolean 
1061
         */
1062
        protected function smtpConnection() {
1063
            $this->smtpSocket = fsockopen(
0 ignored issues
show
Documentation Bug introduced by
It seems like fsockopen($this->smtpHos...>smtpConnectionTimeout) can also be of type false. However, the property $smtpSocket is declared as type resource. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
1064
                                        $this->smtpHostname,
1065
                                        $this->smtpPort,
1066
                                        $errorNumber,
1067
                                        $errorMessage,
1068
                                        $this->smtpConnectionTimeout
1069
                                    );
1070
1071
            if (! is_resource($this->smtpSocket)) {
1072
                $this->error = $errorNumber . ':' . $errorMessage;
1073
                return false;
1074
            }
1075
            $response = $this->getSmtpServerResponse();
1076
            $code = $this->getSmtpResponseCode($response);
1077
            if ($code !== 220) {
1078
                $this->error = $response;
1079
                return false;
1080
            }
1081
            $this->logs['CONNECTION'] = $response;
1082
            $hello = $this->sendHelloCommand();
1083
            $this->logs['HELLO'] = $this->smtpResponse; 
1084
            if (!$hello) {
1085
                return false;
1086
            }
1087
1088
            //Check if can use TLS connection to server
1089
            if (!$this->checkForSmtpConnectionTls()) {
1090
                return false;
1091
            }
1092
1093
            //Authentication of the client
1094
            if (!$this->smtpAuthentication()) {
1095
                return false;
1096
            }
1097
            return true;
1098
        }
1099
1100
        /**
1101
         * Check if server support TLS connection
1102
         * @return object
1103
         */
1104
        protected function checkForSmtpConnectionTls() {
1105
            if ($this->transport == 'tls') {
1106
                $tlsCode = $this->sendCommand('STARTTLS')->getSmtpResponseCode();
1107
                $this->logs['STARTTLS'] = $this->smtpResponse;
1108
                if ($tlsCode === 220) {
1109
                    /**
1110
                     * STREAM_CRYPTO_METHOD_TLS_CLIENT is quite the mess ...
1111
                     *
1112
                     * - On PHP <5.6 it doesn't even mean TLS, but SSL 2.0, and there's no option to use actual TLS
1113
                     * - On PHP 5.6.0-5.6.6, >=7.2 it means negotiation with any of TLS 1.0, 1.1, 1.2
1114
                     * - On PHP 5.6.7-7.1.* it means only TLS 1.0
1115
                     *
1116
                     * We want the negotiation, so we'll force it below ...
1117
                     */
1118
                    $method = STREAM_CRYPTO_METHOD_TLS_CLIENT;
1119
                    if(version_compare(PHP_VERSION, '5.6', '>=')) {
1120
                        $method = STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT | STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT | STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
1121
                    }
1122
                    stream_socket_enable_crypto($this->smtpSocket, true, $method);
1123
                    $hello = $this->sendHelloCommand();
1124
                    $this->logs['HELLO_TLS'] = $this->smtpResponse; 
1125
                    if (!$hello) {
1126
                        return false;
1127
                    }
1128
                }
1129
            }
1130
            return true;
0 ignored issues
show
Bug Best Practice introduced by
The expression return true returns the type true which is incompatible with the documented return type object.
Loading history...
1131
        }
1132
1133
        /**
1134
         * Client authentication
1135
         * @return boolean
1136
         */
1137
        protected function smtpAuthentication() {
1138
            $authCode = $this->sendCommand('AUTH LOGIN')->getSmtpResponseCode();
1139
            $this->logs['AUTH_LOGIN'] = $this->smtpResponse;
1140
            if ($authCode === 334) {
1141
                $this->sendCommand(base64_encode($this->smtpUsername))->getSmtpResponseCode();
1142
                $code = $this->sendCommand(base64_encode($this->smtpPassword))->getSmtpResponseCode();
1143
                $this->logs['CLIENT_AUTH'] = $this->smtpResponse;
1144
                if ($code !== 235) {
1145
                    $this->error = $this->smtpResponse;
1146
                    return false;
1147
                }
1148
            }
1149
            return true;
1150
        }
1151
1152
        /**
1153
         * Send mail using "mail" protocol
1154
         * @return boolean
1155
         */
1156
        protected function sendMail() {
1157
            $to = $this->getToForSend();
1158
            $message = $this->getMessageWithAttachmentForSend();
1159
            $headers = $this->getHeadersForSend(); 
1160
            $this->logger->info('Sending new mail using mail protocol, the information are listed below: '
1161
                                  . 'destination: ' . $to . ', headers: ' . $headers . ', message: ' . $message);
1162
            $result = mail($to, $this->subject, $message, $headers, $this->params);
1163
            if (!$result) {
1164
                $this->error = 'Error when sending mail using mail protocol';
1165
            }
1166
            return $result;
1167
        }
1168
1169
         /**
1170
         * Send mail using "smtp" protocol
1171
         * @return boolean
1172
         */
1173
        protected function sendSmtp() {
1174
            if (!$this->smtpConnection()) {
1175
                return false;
1176
            }
1177
            $to = $this->getToForSend();
1178
            $additionalHeaders = array(
1179
                'Date' => date('r'),
1180
                'Subject' => $this->subject,
1181
                'Return-Path' => $this->from,
1182
                'To' => $to
1183
            );
1184
            foreach ($additionalHeaders as $key => $value) {
1185
                if (! isset($this->headers[$key])) {
1186
                    $this->headers[$key] = $value;
1187
                }
1188
            }
1189
            $message = $this->getMessageWithAttachmentForSend();
1190
            $headers = $this->getHeadersForSend();
1191
            $this->logger->info('Sending new mail using SMTP protocol, the information are listed below: '
1192
                                  . 'destination: ' . $to . ', headers: ' . $headers . ', message: ' . $message);
1193
            
1194
            $recipients = array_merge($this->to, $this->cc, $this->bcc);
1195
            $commands = array(
1196
                                'mail_from' => array('MAIL FROM: <' . $this->from . '>', 'MAIL_FROM', 250),
1197
                                'recipients' => array($recipients, 'RECIPIENTS'),
1198
                                'date_1' => array('DATA', 'DATA_1', 354),
1199
                                'date_2' => array($headers . PHP_EOL . $message . PHP_EOL . '.', 'DATA_2', 250),
1200
                            );
1201
            foreach ($commands as $key => $value) {
1202
                if ($key == 'recipients') {
1203
                    foreach ($value[0] as $address) {
1204
                        $code = $this->sendCommand('RCPT TO: <' . $address . '>')->getSmtpResponseCode();
1205
                        $this->logs[$value[1]][] = $this->smtpResponse;
1206
                        if ($code !== 250) {
1207
                            $this->error = $this->smtpResponse;
1208
                            return false;
1209
                        }
1210
                    }
1211
                } else {
1212
                        $code = $this->sendCommand($value[0])->getSmtpResponseCode();
1213
                        $this->logs[$value[1]] = $this->smtpResponse;
1214
                        if ($code !== $value[2]) {
1215
                            $this->error = $this->smtpResponse;
1216
                            return false;
1217
                        }
1218
                }
1219
            }
1220
            $quitCode = $this->sendCommand('QUIT')->getSmtpResponseCode();
0 ignored issues
show
Unused Code introduced by
The assignment to $quitCode is dead and can be removed.
Loading history...
1221
            $this->logs['QUIT'] = $this->smtpResponse;
1222
            fclose($this->smtpSocket); 
0 ignored issues
show
Bug introduced by
It seems like $this->smtpSocket can also be of type false; however, parameter $handle of fclose() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1222
            fclose(/** @scrutinizer ignore-type */ $this->smtpSocket); 
Loading history...
1223
            return empty($this->error);
1224
        }
1225
1226
         /**
1227
         * Return the client hostname for SMTP
1228
         * 
1229
         * There are only two legal types of hostname - either a fully
1230
         * qualified domain name (eg: "mail.example.com") or an IP literal
1231
         * (eg: "[1.2.3.4]").
1232
         *
1233
         * @link    https://tools.ietf.org/html/rfc5321#section-2.3.5
1234
         * @link    http://cbl.abuseat.org/namingproblems.html
1235
         * @return string
1236
         */
1237
        protected function getSmtpClientHostname() {
1238
            $globals = &class_loader('GlobalVar', 'classes');
1239
            if ($globals->server('SERVER_NAME')) {
1240
                return $globals->server('SERVER_NAME');
1241
            }
1242
            if ($globals->server('SERVER_ADDR')) {
1243
                return $globals->server('SERVER_ADDR');
1244
            }
1245
            return '[127.0.0.1]';
1246
        }
1247
1248
        /**
1249
         * Class desctructor
1250
         * @codeCoverageIgnore
1251
         */
1252
        public function __destruct() {
1253
            if (is_resource($this->smtpSocket)) {
1254
                fclose($this->smtpSocket);
1255
            }
1256
        }
1257
    }
1258