[ Index ]

PHP Cross Reference of WordPress

title

Body

[close]

/wp-includes/PHPMailer/ -> PHPMailer.php (source)

   1  <?php
   2  
   3  /**
   4   * PHPMailer - PHP email creation and transport class.
   5   * PHP Version 5.5.
   6   *
   7   * @see https://github.com/PHPMailer/PHPMailer/ The PHPMailer GitHub project
   8   *
   9   * @author    Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk>
  10   * @author    Jim Jagielski (jimjag) <jimjag@gmail.com>
  11   * @author    Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net>
  12   * @author    Brent R. Matzelle (original founder)
  13   * @copyright 2012 - 2020 Marcus Bointon
  14   * @copyright 2010 - 2012 Jim Jagielski
  15   * @copyright 2004 - 2009 Andy Prevost
  16   * @license   http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
  17   * @note      This program is distributed in the hope that it will be useful - WITHOUT
  18   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  19   * FITNESS FOR A PARTICULAR PURPOSE.
  20   */
  21  
  22  namespace PHPMailer\PHPMailer;
  23  
  24  /**
  25   * PHPMailer - PHP email creation and transport class.
  26   *
  27   * @author Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk>
  28   * @author Jim Jagielski (jimjag) <jimjag@gmail.com>
  29   * @author Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net>
  30   * @author Brent R. Matzelle (original founder)
  31   */
  32  class PHPMailer
  33  {
  34      const CHARSET_ASCII = 'us-ascii';
  35      const CHARSET_ISO88591 = 'iso-8859-1';
  36      const CHARSET_UTF8 = 'utf-8';
  37  
  38      const CONTENT_TYPE_PLAINTEXT = 'text/plain';
  39      const CONTENT_TYPE_TEXT_CALENDAR = 'text/calendar';
  40      const CONTENT_TYPE_TEXT_HTML = 'text/html';
  41      const CONTENT_TYPE_MULTIPART_ALTERNATIVE = 'multipart/alternative';
  42      const CONTENT_TYPE_MULTIPART_MIXED = 'multipart/mixed';
  43      const CONTENT_TYPE_MULTIPART_RELATED = 'multipart/related';
  44  
  45      const ENCODING_7BIT = '7bit';
  46      const ENCODING_8BIT = '8bit';
  47      const ENCODING_BASE64 = 'base64';
  48      const ENCODING_BINARY = 'binary';
  49      const ENCODING_QUOTED_PRINTABLE = 'quoted-printable';
  50  
  51      const ENCRYPTION_STARTTLS = 'tls';
  52      const ENCRYPTION_SMTPS = 'ssl';
  53  
  54      const ICAL_METHOD_REQUEST = 'REQUEST';
  55      const ICAL_METHOD_PUBLISH = 'PUBLISH';
  56      const ICAL_METHOD_REPLY = 'REPLY';
  57      const ICAL_METHOD_ADD = 'ADD';
  58      const ICAL_METHOD_CANCEL = 'CANCEL';
  59      const ICAL_METHOD_REFRESH = 'REFRESH';
  60      const ICAL_METHOD_COUNTER = 'COUNTER';
  61      const ICAL_METHOD_DECLINECOUNTER = 'DECLINECOUNTER';
  62  
  63      /**
  64       * Email priority.
  65       * Options: null (default), 1 = High, 3 = Normal, 5 = low.
  66       * When null, the header is not set at all.
  67       *
  68       * @var int|null
  69       */
  70      public $Priority;
  71  
  72      /**
  73       * The character set of the message.
  74       *
  75       * @var string
  76       */
  77      public $CharSet = self::CHARSET_ISO88591;
  78  
  79      /**
  80       * The MIME Content-type of the message.
  81       *
  82       * @var string
  83       */
  84      public $ContentType = self::CONTENT_TYPE_PLAINTEXT;
  85  
  86      /**
  87       * The message encoding.
  88       * Options: "8bit", "7bit", "binary", "base64", and "quoted-printable".
  89       *
  90       * @var string
  91       */
  92      public $Encoding = self::ENCODING_8BIT;
  93  
  94      /**
  95       * Holds the most recent mailer error message.
  96       *
  97       * @var string
  98       */
  99      public $ErrorInfo = '';
 100  
 101      /**
 102       * The From email address for the message.
 103       *
 104       * @var string
 105       */
 106      public $From = 'root@localhost';
 107  
 108      /**
 109       * The From name of the message.
 110       *
 111       * @var string
 112       */
 113      public $FromName = 'Root User';
 114  
 115      /**
 116       * The envelope sender of the message.
 117       * This will usually be turned into a Return-Path header by the receiver,
 118       * and is the address that bounces will be sent to.
 119       * If not empty, will be passed via `-f` to sendmail or as the 'MAIL FROM' value over SMTP.
 120       *
 121       * @var string
 122       */
 123      public $Sender = '';
 124  
 125      /**
 126       * The Subject of the message.
 127       *
 128       * @var string
 129       */
 130      public $Subject = '';
 131  
 132      /**
 133       * An HTML or plain text message body.
 134       * If HTML then call isHTML(true).
 135       *
 136       * @var string
 137       */
 138      public $Body = '';
 139  
 140      /**
 141       * The plain-text message body.
 142       * This body can be read by mail clients that do not have HTML email
 143       * capability such as mutt & Eudora.
 144       * Clients that can read HTML will view the normal Body.
 145       *
 146       * @var string
 147       */
 148      public $AltBody = '';
 149  
 150      /**
 151       * An iCal message part body.
 152       * Only supported in simple alt or alt_inline message types
 153       * To generate iCal event structures, use classes like EasyPeasyICS or iCalcreator.
 154       *
 155       * @see http://sprain.ch/blog/downloads/php-class-easypeasyics-create-ical-files-with-php/
 156       * @see http://kigkonsult.se/iCalcreator/
 157       *
 158       * @var string
 159       */
 160      public $Ical = '';
 161  
 162      /**
 163       * Value-array of "method" in Contenttype header "text/calendar"
 164       *
 165       * @var string[]
 166       */
 167      protected static $IcalMethods = [
 168          self::ICAL_METHOD_REQUEST,
 169          self::ICAL_METHOD_PUBLISH,
 170          self::ICAL_METHOD_REPLY,
 171          self::ICAL_METHOD_ADD,
 172          self::ICAL_METHOD_CANCEL,
 173          self::ICAL_METHOD_REFRESH,
 174          self::ICAL_METHOD_COUNTER,
 175          self::ICAL_METHOD_DECLINECOUNTER,
 176      ];
 177  
 178      /**
 179       * The complete compiled MIME message body.
 180       *
 181       * @var string
 182       */
 183      protected $MIMEBody = '';
 184  
 185      /**
 186       * The complete compiled MIME message headers.
 187       *
 188       * @var string
 189       */
 190      protected $MIMEHeader = '';
 191  
 192      /**
 193       * Extra headers that createHeader() doesn't fold in.
 194       *
 195       * @var string
 196       */
 197      protected $mailHeader = '';
 198  
 199      /**
 200       * Word-wrap the message body to this number of chars.
 201       * Set to 0 to not wrap. A useful value here is 78, for RFC2822 section 2.1.1 compliance.
 202       *
 203       * @see static::STD_LINE_LENGTH
 204       *
 205       * @var int
 206       */
 207      public $WordWrap = 0;
 208  
 209      /**
 210       * Which method to use to send mail.
 211       * Options: "mail", "sendmail", or "smtp".
 212       *
 213       * @var string
 214       */
 215      public $Mailer = 'mail';
 216  
 217      /**
 218       * The path to the sendmail program.
 219       *
 220       * @var string
 221       */
 222      public $Sendmail = '/usr/sbin/sendmail';
 223  
 224      /**
 225       * Whether mail() uses a fully sendmail-compatible MTA.
 226       * One which supports sendmail's "-oi -f" options.
 227       *
 228       * @var bool
 229       */
 230      public $UseSendmailOptions = true;
 231  
 232      /**
 233       * The email address that a reading confirmation should be sent to, also known as read receipt.
 234       *
 235       * @var string
 236       */
 237      public $ConfirmReadingTo = '';
 238  
 239      /**
 240       * The hostname to use in the Message-ID header and as default HELO string.
 241       * If empty, PHPMailer attempts to find one with, in order,
 242       * $_SERVER['SERVER_NAME'], gethostname(), php_uname('n'), or the value
 243       * 'localhost.localdomain'.
 244       *
 245       * @see PHPMailer::$Helo
 246       *
 247       * @var string
 248       */
 249      public $Hostname = '';
 250  
 251      /**
 252       * An ID to be used in the Message-ID header.
 253       * If empty, a unique id will be generated.
 254       * You can set your own, but it must be in the format "<id@domain>",
 255       * as defined in RFC5322 section 3.6.4 or it will be ignored.
 256       *
 257       * @see https://tools.ietf.org/html/rfc5322#section-3.6.4
 258       *
 259       * @var string
 260       */
 261      public $MessageID = '';
 262  
 263      /**
 264       * The message Date to be used in the Date header.
 265       * If empty, the current date will be added.
 266       *
 267       * @var string
 268       */
 269      public $MessageDate = '';
 270  
 271      /**
 272       * SMTP hosts.
 273       * Either a single hostname or multiple semicolon-delimited hostnames.
 274       * You can also specify a different port
 275       * for each host by using this format: [hostname:port]
 276       * (e.g. "smtp1.example.com:25;smtp2.example.com").
 277       * You can also specify encryption type, for example:
 278       * (e.g. "tls://smtp1.example.com:587;ssl://smtp2.example.com:465").
 279       * Hosts will be tried in order.
 280       *
 281       * @var string
 282       */
 283      public $Host = 'localhost';
 284  
 285      /**
 286       * The default SMTP server port.
 287       *
 288       * @var int
 289       */
 290      public $Port = 25;
 291  
 292      /**
 293       * The SMTP HELO/EHLO name used for the SMTP connection.
 294       * Default is $Hostname. If $Hostname is empty, PHPMailer attempts to find
 295       * one with the same method described above for $Hostname.
 296       *
 297       * @see PHPMailer::$Hostname
 298       *
 299       * @var string
 300       */
 301      public $Helo = '';
 302  
 303      /**
 304       * What kind of encryption to use on the SMTP connection.
 305       * Options: '', static::ENCRYPTION_STARTTLS, or static::ENCRYPTION_SMTPS.
 306       *
 307       * @var string
 308       */
 309      public $SMTPSecure = '';
 310  
 311      /**
 312       * Whether to enable TLS encryption automatically if a server supports it,
 313       * even if `SMTPSecure` is not set to 'tls'.
 314       * Be aware that in PHP >= 5.6 this requires that the server's certificates are valid.
 315       *
 316       * @var bool
 317       */
 318      public $SMTPAutoTLS = true;
 319  
 320      /**
 321       * Whether to use SMTP authentication.
 322       * Uses the Username and Password properties.
 323       *
 324       * @see PHPMailer::$Username
 325       * @see PHPMailer::$Password
 326       *
 327       * @var bool
 328       */
 329      public $SMTPAuth = false;
 330  
 331      /**
 332       * Options array passed to stream_context_create when connecting via SMTP.
 333       *
 334       * @var array
 335       */
 336      public $SMTPOptions = [];
 337  
 338      /**
 339       * SMTP username.
 340       *
 341       * @var string
 342       */
 343      public $Username = '';
 344  
 345      /**
 346       * SMTP password.
 347       *
 348       * @var string
 349       */
 350      public $Password = '';
 351  
 352      /**
 353       * SMTP auth type.
 354       * Options are CRAM-MD5, LOGIN, PLAIN, XOAUTH2, attempted in that order if not specified.
 355       *
 356       * @var string
 357       */
 358      public $AuthType = '';
 359  
 360      /**
 361       * An instance of the PHPMailer OAuth class.
 362       *
 363       * @var OAuth
 364       */
 365      protected $oauth;
 366  
 367      /**
 368       * The SMTP server timeout in seconds.
 369       * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2.
 370       *
 371       * @var int
 372       */
 373      public $Timeout = 300;
 374  
 375      /**
 376       * Comma separated list of DSN notifications
 377       * 'NEVER' under no circumstances a DSN must be returned to the sender.
 378       *         If you use NEVER all other notifications will be ignored.
 379       * 'SUCCESS' will notify you when your mail has arrived at its destination.
 380       * 'FAILURE' will arrive if an error occurred during delivery.
 381       * 'DELAY'   will notify you if there is an unusual delay in delivery, but the actual
 382       *           delivery's outcome (success or failure) is not yet decided.
 383       *
 384       * @see https://tools.ietf.org/html/rfc3461 See section 4.1 for more information about NOTIFY
 385       */
 386      public $dsn = '';
 387  
 388      /**
 389       * SMTP class debug output mode.
 390       * Debug output level.
 391       * Options:
 392       * @see SMTP::DEBUG_OFF: No output
 393       * @see SMTP::DEBUG_CLIENT: Client messages
 394       * @see SMTP::DEBUG_SERVER: Client and server messages
 395       * @see SMTP::DEBUG_CONNECTION: As SERVER plus connection status
 396       * @see SMTP::DEBUG_LOWLEVEL: Noisy, low-level data output, rarely needed
 397       *
 398       * @see SMTP::$do_debug
 399       *
 400       * @var int
 401       */
 402      public $SMTPDebug = 0;
 403  
 404      /**
 405       * How to handle debug output.
 406       * Options:
 407       * * `echo` Output plain-text as-is, appropriate for CLI
 408       * * `html` Output escaped, line breaks converted to `<br>`, appropriate for browser output
 409       * * `error_log` Output to error log as configured in php.ini
 410       * By default PHPMailer will use `echo` if run from a `cli` or `cli-server` SAPI, `html` otherwise.
 411       * Alternatively, you can provide a callable expecting two params: a message string and the debug level:
 412       *
 413       * ```php
 414       * $mail->Debugoutput = function($str, $level) {echo "debug level $level; message: $str";};
 415       * ```
 416       *
 417       * Alternatively, you can pass in an instance of a PSR-3 compatible logger, though only `debug`
 418       * level output is used:
 419       *
 420       * ```php
 421       * $mail->Debugoutput = new myPsr3Logger;
 422       * ```
 423       *
 424       * @see SMTP::$Debugoutput
 425       *
 426       * @var string|callable|\Psr\Log\LoggerInterface
 427       */
 428      public $Debugoutput = 'echo';
 429  
 430      /**
 431       * Whether to keep SMTP connection open after each message.
 432       * If this is set to true then to close the connection
 433       * requires an explicit call to smtpClose().
 434       *
 435       * @var bool
 436       */
 437      public $SMTPKeepAlive = false;
 438  
 439      /**
 440       * Whether to split multiple to addresses into multiple messages
 441       * or send them all in one message.
 442       * Only supported in `mail` and `sendmail` transports, not in SMTP.
 443       *
 444       * @var bool
 445       *
 446       * @deprecated 6.0.0 PHPMailer isn't a mailing list manager!
 447       */
 448      public $SingleTo = false;
 449  
 450      /**
 451       * Storage for addresses when SingleTo is enabled.
 452       *
 453       * @var array
 454       */
 455      protected $SingleToArray = [];
 456  
 457      /**
 458       * Whether to generate VERP addresses on send.
 459       * Only applicable when sending via SMTP.
 460       *
 461       * @see https://en.wikipedia.org/wiki/Variable_envelope_return_path
 462       * @see http://www.postfix.org/VERP_README.html Postfix VERP info
 463       *
 464       * @var bool
 465       */
 466      public $do_verp = false;
 467  
 468      /**
 469       * Whether to allow sending messages with an empty body.
 470       *
 471       * @var bool
 472       */
 473      public $AllowEmpty = false;
 474  
 475      /**
 476       * DKIM selector.
 477       *
 478       * @var string
 479       */
 480      public $DKIM_selector = '';
 481  
 482      /**
 483       * DKIM Identity.
 484       * Usually the email address used as the source of the email.
 485       *
 486       * @var string
 487       */
 488      public $DKIM_identity = '';
 489  
 490      /**
 491       * DKIM passphrase.
 492       * Used if your key is encrypted.
 493       *
 494       * @var string
 495       */
 496      public $DKIM_passphrase = '';
 497  
 498      /**
 499       * DKIM signing domain name.
 500       *
 501       * @example 'example.com'
 502       *
 503       * @var string
 504       */
 505      public $DKIM_domain = '';
 506  
 507      /**
 508       * DKIM Copy header field values for diagnostic use.
 509       *
 510       * @var bool
 511       */
 512      public $DKIM_copyHeaderFields = true;
 513  
 514      /**
 515       * DKIM Extra signing headers.
 516       *
 517       * @example ['List-Unsubscribe', 'List-Help']
 518       *
 519       * @var array
 520       */
 521      public $DKIM_extraHeaders = [];
 522  
 523      /**
 524       * DKIM private key file path.
 525       *
 526       * @var string
 527       */
 528      public $DKIM_private = '';
 529  
 530      /**
 531       * DKIM private key string.
 532       *
 533       * If set, takes precedence over `$DKIM_private`.
 534       *
 535       * @var string
 536       */
 537      public $DKIM_private_string = '';
 538  
 539      /**
 540       * Callback Action function name.
 541       *
 542       * The function that handles the result of the send email action.
 543       * It is called out by send() for each email sent.
 544       *
 545       * Value can be any php callable: http://www.php.net/is_callable
 546       *
 547       * Parameters:
 548       *   bool $result        result of the send action
 549       *   array   $to            email addresses of the recipients
 550       *   array   $cc            cc email addresses
 551       *   array   $bcc           bcc email addresses
 552       *   string  $subject       the subject
 553       *   string  $body          the email body
 554       *   string  $from          email address of sender
 555       *   string  $extra         extra information of possible use
 556       *                          "smtp_transaction_id' => last smtp transaction id
 557       *
 558       * @var string
 559       */
 560      public $action_function = '';
 561  
 562      /**
 563       * What to put in the X-Mailer header.
 564       * Options: An empty string for PHPMailer default, whitespace/null for none, or a string to use.
 565       *
 566       * @var string|null
 567       */
 568      public $XMailer = '';
 569  
 570      /**
 571       * Which validator to use by default when validating email addresses.
 572       * May be a callable to inject your own validator, but there are several built-in validators.
 573       * The default validator uses PHP's FILTER_VALIDATE_EMAIL filter_var option.
 574       *
 575       * @see PHPMailer::validateAddress()
 576       *
 577       * @var string|callable
 578       */
 579      public static $validator = 'php';
 580  
 581      /**
 582       * An instance of the SMTP sender class.
 583       *
 584       * @var SMTP
 585       */
 586      protected $smtp;
 587  
 588      /**
 589       * The array of 'to' names and addresses.
 590       *
 591       * @var array
 592       */
 593      protected $to = [];
 594  
 595      /**
 596       * The array of 'cc' names and addresses.
 597       *
 598       * @var array
 599       */
 600      protected $cc = [];
 601  
 602      /**
 603       * The array of 'bcc' names and addresses.
 604       *
 605       * @var array
 606       */
 607      protected $bcc = [];
 608  
 609      /**
 610       * The array of reply-to names and addresses.
 611       *
 612       * @var array
 613       */
 614      protected $ReplyTo = [];
 615  
 616      /**
 617       * An array of all kinds of addresses.
 618       * Includes all of $to, $cc, $bcc.
 619       *
 620       * @see PHPMailer::$to
 621       * @see PHPMailer::$cc
 622       * @see PHPMailer::$bcc
 623       *
 624       * @var array
 625       */
 626      protected $all_recipients = [];
 627  
 628      /**
 629       * An array of names and addresses queued for validation.
 630       * In send(), valid and non duplicate entries are moved to $all_recipients
 631       * and one of $to, $cc, or $bcc.
 632       * This array is used only for addresses with IDN.
 633       *
 634       * @see PHPMailer::$to
 635       * @see PHPMailer::$cc
 636       * @see PHPMailer::$bcc
 637       * @see PHPMailer::$all_recipients
 638       *
 639       * @var array
 640       */
 641      protected $RecipientsQueue = [];
 642  
 643      /**
 644       * An array of reply-to names and addresses queued for validation.
 645       * In send(), valid and non duplicate entries are moved to $ReplyTo.
 646       * This array is used only for addresses with IDN.
 647       *
 648       * @see PHPMailer::$ReplyTo
 649       *
 650       * @var array
 651       */
 652      protected $ReplyToQueue = [];
 653  
 654      /**
 655       * The array of attachments.
 656       *
 657       * @var array
 658       */
 659      protected $attachment = [];
 660  
 661      /**
 662       * The array of custom headers.
 663       *
 664       * @var array
 665       */
 666      protected $CustomHeader = [];
 667  
 668      /**
 669       * The most recent Message-ID (including angular brackets).
 670       *
 671       * @var string
 672       */
 673      protected $lastMessageID = '';
 674  
 675      /**
 676       * The message's MIME type.
 677       *
 678       * @var string
 679       */
 680      protected $message_type = '';
 681  
 682      /**
 683       * The array of MIME boundary strings.
 684       *
 685       * @var array
 686       */
 687      protected $boundary = [];
 688  
 689      /**
 690       * The array of available languages.
 691       *
 692       * @var array
 693       */
 694      protected $language = [];
 695  
 696      /**
 697       * The number of errors encountered.
 698       *
 699       * @var int
 700       */
 701      protected $error_count = 0;
 702  
 703      /**
 704       * The S/MIME certificate file path.
 705       *
 706       * @var string
 707       */
 708      protected $sign_cert_file = '';
 709  
 710      /**
 711       * The S/MIME key file path.
 712       *
 713       * @var string
 714       */
 715      protected $sign_key_file = '';
 716  
 717      /**
 718       * The optional S/MIME extra certificates ("CA Chain") file path.
 719       *
 720       * @var string
 721       */
 722      protected $sign_extracerts_file = '';
 723  
 724      /**
 725       * The S/MIME password for the key.
 726       * Used only if the key is encrypted.
 727       *
 728       * @var string
 729       */
 730      protected $sign_key_pass = '';
 731  
 732      /**
 733       * Whether to throw exceptions for errors.
 734       *
 735       * @var bool
 736       */
 737      protected $exceptions = false;
 738  
 739      /**
 740       * Unique ID used for message ID and boundaries.
 741       *
 742       * @var string
 743       */
 744      protected $uniqueid = '';
 745  
 746      /**
 747       * The PHPMailer Version number.
 748       *
 749       * @var string
 750       */
 751      const VERSION = '6.2.0';
 752  
 753      /**
 754       * Error severity: message only, continue processing.
 755       *
 756       * @var int
 757       */
 758      const STOP_MESSAGE = 0;
 759  
 760      /**
 761       * Error severity: message, likely ok to continue processing.
 762       *
 763       * @var int
 764       */
 765      const STOP_CONTINUE = 1;
 766  
 767      /**
 768       * Error severity: message, plus full stop, critical error reached.
 769       *
 770       * @var int
 771       */
 772      const STOP_CRITICAL = 2;
 773  
 774      /**
 775       * The SMTP standard CRLF line break.
 776       * If you want to change line break format, change static::$LE, not this.
 777       */
 778      const CRLF = "\r\n";
 779  
 780      /**
 781       * "Folding White Space" a white space string used for line folding.
 782       */
 783      const FWS = ' ';
 784  
 785      /**
 786       * SMTP RFC standard line ending; Carriage Return, Line Feed.
 787       *
 788       * @var string
 789       */
 790      protected static $LE = self::CRLF;
 791  
 792      /**
 793       * The maximum line length supported by mail().
 794       *
 795       * Background: mail() will sometimes corrupt messages
 796       * with headers headers longer than 65 chars, see #818.
 797       *
 798       * @var int
 799       */
 800      const MAIL_MAX_LINE_LENGTH = 63;
 801  
 802      /**
 803       * The maximum line length allowed by RFC 2822 section 2.1.1.
 804       *
 805       * @var int
 806       */
 807      const MAX_LINE_LENGTH = 998;
 808  
 809      /**
 810       * The lower maximum line length allowed by RFC 2822 section 2.1.1.
 811       * This length does NOT include the line break
 812       * 76 means that lines will be 77 or 78 chars depending on whether
 813       * the line break format is LF or CRLF; both are valid.
 814       *
 815       * @var int
 816       */
 817      const STD_LINE_LENGTH = 76;
 818  
 819      /**
 820       * Constructor.
 821       *
 822       * @param bool $exceptions Should we throw external exceptions?
 823       */
 824      public function __construct($exceptions = null)
 825      {
 826          if (null !== $exceptions) {
 827              $this->exceptions = (bool) $exceptions;
 828          }
 829          //Pick an appropriate debug output format automatically
 830          $this->Debugoutput = (strpos(PHP_SAPI, 'cli') !== false ? 'echo' : 'html');
 831      }
 832  
 833      /**
 834       * Destructor.
 835       */
 836      public function __destruct()
 837      {
 838          //Close any open SMTP connection nicely
 839          $this->smtpClose();
 840      }
 841  
 842      /**
 843       * Call mail() in a safe_mode-aware fashion.
 844       * Also, unless sendmail_path points to sendmail (or something that
 845       * claims to be sendmail), don't pass params (not a perfect fix,
 846       * but it will do).
 847       *
 848       * @param string      $to      To
 849       * @param string      $subject Subject
 850       * @param string      $body    Message Body
 851       * @param string      $header  Additional Header(s)
 852       * @param string|null $params  Params
 853       *
 854       * @return bool
 855       */
 856      private function mailPassthru($to, $subject, $body, $header, $params)
 857      {
 858          //Check overloading of mail function to avoid double-encoding
 859          if (ini_get('mbstring.func_overload') & 1) { // phpcs:ignore PHPCompatibility.IniDirectives.RemovedIniDirectives.mbstring_func_overloadDeprecated
 860              $subject = $this->secureHeader($subject);
 861          } else {
 862              $subject = $this->encodeHeader($this->secureHeader($subject));
 863          }
 864          //Calling mail() with null params breaks
 865          if (!$this->UseSendmailOptions || null === $params) {
 866              $result = @mail($to, $subject, $body, $header);
 867          } else {
 868              $result = @mail($to, $subject, $body, $header, $params);
 869          }
 870  
 871          return $result;
 872      }
 873  
 874      /**
 875       * Output debugging info via user-defined method.
 876       * Only generates output if SMTP debug output is enabled (@see SMTP::$do_debug).
 877       *
 878       * @see PHPMailer::$Debugoutput
 879       * @see PHPMailer::$SMTPDebug
 880       *
 881       * @param string $str
 882       */
 883      protected function edebug($str)
 884      {
 885          if ($this->SMTPDebug <= 0) {
 886              return;
 887          }
 888          //Is this a PSR-3 logger?
 889          if ($this->Debugoutput instanceof \Psr\Log\LoggerInterface) {
 890              $this->Debugoutput->debug($str);
 891  
 892              return;
 893          }
 894          //Avoid clash with built-in function names
 895          if (is_callable($this->Debugoutput) && !in_array($this->Debugoutput, ['error_log', 'html', 'echo'])) {
 896              call_user_func($this->Debugoutput, $str, $this->SMTPDebug);
 897  
 898              return;
 899          }
 900          switch ($this->Debugoutput) {
 901              case 'error_log':
 902                  //Don't output, just log
 903                  /** @noinspection ForgottenDebugOutputInspection */
 904                  error_log($str);
 905                  break;
 906              case 'html':
 907                  //Cleans up output a bit for a better looking, HTML-safe output
 908                  echo htmlentities(
 909                      preg_replace('/[\r\n]+/', '', $str),
 910                      ENT_QUOTES,
 911                      'UTF-8'
 912                  ), "<br>\n";
 913                  break;
 914              case 'echo':
 915              default:
 916                  //Normalize line breaks
 917                  $str = preg_replace('/\r\n|\r/m', "\n", $str);
 918                  echo gmdate('Y-m-d H:i:s'),
 919                  "\t",
 920                      //Trim trailing space
 921                  trim(
 922                      //Indent for readability, except for trailing break
 923                      str_replace(
 924                          "\n",
 925                          "\n                   \t                  ",
 926                          trim($str)
 927                      )
 928                  ),
 929                  "\n";
 930          }
 931      }
 932  
 933      /**
 934       * Sets message type to HTML or plain.
 935       *
 936       * @param bool $isHtml True for HTML mode
 937       */
 938      public function isHTML($isHtml = true)
 939      {
 940          if ($isHtml) {
 941              $this->ContentType = static::CONTENT_TYPE_TEXT_HTML;
 942          } else {
 943              $this->ContentType = static::CONTENT_TYPE_PLAINTEXT;
 944          }
 945      }
 946  
 947      /**
 948       * Send messages using SMTP.
 949       */
 950      public function isSMTP()
 951      {
 952          $this->Mailer = 'smtp';
 953      }
 954  
 955      /**
 956       * Send messages using PHP's mail() function.
 957       */
 958      public function isMail()
 959      {
 960          $this->Mailer = 'mail';
 961      }
 962  
 963      /**
 964       * Send messages using $Sendmail.
 965       */
 966      public function isSendmail()
 967      {
 968          $ini_sendmail_path = ini_get('sendmail_path');
 969  
 970          if (false === stripos($ini_sendmail_path, 'sendmail')) {
 971              $this->Sendmail = '/usr/sbin/sendmail';
 972          } else {
 973              $this->Sendmail = $ini_sendmail_path;
 974          }
 975          $this->Mailer = 'sendmail';
 976      }
 977  
 978      /**
 979       * Send messages using qmail.
 980       */
 981      public function isQmail()
 982      {
 983          $ini_sendmail_path = ini_get('sendmail_path');
 984  
 985          if (false === stripos($ini_sendmail_path, 'qmail')) {
 986              $this->Sendmail = '/var/qmail/bin/qmail-inject';
 987          } else {
 988              $this->Sendmail = $ini_sendmail_path;
 989          }
 990          $this->Mailer = 'qmail';
 991      }
 992  
 993      /**
 994       * Add a "To" address.
 995       *
 996       * @param string $address The email address to send to
 997       * @param string $name
 998       *
 999       * @throws Exception
1000       *
1001       * @return bool true on success, false if address already used or invalid in some way
1002       */
1003      public function addAddress($address, $name = '')
1004      {
1005          return $this->addOrEnqueueAnAddress('to', $address, $name);
1006      }
1007  
1008      /**
1009       * Add a "CC" address.
1010       *
1011       * @param string $address The email address to send to
1012       * @param string $name
1013       *
1014       * @throws Exception
1015       *
1016       * @return bool true on success, false if address already used or invalid in some way
1017       */
1018      public function addCC($address, $name = '')
1019      {
1020          return $this->addOrEnqueueAnAddress('cc', $address, $name);
1021      }
1022  
1023      /**
1024       * Add a "BCC" address.
1025       *
1026       * @param string $address The email address to send to
1027       * @param string $name
1028       *
1029       * @throws Exception
1030       *
1031       * @return bool true on success, false if address already used or invalid in some way
1032       */
1033      public function addBCC($address, $name = '')
1034      {
1035          return $this->addOrEnqueueAnAddress('bcc', $address, $name);
1036      }
1037  
1038      /**
1039       * Add a "Reply-To" address.
1040       *
1041       * @param string $address The email address to reply to
1042       * @param string $name
1043       *
1044       * @throws Exception
1045       *
1046       * @return bool true on success, false if address already used or invalid in some way
1047       */
1048      public function addReplyTo($address, $name = '')
1049      {
1050          return $this->addOrEnqueueAnAddress('Reply-To', $address, $name);
1051      }
1052  
1053      /**
1054       * Add an address to one of the recipient arrays or to the ReplyTo array. Because PHPMailer
1055       * can't validate addresses with an IDN without knowing the PHPMailer::$CharSet (that can still
1056       * be modified after calling this function), addition of such addresses is delayed until send().
1057       * Addresses that have been added already return false, but do not throw exceptions.
1058       *
1059       * @param string $kind    One of 'to', 'cc', 'bcc', or 'ReplyTo'
1060       * @param string $address The email address to send, resp. to reply to
1061       * @param string $name
1062       *
1063       * @throws Exception
1064       *
1065       * @return bool true on success, false if address already used or invalid in some way
1066       */
1067      protected function addOrEnqueueAnAddress($kind, $address, $name)
1068      {
1069          $address = trim($address);
1070          $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim
1071          $pos = strrpos($address, '@');
1072          if (false === $pos) {
1073              // At-sign is missing.
1074              $error_message = sprintf(
1075                  '%s (%s): %s',
1076                  $this->lang('invalid_address'),
1077                  $kind,
1078                  $address
1079              );
1080              $this->setError($error_message);
1081              $this->edebug($error_message);
1082              if ($this->exceptions) {
1083                  throw new Exception($error_message);
1084              }
1085  
1086              return false;
1087          }
1088          $params = [$kind, $address, $name];
1089          // Enqueue addresses with IDN until we know the PHPMailer::$CharSet.
1090          if (static::idnSupported() && $this->has8bitChars(substr($address, ++$pos))) {
1091              if ('Reply-To' !== $kind) {
1092                  if (!array_key_exists($address, $this->RecipientsQueue)) {
1093                      $this->RecipientsQueue[$address] = $params;
1094  
1095                      return true;
1096                  }
1097              } elseif (!array_key_exists($address, $this->ReplyToQueue)) {
1098                  $this->ReplyToQueue[$address] = $params;
1099  
1100                  return true;
1101              }
1102  
1103              return false;
1104          }
1105  
1106          // Immediately add standard addresses without IDN.
1107          return call_user_func_array([$this, 'addAnAddress'], $params);
1108      }
1109  
1110      /**
1111       * Add an address to one of the recipient arrays or to the ReplyTo array.
1112       * Addresses that have been added already return false, but do not throw exceptions.
1113       *
1114       * @param string $kind    One of 'to', 'cc', 'bcc', or 'ReplyTo'
1115       * @param string $address The email address to send, resp. to reply to
1116       * @param string $name
1117       *
1118       * @throws Exception
1119       *
1120       * @return bool true on success, false if address already used or invalid in some way
1121       */
1122      protected function addAnAddress($kind, $address, $name = '')
1123      {
1124          if (!in_array($kind, ['to', 'cc', 'bcc', 'Reply-To'])) {
1125              $error_message = sprintf(
1126                  '%s: %s',
1127                  $this->lang('Invalid recipient kind'),
1128                  $kind
1129              );
1130              $this->setError($error_message);
1131              $this->edebug($error_message);
1132              if ($this->exceptions) {
1133                  throw new Exception($error_message);
1134              }
1135  
1136              return false;
1137          }
1138          if (!static::validateAddress($address)) {
1139              $error_message = sprintf(
1140                  '%s (%s): %s',
1141                  $this->lang('invalid_address'),
1142                  $kind,
1143                  $address
1144              );
1145              $this->setError($error_message);
1146              $this->edebug($error_message);
1147              if ($this->exceptions) {
1148                  throw new Exception($error_message);
1149              }
1150  
1151              return false;
1152          }
1153          if ('Reply-To' !== $kind) {
1154              if (!array_key_exists(strtolower($address), $this->all_recipients)) {
1155                  $this->{$kind}[] = [$address, $name];
1156                  $this->all_recipients[strtolower($address)] = true;
1157  
1158                  return true;
1159              }
1160          } elseif (!array_key_exists(strtolower($address), $this->ReplyTo)) {
1161              $this->ReplyTo[strtolower($address)] = [$address, $name];
1162  
1163              return true;
1164          }
1165  
1166          return false;
1167      }
1168  
1169      /**
1170       * Parse and validate a string containing one or more RFC822-style comma-separated email addresses
1171       * of the form "display name <address>" into an array of name/address pairs.
1172       * Uses the imap_rfc822_parse_adrlist function if the IMAP extension is available.
1173       * Note that quotes in the name part are removed.
1174       *
1175       * @see http://www.andrew.cmu.edu/user/agreen1/testing/mrbs/web/Mail/RFC822.php A more careful implementation
1176       *
1177       * @param string $addrstr The address list string
1178       * @param bool   $useimap Whether to use the IMAP extension to parse the list
1179       *
1180       * @return array
1181       */
1182      public static function parseAddresses($addrstr, $useimap = true)
1183      {
1184          $addresses = [];
1185          if ($useimap && function_exists('imap_rfc822_parse_adrlist')) {
1186              //Use this built-in parser if it's available
1187              $list = imap_rfc822_parse_adrlist($addrstr, '');
1188              foreach ($list as $address) {
1189                  if (
1190                      ('.SYNTAX-ERROR.' !== $address->host) && static::validateAddress(
1191                          $address->mailbox . '@' . $address->host
1192                      )
1193                  ) {
1194                      $addresses[] = [
1195                          'name' => (property_exists($address, 'personal') ? $address->personal : ''),
1196                          'address' => $address->mailbox . '@' . $address->host,
1197                      ];
1198                  }
1199              }
1200          } else {
1201              //Use this simpler parser
1202              $list = explode(',', $addrstr);
1203              foreach ($list as $address) {
1204                  $address = trim($address);
1205                  //Is there a separate name part?
1206                  if (strpos($address, '<') === false) {
1207                      //No separate name, just use the whole thing
1208                      if (static::validateAddress($address)) {
1209                          $addresses[] = [
1210                              'name' => '',
1211                              'address' => $address,
1212                          ];
1213                      }
1214                  } else {
1215                      list($name, $email) = explode('<', $address);
1216                      $email = trim(str_replace('>', '', $email));
1217                      if (static::validateAddress($email)) {
1218                          $addresses[] = [
1219                              'name' => trim(str_replace(['"', "'"], '', $name)),
1220                              'address' => $email,
1221                          ];
1222                      }
1223                  }
1224              }
1225          }
1226  
1227          return $addresses;
1228      }
1229  
1230      /**
1231       * Set the From and FromName properties.
1232       *
1233       * @param string $address
1234       * @param string $name
1235       * @param bool   $auto    Whether to also set the Sender address, defaults to true
1236       *
1237       * @throws Exception
1238       *
1239       * @return bool
1240       */
1241      public function setFrom($address, $name = '', $auto = true)
1242      {
1243          $address = trim($address);
1244          $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim
1245          // Don't validate now addresses with IDN. Will be done in send().
1246          $pos = strrpos($address, '@');
1247          if (
1248              (false === $pos)
1249              || ((!$this->has8bitChars(substr($address, ++$pos)) || !static::idnSupported())
1250              && !static::validateAddress($address))
1251          ) {
1252              $error_message = sprintf(
1253                  '%s (From): %s',
1254                  $this->lang('invalid_address'),
1255                  $address
1256              );
1257              $this->setError($error_message);
1258              $this->edebug($error_message);
1259              if ($this->exceptions) {
1260                  throw new Exception($error_message);
1261              }
1262  
1263              return false;
1264          }
1265          $this->From = $address;
1266          $this->FromName = $name;
1267          if ($auto && empty($this->Sender)) {
1268              $this->Sender = $address;
1269          }
1270  
1271          return true;
1272      }
1273  
1274      /**
1275       * Return the Message-ID header of the last email.
1276       * Technically this is the value from the last time the headers were created,
1277       * but it's also the message ID of the last sent message except in
1278       * pathological cases.
1279       *
1280       * @return string
1281       */
1282      public function getLastMessageID()
1283      {
1284          return $this->lastMessageID;
1285      }
1286  
1287      /**
1288       * Check that a string looks like an email address.
1289       * Validation patterns supported:
1290       * * `auto` Pick best pattern automatically;
1291       * * `pcre8` Use the squiloople.com pattern, requires PCRE > 8.0;
1292       * * `pcre` Use old PCRE implementation;
1293       * * `php` Use PHP built-in FILTER_VALIDATE_EMAIL;
1294       * * `html5` Use the pattern given by the HTML5 spec for 'email' type form input elements.
1295       * * `noregex` Don't use a regex: super fast, really dumb.
1296       * Alternatively you may pass in a callable to inject your own validator, for example:
1297       *
1298       * ```php
1299       * PHPMailer::validateAddress('user@example.com', function($address) {
1300       *     return (strpos($address, '@') !== false);
1301       * });
1302       * ```
1303       *
1304       * You can also set the PHPMailer::$validator static to a callable, allowing built-in methods to use your validator.
1305       *
1306       * @param string          $address       The email address to check
1307       * @param string|callable $patternselect Which pattern to use
1308       *
1309       * @return bool
1310       */
1311      public static function validateAddress($address, $patternselect = null)
1312      {
1313          if (null === $patternselect) {
1314              $patternselect = static::$validator;
1315          }
1316          if (is_callable($patternselect)) {
1317              return call_user_func($patternselect, $address);
1318          }
1319          //Reject line breaks in addresses; it's valid RFC5322, but not RFC5321
1320          if (strpos($address, "\n") !== false || strpos($address, "\r") !== false) {
1321              return false;
1322          }
1323          switch ($patternselect) {
1324              case 'pcre': //Kept for BC
1325              case 'pcre8':
1326                  /*
1327                   * A more complex and more permissive version of the RFC5322 regex on which FILTER_VALIDATE_EMAIL
1328                   * is based.
1329                   * In addition to the addresses allowed by filter_var, also permits:
1330                   *  * dotless domains: `a@b`
1331                   *  * comments: `1234 @ local(blah) .machine .example`
1332                   *  * quoted elements: `'"test blah"@example.org'`
1333                   *  * numeric TLDs: `a@b.123`
1334                   *  * unbracketed IPv4 literals: `a@192.168.0.1`
1335                   *  * IPv6 literals: 'first.last@[IPv6:a1::]'
1336                   * Not all of these will necessarily work for sending!
1337                   *
1338                   * @see       http://squiloople.com/2009/12/20/email-address-validation/
1339                   * @copyright 2009-2010 Michael Rushton
1340                   * Feel free to use and redistribute this code. But please keep this copyright notice.
1341                   */
1342                  return (bool) preg_match(
1343                      '/^(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){255,})(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){65,}@)' .
1344                      '((?>(?>(?>((?>(?>(?>\x0D\x0A)?[\t ])+|(?>[\t ]*\x0D\x0A)?[\t ]+)?)(\((?>(?2)' .
1345                      '(?>[\x01-\x08\x0B\x0C\x0E-\'*-\[\]-\x7F]|\\\[\x00-\x7F]|(?3)))*(?2)\)))+(?2))|(?2))?)' .
1346                      '([!#-\'*+\/-9=?^-~-]+|"(?>(?2)(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\x7F]))*' .
1347                      '(?2)")(?>(?1)\.(?1)(?4))*(?1)@(?!(?1)[a-z0-9-]{64,})(?1)(?>([a-z0-9](?>[a-z0-9-]*[a-z0-9])?)' .
1348                      '(?>(?1)\.(?!(?1)[a-z0-9-]{64,})(?1)(?5)){0,126}|\[(?:(?>IPv6:(?>([a-f0-9]{1,4})(?>:(?6)){7}' .
1349                      '|(?!(?:.*[a-f0-9][:\]]){8,})((?6)(?>:(?6)){0,6})?::(?7)?))|(?>(?>IPv6:(?>(?6)(?>:(?6)){5}:' .
1350                      '|(?!(?:.*[a-f0-9]:){6,})(?8)?::(?>((?6)(?>:(?6)){0,4}):)?))?(25[0-5]|2[0-4][0-9]|1[0-9]{2}' .
1351                      '|[1-9]?[0-9])(?>\.(?9)){3}))\])(?1)$/isD',
1352                      $address
1353                  );
1354              case 'html5':
1355                  /*
1356                   * This is the pattern used in the HTML5 spec for validation of 'email' type form input elements.
1357                   *
1358                   * @see https://html.spec.whatwg.org/#e-mail-state-(type=email)
1359                   */
1360                  return (bool) preg_match(
1361                      '/^[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}' .
1362                      '[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/sD',
1363                      $address
1364                  );
1365              case 'php':
1366              default:
1367                  return filter_var($address, FILTER_VALIDATE_EMAIL) !== false;
1368          }
1369      }
1370  
1371      /**
1372       * Tells whether IDNs (Internationalized Domain Names) are supported or not. This requires the
1373       * `intl` and `mbstring` PHP extensions.
1374       *
1375       * @return bool `true` if required functions for IDN support are present
1376       */
1377      public static function idnSupported()
1378      {
1379          return function_exists('idn_to_ascii') && function_exists('mb_convert_encoding');
1380      }
1381  
1382      /**
1383       * Converts IDN in given email address to its ASCII form, also known as punycode, if possible.
1384       * Important: Address must be passed in same encoding as currently set in PHPMailer::$CharSet.
1385       * This function silently returns unmodified address if:
1386       * - No conversion is necessary (i.e. domain name is not an IDN, or is already in ASCII form)
1387       * - Conversion to punycode is impossible (e.g. required PHP functions are not available)
1388       *   or fails for any reason (e.g. domain contains characters not allowed in an IDN).
1389       *
1390       * @see PHPMailer::$CharSet
1391       *
1392       * @param string $address The email address to convert
1393       *
1394       * @return string The encoded address in ASCII form
1395       */
1396      public function punyencodeAddress($address)
1397      {
1398          // Verify we have required functions, CharSet, and at-sign.
1399          $pos = strrpos($address, '@');
1400          if (
1401              !empty($this->CharSet) &&
1402              false !== $pos &&
1403              static::idnSupported()
1404          ) {
1405              $domain = substr($address, ++$pos);
1406              // Verify CharSet string is a valid one, and domain properly encoded in this CharSet.
1407              if ($this->has8bitChars($domain) && @mb_check_encoding($domain, $this->CharSet)) {
1408                  $domain = mb_convert_encoding($domain, 'UTF-8', $this->CharSet);
1409                  //Ignore IDE complaints about this line - method signature changed in PHP 5.4
1410                  $errorcode = 0;
1411                  if (defined('INTL_IDNA_VARIANT_UTS46')) {
1412                      $punycode = idn_to_ascii($domain, $errorcode, INTL_IDNA_VARIANT_UTS46);
1413                  } elseif (defined('INTL_IDNA_VARIANT_2003')) {
1414                      // phpcs:ignore PHPCompatibility.Constants.RemovedConstants.intl_idna_variant_2003Deprecated
1415                      $punycode = idn_to_ascii($domain, $errorcode, INTL_IDNA_VARIANT_2003);
1416                  } else {
1417                      // phpcs:ignore PHPCompatibility.ParameterValues.NewIDNVariantDefault.NotSet
1418                      $punycode = idn_to_ascii($domain, $errorcode);
1419                  }
1420                  if (false !== $punycode) {
1421                      return substr($address, 0, $pos) . $punycode;
1422                  }
1423              }
1424          }
1425  
1426          return $address;
1427      }
1428  
1429      /**
1430       * Create a message and send it.
1431       * Uses the sending method specified by $Mailer.
1432       *
1433       * @throws Exception
1434       *
1435       * @return bool false on error - See the ErrorInfo property for details of the error
1436       */
1437      public function send()
1438      {
1439          try {
1440              if (!$this->preSend()) {
1441                  return false;
1442              }
1443  
1444              return $this->postSend();
1445          } catch (Exception $exc) {
1446              $this->mailHeader = '';
1447              $this->setError($exc->getMessage());
1448              if ($this->exceptions) {
1449                  throw $exc;
1450              }
1451  
1452              return false;
1453          }
1454      }
1455  
1456      /**
1457       * Prepare a message for sending.
1458       *
1459       * @throws Exception
1460       *
1461       * @return bool
1462       */
1463      public function preSend()
1464      {
1465          if (
1466              'smtp' === $this->Mailer
1467              || ('mail' === $this->Mailer && (PHP_VERSION_ID >= 80000 || stripos(PHP_OS, 'WIN') === 0))
1468          ) {
1469              //SMTP mandates RFC-compliant line endings
1470              //and it's also used with mail() on Windows
1471              static::setLE(self::CRLF);
1472          } else {
1473              //Maintain backward compatibility with legacy Linux command line mailers
1474              static::setLE(PHP_EOL);
1475          }
1476          //Check for buggy PHP versions that add a header with an incorrect line break
1477          if (
1478              'mail' === $this->Mailer
1479              && ((PHP_VERSION_ID >= 70000 && PHP_VERSION_ID < 70017)
1480                  || (PHP_VERSION_ID >= 70100 && PHP_VERSION_ID < 70103))
1481              && ini_get('mail.add_x_header') === '1'
1482              && stripos(PHP_OS, 'WIN') === 0
1483          ) {
1484              trigger_error(
1485                  'Your version of PHP is affected by a bug that may result in corrupted messages.' .
1486                  ' To fix it, switch to sending using SMTP, disable the mail.add_x_header option in' .
1487                  ' your php.ini, switch to MacOS or Linux, or upgrade your PHP to version 7.0.17+ or 7.1.3+.',
1488                  E_USER_WARNING
1489              );
1490          }
1491  
1492          try {
1493              $this->error_count = 0; // Reset errors
1494              $this->mailHeader = '';
1495  
1496              // Dequeue recipient and Reply-To addresses with IDN
1497              foreach (array_merge($this->RecipientsQueue, $this->ReplyToQueue) as $params) {
1498                  $params[1] = $this->punyencodeAddress($params[1]);
1499                  call_user_func_array([$this, 'addAnAddress'], $params);
1500              }
1501              if (count($this->to) + count($this->cc) + count($this->bcc) < 1) {
1502                  throw new Exception($this->lang('provide_address'), self::STOP_CRITICAL);
1503              }
1504  
1505              // Validate From, Sender, and ConfirmReadingTo addresses
1506              foreach (['From', 'Sender', 'ConfirmReadingTo'] as $address_kind) {
1507                  $this->$address_kind = trim($this->$address_kind);
1508                  if (empty($this->$address_kind)) {
1509                      continue;
1510                  }
1511                  $this->$address_kind = $this->punyencodeAddress($this->$address_kind);
1512                  if (!static::validateAddress($this->$address_kind)) {
1513                      $error_message = sprintf(
1514                          '%s (%s): %s',
1515                          $this->lang('invalid_address'),
1516                          $address_kind,
1517                          $this->$address_kind
1518                      );
1519                      $this->setError($error_message);
1520                      $this->edebug($error_message);
1521                      if ($this->exceptions) {
1522                          throw new Exception($error_message);
1523                      }
1524  
1525                      return false;
1526                  }
1527              }
1528  
1529              // Set whether the message is multipart/alternative
1530              if ($this->alternativeExists()) {
1531                  $this->ContentType = static::CONTENT_TYPE_MULTIPART_ALTERNATIVE;
1532              }
1533  
1534              $this->setMessageType();
1535              // Refuse to send an empty message unless we are specifically allowing it
1536              if (!$this->AllowEmpty && empty($this->Body)) {
1537                  throw new Exception($this->lang('empty_message'), self::STOP_CRITICAL);
1538              }
1539  
1540              //Trim subject consistently
1541              $this->Subject = trim($this->Subject);
1542              // Create body before headers in case body makes changes to headers (e.g. altering transfer encoding)
1543              $this->MIMEHeader = '';
1544              $this->MIMEBody = $this->createBody();
1545              // createBody may have added some headers, so retain them
1546              $tempheaders = $this->MIMEHeader;
1547              $this->MIMEHeader = $this->createHeader();
1548              $this->MIMEHeader .= $tempheaders;
1549  
1550              // To capture the complete message when using mail(), create
1551              // an extra header list which createHeader() doesn't fold in
1552              if ('mail' === $this->Mailer) {
1553                  if (count($this->to) > 0) {
1554                      $this->mailHeader .= $this->addrAppend('To', $this->to);
1555                  } else {
1556                      $this->mailHeader .= $this->headerLine('To', 'undisclosed-recipients:;');
1557                  }
1558                  $this->mailHeader .= $this->headerLine(
1559                      'Subject',
1560                      $this->encodeHeader($this->secureHeader($this->Subject))
1561                  );
1562              }
1563  
1564              // Sign with DKIM if enabled
1565              if (
1566                  !empty($this->DKIM_domain)
1567                  && !empty($this->DKIM_selector)
1568                  && (!empty($this->DKIM_private_string)
1569                      || (!empty($this->DKIM_private)
1570                          && static::isPermittedPath($this->DKIM_private)
1571                          && file_exists($this->DKIM_private)
1572                      )
1573                  )
1574              ) {
1575                  $header_dkim = $this->DKIM_Add(
1576                      $this->MIMEHeader . $this->mailHeader,
1577                      $this->encodeHeader($this->secureHeader($this->Subject)),
1578                      $this->MIMEBody
1579                  );
1580                  $this->MIMEHeader = static::stripTrailingWSP($this->MIMEHeader) . static::$LE .
1581                      static::normalizeBreaks($header_dkim) . static::$LE;
1582              }
1583  
1584              return true;
1585          } catch (Exception $exc) {
1586              $this->setError($exc->getMessage());
1587              if ($this->exceptions) {
1588                  throw $exc;
1589              }
1590  
1591              return false;
1592          }
1593      }
1594  
1595      /**
1596       * Actually send a message via the selected mechanism.
1597       *
1598       * @throws Exception
1599       *
1600       * @return bool
1601       */
1602      public function postSend()
1603      {
1604          try {
1605              // Choose the mailer and send through it
1606              switch ($this->Mailer) {
1607                  case 'sendmail':
1608                  case 'qmail':
1609                      return $this->sendmailSend($this->MIMEHeader, $this->MIMEBody);
1610                  case 'smtp':
1611                      return $this->smtpSend($this->MIMEHeader, $this->MIMEBody);
1612                  case 'mail':
1613                      return $this->mailSend($this->MIMEHeader, $this->MIMEBody);
1614                  default:
1615                      $sendMethod = $this->Mailer . 'Send';
1616                      if (method_exists($this, $sendMethod)) {
1617                          return $this->$sendMethod($this->MIMEHeader, $this->MIMEBody);
1618                      }
1619  
1620                      return $this->mailSend($this->MIMEHeader, $this->MIMEBody);
1621              }
1622          } catch (Exception $exc) {
1623              if ($this->Mailer === 'smtp' && $this->SMTPKeepAlive == true) {
1624                  $this->smtp->reset();
1625              }
1626              $this->setError($exc->getMessage());
1627              $this->edebug($exc->getMessage());
1628              if ($this->exceptions) {
1629                  throw $exc;
1630              }
1631          }
1632  
1633          return false;
1634      }
1635  
1636      /**
1637       * Send mail using the $Sendmail program.
1638       *
1639       * @see PHPMailer::$Sendmail
1640       *
1641       * @param string $header The message headers
1642       * @param string $body   The message body
1643       *
1644       * @throws Exception
1645       *
1646       * @return bool
1647       */
1648      protected function sendmailSend($header, $body)
1649      {
1650          $header = static::stripTrailingWSP($header) . static::$LE . static::$LE;
1651  
1652          // CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped.
1653          if (!empty($this->Sender) && self::isShellSafe($this->Sender)) {
1654              if ('qmail' === $this->Mailer) {
1655                  $sendmailFmt = '%s -f%s';
1656              } else {
1657                  $sendmailFmt = '%s -oi -f%s -t';
1658              }
1659          } elseif ('qmail' === $this->Mailer) {
1660              $sendmailFmt = '%s';
1661          } else {
1662              $sendmailFmt = '%s -oi -t';
1663          }
1664  
1665          $sendmail = sprintf($sendmailFmt, escapeshellcmd($this->Sendmail), $this->Sender);
1666  
1667          if ($this->SingleTo) {
1668              foreach ($this->SingleToArray as $toAddr) {
1669                  $mail = @popen($sendmail, 'w');
1670                  if (!$mail) {
1671                      throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
1672                  }
1673                  fwrite($mail, 'To: ' . $toAddr . "\n");
1674                  fwrite($mail, $header);
1675                  fwrite($mail, $body);
1676                  $result = pclose($mail);
1677                  $this->doCallback(
1678                      ($result === 0),
1679                      [$toAddr],
1680                      $this->cc,
1681                      $this->bcc,
1682                      $this->Subject,
1683                      $body,
1684                      $this->From,
1685                      []
1686                  );
1687                  if (0 !== $result) {
1688                      throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
1689                  }
1690              }
1691          } else {
1692              $mail = @popen($sendmail, 'w');
1693              if (!$mail) {
1694                  throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
1695              }
1696              fwrite($mail, $header);
1697              fwrite($mail, $body);
1698              $result = pclose($mail);
1699              $this->doCallback(
1700                  ($result === 0),
1701                  $this->to,
1702                  $this->cc,
1703                  $this->bcc,
1704                  $this->Subject,
1705                  $body,
1706                  $this->From,
1707                  []
1708              );
1709              if (0 !== $result) {
1710                  throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
1711              }
1712          }
1713  
1714          return true;
1715      }
1716  
1717      /**
1718       * Fix CVE-2016-10033 and CVE-2016-10045 by disallowing potentially unsafe shell characters.
1719       * Note that escapeshellarg and escapeshellcmd are inadequate for our purposes, especially on Windows.
1720       *
1721       * @see https://github.com/PHPMailer/PHPMailer/issues/924 CVE-2016-10045 bug report
1722       *
1723       * @param string $string The string to be validated
1724       *
1725       * @return bool
1726       */
1727      protected static function isShellSafe($string)
1728      {
1729          // Future-proof
1730          if (
1731              escapeshellcmd($string) !== $string
1732              || !in_array(escapeshellarg($string), ["'$string'", "\"$string\""])
1733          ) {
1734              return false;
1735          }
1736  
1737          $length = strlen($string);
1738  
1739          for ($i = 0; $i < $length; ++$i) {
1740              $c = $string[$i];
1741  
1742              // All other characters have a special meaning in at least one common shell, including = and +.
1743              // Full stop (.) has a special meaning in cmd.exe, but its impact should be negligible here.
1744              // Note that this does permit non-Latin alphanumeric characters based on the current locale.
1745              if (!ctype_alnum($c) && strpos('@_-.', $c) === false) {
1746                  return false;
1747              }
1748          }
1749  
1750          return true;
1751      }
1752  
1753      /**
1754       * Check whether a file path is of a permitted type.
1755       * Used to reject URLs and phar files from functions that access local file paths,
1756       * such as addAttachment.
1757       *
1758       * @param string $path A relative or absolute path to a file
1759       *
1760       * @return bool
1761       */
1762      protected static function isPermittedPath($path)
1763      {
1764          return !preg_match('#^[a-z]+://#i', $path);
1765      }
1766  
1767      /**
1768       * Check whether a file path is safe, accessible, and readable.
1769       *
1770       * @param string $path A relative or absolute path to a file
1771       *
1772       * @return bool
1773       */
1774      protected static function fileIsAccessible($path)
1775      {
1776          $readable = file_exists($path);
1777          //If not a UNC path (expected to start with \\), check read permission, see #2069
1778          if (strpos($path, '\\\\') !== 0) {
1779              $readable = $readable && is_readable($path);
1780          }
1781          return static::isPermittedPath($path) && $readable;
1782      }
1783  
1784      /**
1785       * Send mail using the PHP mail() function.
1786       *
1787       * @see http://www.php.net/manual/en/book.mail.php
1788       *
1789       * @param string $header The message headers
1790       * @param string $body   The message body
1791       *
1792       * @throws Exception
1793       *
1794       * @return bool
1795       */
1796      protected function mailSend($header, $body)
1797      {
1798          $header = static::stripTrailingWSP($header) . static::$LE . static::$LE;
1799  
1800          $toArr = [];
1801          foreach ($this->to as $toaddr) {
1802              $toArr[] = $this->addrFormat($toaddr);
1803          }
1804          $to = implode(', ', $toArr);
1805  
1806          $params = null;
1807          //This sets the SMTP envelope sender which gets turned into a return-path header by the receiver
1808          //A space after `-f` is optional, but there is a long history of its presence
1809          //causing problems, so we don't use one
1810          //Exim docs: http://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_exim_command_line.html
1811          //Sendmail docs: http://www.sendmail.org/~ca/email/man/sendmail.html
1812          //Qmail docs: http://www.qmail.org/man/man8/qmail-inject.html
1813          //Example problem: https://www.drupal.org/node/1057954
1814          // CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped.
1815          if (!empty($this->Sender) && static::validateAddress($this->Sender) && self::isShellSafe($this->Sender)) {
1816              $params = sprintf('-f%s', $this->Sender);
1817          }
1818          if (!empty($this->Sender) && static::validateAddress($this->Sender)) {
1819              $old_from = ini_get('sendmail_from');
1820              ini_set('sendmail_from', $this->Sender);
1821          }
1822          $result = false;
1823          if ($this->SingleTo && count($toArr) > 1) {
1824              foreach ($toArr as $toAddr) {
1825                  $result = $this->mailPassthru($toAddr, $this->Subject, $body, $header, $params);
1826                  $this->doCallback($result, [$toAddr], $this->cc, $this->bcc, $this->Subject, $body, $this->From, []);
1827              }
1828          } else {
1829              $result = $this->mailPassthru($to, $this->Subject, $body, $header, $params);
1830              $this->doCallback($result, $this->to, $this->cc, $this->bcc, $this->Subject, $body, $this->From, []);
1831          }
1832          if (isset($old_from)) {
1833              ini_set('sendmail_from', $old_from);
1834          }
1835          if (!$result) {
1836              throw new Exception($this->lang('instantiate'), self::STOP_CRITICAL);
1837          }
1838  
1839          return true;
1840      }
1841  
1842      /**
1843       * Get an instance to use for SMTP operations.
1844       * Override this function to load your own SMTP implementation,
1845       * or set one with setSMTPInstance.
1846       *
1847       * @return SMTP
1848       */
1849      public function getSMTPInstance()
1850      {
1851          if (!is_object($this->smtp)) {
1852              $this->smtp = new SMTP();
1853          }
1854  
1855          return $this->smtp;
1856      }
1857  
1858      /**
1859       * Provide an instance to use for SMTP operations.
1860       *
1861       * @return SMTP
1862       */
1863      public function setSMTPInstance(SMTP $smtp)
1864      {
1865          $this->smtp = $smtp;
1866  
1867          return $this->smtp;
1868      }
1869  
1870      /**
1871       * Send mail via SMTP.
1872       * Returns false if there is a bad MAIL FROM, RCPT, or DATA input.
1873       *
1874       * @see PHPMailer::setSMTPInstance() to use a different class.
1875       *
1876       * @uses \PHPMailer\PHPMailer\SMTP
1877       *
1878       * @param string $header The message headers
1879       * @param string $body   The message body
1880       *
1881       * @throws Exception
1882       *
1883       * @return bool
1884       */
1885      protected function smtpSend($header, $body)
1886      {
1887          $header = static::stripTrailingWSP($header) . static::$LE . static::$LE;
1888          $bad_rcpt = [];
1889          if (!$this->smtpConnect($this->SMTPOptions)) {
1890              throw new Exception($this->lang('smtp_connect_failed'), self::STOP_CRITICAL);
1891          }
1892          //Sender already validated in preSend()
1893          if ('' === $this->Sender) {
1894              $smtp_from = $this->From;
1895          } else {
1896              $smtp_from = $this->Sender;
1897          }
1898          if (!$this->smtp->mail($smtp_from)) {
1899              $this->setError($this->lang('from_failed') . $smtp_from . ' : ' . implode(',', $this->smtp->getError()));
1900              throw new Exception($this->ErrorInfo, self::STOP_CRITICAL);
1901          }
1902  
1903          $callbacks = [];
1904          // Attempt to send to all recipients
1905          foreach ([$this->to, $this->cc, $this->bcc] as $togroup) {
1906              foreach ($togroup as $to) {
1907                  if (!$this->smtp->recipient($to[0], $this->dsn)) {
1908                      $error = $this->smtp->getError();
1909                      $bad_rcpt[] = ['to' => $to[0], 'error' => $error['detail']];
1910                      $isSent = false;
1911                  } else {
1912                      $isSent = true;
1913                  }
1914  
1915                  $callbacks[] = ['issent' => $isSent, 'to' => $to[0]];
1916              }
1917          }
1918  
1919          // Only send the DATA command if we have viable recipients
1920          if ((count($this->all_recipients) > count($bad_rcpt)) && !$this->smtp->data($header . $body)) {
1921              throw new Exception($this->lang('data_not_accepted'), self::STOP_CRITICAL);
1922          }
1923  
1924          $smtp_transaction_id = $this->smtp->getLastTransactionID();
1925  
1926          if ($this->SMTPKeepAlive) {
1927              $this->smtp->reset();
1928          } else {
1929              $this->smtp->quit();
1930              $this->smtp->close();
1931          }
1932  
1933          foreach ($callbacks as $cb) {
1934              $this->doCallback(
1935                  $cb['issent'],
1936                  [$cb['to']],
1937                  [],
1938                  [],
1939                  $this->Subject,
1940                  $body,
1941                  $this->From,
1942                  ['smtp_transaction_id' => $smtp_transaction_id]
1943              );
1944          }
1945  
1946          //Create error message for any bad addresses
1947          if (count($bad_rcpt) > 0) {
1948              $errstr = '';
1949              foreach ($bad_rcpt as $bad) {
1950                  $errstr .= $bad['to'] . ': ' . $bad['error'];
1951              }
1952              throw new Exception($this->lang('recipients_failed') . $errstr, self::STOP_CONTINUE);
1953          }
1954  
1955          return true;
1956      }
1957  
1958      /**
1959       * Initiate a connection to an SMTP server.
1960       * Returns false if the operation failed.
1961       *
1962       * @param array $options An array of options compatible with stream_context_create()
1963       *
1964       * @throws Exception
1965       *
1966       * @uses \PHPMailer\PHPMailer\SMTP
1967       *
1968       * @return bool
1969       */
1970      public function smtpConnect($options = null)
1971      {
1972          if (null === $this->smtp) {
1973              $this->smtp = $this->getSMTPInstance();
1974          }
1975  
1976          //If no options are provided, use whatever is set in the instance
1977          if (null === $options) {
1978              $options = $this->SMTPOptions;
1979          }
1980  
1981          // Already connected?
1982          if ($this->smtp->connected()) {
1983              return true;
1984          }
1985  
1986          $this->smtp->setTimeout($this->Timeout);
1987          $this->smtp->setDebugLevel($this->SMTPDebug);
1988          $this->smtp->setDebugOutput($this->Debugoutput);
1989          $this->smtp->setVerp($this->do_verp);
1990          $hosts = explode(';', $this->Host);
1991          $lastexception = null;
1992  
1993          foreach ($hosts as $hostentry) {
1994              $hostinfo = [];
1995              if (
1996                  !preg_match(
1997                      '/^(?:(ssl|tls):\/\/)?(.+?)(?::(\d+))?$/',
1998                      trim($hostentry),
1999                      $hostinfo
2000                  )
2001              ) {
2002                  $this->edebug($this->lang('invalid_hostentry') . ' ' . trim($hostentry));
2003                  // Not a valid host entry
2004                  continue;
2005              }
2006              // $hostinfo[1]: optional ssl or tls prefix
2007              // $hostinfo[2]: the hostname
2008              // $hostinfo[3]: optional port number
2009              // The host string prefix can temporarily override the current setting for SMTPSecure
2010              // If it's not specified, the default value is used
2011  
2012              //Check the host name is a valid name or IP address before trying to use it
2013              if (!static::isValidHost($hostinfo[2])) {
2014                  $this->edebug($this->lang('invalid_host') . ' ' . $hostinfo[2]);
2015                  continue;
2016              }
2017              $prefix = '';
2018              $secure = $this->SMTPSecure;
2019              $tls = (static::ENCRYPTION_STARTTLS === $this->SMTPSecure);
2020              if ('ssl' === $hostinfo[1] || ('' === $hostinfo[1] && static::ENCRYPTION_SMTPS === $this->SMTPSecure)) {
2021                  $prefix = 'ssl://';
2022                  $tls = false; // Can't have SSL and TLS at the same time
2023                  $secure = static::ENCRYPTION_SMTPS;
2024              } elseif ('tls' === $hostinfo[1]) {
2025                  $tls = true;
2026                  // tls doesn't use a prefix
2027                  $secure = static::ENCRYPTION_STARTTLS;
2028              }
2029              //Do we need the OpenSSL extension?
2030              $sslext = defined('OPENSSL_ALGO_SHA256');
2031              if (static::ENCRYPTION_STARTTLS === $secure || static::ENCRYPTION_SMTPS === $secure) {
2032                  //Check for an OpenSSL constant rather than using extension_loaded, which is sometimes disabled
2033                  if (!$sslext) {
2034                      throw new Exception($this->lang('extension_missing') . 'openssl', self::STOP_CRITICAL);
2035                  }
2036              }
2037              $host = $hostinfo[2];
2038              $port = $this->Port;
2039              if (
2040                  array_key_exists(3, $hostinfo) &&
2041                  is_numeric($hostinfo[3]) &&
2042                  $hostinfo[3] > 0 &&
2043                  $hostinfo[3] < 65536
2044              ) {
2045                  $port = (int) $hostinfo[3];
2046              }
2047              if ($this->smtp->connect($prefix . $host, $port, $this->Timeout, $options)) {
2048                  try {
2049                      if ($this->Helo) {
2050                          $hello = $this->Helo;
2051                      } else {
2052                          $hello = $this->serverHostname();
2053                      }
2054                      $this->smtp->hello($hello);
2055                      //Automatically enable TLS encryption if:
2056                      // * it's not disabled
2057                      // * we have openssl extension
2058                      // * we are not already using SSL
2059                      // * the server offers STARTTLS
2060                      if ($this->SMTPAutoTLS && $sslext && 'ssl' !== $secure && $this->smtp->getServerExt('STARTTLS')) {
2061                          $tls = true;
2062                      }
2063                      if ($tls) {
2064                          if (!$this->smtp->startTLS()) {
2065                              throw new Exception($this->lang('connect_host'));
2066                          }
2067                          // We must resend EHLO after TLS negotiation
2068                          $this->smtp->hello($hello);
2069                      }
2070                      if (
2071                          $this->SMTPAuth && !$this->smtp->authenticate(
2072                              $this->Username,
2073                              $this->Password,
2074                              $this->AuthType,
2075                              $this->oauth
2076                          )
2077                      ) {
2078                          throw new Exception($this->lang('authenticate'));
2079                      }
2080  
2081                      return true;
2082                  } catch (Exception $exc) {
2083                      $lastexception = $exc;
2084                      $this->edebug($exc->getMessage());
2085                      // We must have connected, but then failed TLS or Auth, so close connection nicely
2086                      $this->smtp->quit();
2087                  }
2088              }
2089          }
2090          // If we get here, all connection attempts have failed, so close connection hard
2091          $this->smtp->close();
2092          // As we've caught all exceptions, just report whatever the last one was
2093          if ($this->exceptions && null !== $lastexception) {
2094              throw $lastexception;
2095          }
2096  
2097          return false;
2098      }
2099  
2100      /**
2101       * Close the active SMTP session if one exists.
2102       */
2103      public function smtpClose()
2104      {
2105          if ((null !== $this->smtp) && $this->smtp->connected()) {
2106              $this->smtp->quit();
2107              $this->smtp->close();
2108          }
2109      }
2110  
2111      /**
2112       * Set the language for error messages.
2113       * Returns false if it cannot load the language file.
2114       * The default language is English.
2115       *
2116       * @param string $langcode  ISO 639-1 2-character language code (e.g. French is "fr")
2117       * @param string $lang_path Path to the language file directory, with trailing separator (slash)
2118       *
2119       * @return bool
2120       */
2121      public function setLanguage($langcode = 'en', $lang_path = '')
2122      {
2123          // Backwards compatibility for renamed language codes
2124          $renamed_langcodes = [
2125              'br' => 'pt_br',
2126              'cz' => 'cs',
2127              'dk' => 'da',
2128              'no' => 'nb',
2129              'se' => 'sv',
2130              'rs' => 'sr',
2131              'tg' => 'tl',
2132              'am' => 'hy',
2133          ];
2134  
2135          if (array_key_exists($langcode, $renamed_langcodes)) {
2136              $langcode = $renamed_langcodes[$langcode];
2137          }
2138  
2139          // Define full set of translatable strings in English
2140          $PHPMAILER_LANG = [
2141              'authenticate' => 'SMTP Error: Could not authenticate.',
2142              'connect_host' => 'SMTP Error: Could not connect to SMTP host.',
2143              'data_not_accepted' => 'SMTP Error: data not accepted.',
2144              'empty_message' => 'Message body empty',
2145              'encoding' => 'Unknown encoding: ',
2146              'execute' => 'Could not execute: ',
2147              'file_access' => 'Could not access file: ',
2148              'file_open' => 'File Error: Could not open file: ',
2149              'from_failed' => 'The following From address failed: ',
2150              'instantiate' => 'Could not instantiate mail function.',
2151              'invalid_address' => 'Invalid address: ',
2152              'invalid_hostentry' => 'Invalid hostentry: ',
2153              'invalid_host' => 'Invalid host: ',
2154              'mailer_not_supported' => ' mailer is not supported.',
2155              'provide_address' => 'You must provide at least one recipient email address.',
2156              'recipients_failed' => 'SMTP Error: The following recipients failed: ',
2157              'signing' => 'Signing Error: ',
2158              'smtp_connect_failed' => 'SMTP connect() failed.',
2159              'smtp_error' => 'SMTP server error: ',
2160              'variable_set' => 'Cannot set or reset variable: ',
2161              'extension_missing' => 'Extension missing: ',
2162          ];
2163          if (empty($lang_path)) {
2164              // Calculate an absolute path so it can work if CWD is not here
2165              $lang_path = dirname(__DIR__) . DIRECTORY_SEPARATOR . 'language' . DIRECTORY_SEPARATOR;
2166          }
2167          //Validate $langcode
2168          if (!preg_match('/^[a-z]{2}(?:_[a-zA-Z]{2})?$/', $langcode)) {
2169              $langcode = 'en';
2170          }
2171          $foundlang = true;
2172          $lang_file = $lang_path . 'phpmailer.lang-' . $langcode . '.php';
2173          // There is no English translation file
2174          if ('en' !== $langcode) {
2175              // Make sure language file path is readable
2176              if (!static::fileIsAccessible($lang_file)) {
2177                  $foundlang = false;
2178              } else {
2179                  // Overwrite language-specific strings.
2180                  // This way we'll never have missing translation keys.
2181                  $foundlang = include $lang_file;
2182              }
2183          }
2184          $this->language = $PHPMAILER_LANG;
2185  
2186          return (bool) $foundlang; // Returns false if language not found
2187      }
2188  
2189      /**
2190       * Get the array of strings for the current language.
2191       *
2192       * @return array
2193       */
2194      public function getTranslations()
2195      {
2196          return $this->language;
2197      }
2198  
2199      /**
2200       * Create recipient headers.
2201       *
2202       * @param string $type
2203       * @param array  $addr An array of recipients,
2204       *                     where each recipient is a 2-element indexed array with element 0 containing an address
2205       *                     and element 1 containing a name, like:
2206       *                     [['joe@example.com', 'Joe User'], ['zoe@example.com', 'Zoe User']]
2207       *
2208       * @return string
2209       */
2210      public function addrAppend($type, $addr)
2211      {
2212          $addresses = [];
2213          foreach ($addr as $address) {
2214              $addresses[] = $this->addrFormat($address);
2215          }
2216  
2217          return $type . ': ' . implode(', ', $addresses) . static::$LE;
2218      }
2219  
2220      /**
2221       * Format an address for use in a message header.
2222       *
2223       * @param array $addr A 2-element indexed array, element 0 containing an address, element 1 containing a name like
2224       *                    ['joe@example.com', 'Joe User']
2225       *
2226       * @return string
2227       */
2228      public function addrFormat($addr)
2229      {
2230          if (empty($addr[1])) { // No name provided
2231              return $this->secureHeader($addr[0]);
2232          }
2233  
2234          return $this->encodeHeader($this->secureHeader($addr[1]), 'phrase') .
2235              ' <' . $this->secureHeader($addr[0]) . '>';
2236      }
2237  
2238      /**
2239       * Word-wrap message.
2240       * For use with mailers that do not automatically perform wrapping
2241       * and for quoted-printable encoded messages.
2242       * Original written by philippe.
2243       *
2244       * @param string $message The message to wrap
2245       * @param int    $length  The line length to wrap to
2246       * @param bool   $qp_mode Whether to run in Quoted-Printable mode
2247       *
2248       * @return string
2249       */
2250      public function wrapText($message, $length, $qp_mode = false)
2251      {
2252          if ($qp_mode) {
2253              $soft_break = sprintf(' =%s', static::$LE);
2254          } else {
2255              $soft_break = static::$LE;
2256          }
2257          // If utf-8 encoding is used, we will need to make sure we don't
2258          // split multibyte characters when we wrap
2259          $is_utf8 = static::CHARSET_UTF8 === strtolower($this->CharSet);
2260          $lelen = strlen(static::$LE);
2261          $crlflen = strlen(static::$LE);
2262  
2263          $message = static::normalizeBreaks($message);
2264          //Remove a trailing line break
2265          if (substr($message, -$lelen) === static::$LE) {
2266              $message = substr($message, 0, -$lelen);
2267          }
2268  
2269          //Split message into lines
2270          $lines = explode(static::$LE, $message);
2271          //Message will be rebuilt in here
2272          $message = '';
2273          foreach ($lines as $line) {
2274              $words = explode(' ', $line);
2275              $buf = '';
2276              $firstword = true;
2277              foreach ($words as $word) {
2278                  if ($qp_mode && (strlen($word) > $length)) {
2279                      $space_left = $length - strlen($buf) - $crlflen;
2280                      if (!$firstword) {
2281                          if ($space_left > 20) {
2282                              $len = $space_left;
2283                              if ($is_utf8) {
2284                                  $len = $this->utf8CharBoundary($word, $len);
2285                              } elseif ('=' === substr($word, $len - 1, 1)) {
2286                                  --$len;
2287                              } elseif ('=' === substr($word, $len - 2, 1)) {
2288                                  $len -= 2;
2289                              }
2290                              $part = substr($word, 0, $len);
2291                              $word = substr($word, $len);
2292                              $buf .= ' ' . $part;
2293                              $message .= $buf . sprintf('=%s', static::$LE);
2294                          } else {
2295                              $message .= $buf . $soft_break;
2296                          }
2297                          $buf = '';
2298                      }
2299                      while ($word !== '') {
2300                          if ($length <= 0) {
2301                              break;
2302                          }
2303                          $len = $length;
2304                          if ($is_utf8) {
2305                              $len = $this->utf8CharBoundary($word, $len);
2306                          } elseif ('=' === substr($word, $len - 1, 1)) {
2307                              --$len;
2308                          } elseif ('=' === substr($word, $len - 2, 1)) {
2309                              $len -= 2;
2310                          }
2311                          $part = substr($word, 0, $len);
2312                          $word = (string) substr($word, $len);
2313  
2314                          if ($word !== '') {
2315                              $message .= $part . sprintf('=%s', static::$LE);
2316                          } else {
2317                              $buf = $part;
2318                          }
2319                      }
2320                  } else {
2321                      $buf_o = $buf;
2322                      if (!$firstword) {
2323                          $buf .= ' ';
2324                      }
2325                      $buf .= $word;
2326  
2327                      if ('' !== $buf_o && strlen($buf) > $length) {
2328                          $message .= $buf_o . $soft_break;
2329                          $buf = $word;
2330                      }
2331                  }
2332                  $firstword = false;
2333              }
2334              $message .= $buf . static::$LE;
2335          }
2336  
2337          return $message;
2338      }
2339  
2340      /**
2341       * Find the last character boundary prior to $maxLength in a utf-8
2342       * quoted-printable encoded string.
2343       * Original written by Colin Brown.
2344       *
2345       * @param string $encodedText utf-8 QP text
2346       * @param int    $maxLength   Find the last character boundary prior to this length
2347       *
2348       * @return int
2349       */
2350      public function utf8CharBoundary($encodedText, $maxLength)
2351      {
2352          $foundSplitPos = false;
2353          $lookBack = 3;
2354          while (!$foundSplitPos) {
2355              $lastChunk = substr($encodedText, $maxLength - $lookBack, $lookBack);
2356              $encodedCharPos = strpos($lastChunk, '=');
2357              if (false !== $encodedCharPos) {
2358                  // Found start of encoded character byte within $lookBack block.
2359                  // Check the encoded byte value (the 2 chars after the '=')
2360                  $hex = substr($encodedText, $maxLength - $lookBack + $encodedCharPos + 1, 2);
2361                  $dec = hexdec($hex);
2362                  if ($dec < 128) {
2363                      // Single byte character.
2364                      // If the encoded char was found at pos 0, it will fit
2365                      // otherwise reduce maxLength to start of the encoded char
2366                      if ($encodedCharPos > 0) {
2367                          $maxLength -= $lookBack - $encodedCharPos;
2368                      }
2369                      $foundSplitPos = true;
2370                  } elseif ($dec >= 192) {
2371                      // First byte of a multi byte character
2372                      // Reduce maxLength to split at start of character
2373                      $maxLength -= $lookBack - $encodedCharPos;
2374                      $foundSplitPos = true;
2375                  } elseif ($dec < 192) {
2376                      // Middle byte of a multi byte character, look further back
2377                      $lookBack += 3;
2378                  }
2379              } else {
2380                  // No encoded character found
2381                  $foundSplitPos = true;
2382              }
2383          }
2384  
2385          return $maxLength;
2386      }
2387  
2388      /**
2389       * Apply word wrapping to the message body.
2390       * Wraps the message body to the number of chars set in the WordWrap property.
2391       * You should only do this to plain-text bodies as wrapping HTML tags may break them.
2392       * This is called automatically by createBody(), so you don't need to call it yourself.
2393       */
2394      public function setWordWrap()
2395      {
2396          if ($this->WordWrap < 1) {
2397              return;
2398          }
2399  
2400          switch ($this->message_type) {
2401              case 'alt':
2402              case 'alt_inline':
2403              case 'alt_attach':
2404              case 'alt_inline_attach':
2405                  $this->AltBody = $this->wrapText($this->AltBody, $this->WordWrap);
2406                  break;
2407              default:
2408                  $this->Body = $this->wrapText($this->Body, $this->WordWrap);
2409                  break;
2410          }
2411      }
2412  
2413      /**
2414       * Assemble message headers.
2415       *
2416       * @return string The assembled headers
2417       */
2418      public function createHeader()
2419      {
2420          $result = '';
2421  
2422          $result .= $this->headerLine('Date', '' === $this->MessageDate ? self::rfcDate() : $this->MessageDate);
2423  
2424          // The To header is created automatically by mail(), so needs to be omitted here
2425          if ('mail' !== $this->Mailer) {
2426              if ($this->SingleTo) {
2427                  foreach ($this->to as $toaddr) {
2428                      $this->SingleToArray[] = $this->addrFormat($toaddr);
2429                  }
2430              } elseif (count($this->to) > 0) {
2431                  $result .= $this->addrAppend('To', $this->to);
2432              } elseif (count($this->cc) === 0) {
2433                  $result .= $this->headerLine('To', 'undisclosed-recipients:;');
2434              }
2435          }
2436          $result .= $this->addrAppend('From', [[trim($this->From), $this->FromName]]);
2437  
2438          // sendmail and mail() extract Cc from the header before sending
2439          if (count($this->cc) > 0) {
2440              $result .= $this->addrAppend('Cc', $this->cc);
2441          }
2442  
2443          // sendmail and mail() extract Bcc from the header before sending
2444          if (
2445              (
2446                  'sendmail' === $this->Mailer || 'qmail' === $this->Mailer || 'mail' === $this->Mailer
2447              )
2448              && count($this->bcc) > 0
2449          ) {
2450              $result .= $this->addrAppend('Bcc', $this->bcc);
2451          }
2452  
2453          if (count($this->ReplyTo) > 0) {
2454              $result .= $this->addrAppend('Reply-To', $this->ReplyTo);
2455          }
2456  
2457          // mail() sets the subject itself
2458          if ('mail' !== $this->Mailer) {
2459              $result .= $this->headerLine('Subject', $this->encodeHeader($this->secureHeader($this->Subject)));
2460          }
2461  
2462          // Only allow a custom message ID if it conforms to RFC 5322 section 3.6.4
2463          // https://tools.ietf.org/html/rfc5322#section-3.6.4
2464          if ('' !== $this->MessageID && preg_match('/^<.*@.*>$/', $this->MessageID)) {
2465              $this->lastMessageID = $this->MessageID;
2466          } else {
2467              $this->lastMessageID = sprintf('<%s@%s>', $this->uniqueid, $this->serverHostname());
2468          }
2469          $result .= $this->headerLine('Message-ID', $this->lastMessageID);
2470          if (null !== $this->Priority) {
2471              $result .= $this->headerLine('X-Priority', $this->Priority);
2472          }
2473          if ('' === $this->XMailer) {
2474              $result .= $this->headerLine(
2475                  'X-Mailer',
2476                  'PHPMailer ' . self::VERSION . ' (https://github.com/PHPMailer/PHPMailer)'
2477              );
2478          } else {
2479              $myXmailer = trim($this->XMailer);
2480              if ($myXmailer) {
2481                  $result .= $this->headerLine('X-Mailer', $myXmailer);
2482              }
2483          }
2484  
2485          if ('' !== $this->ConfirmReadingTo) {
2486              $result .= $this->headerLine('Disposition-Notification-To', '<' . $this->ConfirmReadingTo . '>');
2487          }
2488  
2489          // Add custom headers
2490          foreach ($this->CustomHeader as $header) {
2491              $result .= $this->headerLine(
2492                  trim($header[0]),
2493                  $this->encodeHeader(trim($header[1]))
2494              );
2495          }
2496          if (!$this->sign_key_file) {
2497              $result .= $this->headerLine('MIME-Version', '1.0');
2498              $result .= $this->getMailMIME();
2499          }
2500  
2501          return $result;
2502      }
2503  
2504      /**
2505       * Get the message MIME type headers.
2506       *
2507       * @return string
2508       */
2509      public function getMailMIME()
2510      {
2511          $result = '';
2512          $ismultipart = true;
2513          switch ($this->message_type) {
2514              case 'inline':
2515                  $result .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_RELATED . ';');
2516                  $result .= $this->textLine(' boundary="' . $this->boundary[1] . '"');
2517                  break;
2518              case 'attach':
2519              case 'inline_attach':
2520              case 'alt_attach':
2521              case 'alt_inline_attach':
2522                  $result .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_MIXED . ';');
2523                  $result .= $this->textLine(' boundary="' . $this->boundary[1] . '"');
2524                  break;
2525              case 'alt':
2526              case 'alt_inline':
2527                  $result .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_ALTERNATIVE . ';');
2528                  $result .= $this->textLine(' boundary="' . $this->boundary[1] . '"');
2529                  break;
2530              default:
2531                  // Catches case 'plain': and case '':
2532                  $result .= $this->textLine('Content-Type: ' . $this->ContentType . '; charset=' . $this->CharSet);
2533                  $ismultipart = false;
2534                  break;
2535          }
2536          // RFC1341 part 5 says 7bit is assumed if not specified
2537          if (static::ENCODING_7BIT !== $this->Encoding) {
2538              // RFC 2045 section 6.4 says multipart MIME parts may only use 7bit, 8bit or binary CTE
2539              if ($ismultipart) {
2540                  if (static::ENCODING_8BIT === $this->Encoding) {
2541                      $result .= $this->headerLine('Content-Transfer-Encoding', static::ENCODING_8BIT);
2542                  }
2543                  // The only remaining alternatives are quoted-printable and base64, which are both 7bit compatible
2544              } else {
2545                  $result .= $this->headerLine('Content-Transfer-Encoding', $this->Encoding);
2546              }
2547          }
2548  
2549          if ('mail' !== $this->Mailer) {
2550  //            $result .= static::$LE;
2551          }
2552  
2553          return $result;
2554      }
2555  
2556      /**
2557       * Returns the whole MIME message.
2558       * Includes complete headers and body.
2559       * Only valid post preSend().
2560       *
2561       * @see PHPMailer::preSend()
2562       *
2563       * @return string
2564       */
2565      public function getSentMIMEMessage()
2566      {
2567          return static::stripTrailingWSP($this->MIMEHeader . $this->mailHeader) .
2568              static::$LE . static::$LE . $this->MIMEBody;
2569      }
2570  
2571      /**
2572       * Create a unique ID to use for boundaries.
2573       *
2574       * @return string
2575       */
2576      protected function generateId()
2577      {
2578          $len = 32; //32 bytes = 256 bits
2579          $bytes = '';
2580          if (function_exists('random_bytes')) {
2581              try {
2582                  $bytes = random_bytes($len);
2583              } catch (\Exception $e) {
2584                  //Do nothing
2585              }
2586          } elseif (function_exists('openssl_random_pseudo_bytes')) {
2587              /** @noinspection CryptographicallySecureRandomnessInspection */
2588              $bytes = openssl_random_pseudo_bytes($len);
2589          }
2590          if ($bytes === '') {
2591              //We failed to produce a proper random string, so make do.
2592              //Use a hash to force the length to the same as the other methods
2593              $bytes = hash('sha256', uniqid((string) mt_rand(), true), true);
2594          }
2595  
2596          //We don't care about messing up base64 format here, just want a random string
2597          return str_replace(['=', '+', '/'], '', base64_encode(hash('sha256', $bytes, true)));
2598      }
2599  
2600      /**
2601       * Assemble the message body.
2602       * Returns an empty string on failure.
2603       *
2604       * @throws Exception
2605       *
2606       * @return string The assembled message body
2607       */
2608      public function createBody()
2609      {
2610          $body = '';
2611          //Create unique IDs and preset boundaries
2612          $this->uniqueid = $this->generateId();
2613          $this->boundary[1] = 'b1_' . $this->uniqueid;
2614          $this->boundary[2] = 'b2_' . $this->uniqueid;
2615          $this->boundary[3] = 'b3_' . $this->uniqueid;
2616  
2617          if ($this->sign_key_file) {
2618              $body .= $this->getMailMIME() . static::$LE;
2619          }
2620  
2621          $this->setWordWrap();
2622  
2623          $bodyEncoding = $this->Encoding;
2624          $bodyCharSet = $this->CharSet;
2625          //Can we do a 7-bit downgrade?
2626          if (static::ENCODING_8BIT === $bodyEncoding && !$this->has8bitChars($this->Body)) {
2627              $bodyEncoding = static::ENCODING_7BIT;
2628              //All ISO 8859, Windows codepage and UTF-8 charsets are ascii compatible up to 7-bit
2629              $bodyCharSet = static::CHARSET_ASCII;
2630          }
2631          //If lines are too long, and we're not already using an encoding that will shorten them,
2632          //change to quoted-printable transfer encoding for the body part only
2633          if (static::ENCODING_BASE64 !== $this->Encoding && static::hasLineLongerThanMax($this->Body)) {
2634              $bodyEncoding = static::ENCODING_QUOTED_PRINTABLE;
2635          }
2636  
2637          $altBodyEncoding = $this->Encoding;
2638          $altBodyCharSet = $this->CharSet;
2639          //Can we do a 7-bit downgrade?
2640          if (static::ENCODING_8BIT === $altBodyEncoding && !$this->has8bitChars($this->AltBody)) {
2641              $altBodyEncoding = static::ENCODING_7BIT;
2642              //All ISO 8859, Windows codepage and UTF-8 charsets are ascii compatible up to 7-bit
2643              $altBodyCharSet = static::CHARSET_ASCII;
2644          }
2645          //If lines are too long, and we're not already using an encoding that will shorten them,
2646          //change to quoted-printable transfer encoding for the alt body part only
2647          if (static::ENCODING_BASE64 !== $altBodyEncoding && static::hasLineLongerThanMax($this->AltBody)) {
2648              $altBodyEncoding = static::ENCODING_QUOTED_PRINTABLE;
2649          }
2650          //Use this as a preamble in all multipart message types
2651          $mimepre = 'This is a multi-part message in MIME format.' . static::$LE . static::$LE;
2652          switch ($this->message_type) {
2653              case 'inline':
2654                  $body .= $mimepre;
2655                  $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, '', $bodyEncoding);
2656                  $body .= $this->encodeString($this->Body, $bodyEncoding);
2657                  $body .= static::$LE;
2658                  $body .= $this->attachAll('inline', $this->boundary[1]);
2659                  break;
2660              case 'attach':
2661                  $body .= $mimepre;
2662                  $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, '', $bodyEncoding);
2663                  $body .= $this->encodeString($this->Body, $bodyEncoding);
2664                  $body .= static::$LE;
2665                  $body .= $this->attachAll('attachment', $this->boundary[1]);
2666                  break;
2667              case 'inline_attach':
2668                  $body .= $mimepre;
2669                  $body .= $this->textLine('--' . $this->boundary[1]);
2670                  $body .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_RELATED . ';');
2671                  $body .= $this->textLine(' boundary="' . $this->boundary[2] . '";');
2672                  $body .= $this->textLine(' type="' . static::CONTENT_TYPE_TEXT_HTML . '"');
2673                  $body .= static::$LE;
2674                  $body .= $this->getBoundary($this->boundary[2], $bodyCharSet, '', $bodyEncoding);
2675                  $body .= $this->encodeString($this->Body, $bodyEncoding);
2676                  $body .= static::$LE;
2677                  $body .= $this->attachAll('inline', $this->boundary[2]);
2678                  $body .= static::$LE;
2679                  $body .= $this->attachAll('attachment', $this->boundary[1]);
2680                  break;
2681              case 'alt':
2682                  $body .= $mimepre;
2683                  $body .= $this->getBoundary(
2684                      $this->boundary[1],
2685                      $altBodyCharSet,
2686                      static::CONTENT_TYPE_PLAINTEXT,
2687                      $altBodyEncoding
2688                  );
2689                  $body .= $this->encodeString($this->AltBody, $altBodyEncoding);
2690                  $body .= static::$LE;
2691                  $body .= $this->getBoundary(
2692                      $this->boundary[1],
2693                      $bodyCharSet,
2694                      static::CONTENT_TYPE_TEXT_HTML,
2695                      $bodyEncoding
2696                  );
2697                  $body .= $this->encodeString($this->Body, $bodyEncoding);
2698                  $body .= static::$LE;
2699                  if (!empty($this->Ical)) {
2700                      $method = static::ICAL_METHOD_REQUEST;
2701                      foreach (static::$IcalMethods as $imethod) {
2702                          if (stripos($this->Ical, 'METHOD:' . $imethod) !== false) {
2703                              $method = $imethod;
2704                              break;
2705                          }
2706                      }
2707                      $body .= $this->getBoundary(
2708                          $this->boundary[1],
2709                          '',
2710                          static::CONTENT_TYPE_TEXT_CALENDAR . '; method=' . $method,
2711                          ''
2712                      );
2713                      $body .= $this->encodeString($this->Ical, $this->Encoding);
2714                      $body .= static::$LE;
2715                  }
2716                  $body .= $this->endBoundary($this->boundary[1]);
2717                  break;
2718              case 'alt_inline':
2719                  $body .= $mimepre;
2720                  $body .= $this->getBoundary(
2721                      $this->boundary[1],
2722                      $altBodyCharSet,
2723                      static::CONTENT_TYPE_PLAINTEXT,
2724                      $altBodyEncoding
2725                  );
2726                  $body .= $this->encodeString($this->AltBody, $altBodyEncoding);
2727                  $body .= static::$LE;
2728                  $body .= $this->textLine('--' . $this->boundary[1]);
2729                  $body .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_RELATED . ';');
2730                  $body .= $this->textLine(' boundary="' . $this->boundary[2] . '";');
2731                  $body .= $this->textLine(' type="' . static::CONTENT_TYPE_TEXT_HTML . '"');
2732                  $body .= static::$LE;
2733                  $body .= $this->getBoundary(
2734                      $this->boundary[2],
2735                      $bodyCharSet,
2736                      static::CONTENT_TYPE_TEXT_HTML,
2737                      $bodyEncoding
2738                  );
2739                  $body .= $this->encodeString($this->Body, $bodyEncoding);
2740                  $body .= static::$LE;
2741                  $body .= $this->attachAll('inline', $this->boundary[2]);
2742                  $body .= static::$LE;
2743                  $body .= $this->endBoundary($this->boundary[1]);
2744                  break;
2745              case 'alt_attach':
2746                  $body .= $mimepre;
2747                  $body .= $this->textLine('--' . $this->boundary[1]);
2748                  $body .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_ALTERNATIVE . ';');
2749                  $body .= $this->textLine(' boundary="' . $this->boundary[2] . '"');
2750                  $body .= static::$LE;
2751                  $body .= $this->getBoundary(
2752                      $this->boundary[2],
2753                      $altBodyCharSet,
2754                      static::CONTENT_TYPE_PLAINTEXT,
2755                      $altBodyEncoding
2756                  );
2757                  $body .= $this->encodeString($this->AltBody, $altBodyEncoding);
2758                  $body .= static::$LE;
2759                  $body .= $this->getBoundary(
2760                      $this->boundary[2],
2761                      $bodyCharSet,
2762                      static::CONTENT_TYPE_TEXT_HTML,
2763                      $bodyEncoding
2764                  );
2765                  $body .= $this->encodeString($this->Body, $bodyEncoding);
2766                  $body .= static::$LE;
2767                  if (!empty($this->Ical)) {
2768                      $method = static::ICAL_METHOD_REQUEST;
2769                      foreach (static::$IcalMethods as $imethod) {
2770                          if (stripos($this->Ical, 'METHOD:' . $imethod) !== false) {
2771                              $method = $imethod;
2772                              break;
2773                          }
2774                      }
2775                      $body .= $this->getBoundary(
2776                          $this->boundary[2],
2777                          '',
2778                          static::CONTENT_TYPE_TEXT_CALENDAR . '; method=' . $method,
2779                          ''
2780                      );
2781                      $body .= $this->encodeString($this->Ical, $this->Encoding);
2782                  }
2783                  $body .= $this->endBoundary($this->boundary[2]);
2784                  $body .= static::$LE;
2785                  $body .= $this->attachAll('attachment', $this->boundary[1]);
2786                  break;
2787              case 'alt_inline_attach':
2788                  $body .= $mimepre;
2789                  $body .= $this->textLine('--' . $this->boundary[1]);
2790                  $body .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_ALTERNATIVE . ';');
2791                  $body .= $this->textLine(' boundary="' . $this->boundary[2] . '"');
2792                  $body .= static::$LE;
2793                  $body .= $this->getBoundary(
2794                      $this->boundary[2],
2795                      $altBodyCharSet,
2796                      static::CONTENT_TYPE_PLAINTEXT,
2797                      $altBodyEncoding
2798                  );
2799                  $body .= $this->encodeString($this->AltBody, $altBodyEncoding);
2800                  $body .= static::$LE;
2801                  $body .= $this->textLine('--' . $this->boundary[2]);
2802                  $body .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_RELATED . ';');
2803                  $body .= $this->textLine(' boundary="' . $this->boundary[3] . '";');
2804                  $body .= $this->textLine(' type="' . static::CONTENT_TYPE_TEXT_HTML . '"');
2805                  $body .= static::$LE;
2806                  $body .= $this->getBoundary(
2807                      $this->boundary[3],
2808                      $bodyCharSet,
2809                      static::CONTENT_TYPE_TEXT_HTML,
2810                      $bodyEncoding
2811                  );
2812                  $body .= $this->encodeString($this->Body, $bodyEncoding);
2813                  $body .= static::$LE;
2814                  $body .= $this->attachAll('inline', $this->boundary[3]);
2815                  $body .= static::$LE;
2816                  $body .= $this->endBoundary($this->boundary[2]);
2817                  $body .= static::$LE;
2818                  $body .= $this->attachAll('attachment', $this->boundary[1]);
2819                  break;
2820              default:
2821                  // Catch case 'plain' and case '', applies to simple `text/plain` and `text/html` body content types
2822                  //Reset the `Encoding` property in case we changed it for line length reasons
2823                  $this->Encoding = $bodyEncoding;
2824                  $body .= $this->encodeString($this->Body, $this->Encoding);
2825                  break;
2826          }
2827  
2828          if ($this->isError()) {
2829              $body = '';
2830              if ($this->exceptions) {
2831                  throw new Exception($this->lang('empty_message'), self::STOP_CRITICAL);
2832              }
2833          } elseif ($this->sign_key_file) {
2834              try {
2835                  if (!defined('PKCS7_TEXT')) {
2836                      throw new Exception($this->lang('extension_missing') . 'openssl');
2837                  }
2838  
2839                  $file = tempnam(sys_get_temp_dir(), 'srcsign');
2840                  $signed = tempnam(sys_get_temp_dir(), 'mailsign');
2841                  file_put_contents($file, $body);
2842  
2843                  //Workaround for PHP bug https://bugs.php.net/bug.php?id=69197
2844                  if (empty($this->sign_extracerts_file)) {
2845                      $sign = @openssl_pkcs7_sign(
2846                          $file,
2847                          $signed,
2848                          'file://' . realpath($this->sign_cert_file),
2849                          ['file://' . realpath($this->sign_key_file), $this->sign_key_pass],
2850                          []
2851                      );
2852                  } else {
2853                      $sign = @openssl_pkcs7_sign(
2854                          $file,
2855                          $signed,
2856                          'file://' . realpath($this->sign_cert_file),
2857                          ['file://' . realpath($this->sign_key_file), $this->sign_key_pass],
2858                          [],
2859                          PKCS7_DETACHED,
2860                          $this->sign_extracerts_file
2861                      );
2862                  }
2863  
2864                  @unlink($file);
2865                  if ($sign) {
2866                      $body = file_get_contents($signed);
2867                      @unlink($signed);
2868                      //The message returned by openssl contains both headers and body, so need to split them up
2869                      $parts = explode("\n\n", $body, 2);
2870                      $this->MIMEHeader .= $parts[0] . static::$LE . static::$LE;
2871                      $body = $parts[1];
2872                  } else {
2873                      @unlink($signed);
2874                      throw new Exception($this->lang('signing') . openssl_error_string());
2875                  }
2876              } catch (Exception $exc) {
2877                  $body = '';
2878                  if ($this->exceptions) {
2879                      throw $exc;
2880                  }
2881              }
2882          }
2883  
2884          return $body;
2885      }
2886  
2887      /**
2888       * Return the start of a message boundary.
2889       *
2890       * @param string $boundary
2891       * @param string $charSet
2892       * @param string $contentType
2893       * @param string $encoding
2894       *
2895       * @return string
2896       */
2897      protected function getBoundary($boundary, $charSet, $contentType, $encoding)
2898      {
2899          $result = '';
2900          if ('' === $charSet) {
2901              $charSet = $this->CharSet;
2902          }
2903          if ('' === $contentType) {
2904              $contentType = $this->ContentType;
2905          }
2906          if ('' === $encoding) {
2907              $encoding = $this->Encoding;
2908          }
2909          $result .= $this->textLine('--' . $boundary);
2910          $result .= sprintf('Content-Type: %s; charset=%s', $contentType, $charSet);
2911          $result .= static::$LE;
2912          // RFC1341 part 5 says 7bit is assumed if not specified
2913          if (static::ENCODING_7BIT !== $encoding) {
2914              $result .= $this->headerLine('Content-Transfer-Encoding', $encoding);
2915          }
2916          $result .= static::$LE;
2917  
2918          return $result;
2919      }
2920  
2921      /**
2922       * Return the end of a message boundary.
2923       *
2924       * @param string $boundary
2925       *
2926       * @return string
2927       */
2928      protected function endBoundary($boundary)
2929      {
2930          return static::$LE . '--' . $boundary . '--' . static::$LE;
2931      }
2932  
2933      /**
2934       * Set the message type.
2935       * PHPMailer only supports some preset message types, not arbitrary MIME structures.
2936       */
2937      protected function setMessageType()
2938      {
2939          $type = [];
2940          if ($this->alternativeExists()) {
2941              $type[] = 'alt';
2942          }
2943          if ($this->inlineImageExists()) {
2944              $type[] = 'inline';
2945          }
2946          if ($this->attachmentExists()) {
2947              $type[] = 'attach';
2948          }
2949          $this->message_type = implode('_', $type);
2950          if ('' === $this->message_type) {
2951              //The 'plain' message_type refers to the message having a single body element, not that it is plain-text
2952              $this->message_type = 'plain';
2953          }
2954      }
2955  
2956      /**
2957       * Format a header line.
2958       *
2959       * @param string     $name
2960       * @param string|int $value
2961       *
2962       * @return string
2963       */
2964      public function headerLine($name, $value)
2965      {
2966          return $name . ': ' . $value . static::$LE;
2967      }
2968  
2969      /**
2970       * Return a formatted mail line.
2971       *
2972       * @param string $value
2973       *
2974       * @return string
2975       */
2976      public function textLine($value)
2977      {
2978          return $value . static::$LE;
2979      }
2980  
2981      /**
2982       * Add an attachment from a path on the filesystem.
2983       * Never use a user-supplied path to a file!
2984       * Returns false if the file could not be found or read.
2985       * Explicitly *does not* support passing URLs; PHPMailer is not an HTTP client.
2986       * If you need to do that, fetch the resource yourself and pass it in via a local file or string.
2987       *
2988       * @param string $path        Path to the attachment
2989       * @param string $name        Overrides the attachment name
2990       * @param string $encoding    File encoding (see $Encoding)
2991       * @param string $type        MIME type, e.g. `image/jpeg`; determined automatically from $path if not specified
2992       * @param string $disposition Disposition to use
2993       *
2994       * @throws Exception
2995       *
2996       * @return bool
2997       */
2998      public function addAttachment(
2999          $path,
3000          $name = '',
3001          $encoding = self::ENCODING_BASE64,
3002          $type = '',
3003          $disposition = 'attachment'
3004      ) {
3005          try {
3006              if (!static::fileIsAccessible($path)) {
3007                  throw new Exception($this->lang('file_access') . $path, self::STOP_CONTINUE);
3008              }
3009  
3010              // If a MIME type is not specified, try to work it out from the file name
3011              if ('' === $type) {
3012                  $type = static::filenameToType($path);
3013              }
3014  
3015              $filename = (string) static::mb_pathinfo($path, PATHINFO_BASENAME);
3016              if ('' === $name) {
3017                  $name = $filename;
3018              }
3019              if (!$this->validateEncoding($encoding)) {
3020                  throw new Exception($this->lang('encoding') . $encoding);
3021              }
3022  
3023              $this->attachment[] = [
3024                  0 => $path,
3025                  1 => $filename,
3026                  2 => $name,
3027                  3 => $encoding,
3028                  4 => $type,
3029                  5 => false, // isStringAttachment
3030                  6 => $disposition,
3031                  7 => $name,
3032              ];
3033          } catch (Exception $exc) {
3034              $this->setError($exc->getMessage());
3035              $this->edebug($exc->getMessage());
3036              if ($this->exceptions) {
3037                  throw $exc;
3038              }
3039  
3040              return false;
3041          }
3042  
3043          return true;
3044      }
3045  
3046      /**
3047       * Return the array of attachments.
3048       *
3049       * @return array
3050       */
3051      public function getAttachments()
3052      {
3053          return $this->attachment;
3054      }
3055  
3056      /**
3057       * Attach all file, string, and binary attachments to the message.
3058       * Returns an empty string on failure.
3059       *
3060       * @param string $disposition_type
3061       * @param string $boundary
3062       *
3063       * @throws Exception
3064       *
3065       * @return string
3066       */
3067      protected function attachAll($disposition_type, $boundary)
3068      {
3069          // Return text of body
3070          $mime = [];
3071          $cidUniq = [];
3072          $incl = [];
3073  
3074          // Add all attachments
3075          foreach ($this->attachment as $attachment) {
3076              // Check if it is a valid disposition_filter
3077              if ($attachment[6] === $disposition_type) {
3078                  // Check for string attachment
3079                  $string = '';
3080                  $path = '';
3081                  $bString = $attachment[5];
3082                  if ($bString) {
3083                      $string = $attachment[0];
3084                  } else {
3085                      $path = $attachment[0];
3086                  }
3087  
3088                  $inclhash = hash('sha256', serialize($attachment));
3089                  if (in_array($inclhash, $incl, true)) {
3090                      continue;
3091                  }
3092                  $incl[] = $inclhash;
3093                  $name = $attachment[2];
3094                  $encoding = $attachment[3];
3095                  $type = $attachment[4];
3096                  $disposition = $attachment[6];
3097                  $cid = $attachment[7];
3098                  if ('inline' === $disposition && array_key_exists($cid, $cidUniq)) {
3099                      continue;
3100                  }
3101                  $cidUniq[$cid] = true;
3102  
3103                  $mime[] = sprintf('--%s%s', $boundary, static::$LE);
3104                  //Only include a filename property if we have one
3105                  if (!empty($name)) {
3106                      $mime[] = sprintf(
3107                          'Content-Type: %s; name=%s%s',
3108                          $type,
3109                          static::quotedString($this->encodeHeader($this->secureHeader($name))),
3110                          static::$LE
3111                      );
3112                  } else {
3113                      $mime[] = sprintf(
3114                          'Content-Type: %s%s',
3115                          $type,
3116                          static::$LE
3117                      );
3118                  }
3119                  // RFC1341 part 5 says 7bit is assumed if not specified
3120                  if (static::ENCODING_7BIT !== $encoding) {
3121                      $mime[] = sprintf('Content-Transfer-Encoding: %s%s', $encoding, static::$LE);
3122                  }
3123  
3124                  //Only set Content-IDs on inline attachments
3125                  if ((string) $cid !== '' && $disposition === 'inline') {
3126                      $mime[] = 'Content-ID: <' . $this->encodeHeader($this->secureHeader($cid)) . '>' . static::$LE;
3127                  }
3128  
3129                  // Allow for bypassing the Content-Disposition header
3130                  if (!empty($disposition)) {
3131                      $encoded_name = $this->encodeHeader($this->secureHeader($name));
3132                      if (!empty($encoded_name)) {
3133                          $mime[] = sprintf(
3134                              'Content-Disposition: %s; filename=%s%s',
3135                              $disposition,
3136                              static::quotedString($encoded_name),
3137                              static::$LE . static::$LE
3138                          );
3139                      } else {
3140                          $mime[] = sprintf(
3141                              'Content-Disposition: %s%s',
3142                              $disposition,
3143                              static::$LE . static::$LE
3144                          );
3145                      }
3146                  } else {
3147                      $mime[] = static::$LE;
3148                  }
3149  
3150                  // Encode as string attachment
3151                  if ($bString) {
3152                      $mime[] = $this->encodeString($string, $encoding);
3153                  } else {
3154                      $mime[] = $this->encodeFile($path, $encoding);
3155                  }
3156                  if ($this->isError()) {
3157                      return '';
3158                  }
3159                  $mime[] = static::$LE;
3160              }
3161          }
3162  
3163          $mime[] = sprintf('--%s--%s', $boundary, static::$LE);
3164  
3165          return implode('', $mime);
3166      }
3167  
3168      /**
3169       * Encode a file attachment in requested format.
3170       * Returns an empty string on failure.
3171       *
3172       * @param string $path     The full path to the file
3173       * @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable'
3174       *
3175       * @return string
3176       */
3177      protected function encodeFile($path, $encoding = self::ENCODING_BASE64)
3178      {
3179          try {
3180              if (!static::fileIsAccessible($path)) {
3181                  throw new Exception($this->lang('file_open') . $path, self::STOP_CONTINUE);
3182              }
3183              $file_buffer = file_get_contents($path);
3184              if (false === $file_buffer) {
3185                  throw new Exception($this->lang('file_open') . $path, self::STOP_CONTINUE);
3186              }
3187              $file_buffer = $this->encodeString($file_buffer, $encoding);
3188  
3189              return $file_buffer;
3190          } catch (Exception $exc) {
3191              $this->setError($exc->getMessage());
3192              $this->edebug($exc->getMessage());
3193              if ($this->exceptions) {
3194                  throw $exc;
3195              }
3196  
3197              return '';
3198          }
3199      }
3200  
3201      /**
3202       * Encode a string in requested format.
3203       * Returns an empty string on failure.
3204       *
3205       * @param string $str      The text to encode
3206       * @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable'
3207       *
3208       * @throws Exception
3209       *
3210       * @return string
3211       */
3212      public function encodeString($str, $encoding = self::ENCODING_BASE64)
3213      {
3214          $encoded = '';
3215          switch (strtolower($encoding)) {
3216              case static::ENCODING_BASE64:
3217                  $encoded = chunk_split(
3218                      base64_encode($str),
3219                      static::STD_LINE_LENGTH,
3220                      static::$LE
3221                  );
3222                  break;
3223              case static::ENCODING_7BIT:
3224              case static::ENCODING_8BIT:
3225                  $encoded = static::normalizeBreaks($str);
3226                  // Make sure it ends with a line break
3227                  if (substr($encoded, -(strlen(static::$LE))) !== static::$LE) {
3228                      $encoded .= static::$LE;
3229                  }
3230                  break;
3231              case static::ENCODING_BINARY:
3232                  $encoded = $str;
3233                  break;
3234              case static::ENCODING_QUOTED_PRINTABLE:
3235                  $encoded = $this->encodeQP($str);
3236                  break;
3237              default:
3238                  $this->setError($this->lang('encoding') . $encoding);
3239                  if ($this->exceptions) {
3240                      throw new Exception($this->lang('encoding') . $encoding);
3241                  }
3242                  break;
3243          }
3244  
3245          return $encoded;
3246      }
3247  
3248      /**
3249       * Encode a header value (not including its label) optimally.
3250       * Picks shortest of Q, B, or none. Result includes folding if needed.
3251       * See RFC822 definitions for phrase, comment and text positions.
3252       *
3253       * @param string $str      The header value to encode
3254       * @param string $position What context the string will be used in
3255       *
3256       * @return string
3257       */
3258      public function encodeHeader($str, $position = 'text')
3259      {
3260          $matchcount = 0;
3261          switch (strtolower($position)) {
3262              case 'phrase':
3263                  if (!preg_match('/[\200-\377]/', $str)) {
3264                      // Can't use addslashes as we don't know the value of magic_quotes_sybase
3265                      $encoded = addcslashes($str, "\0..\37\177\\\"");
3266                      if (($str === $encoded) && !preg_match('/[^A-Za-z0-9!#$%&\'*+\/=?^_`{|}~ -]/', $str)) {
3267                          return $encoded;
3268                      }
3269  
3270                      return "\"$encoded\"";
3271                  }
3272                  $matchcount = preg_match_all('/[^\040\041\043-\133\135-\176]/', $str, $matches);
3273                  break;
3274              /* @noinspection PhpMissingBreakStatementInspection */
3275              case 'comment':
3276                  $matchcount = preg_match_all('/[()"]/', $str, $matches);
3277              //fallthrough
3278              case 'text':
3279              default:
3280                  $matchcount += preg_match_all('/[\000-\010\013\014\016-\037\177-\377]/', $str, $matches);
3281                  break;
3282          }
3283  
3284          if ($this->has8bitChars($str)) {
3285              $charset = $this->CharSet;
3286          } else {
3287              $charset = static::CHARSET_ASCII;
3288          }
3289  
3290          // Q/B encoding adds 8 chars and the charset ("` =?<charset>?[QB]?<content>?=`").
3291          $overhead = 8 + strlen($charset);
3292  
3293          if ('mail' === $this->Mailer) {
3294              $maxlen = static::MAIL_MAX_LINE_LENGTH - $overhead;
3295          } else {
3296              $maxlen = static::MAX_LINE_LENGTH - $overhead;
3297          }
3298  
3299          // Select the encoding that produces the shortest output and/or prevents corruption.
3300          if ($matchcount > strlen($str) / 3) {
3301              // More than 1/3 of the content needs encoding, use B-encode.
3302              $encoding = 'B';
3303          } elseif ($matchcount > 0) {
3304              // Less than 1/3 of the content needs encoding, use Q-encode.
3305              $encoding = 'Q';
3306          } elseif (strlen($str) > $maxlen) {
3307              // No encoding needed, but value exceeds max line length, use Q-encode to prevent corruption.
3308              $encoding = 'Q';
3309          } else {
3310              // No reformatting needed
3311              $encoding = false;
3312          }
3313  
3314          switch ($encoding) {
3315              case 'B':
3316                  if ($this->hasMultiBytes($str)) {
3317                      // Use a custom function which correctly encodes and wraps long
3318                      // multibyte strings without breaking lines within a character
3319                      $encoded = $this->base64EncodeWrapMB($str, "\n");
3320                  } else {
3321                      $encoded = base64_encode($str);
3322                      $maxlen -= $maxlen % 4;
3323                      $encoded = trim(chunk_split($encoded, $maxlen, "\n"));
3324                  }
3325                  $encoded = preg_replace('/^(.*)$/m', ' =?' . $charset . "?$encoding?\\1?=", $encoded);
3326                  break;
3327              case 'Q':
3328                  $encoded = $this->encodeQ($str, $position);
3329                  $encoded = $this->wrapText($encoded, $maxlen, true);
3330                  $encoded = str_replace('=' . static::$LE, "\n", trim($encoded));
3331                  $encoded = preg_replace('/^(.*)$/m', ' =?' . $charset . "?$encoding?\\1?=", $encoded);
3332                  break;
3333              default:
3334                  return $str;
3335          }
3336  
3337          return trim(static::normalizeBreaks($encoded));
3338      }
3339  
3340      /**
3341       * Check if a string contains multi-byte characters.
3342       *
3343       * @param string $str multi-byte text to wrap encode
3344       *
3345       * @return bool
3346       */
3347      public function hasMultiBytes($str)
3348      {
3349          if (function_exists('mb_strlen')) {
3350              return strlen($str) > mb_strlen($str, $this->CharSet);
3351          }
3352  
3353          // Assume no multibytes (we can't handle without mbstring functions anyway)
3354          return false;
3355      }
3356  
3357      /**
3358       * Does a string contain any 8-bit chars (in any charset)?
3359       *
3360       * @param string $text
3361       *
3362       * @return bool
3363       */
3364      public function has8bitChars($text)
3365      {
3366          return (bool) preg_match('/[\x80-\xFF]/', $text);
3367      }
3368  
3369      /**
3370       * Encode and wrap long multibyte strings for mail headers
3371       * without breaking lines within a character.
3372       * Adapted from a function by paravoid.
3373       *
3374       * @see http://www.php.net/manual/en/function.mb-encode-mimeheader.php#60283
3375       *
3376       * @param string $str       multi-byte text to wrap encode
3377       * @param string $linebreak string to use as linefeed/end-of-line
3378       *
3379       * @return string
3380       */
3381      public function base64EncodeWrapMB($str, $linebreak = null)
3382      {
3383          $start = '=?' . $this->CharSet . '?B?';
3384          $end = '?=';
3385          $encoded = '';
3386          if (null === $linebreak) {
3387              $linebreak = static::$LE;
3388          }
3389  
3390          $mb_length = mb_strlen($str, $this->CharSet);
3391          // Each line must have length <= 75, including $start and $end
3392          $length = 75 - strlen($start) - strlen($end);
3393          // Average multi-byte ratio
3394          $ratio = $mb_length / strlen($str);
3395          // Base64 has a 4:3 ratio
3396          $avgLength = floor($length * $ratio * .75);
3397  
3398          $offset = 0;
3399          for ($i = 0; $i < $mb_length; $i += $offset) {
3400              $lookBack = 0;
3401              do {
3402                  $offset = $avgLength - $lookBack;
3403                  $chunk = mb_substr($str, $i, $offset, $this->CharSet);
3404                  $chunk = base64_encode($chunk);
3405                  ++$lookBack;
3406              } while (strlen($chunk) > $length);
3407              $encoded .= $chunk . $linebreak;
3408          }
3409  
3410          // Chomp the last linefeed
3411          return substr($encoded, 0, -strlen($linebreak));
3412      }
3413  
3414      /**
3415       * Encode a string in quoted-printable format.
3416       * According to RFC2045 section 6.7.
3417       *
3418       * @param string $string The text to encode
3419       *
3420       * @return string
3421       */
3422      public function encodeQP($string)
3423      {
3424          return static::normalizeBreaks(quoted_printable_encode($string));
3425      }
3426  
3427      /**
3428       * Encode a string using Q encoding.
3429       *
3430       * @see http://tools.ietf.org/html/rfc2047#section-4.2
3431       *
3432       * @param string $str      the text to encode
3433       * @param string $position Where the text is going to be used, see the RFC for what that means
3434       *
3435       * @return string
3436       */
3437      public function encodeQ($str, $position = 'text')
3438      {
3439          // There should not be any EOL in the string
3440          $pattern = '';
3441          $encoded = str_replace(["\r", "\n"], '', $str);
3442          switch (strtolower($position)) {
3443              case 'phrase':
3444                  // RFC 2047 section 5.3
3445                  $pattern = '^A-Za-z0-9!*+\/ -';
3446                  break;
3447              /*
3448               * RFC 2047 section 5.2.
3449               * Build $pattern without including delimiters and []
3450               */
3451              /* @noinspection PhpMissingBreakStatementInspection */
3452              case 'comment':
3453                  $pattern = '\(\)"';
3454              /* Intentional fall through */
3455              case 'text':
3456              default:
3457                  // RFC 2047 section 5.1
3458                  // Replace every high ascii, control, =, ? and _ characters
3459                  $pattern = '\000-\011\013\014\016-\037\075\077\137\177-\377' . $pattern;
3460                  break;
3461          }
3462          $matches = [];
3463          if (preg_match_all("/[{$pattern}]/", $encoded, $matches)) {
3464              // If the string contains an '=', make sure it's the first thing we replace
3465              // so as to avoid double-encoding
3466              $eqkey = array_search('=', $matches[0], true);
3467              if (false !== $eqkey) {
3468                  unset($matches[0][$eqkey]);
3469                  array_unshift($matches[0], '=');
3470              }
3471              foreach (array_unique($matches[0]) as $char) {
3472                  $encoded = str_replace($char, '=' . sprintf('%02X', ord($char)), $encoded);
3473              }
3474          }
3475          // Replace spaces with _ (more readable than =20)
3476          // RFC 2047 section 4.2(2)
3477          return str_replace(' ', '_', $encoded);
3478      }
3479  
3480      /**
3481       * Add a string or binary attachment (non-filesystem).
3482       * This method can be used to attach ascii or binary data,
3483       * such as a BLOB record from a database.
3484       *
3485       * @param string $string      String attachment data
3486       * @param string $filename    Name of the attachment
3487       * @param string $encoding    File encoding (see $Encoding)
3488       * @param string $type        File extension (MIME) type
3489       * @param string $disposition Disposition to use
3490       *
3491       * @throws Exception
3492       *
3493       * @return bool True on successfully adding an attachment
3494       */
3495      public function addStringAttachment(
3496          $string,
3497          $filename,
3498          $encoding = self::ENCODING_BASE64,
3499          $type = '',
3500          $disposition = 'attachment'
3501      ) {
3502          try {
3503              // If a MIME type is not specified, try to work it out from the file name
3504              if ('' === $type) {
3505                  $type = static::filenameToType($filename);
3506              }
3507  
3508              if (!$this->validateEncoding($encoding)) {
3509                  throw new Exception($this->lang('encoding') . $encoding);
3510              }
3511  
3512              // Append to $attachment array
3513              $this->attachment[] = [
3514                  0 => $string,
3515                  1 => $filename,
3516                  2 => static::mb_pathinfo($filename, PATHINFO_BASENAME),
3517                  3 => $encoding,
3518                  4 => $type,
3519                  5 => true, // isStringAttachment
3520                  6 => $disposition,
3521                  7 => 0,
3522              ];
3523          } catch (Exception $exc) {
3524              $this->setError($exc->getMessage());
3525              $this->edebug($exc->getMessage());
3526              if ($this->exceptions) {
3527                  throw $exc;
3528              }
3529  
3530              return false;
3531          }
3532  
3533          return true;
3534      }
3535  
3536      /**
3537       * Add an embedded (inline) attachment from a file.
3538       * This can include images, sounds, and just about any other document type.
3539       * These differ from 'regular' attachments in that they are intended to be
3540       * displayed inline with the message, not just attached for download.
3541       * This is used in HTML messages that embed the images
3542       * the HTML refers to using the $cid value.
3543       * Never use a user-supplied path to a file!
3544       *
3545       * @param string $path        Path to the attachment
3546       * @param string $cid         Content ID of the attachment; Use this to reference
3547       *                            the content when using an embedded image in HTML
3548       * @param string $name        Overrides the attachment name
3549       * @param string $encoding    File encoding (see $Encoding)
3550       * @param string $type        File MIME type
3551       * @param string $disposition Disposition to use
3552       *
3553       * @throws Exception
3554       *
3555       * @return bool True on successfully adding an attachment
3556       */
3557      public function addEmbeddedImage(
3558          $path,
3559          $cid,
3560          $name = '',
3561          $encoding = self::ENCODING_BASE64,
3562          $type = '',
3563          $disposition = 'inline'
3564      ) {
3565          try {
3566              if (!static::fileIsAccessible($path)) {
3567                  throw new Exception($this->lang('file_access') . $path, self::STOP_CONTINUE);
3568              }
3569  
3570              // If a MIME type is not specified, try to work it out from the file name
3571              if ('' === $type) {
3572                  $type = static::filenameToType($path);
3573              }
3574  
3575              if (!$this->validateEncoding($encoding)) {
3576                  throw new Exception($this->lang('encoding') . $encoding);
3577              }
3578  
3579              $filename = (string) static::mb_pathinfo($path, PATHINFO_BASENAME);
3580              if ('' === $name) {
3581                  $name = $filename;
3582              }
3583  
3584              // Append to $attachment array
3585              $this->attachment[] = [
3586                  0 => $path,
3587                  1 => $filename,
3588                  2 => $name,
3589                  3 => $encoding,
3590                  4 => $type,
3591                  5 => false, // isStringAttachment
3592                  6 => $disposition,
3593                  7 => $cid,
3594              ];
3595          } catch (Exception $exc) {
3596              $this->setError($exc->getMessage());
3597              $this->edebug($exc->getMessage());
3598              if ($this->exceptions) {
3599                  throw $exc;
3600              }
3601  
3602              return false;
3603          }
3604  
3605          return true;
3606      }
3607  
3608      /**
3609       * Add an embedded stringified attachment.
3610       * This can include images, sounds, and just about any other document type.
3611       * If your filename doesn't contain an extension, be sure to set the $type to an appropriate MIME type.
3612       *
3613       * @param string $string      The attachment binary data
3614       * @param string $cid         Content ID of the attachment; Use this to reference
3615       *                            the content when using an embedded image in HTML
3616       * @param string $name        A filename for the attachment. If this contains an extension,
3617       *                            PHPMailer will attempt to set a MIME type for the attachment.
3618       *                            For example 'file.jpg' would get an 'image/jpeg' MIME type.
3619       * @param string $encoding    File encoding (see $Encoding), defaults to 'base64'
3620       * @param string $type        MIME type - will be used in preference to any automatically derived type
3621       * @param string $disposition Disposition to use
3622       *
3623       * @throws Exception
3624       *
3625       * @return bool True on successfully adding an attachment
3626       */
3627      public function addStringEmbeddedImage(
3628          $string,
3629          $cid,
3630          $name = '',
3631          $encoding = self::ENCODING_BASE64,
3632          $type = '',
3633          $disposition = 'inline'
3634      ) {
3635          try {
3636              // If a MIME type is not specified, try to work it out from the name
3637              if ('' === $type && !empty($name)) {
3638                  $type = static::filenameToType($name);
3639              }
3640  
3641              if (!$this->validateEncoding($encoding)) {
3642                  throw new Exception($this->lang('encoding') . $encoding);
3643              }
3644  
3645              // Append to $attachment array
3646              $this->attachment[] = [
3647                  0 => $string,
3648                  1 => $name,
3649                  2 => $name,
3650                  3 => $encoding,
3651                  4 => $type,
3652                  5 => true, // isStringAttachment
3653                  6 => $disposition,
3654                  7 => $cid,
3655              ];
3656          } catch (Exception $exc) {
3657              $this->setError($exc->getMessage());
3658              $this->edebug($exc->getMessage());
3659              if ($this->exceptions) {
3660                  throw $exc;
3661              }
3662  
3663              return false;
3664          }
3665  
3666          return true;
3667      }
3668  
3669      /**
3670       * Validate encodings.
3671       *
3672       * @param string $encoding
3673       *
3674       * @return bool
3675       */
3676      protected function validateEncoding($encoding)
3677      {
3678          return in_array(
3679              $encoding,
3680              [
3681                  self::ENCODING_7BIT,
3682                  self::ENCODING_QUOTED_PRINTABLE,
3683                  self::ENCODING_BASE64,
3684                  self::ENCODING_8BIT,
3685                  self::ENCODING_BINARY,
3686              ],
3687              true
3688          );
3689      }
3690  
3691      /**
3692       * Check if an embedded attachment is present with this cid.
3693       *
3694       * @param string $cid
3695       *
3696       * @return bool
3697       */
3698      protected function cidExists($cid)
3699      {
3700          foreach ($this->attachment as $attachment) {
3701              if ('inline' === $attachment[6] && $cid === $attachment[7]) {
3702                  return true;
3703              }
3704          }
3705  
3706          return false;
3707      }
3708  
3709      /**
3710       * Check if an inline attachment is present.
3711       *
3712       * @return bool
3713       */
3714      public function inlineImageExists()
3715      {
3716          foreach ($this->attachment as $attachment) {
3717              if ('inline' === $attachment[6]) {
3718                  return true;
3719              }
3720          }
3721  
3722          return false;
3723      }
3724  
3725      /**
3726       * Check if an attachment (non-inline) is present.
3727       *
3728       * @return bool
3729       */
3730      public function attachmentExists()
3731      {
3732          foreach ($this->attachment as $attachment) {
3733              if ('attachment' === $attachment[6]) {
3734                  return true;
3735              }
3736          }
3737  
3738          return false;
3739      }
3740  
3741      /**
3742       * Check if this message has an alternative body set.
3743       *
3744       * @return bool
3745       */
3746      public function alternativeExists()
3747      {
3748          return !empty($this->AltBody);
3749      }
3750  
3751      /**
3752       * Clear queued addresses of given kind.
3753       *
3754       * @param string $kind 'to', 'cc', or 'bcc'
3755       */
3756      public function clearQueuedAddresses($kind)
3757      {
3758          $this->RecipientsQueue = array_filter(
3759              $this->RecipientsQueue,
3760              static function ($params) use ($kind) {
3761                  return $params[0] !== $kind;
3762              }
3763          );
3764      }
3765  
3766      /**
3767       * Clear all To recipients.
3768       */
3769      public function clearAddresses()
3770      {
3771          foreach ($this->to as $to) {
3772              unset($this->all_recipients[strtolower($to[0])]);
3773          }
3774          $this->to = [];
3775          $this->clearQueuedAddresses('to');
3776      }
3777  
3778      /**
3779       * Clear all CC recipients.
3780       */
3781      public function clearCCs()
3782      {
3783          foreach ($this->cc as $cc) {
3784              unset($this->all_recipients[strtolower($cc[0])]);
3785          }
3786          $this->cc = [];
3787          $this->clearQueuedAddresses('cc');
3788      }
3789  
3790      /**
3791       * Clear all BCC recipients.
3792       */
3793      public function clearBCCs()
3794      {
3795          foreach ($this->bcc as $bcc) {
3796              unset($this->all_recipients[strtolower($bcc[0])]);
3797          }
3798          $this->bcc = [];
3799          $this->clearQueuedAddresses('bcc');
3800      }
3801  
3802      /**
3803       * Clear all ReplyTo recipients.
3804       */
3805      public function clearReplyTos()
3806      {
3807          $this->ReplyTo = [];
3808          $this->ReplyToQueue = [];
3809      }
3810  
3811      /**
3812       * Clear all recipient types.
3813       */
3814      public function clearAllRecipients()
3815      {
3816          $this->to = [];
3817          $this->cc = [];
3818          $this->bcc = [];
3819          $this->all_recipients = [];
3820          $this->RecipientsQueue = [];
3821      }
3822  
3823      /**
3824       * Clear all filesystem, string, and binary attachments.
3825       */
3826      public function clearAttachments()
3827      {
3828          $this->attachment = [];
3829      }
3830  
3831      /**
3832       * Clear all custom headers.
3833       */
3834      public function clearCustomHeaders()
3835      {
3836          $this->CustomHeader = [];
3837      }
3838  
3839      /**
3840       * Add an error message to the error container.
3841       *
3842       * @param string $msg
3843       */
3844      protected function setError($msg)
3845      {
3846          ++$this->error_count;
3847          if ('smtp' === $this->Mailer && null !== $this->smtp) {
3848              $lasterror = $this->smtp->getError();
3849              if (!empty($lasterror['error'])) {
3850                  $msg .= $this->lang('smtp_error') . $lasterror['error'];
3851                  if (!empty($lasterror['detail'])) {
3852                      $msg .= ' Detail: ' . $lasterror['detail'];
3853                  }
3854                  if (!empty($lasterror['smtp_code'])) {
3855                      $msg .= ' SMTP code: ' . $lasterror['smtp_code'];
3856                  }
3857                  if (!empty($lasterror['smtp_code_ex'])) {
3858                      $msg .= ' Additional SMTP info: ' . $lasterror['smtp_code_ex'];
3859                  }
3860              }
3861          }
3862          $this->ErrorInfo = $msg;
3863      }
3864  
3865      /**
3866       * Return an RFC 822 formatted date.
3867       *
3868       * @return string
3869       */
3870      public static function rfcDate()
3871      {
3872          // Set the time zone to whatever the default is to avoid 500 errors
3873          // Will default to UTC if it's not set properly in php.ini
3874          date_default_timezone_set(@date_default_timezone_get());
3875  
3876          return date('D, j M Y H:i:s O');
3877      }
3878  
3879      /**
3880       * Get the server hostname.
3881       * Returns 'localhost.localdomain' if unknown.
3882       *
3883       * @return string
3884       */
3885      protected function serverHostname()
3886      {
3887          $result = '';
3888          if (!empty($this->Hostname)) {
3889              $result = $this->Hostname;
3890          } elseif (isset($_SERVER) && array_key_exists('SERVER_NAME', $_SERVER)) {
3891              $result = $_SERVER['SERVER_NAME'];
3892          } elseif (function_exists('gethostname') && gethostname() !== false) {
3893              $result = gethostname();
3894          } elseif (php_uname('n') !== false) {
3895              $result = php_uname('n');
3896          }
3897          if (!static::isValidHost($result)) {
3898              return 'localhost.localdomain';
3899          }
3900  
3901          return $result;
3902      }
3903  
3904      /**
3905       * Validate whether a string contains a valid value to use as a hostname or IP address.
3906       * IPv6 addresses must include [], e.g. `[::1]`, not just `::1`.
3907       *
3908       * @param string $host The host name or IP address to check
3909       *
3910       * @return bool
3911       */
3912      public static function isValidHost($host)
3913      {
3914          //Simple syntax limits
3915          if (
3916              empty($host)
3917              || !is_string($host)
3918              || strlen($host) > 256
3919              || !preg_match('/^([a-zA-Z\d.-]*|\[[a-fA-F\d:]+])$/', $host)
3920          ) {
3921              return false;
3922          }
3923          //Looks like a bracketed IPv6 address
3924          if (strlen($host) > 2 && substr($host, 0, 1) === '[' && substr($host, -1, 1) === ']') {
3925              return filter_var(substr($host, 1, -1), FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) !== false;
3926          }
3927          //If removing all the dots results in a numeric string, it must be an IPv4 address.
3928          //Need to check this first because otherwise things like `999.0.0.0` are considered valid host names
3929          if (is_numeric(str_replace('.', '', $host))) {
3930              //Is it a valid IPv4 address?
3931              return filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false;
3932          }
3933          if (filter_var('http://' . $host, FILTER_VALIDATE_URL) !== false) {
3934              //Is it a syntactically valid hostname?
3935              return true;
3936          }
3937  
3938          return false;
3939      }
3940  
3941      /**
3942       * Get an error message in the current language.
3943       *
3944       * @param string $key
3945       *
3946       * @return string
3947       */
3948      protected function lang($key)
3949      {
3950          if (count($this->language) < 1) {
3951              $this->setLanguage(); // set the default language
3952          }
3953  
3954          if (array_key_exists($key, $this->language)) {
3955              if ('smtp_connect_failed' === $key) {
3956                  //Include a link to troubleshooting docs on SMTP connection failure
3957                  //this is by far the biggest cause of support questions
3958                  //but it's usually not PHPMailer's fault.
3959                  return $this->language[$key] . ' https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting';
3960              }
3961  
3962              return $this->language[$key];
3963          }
3964  
3965          //Return the key as a fallback
3966          return $key;
3967      }
3968  
3969      /**
3970       * Check if an error occurred.
3971       *
3972       * @return bool True if an error did occur
3973       */
3974      public function isError()
3975      {
3976          return $this->error_count > 0;
3977      }
3978  
3979      /**
3980       * Add a custom header.
3981       * $name value can be overloaded to contain
3982       * both header name and value (name:value).
3983       *
3984       * @param string      $name  Custom header name
3985       * @param string|null $value Header value
3986       *
3987       * @throws Exception
3988       */
3989      public function addCustomHeader($name, $value = null)
3990      {
3991          if (null === $value && strpos($name, ':') !== false) {
3992              // Value passed in as name:value
3993              list($name, $value) = explode(':', $name, 2);
3994          }
3995          $name = trim($name);
3996          $value = trim($value);
3997          //Ensure name is not empty, and that neither name nor value contain line breaks
3998          if (empty($name) || strpbrk($name . $value, "\r\n") !== false) {
3999              if ($this->exceptions) {
4000                  throw new Exception('Invalid header name or value');
4001              }
4002  
4003              return false;
4004          }
4005          $this->CustomHeader[] = [$name, $value];
4006  
4007          return true;
4008      }
4009  
4010      /**
4011       * Returns all custom headers.
4012       *
4013       * @return array
4014       */
4015      public function getCustomHeaders()
4016      {
4017          return $this->CustomHeader;
4018      }
4019  
4020      /**
4021       * Create a message body from an HTML string.
4022       * Automatically inlines images and creates a plain-text version by converting the HTML,
4023       * overwriting any existing values in Body and AltBody.
4024       * Do not source $message content from user input!
4025       * $basedir is prepended when handling relative URLs, e.g. <img src="/images/a.png"> and must not be empty
4026       * will look for an image file in $basedir/images/a.png and convert it to inline.
4027       * If you don't provide a $basedir, relative paths will be left untouched (and thus probably break in email)
4028       * Converts data-uri images into embedded attachments.
4029       * If you don't want to apply these transformations to your HTML, just set Body and AltBody directly.
4030       *
4031       * @param string        $message  HTML message string
4032       * @param string        $basedir  Absolute path to a base directory to prepend to relative paths to images
4033       * @param bool|callable $advanced Whether to use the internal HTML to text converter
4034       *                                or your own custom converter
4035       * @return string The transformed message body
4036       *
4037       * @throws Exception
4038       *
4039       * @see PHPMailer::html2text()
4040       */
4041      public function msgHTML($message, $basedir = '', $advanced = false)
4042      {
4043          preg_match_all('/(?<!-)(src|background)=["\'](.*)["\']/Ui', $message, $images);
4044          if (array_key_exists(2, $images)) {
4045              if (strlen($basedir) > 1 && '/' !== substr($basedir, -1)) {
4046                  // Ensure $basedir has a trailing /
4047                  $basedir .= '/';
4048              }
4049              foreach ($images[2] as $imgindex => $url) {
4050                  // Convert data URIs into embedded images
4051                  //e.g. "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="
4052                  $match = [];
4053                  if (preg_match('#^data:(image/(?:jpe?g|gif|png));?(base64)?,(.+)#', $url, $match)) {
4054                      if (count($match) === 4 && static::ENCODING_BASE64 === $match[2]) {
4055                          $data = base64_decode($match[3]);
4056                      } elseif ('' === $match[2]) {
4057                          $data = rawurldecode($match[3]);
4058                      } else {
4059                          //Not recognised so leave it alone
4060                          continue;
4061                      }
4062                      //Hash the decoded data, not the URL, so that the same data-URI image used in multiple places
4063                      //will only be embedded once, even if it used a different encoding
4064                      $cid = substr(hash('sha256', $data), 0, 32) . '@phpmailer.0'; // RFC2392 S 2
4065  
4066                      if (!$this->cidExists($cid)) {
4067                          $this->addStringEmbeddedImage(
4068                              $data,
4069                              $cid,
4070                              'embed' . $imgindex,
4071                              static::ENCODING_BASE64,
4072                              $match[1]
4073                          );
4074                      }
4075                      $message = str_replace(
4076                          $images[0][$imgindex],
4077                          $images[1][$imgindex] . '="cid:' . $cid . '"',
4078                          $message
4079                      );
4080                      continue;
4081                  }
4082                  if (
4083                      // Only process relative URLs if a basedir is provided (i.e. no absolute local paths)
4084                      !empty($basedir)
4085                      // Ignore URLs containing parent dir traversal (..)
4086                      && (strpos($url, '..') === false)
4087                      // Do not change urls that are already inline images
4088                      && 0 !== strpos($url, 'cid:')
4089                      // Do not change absolute URLs, including anonymous protocol
4090                      && !preg_match('#^[a-z][a-z0-9+.-]*:?//#i', $url)
4091                  ) {
4092                      $filename = static::mb_pathinfo($url, PATHINFO_BASENAME);
4093                      $directory = dirname($url);
4094                      if ('.' === $directory) {
4095                          $directory = '';
4096                      }
4097                      // RFC2392 S 2
4098                      $cid = substr(hash('sha256', $url), 0, 32) . '@phpmailer.0';
4099                      if (strlen($basedir) > 1 && '/' !== substr($basedir, -1)) {
4100                          $basedir .= '/';
4101                      }
4102                      if (strlen($directory) > 1 && '/' !== substr($directory, -1)) {
4103                          $directory .= '/';
4104                      }
4105                      if (
4106                          $this->addEmbeddedImage(
4107                              $basedir . $directory . $filename,
4108                              $cid,
4109                              $filename,
4110                              static::ENCODING_BASE64,
4111                              static::_mime_types((string) static::mb_pathinfo($filename, PATHINFO_EXTENSION))
4112                          )
4113                      ) {
4114                          $message = preg_replace(
4115                              '/' . $images[1][$imgindex] . '=["\']' . preg_quote($url, '/') . '["\']/Ui',
4116                              $images[1][$imgindex] . '="cid:' . $cid . '"',
4117                              $message
4118                          );
4119                      }
4120                  }
4121              }
4122          }
4123          $this->isHTML();
4124          // Convert all message body line breaks to LE, makes quoted-printable encoding work much better
4125          $this->Body = static::normalizeBreaks($message);
4126          $this->AltBody = static::normalizeBreaks($this->html2text($message, $advanced));
4127          if (!$this->alternativeExists()) {
4128              $this->AltBody = 'This is an HTML-only message. To view it, activate HTML in your email application.'
4129                  . static::$LE;
4130          }
4131  
4132          return $this->Body;
4133      }
4134  
4135      /**
4136       * Convert an HTML string into plain text.
4137       * This is used by msgHTML().
4138       * Note - older versions of this function used a bundled advanced converter
4139       * which was removed for license reasons in #232.
4140       * Example usage:
4141       *
4142       * ```php
4143       * // Use default conversion
4144       * $plain = $mail->html2text($html);
4145       * // Use your own custom converter
4146       * $plain = $mail->html2text($html, function($html) {
4147       *     $converter = new MyHtml2text($html);
4148       *     return $converter->get_text();
4149       * });
4150       * ```
4151       *
4152       * @param string        $html     The HTML text to convert
4153       * @param bool|callable $advanced Any boolean value to use the internal converter,
4154       *                                or provide your own callable for custom conversion
4155       *
4156       * @return string
4157       */
4158      public function html2text($html, $advanced = false)
4159      {
4160          if (is_callable($advanced)) {
4161              return call_user_func($advanced, $html);
4162          }
4163  
4164          return html_entity_decode(
4165              trim(strip_tags(preg_replace('/<(head|title|style|script)[^>]*>.*?<\/\\1>/si', '', $html))),
4166              ENT_QUOTES,
4167              $this->CharSet
4168          );
4169      }
4170  
4171      /**
4172       * Get the MIME type for a file extension.
4173       *
4174       * @param string $ext File extension
4175       *
4176       * @return string MIME type of file
4177       */
4178      public static function _mime_types($ext = '')
4179      {
4180          $mimes = [
4181              'xl' => 'application/excel',
4182              'js' => 'application/javascript',
4183              'hqx' => 'application/mac-binhex40',
4184              'cpt' => 'application/mac-compactpro',
4185              'bin' => 'application/macbinary',
4186              'doc' => 'application/msword',
4187              'word' => 'application/msword',
4188              'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
4189              'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
4190              'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template',
4191              'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
4192              'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
4193              'sldx' => 'application/vnd.openxmlformats-officedocument.presentationml.slide',
4194              'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
4195              'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
4196              'xlam' => 'application/vnd.ms-excel.addin.macroEnabled.12',
4197              'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
4198              'class' => 'application/octet-stream',
4199              'dll' => 'application/octet-stream',
4200              'dms' => 'application/octet-stream',
4201              'exe' => 'application/octet-stream',
4202              'lha' => 'application/octet-stream',
4203              'lzh' => 'application/octet-stream',
4204              'psd' => 'application/octet-stream',
4205              'sea' => 'application/octet-stream',
4206              'so' => 'application/octet-stream',
4207              'oda' => 'application/oda',
4208              'pdf' => 'application/pdf',
4209              'ai' => 'application/postscript',
4210              'eps' => 'application/postscript',
4211              'ps' => 'application/postscript',
4212              'smi' => 'application/smil',
4213              'smil' => 'application/smil',
4214              'mif' => 'application/vnd.mif',
4215              'xls' => 'application/vnd.ms-excel',
4216              'ppt' => 'application/vnd.ms-powerpoint',
4217              'wbxml' => 'application/vnd.wap.wbxml',
4218              'wmlc' => 'application/vnd.wap.wmlc',
4219              'dcr' => 'application/x-director',
4220              'dir' => 'application/x-director',
4221              'dxr' => 'application/x-director',
4222              'dvi' => 'application/x-dvi',
4223              'gtar' => 'application/x-gtar',
4224              'php3' => 'application/x-httpd-php',
4225              'php4' => 'application/x-httpd-php',
4226              'php' => 'application/x-httpd-php',
4227              'phtml' => 'application/x-httpd-php',
4228              'phps' => 'application/x-httpd-php-source',
4229              'swf' => 'application/x-shockwave-flash',
4230              'sit' => 'application/x-stuffit',
4231              'tar' => 'application/x-tar',
4232              'tgz' => 'application/x-tar',
4233              'xht' => 'application/xhtml+xml',
4234              'xhtml' => 'application/xhtml+xml',
4235              'zip' => 'application/zip',
4236              'mid' => 'audio/midi',
4237              'midi' => 'audio/midi',
4238              'mp2' => 'audio/mpeg',
4239              'mp3' => 'audio/mpeg',
4240              'm4a' => 'audio/mp4',
4241              'mpga' => 'audio/mpeg',
4242              'aif' => 'audio/x-aiff',
4243              'aifc' => 'audio/x-aiff',
4244              'aiff' => 'audio/x-aiff',
4245              'ram' => 'audio/x-pn-realaudio',
4246              'rm' => 'audio/x-pn-realaudio',
4247              'rpm' => 'audio/x-pn-realaudio-plugin',
4248              'ra' => 'audio/x-realaudio',
4249              'wav' => 'audio/x-wav',
4250              'mka' => 'audio/x-matroska',
4251              'bmp' => 'image/bmp',
4252              'gif' => 'image/gif',
4253              'jpeg' => 'image/jpeg',
4254              'jpe' => 'image/jpeg',
4255              'jpg' => 'image/jpeg',
4256              'png' => 'image/png',
4257              'tiff' => 'image/tiff',
4258              'tif' => 'image/tiff',
4259              'webp' => 'image/webp',
4260              'avif' => 'image/avif',
4261              'heif' => 'image/heif',
4262              'heifs' => 'image/heif-sequence',
4263              'heic' => 'image/heic',
4264              'heics' => 'image/heic-sequence',
4265              'eml' => 'message/rfc822',
4266              'css' => 'text/css',
4267              'html' => 'text/html',
4268              'htm' => 'text/html',
4269              'shtml' => 'text/html',
4270              'log' => 'text/plain',
4271              'text' => 'text/plain',
4272              'txt' => 'text/plain',
4273              'rtx' => 'text/richtext',
4274              'rtf' => 'text/rtf',
4275              'vcf' => 'text/vcard',
4276              'vcard' => 'text/vcard',
4277              'ics' => 'text/calendar',
4278              'xml' => 'text/xml',
4279              'xsl' => 'text/xml',
4280              'wmv' => 'video/x-ms-wmv',
4281              'mpeg' => 'video/mpeg',
4282              'mpe' => 'video/mpeg',
4283              'mpg' => 'video/mpeg',
4284              'mp4' => 'video/mp4',
4285              'm4v' => 'video/mp4',
4286              'mov' => 'video/quicktime',
4287              'qt' => 'video/quicktime',
4288              'rv' => 'video/vnd.rn-realvideo',
4289              'avi' => 'video/x-msvideo',
4290              'movie' => 'video/x-sgi-movie',
4291              'webm' => 'video/webm',
4292              'mkv' => 'video/x-matroska',
4293          ];
4294          $ext = strtolower($ext);
4295          if (array_key_exists($ext, $mimes)) {
4296              return $mimes[$ext];
4297          }
4298  
4299          return 'application/octet-stream';
4300      }
4301  
4302      /**
4303       * Map a file name to a MIME type.
4304       * Defaults to 'application/octet-stream', i.e.. arbitrary binary data.
4305       *
4306       * @param string $filename A file name or full path, does not need to exist as a file
4307       *
4308       * @return string
4309       */
4310      public static function filenameToType($filename)
4311      {
4312          // In case the path is a URL, strip any query string before getting extension
4313          $qpos = strpos($filename, '?');
4314          if (false !== $qpos) {
4315              $filename = substr($filename, 0, $qpos);
4316          }
4317          $ext = static::mb_pathinfo($filename, PATHINFO_EXTENSION);
4318  
4319          return static::_mime_types($ext);
4320      }
4321  
4322      /**
4323       * Multi-byte-safe pathinfo replacement.
4324       * Drop-in replacement for pathinfo(), but multibyte- and cross-platform-safe.
4325       *
4326       * @see http://www.php.net/manual/en/function.pathinfo.php#107461
4327       *
4328       * @param string     $path    A filename or path, does not need to exist as a file
4329       * @param int|string $options Either a PATHINFO_* constant,
4330       *                            or a string name to return only the specified piece
4331       *
4332       * @return string|array
4333       */
4334      public static function mb_pathinfo($path, $options = null)
4335      {
4336          $ret = ['dirname' => '', 'basename' => '', 'extension' => '', 'filename' => ''];
4337          $pathinfo = [];
4338          if (preg_match('#^(.*?)[\\\\/]*(([^/\\\\]*?)(\.([^.\\\\/]+?)|))[\\\\/.]*$#m', $path, $pathinfo)) {
4339              if (array_key_exists(1, $pathinfo)) {
4340                  $ret['dirname'] = $pathinfo[1];
4341              }
4342              if (array_key_exists(2, $pathinfo)) {
4343                  $ret['basename'] = $pathinfo[2];
4344              }
4345              if (array_key_exists(5, $pathinfo)) {
4346                  $ret['extension'] = $pathinfo[5];
4347              }
4348              if (array_key_exists(3, $pathinfo)) {
4349                  $ret['filename'] = $pathinfo[3];
4350              }
4351          }
4352          switch ($options) {
4353              case PATHINFO_DIRNAME:
4354              case 'dirname':
4355                  return $ret['dirname'];
4356              case PATHINFO_BASENAME:
4357              case 'basename':
4358                  return $ret['basename'];
4359              case PATHINFO_EXTENSION:
4360              case 'extension':
4361                  return $ret['extension'];
4362              case PATHINFO_FILENAME:
4363              case 'filename':
4364                  return $ret['filename'];
4365              default:
4366                  return $ret;
4367          }
4368      }
4369  
4370      /**
4371       * Set or reset instance properties.
4372       * You should avoid this function - it's more verbose, less efficient, more error-prone and
4373       * harder to debug than setting properties directly.
4374       * Usage Example:
4375       * `$mail->set('SMTPSecure', static::ENCRYPTION_STARTTLS);`
4376       *   is the same as:
4377       * `$mail->SMTPSecure = static::ENCRYPTION_STARTTLS;`.
4378       *
4379       * @param string $name  The property name to set
4380       * @param mixed  $value The value to set the property to
4381       *
4382       * @return bool
4383       */
4384      public function set($name, $value = '')
4385      {
4386          if (property_exists($this, $name)) {
4387              $this->$name = $value;
4388  
4389              return true;
4390          }
4391          $this->setError($this->lang('variable_set') . $name);
4392  
4393          return false;
4394      }
4395  
4396      /**
4397       * Strip newlines to prevent header injection.
4398       *
4399       * @param string $str
4400       *
4401       * @return string
4402       */
4403      public function secureHeader($str)
4404      {
4405          return trim(str_replace(["\r", "\n"], '', $str));
4406      }
4407  
4408      /**
4409       * Normalize line breaks in a string.
4410       * Converts UNIX LF, Mac CR and Windows CRLF line breaks into a single line break format.
4411       * Defaults to CRLF (for message bodies) and preserves consecutive breaks.
4412       *
4413       * @param string $text
4414       * @param string $breaktype What kind of line break to use; defaults to static::$LE
4415       *
4416       * @return string
4417       */
4418      public static function normalizeBreaks($text, $breaktype = null)
4419      {
4420          if (null === $breaktype) {
4421              $breaktype = static::$LE;
4422          }
4423          // Normalise to \n
4424          $text = str_replace([self::CRLF, "\r"], "\n", $text);
4425          // Now convert LE as needed
4426          if ("\n" !== $breaktype) {
4427              $text = str_replace("\n", $breaktype, $text);
4428          }
4429  
4430          return $text;
4431      }
4432  
4433      /**
4434       * Remove trailing breaks from a string.
4435       *
4436       * @param string $text
4437       *
4438       * @return string The text to remove breaks from
4439       */
4440      public static function stripTrailingWSP($text)
4441      {
4442          return rtrim($text, " \r\n\t");
4443      }
4444  
4445      /**
4446       * Return the current line break format string.
4447       *
4448       * @return string
4449       */
4450      public static function getLE()
4451      {
4452          return static::$LE;
4453      }
4454  
4455      /**
4456       * Set the line break format string, e.g. "\r\n".
4457       *
4458       * @param string $le
4459       */
4460      protected static function setLE($le)
4461      {
4462          static::$LE = $le;
4463      }
4464  
4465      /**
4466       * Set the public and private key files and password for S/MIME signing.
4467       *
4468       * @param string $cert_filename
4469       * @param string $key_filename
4470       * @param string $key_pass            Password for private key
4471       * @param string $extracerts_filename Optional path to chain certificate
4472       */
4473      public function sign($cert_filename, $key_filename, $key_pass, $extracerts_filename = '')
4474      {
4475          $this->sign_cert_file = $cert_filename;
4476          $this->sign_key_file = $key_filename;
4477          $this->sign_key_pass = $key_pass;
4478          $this->sign_extracerts_file = $extracerts_filename;
4479      }
4480  
4481      /**
4482       * Quoted-Printable-encode a DKIM header.
4483       *
4484       * @param string $txt
4485       *
4486       * @return string
4487       */
4488      public function DKIM_QP($txt)
4489      {
4490          $line = '';
4491          $len = strlen($txt);
4492          for ($i = 0; $i < $len; ++$i) {
4493              $ord = ord($txt[$i]);
4494              if (((0x21 <= $ord) && ($ord <= 0x3A)) || $ord === 0x3C || ((0x3E <= $ord) && ($ord <= 0x7E))) {
4495                  $line .= $txt[$i];
4496              } else {
4497                  $line .= '=' . sprintf('%02X', $ord);
4498              }
4499          }
4500  
4501          return $line;
4502      }
4503  
4504      /**
4505       * Generate a DKIM signature.
4506       *
4507       * @param string $signHeader
4508       *
4509       * @throws Exception
4510       *
4511       * @return string The DKIM signature value
4512       */
4513      public function DKIM_Sign($signHeader)
4514      {
4515          if (!defined('PKCS7_TEXT')) {
4516              if ($this->exceptions) {
4517                  throw new Exception($this->lang('extension_missing') . 'openssl');
4518              }
4519  
4520              return '';
4521          }
4522          $privKeyStr = !empty($this->DKIM_private_string) ?
4523              $this->DKIM_private_string :
4524              file_get_contents($this->DKIM_private);
4525          if ('' !== $this->DKIM_passphrase) {
4526              $privKey = openssl_pkey_get_private($privKeyStr, $this->DKIM_passphrase);
4527          } else {
4528              $privKey = openssl_pkey_get_private($privKeyStr);
4529          }
4530          if (openssl_sign($signHeader, $signature, $privKey, 'sha256WithRSAEncryption')) {
4531              if (PHP_MAJOR_VERSION < 8) {
4532                  openssl_pkey_free($privKey);
4533              }
4534  
4535              return base64_encode($signature);
4536          }
4537          if (PHP_MAJOR_VERSION < 8) {
4538              openssl_pkey_free($privKey);
4539          }
4540  
4541          return '';
4542      }
4543  
4544      /**
4545       * Generate a DKIM canonicalization header.
4546       * Uses the 'relaxed' algorithm from RFC6376 section 3.4.2.
4547       * Canonicalized headers should *always* use CRLF, regardless of mailer setting.
4548       *
4549       * @see https://tools.ietf.org/html/rfc6376#section-3.4.2
4550       *
4551       * @param string $signHeader Header
4552       *
4553       * @return string
4554       */
4555      public function DKIM_HeaderC($signHeader)
4556      {
4557          //Normalize breaks to CRLF (regardless of the mailer)
4558          $signHeader = static::normalizeBreaks($signHeader, self::CRLF);
4559          //Unfold header lines
4560          //Note PCRE \s is too broad a definition of whitespace; RFC5322 defines it as `[ \t]`
4561          //@see https://tools.ietf.org/html/rfc5322#section-2.2
4562          //That means this may break if you do something daft like put vertical tabs in your headers.
4563          $signHeader = preg_replace('/\r\n[ \t]+/', ' ', $signHeader);
4564          //Break headers out into an array
4565          $lines = explode(self::CRLF, $signHeader);
4566          foreach ($lines as $key => $line) {
4567              //If the header is missing a :, skip it as it's invalid
4568              //This is likely to happen because the explode() above will also split
4569              //on the trailing LE, leaving an empty line
4570              if (strpos($line, ':') === false) {
4571                  continue;
4572              }
4573              list($heading, $value) = explode(':', $line, 2);
4574              //Lower-case header name
4575              $heading = strtolower($heading);
4576              //Collapse white space within the value, also convert WSP to space
4577              $value = preg_replace('/[ \t]+/', ' ', $value);
4578              //RFC6376 is slightly unclear here - it says to delete space at the *end* of each value
4579              //But then says to delete space before and after the colon.
4580              //Net result is the same as trimming both ends of the value.
4581              //By elimination, the same applies to the field name
4582              $lines[$key] = trim($heading, " \t") . ':' . trim($value, " \t");
4583          }
4584  
4585          return implode(self::CRLF, $lines);
4586      }
4587  
4588      /**
4589       * Generate a DKIM canonicalization body.
4590       * Uses the 'simple' algorithm from RFC6376 section 3.4.3.
4591       * Canonicalized bodies should *always* use CRLF, regardless of mailer setting.
4592       *
4593       * @see https://tools.ietf.org/html/rfc6376#section-3.4.3
4594       *
4595       * @param string $body Message Body
4596       *
4597       * @return string
4598       */
4599      public function DKIM_BodyC($body)
4600      {
4601          if (empty($body)) {
4602              return self::CRLF;
4603          }
4604          // Normalize line endings to CRLF
4605          $body = static::normalizeBreaks($body, self::CRLF);
4606  
4607          //Reduce multiple trailing line breaks to a single one
4608          return static::stripTrailingWSP($body) . self::CRLF;
4609      }
4610  
4611      /**
4612       * Create the DKIM header and body in a new message header.
4613       *
4614       * @param string $headers_line Header lines
4615       * @param string $subject      Subject
4616       * @param string $body         Body
4617       *
4618       * @throws Exception
4619       *
4620       * @return string
4621       */
4622      public function DKIM_Add($headers_line, $subject, $body)
4623      {
4624          $DKIMsignatureType = 'rsa-sha256'; // Signature & hash algorithms
4625          $DKIMcanonicalization = 'relaxed/simple'; // Canonicalization methods of header & body
4626          $DKIMquery = 'dns/txt'; // Query method
4627          $DKIMtime = time();
4628          //Always sign these headers without being asked
4629          //Recommended list from https://tools.ietf.org/html/rfc6376#section-5.4.1
4630          $autoSignHeaders = [
4631              'from',
4632              'to',
4633              'cc',
4634              'date',
4635              'subject',
4636              'reply-to',
4637              'message-id',
4638              'content-type',
4639              'mime-version',
4640              'x-mailer',
4641          ];
4642          if (stripos($headers_line, 'Subject') === false) {
4643              $headers_line .= 'Subject: ' . $subject . static::$LE;
4644          }
4645          $headerLines = explode(static::$LE, $headers_line);
4646          $currentHeaderLabel = '';
4647          $currentHeaderValue = '';
4648          $parsedHeaders = [];
4649          $headerLineIndex = 0;
4650          $headerLineCount = count($headerLines);
4651          foreach ($headerLines as $headerLine) {
4652              $matches = [];
4653              if (preg_match('/^([^ \t]*?)(?::[ \t]*)(.*)$/', $headerLine, $matches)) {
4654                  if ($currentHeaderLabel !== '') {
4655                      //We were previously in another header; This is the start of a new header, so save the previous one
4656                      $parsedHeaders[] = ['label' => $currentHeaderLabel, 'value' => $currentHeaderValue];
4657                  }
4658                  $currentHeaderLabel = $matches[1];
4659                  $currentHeaderValue = $matches[2];
4660              } elseif (preg_match('/^[ \t]+(.*)$/', $headerLine, $matches)) {
4661                  //This is a folded continuation of the current header, so unfold it
4662                  $currentHeaderValue .= ' ' . $matches[1];
4663              }
4664              ++$headerLineIndex;
4665              if ($headerLineIndex >= $headerLineCount) {
4666                  //This was the last line, so finish off this header
4667                  $parsedHeaders[] = ['label' => $currentHeaderLabel, 'value' => $currentHeaderValue];
4668              }
4669          }
4670          $copiedHeaders = [];
4671          $headersToSignKeys = [];
4672          $headersToSign = [];
4673          foreach ($parsedHeaders as $header) {
4674              //Is this header one that must be included in the DKIM signature?
4675              if (in_array(strtolower($header['label']), $autoSignHeaders, true)) {
4676                  $headersToSignKeys[] = $header['label'];
4677                  $headersToSign[] = $header['label'] . ': ' . $header['value'];
4678                  if ($this->DKIM_copyHeaderFields) {
4679                      $copiedHeaders[] = $header['label'] . ':' . //Note no space after this, as per RFC
4680                          str_replace('|', '=7C', $this->DKIM_QP($header['value']));
4681                  }
4682                  continue;
4683              }
4684              //Is this an extra custom header we've been asked to sign?
4685              if (in_array($header['label'], $this->DKIM_extraHeaders, true)) {
4686                  //Find its value in custom headers
4687                  foreach ($this->CustomHeader as $customHeader) {
4688                      if ($customHeader[0] === $header['label']) {
4689                          $headersToSignKeys[] = $header['label'];
4690                          $headersToSign[] = $header['label'] . ': ' . $header['value'];
4691                          if ($this->DKIM_copyHeaderFields) {
4692                              $copiedHeaders[] = $header['label'] . ':' . //Note no space after this, as per RFC
4693                                  str_replace('|', '=7C', $this->DKIM_QP($header['value']));
4694                          }
4695                          //Skip straight to the next header
4696                          continue 2;
4697                      }
4698                  }
4699              }
4700          }
4701          $copiedHeaderFields = '';
4702          if ($this->DKIM_copyHeaderFields && count($copiedHeaders) > 0) {
4703              //Assemble a DKIM 'z' tag
4704              $copiedHeaderFields = ' z=';
4705              $first = true;
4706              foreach ($copiedHeaders as $copiedHeader) {
4707                  if (!$first) {
4708                      $copiedHeaderFields .= static::$LE . ' |';
4709                  }
4710                  //Fold long values
4711                  if (strlen($copiedHeader) > self::STD_LINE_LENGTH - 3) {
4712                      $copiedHeaderFields .= substr(
4713                          chunk_split($copiedHeader, self::STD_LINE_LENGTH - 3, static::$LE . self::FWS),
4714                          0,
4715                          -strlen(static::$LE . self::FWS)
4716                      );
4717                  } else {
4718                      $copiedHeaderFields .= $copiedHeader;
4719                  }
4720                  $first = false;
4721              }
4722              $copiedHeaderFields .= ';' . static::$LE;
4723          }
4724          $headerKeys = ' h=' . implode(':', $headersToSignKeys) . ';' . static::$LE;
4725          $headerValues = implode(static::$LE, $headersToSign);
4726          $body = $this->DKIM_BodyC($body);
4727          $DKIMb64 = base64_encode(pack('H*', hash('sha256', $body))); // Base64 of packed binary SHA-256 hash of body
4728          $ident = '';
4729          if ('' !== $this->DKIM_identity) {
4730              $ident = ' i=' . $this->DKIM_identity . ';' . static::$LE;
4731          }
4732          //The DKIM-Signature header is included in the signature *except for* the value of the `b` tag
4733          //which is appended after calculating the signature
4734          //https://tools.ietf.org/html/rfc6376#section-3.5
4735          $dkimSignatureHeader = 'DKIM-Signature: v=1;' .
4736              ' d=' . $this->DKIM_domain . ';' .
4737              ' s=' . $this->DKIM_selector . ';' . static::$LE .
4738              ' a=' . $DKIMsignatureType . ';' .
4739              ' q=' . $DKIMquery . ';' .
4740              ' t=' . $DKIMtime . ';' .
4741              ' c=' . $DKIMcanonicalization . ';' . static::$LE .
4742              $headerKeys .
4743              $ident .
4744              $copiedHeaderFields .
4745              ' bh=' . $DKIMb64 . ';' . static::$LE .
4746              ' b=';
4747          //Canonicalize the set of headers
4748          $canonicalizedHeaders = $this->DKIM_HeaderC(
4749              $headerValues . static::$LE . $dkimSignatureHeader
4750          );
4751          $signature = $this->DKIM_Sign($canonicalizedHeaders);
4752          $signature = trim(chunk_split($signature, self::STD_LINE_LENGTH - 3, static::$LE . self::FWS));
4753  
4754          return static::normalizeBreaks($dkimSignatureHeader . $signature);
4755      }
4756  
4757      /**
4758       * Detect if a string contains a line longer than the maximum line length
4759       * allowed by RFC 2822 section 2.1.1.
4760       *
4761       * @param string $str
4762       *
4763       * @return bool
4764       */
4765      public static function hasLineLongerThanMax($str)
4766      {
4767          return (bool) preg_match('/^(.{' . (self::MAX_LINE_LENGTH + strlen(static::$LE)) . ',})/m', $str);
4768      }
4769  
4770      /**
4771       * If a string contains any "special" characters, double-quote the name,
4772       * and escape any double quotes with a backslash.
4773       *
4774       * @param string $str
4775       *
4776       * @return string
4777       *
4778       * @see RFC822 3.4.1
4779       */
4780      public static function quotedString($str)
4781      {
4782          if (preg_match('/[ ()<>@,;:"\/\[\]?=]/', $str)) {
4783              //If the string contains any of these chars, it must be double-quoted
4784              //and any double quotes must be escaped with a backslash
4785              return '"' . str_replace('"', '\\"', $str) . '"';
4786          }
4787  
4788          //Return the string untouched, it doesn't need quoting
4789          return $str;
4790      }
4791  
4792      /**
4793       * Allows for public read access to 'to' property.
4794       * Before the send() call, queued addresses (i.e. with IDN) are not yet included.
4795       *
4796       * @return array
4797       */
4798      public function getToAddresses()
4799      {
4800          return $this->to;
4801      }
4802  
4803      /**
4804       * Allows for public read access to 'cc' property.
4805       * Before the send() call, queued addresses (i.e. with IDN) are not yet included.
4806       *
4807       * @return array
4808       */
4809      public function getCcAddresses()
4810      {
4811          return $this->cc;
4812      }
4813  
4814      /**
4815       * Allows for public read access to 'bcc' property.
4816       * Before the send() call, queued addresses (i.e. with IDN) are not yet included.
4817       *
4818       * @return array
4819       */
4820      public function getBccAddresses()
4821      {
4822          return $this->bcc;
4823      }
4824  
4825      /**
4826       * Allows for public read access to 'ReplyTo' property.
4827       * Before the send() call, queued addresses (i.e. with IDN) are not yet included.
4828       *
4829       * @return array
4830       */
4831      public function getReplyToAddresses()
4832      {
4833          return $this->ReplyTo;
4834      }
4835  
4836      /**
4837       * Allows for public read access to 'all_recipients' property.
4838       * Before the send() call, queued addresses (i.e. with IDN) are not yet included.
4839       *
4840       * @return array
4841       */
4842      public function getAllRecipientAddresses()
4843      {
4844          return $this->all_recipients;
4845      }
4846  
4847      /**
4848       * Perform a callback.
4849       *
4850       * @param bool   $isSent
4851       * @param array  $to
4852       * @param array  $cc