Index: src/wp-includes/pluggable.php =================================================================== --- src/wp-includes/pluggable.php (revision 38500) +++ src/wp-includes/pluggable.php (working copy) @@ -156,6 +156,11 @@ * 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. * @@ -165,7 +170,7 @@ * * @param string|array $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|array $headers Optional. Additional headers. * @param string|array $attachments Optional. Files to attach. * @return bool Whether the email contents were sent successfully. @@ -270,6 +275,10 @@ } break; case 'content-type': + if ( is_array( $message ) ) { + // Multipart email, ignore the content-type header + break; + } if ( strpos( $content, ';' ) !== false ) { list( $type, $charset_content ) = explode( ';', $content ); $content_type = trim( $type ); @@ -309,6 +318,9 @@ $phpmailer->ClearCustomHeaders(); $phpmailer->ClearReplyTos(); + $phpmailer->Body= ''; + $phpmailer->AltBody= ''; + // From email and name // If we don't have a name from the input headers if ( !isset( $from_name ) ) @@ -355,10 +367,6 @@ if ( !is_array( $to ) ) $to = explode( ',', $to ); - // Set mail's subject and body - $phpmailer->Subject = $subject; - $phpmailer->Body = $message; - // Use appropriate methods for handling addresses, rather than treating them as generic headers $address_headers = compact( 'to', 'cc', 'bcc', 'reply_to' ); @@ -399,35 +407,11 @@ } } - // 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 charset // If we don't have a charset from the input headers if ( !isset( $charset ) ) $charset = get_bloginfo( 'charset' ); - // Set the content-type and charset - /** * Filters the default wp_mail() charset. * @@ -437,14 +421,61 @@ */ $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;\n\t 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 ) { $phpmailer->AddCustomHeader( sprintf( '%1$s: %2$s', $name, $content ) ); } - - if ( false !== stripos( $content_type, 'multipart' ) && ! empty($boundary) ) - $phpmailer->AddCustomHeader( sprintf( "Content-Type: %s;\n\t boundary=\"%s\"", $content_type, $boundary ) ); } if ( !empty( $attachments ) ) { Index: tests/phpunit/tests/mail.php =================================================================== --- tests/phpunit/tests/mail.php (revision 38500) +++ tests/phpunit/tests/mail.php (working copy) @@ -87,6 +87,71 @@ } /** + * @ticket 15448 + */ + 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 ); + + preg_match( '/boundary="(.*)"/', $GLOBALS['phpmailer']->mock_sent[0]['header'], $matches ); + $boundry = $matches[1]; + $body = 'This is a multi-part message in MIME format. + +--' . $boundry . ' +Content-Type: text/plain; charset=us-ascii + +Here is some plain text. + + +--' . $boundry . ' +Content-Type: text/html; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Here is the HTML with UTF-8 γειά σου Κόσμε;-) + + + +--' . $boundry . '-- +'; + // We need some better assertions here but these test the behaviour for now. + $this->assertEquals( $body, $GLOBALS['phpmailer']->mock_sent[0]['body'] ); + $this->assertSame( 1, substr_count( $GLOBALS['phpmailer']->mock_sent[0]['header'], 'Content-Type: multipart/alternative;' ) ); + $this->assertSame( 1, substr_count( $GLOBALS['phpmailer']->mock_sent[0]['header'], 'Content-Type:' ) ); + } + + /* + * 'phpmailer_init' action for test_wp_mail_plain_and_html_workaround(). + */ + function wp_mail_set_alt_body( $mailer ) { + $mailer->AltBody = strip_tags( $mailer->Body ); + } + + /** + * @ticket 15448 + * + * Check workarounds using phpmailer_init still work around. + */ + 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' ) ); + + $this->assertSame( 1, substr_count( $GLOBALS['phpmailer']->mock_sent[0]['header'], 'Content-Type: multipart/alternative;' ) ); + $this->assertSame( 1, substr_count( $GLOBALS['phpmailer']->mock_sent[0]['header'], 'Content-Type:' ) ); + $this->assertSame( 1, substr_count( $GLOBALS['phpmailer']->mock_sent[0]['body'], 'Content-Type: text/plain; charset=UTF-8' ) ); + $this->assertSame( 1, substr_count( $GLOBALS['phpmailer']->mock_sent[0]['body'], 'Content-Type: text/html; charset=UTF-8' ) ); + $this->assertSame( 2, substr_count( $GLOBALS['phpmailer']->mock_sent[0]['body'], 'Content-Type:' ) ); + } + + /** * @ticket 17305 */ function test_wp_mail_rfc2822_addresses() {