diff --git a/src/wp-includes/pluggable.php b/src/wp-includes/pluggable.php index 1dbac5e1d707c..5b4c1913b57bd 100644 --- a/src/wp-includes/pluggable.php +++ b/src/wp-includes/pluggable.php @@ -155,6 +155,11 @@ function cache_users( $user_ids ) { * However, you can set the content type of the email by using the * {@see 'wp_mail_content_type'} filter. * + * If $message is an array, the key of each is used to add as an attachment + * with the value used as the body. The 'text/plain' element is used as the + * text version of the body, with the 'text/html' element used as the HTML + * version of the body. All other types are added as attachments. + * * The default charset is based on the charset used on the blog. The charset can * be set using the {@see 'wp_mail_charset'} filter. * @@ -166,7 +171,7 @@ function cache_users( $user_ids ) { * * @param string|string[] $to Array or comma-separated list of email addresses to send message. * @param string $subject Email subject. - * @param string $message Message contents. + * @param string|array $message Message contents * @param string|string[] $headers Optional. Additional headers. * @param string|string[] $attachments Optional. Paths to files to attach. * @return bool Whether the email was sent successfully. @@ -318,6 +323,10 @@ function wp_mail( $to, $subject, $message, $headers = '', $attachments = array() } break; case 'content-type': + if ( is_array( $message ) ) { + // Multipart email, ignore the content-type header + break; + } if ( str_contains( $content, ';' ) ) { list( $type, $charset_content ) = explode( ';', $content ); $content_type = trim( $type ); @@ -417,10 +426,6 @@ function wp_mail( $to, $subject, $message, $headers = '', $attachments = array() return false; } - // Set mail's subject and body. - $phpmailer->Subject = $subject; - $phpmailer->Body = $message; - // Set destination addresses, using appropriate methods for handling addresses. $address_headers = compact( 'to', 'cc', 'bcc', 'reply_to' ); @@ -461,32 +466,7 @@ function wp_mail( $to, $subject, $message, $headers = '', $attachments = array() } } - // Set to use PHP's mail(). - $phpmailer->isMail(); - - // Set Content-Type and charset. - - // If we don't have a Content-Type from the input headers. - if ( ! isset( $content_type ) ) { - $content_type = 'text/plain'; - } - - /** - * Filters the wp_mail() content type. - * - * @since 2.3.0 - * - * @param string $content_type Default wp_mail() content type. - */ - $content_type = apply_filters( 'wp_mail_content_type', $content_type ); - - $phpmailer->ContentType = $content_type; - - // Set whether it's plaintext, depending on $content_type. - if ( 'text/html' === $content_type ) { - $phpmailer->isHTML( true ); - } - + //Set a charset. // If we don't have a charset from the input headers. if ( ! isset( $charset ) ) { $charset = get_bloginfo( 'charset' ); @@ -501,6 +481,53 @@ function wp_mail( $to, $subject, $message, $headers = '', $attachments = array() */ $phpmailer->CharSet = apply_filters( 'wp_mail_charset', $charset ); + // Set mail's subject and body. + $phpmailer->Subject = $subject; + + if ( is_string( $message ) ) { + $phpmailer->Body = $message; + // Set Content-Type. + // If we don't have a content-type from the input headers. + if ( ! isset( $content_type ) ) { + $content_type = 'text/plain'; + } + + /** + * Filters the wp_mail() content type. + * + * @since 2.3.0 + * + * @param string $content_type Default wp_mail() content type. + */ + $content_type = apply_filters( 'wp_mail_content_type', $content_type ); + $phpmailer->ContentType = $content_type; + // Set whether it's plaintext, depending on $content_type. + if ( 'text/html' === $content_type ) { + $phpmailer->isHTML( true ); + } + + // For backwards compatibility, new multipart emails should use + // the array style $message. This never really worked well anyway. + if ( false !== stripos( $content_type, 'multipart' ) && ! empty( $boundary ) ) { + $phpmailer->addCustomHeader( sprintf( 'Content-Type: %s; boundary="%s"', $content_type, $boundary ) ); + } + } elseif ( is_array( $message ) ) { + foreach ( $message as $type => $bodies ) { + foreach ( (array) $bodies as $body ) { + if ( 'text/html' === $type ) { + $phpmailer->Body = $body; + } elseif ( 'text/plain' === $type ) { + $phpmailer->AltBody = $body; + } else { + $phpmailer->addAttachment( $body, '', 'base64', $type ); + } + } + } + } + + // Set to use PHP's mail(). + $phpmailer->isMail(); + // Set custom headers. if ( ! empty( $headers ) ) { foreach ( (array) $headers as $name => $content ) { @@ -513,10 +540,6 @@ function wp_mail( $to, $subject, $message, $headers = '', $attachments = array() } } } - - if ( false !== stripos( $content_type, 'multipart' ) && ! empty( $boundary ) ) { - $phpmailer->addCustomHeader( sprintf( 'Content-Type: %s; boundary="%s"', $content_type, $boundary ) ); - } } if ( ! empty( $attachments ) ) { diff --git a/tests/phpunit/tests/pluggable/wpMail.php b/tests/phpunit/tests/pluggable/wpMail.php index 7b88d739add22..d980f69e13b3b 100644 --- a/tests/phpunit/tests/pluggable/wpMail.php +++ b/tests/phpunit/tests/pluggable/wpMail.php @@ -554,4 +554,84 @@ public function test_wp_mail_resets_properties() { $phpmailer = $GLOBALS['phpmailer']; $this->assertNotSame( 'user1', $phpmailer->AltBody ); } + + /** + * Test that wp_mail() can send a multipart/alternative email with plain text and html versions. + * + * @ticket 15448 + */ + public function test_wp_mail_plain_and_html() { + $to = 'user@example.com'; + $subject = 'Test email with plain text and html versions'; + $messages = array( + 'text/plain' => 'Here is some plain text.', + 'text/html' => 'Here is the HTML with UTF-8 γειά σου Κόσμε;-)', + ); + + wp_mail( $to, $subject, $messages ); + $mailer = tests_retrieve_phpmailer_instance(); + + preg_match( '/boundary="(.*)"/', $mailer->get_sent()->header, $matches ); + $boundary = $matches[1]; + $body = '--' . $boundary . "\n"; + $body .= 'Content-Type: text/plain; charset=us-ascii' . "\n"; + $body .= "\n"; + $body .= 'Here is some plain text.' . "\n"; + $body .= "\n"; + $body .= '--' . $boundary . "\n"; + $body .= 'Content-Type: text/html; charset=UTF-8' . "\n"; + $body .= 'Content-Transfer-Encoding: 8bit' . "\n"; + $body .= "\n"; + $body .= 'Here is the HTML with UTF-8 γειά σου Κόσμε;-)' . "\n"; + $body .= "\n"; + $body .= "\n"; + $body .= '--' . $boundary . '--' . "\n"; + + $this->assertSameIgnoreEOL( $body, $mailer->get_sent()->body, 'The body is not as expected.' ); + $this->assertStringContainsString( + 'Content-Type: multipart/alternative;', + $mailer->get_sent()->header, + 'The multipart/alternative header is not present.' + ); + } + + /* + * 'phpmailer_init' action for test_wp_mail_plain_and_html_workaround(). + */ + public function wp_mail_set_alt_body( $mailer ) { + $mailer->AltBody = strip_tags( $mailer->Body ); + } + + /** + * Check workarounds using phpmailer_init still work around. + * + * @ticket 15448 + */ + public function test_wp_mail_plain_and_html_workaround() { + $to = 'user@example.com'; + $subject = 'Test email with plain text derived from html version'; + $message = '

Hello World! γειά σου Κόσμε

'; + + add_action( 'phpmailer_init', array( $this, 'wp_mail_set_alt_body' ) ); + wp_mail( $to, $subject, $message ); + remove_action( 'phpmailer_init', array( $this, 'wp_mail_set_alt_body' ) ); + + $mailer = tests_retrieve_phpmailer_instance(); + + $this->assertStringContainsString( + 'Content-Type: multipart/alternative;', + $mailer->get_sent()->header, + 'The multipart/alternative header is not present.' + ); + $this->assertStringContainsString( + 'Content-Type: text/plain; charset=UTF-8', + $mailer->get_sent()->body, + 'The text/plain Content-Type header is not present.' + ); + $this->assertStringContainsString( + 'Content-Type: text/html; charset=UTF-8', + $mailer->get_sent()->body, + 'The text/html Content-Type header is not present.' + ); + } }