GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

PHPMailer::createBody()   F
last analyzed

Complexity

Conditions 25
Paths 16128

Size

Total Lines 200

Duplication

Lines 46
Ratio 23 %

Importance

Changes 0
Metric Value
cc 25
nc 16128
nop 0
dl 46
loc 200
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * PHPMailer - PHP email creation and transport class.
4
 * PHP Version 5
5
 * @package PHPMailer
6
 * @link https://github.com/PHPMailer/PHPMailer/ The PHPMailer GitHub project
7
 * @author Marcus Bointon (Synchro/coolbru) <[email protected]>
8
 * @author Jim Jagielski (jimjag) <[email protected]>
9
 * @author Andy Prevost (codeworxtech) <[email protected]>
10
 * @author Brent R. Matzelle (original founder)
11
 * @copyright 2012 - 2014 Marcus Bointon
12
 * @copyright 2010 - 2012 Jim Jagielski
13
 * @copyright 2004 - 2009 Andy Prevost
14
 * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
15
 * @note This program is distributed in the hope that it will be useful - WITHOUT
16
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
17
 * FITNESS FOR A PARTICULAR PURPOSE.
18
 */
19
20
/**
21
 * PHPMailer - PHP email creation and transport class.
22
 * @package PHPMailer
23
 * @author Marcus Bointon (Synchro/coolbru) <[email protected]>
24
 * @author Jim Jagielski (jimjag) <[email protected]>
25
 * @author Andy Prevost (codeworxtech) <[email protected]>
26
 * @author Brent R. Matzelle (original founder)
27
 */
28
class PHPMailer
29
{
30
    /**
31
     * The PHPMailer Version number.
32
     * @type string
33
     */
34
    public $Version = '5.2.10';
35
36
    /**
37
     * Email priority.
38
     * Options: 1 = High, 3 = Normal, 5 = low.
39
     * @type integer
40
     */
41
    public $Priority = 3;
42
43
    /**
44
     * The character set of the message.
45
     * @type string
46
     */
47
    public $CharSet = 'iso-8859-1';
48
49
    /**
50
     * The MIME Content-type of the message.
51
     * @type string
52
     */
53
    public $ContentType = 'text/plain';
54
55
    /**
56
     * The message encoding.
57
     * Options: "8bit", "7bit", "binary", "base64", and "quoted-printable".
58
     * @type string
59
     */
60
    public $Encoding = '8bit';
61
62
    /**
63
     * Holds the most recent mailer error message.
64
     * @type string
65
     */
66
    public $ErrorInfo = '';
67
68
    /**
69
     * The From email address for the message.
70
     * @type string
71
     */
72
    public $From = 'root@localhost';
73
74
    /**
75
     * The From name of the message.
76
     * @type string
77
     */
78
    public $FromName = 'Root User';
79
80
    /**
81
     * The Sender email (Return-Path) of the message.
82
     * If not empty, will be sent via -f to sendmail or as 'MAIL FROM' in smtp mode.
83
     * @type string
84
     */
85
    public $Sender = '';
86
87
    /**
88
     * The Return-Path of the message.
89
     * If empty, it will be set to either From or Sender.
90
     * @type string
91
     * @deprecated Email senders should never set a return-path header;
92
     * it's the receiver's job (RFC5321 section 4.4), so this no longer does anything.
93
     * @link https://tools.ietf.org/html/rfc5321#section-4.4 RFC5321 reference
94
     */
95
    public $ReturnPath = '';
96
97
    /**
98
     * The Subject of the message.
99
     * @type string
100
     */
101
    public $Subject = '';
102
103
    /**
104
     * An HTML or plain text message body.
105
     * If HTML then call isHTML(true).
106
     * @type string
107
     */
108
    public $Body = '';
109
110
    /**
111
     * The plain-text message body.
112
     * This body can be read by mail clients that do not have HTML email
113
     * capability such as mutt & Eudora.
114
     * Clients that can read HTML will view the normal Body.
115
     * @type string
116
     */
117
    public $AltBody = '';
118
119
    /**
120
     * An iCal message part body.
121
     * Only supported in simple alt or alt_inline message types
122
     * To generate iCal events, use the bundled extras/EasyPeasyICS.php class or iCalcreator
123
     * @link http://sprain.ch/blog/downloads/php-class-easypeasyics-create-ical-files-with-php/
124
     * @link http://kigkonsult.se/iCalcreator/
125
     * @type string
126
     */
127
    public $Ical = '';
128
129
    /**
130
     * The complete compiled MIME message body.
131
     * @access protected
132
     * @type string
133
     */
134
    protected $MIMEBody = '';
135
136
    /**
137
     * The complete compiled MIME message headers.
138
     * @type string
139
     * @access protected
140
     */
141
    protected $MIMEHeader = '';
142
143
    /**
144
     * Extra headers that createHeader() doesn't fold in.
145
     * @type string
146
     * @access protected
147
     */
148
    protected $mailHeader = '';
149
150
    /**
151
     * Word-wrap the message body to this number of chars.
152
     * Set to 0 to not wrap. A useful value here is 78, for RFC2822 section 2.1.1 compliance.
153
     * @type integer
154
     */
155
    public $WordWrap = 0;
156
157
    /**
158
     * Which method to use to send mail.
159
     * Options: "mail", "sendmail", or "smtp".
160
     * @type string
161
     */
162
    public $Mailer = 'mail';
163
164
    /**
165
     * The path to the sendmail program.
166
     * @type string
167
     */
168
    public $Sendmail = '/usr/sbin/sendmail';
169
170
    /**
171
     * Whether mail() uses a fully sendmail-compatible MTA.
172
     * One which supports sendmail's "-oi -f" options.
173
     * @type boolean
174
     */
175
    public $UseSendmailOptions = true;
176
177
    /**
178
     * Path to PHPMailer plugins.
179
     * Useful if the SMTP class is not in the PHP include path.
180
     * @type string
181
     * @deprecated Should not be needed now there is an autoloader.
182
     */
183
    public $PluginDir = '';
184
185
    /**
186
     * The email address that a reading confirmation should be sent to.
187
     * @type string
188
     */
189
    public $ConfirmReadingTo = '';
190
191
    /**
192
     * The hostname to use in Message-Id and Received headers
193
     * and as default HELO string.
194
     * If empty, the value returned
195
     * by SERVER_NAME is used or 'localhost.localdomain'.
196
     * @type string
197
     */
198
    public $Hostname = '';
199
200
    /**
201
     * An ID to be used in the Message-Id header.
202
     * If empty, a unique id will be generated.
203
     * @type string
204
     */
205
    public $MessageID = '';
206
207
    /**
208
     * The message Date to be used in the Date header.
209
     * If empty, the current date will be added.
210
     * @type string
211
     */
212
    public $MessageDate = '';
213
214
    /**
215
     * SMTP hosts.
216
     * Either a single hostname or multiple semicolon-delimited hostnames.
217
     * You can also specify a different port
218
     * for each host by using this format: [hostname:port]
219
     * (e.g. "smtp1.example.com:25;smtp2.example.com").
220
     * You can also specify encryption type, for example:
221
     * (e.g. "tls://smtp1.example.com:587;ssl://smtp2.example.com:465").
222
     * Hosts will be tried in order.
223
     * @type string
224
     */
225
    public $Host = 'localhost';
226
227
    /**
228
     * The default SMTP server port.
229
     * @type integer
230
     * @TODO Why is this needed when the SMTP class takes care of it?
231
     */
232
    public $Port = 25;
233
234
    /**
235
     * The SMTP HELO of the message.
236
     * Default is $Hostname.
237
     * @type string
238
     * @see PHPMailer::$Hostname
239
     */
240
    public $Helo = '';
241
242
    /**
243
     * What kind of encryption to use on the SMTP connection.
244
     * Options: '', 'ssl' or 'tls'
245
     * @type string
246
     */
247
    public $SMTPSecure = '';
248
249
    /**
250
     * Whether to enable TLS encryption automatically if a server supports it,
251
     * even if `SMTPSecure` is not set to 'tls'.
252
     * Be aware that in PHP >= 5.6 this requires that the server's certificates are valid.
253
     * @type boolean
254
     */
255
    public $SMTPAutoTLS = true;
256
257
    /**
258
     * Whether to use SMTP authentication.
259
     * Uses the Username and Password properties.
260
     * @type boolean
261
     * @see PHPMailer::$Username
262
     * @see PHPMailer::$Password
263
     */
264
    public $SMTPAuth = false;
265
266
    /**
267
     * Options array passed to stream_context_create when connecting via SMTP.
268
     * @type array
269
     */
270
    public $SMTPOptions = array();
271
272
    /**
273
     * SMTP username.
274
     * @type string
275
     */
276
    public $Username = '';
277
278
    /**
279
     * SMTP password.
280
     * @type string
281
     */
282
    public $Password = '';
283
284
    /**
285
     * SMTP auth type.
286
     * Options are LOGIN (default), PLAIN, NTLM, CRAM-MD5
287
     * @type string
288
     */
289
    public $AuthType = '';
290
291
    /**
292
     * SMTP realm.
293
     * Used for NTLM auth
294
     * @type string
295
     */
296
    public $Realm = '';
297
298
    /**
299
     * SMTP workstation.
300
     * Used for NTLM auth
301
     * @type string
302
     */
303
    public $Workstation = '';
304
305
    /**
306
     * The SMTP server timeout in seconds.
307
     * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2
308
     * @type integer
309
     */
310
    public $Timeout = 300;
311
312
    /**
313
     * SMTP class debug output mode.
314
     * Debug output level.
315
     * Options:
316
     * * `0` No output
317
     * * `1` Commands
318
     * * `2` Data and commands
319
     * * `3` As 2 plus connection status
320
     * * `4` Low-level data output
321
     * @type integer
322
     * @see SMTP::$do_debug
323
     */
324
    public $SMTPDebug = 0;
325
326
    /**
327
     * How to handle debug output.
328
     * Options:
329
     * * `echo` Output plain-text as-is, appropriate for CLI
330
     * * `html` Output escaped, line breaks converted to `<br>`, appropriate for browser output
331
     * * `error_log` Output to error log as configured in php.ini
332
     *
333
     * Alternatively, you can provide a callable expecting two params: a message string and the debug level:
334
     * <code>
335
     * $mail->Debugoutput = function($str, $level) {echo "debug level $level; message: $str";};
336
     * </code>
337
     * @type string|callable
338
     * @see SMTP::$Debugoutput
339
     */
340
    public $Debugoutput = 'echo';
341
342
    /**
343
     * Whether to keep SMTP connection open after each message.
344
     * If this is set to true then to close the connection
345
     * requires an explicit call to smtpClose().
346
     * @type boolean
347
     */
348
    public $SMTPKeepAlive = false;
349
350
    /**
351
     * Whether to split multiple to addresses into multiple messages
352
     * or send them all in one message.
353
     * @type boolean
354
     */
355
    public $SingleTo = false;
356
357
    /**
358
     * Storage for addresses when SingleTo is enabled.
359
     * @type array
360
     * @TODO This should really not be public
361
     */
362
    public $SingleToArray = array();
363
364
    /**
365
     * Whether to generate VERP addresses on send.
366
     * Only applicable when sending via SMTP.
367
     * @link http://en.wikipedia.org/wiki/Variable_envelope_return_path
368
     * @link http://www.postfix.org/VERP_README.html Postfix VERP info
369
     * @type boolean
370
     */
371
    public $do_verp = false;
372
373
    /**
374
     * Whether to allow sending messages with an empty body.
375
     * @type boolean
376
     */
377
    public $AllowEmpty = false;
378
379
    /**
380
     * The default line ending.
381
     * @note The default remains "\n". We force CRLF where we know
382
     *        it must be used via self::CRLF.
383
     * @type string
384
     */
385
    public $LE = "\n";
386
387
    /**
388
     * DKIM selector.
389
     * @type string
390
     */
391
    public $DKIM_selector = '';
392
393
    /**
394
     * DKIM Identity.
395
     * Usually the email address used as the source of the email
396
     * @type string
397
     */
398
    public $DKIM_identity = '';
399
400
    /**
401
     * DKIM passphrase.
402
     * Used if your key is encrypted.
403
     * @type string
404
     */
405
    public $DKIM_passphrase = '';
406
407
    /**
408
     * DKIM signing domain name.
409
     * @example 'example.com'
410
     * @type string
411
     */
412
    public $DKIM_domain = '';
413
414
    /**
415
     * DKIM private key file path.
416
     * @type string
417
     */
418
    public $DKIM_private = '';
419
420
    /**
421
     * Callback Action function name.
422
     *
423
     * The function that handles the result of the send email action.
424
     * It is called out by send() for each email sent.
425
     *
426
     * Value can be any php callable: http://www.php.net/is_callable
427
     *
428
     * Parameters:
429
     *   boolean $result        result of the send action
430
     *   string  $to            email address of the recipient
431
     *   string  $cc            cc email addresses
432
     *   string  $bcc           bcc email addresses
433
     *   string  $subject       the subject
434
     *   string  $body          the email body
435
     *   string  $from          email address of sender
436
     * @type string
437
     */
438
    public $action_function = '';
439
440
    /**
441
     * What to put in the X-Mailer header.
442
     * Options: An empty string for PHPMailer default, whitespace for none, or a string to use
443
     * @type string
444
     */
445
    public $XMailer = '';
446
447
    /**
448
     * An instance of the SMTP sender class.
449
     * @type SMTP
450
     * @access protected
451
     */
452
    protected $smtp = null;
453
454
    /**
455
     * The array of 'to' addresses.
456
     * @type array
457
     * @access protected
458
     */
459
    protected $to = array();
460
461
    /**
462
     * The array of 'cc' addresses.
463
     * @type array
464
     * @access protected
465
     */
466
    protected $cc = array();
467
468
    /**
469
     * The array of 'bcc' addresses.
470
     * @type array
471
     * @access protected
472
     */
473
    protected $bcc = array();
474
475
    /**
476
     * The array of reply-to names and addresses.
477
     * @type array
478
     * @access protected
479
     */
480
    protected $ReplyTo = array();
481
482
    /**
483
     * An array of all kinds of addresses.
484
     * Includes all of $to, $cc, $bcc
485
     * @type array
486
     * @access protected
487
     */
488
    protected $all_recipients = array();
489
490
    /**
491
     * The array of attachments.
492
     * @type array
493
     * @access protected
494
     */
495
    protected $attachment = array();
496
497
    /**
498
     * The array of custom headers.
499
     * @type array
500
     * @access protected
501
     */
502
    protected $CustomHeader = array();
503
504
    /**
505
     * The most recent Message-ID (including angular brackets).
506
     * @type string
507
     * @access protected
508
     */
509
    protected $lastMessageID = '';
510
511
    /**
512
     * The message's MIME type.
513
     * @type string
514
     * @access protected
515
     */
516
    protected $message_type = '';
517
518
    /**
519
     * The array of MIME boundary strings.
520
     * @type array
521
     * @access protected
522
     */
523
    protected $boundary = array();
524
525
    /**
526
     * The array of available languages.
527
     * @type array
528
     * @access protected
529
     */
530
    protected $language = array();
531
532
    /**
533
     * The number of errors encountered.
534
     * @type integer
535
     * @access protected
536
     */
537
    protected $error_count = 0;
538
539
    /**
540
     * The S/MIME certificate file path.
541
     * @type string
542
     * @access protected
543
     */
544
    protected $sign_cert_file = '';
545
546
    /**
547
     * The S/MIME key file path.
548
     * @type string
549
     * @access protected
550
     */
551
    protected $sign_key_file = '';
552
553
    /**
554
     * The optional S/MIME extra certificates ("CA Chain") file path.
555
     * @type string
556
     * @access protected
557
     */
558
    protected $sign_extracerts_file = '';
559
560
    /**
561
     * The S/MIME password for the key.
562
     * Used only if the key is encrypted.
563
     * @type string
564
     * @access protected
565
     */
566
    protected $sign_key_pass = '';
567
568
    /**
569
     * Whether to throw exceptions for errors.
570
     * @type boolean
571
     * @access protected
572
     */
573
    protected $exceptions = false;
574
575
    /**
576
     * Unique ID used for message ID and boundaries.
577
     * @type string
578
     * @access protected
579
     */
580
    protected $uniqueid = '';
581
582
    /**
583
     * Error severity: message only, continue processing.
584
     */
585
    const STOP_MESSAGE = 0;
586
587
    /**
588
     * Error severity: message, likely ok to continue processing.
589
     */
590
    const STOP_CONTINUE = 1;
591
592
    /**
593
     * Error severity: message, plus full stop, critical error reached.
594
     */
595
    const STOP_CRITICAL = 2;
596
597
    /**
598
     * SMTP RFC standard line ending.
599
     */
600
    const CRLF = "\r\n";
601
602
    /**
603
     * The maximum line length allowed by RFC 2822 section 2.1.1
604
     * @type integer
605
     */
606
    const MAX_LINE_LENGTH = 998;
607
608
    /**
609
     * Constructor.
610
     * @param boolean $exceptions Should we throw external exceptions?
611
     */
612
    public function __construct($exceptions = false)
613
    {
614
        $this->exceptions = (boolean)$exceptions;
615
    }
616
617
    /**
618
     * Destructor.
619
     */
620
    public function __destruct()
621
    {
622
        //Close any open SMTP connection nicely
623
        if ($this->Mailer == 'smtp') {
624
            $this->smtpClose();
625
        }
626
    }
627
628
    /**
629
     * Call mail() in a safe_mode-aware fashion.
630
     * Also, unless sendmail_path points to sendmail (or something that
631
     * claims to be sendmail), don't pass params (not a perfect fix,
632
     * but it will do)
633
     * @param string $to To
634
     * @param string $subject Subject
635
     * @param string $body Message Body
636
     * @param string $header Additional Header(s)
637
     * @param string $params Params
638
     * @access private
639
     * @return boolean
640
     */
641
    private function mailPassthru($to, $subject, $body, $header, $params)
642
    {
643
        //Check overloading of mail function to avoid double-encoding
644
        if (ini_get('mbstring.func_overload') & 1) {
645
            $subject = $this->secureHeader($subject);
646
        } else {
647
            $subject = $this->encodeHeader($this->secureHeader($subject));
648
        }
649
        if (ini_get('safe_mode') || !($this->UseSendmailOptions)) {
650
            $result = @mail($to, $subject, $body, $header);
651
        } else {
652
            $result = @mail($to, $subject, $body, $header, $params);
653
        }
654
        return $result;
655
    }
656
657
    /**
658
     * Output debugging info via user-defined method.
659
     * Only generates output if SMTP debug output is enabled (@see SMTP::$do_debug).
660
     * @see PHPMailer::$Debugoutput
661
     * @see PHPMailer::$SMTPDebug
662
     * @param string $str
663
     */
664 View Code Duplication
    protected function edebug($str)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
665
    {
666
        if ($this->SMTPDebug <= 0) {
667
            return;
668
        }
669
        //Avoid clash with built-in function names
670
        if (!in_array($this->Debugoutput, array('error_log', 'html', 'echo')) and is_callable($this->Debugoutput)) {
671
            call_user_func($this->Debugoutput, $str, $this->SMTPDebug);
672
            return;
673
        }
674
        switch ($this->Debugoutput) {
675
            case 'error_log':
676
                //Don't output, just log
677
                error_log($str);
678
                break;
679
            case 'html':
680
                //Cleans up output a bit for a better looking, HTML-safe output
681
                echo htmlentities(
682
                    preg_replace('/[\r\n]+/', '', $str),
683
                    ENT_QUOTES,
684
                    'UTF-8'
685
                )
686
                . "<br>\n";
687
                break;
688
            case 'echo':
689
            default:
690
                //Normalize line breaks
691
                $str = preg_replace('/(\r\n|\r|\n)/ms', "\n", $str);
692
                echo gmdate('Y-m-d H:i:s') . "\t" . str_replace(
693
                    "\n",
694
                    "\n                   \t                  ",
695
                    trim($str)
696
                ) . "\n";
697
        }
698
    }
699
700
    /**
701
     * Sets message type to HTML or plain.
702
     * @param boolean $isHtml True for HTML mode.
703
     * @return void
704
     */
705
    public function isHTML($isHtml = true)
706
    {
707
        if ($isHtml) {
708
            $this->ContentType = 'text/html';
709
        } else {
710
            $this->ContentType = 'text/plain';
711
        }
712
    }
713
714
    /**
715
     * Send messages using SMTP.
716
     * @return void
717
     */
718
    public function isSMTP()
719
    {
720
        $this->Mailer = 'smtp';
721
    }
722
723
    /**
724
     * Send messages using PHP's mail() function.
725
     * @return void
726
     */
727
    public function isMail()
728
    {
729
        $this->Mailer = 'mail';
730
    }
731
732
    /**
733
     * Send messages using $Sendmail.
734
     * @return void
735
     */
736 View Code Duplication
    public function isSendmail()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
737
    {
738
        $ini_sendmail_path = ini_get('sendmail_path');
739
740
        if (!stristr($ini_sendmail_path, 'sendmail')) {
741
            $this->Sendmail = '/usr/sbin/sendmail';
742
        } else {
743
            $this->Sendmail = $ini_sendmail_path;
744
        }
745
        $this->Mailer = 'sendmail';
746
    }
747
748
    /**
749
     * Send messages using qmail.
750
     * @return void
751
     */
752 View Code Duplication
    public function isQmail()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
753
    {
754
        $ini_sendmail_path = ini_get('sendmail_path');
755
756
        if (!stristr($ini_sendmail_path, 'qmail')) {
757
            $this->Sendmail = '/var/qmail/bin/qmail-inject';
758
        } else {
759
            $this->Sendmail = $ini_sendmail_path;
760
        }
761
        $this->Mailer = 'qmail';
762
    }
763
764
    /**
765
     * Add a "To" address.
766
     * @param string $address
767
     * @param string $name
768
     * @return boolean true on success, false if address already used
769
     */
770
    public function addAddress($address, $name = '')
771
    {
772
        return $this->addAnAddress('to', $address, $name);
773
    }
774
775
    /**
776
     * Add a "CC" address.
777
     * @note: This function works with the SMTP mailer on win32, not with the "mail" mailer.
778
     * @param string $address
779
     * @param string $name
780
     * @return boolean true on success, false if address already used
781
     */
782
    public function addCC($address, $name = '')
783
    {
784
        return $this->addAnAddress('cc', $address, $name);
785
    }
786
787
    /**
788
     * Add a "BCC" address.
789
     * @note: This function works with the SMTP mailer on win32, not with the "mail" mailer.
790
     * @param string $address
791
     * @param string $name
792
     * @return boolean true on success, false if address already used
793
     */
794
    public function addBCC($address, $name = '')
795
    {
796
        return $this->addAnAddress('bcc', $address, $name);
797
    }
798
799
    /**
800
     * Add a "Reply-to" address.
801
     * @param string $address
802
     * @param string $name
803
     * @return boolean
804
     */
805
    public function addReplyTo($address, $name = '')
806
    {
807
        return $this->addAnAddress('Reply-To', $address, $name);
808
    }
809
810
    /**
811
     * Add an address to one of the recipient arrays.
812
     * Addresses that have been added already return false, but do not throw exceptions
813
     * @param string $kind One of 'to', 'cc', 'bcc', 'ReplyTo'
814
     * @param string $address The email address to send to
815
     * @param string $name
816
     * @throws phpmailerException
817
     * @return boolean true on success, false if address already used or invalid in some way
818
     * @access protected
819
     */
820
    protected function addAnAddress($kind, $address, $name = '')
821
    {
822
        if (!preg_match('/^(to|cc|bcc|Reply-To)$/', $kind)) {
823
            $this->setError($this->lang('Invalid recipient array') . ': ' . $kind);
824
            $this->edebug($this->lang('Invalid recipient array') . ': ' . $kind);
825
            if ($this->exceptions) {
826
                throw new phpmailerException('Invalid recipient array: ' . $kind);
827
            }
828
            return false;
829
        }
830
        $address = trim($address);
831
        $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim
832 View Code Duplication
        if (!$this->validateAddress($address)) {
833
            $this->setError($this->lang('invalid_address') . ': ' . $address);
834
            $this->edebug($this->lang('invalid_address') . ': ' . $address);
835
            if ($this->exceptions) {
836
                throw new phpmailerException($this->lang('invalid_address') . ': ' . $address);
837
            }
838
            return false;
839
        }
840
        if ($kind != 'Reply-To') {
841
            if (!isset($this->all_recipients[strtolower($address)])) {
842
                array_push($this->$kind, array($address, $name));
843
                $this->all_recipients[strtolower($address)] = true;
844
                return true;
845
            }
846
        } else {
847
            if (!array_key_exists(strtolower($address), $this->ReplyTo)) {
848
                $this->ReplyTo[strtolower($address)] = array($address, $name);
849
                return true;
850
            }
851
        }
852
        return false;
853
    }
854
855
    /**
856
     * Parse and validate a string containing one or more RFC822-style comma-separated email addresses
857
     * of the form "display name <address>" into an array of name/address pairs.
858
     * Uses the imap_rfc822_parse_adrlist function if the IMAP extension is available.
859
     * Note that quotes in the name part are removed.
860
     * @param string $addrstr The address list string
861
     * @param bool $useimap Whether to use the IMAP extension to parse the list
862
     * @return array
863
     * @link http://www.andrew.cmu.edu/user/agreen1/testing/mrbs/web/Mail/RFC822.php A more careful implementation
864
     */
865
    public function parseAddresses($addrstr, $useimap = true)
866
    {
867
        $addresses = array();
868
        if ($useimap and function_exists('imap_rfc822_parse_adrlist')) {
869
            //Use this built-in parser if it's available
870
            $list = imap_rfc822_parse_adrlist($addrstr, '');
871
            foreach ($list as $address) {
872
                if ($address->host != '.SYNTAX-ERROR.') {
873
                    if ($this->validateAddress($address->mailbox . '@' . $address->host)) {
874
                        $addresses[] = array(
875
                            'name' => (property_exists($address, 'personal') ? $address->personal : ''),
876
                            'address' => $address->mailbox . '@' . $address->host
877
                        );
878
                    }
879
                }
880
            }
881
        } else {
882
            //Use this simpler parser
883
            $list = explode(',', $addrstr);
884
            foreach ($list as $address) {
885
                $address = trim($address);
886
                //Is there a separate name part?
887
                if (strpos($address, '<') === false) {
888
                    //No separate name, just use the whole thing
889
                    if ($this->validateAddress($address)) {
890
                        $addresses[] = array(
891
                            'name' => '',
892
                            'address' => $address
893
                        );
894
                    }
895
                } else {
896
                    list($name, $email) = explode('<', $address);
897
                    $email = trim(str_replace('>', '', $email));
898
                    if ($this->validateAddress($email)) {
899
                        $addresses[] = array(
900
                            'name' => trim(str_replace(array('"', "'"), '', $name)),
901
                            'address' => $email
902
                        );
903
                    }
904
                }
905
            }
906
        }
907
        return $addresses;
908
    }
909
910
    /**
911
     * Set the From and FromName properties.
912
     * @param string $address
913
     * @param string $name
914
     * @param boolean $auto Whether to also set the Sender address, defaults to true
915
     * @throws phpmailerException
916
     * @return boolean
917
     */
918
    public function setFrom($address, $name = '', $auto = true)
919
    {
920
        $address = trim($address);
921
        $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim
922 View Code Duplication
        if (!$this->validateAddress($address)) {
923
            $this->setError($this->lang('invalid_address') . ': ' . $address);
924
            $this->edebug($this->lang('invalid_address') . ': ' . $address);
925
            if ($this->exceptions) {
926
                throw new phpmailerException($this->lang('invalid_address') . ': ' . $address);
927
            }
928
            return false;
929
        }
930
        $this->From = $address;
931
        $this->FromName = $name;
932
        if ($auto) {
933
            if (empty($this->Sender)) {
934
                $this->Sender = $address;
935
            }
936
        }
937
        return true;
938
    }
939
940
    /**
941
     * Return the Message-ID header of the last email.
942
     * Technically this is the value from the last time the headers were created,
943
     * but it's also the message ID of the last sent message except in
944
     * pathological cases.
945
     * @return string
946
     */
947
    public function getLastMessageID()
948
    {
949
        return $this->lastMessageID;
950
    }
951
952
    /**
953
     * Check that a string looks like an email address.
954
     * @param string $address The email address to check
955
     * @param string $patternselect A selector for the validation pattern to use :
956
     * * `auto` Pick strictest one automatically;
957
     * * `pcre8` Use the squiloople.com pattern, requires PCRE > 8.0, PHP >= 5.3.2, 5.2.14;
958
     * * `pcre` Use old PCRE implementation;
959
     * * `php` Use PHP built-in FILTER_VALIDATE_EMAIL; same as pcre8 but does not allow 'dotless' domains;
960
     * * `html5` Use the pattern given by the HTML5 spec for 'email' type form input elements.
961
     * * `noregex` Don't use a regex: super fast, really dumb.
962
     * @return boolean
963
     * @static
964
     * @access public
965
     */
966
    public static function validateAddress($address, $patternselect = 'auto')
967
    {
968
        if (!$patternselect or $patternselect == 'auto') {
969
            //Check this constant first so it works when extension_loaded() is disabled by safe mode
970
            //Constant was added in PHP 5.2.4
971
            if (defined('PCRE_VERSION')) {
972
                //This pattern can get stuck in a recursive loop in PCRE <= 8.0.2
973
                if (version_compare(PCRE_VERSION, '8.0.3') >= 0) {
974
                    $patternselect = 'pcre8';
975
                } else {
976
                    $patternselect = 'pcre';
977
                }
978
            } elseif (function_exists('extension_loaded') and extension_loaded('pcre')) {
979
                //Fall back to older PCRE
980
                $patternselect = 'pcre';
981
            } else {
982
                //Filter_var appeared in PHP 5.2.0 and does not require the PCRE extension
983
                if (version_compare(PHP_VERSION, '5.2.0') >= 0) {
984
                    $patternselect = 'php';
985
                } else {
986
                    $patternselect = 'noregex';
987
                }
988
            }
989
        }
990
        switch ($patternselect) {
991 View Code Duplication
            case 'pcre8':
992
                /**
993
                 * Uses the same RFC5322 regex on which FILTER_VALIDATE_EMAIL is based, but allows dotless domains.
994
                 * @link http://squiloople.com/2009/12/20/email-address-validation/
995
                 * @copyright 2009-2010 Michael Rushton
996
                 * Feel free to use and redistribute this code. But please keep this copyright notice.
997
                 */
998
                return (boolean)preg_match(
999
                    '/^(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){255,})(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){65,}@)' .
1000
                    '((?>(?>(?>((?>(?>(?>\x0D\x0A)?[\t ])+|(?>[\t ]*\x0D\x0A)?[\t ]+)?)(\((?>(?2)' .
1001
                    '(?>[\x01-\x08\x0B\x0C\x0E-\'*-\[\]-\x7F]|\\\[\x00-\x7F]|(?3)))*(?2)\)))+(?2))|(?2))?)' .
1002
                    '([!#-\'*+\/-9=?^-~-]+|"(?>(?2)(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\x7F]))*' .
1003
                    '(?2)")(?>(?1)\.(?1)(?4))*(?1)@(?!(?1)[a-z0-9-]{64,})(?1)(?>([a-z0-9](?>[a-z0-9-]*[a-z0-9])?)' .
1004
                    '(?>(?1)\.(?!(?1)[a-z0-9-]{64,})(?1)(?5)){0,126}|\[(?:(?>IPv6:(?>([a-f0-9]{1,4})(?>:(?6)){7}' .
1005
                    '|(?!(?:.*[a-f0-9][:\]]){8,})((?6)(?>:(?6)){0,6})?::(?7)?))|(?>(?>IPv6:(?>(?6)(?>:(?6)){5}:' .
1006
                    '|(?!(?:.*[a-f0-9]:){6,})(?8)?::(?>((?6)(?>:(?6)){0,4}):)?))?(25[0-5]|2[0-4][0-9]|1[0-9]{2}' .
1007
                    '|[1-9]?[0-9])(?>\.(?9)){3}))\])(?1)$/isD',
1008
                    $address
1009
                );
1010 View Code Duplication
            case 'pcre':
1011
                //An older regex that doesn't need a recent PCRE
1012
                return (boolean)preg_match(
1013
                    '/^(?!(?>"?(?>\\\[ -~]|[^"])"?){255,})(?!(?>"?(?>\\\[ -~]|[^"])"?){65,}@)(?>' .
1014
                    '[!#-\'*+\/-9=?^-~-]+|"(?>(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\xFF]))*")' .
1015
                    '(?>\.(?>[!#-\'*+\/-9=?^-~-]+|"(?>(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\xFF]))*"))*' .
1016
                    '@(?>(?![a-z0-9-]{64,})(?>[a-z0-9](?>[a-z0-9-]*[a-z0-9])?)(?>\.(?![a-z0-9-]{64,})' .
1017
                    '(?>[a-z0-9](?>[a-z0-9-]*[a-z0-9])?)){0,126}|\[(?:(?>IPv6:(?>(?>[a-f0-9]{1,4})(?>:' .
1018
                    '[a-f0-9]{1,4}){7}|(?!(?:.*[a-f0-9][:\]]){8,})(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,6})?' .
1019
                    '::(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,6})?))|(?>(?>IPv6:(?>[a-f0-9]{1,4}(?>:' .
1020
                    '[a-f0-9]{1,4}){5}:|(?!(?:.*[a-f0-9]:){6,})(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,4})?' .
1021
                    '::(?>(?:[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,4}):)?))?(?>25[0-5]|2[0-4][0-9]|1[0-9]{2}' .
1022
                    '|[1-9]?[0-9])(?>\.(?>25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}))\])$/isD',
1023
                    $address
1024
                );
1025
            case 'html5':
1026
                /**
1027
                 * This is the pattern used in the HTML5 spec for validation of 'email' type form input elements.
1028
                 * @link http://www.whatwg.org/specs/web-apps/current-work/#e-mail-state-(type=email)
1029
                 */
1030
                return (boolean)preg_match(
1031
                    '/^[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}' .
1032
                    '[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/sD',
1033
                    $address
1034
                );
1035
            case 'noregex':
1036
                //No PCRE! Do something _very_ approximate!
1037
                //Check the address is 3 chars or longer and contains an @ that's not the first or last char
1038
                return (strlen($address) >= 3
1039
                    and strpos($address, '@') >= 1
1040
                    and strpos($address, '@') != strlen($address) - 1);
1041
            case 'php':
1042
            default:
1043
                return (boolean)filter_var($address, FILTER_VALIDATE_EMAIL);
1044
        }
1045
    }
1046
1047
    /**
1048
     * Create a message and send it.
1049
     * Uses the sending method specified by $Mailer.
1050
     * @throws phpmailerException
1051
     * @return boolean false on error - See the ErrorInfo property for details of the error.
1052
     */
1053
    public function send()
1054
    {
1055
        try {
1056
            if (!$this->preSend()) {
1057
                return false;
1058
            }
1059
            return $this->postSend();
1060
        } catch (phpmailerException $exc) {
1061
            $this->mailHeader = '';
1062
            $this->setError($exc->getMessage());
1063
            if ($this->exceptions) {
1064
                throw $exc;
1065
            }
1066
            return false;
1067
        }
1068
    }
1069
1070
    /**
1071
     * Prepare a message for sending.
1072
     * @throws phpmailerException
1073
     * @return boolean
1074
     */
1075
    public function preSend()
1076
    {
1077
        try {
1078
            $this->mailHeader = '';
1079
            if ((count($this->to) + count($this->cc) + count($this->bcc)) < 1) {
1080
                throw new phpmailerException($this->lang('provide_address'), self::STOP_CRITICAL);
1081
            }
1082
1083
            // Set whether the message is multipart/alternative
1084
            if (!empty($this->AltBody)) {
1085
                $this->ContentType = 'multipart/alternative';
1086
            }
1087
1088
            $this->error_count = 0; // Reset errors
1089
            $this->setMessageType();
1090
            // Refuse to send an empty message unless we are specifically allowing it
1091
            if (!$this->AllowEmpty and empty($this->Body)) {
1092
                throw new phpmailerException($this->lang('empty_message'), self::STOP_CRITICAL);
1093
            }
1094
1095
            // Create body before headers in case body makes changes to headers (e.g. altering transfer encoding)
1096
            $this->MIMEHeader = '';
1097
            $this->MIMEBody = $this->createBody();
1098
            // createBody may have added some headers, so retain them
1099
            $tempheaders = $this->MIMEHeader;
1100
            $this->MIMEHeader = $this->createHeader();
1101
            $this->MIMEHeader .= $tempheaders;
1102
1103
            // To capture the complete message when using mail(), create
1104
            // an extra header list which createHeader() doesn't fold in
1105
            if ($this->Mailer == 'mail') {
1106
                if (count($this->to) > 0) {
1107
                    $this->mailHeader .= $this->addrAppend('To', $this->to);
1108
                } else {
1109
                    $this->mailHeader .= $this->headerLine('To', 'undisclosed-recipients:;');
1110
                }
1111
                $this->mailHeader .= $this->headerLine(
1112
                    'Subject',
1113
                    $this->encodeHeader($this->secureHeader(trim($this->Subject)))
1114
                );
1115
            }
1116
1117
            // Sign with DKIM if enabled
1118
            if (!empty($this->DKIM_domain)
1119
                && !empty($this->DKIM_private)
1120
                && !empty($this->DKIM_selector)
1121
                && file_exists($this->DKIM_private)) {
1122
                $header_dkim = $this->DKIM_Add(
1123
                    $this->MIMEHeader . $this->mailHeader,
1124
                    $this->encodeHeader($this->secureHeader($this->Subject)),
1125
                    $this->MIMEBody
1126
                );
1127
                $this->MIMEHeader = rtrim($this->MIMEHeader, "\r\n ") . self::CRLF .
1128
                    str_replace("\r\n", "\n", $header_dkim) . self::CRLF;
1129
            }
1130
            return true;
1131
        } catch (phpmailerException $exc) {
1132
            $this->setError($exc->getMessage());
1133
            if ($this->exceptions) {
1134
                throw $exc;
1135
            }
1136
            return false;
1137
        }
1138
    }
1139
1140
    /**
1141
     * Actually send a message.
1142
     * Send the email via the selected mechanism
1143
     * @throws phpmailerException
1144
     * @return boolean
1145
     */
1146
    public function postSend()
1147
    {
1148
        try {
1149
            // Choose the mailer and send through it
1150
            switch ($this->Mailer) {
1151
                case 'sendmail':
1152
                case 'qmail':
1153
                    return $this->sendmailSend($this->MIMEHeader, $this->MIMEBody);
1154
                case 'smtp':
1155
                    return $this->smtpSend($this->MIMEHeader, $this->MIMEBody);
1156
                case 'mail':
1157
                    return $this->mailSend($this->MIMEHeader, $this->MIMEBody);
1158
                default:
1159
                    $sendMethod = $this->Mailer.'Send';
1160
                    if (method_exists($this, $sendMethod)) {
1161
                        return $this->$sendMethod($this->MIMEHeader, $this->MIMEBody);
1162
                    }
1163
1164
                    return $this->mailSend($this->MIMEHeader, $this->MIMEBody);
1165
            }
1166
        } catch (phpmailerException $exc) {
1167
            $this->setError($exc->getMessage());
1168
            $this->edebug($exc->getMessage());
1169
            if ($this->exceptions) {
1170
                throw $exc;
1171
            }
1172
        }
1173
        return false;
1174
    }
1175
1176
    /**
1177
     * Send mail using the $Sendmail program.
1178
     * @param string $header The message headers
1179
     * @param string $body The message body
1180
     * @see PHPMailer::$Sendmail
1181
     * @throws phpmailerException
1182
     * @access protected
1183
     * @return boolean
1184
     */
1185
    protected function sendmailSend($header, $body)
1186
    {
1187
        if ($this->Sender != '') {
1188
            if ($this->Mailer == 'qmail') {
1189
                $sendmail = sprintf('%s -f%s', escapeshellcmd($this->Sendmail), escapeshellarg($this->Sender));
1190
            } else {
1191
                $sendmail = sprintf('%s -oi -f%s -t', escapeshellcmd($this->Sendmail), escapeshellarg($this->Sender));
1192
            }
1193
        } else {
1194
            if ($this->Mailer == 'qmail') {
1195
                $sendmail = sprintf('%s', escapeshellcmd($this->Sendmail));
1196
            } else {
1197
                $sendmail = sprintf('%s -oi -t', escapeshellcmd($this->Sendmail));
1198
            }
1199
        }
1200
        if ($this->SingleTo) {
1201
            foreach ($this->SingleToArray as $toAddr) {
1202 View Code Duplication
                if (!@$mail = popen($sendmail, 'w')) {
1203
                    throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
1204
                }
1205
                fputs($mail, 'To: ' . $toAddr . "\n");
1206
                fputs($mail, $header);
1207
                fputs($mail, $body);
1208
                $result = pclose($mail);
1209
                $this->doCallback(
1210
                    ($result == 0),
1211
                    array($toAddr),
1212
                    $this->cc,
1213
                    $this->bcc,
1214
                    $this->Subject,
1215
                    $body,
1216
                    $this->From
1217
                );
1218
                if ($result != 0) {
1219
                    throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
1220
                }
1221
            }
1222
        } else {
1223 View Code Duplication
            if (!@$mail = popen($sendmail, 'w')) {
1224
                throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
1225
            }
1226
            fputs($mail, $header);
1227
            fputs($mail, $body);
1228
            $result = pclose($mail);
1229
            $this->doCallback(($result == 0), $this->to, $this->cc, $this->bcc, $this->Subject, $body, $this->From);
1230
            if ($result != 0) {
1231
                throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
1232
            }
1233
        }
1234
        return true;
1235
    }
1236
1237
    /**
1238
     * Send mail using the PHP mail() function.
1239
     * @param string $header The message headers
1240
     * @param string $body The message body
1241
     * @link http://www.php.net/manual/en/book.mail.php
1242
     * @throws phpmailerException
1243
     * @access protected
1244
     * @return boolean
1245
     */
1246
    protected function mailSend($header, $body)
1247
    {
1248
        $toArr = array();
1249
        foreach ($this->to as $toaddr) {
1250
            $toArr[] = $this->addrFormat($toaddr);
1251
        }
1252
        $to = implode(', ', $toArr);
1253
1254
        if (empty($this->Sender)) {
1255
            $params = ' ';
1256
        } else {
1257
            $params = sprintf('-f%s', $this->Sender);
1258
        }
1259
        if ($this->Sender != '' and !ini_get('safe_mode')) {
1260
            $old_from = ini_get('sendmail_from');
1261
            ini_set('sendmail_from', $this->Sender);
1262
        }
1263
        $result = false;
1264
        if ($this->SingleTo && count($toArr) > 1) {
1265
            foreach ($toArr as $toAddr) {
1266
                $result = $this->mailPassthru($toAddr, $this->Subject, $body, $header, $params);
1267
                $this->doCallback($result, array($toAddr), $this->cc, $this->bcc, $this->Subject, $body, $this->From);
1268
            }
1269
        } else {
1270
            $result = $this->mailPassthru($to, $this->Subject, $body, $header, $params);
1271
            $this->doCallback($result, $this->to, $this->cc, $this->bcc, $this->Subject, $body, $this->From);
1272
        }
1273
        if (isset($old_from)) {
1274
            ini_set('sendmail_from', $old_from);
1275
        }
1276
        if (!$result) {
1277
            throw new phpmailerException($this->lang('instantiate'), self::STOP_CRITICAL);
1278
        }
1279
        return true;
1280
    }
1281
1282
    /**
1283
     * Get an instance to use for SMTP operations.
1284
     * Override this function to load your own SMTP implementation
1285
     * @return SMTP
1286
     */
1287
    public function getSMTPInstance()
1288
    {
1289
        if (!is_object($this->smtp)) {
1290
            $this->smtp = new SMTP;
1291
        }
1292
        return $this->smtp;
1293
    }
1294
1295
    /**
1296
     * Send mail via SMTP.
1297
     * Returns false if there is a bad MAIL FROM, RCPT, or DATA input.
1298
     * Uses the PHPMailerSMTP class by default.
1299
     * @see PHPMailer::getSMTPInstance() to use a different class.
1300
     * @param string $header The message headers
1301
     * @param string $body The message body
1302
     * @throws phpmailerException
1303
     * @uses SMTP
1304
     * @access protected
1305
     * @return boolean
1306
     */
1307
    protected function smtpSend($header, $body)
1308
    {
1309
        $bad_rcpt = array();
1310
        if (!$this->smtpConnect($this->SMTPOptions)) {
1311
            throw new phpmailerException($this->lang('smtp_connect_failed'), self::STOP_CRITICAL);
1312
        }
1313
        if ('' == $this->Sender) {
1314
            $smtp_from = $this->From;
1315
        } else {
1316
            $smtp_from = $this->Sender;
1317
        }
1318
        if (!$this->smtp->mail($smtp_from)) {
1319
            $this->setError($this->lang('from_failed') . $smtp_from . ' : ' . implode(',', $this->smtp->getError()));
1320
            throw new phpmailerException($this->ErrorInfo, self::STOP_CRITICAL);
1321
        }
1322
1323
        // Attempt to send to all recipients
1324
        foreach (array($this->to, $this->cc, $this->bcc) as $togroup) {
1325
            foreach ($togroup as $to) {
1326
                if (!$this->smtp->recipient($to[0])) {
1327
                    $error = $this->smtp->getError();
1328
                    $bad_rcpt[] = array('to' => $to[0], 'error' => $error['detail']);
1329
                    $isSent = false;
1330
                } else {
1331
                    $isSent = true;
1332
                }
1333
                $this->doCallback($isSent, array($to[0]), array(), array(), $this->Subject, $body, $this->From);
1334
            }
1335
        }
1336
1337
        // Only send the DATA command if we have viable recipients
1338
        if ((count($this->all_recipients) > count($bad_rcpt)) and !$this->smtp->data($header . $body)) {
1339
            throw new phpmailerException($this->lang('data_not_accepted'), self::STOP_CRITICAL);
1340
        }
1341
        if ($this->SMTPKeepAlive) {
1342
            $this->smtp->reset();
1343
        } else {
1344
            $this->smtp->quit();
1345
            $this->smtp->close();
1346
        }
1347
        //Create error message for any bad addresses
1348
        if (count($bad_rcpt) > 0) {
1349
            $errstr = '';
1350
            foreach ($bad_rcpt as $bad) {
1351
                $errstr .= $bad['to'] . ': ' . $bad['error'];
1352
            }
1353
            throw new phpmailerException(
1354
                $this->lang('recipients_failed') . $errstr,
1355
                self::STOP_CONTINUE
1356
            );
1357
        }
1358
        return true;
1359
    }
1360
1361
    /**
1362
     * Initiate a connection to an SMTP server.
1363
     * Returns false if the operation failed.
1364
     * @param array $options An array of options compatible with stream_context_create()
1365
     * @uses SMTP
1366
     * @access public
1367
     * @throws phpmailerException
1368
     * @return boolean
1369
     */
1370
    public function smtpConnect($options = array())
1371
    {
1372
        if (is_null($this->smtp)) {
1373
            $this->smtp = $this->getSMTPInstance();
1374
        }
1375
1376
        // Already connected?
1377
        if ($this->smtp->connected()) {
1378
            return true;
1379
        }
1380
1381
        $this->smtp->setTimeout($this->Timeout);
1382
        $this->smtp->setDebugLevel($this->SMTPDebug);
1383
        $this->smtp->setDebugOutput($this->Debugoutput);
1384
        $this->smtp->setVerp($this->do_verp);
1385
        $hosts = explode(';', $this->Host);
1386
        $lastexception = null;
1387
1388
        foreach ($hosts as $hostentry) {
1389
            $hostinfo = array();
1390
            if (!preg_match('/^((ssl|tls):\/\/)*([a-zA-Z0-9\.-]*):?([0-9]*)$/', trim($hostentry), $hostinfo)) {
1391
                // Not a valid host entry
1392
                continue;
1393
            }
1394
            // $hostinfo[2]: optional ssl or tls prefix
1395
            // $hostinfo[3]: the hostname
1396
            // $hostinfo[4]: optional port number
1397
            // The host string prefix can temporarily override the current setting for SMTPSecure
1398
            // If it's not specified, the default value is used
1399
            $prefix = '';
1400
            $secure = $this->SMTPSecure;
1401
            $tls = ($this->SMTPSecure == 'tls');
1402
            if ('ssl' == $hostinfo[2] or ('' == $hostinfo[2] and 'ssl' == $this->SMTPSecure)) {
1403
                $prefix = 'ssl://';
1404
                $tls = false; // Can't have SSL and TLS at the same time
1405
                $secure = 'ssl';
1406
            } elseif ($hostinfo[2] == 'tls') {
1407
                $tls = true;
1408
                // tls doesn't use a prefix
1409
                $secure = 'tls';
1410
            }
1411
            //Do we need the OpenSSL extension?
1412
            $sslext = defined('OPENSSL_ALGO_SHA1');
1413
            if ('tls' === $secure or 'ssl' === $secure) {
1414
                //Check for an OpenSSL constant rather than using extension_loaded, which is sometimes disabled
1415
                if (!$sslext) {
1416
                    throw new phpmailerException($this->lang('extension_missing').'openssl', self::STOP_CRITICAL);
1417
                }
1418
            }
1419
            $host = $hostinfo[3];
1420
            $port = $this->Port;
1421
            $tport = (integer)$hostinfo[4];
1422
            if ($tport > 0 and $tport < 65536) {
1423
                $port = $tport;
1424
            }
1425
            if ($this->smtp->connect($prefix . $host, $port, $this->Timeout, $options)) {
1426
                try {
1427
                    if ($this->Helo) {
1428
                        $hello = $this->Helo;
1429
                    } else {
1430
                        $hello = $this->serverHostname();
1431
                    }
1432
                    $this->smtp->hello($hello);
1433
                    //Automatically enable TLS encryption if:
1434
                    // * it's not disabled
1435
                    // * we have openssl extension
1436
                    // * we are not already using SSL
1437
                    // * the server offers STARTTLS
1438
                    if ($this->SMTPAutoTLS and $sslext and $secure != 'ssl' and $this->smtp->getServerExt('STARTTLS')) {
1439
                        $tls = true;
1440
                    }
1441
                    if ($tls) {
1442
                        if (!$this->smtp->startTLS()) {
1443
                            throw new phpmailerException($this->lang('connect_host'));
1444
                        }
1445
                        // We must resend HELO after tls negotiation
1446
                        $this->smtp->hello($hello);
1447
                    }
1448
                    if ($this->SMTPAuth) {
1449
                        if (!$this->smtp->authenticate(
1450
                            $this->Username,
1451
                            $this->Password,
1452
                            $this->AuthType,
1453
                            $this->Realm,
1454
                            $this->Workstation
1455
                        )
1456
                        ) {
1457
                            throw new phpmailerException($this->lang('authenticate'));
1458
                        }
1459
                    }
1460
                    return true;
1461
                } catch (phpmailerException $exc) {
1462
                    $lastexception = $exc;
1463
                    $this->edebug($exc->getMessage());
1464
                    // We must have connected, but then failed TLS or Auth, so close connection nicely
1465
                    $this->smtp->quit();
1466
                }
1467
            }
1468
        }
1469
        // If we get here, all connection attempts have failed, so close connection hard
1470
        $this->smtp->close();
1471
        // As we've caught all exceptions, just report whatever the last one was
1472
        if ($this->exceptions and !is_null($lastexception)) {
1473
            throw $lastexception;
1474
        }
1475
        return false;
1476
    }
1477
1478
    /**
1479
     * Close the active SMTP session if one exists.
1480
     * @return void
1481
     */
1482
    public function smtpClose()
1483
    {
1484
        if ($this->smtp !== null) {
1485
            if ($this->smtp->connected()) {
1486
                $this->smtp->quit();
1487
                $this->smtp->close();
1488
            }
1489
        }
1490
    }
1491
1492
    /**
1493
     * Set the language for error messages.
1494
     * Returns false if it cannot load the language file.
1495
     * The default language is English.
1496
     * @param string $langcode ISO 639-1 2-character language code (e.g. French is "fr")
1497
     * @param string $lang_path Path to the language file directory, with trailing separator (slash)
1498
     * @return boolean
1499
     * @access public
1500
     */
1501
    public function setLanguage($langcode = 'en', $lang_path = '')
1502
    {
1503
        // Define full set of translatable strings in English
1504
        $PHPMAILER_LANG = array(
1505
            'authenticate' => 'SMTP Error: Could not authenticate.',
1506
            'connect_host' => 'SMTP Error: Could not connect to SMTP host.',
1507
            'data_not_accepted' => 'SMTP Error: data not accepted.',
1508
            'empty_message' => 'Message body empty',
1509
            'encoding' => 'Unknown encoding: ',
1510
            'execute' => 'Could not execute: ',
1511
            'file_access' => 'Could not access file: ',
1512
            'file_open' => 'File Error: Could not open file: ',
1513
            'from_failed' => 'The following From address failed: ',
1514
            'instantiate' => 'Could not instantiate mail function.',
1515
            'invalid_address' => 'Invalid address',
1516
            'mailer_not_supported' => ' mailer is not supported.',
1517
            'provide_address' => 'You must provide at least one recipient email address.',
1518
            'recipients_failed' => 'SMTP Error: The following recipients failed: ',
1519
            'signing' => 'Signing Error: ',
1520
            'smtp_connect_failed' => 'SMTP connect() failed.',
1521
            'smtp_error' => 'SMTP server error: ',
1522
            'variable_set' => 'Cannot set or reset variable: ',
1523
            'extension_missing' => 'Extension missing: '
1524
        );
1525
        if (empty($lang_path)) {
1526
            // Calculate an absolute path so it can work if CWD is not here
1527
            $lang_path = dirname(__FILE__). DIRECTORY_SEPARATOR . 'language'. DIRECTORY_SEPARATOR;
1528
        }
1529
        $foundlang = true;
1530
        $lang_file = $lang_path . 'phpmailer.lang-' . $langcode . '.php';
1531
        // There is no English translation file
1532
        if ($langcode != 'en') {
1533
            // Make sure language file path is readable
1534
            if (!is_readable($lang_file)) {
1535
                $foundlang = false;
1536
            } else {
1537
                // Overwrite language-specific strings.
1538
                // This way we'll never have missing translation keys.
1539
                $foundlang = include $lang_file;
1540
            }
1541
        }
1542
        $this->language = $PHPMAILER_LANG;
1543
        return (boolean)$foundlang; // Returns false if language not found
1544
    }
1545
1546
    /**
1547
     * Get the array of strings for the current language.
1548
     * @return array
1549
     */
1550
    public function getTranslations()
1551
    {
1552
        return $this->language;
1553
    }
1554
1555
    /**
1556
     * Create recipient headers.
1557
     * @access public
1558
     * @param string $type
1559
     * @param array $addr An array of recipient,
1560
     * where each recipient is a 2-element indexed array with element 0 containing an address
1561
     * and element 1 containing a name, like:
1562
     * array(array('[email protected]', 'Joe User'), array('[email protected]', 'Zoe User'))
1563
     * @return string
1564
     */
1565
    public function addrAppend($type, $addr)
1566
    {
1567
        $addresses = array();
1568
        foreach ($addr as $address) {
1569
            $addresses[] = $this->addrFormat($address);
1570
        }
1571
        return $type . ': ' . implode(', ', $addresses) . $this->LE;
1572
    }
1573
1574
    /**
1575
     * Format an address for use in a message header.
1576
     * @access public
1577
     * @param array $addr A 2-element indexed array, element 0 containing an address, element 1 containing a name
1578
     *      like array('[email protected]', 'Joe User')
1579
     * @return string
1580
     */
1581
    public function addrFormat($addr)
1582
    {
1583
        if (empty($addr[1])) { // No name provided
1584
            return $this->secureHeader($addr[0]);
1585
        } else {
1586
            return $this->encodeHeader($this->secureHeader($addr[1]), 'phrase') . ' <' . $this->secureHeader(
1587
                $addr[0]
1588
            ) . '>';
1589
        }
1590
    }
1591
1592
    /**
1593
     * Word-wrap message.
1594
     * For use with mailers that do not automatically perform wrapping
1595
     * and for quoted-printable encoded messages.
1596
     * Original written by philippe.
1597
     * @param string $message The message to wrap
1598
     * @param integer $length The line length to wrap to
1599
     * @param boolean $qp_mode Whether to run in Quoted-Printable mode
1600
     * @access public
1601
     * @return string
1602
     */
1603
    public function wrapText($message, $length, $qp_mode = false)
1604
    {
1605
        if ($qp_mode) {
1606
            $soft_break = sprintf(' =%s', $this->LE);
1607
        } else {
1608
            $soft_break = $this->LE;
1609
        }
1610
        // If utf-8 encoding is used, we will need to make sure we don't
1611
        // split multibyte characters when we wrap
1612
        $is_utf8 = (strtolower($this->CharSet) == 'utf-8');
1613
        $lelen = strlen($this->LE);
1614
        $crlflen = strlen(self::CRLF);
1615
1616
        $message = $this->fixEOL($message);
1617
        //Remove a trailing line break
1618
        if (substr($message, -$lelen) == $this->LE) {
1619
            $message = substr($message, 0, -$lelen);
1620
        }
1621
1622
        //Split message into lines
1623
        $lines = explode($this->LE, $message);
1624
        //Message will be rebuilt in here
1625
        $message = '';
1626
        foreach ($lines as $line) {
1627
            $words = explode(' ', $line);
1628
            $buf = '';
1629
            $firstword = true;
1630
            foreach ($words as $word) {
1631
                if ($qp_mode and (strlen($word) > $length)) {
1632
                    $space_left = $length - strlen($buf) - $crlflen;
1633
                    if (!$firstword) {
1634
                        if ($space_left > 20) {
1635
                            $len = $space_left;
1636 View Code Duplication
                            if ($is_utf8) {
1637
                                $len = $this->utf8CharBoundary($word, $len);
1638
                            } elseif (substr($word, $len - 1, 1) == '=') {
1639
                                $len--;
1640
                            } elseif (substr($word, $len - 2, 1) == '=') {
1641
                                $len -= 2;
1642
                            }
1643
                            $part = substr($word, 0, $len);
1644
                            $word = substr($word, $len);
1645
                            $buf .= ' ' . $part;
1646
                            $message .= $buf . sprintf('=%s', self::CRLF);
1647
                        } else {
1648
                            $message .= $buf . $soft_break;
1649
                        }
1650
                        $buf = '';
1651
                    }
1652
                    while (strlen($word) > 0) {
1653
                        if ($length <= 0) {
1654
                            break;
1655
                        }
1656
                        $len = $length;
1657 View Code Duplication
                        if ($is_utf8) {
1658
                            $len = $this->utf8CharBoundary($word, $len);
1659
                        } elseif (substr($word, $len - 1, 1) == '=') {
1660
                            $len--;
1661
                        } elseif (substr($word, $len - 2, 1) == '=') {
1662
                            $len -= 2;
1663
                        }
1664
                        $part = substr($word, 0, $len);
1665
                        $word = substr($word, $len);
1666
1667
                        if (strlen($word) > 0) {
1668
                            $message .= $part . sprintf('=%s', self::CRLF);
1669
                        } else {
1670
                            $buf = $part;
1671
                        }
1672
                    }
1673
                } else {
1674
                    $buf_o = $buf;
1675
                    if (!$firstword) {
1676
                        $buf .= ' ';
1677
                    }
1678
                    $buf .= $word;
1679
1680
                    if (strlen($buf) > $length and $buf_o != '') {
1681
                        $message .= $buf_o . $soft_break;
1682
                        $buf = $word;
1683
                    }
1684
                }
1685
                $firstword = false;
1686
            }
1687
            $message .= $buf . self::CRLF;
1688
        }
1689
1690
        return $message;
1691
    }
1692
1693
    /**
1694
     * Find the last character boundary prior to $maxLength in a utf-8
1695
     * quoted-printable encoded string.
1696
     * Original written by Colin Brown.
1697
     * @access public
1698
     * @param string $encodedText utf-8 QP text
1699
     * @param integer $maxLength Find the last character boundary prior to this length
1700
     * @return integer
1701
     */
1702
    public function utf8CharBoundary($encodedText, $maxLength)
1703
    {
1704
        $foundSplitPos = false;
1705
        $lookBack = 3;
1706
        while (!$foundSplitPos) {
1707
            $lastChunk = substr($encodedText, $maxLength - $lookBack, $lookBack);
1708
            $encodedCharPos = strpos($lastChunk, '=');
1709
            if (false !== $encodedCharPos) {
1710
                // Found start of encoded character byte within $lookBack block.
1711
                // Check the encoded byte value (the 2 chars after the '=')
1712
                $hex = substr($encodedText, $maxLength - $lookBack + $encodedCharPos + 1, 2);
1713
                $dec = hexdec($hex);
1714
                if ($dec < 128) {
1715
                    // Single byte character.
1716
                    // If the encoded char was found at pos 0, it will fit
1717
                    // otherwise reduce maxLength to start of the encoded char
1718
                    if ($encodedCharPos > 0) {
1719
                        $maxLength = $maxLength - ($lookBack - $encodedCharPos);
1720
                    }
1721
                    $foundSplitPos = true;
1722
                } elseif ($dec >= 192) {
1723
                    // First byte of a multi byte character
1724
                    // Reduce maxLength to split at start of character
1725
                    $maxLength = $maxLength - ($lookBack - $encodedCharPos);
1726
                    $foundSplitPos = true;
1727
                } elseif ($dec < 192) {
1728
                    // Middle byte of a multi byte character, look further back
1729
                    $lookBack += 3;
1730
                }
1731
            } else {
1732
                // No encoded character found
1733
                $foundSplitPos = true;
1734
            }
1735
        }
1736
        return $maxLength;
1737
    }
1738
1739
    /**
1740
     * Apply word wrapping to the message body.
1741
     * Wraps the message body to the number of chars set in the WordWrap property.
1742
     * You should only do this to plain-text bodies as wrapping HTML tags may break them.
1743
     * This is called automatically by createBody(), so you don't need to call it yourself.
1744
     * @access public
1745
     * @return void
1746
     */
1747
    public function setWordWrap()
1748
    {
1749
        if ($this->WordWrap < 1) {
1750
            return;
1751
        }
1752
1753
        switch ($this->message_type) {
1754
            case 'alt':
1755
            case 'alt_inline':
1756
            case 'alt_attach':
1757
            case 'alt_inline_attach':
1758
                $this->AltBody = $this->wrapText($this->AltBody, $this->WordWrap);
1759
                break;
1760
            default:
1761
                $this->Body = $this->wrapText($this->Body, $this->WordWrap);
1762
                break;
1763
        }
1764
    }
1765
1766
    /**
1767
     * Assemble message headers.
1768
     * @access public
1769
     * @return string The assembled headers
1770
     */
1771
    public function createHeader()
1772
    {
1773
        $result = '';
1774
1775
        if ($this->MessageDate == '') {
1776
            $this->MessageDate = self::rfcDate();
1777
        }
1778
        $result .= $this->headerLine('Date', $this->MessageDate);
1779
1780
1781
        // To be created automatically by mail()
1782
        if ($this->SingleTo) {
1783
            if ($this->Mailer != 'mail') {
1784
                foreach ($this->to as $toaddr) {
1785
                    $this->SingleToArray[] = $this->addrFormat($toaddr);
1786
                }
1787
            }
1788
        } else {
1789
            if (count($this->to) > 0) {
1790
                if ($this->Mailer != 'mail') {
1791
                    $result .= $this->addrAppend('To', $this->to);
1792
                }
1793
            } elseif (count($this->cc) == 0) {
1794
                $result .= $this->headerLine('To', 'undisclosed-recipients:;');
1795
            }
1796
        }
1797
1798
        $result .= $this->addrAppend('From', array(array(trim($this->From), $this->FromName)));
1799
1800
        // sendmail and mail() extract Cc from the header before sending
1801
        if (count($this->cc) > 0) {
1802
            $result .= $this->addrAppend('Cc', $this->cc);
1803
        }
1804
1805
        // sendmail and mail() extract Bcc from the header before sending
1806
        if ((
1807
                $this->Mailer == 'sendmail' or $this->Mailer == 'qmail' or $this->Mailer == 'mail'
1808
            )
1809
            and count($this->bcc) > 0
1810
        ) {
1811
            $result .= $this->addrAppend('Bcc', $this->bcc);
1812
        }
1813
1814
        if (count($this->ReplyTo) > 0) {
1815
            $result .= $this->addrAppend('Reply-To', $this->ReplyTo);
1816
        }
1817
1818
        // mail() sets the subject itself
1819
        if ($this->Mailer != 'mail') {
1820
            $result .= $this->headerLine('Subject', $this->encodeHeader($this->secureHeader($this->Subject)));
1821
        }
1822
1823
        if ($this->MessageID != '') {
1824
            $this->lastMessageID = $this->MessageID;
1825
        } else {
1826
            $this->lastMessageID = sprintf('<%s@%s>', $this->uniqueid, $this->ServerHostname());
1827
        }
1828
        $result .= $this->headerLine('Message-ID', $this->lastMessageID);
1829
        $result .= $this->headerLine('X-Priority', $this->Priority);
1830
        if ($this->XMailer == '') {
1831
            $result .= $this->headerLine(
1832
                'X-Mailer',
1833
                'PHPMailer ' . $this->Version . ' (https://github.com/PHPMailer/PHPMailer/)'
1834
            );
1835
        } else {
1836
            $myXmailer = trim($this->XMailer);
1837
            if ($myXmailer) {
1838
                $result .= $this->headerLine('X-Mailer', $myXmailer);
1839
            }
1840
        }
1841
1842
        if ($this->ConfirmReadingTo != '') {
1843
            $result .= $this->headerLine('Disposition-Notification-To', '<' . trim($this->ConfirmReadingTo) . '>');
1844
        }
1845
1846
        // Add custom headers
1847
        foreach ($this->CustomHeader as $header) {
1848
            $result .= $this->headerLine(
1849
                trim($header[0]),
1850
                $this->encodeHeader(trim($header[1]))
1851
            );
1852
        }
1853
        if (!$this->sign_key_file) {
1854
            $result .= $this->headerLine('MIME-Version', '1.0');
1855
            $result .= $this->getMailMIME();
1856
        }
1857
1858
        return $result;
1859
    }
1860
1861
    /**
1862
     * Get the message MIME type headers.
1863
     * @access public
1864
     * @return string
1865
     */
1866
    public function getMailMIME()
1867
    {
1868
        $result = '';
1869
        $ismultipart = true;
1870
        switch ($this->message_type) {
1871 View Code Duplication
            case 'inline':
1872
                $result .= $this->headerLine('Content-Type', 'multipart/related;');
1873
                $result .= $this->textLine("\tboundary=\"" . $this->boundary[1] . '"');
1874
                break;
1875
            case 'attach':
1876
            case 'inline_attach':
1877
            case 'alt_attach':
1878 View Code Duplication
            case 'alt_inline_attach':
1879
                $result .= $this->headerLine('Content-Type', 'multipart/mixed;');
1880
                $result .= $this->textLine("\tboundary=\"" . $this->boundary[1] . '"');
1881
                break;
1882
            case 'alt':
1883 View Code Duplication
            case 'alt_inline':
1884
                $result .= $this->headerLine('Content-Type', 'multipart/alternative;');
1885
                $result .= $this->textLine("\tboundary=\"" . $this->boundary[1] . '"');
1886
                break;
1887
            default:
1888
                // Catches case 'plain': and case '':
1889
                $result .= $this->textLine('Content-Type: ' . $this->ContentType . '; charset=' . $this->CharSet);
1890
                $ismultipart = false;
1891
                break;
1892
        }
1893
        // RFC1341 part 5 says 7bit is assumed if not specified
1894
        if ($this->Encoding != '7bit') {
1895
            // RFC 2045 section 6.4 says multipart MIME parts may only use 7bit, 8bit or binary CTE
1896
            if ($ismultipart) {
1897
                if ($this->Encoding == '8bit') {
1898
                    $result .= $this->headerLine('Content-Transfer-Encoding', '8bit');
1899
                }
1900
                // The only remaining alternatives are quoted-printable and base64, which are both 7bit compatible
1901
            } else {
1902
                $result .= $this->headerLine('Content-Transfer-Encoding', $this->Encoding);
1903
            }
1904
        }
1905
1906
        if ($this->Mailer != 'mail') {
1907
            $result .= $this->LE;
1908
        }
1909
1910
        return $result;
1911
    }
1912
1913
    /**
1914
     * Returns the whole MIME message.
1915
     * Includes complete headers and body.
1916
     * Only valid post preSend().
1917
     * @see PHPMailer::preSend()
1918
     * @access public
1919
     * @return string
1920
     */
1921
    public function getSentMIMEMessage()
1922
    {
1923
        return $this->MIMEHeader . $this->mailHeader . self::CRLF . $this->MIMEBody;
1924
    }
1925
1926
    /**
1927
     * Assemble the message body.
1928
     * Returns an empty string on failure.
1929
     * @access public
1930
     * @throws phpmailerException
1931
     * @return string The assembled message body
1932
     */
1933
    public function createBody()
1934
    {
1935
        $body = '';
1936
        //Create unique IDs and preset boundaries
1937
        $this->uniqueid = md5(uniqid(time()));
1938
        $this->boundary[1] = 'b1_' . $this->uniqueid;
1939
        $this->boundary[2] = 'b2_' . $this->uniqueid;
1940
        $this->boundary[3] = 'b3_' . $this->uniqueid;
1941
1942
        if ($this->sign_key_file) {
1943
            $body .= $this->getMailMIME() . $this->LE;
1944
        }
1945
1946
        $this->setWordWrap();
1947
1948
        $bodyEncoding = $this->Encoding;
1949
        $bodyCharSet = $this->CharSet;
1950
        //Can we do a 7-bit downgrade?
1951
        if ($bodyEncoding == '8bit' and !$this->has8bitChars($this->Body)) {
1952
            $bodyEncoding = '7bit';
1953
            $bodyCharSet = 'us-ascii';
1954
        }
1955
        //If lines are too long, and we're not already using an encoding that will shorten them,
1956
        //change to quoted-printable transfer encoding
1957
        if ('base64' != $this->Encoding and self::hasLineLongerThanMax($this->Body)) {
1958
            $this->Encoding = 'quoted-printable';
1959
            $bodyEncoding = 'quoted-printable';
1960
        }
1961
1962
        $altBodyEncoding = $this->Encoding;
1963
        $altBodyCharSet = $this->CharSet;
1964
        //Can we do a 7-bit downgrade?
1965
        if ($altBodyEncoding == '8bit' and !$this->has8bitChars($this->AltBody)) {
1966
            $altBodyEncoding = '7bit';
1967
            $altBodyCharSet = 'us-ascii';
1968
        }
1969
        //If lines are too long, change to quoted-printable transfer encoding
1970
        if (self::hasLineLongerThanMax($this->AltBody)) {
1971
            $altBodyEncoding = 'quoted-printable';
1972
        }
1973
        //Use this as a preamble in all multipart message types
1974
        $mimepre = "This is a multi-part message in MIME format." . $this->LE . $this->LE;
1975
        switch ($this->message_type) {
1976 View Code Duplication
            case 'inline':
1977
                $body .= $mimepre;
1978
                $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, '', $bodyEncoding);
1979
                $body .= $this->encodeString($this->Body, $bodyEncoding);
1980
                $body .= $this->LE . $this->LE;
1981
                $body .= $this->attachAll('inline', $this->boundary[1]);
1982
                break;
1983 View Code Duplication
            case 'attach':
1984
                $body .= $mimepre;
1985
                $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, '', $bodyEncoding);
1986
                $body .= $this->encodeString($this->Body, $bodyEncoding);
1987
                $body .= $this->LE . $this->LE;
1988
                $body .= $this->attachAll('attachment', $this->boundary[1]);
1989
                break;
1990
            case 'inline_attach':
1991
                $body .= $mimepre;
1992
                $body .= $this->textLine('--' . $this->boundary[1]);
1993
                $body .= $this->headerLine('Content-Type', 'multipart/related;');
1994
                $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"');
1995
                $body .= $this->LE;
1996
                $body .= $this->getBoundary($this->boundary[2], $bodyCharSet, '', $bodyEncoding);
1997
                $body .= $this->encodeString($this->Body, $bodyEncoding);
1998
                $body .= $this->LE . $this->LE;
1999
                $body .= $this->attachAll('inline', $this->boundary[2]);
2000
                $body .= $this->LE;
2001
                $body .= $this->attachAll('attachment', $this->boundary[1]);
2002
                break;
2003
            case 'alt':
2004
                $body .= $mimepre;
2005
                $body .= $this->getBoundary($this->boundary[1], $altBodyCharSet, 'text/plain', $altBodyEncoding);
2006
                $body .= $this->encodeString($this->AltBody, $altBodyEncoding);
2007
                $body .= $this->LE . $this->LE;
2008
                $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, 'text/html', $bodyEncoding);
2009
                $body .= $this->encodeString($this->Body, $bodyEncoding);
2010
                $body .= $this->LE . $this->LE;
2011
                if (!empty($this->Ical)) {
2012
                    $body .= $this->getBoundary($this->boundary[1], '', 'text/calendar; method=REQUEST', '');
2013
                    $body .= $this->encodeString($this->Ical, $this->Encoding);
2014
                    $body .= $this->LE . $this->LE;
2015
                }
2016
                $body .= $this->endBoundary($this->boundary[1]);
2017
                break;
2018 View Code Duplication
            case 'alt_inline':
2019
                $body .= $mimepre;
2020
                $body .= $this->getBoundary($this->boundary[1], $altBodyCharSet, 'text/plain', $altBodyEncoding);
2021
                $body .= $this->encodeString($this->AltBody, $altBodyEncoding);
2022
                $body .= $this->LE . $this->LE;
2023
                $body .= $this->textLine('--' . $this->boundary[1]);
2024
                $body .= $this->headerLine('Content-Type', 'multipart/related;');
2025
                $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"');
2026
                $body .= $this->LE;
2027
                $body .= $this->getBoundary($this->boundary[2], $bodyCharSet, 'text/html', $bodyEncoding);
2028
                $body .= $this->encodeString($this->Body, $bodyEncoding);
2029
                $body .= $this->LE . $this->LE;
2030
                $body .= $this->attachAll('inline', $this->boundary[2]);
2031
                $body .= $this->LE;
2032
                $body .= $this->endBoundary($this->boundary[1]);
2033
                break;
2034 View Code Duplication
            case 'alt_attach':
2035
                $body .= $mimepre;
2036
                $body .= $this->textLine('--' . $this->boundary[1]);
2037
                $body .= $this->headerLine('Content-Type', 'multipart/alternative;');
2038
                $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"');
2039
                $body .= $this->LE;
2040
                $body .= $this->getBoundary($this->boundary[2], $altBodyCharSet, 'text/plain', $altBodyEncoding);
2041
                $body .= $this->encodeString($this->AltBody, $altBodyEncoding);
2042
                $body .= $this->LE . $this->LE;
2043
                $body .= $this->getBoundary($this->boundary[2], $bodyCharSet, 'text/html', $bodyEncoding);
2044
                $body .= $this->encodeString($this->Body, $bodyEncoding);
2045
                $body .= $this->LE . $this->LE;
2046
                $body .= $this->endBoundary($this->boundary[2]);
2047
                $body .= $this->LE;
2048
                $body .= $this->attachAll('attachment', $this->boundary[1]);
2049
                break;
2050
            case 'alt_inline_attach':
2051
                $body .= $mimepre;
2052
                $body .= $this->textLine('--' . $this->boundary[1]);
2053
                $body .= $this->headerLine('Content-Type', 'multipart/alternative;');
2054
                $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"');
2055
                $body .= $this->LE;
2056
                $body .= $this->getBoundary($this->boundary[2], $altBodyCharSet, 'text/plain', $altBodyEncoding);
2057
                $body .= $this->encodeString($this->AltBody, $altBodyEncoding);
2058
                $body .= $this->LE . $this->LE;
2059
                $body .= $this->textLine('--' . $this->boundary[2]);
2060
                $body .= $this->headerLine('Content-Type', 'multipart/related;');
2061
                $body .= $this->textLine("\tboundary=\"" . $this->boundary[3] . '"');
2062
                $body .= $this->LE;
2063
                $body .= $this->getBoundary($this->boundary[3], $bodyCharSet, 'text/html', $bodyEncoding);
2064
                $body .= $this->encodeString($this->Body, $bodyEncoding);
2065
                $body .= $this->LE . $this->LE;
2066
                $body .= $this->attachAll('inline', $this->boundary[3]);
2067
                $body .= $this->LE;
2068
                $body .= $this->endBoundary($this->boundary[2]);
2069
                $body .= $this->LE;
2070
                $body .= $this->attachAll('attachment', $this->boundary[1]);
2071
                break;
2072
            default:
2073
                // catch case 'plain' and case ''
2074
                $body .= $this->encodeString($this->Body, $bodyEncoding);
2075
                break;
2076
        }
2077
2078
        if ($this->isError()) {
2079
            $body = '';
2080
        } elseif ($this->sign_key_file) {
2081
            try {
2082
                if (!defined('PKCS7_TEXT')) {
2083
                    throw new phpmailerException($this->lang('extension_missing') . 'openssl');
2084
                }
2085
                // @TODO would be nice to use php://temp streams here, but need to wrap for PHP < 5.1
2086
                $file = tempnam(sys_get_temp_dir(), 'mail');
2087
                if (false === file_put_contents($file, $body)) {
2088
                    throw new phpmailerException($this->lang('signing') . ' Could not write temp file');
2089
                }
2090
                $signed = tempnam(sys_get_temp_dir(), 'signed');
2091
                //Workaround for PHP bug https://bugs.php.net/bug.php?id=69197
2092
                if (empty($this->sign_extracerts_file)) {
2093
                    $sign = @openssl_pkcs7_sign(
2094
                        $file,
2095
                        $signed,
2096
                        'file://' . realpath($this->sign_cert_file),
2097
                        array('file://' . realpath($this->sign_key_file), $this->sign_key_pass),
2098
                        null
2099
                    );
2100
                } else {
2101
                    $sign = @openssl_pkcs7_sign(
2102
                        $file,
2103
                        $signed,
2104
                        'file://' . realpath($this->sign_cert_file),
2105
                        array('file://' . realpath($this->sign_key_file), $this->sign_key_pass),
2106
                        null,
2107
                        PKCS7_DETACHED,
2108
                        $this->sign_extracerts_file
2109
                    );
2110
                }
2111
                if ($sign) {
2112
                    @unlink($file);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
2113
                    $body = file_get_contents($signed);
2114
                    @unlink($signed);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
2115
                    //The message returned by openssl contains both headers and body, so need to split them up
2116
                    $parts = explode("\n\n", $body, 2);
2117
                    $this->MIMEHeader .= $parts[0] . $this->LE . $this->LE;
2118
                    $body = $parts[1];
2119
                } else {
2120
                    @unlink($file);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
2121
                    @unlink($signed);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
2122
                    throw new phpmailerException($this->lang('signing') . openssl_error_string());
2123
                }
2124
            } catch (phpmailerException $exc) {
2125
                $body = '';
2126
                if ($this->exceptions) {
2127
                    throw $exc;
2128
                }
2129
            }
2130
        }
2131
        return $body;
2132
    }
2133
2134
    /**
2135
     * Return the start of a message boundary.
2136
     * @access protected
2137
     * @param string $boundary
2138
     * @param string $charSet
2139
     * @param string $contentType
2140
     * @param string $encoding
2141
     * @return string
2142
     */
2143
    protected function getBoundary($boundary, $charSet, $contentType, $encoding)
2144
    {
2145
        $result = '';
2146
        if ($charSet == '') {
2147
            $charSet = $this->CharSet;
2148
        }
2149
        if ($contentType == '') {
2150
            $contentType = $this->ContentType;
2151
        }
2152
        if ($encoding == '') {
2153
            $encoding = $this->Encoding;
2154
        }
2155
        $result .= $this->textLine('--' . $boundary);
2156
        $result .= sprintf('Content-Type: %s; charset=%s', $contentType, $charSet);
2157
        $result .= $this->LE;
2158
        // RFC1341 part 5 says 7bit is assumed if not specified
2159
        if ($encoding != '7bit') {
2160
            $result .= $this->headerLine('Content-Transfer-Encoding', $encoding);
2161
        }
2162
        $result .= $this->LE;
2163
2164
        return $result;
2165
    }
2166
2167
    /**
2168
     * Return the end of a message boundary.
2169
     * @access protected
2170
     * @param string $boundary
2171
     * @return string
2172
     */
2173
    protected function endBoundary($boundary)
2174
    {
2175
        return $this->LE . '--' . $boundary . '--' . $this->LE;
2176
    }
2177
2178
    /**
2179
     * Set the message type.
2180
     * PHPMailer only supports some preset message types,
2181
     * not arbitrary MIME structures.
2182
     * @access protected
2183
     * @return void
2184
     */
2185
    protected function setMessageType()
2186
    {
2187
        $type = array();
2188
        if ($this->alternativeExists()) {
2189
            $type[] = 'alt';
2190
        }
2191
        if ($this->inlineImageExists()) {
2192
            $type[] = 'inline';
2193
        }
2194
        if ($this->attachmentExists()) {
2195
            $type[] = 'attach';
2196
        }
2197
        $this->message_type = implode('_', $type);
2198
        if ($this->message_type == '') {
2199
            $this->message_type = 'plain';
2200
        }
2201
    }
2202
2203
    /**
2204
     * Format a header line.
2205
     * @access public
2206
     * @param string $name
2207
     * @param string $value
2208
     * @return string
2209
     */
2210
    public function headerLine($name, $value)
2211
    {
2212
        return $name . ': ' . $value . $this->LE;
2213
    }
2214
2215
    /**
2216
     * Return a formatted mail line.
2217
     * @access public
2218
     * @param string $value
2219
     * @return string
2220
     */
2221
    public function textLine($value)
2222
    {
2223
        return $value . $this->LE;
2224
    }
2225
2226
    /**
2227
     * Add an attachment from a path on the filesystem.
2228
     * Returns false if the file could not be found or read.
2229
     * @param string $path Path to the attachment.
2230
     * @param string $name Overrides the attachment name.
2231
     * @param string $encoding File encoding (see $Encoding).
2232
     * @param string $type File extension (MIME) type.
2233
     * @param string $disposition Disposition to use
2234
     * @throws phpmailerException
2235
     * @return boolean
2236
     */
2237
    public function addAttachment($path, $name = '', $encoding = 'base64', $type = '', $disposition = 'attachment')
2238
    {
2239
        try {
2240
            if (!@is_file($path)) {
2241
                throw new phpmailerException($this->lang('file_access') . $path, self::STOP_CONTINUE);
2242
            }
2243
2244
            // If a MIME type is not specified, try to work it out from the file name
2245
            if ($type == '') {
2246
                $type = self::filenameToType($path);
2247
            }
2248
2249
            $filename = basename($path);
2250
            if ($name == '') {
2251
                $name = $filename;
2252
            }
2253
2254
            $this->attachment[] = array(
2255
                0 => $path,
2256
                1 => $filename,
2257
                2 => $name,
2258
                3 => $encoding,
2259
                4 => $type,
2260
                5 => false, // isStringAttachment
2261
                6 => $disposition,
2262
                7 => 0
2263
            );
2264
2265
        } catch (phpmailerException $exc) {
2266
            $this->setError($exc->getMessage());
2267
            $this->edebug($exc->getMessage());
2268
            if ($this->exceptions) {
2269
                throw $exc;
2270
            }
2271
            return false;
2272
        }
2273
        return true;
2274
    }
2275
2276
    /**
2277
     * Return the array of attachments.
2278
     * @return array
2279
     */
2280
    public function getAttachments()
2281
    {
2282
        return $this->attachment;
2283
    }
2284
2285
    /**
2286
     * Attach all file, string, and binary attachments to the message.
2287
     * Returns an empty string on failure.
2288
     * @access protected
2289
     * @param string $disposition_type
2290
     * @param string $boundary
2291
     * @return string
2292
     */
2293
    protected function attachAll($disposition_type, $boundary)
2294
    {
2295
        // Return text of body
2296
        $mime = array();
2297
        $cidUniq = array();
2298
        $incl = array();
2299
2300
        // Add all attachments
2301
        foreach ($this->attachment as $attachment) {
2302
            // Check if it is a valid disposition_filter
2303
            if ($attachment[6] == $disposition_type) {
2304
                // Check for string attachment
2305
                $string = '';
2306
                $path = '';
2307
                $bString = $attachment[5];
2308
                if ($bString) {
2309
                    $string = $attachment[0];
2310
                } else {
2311
                    $path = $attachment[0];
2312
                }
2313
2314
                $inclhash = md5(serialize($attachment));
2315
                if (in_array($inclhash, $incl)) {
2316
                    continue;
2317
                }
2318
                $incl[] = $inclhash;
2319
                $name = $attachment[2];
2320
                $encoding = $attachment[3];
2321
                $type = $attachment[4];
2322
                $disposition = $attachment[6];
2323
                $cid = $attachment[7];
2324
                if ($disposition == 'inline' && isset($cidUniq[$cid])) {
2325
                    continue;
2326
                }
2327
                $cidUniq[$cid] = true;
2328
2329
                $mime[] = sprintf('--%s%s', $boundary, $this->LE);
2330
                $mime[] = sprintf(
2331
                    'Content-Type: %s; name="%s"%s',
2332
                    $type,
2333
                    $this->encodeHeader($this->secureHeader($name)),
2334
                    $this->LE
2335
                );
2336
                // RFC1341 part 5 says 7bit is assumed if not specified
2337
                if ($encoding != '7bit') {
2338
                    $mime[] = sprintf('Content-Transfer-Encoding: %s%s', $encoding, $this->LE);
2339
                }
2340
2341
                if ($disposition == 'inline') {
2342
                    $mime[] = sprintf('Content-ID: <%s>%s', $cid, $this->LE);
2343
                }
2344
2345
                // If a filename contains any of these chars, it should be quoted,
2346
                // but not otherwise: RFC2183 & RFC2045 5.1
2347
                // Fixes a warning in IETF's msglint MIME checker
2348
                // Allow for bypassing the Content-Disposition header totally
2349
                if (!(empty($disposition))) {
2350
                    $encoded_name = $this->encodeHeader($this->secureHeader($name));
2351
                    if (preg_match('/[ \(\)<>@,;:\\"\/\[\]\?=]/', $encoded_name)) {
2352
                        $mime[] = sprintf(
2353
                            'Content-Disposition: %s; filename="%s"%s',
2354
                            $disposition,
2355
                            $encoded_name,
2356
                            $this->LE . $this->LE
2357
                        );
2358
                    } else {
2359
                        $mime[] = sprintf(
2360
                            'Content-Disposition: %s; filename=%s%s',
2361
                            $disposition,
2362
                            $encoded_name,
2363
                            $this->LE . $this->LE
2364
                        );
2365
                    }
2366
                } else {
2367
                    $mime[] = $this->LE;
2368
                }
2369
2370
                // Encode as string attachment
2371
                if ($bString) {
2372
                    $mime[] = $this->encodeString($string, $encoding);
2373
                    if ($this->isError()) {
2374
                        return '';
2375
                    }
2376
                    $mime[] = $this->LE . $this->LE;
2377
                } else {
2378
                    $mime[] = $this->encodeFile($path, $encoding);
2379
                    if ($this->isError()) {
2380
                        return '';
2381
                    }
2382
                    $mime[] = $this->LE . $this->LE;
2383
                }
2384
            }
2385
        }
2386
2387
        $mime[] = sprintf('--%s--%s', $boundary, $this->LE);
2388
2389
        return implode('', $mime);
2390
    }
2391
2392
    /**
2393
     * Encode a file attachment in requested format.
2394
     * Returns an empty string on failure.
2395
     * @param string $path The full path to the file
2396
     * @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable'
2397
     * @throws phpmailerException
2398
     * @see EncodeFile(encodeFile
2399
     * @access protected
2400
     * @return string
2401
     */
2402
    protected function encodeFile($path, $encoding = 'base64')
2403
    {
2404
        try {
2405
            if (!is_readable($path)) {
2406
                throw new phpmailerException($this->lang('file_open') . $path, self::STOP_CONTINUE);
2407
            }
2408
            $magic_quotes = get_magic_quotes_runtime();
2409
            if ($magic_quotes) {
2410
                if (version_compare(PHP_VERSION, '5.3.0', '<')) {
2411
                    set_magic_quotes_runtime(false);
2412
                } else {
2413
                    //Doesn't exist in PHP 5.4, but we don't need to check because
2414
                    //get_magic_quotes_runtime always returns false in 5.4+
2415
                    //so it will never get here
2416
                    ini_set('magic_quotes_runtime', false);
2417
                }
2418
            }
2419
            $file_buffer = file_get_contents($path);
2420
            $file_buffer = $this->encodeString($file_buffer, $encoding);
2421
            if ($magic_quotes) {
2422
                if (version_compare(PHP_VERSION, '5.3.0', '<')) {
2423
                    set_magic_quotes_runtime($magic_quotes);
2424
                } else {
2425
                    ini_set('magic_quotes_runtime', $magic_quotes);
2426
                }
2427
            }
2428
            return $file_buffer;
2429
        } catch (Exception $exc) {
2430
            $this->setError($exc->getMessage());
2431
            return '';
2432
        }
2433
    }
2434
2435
    /**
2436
     * Encode a string in requested format.
2437
     * Returns an empty string on failure.
2438
     * @param string $str The text to encode
2439
     * @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable'
2440
     * @access public
2441
     * @return string
2442
     */
2443
    public function encodeString($str, $encoding = 'base64')
2444
    {
2445
        $encoded = '';
2446
        switch (strtolower($encoding)) {
2447
            case 'base64':
2448
                $encoded = chunk_split(base64_encode($str), 76, $this->LE);
2449
                break;
2450
            case '7bit':
2451
            case '8bit':
2452
                $encoded = $this->fixEOL($str);
2453
                // Make sure it ends with a line break
2454
                if (substr($encoded, -(strlen($this->LE))) != $this->LE) {
2455
                    $encoded .= $this->LE;
2456
                }
2457
                break;
2458
            case 'binary':
2459
                $encoded = $str;
2460
                break;
2461
            case 'quoted-printable':
2462
                $encoded = $this->encodeQP($str);
2463
                break;
2464
            default:
2465
                $this->setError($this->lang('encoding') . $encoding);
2466
                break;
2467
        }
2468
        return $encoded;
2469
    }
2470
2471
    /**
2472
     * Encode a header string optimally.
2473
     * Picks shortest of Q, B, quoted-printable or none.
2474
     * @access public
2475
     * @param string $str
2476
     * @param string $position
2477
     * @return string
2478
     */
2479
    public function encodeHeader($str, $position = 'text')
2480
    {
2481
        $matchcount = 0;
2482
        switch (strtolower($position)) {
2483
            case 'phrase':
2484
                if (!preg_match('/[\200-\377]/', $str)) {
2485
                    // Can't use addslashes as we don't know the value of magic_quotes_sybase
2486
                    $encoded = addcslashes($str, "\0..\37\177\\\"");
2487
                    if (($str == $encoded) && !preg_match('/[^A-Za-z0-9!#$%&\'*+\/=?^_`{|}~ -]/', $str)) {
2488
                        return ($encoded);
2489
                    } else {
2490
                        return ("\"$encoded\"");
2491
                    }
2492
                }
2493
                $matchcount = preg_match_all('/[^\040\041\043-\133\135-\176]/', $str, $matches);
2494
                break;
2495
            /** @noinspection PhpMissingBreakStatementInspection */
2496
            case 'comment':
2497
                $matchcount = preg_match_all('/[()"]/', $str, $matches);
2498
                // Intentional fall-through
2499
            case 'text':
2500
            default:
2501
                $matchcount += preg_match_all('/[\000-\010\013\014\016-\037\177-\377]/', $str, $matches);
2502
                break;
2503
        }
2504
2505
        //There are no chars that need encoding
2506
        if ($matchcount == 0) {
2507
            return ($str);
2508
        }
2509
2510
        $maxlen = 75 - 7 - strlen($this->CharSet);
2511
        // Try to select the encoding which should produce the shortest output
2512
        if ($matchcount > strlen($str) / 3) {
2513
            // More than a third of the content will need encoding, so B encoding will be most efficient
2514
            $encoding = 'B';
2515
            if (function_exists('mb_strlen') && $this->hasMultiBytes($str)) {
2516
                // Use a custom function which correctly encodes and wraps long
2517
                // multibyte strings without breaking lines within a character
2518
                $encoded = $this->base64EncodeWrapMB($str, "\n");
2519
            } else {
2520
                $encoded = base64_encode($str);
2521
                $maxlen -= $maxlen % 4;
2522
                $encoded = trim(chunk_split($encoded, $maxlen, "\n"));
2523
            }
2524
        } else {
2525
            $encoding = 'Q';
2526
            $encoded = $this->encodeQ($str, $position);
2527
            $encoded = $this->wrapText($encoded, $maxlen, true);
2528
            $encoded = str_replace('=' . self::CRLF, "\n", trim($encoded));
2529
        }
2530
2531
        $encoded = preg_replace('/^(.*)$/m', ' =?' . $this->CharSet . "?$encoding?\\1?=", $encoded);
2532
        $encoded = trim(str_replace("\n", $this->LE, $encoded));
2533
2534
        return $encoded;
2535
    }
2536
2537
    /**
2538
     * Check if a string contains multi-byte characters.
2539
     * @access public
2540
     * @param string $str multi-byte text to wrap encode
2541
     * @return boolean
2542
     */
2543
    public function hasMultiBytes($str)
2544
    {
2545
        if (function_exists('mb_strlen')) {
2546
            return (strlen($str) > mb_strlen($str, $this->CharSet));
2547
        } else { // Assume no multibytes (we can't handle without mbstring functions anyway)
2548
            return false;
2549
        }
2550
    }
2551
2552
    /**
2553
     * Does a string contain any 8-bit chars (in any charset)?
2554
     * @param string $text
2555
     * @return boolean
2556
     */
2557
    public function has8bitChars($text)
2558
    {
2559
        return (boolean)preg_match('/[\x80-\xFF]/', $text);
2560
    }
2561
2562
    /**
2563
     * Encode and wrap long multibyte strings for mail headers
2564
     * without breaking lines within a character.
2565
     * Adapted from a function by paravoid
2566
     * @link http://www.php.net/manual/en/function.mb-encode-mimeheader.php#60283
2567
     * @access public
2568
     * @param string $str multi-byte text to wrap encode
2569
     * @param string $linebreak string to use as linefeed/end-of-line
2570
     * @return string
2571
     */
2572
    public function base64EncodeWrapMB($str, $linebreak = null)
2573
    {
2574
        $start = '=?' . $this->CharSet . '?B?';
2575
        $end = '?=';
2576
        $encoded = '';
2577
        if ($linebreak === null) {
2578
            $linebreak = $this->LE;
2579
        }
2580
2581
        $mb_length = mb_strlen($str, $this->CharSet);
2582
        // Each line must have length <= 75, including $start and $end
2583
        $length = 75 - strlen($start) - strlen($end);
2584
        // Average multi-byte ratio
2585
        $ratio = $mb_length / strlen($str);
2586
        // Base64 has a 4:3 ratio
2587
        $avgLength = floor($length * $ratio * .75);
2588
2589
        for ($i = 0; $i < $mb_length; $i += $offset) {
2590
            $lookBack = 0;
2591
            do {
2592
                $offset = $avgLength - $lookBack;
2593
                $chunk = mb_substr($str, $i, $offset, $this->CharSet);
2594
                $chunk = base64_encode($chunk);
2595
                $lookBack++;
2596
            } while (strlen($chunk) > $length);
2597
            $encoded .= $chunk . $linebreak;
2598
        }
2599
2600
        // Chomp the last linefeed
2601
        $encoded = substr($encoded, 0, -strlen($linebreak));
2602
        return $encoded;
2603
    }
2604
2605
    /**
2606
     * Encode a string in quoted-printable format.
2607
     * According to RFC2045 section 6.7.
2608
     * @access public
2609
     * @param string $string The text to encode
2610
     * @param integer $line_max Number of chars allowed on a line before wrapping
2611
     * @return string
2612
     * @link http://www.php.net/manual/en/function.quoted-printable-decode.php#89417 Adapted from this comment
2613
     */
2614
    public function encodeQP($string, $line_max = 76)
2615
    {
2616
        // Use native function if it's available (>= PHP5.3)
2617
        if (function_exists('quoted_printable_encode')) {
2618
            return $this->fixEOL(quoted_printable_encode($string));
2619
        }
2620
        // Fall back to a pure PHP implementation
2621
        $string = str_replace(
2622
            array('%20', '%0D%0A.', '%0D%0A', '%'),
2623
            array(' ', "\r\n=2E", "\r\n", '='),
2624
            rawurlencode($string)
2625
        );
2626
        $string = preg_replace('/[^\r\n]{' . ($line_max - 3) . '}[^=\r\n]{2}/', "$0=\r\n", $string);
2627
        return $this->fixEOL($string);
2628
    }
2629
2630
    /**
2631
     * Backward compatibility wrapper for an old QP encoding function that was removed.
2632
     * @see PHPMailer::encodeQP()
2633
     * @access public
2634
     * @param string $string
2635
     * @param integer $line_max
2636
     * @param boolean $space_conv
2637
     * @return string
2638
     * @deprecated Use encodeQP instead.
2639
     */
2640
    public function encodeQPphp(
2641
        $string,
2642
        $line_max = 76,
2643
        /** @noinspection PhpUnusedParameterInspection */ $space_conv = false
0 ignored issues
show
Unused Code introduced by
The parameter $space_conv is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
2644
    ) {
2645
        return $this->encodeQP($string, $line_max);
2646
    }
2647
2648
    /**
2649
     * Encode a string using Q encoding.
2650
     * @link http://tools.ietf.org/html/rfc2047
2651
     * @param string $str the text to encode
2652
     * @param string $position Where the text is going to be used, see the RFC for what that means
2653
     * @access public
2654
     * @return string
2655
     */
2656
    public function encodeQ($str, $position = 'text')
2657
    {
2658
        // There should not be any EOL in the string
2659
        $pattern = '';
2660
        $encoded = str_replace(array("\r", "\n"), '', $str);
2661
        switch (strtolower($position)) {
2662
            case 'phrase':
2663
                // RFC 2047 section 5.3
2664
                $pattern = '^A-Za-z0-9!*+\/ -';
2665
                break;
2666
            /** @noinspection PhpMissingBreakStatementInspection */
2667
            case 'comment':
2668
                // RFC 2047 section 5.2
2669
                $pattern = '\(\)"';
2670
                // intentional fall-through
2671
                // for this reason we build the $pattern without including delimiters and []
2672
            case 'text':
2673
            default:
2674
                // RFC 2047 section 5.1
2675
                // Replace every high ascii, control, =, ? and _ characters
2676
                $pattern = '\000-\011\013\014\016-\037\075\077\137\177-\377' . $pattern;
2677
                break;
2678
        }
2679
        $matches = array();
2680
        if (preg_match_all("/[{$pattern}]/", $encoded, $matches)) {
2681
            // If the string contains an '=', make sure it's the first thing we replace
2682
            // so as to avoid double-encoding
2683
            $eqkey = array_search('=', $matches[0]);
2684
            if (false !== $eqkey) {
2685
                unset($matches[0][$eqkey]);
2686
                array_unshift($matches[0], '=');
2687
            }
2688
            foreach (array_unique($matches[0]) as $char) {
2689
                $encoded = str_replace($char, '=' . sprintf('%02X', ord($char)), $encoded);
2690
            }
2691
        }
2692
        // Replace every spaces to _ (more readable than =20)
2693
        return str_replace(' ', '_', $encoded);
2694
    }
2695
2696
2697
    /**
2698
     * Add a string or binary attachment (non-filesystem).
2699
     * This method can be used to attach ascii or binary data,
2700
     * such as a BLOB record from a database.
2701
     * @param string $string String attachment data.
2702
     * @param string $filename Name of the attachment.
2703
     * @param string $encoding File encoding (see $Encoding).
2704
     * @param string $type File extension (MIME) type.
2705
     * @param string $disposition Disposition to use
2706
     * @return void
2707
     */
2708 View Code Duplication
    public function addStringAttachment(
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2709
        $string,
2710
        $filename,
2711
        $encoding = 'base64',
2712
        $type = '',
2713
        $disposition = 'attachment'
2714
    ) {
2715
        // If a MIME type is not specified, try to work it out from the file name
2716
        if ($type == '') {
2717
            $type = self::filenameToType($filename);
2718
        }
2719
        // Append to $attachment array
2720
        $this->attachment[] = array(
2721
            0 => $string,
2722
            1 => $filename,
2723
            2 => basename($filename),
2724
            3 => $encoding,
2725
            4 => $type,
2726
            5 => true, // isStringAttachment
2727
            6 => $disposition,
2728
            7 => 0
2729
        );
2730
    }
2731
2732
    /**
2733
     * Add an embedded (inline) attachment from a file.
2734
     * This can include images, sounds, and just about any other document type.
2735
     * These differ from 'regular' attachments in that they are intended to be
2736
     * displayed inline with the message, not just attached for download.
2737
     * This is used in HTML messages that embed the images
2738
     * the HTML refers to using the $cid value.
2739
     * @param string $path Path to the attachment.
2740
     * @param string $cid Content ID of the attachment; Use this to reference
2741
     *        the content when using an embedded image in HTML.
2742
     * @param string $name Overrides the attachment name.
2743
     * @param string $encoding File encoding (see $Encoding).
2744
     * @param string $type File MIME type.
2745
     * @param string $disposition Disposition to use
2746
     * @return boolean True on successfully adding an attachment
2747
     */
2748
    public function addEmbeddedImage($path, $cid, $name = '', $encoding = 'base64', $type = '', $disposition = 'inline')
2749
    {
2750
        if (!@is_file($path)) {
2751
            $this->setError($this->lang('file_access') . $path);
2752
            return false;
2753
        }
2754
2755
        // If a MIME type is not specified, try to work it out from the file name
2756
        if ($type == '') {
2757
            $type = self::filenameToType($path);
2758
        }
2759
2760
        $filename = basename($path);
2761
        if ($name == '') {
2762
            $name = $filename;
2763
        }
2764
2765
        // Append to $attachment array
2766
        $this->attachment[] = array(
2767
            0 => $path,
2768
            1 => $filename,
2769
            2 => $name,
2770
            3 => $encoding,
2771
            4 => $type,
2772
            5 => false, // isStringAttachment
2773
            6 => $disposition,
2774
            7 => $cid
2775
        );
2776
        return true;
2777
    }
2778
2779
    /**
2780
     * Add an embedded stringified attachment.
2781
     * This can include images, sounds, and just about any other document type.
2782
     * Be sure to set the $type to an image type for images:
2783
     * JPEG images use 'image/jpeg', GIF uses 'image/gif', PNG uses 'image/png'.
2784
     * @param string $string The attachment binary data.
2785
     * @param string $cid Content ID of the attachment; Use this to reference
2786
     *        the content when using an embedded image in HTML.
2787
     * @param string $name
2788
     * @param string $encoding File encoding (see $Encoding).
2789
     * @param string $type MIME type.
2790
     * @param string $disposition Disposition to use
2791
     * @return boolean True on successfully adding an attachment
2792
     */
2793 View Code Duplication
    public function addStringEmbeddedImage(
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2794
        $string,
2795
        $cid,
2796
        $name = '',
2797
        $encoding = 'base64',
2798
        $type = '',
2799
        $disposition = 'inline'
2800
    ) {
2801
        // If a MIME type is not specified, try to work it out from the name
2802
        if ($type == '') {
2803
            $type = self::filenameToType($name);
2804
        }
2805
2806
        // Append to $attachment array
2807
        $this->attachment[] = array(
2808
            0 => $string,
2809
            1 => $name,
2810
            2 => $name,
2811
            3 => $encoding,
2812
            4 => $type,
2813
            5 => true, // isStringAttachment
2814
            6 => $disposition,
2815
            7 => $cid
2816
        );
2817
        return true;
2818
    }
2819
2820
    /**
2821
     * Check if an inline attachment is present.
2822
     * @access public
2823
     * @return boolean
2824
     */
2825
    public function inlineImageExists()
2826
    {
2827
        foreach ($this->attachment as $attachment) {
2828
            if ($attachment[6] == 'inline') {
2829
                return true;
2830
            }
2831
        }
2832
        return false;
2833
    }
2834
2835
    /**
2836
     * Check if an attachment (non-inline) is present.
2837
     * @return boolean
2838
     */
2839
    public function attachmentExists()
2840
    {
2841
        foreach ($this->attachment as $attachment) {
2842
            if ($attachment[6] == 'attachment') {
2843
                return true;
2844
            }
2845
        }
2846
        return false;
2847
    }
2848
2849
    /**
2850
     * Check if this message has an alternative body set.
2851
     * @return boolean
2852
     */
2853
    public function alternativeExists()
2854
    {
2855
        return !empty($this->AltBody);
2856
    }
2857
2858
    /**
2859
     * Clear all To recipients.
2860
     * @return void
2861
     */
2862
    public function clearAddresses()
2863
    {
2864
        foreach ($this->to as $to) {
2865
            unset($this->all_recipients[strtolower($to[0])]);
2866
        }
2867
        $this->to = array();
2868
    }
2869
2870
    /**
2871
     * Clear all CC recipients.
2872
     * @return void
2873
     */
2874 View Code Duplication
    public function clearCCs()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2875
    {
2876
        foreach ($this->cc as $cc) {
2877
            unset($this->all_recipients[strtolower($cc[0])]);
2878
        }
2879
        $this->cc = array();
2880
    }
2881
2882
    /**
2883
     * Clear all BCC recipients.
2884
     * @return void
2885
     */
2886 View Code Duplication
    public function clearBCCs()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2887
    {
2888
        foreach ($this->bcc as $bcc) {
2889
            unset($this->all_recipients[strtolower($bcc[0])]);
2890
        }
2891
        $this->bcc = array();
2892
    }
2893
2894
    /**
2895
     * Clear all ReplyTo recipients.
2896
     * @return void
2897
     */
2898
    public function clearReplyTos()
2899
    {
2900
        $this->ReplyTo = array();
2901
    }
2902
2903
    /**
2904
     * Clear all recipient types.
2905
     * @return void
2906
     */
2907
    public function clearAllRecipients()
2908
    {
2909
        $this->to = array();
2910
        $this->cc = array();
2911
        $this->bcc = array();
2912
        $this->all_recipients = array();
2913
    }
2914
2915
    /**
2916
     * Clear all filesystem, string, and binary attachments.
2917
     * @return void
2918
     */
2919
    public function clearAttachments()
2920
    {
2921
        $this->attachment = array();
2922
    }
2923
2924
    /**
2925
     * Clear all custom headers.
2926
     * @return void
2927
     */
2928
    public function clearCustomHeaders()
2929
    {
2930
        $this->CustomHeader = array();
2931
    }
2932
2933
    /**
2934
     * Add an error message to the error container.
2935
     * @access protected
2936
     * @param string $msg
2937
     * @return void
2938
     */
2939
    protected function setError($msg)
2940
    {
2941
        $this->error_count++;
2942
        if ($this->Mailer == 'smtp' and !is_null($this->smtp)) {
2943
            $lasterror = $this->smtp->getError();
2944
            if (!empty($lasterror['error'])) {
2945
                $msg .= $this->lang('smtp_error') . $lasterror['error'];
2946
                if (!empty($lasterror['detail'])) {
2947
                    $msg .= ' Detail: '. $lasterror['detail'];
2948
                }
2949
                if (!empty($lasterror['smtp_code'])) {
2950
                    $msg .= ' SMTP code: ' . $lasterror['smtp_code'];
2951
                }
2952
                if (!empty($lasterror['smtp_code_ex'])) {
2953
                    $msg .= ' Additional SMTP info: ' . $lasterror['smtp_code_ex'];
2954
                }
2955
            }
2956
        }
2957
        $this->ErrorInfo = $msg;
2958
    }
2959
2960
    /**
2961
     * Return an RFC 822 formatted date.
2962
     * @access public
2963
     * @return string
2964
     * @static
2965
     */
2966
    public static function rfcDate()
2967
    {
2968
        // Set the time zone to whatever the default is to avoid 500 errors
2969
        // Will default to UTC if it's not set properly in php.ini
2970
        date_default_timezone_set(@date_default_timezone_get());
2971
        return date('D, j M Y H:i:s O');
2972
    }
2973
2974
    /**
2975
     * Get the server hostname.
2976
     * Returns 'localhost.localdomain' if unknown.
2977
     * @access protected
2978
     * @return string
2979
     */
2980
    protected function serverHostname()
2981
    {
2982
        $result = 'localhost.localdomain';
2983
        if (!empty($this->Hostname)) {
2984
            $result = $this->Hostname;
2985
        } elseif (isset($_SERVER) and array_key_exists('SERVER_NAME', $_SERVER) and !empty($_SERVER['SERVER_NAME'])) {
2986
            $result = $_SERVER['SERVER_NAME'];
2987
        } elseif (function_exists('gethostname') && gethostname() !== false) {
2988
            $result = gethostname();
2989
        } elseif (php_uname('n') !== false) {
2990
            $result = php_uname('n');
2991
        }
2992
        return $result;
2993
    }
2994
2995
    /**
2996
     * Get an error message in the current language.
2997
     * @access protected
2998
     * @param string $key
2999
     * @return string
3000
     */
3001
    protected function lang($key)
3002
    {
3003
        if (count($this->language) < 1) {
3004
            $this->setLanguage('en'); // set the default language
3005
        }
3006
3007
        if (array_key_exists($key, $this->language)) {
3008
            if ($key == 'smtp_connect_failed') {
3009
                //Include a link to troubleshooting docs on SMTP connection failure
3010
                //this is by far the biggest cause of support questions
3011
                //but it's usually not PHPMailer's fault.
3012
                return $this->language[$key] . ' https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting';
3013
            }
3014
            return $this->language[$key];
3015
        } else {
3016
            //Return the key as a fallback
3017
            return $key;
3018
        }
3019
    }
3020
3021
    /**
3022
     * Check if an error occurred.
3023
     * @access public
3024
     * @return boolean True if an error did occur.
3025
     */
3026
    public function isError()
3027
    {
3028
        return ($this->error_count > 0);
3029
    }
3030
3031
    /**
3032
     * Ensure consistent line endings in a string.
3033
     * Changes every end of line from CRLF, CR or LF to $this->LE.
3034
     * @access public
3035
     * @param string $str String to fixEOL
3036
     * @return string
3037
     */
3038
    public function fixEOL($str)
3039
    {
3040
        // Normalise to \n
3041
        $nstr = str_replace(array("\r\n", "\r"), "\n", $str);
3042
        // Now convert LE as needed
3043
        if ($this->LE !== "\n") {
3044
            $nstr = str_replace("\n", $this->LE, $nstr);
3045
        }
3046
        return $nstr;
3047
    }
3048
3049
    /**
3050
     * Add a custom header.
3051
     * $name value can be overloaded to contain
3052
     * both header name and value (name:value)
3053
     * @access public
3054
     * @param string $name Custom header name
3055
     * @param string $value Header value
3056
     * @return void
3057
     */
3058
    public function addCustomHeader($name, $value = null)
3059
    {
3060
        if ($value === null) {
3061
            // Value passed in as name:value
3062
            $this->CustomHeader[] = explode(':', $name, 2);
3063
        } else {
3064
            $this->CustomHeader[] = array($name, $value);
3065
        }
3066
    }
3067
3068
    /**
3069
     * Returns all custom headers
3070
     *
3071
     * @return array
3072
     */
3073
    public function getCustomHeaders()
3074
    {
3075
        return $this->CustomHeader;
3076
    }
3077
3078
    /**
3079
     * Create a message from an HTML string.
3080
     * Automatically makes modifications for inline images and backgrounds
3081
     * and creates a plain-text version by converting the HTML.
3082
     * Overwrites any existing values in $this->Body and $this->AltBody
3083
     * @access public
3084
     * @param string $message HTML message string
3085
     * @param string $basedir baseline directory for path
3086
     * @param boolean|callable $advanced Whether to use the internal HTML to text converter
3087
     *    or your own custom converter @see html2text()
3088
     * @return string $message
3089
     */
3090
    public function msgHTML($message, $basedir = '', $advanced = false)
3091
    {
3092
        preg_match_all('/(src|background)=["\'](.*)["\']/Ui', $message, $images);
3093
        if (isset($images[2])) {
3094
            foreach ($images[2] as $imgindex => $url) {
3095
                // Convert data URIs into embedded images
3096
                if (preg_match('#^data:(image[^;,]*)(;base64)?,#', $url, $match)) {
3097
                    $data = substr($url, strpos($url, ','));
3098
                    if ($match[2]) {
3099
                        $data = base64_decode($data);
3100
                    } else {
3101
                        $data = rawurldecode($data);
3102
                    }
3103
                    $cid = md5($url) . '@phpmailer.0'; // RFC2392 S 2
3104
                    if ($this->addStringEmbeddedImage($data, $cid, '', 'base64', $match[1])) {
3105
                        $message = str_replace(
3106
                            $images[0][$imgindex],
3107
                            $images[1][$imgindex] . '="cid:' . $cid . '"',
3108
                            $message
3109
                        );
3110
                    }
3111
                } elseif (!preg_match('#^[A-z]+://#', $url)) {
3112
                    // Do not change urls for absolute images (thanks to corvuscorax)
3113
                    $filename = basename($url);
3114
                    $directory = dirname($url);
3115
                    if ($directory == '.') {
3116
                        $directory = '';
3117
                    }
3118
                    $cid = md5($url) . '@phpmailer.0'; // RFC2392 S 2
3119
                    if (strlen($basedir) > 1 && substr($basedir, -1) != '/') {
3120
                        $basedir .= '/';
3121
                    }
3122
                    if (strlen($directory) > 1 && substr($directory, -1) != '/') {
3123
                        $directory .= '/';
3124
                    }
3125
                    if ($this->addEmbeddedImage(
3126
                        $basedir . $directory . $filename,
3127
                        $cid,
3128
                        $filename,
3129
                        'base64',
3130
                        self::_mime_types((string)self::mb_pathinfo($filename, PATHINFO_EXTENSION))
3131
                    )
3132
                    ) {
3133
                        $message = preg_replace(
3134
                            '/' . $images[1][$imgindex] . '=["\']' . preg_quote($url, '/') . '["\']/Ui',
3135
                            $images[1][$imgindex] . '="cid:' . $cid . '"',
3136
                            $message
3137
                        );
3138
                    }
3139
                }
3140
            }
3141
        }
3142
        $this->isHTML(true);
3143
        // Convert all message body line breaks to CRLF, makes quoted-printable encoding work much better
3144
        $this->Body = $this->normalizeBreaks($message);
3145
        $this->AltBody = $this->normalizeBreaks($this->html2text($message, $advanced));
3146
        if (empty($this->AltBody)) {
3147
            $this->AltBody = 'To view this email message, open it in a program that understands HTML!' .
3148
                self::CRLF . self::CRLF;
3149
        }
3150
        return $this->Body;
3151
    }
3152
3153
    /**
3154
     * Convert an HTML string into plain text.
3155
     * This is used by msgHTML().
3156
     * Note - older versions of this function used a bundled advanced converter
3157
     * which was been removed for license reasons in #232
3158
     * Example usage:
3159
     * <code>
3160
     * // Use default conversion
3161
     * $plain = $mail->html2text($html);
3162
     * // Use your own custom converter
3163
     * $plain = $mail->html2text($html, function($html) {
3164
     *     $converter = new MyHtml2text($html);
3165
     *     return $converter->get_text();
3166
     * });
3167
     * </code>
3168
     * @param string $html The HTML text to convert
3169
     * @param boolean|callable $advanced Any boolean value to use the internal converter,
3170
     *   or provide your own callable for custom conversion.
3171
     * @return string
3172
     */
3173
    public function html2text($html, $advanced = false)
3174
    {
3175
        if (is_callable($advanced)) {
3176
            return call_user_func($advanced, $html);
3177
        }
3178
        return html_entity_decode(
3179
            trim(strip_tags(preg_replace('/<(head|title|style|script)[^>]*>.*?<\/\\1>/si', '', $html))),
3180
            ENT_QUOTES,
3181
            $this->CharSet
3182
        );
3183
    }
3184
3185
    /**
3186
     * Get the MIME type for a file extension.
3187
     * @param string $ext File extension
3188
     * @access public
3189
     * @return string MIME type of file.
3190
     * @static
3191
     */
3192
    public static function _mime_types($ext = '')
3193
    {
3194
        $mimes = array(
3195
            'xl'    => 'application/excel',
3196
            'js'    => 'application/javascript',
3197
            'hqx'   => 'application/mac-binhex40',
3198
            'cpt'   => 'application/mac-compactpro',
3199
            'bin'   => 'application/macbinary',
3200
            'doc'   => 'application/msword',
3201
            'word'  => 'application/msword',
3202
            'xlsx'  => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
3203
            'xltx'  => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
3204
            'potx'  => 'application/vnd.openxmlformats-officedocument.presentationml.template',
3205
            'ppsx'  => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
3206
            'pptx'  => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
3207
            'sldx'  => 'application/vnd.openxmlformats-officedocument.presentationml.slide',
3208
            'docx'  => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
3209
            'dotx'  => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
3210
            'xlam'  => 'application/vnd.ms-excel.addin.macroEnabled.12',
3211
            'xlsb'  => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
3212
            'class' => 'application/octet-stream',
3213
            'dll'   => 'application/octet-stream',
3214
            'dms'   => 'application/octet-stream',
3215
            'exe'   => 'application/octet-stream',
3216
            'lha'   => 'application/octet-stream',
3217
            'lzh'   => 'application/octet-stream',
3218
            'psd'   => 'application/octet-stream',
3219
            'sea'   => 'application/octet-stream',
3220
            'so'    => 'application/octet-stream',
3221
            'oda'   => 'application/oda',
3222
            'pdf'   => 'application/pdf',
3223
            'ai'    => 'application/postscript',
3224
            'eps'   => 'application/postscript',
3225
            'ps'    => 'application/postscript',
3226
            'smi'   => 'application/smil',
3227
            'smil'  => 'application/smil',
3228
            'mif'   => 'application/vnd.mif',
3229
            'xls'   => 'application/vnd.ms-excel',
3230
            'ppt'   => 'application/vnd.ms-powerpoint',
3231
            'wbxml' => 'application/vnd.wap.wbxml',
3232
            'wmlc'  => 'application/vnd.wap.wmlc',
3233
            'dcr'   => 'application/x-director',
3234
            'dir'   => 'application/x-director',
3235
            'dxr'   => 'application/x-director',
3236
            'dvi'   => 'application/x-dvi',
3237
            'gtar'  => 'application/x-gtar',
3238
            'php3'  => 'application/x-httpd-php',
3239
            'php4'  => 'application/x-httpd-php',
3240
            'php'   => 'application/x-httpd-php',
3241
            'phtml' => 'application/x-httpd-php',
3242
            'phps'  => 'application/x-httpd-php-source',
3243
            'swf'   => 'application/x-shockwave-flash',
3244
            'sit'   => 'application/x-stuffit',
3245
            'tar'   => 'application/x-tar',
3246
            'tgz'   => 'application/x-tar',
3247
            'xht'   => 'application/xhtml+xml',
3248
            'xhtml' => 'application/xhtml+xml',
3249
            'zip'   => 'application/zip',
3250
            'mid'   => 'audio/midi',
3251
            'midi'  => 'audio/midi',
3252
            'mp2'   => 'audio/mpeg',
3253
            'mp3'   => 'audio/mpeg',
3254
            'mpga'  => 'audio/mpeg',
3255
            'aif'   => 'audio/x-aiff',
3256
            'aifc'  => 'audio/x-aiff',
3257
            'aiff'  => 'audio/x-aiff',
3258
            'ram'   => 'audio/x-pn-realaudio',
3259
            'rm'    => 'audio/x-pn-realaudio',
3260
            'rpm'   => 'audio/x-pn-realaudio-plugin',
3261
            'ra'    => 'audio/x-realaudio',
3262
            'wav'   => 'audio/x-wav',
3263
            'bmp'   => 'image/bmp',
3264
            'gif'   => 'image/gif',
3265
            'jpeg'  => 'image/jpeg',
3266
            'jpe'   => 'image/jpeg',
3267
            'jpg'   => 'image/jpeg',
3268
            'png'   => 'image/png',
3269
            'tiff'  => 'image/tiff',
3270
            'tif'   => 'image/tiff',
3271
            'eml'   => 'message/rfc822',
3272
            'css'   => 'text/css',
3273
            'html'  => 'text/html',
3274
            'htm'   => 'text/html',
3275
            'shtml' => 'text/html',
3276
            'log'   => 'text/plain',
3277
            'text'  => 'text/plain',
3278
            'txt'   => 'text/plain',
3279
            'rtx'   => 'text/richtext',
3280
            'rtf'   => 'text/rtf',
3281
            'vcf'   => 'text/vcard',
3282
            'vcard' => 'text/vcard',
3283
            'xml'   => 'text/xml',
3284
            'xsl'   => 'text/xml',
3285
            'mpeg'  => 'video/mpeg',
3286
            'mpe'   => 'video/mpeg',
3287
            'mpg'   => 'video/mpeg',
3288
            'mov'   => 'video/quicktime',
3289
            'qt'    => 'video/quicktime',
3290
            'rv'    => 'video/vnd.rn-realvideo',
3291
            'avi'   => 'video/x-msvideo',
3292
            'movie' => 'video/x-sgi-movie'
3293
        );
3294
        if (array_key_exists(strtolower($ext), $mimes)) {
3295
            return $mimes[strtolower($ext)];
3296
        }
3297
        return 'application/octet-stream';
3298
    }
3299
3300
    /**
3301
     * Map a file name to a MIME type.
3302
     * Defaults to 'application/octet-stream', i.e.. arbitrary binary data.
3303
     * @param string $filename A file name or full path, does not need to exist as a file
3304
     * @return string
3305
     * @static
3306
     */
3307
    public static function filenameToType($filename)
3308
    {
3309
        // In case the path is a URL, strip any query string before getting extension
3310
        $qpos = strpos($filename, '?');
3311
        if (false !== $qpos) {
3312
            $filename = substr($filename, 0, $qpos);
3313
        }
3314
        $pathinfo = self::mb_pathinfo($filename);
3315
        return self::_mime_types($pathinfo['extension']);
3316
    }
3317
3318
    /**
3319
     * Multi-byte-safe pathinfo replacement.
3320
     * Drop-in replacement for pathinfo(), but multibyte-safe, cross-platform-safe, old-version-safe.
3321
     * Works similarly to the one in PHP >= 5.2.0
3322
     * @link http://www.php.net/manual/en/function.pathinfo.php#107461
3323
     * @param string $path A filename or path, does not need to exist as a file
3324
     * @param integer|string $options Either a PATHINFO_* constant,
3325
     *      or a string name to return only the specified piece, allows 'filename' to work on PHP < 5.2
3326
     * @return string|array
3327
     * @static
3328
     */
3329
    public static function mb_pathinfo($path, $options = null)
3330
    {
3331
        $ret = array('dirname' => '', 'basename' => '', 'extension' => '', 'filename' => '');
3332
        $pathinfo = array();
3333
        if (preg_match('%^(.*?)[\\\\/]*(([^/\\\\]*?)(\.([^\.\\\\/]+?)|))[\\\\/\.]*$%im', $path, $pathinfo)) {
3334
            if (array_key_exists(1, $pathinfo)) {
3335
                $ret['dirname'] = $pathinfo[1];
3336
            }
3337
            if (array_key_exists(2, $pathinfo)) {
3338
                $ret['basename'] = $pathinfo[2];
3339
            }
3340
            if (array_key_exists(5, $pathinfo)) {
3341
                $ret['extension'] = $pathinfo[5];
3342
            }
3343
            if (array_key_exists(3, $pathinfo)) {
3344
                $ret['filename'] = $pathinfo[3];
3345
            }
3346
        }
3347
        switch ($options) {
3348
            case PATHINFO_DIRNAME:
3349
            case 'dirname':
3350
                return $ret['dirname'];
3351
            case PATHINFO_BASENAME:
3352
            case 'basename':
3353
                return $ret['basename'];
3354
            case PATHINFO_EXTENSION:
3355
            case 'extension':
3356
                return $ret['extension'];
3357
            case PATHINFO_FILENAME:
3358
            case 'filename':
3359
                return $ret['filename'];
3360
            default:
3361
                return $ret;
3362
        }
3363
    }
3364
3365
    /**
3366
     * Set or reset instance properties.
3367
     * You should avoid this function - it's more verbose, less efficient, more error-prone and
3368
     * harder to debug than setting properties directly.
3369
     * Usage Example:
3370
     * `$mail->set('SMTPSecure', 'tls');`
3371
     *   is the same as:
3372
     * `$mail->SMTPSecure = 'tls';`
3373
     * @access public
3374
     * @param string $name The property name to set
3375
     * @param mixed $value The value to set the property to
3376
     * @return boolean
3377
     * @TODO Should this not be using the __set() magic function?
3378
     */
3379
    public function set($name, $value = '')
3380
    {
3381
        if (property_exists($this, $name)) {
3382
            $this->$name = $value;
3383
            return true;
3384
        } else {
3385
            $this->setError($this->lang('variable_set') . $name);
3386
            return false;
3387
        }
3388
    }
3389
3390
    /**
3391
     * Strip newlines to prevent header injection.
3392
     * @access public
3393
     * @param string $str
3394
     * @return string
3395
     */
3396
    public function secureHeader($str)
3397
    {
3398
        return trim(str_replace(array("\r", "\n"), '', $str));
3399
    }
3400
3401
    /**
3402
     * Normalize line breaks in a string.
3403
     * Converts UNIX LF, Mac CR and Windows CRLF line breaks into a single line break format.
3404
     * Defaults to CRLF (for message bodies) and preserves consecutive breaks.
3405
     * @param string $text
3406
     * @param string $breaktype What kind of line break to use, defaults to CRLF
3407
     * @return string
3408
     * @access public
3409
     * @static
3410
     */
3411
    public static function normalizeBreaks($text, $breaktype = "\r\n")
3412
    {
3413
        return preg_replace('/(\r\n|\r|\n)/ms', $breaktype, $text);
3414
    }
3415
3416
3417
    /**
3418
     * Set the public and private key files and password for S/MIME signing.
3419
     * @access public
3420
     * @param string $cert_filename
3421
     * @param string $key_filename
3422
     * @param string $key_pass Password for private key
3423
     * @param string $extracerts_filename Optional path to chain certificate
3424
     */
3425
    public function sign($cert_filename, $key_filename, $key_pass, $extracerts_filename = '')
3426
    {
3427
        $this->sign_cert_file = $cert_filename;
3428
        $this->sign_key_file = $key_filename;
3429
        $this->sign_key_pass = $key_pass;
3430
        $this->sign_extracerts_file = $extracerts_filename;
3431
    }
3432
3433
    /**
3434
     * Quoted-Printable-encode a DKIM header.
3435
     * @access public
3436
     * @param string $txt
3437
     * @return string
3438
     */
3439
    public function DKIM_QP($txt)
3440
    {
3441
        $line = '';
3442
        for ($i = 0; $i < strlen($txt); $i++) {
3443
            $ord = ord($txt[$i]);
3444
            if (((0x21 <= $ord) && ($ord <= 0x3A)) || $ord == 0x3C || ((0x3E <= $ord) && ($ord <= 0x7E))) {
3445
                $line .= $txt[$i];
3446
            } else {
3447
                $line .= '=' . sprintf('%02X', $ord);
3448
            }
3449
        }
3450
        return $line;
3451
    }
3452
3453
    /**
3454
     * Generate a DKIM signature.
3455
     * @access public
3456
     * @param string $signHeader
3457
     * @throws phpmailerException
3458
     * @return string
3459
     */
3460
    public function DKIM_Sign($signHeader)
3461
    {
3462
        if (!defined('PKCS7_TEXT')) {
3463
            if ($this->exceptions) {
3464
                throw new phpmailerException($this->lang('extension_missing') . 'openssl');
3465
            }
3466
            return '';
3467
        }
3468
        $privKeyStr = file_get_contents($this->DKIM_private);
3469
        if ($this->DKIM_passphrase != '') {
3470
            $privKey = openssl_pkey_get_private($privKeyStr, $this->DKIM_passphrase);
3471
        } else {
3472
            $privKey = $privKeyStr;
3473
        }
3474
        if (openssl_sign($signHeader, $signature, $privKey)) {
3475
            return base64_encode($signature);
3476
        }
3477
        return '';
3478
    }
3479
3480
    /**
3481
     * Generate a DKIM canonicalization header.
3482
     * @access public
3483
     * @param string $signHeader Header
3484
     * @return string
3485
     */
3486
    public function DKIM_HeaderC($signHeader)
3487
    {
3488
        $signHeader = preg_replace('/\r\n\s+/', ' ', $signHeader);
3489
        $lines = explode("\r\n", $signHeader);
3490
        foreach ($lines as $key => $line) {
3491
            list($heading, $value) = explode(':', $line, 2);
3492
            $heading = strtolower($heading);
3493
            $value = preg_replace('/\s+/', ' ', $value); // Compress useless spaces
3494
            $lines[$key] = $heading . ':' . trim($value); // Don't forget to remove WSP around the value
3495
        }
3496
        $signHeader = implode("\r\n", $lines);
3497
        return $signHeader;
3498
    }
3499
3500
    /**
3501
     * Generate a DKIM canonicalization body.
3502
     * @access public
3503
     * @param string $body Message Body
3504
     * @return string
3505
     */
3506
    public function DKIM_BodyC($body)
3507
    {
3508
        if ($body == '') {
3509
            return "\r\n";
3510
        }
3511
        // stabilize line endings
3512
        $body = str_replace("\r\n", "\n", $body);
3513
        $body = str_replace("\n", "\r\n", $body);
3514
        // END stabilize line endings
3515
        while (substr($body, strlen($body) - 4, 4) == "\r\n\r\n") {
3516
            $body = substr($body, 0, strlen($body) - 2);
3517
        }
3518
        return $body;
3519
    }
3520
3521
    /**
3522
     * Create the DKIM header and body in a new message header.
3523
     * @access public
3524
     * @param string $headers_line Header lines
3525
     * @param string $subject Subject
3526
     * @param string $body Body
3527
     * @return string
3528
     */
3529
    public function DKIM_Add($headers_line, $subject, $body)
3530
    {
3531
        $DKIMsignatureType = 'rsa-sha1'; // Signature & hash algorithms
3532
        $DKIMcanonicalization = 'relaxed/simple'; // Canonicalization of header/body
3533
        $DKIMquery = 'dns/txt'; // Query method
3534
        $DKIMtime = time(); // Signature Timestamp = seconds since 00:00:00 - Jan 1, 1970 (UTC time zone)
3535
        $subject_header = "Subject: $subject";
3536
        $headers = explode($this->LE, $headers_line);
3537
        $from_header = '';
3538
        $to_header = '';
3539
        $current = '';
3540
        foreach ($headers as $header) {
3541
            if (strpos($header, 'From:') === 0) {
3542
                $from_header = $header;
3543
                $current = 'from_header';
3544
            } elseif (strpos($header, 'To:') === 0) {
3545
                $to_header = $header;
3546
                $current = 'to_header';
3547
            } else {
3548
                if (!empty($$current) && strpos($header, ' =?') === 0) {
3549
                    $$current .= $header;
3550
                } else {
3551
                    $current = '';
3552
                }
3553
            }
3554
        }
3555
        $from = str_replace('|', '=7C', $this->DKIM_QP($from_header));
3556
        $to = str_replace('|', '=7C', $this->DKIM_QP($to_header));
3557
        $subject = str_replace(
3558
            '|',
3559
            '=7C',
3560
            $this->DKIM_QP($subject_header)
3561
        ); // Copied header fields (dkim-quoted-printable)
3562
        $body = $this->DKIM_BodyC($body);
3563
        $DKIMlen = strlen($body); // Length of body
3564
        $DKIMb64 = base64_encode(pack('H*', sha1($body))); // Base64 of packed binary SHA-1 hash of body
3565
        if ('' == $this->DKIM_identity) {
3566
            $ident = '';
3567
        } else {
3568
            $ident = ' i=' . $this->DKIM_identity . ';';
3569
        }
3570
        $dkimhdrs = 'DKIM-Signature: v=1; a=' .
3571
            $DKIMsignatureType . '; q=' .
3572
            $DKIMquery . '; l=' .
3573
            $DKIMlen . '; s=' .
3574
            $this->DKIM_selector .
3575
            ";\r\n" .
3576
            "\tt=" . $DKIMtime . '; c=' . $DKIMcanonicalization . ";\r\n" .
3577
            "\th=From:To:Subject;\r\n" .
3578
            "\td=" . $this->DKIM_domain . ';' . $ident . "\r\n" .
3579
            "\tz=$from\r\n" .
3580
            "\t|$to\r\n" .
3581
            "\t|$subject;\r\n" .
3582
            "\tbh=" . $DKIMb64 . ";\r\n" .
3583
            "\tb=";
3584
        $toSign = $this->DKIM_HeaderC(
3585
            $from_header . "\r\n" . $to_header . "\r\n" . $subject_header . "\r\n" . $dkimhdrs
3586
        );
3587
        $signed = $this->DKIM_Sign($toSign);
3588
        return $dkimhdrs . $signed . "\r\n";
3589
    }
3590
3591
    /**
3592
     * Detect if a string contains a line longer than the maximum line length allowed.
3593
     * @param string $str
3594
     * @return boolean
3595
     * @static
3596
     */
3597
    public static function hasLineLongerThanMax($str)
3598
    {
3599
        //+2 to include CRLF line break for a 1000 total
3600
        return (boolean)preg_match('/^(.{'.(self::MAX_LINE_LENGTH + 2).',})/m', $str);
3601
    }
3602
3603
    /**
3604
     * Allows for public read access to 'to' property.
3605
     * @access public
3606
     * @return array
3607
     */
3608
    public function getToAddresses()
3609
    {
3610
        return $this->to;
3611
    }
3612
3613
    /**
3614
     * Allows for public read access to 'cc' property.
3615
     * @access public
3616
     * @return array
3617
     */
3618
    public function getCcAddresses()
3619
    {
3620
        return $this->cc;
3621
    }
3622
3623
    /**
3624
     * Allows for public read access to 'bcc' property.
3625
     * @access public
3626
     * @return array
3627
     */
3628
    public function getBccAddresses()
3629
    {
3630
        return $this->bcc;
3631
    }
3632
3633
    /**
3634
     * Allows for public read access to 'ReplyTo' property.
3635
     * @access public
3636
     * @return array
3637
     */
3638
    public function getReplyToAddresses()
3639
    {
3640
        return $this->ReplyTo;
3641
    }
3642
3643
    /**
3644
     * Allows for public read access to 'all_recipients' property.
3645
     * @access public
3646
     * @return array
3647
     */
3648
    public function getAllRecipientAddresses()
3649
    {
3650
        return $this->all_recipients;
3651
    }
3652
3653
    /**
3654
     * Perform a callback.
3655
     * @param boolean $isSent
3656
     * @param array $to
3657
     * @param array $cc
3658
     * @param array $bcc
3659
     * @param string $subject
3660
     * @param string $body
3661
     * @param string $from
3662
     */
3663
    protected function doCallback($isSent, $to, $cc, $bcc, $subject, $body, $from)
3664
    {
3665
        if (!empty($this->action_function) && is_callable($this->action_function)) {
3666
            $params = array($isSent, $to, $cc, $bcc, $subject, $body, $from);
3667
            call_user_func_array($this->action_function, $params);
3668
        }
3669
    }
3670
}
3671
3672
/**
3673
 * PHPMailer exception handler
3674
 * @package PHPMailer
3675
 */
3676
class phpmailerException extends Exception
3677
{
3678
    /**
3679
     * Prettify error message output
3680
     * @return string
3681
     */
3682
    public function errorMessage()
3683
    {
3684
        $errorMsg = '<strong>' . $this->getMessage() . "</strong><br />\n";
3685
        return $errorMsg;
3686
    }
3687
}