: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
public function wrapText($message, $length, $qp_mode = false)
$soft_break = sprintf(' =%s', static::$LE);
$soft_break = static::$LE;
//If utf-8 encoding is used, we will need to make sure we don't
//split multibyte characters when we wrap
$is_utf8 = static::CHARSET_UTF8 === strtolower($this->CharSet);
$lelen = strlen(static::$LE);
$crlflen = strlen(static::$LE);
$message = static::normalizeBreaks($message);
//Remove a trailing line break
if (substr($message, -$lelen) === static::$LE) {
$message = substr($message, 0, -$lelen);
//Split message into lines
$lines = explode(static::$LE, $message);
//Message will be rebuilt in here
foreach ($lines as $line) {
$words = explode(' ', $line);
foreach ($words as $word) {
if ($qp_mode && (strlen($word) > $length)) {
$space_left = $length - strlen($buf) - $crlflen;
$len = $this->utf8CharBoundary($word, $len);
} elseif ('=' === substr($word, $len - 1, 1)) {
} elseif ('=' === substr($word, $len - 2, 1)) {
$part = substr($word, 0, $len);
$word = substr($word, $len);
$message .= $buf . sprintf('=%s', static::$LE);
$message .= $buf . $soft_break;
$len = $this->utf8CharBoundary($word, $len);
} elseif ('=' === substr($word, $len - 1, 1)) {
} elseif ('=' === substr($word, $len - 2, 1)) {
$part = substr($word, 0, $len);
$word = (string) substr($word, $len);
$message .= $part . sprintf('=%s', static::$LE);
if ('' !== $buf_o && strlen($buf) > $length) {
$message .= $buf_o . $soft_break;
$message .= $buf . static::$LE;
* Find the last character boundary prior to $maxLength in a utf-8
* quoted-printable encoded string.
* Original written by Colin Brown.
* @param string $encodedText utf-8 QP text
* @param int $maxLength Find the last character boundary prior to this length
public function utf8CharBoundary($encodedText, $maxLength)
while (!$foundSplitPos) {
$lastChunk = substr($encodedText, $maxLength - $lookBack, $lookBack);
$encodedCharPos = strpos($lastChunk, '=');
if (false !== $encodedCharPos) {
//Found start of encoded character byte within $lookBack block.
//Check the encoded byte value (the 2 chars after the '=')
$hex = substr($encodedText, $maxLength - $lookBack + $encodedCharPos + 1, 2);
//If the encoded char was found at pos 0, it will fit
//otherwise reduce maxLength to start of the encoded char
if ($encodedCharPos > 0) {
$maxLength -= $lookBack - $encodedCharPos;
//First byte of a multi byte character
//Reduce maxLength to split at start of character
$maxLength -= $lookBack - $encodedCharPos;
//Middle byte of a multi byte character, look further back
//No encoded character found
* Apply word wrapping to the message body.
* Wraps the message body to the number of chars set in the WordWrap property.
* You should only do this to plain-text bodies as wrapping HTML tags may break them.
* This is called automatically by createBody(), so you don't need to call it yourself.
public function setWordWrap()
if ($this->WordWrap < 1) {
switch ($this->message_type) {
case 'alt_inline_attach':
$this->AltBody = $this->wrapText($this->AltBody, $this->WordWrap);
$this->Body = $this->wrapText($this->Body, $this->WordWrap);
* Assemble message headers.
* @return string The assembled headers
public function createHeader()
$result .= $this->headerLine('Date', '' === $this->MessageDate ? self::rfcDate() : $this->MessageDate);
//The To header is created automatically by mail(), so needs to be omitted here
if ('mail' !== $this->Mailer) {
foreach ($this->to as $toaddr) {
$this->SingleToArray[] = $this->addrFormat($toaddr);
} elseif (count($this->to) > 0) {
$result .= $this->addrAppend('To', $this->to);
} elseif (count($this->cc) === 0) {
$result .= $this->headerLine('To', 'undisclosed-recipients:;');
$result .= $this->addrAppend('From', [[trim($this->From), $this->FromName]]);
//sendmail and mail() extract Cc from the header before sending
if (count($this->cc) > 0) {
$result .= $this->addrAppend('Cc', $this->cc);
//sendmail and mail() extract Bcc from the header before sending
'sendmail' === $this->Mailer || 'qmail' === $this->Mailer || 'mail' === $this->Mailer
$result .= $this->addrAppend('Bcc', $this->bcc);
if (count($this->ReplyTo) > 0) {
$result .= $this->addrAppend('Reply-To', $this->ReplyTo);
//mail() sets the subject itself
if ('mail' !== $this->Mailer) {
$result .= $this->headerLine('Subject', $this->encodeHeader($this->secureHeader($this->Subject)));
//Only allow a custom message ID if it conforms to RFC 5322 section 3.6.4
//https://tools.ietf.org/html/rfc5322#section-3.6.4
'' !== $this->MessageID &&
'/^<((([a-z\d!#$%&\'*+\/=?^_`{|}~-]+(\.[a-z\d!#$%&\'*+\/=?^_`{|}~-]+)*)' .
'|("(([\x01-\x08\x0B\x0C\x0E-\x1F\x7F]|[\x21\x23-\x5B\x5D-\x7E])' .
'|(\\[\x01-\x09\x0B\x0C\x0E-\x7F]))*"))@(([a-z\d!#$%&\'*+\/=?^_`{|}~-]+' .
'(\.[a-z\d!#$%&\'*+\/=?^_`{|}~-]+)*)|(\[(([\x01-\x08\x0B\x0C\x0E-\x1F\x7F]' .
'|[\x21-\x5A\x5E-\x7E])|(\\[\x01-\x09\x0B\x0C\x0E-\x7F]))*\])))>$/Di',
$this->lastMessageID = $this->MessageID;
$this->lastMessageID = sprintf('<%s@%s>', $this->uniqueid, $this->serverHostname());
$result .= $this->headerLine('Message-ID', $this->lastMessageID);
if (null !== $this->Priority) {
$result .= $this->headerLine('X-Priority', $this->Priority);
if ('' === $this->XMailer) {
//Empty string for default X-Mailer header
$result .= $this->headerLine(
'PHPMailer ' . self::VERSION . ' (https://github.com/PHPMailer/PHPMailer)'
} elseif (is_string($this->XMailer) && trim($this->XMailer) !== '') {
$result .= $this->headerLine('X-Mailer', trim($this->XMailer));
} //Other values result in no X-Mailer header
if ('' !== $this->ConfirmReadingTo) {
$result .= $this->headerLine('Disposition-Notification-To', '<' . $this->ConfirmReadingTo . '>');
foreach ($this->CustomHeader as $header) {
$result .= $this->headerLine(
$this->encodeHeader(trim($header[1]))
if (!$this->sign_key_file) {
$result .= $this->headerLine('MIME-Version', '1.0');
$result .= $this->getMailMIME();
* Get the message MIME type headers.
public function getMailMIME()
switch ($this->message_type) {
$result .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_RELATED . ';');
$result .= $this->textLine(' boundary="' . $this->boundary[1] . '"');
case 'alt_inline_attach':
$result .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_MIXED . ';');
$result .= $this->textLine(' boundary="' . $this->boundary[1] . '"');
$result .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_ALTERNATIVE . ';');
$result .= $this->textLine(' boundary="' . $this->boundary[1] . '"');
//Catches case 'plain': and case '':
$result .= $this->textLine('Content-Type: ' . $this->ContentType . '; charset=' . $this->CharSet);
//RFC1341 part 5 says 7bit is assumed if not specified
if (static::ENCODING_7BIT !== $this->Encoding) {
//RFC 2045 section 6.4 says multipart MIME parts may only use 7bit, 8bit or binary CTE
if (static::ENCODING_8BIT === $this->Encoding) {
$result .= $this->headerLine('Content-Transfer-Encoding', static::ENCODING_8BIT);
//The only remaining alternatives are quoted-printable and base64, which are both 7bit compatible
$result .= $this->headerLine('Content-Transfer-Encoding', $this->Encoding);
* Returns the whole MIME message.
* Includes complete headers and body.
* Only valid post preSend().
* @see PHPMailer::preSend()
public function getSentMIMEMessage()
return static::stripTrailingWSP($this->MIMEHeader . $this->mailHeader) .
static::$LE . static::$LE . $this->MIMEBody;
* Create a unique ID to use for boundaries.
protected function generateId()
$len = 32; //32 bytes = 256 bits
if (function_exists('random_bytes')) {
$bytes = random_bytes($len);
} catch (\Exception $e) {
} elseif (function_exists('openssl_random_pseudo_bytes')) {
/** @noinspection CryptographicallySecureRandomnessInspection */
$bytes = openssl_random_pseudo_bytes($len);
//We failed to produce a proper random string, so make do.
//Use a hash to force the length to the same as the other methods
$bytes = hash('sha256', uniqid((string) mt_rand(), true), true);
//We don't care about messing up base64 format here, just want a random string
return str_replace(['=', '+', '/'], '', base64_encode(hash('sha256', $bytes, true)));
* Assemble the message body.
* Returns an empty string on failure.
* @return string The assembled message body
public function createBody()
//Create unique IDs and preset boundaries
if ($this->sign_key_file) {
$body .= $this->getMailMIME() . static::$LE;
$bodyEncoding = $this->Encoding;
$bodyCharSet = $this->CharSet;
//Can we do a 7-bit downgrade?
if (static::ENCODING_8BIT === $bodyEncoding && !$this->has8bitChars($this->Body)) {
$bodyEncoding = static::ENCODING_7BIT;
//All ISO 8859, Windows codepage and UTF-8 charsets are ascii compatible up to 7-bit
$bodyCharSet = static::CHARSET_ASCII;
//If lines are too long, and we're not already using an encoding that will shorten them,
//change to quoted-printable transfer encoding for the body part only
if (static::ENCODING_BASE64 !== $this->Encoding && static::hasLineLongerThanMax($this->Body)) {
$bodyEncoding = static::ENCODING_QUOTED_PRINTABLE;
$altBodyEncoding = $this->Encoding;
$altBodyCharSet = $this->CharSet;
//Can we do a 7-bit downgrade?
if (static::ENCODING_8BIT === $altBodyEncoding && !$this->has8bitChars($this->AltBody)) {
$altBodyEncoding = static::ENCODING_7BIT;
//All ISO 8859, Windows codepage and UTF-8 charsets are ascii compatible up to 7-bit
$altBodyCharSet = static::CHARSET_ASCII;
//If lines are too long, and we're not already using an encoding that will shorten them,
//change to quoted-printable transfer encoding for the alt body part only
if (static::ENCODING_BASE64 !== $altBodyEncoding && static::hasLineLongerThanMax($this->AltBody)) {
$altBodyEncoding = static::ENCODING_QUOTED_PRINTABLE;
//Use this as a preamble in all multipart message types
switch ($this->message_type) {
$body .= $this->getBoundary($this->boundary[1], $bodyCharSet, '', $bodyEncoding);
$body .= $this->encodeString($this->Body, $bodyEncoding);
$body .= $this->attachAll('inline', $this->boundary[1]);
$body .= $this->getBoundary($this->boundary[1], $bodyCharSet, '', $bodyEncoding);
$body .= $this->encodeString($this->Body, $bodyEncoding);
$body .= $this->attachAll('attachment', $this->boundary[1]);
$body .= $this->textLine('--' . $this->boundary[1]);
$body .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_RELATED . ';');
$body .= $this->textLine(' boundary="' . $this->boundary[2] . '";');
$body .= $this->textLine(' type="' . static::CONTENT_TYPE_TEXT_HTML . '"');
$body .= $this->getBoundary($this->boundary[2], $bodyCharSet, '', $bodyEncoding);
$body .= $this->encodeString($this->Body, $bodyEncoding);
$body .= $this->attachAll('inline', $this->boundary[2]);
$body .= $this->attachAll('attachment', $this->boundary[1]);
$body .= $this->getBoundary(
static::CONTENT_TYPE_PLAINTEXT,
$body .= $this->encodeString($this->AltBody, $altBodyEncoding);
$body .= $this->getBoundary(
static::CONTENT_TYPE_TEXT_HTML,
$body .= $this->encodeString($this->Body, $bodyEncoding);
if (!empty($this->Ical)) {
$method = static::ICAL_METHOD_REQUEST;
foreach (static::$IcalMethods as $imethod) {
if (stripos($this->Ical, 'METHOD:' . $imethod) !== false) {
$body .= $this->getBoundary(
static::CONTENT_TYPE_TEXT_CALENDAR . '; method=' . $method,
$body .= $this->encodeString($this->Ical, $this->Encoding);
$body .= $this->endBoundary($this->boundary[1]);
$body .= $this->getBoundary(
static::CONTENT_TYPE_PLAINTEXT,
$body .= $this->encodeString($this->AltBody, $altBodyEncoding);
$body .= $this->textLine('--' . $this->boundary[1]);
$body .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_RELATED . ';');
$body .= $this->textLine(' boundary="' . $this->boundary[2] . '";');
$body .= $this->textLine(' type="' . static::CONTENT_TYPE_TEXT_HTML . '"');
$body .= $this->getBoundary(
static::CONTENT_TYPE_TEXT_HTML,
$body .= $this->encodeString($this->Body, $bodyEncoding);
$body .= $this->attachAll('inline', $this->boundary[2]);
$body .= $this->endBoundary($this->boundary[1]);
$body .= $this->textLine('--' . $this->boundary[1]);