Mengirim email multi-bagian (teks / html) melalui wp_mail () kemungkinan akan membuat domain Anda diblokir

37

Ringkasan

Karena bug di WP Core, mengirim email multi bagian (html / teks) dengan wp_mail () (untuk mengurangi kemungkinan email berakhir di folder spam) secara ironis akan mengakibatkan domain Anda diblokir oleh Hotmail (dan email Microsoft lainnya).

Ini adalah masalah kompleks yang akan saya uraikan dengan sangat rinci dalam upaya membantu seseorang menemukan solusi yang bisa diterapkan yang pada akhirnya dapat diterapkan pada intinya.

Ini akan menjadi bacaan yang bermanfaat. Mari kita mulai...

Serangga

Saran paling umum untuk menghindari email buletin Anda berakhir di folder spam adalah mengirim pesan multi bagian.

Multi-bagian (pantomim) mengacu pada mengirim bagian HTML dan TEXT dari pesan email dalam satu email. Ketika klien menerima pesan multi-bagian, ia menerima versi HTML jika dapat merender HTML, jika tidak, ia menyajikan versi teks biasa.

Ini terbukti berhasil. Saat mengirim ke gmail, semua email kami mendarat di folder spam hingga kami mengubah pesan menjadi multipart ketika mereka masuk ke kotak masuk utama. Hal yang bagus.

Sekarang, ketika mengirim pesan multi bagian melalui wp_mail (), ia menampilkan Jenis Konten (multi bagian / *) dua kali, sekali dengan batas (jika diatur secara khusus) dan sekali tanpa. Perilaku ini dihasilkan dengan email yang ditampilkan sebagai pesan mentah dan tidak multi-bagian pada beberapa email, termasuk semua Microsoft (Hotmail, Outlook, dll ...)

Microsoft akan menandai pesan ini sebagai sampah, dan beberapa pesan yang datang akan ditandai secara manual oleh penerima. Sayangnya , alamat email Microsoft banyak digunakan. 40% pelanggan kami menggunakannya.

Ini dikonfirmasi oleh Microsoft melalui pertukaran email yang kami miliki baru-baru ini.

Penandaan pesan akan mengakibatkan domain diblokir sepenuhnya . Ini berarti bahwa pesan tidak akan dikirim ke folder spam, mereka bahkan tidak akan dikirimkan ke penerima sama sekali.

Kami telah memblokir domain utama kami 3 kali sejauh ini.

Karena ini adalah bug di inti WP, setiap domain yang mengirim pesan multi bagian diblokir. Masalahnya adalah kebanyakan webmaster tidak tahu mengapa. Saya telah mengkonfirmasi ini ketika melakukan penelitian dan melihat pengguna lain mendiskusikan hal ini di forum, dll. Memerlukan menggali kode mentah dan memiliki pengetahuan yang baik tentang bagaimana jenis pesan email ini bekerja, yang akan kita bahas selanjutnya ...

Mari kita memecahnya menjadi kode

Buat akun hotmail / prospek. Kemudian, jalankan kode berikut:

// Set $to to an hotmail.com or outlook.com email
$to = "[email protected]";

$subject = 'wp_mail testing multipart';

$message = '------=_Part_18243133_1346573420.1408991447668
Content-Type: text/plain; charset=UTF-8

Hello world! This is plain text...


------=_Part_18243133_1346573420.1408991447668
Content-Type: text/html; charset=UTF-8

<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>

<p>Hello World! This is HTML...</p> 

</body>
</html>


------=_Part_18243133_1346573420.1408991447668--';

$headers = "MIME-Version: 1.0\r\n";
$headers .= "From: Foo <[email protected]>\r\n";
$headers .= 'Content-Type: multipart/alternative;boundary="----=_Part_18243133_1346573420.1408991447668"';


// send email
wp_mail( $to, $subject, $message, $headers );

Dan jika Anda ingin mengubah jenis konten default , gunakan:

add_filter( 'wp_mail_content_type', 'set_content_type' );
function set_content_type( $content_type ) {
    return 'multipart/alternative';
}

Ini akan mengirim pesan multi bagian.

Jadi, jika Anda memeriksa sumber mentah lengkap pesan, Anda akan melihat bahwa jenis konten ditambahkan dua kali, sekali tanpa batas:

MIME-Version: 1.0
Content-Type: multipart/alternative;
         boundary="====f230673f9d7c359a81ffebccb88e5d61=="
MIME-Version: 1.0
Content-Type: multipart/alternative; charset=

Itu masalahnya.

Sumber masalahnya terletak pada pluggable.php- jika kita melihat ke suatu tempat di sini:

// 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';

    /**
     * Filter 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 );

    // If we don't have a charset from the input headers
    if ( !isset( $charset ) )
        $charset = get_bloginfo( 'charset' );

    // Set the content-type and charset

    /**
     * Filter the default wp_mail() charset.
     *
     * @since 2.3.0
     *
     * @param string $charset Default email charset.
     */
    $phpmailer->CharSet = apply_filters( 'wp_mail_charset', $charset );

    // 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 ) ) {
        foreach ( $attachments as $attachment ) {
            try {
                $phpmailer->AddAttachment($attachment);
            } catch ( phpmailerException $e ) {
                continue;
            }
        }
    }

Solusi potensial

Jadi Anda bertanya-tanya, mengapa Anda tidak melaporkan ini di trac ? Saya sudah punya . Yang sangat mengejutkan saya, tiket yang berbeda dibuat 5 tahun yang lalu menguraikan masalah yang sama.

Mari kita hadapi itu, sudah setengah dekade. Dalam tahun-tahun internet, itu lebih seperti 30. Masalahnya jelas telah ditinggalkan dan pada dasarnya tidak akan pernah diperbaiki (... kecuali jika kita menyelesaikannya di sini).

Saya menemukan utas yang bagus di sini menawarkan solusi, tetapi sementara solusinya bekerja, itu merusak email yang tidak memiliki $headersset kustom .

Di situlah kita crash setiap saat. Versi multipart berfungsi dengan baik, dan $headerspesan yang tidak disetel normal tidak berfungsi , atau sebaliknya.

Solusi yang kami temukan adalah:

if ( false !== stripos( $content_type, 'multipart' ) && ! empty($boundary) ) {
    $phpmailer->ContentType = $content_type . "; boundary=" . $boundary;
}
else {

        $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 );

    // If we don't have a charset from the input headers
    if ( !isset( $charset ) )
        $charset = get_bloginfo( 'charset' );
}

// Set the content-type and charset

/**
 * Filter the default wp_mail() charset.
 *
 * @since 2.3.0
 *
 * @param string $charset Default email charset.
 */
$phpmailer->CharSet = apply_filters( 'wp_mail_charset', $charset );

// Set custom headers
if ( !empty( $headers ) ) {
    foreach( (array) $headers as $name => $content ) {
        $phpmailer->AddCustomHeader( sprintf( '%1$s: %2$s', $name, $content ) );
    }

}

Ya, saya tahu, mengedit file inti adalah hal yang tabu, duduk kembali ... ini adalah perbaikan yang putus asa dan upaya yang buruk untuk memberikan perbaikan untuk inti.

Masalah dengan perbaikan kami adalah bahwa email default seperti pendaftaran baru, komentar, reset kata sandi dll akan dikirimkan sebagai pesan kosong. Jadi kami memiliki skrip wp_mail () yang berfungsi yang akan mengirim pesan multi bagian tetapi tidak ada yang lain.

Melakukan apa

Tujuannya di sini adalah untuk menemukan cara untuk mengirim pesan normal (teks biasa) dan multi -bagian menggunakan fungsi inti wp_mail () (bukan fungsi sendmail khusus).

Ketika mencoba untuk menyelesaikan ini, masalah utama yang akan Anda temui adalah jumlah waktu yang akan Anda habiskan untuk mengirim pesan palsu, memeriksa apakah mereka diterima dan pada dasarnya membuka kotak aspirin dan memaki di Microsoft karena Anda terbiasa dengan mereka Masalah IE sementara GREMLIN di sini sayangnya WordPress.

Memperbarui

Solusi yang diposting oleh @bonger memungkinkan $messageuntuk menjadi array yang berisi alternatif jenis konten yang dikunci. Saya telah mengkonfirmasi bahwa ini berfungsi di semua skenario.

Kami akan membiarkan pertanyaan ini tetap terbuka sampai hadiah habis untuk meningkatkan kesadaran tentang masalah, mungkin ke tingkat di mana ia akan diperbaiki pada intinya. Jangan ragu untuk mengirim solusi alternatif di mana $messagedapat berupa string.

Christine Cooper
sumber
1
Karena wp_mail()fungsi ini pluggable, tidak mendefinisikan pengganti Anda sebagai plugin yang harus digunakan (dalam wp-content / mu-plugins) bukan solusi yang baik untuk Anda (dan semua orang, gagal memperbaiki inti)? Dalam hal mana tidak akan memindahkan pemeriksaan multi bagian / batas setelah pengaturan $phpmailer->ContentType = $content_type;(daripada elsing) tidak berfungsi?
bonger
@bonger Bisakah Anda menulis jawaban yang merinci solusi Anda?
Christine Cooper
1
Anda tidak perlu mengedit inti, karena itu wp_maildapat dicolokkan . Salin fungsi asli dalam sebuah plugin, edit sesuai kebutuhan dan aktifkan plugin tersebut. WordPress akan menggunakan fungsi yang diedit alih-alih yang asli, tanpa perlu mengedit inti.
gmazzap
@ChristineCooper Saya ragu untuk melakukan ini karena seperti yang Anda katakan pengujian adalah rasa sakit kerajaan, tetapi melihat patch core.trac.wordpress.org/ticket/15448 disarankan dalam trac oleh @ rmccue / @ MattyRob yang terlihat cara yang sangat bagus untuk pergi jadi saya akan mengirim jawaban yang belum diuji berdasarkan itu ...
bonger
2
@ChristineCooper jika Anda menghubungkan ke phpmailer dan mengatur badan teks dalam $ phpmailer-> AltBody kesalahan yang sama terjadi?
chifliiiii

Jawaban:

15

Versi berikut wp_mail()adalah dengan tambalan @ rmccue / @ MattyRob di tiket https://core.trac.wordpress.org/ticket/15448 , disegarkan untuk 4.2.2, yang memungkinkan $messageuntuk menjadi array yang berisi tipe-konten. alternatif yang dikunci:

/**
 * Send mail, similar to PHP's mail
 *
 * A true return value does not automatically mean that the user received the
 * email successfully. It just only means that the method used was able to
 * process the request without any errors.
 *
 * Using the two 'wp_mail_from' and 'wp_mail_from_name' hooks allow from
 * creating a from address like 'Name <[email protected]>' when both are set. If
 * just 'wp_mail_from' is set, then just the email address will be used with no
 * name.
 *
 * The default content type is 'text/plain' which does not allow using HTML.
 * However, you can set the content type of the email by using the
 * '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 'wp_mail_charset' filter.
 *
 * @since 1.2.1
 *
 * @uses PHPMailer
 *
 * @param string|array $to Array or comma-separated list of email addresses to send message.
 * @param string $subject Email subject
 * @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.
 */
function wp_mail( $to, $subject, $message, $headers = '', $attachments = array() ) {
    // Compact the input, apply the filters, and extract them back out

    /**
     * Filter the wp_mail() arguments.
     *
     * @since 2.2.0
     *
     * @param array $args A compacted array of wp_mail() arguments, including the "to" email,
     *                    subject, message, headers, and attachments values.
     */
    $atts = apply_filters( 'wp_mail', compact( 'to', 'subject', 'message', 'headers', 'attachments' ) );

    if ( isset( $atts['to'] ) ) {
        $to = $atts['to'];
    }

    if ( isset( $atts['subject'] ) ) {
        $subject = $atts['subject'];
    }

    if ( isset( $atts['message'] ) ) {
        $message = $atts['message'];
    }

    if ( isset( $atts['headers'] ) ) {
        $headers = $atts['headers'];
    }

    if ( isset( $atts['attachments'] ) ) {
        $attachments = $atts['attachments'];
    }

    if ( ! is_array( $attachments ) ) {
        $attachments = explode( "\n", str_replace( "\r\n", "\n", $attachments ) );
    }
    global $phpmailer;

    // (Re)create it, if it's gone missing
    if ( ! ( $phpmailer instanceof PHPMailer ) ) {
        require_once ABSPATH . WPINC . '/class-phpmailer.php';
        require_once ABSPATH . WPINC . '/class-smtp.php';
        $phpmailer = new PHPMailer( true );
    }

    // Headers
    if ( empty( $headers ) ) {
        $headers = array();
    } else {
        if ( !is_array( $headers ) ) {
            // Explode the headers out, so this function can take both
            // string headers and an array of headers.
            $tempheaders = explode( "\n", str_replace( "\r\n", "\n", $headers ) );
        } else {
            $tempheaders = $headers;
        }
        $headers = array();
        $cc = array();
        $bcc = array();

        // If it's actually got contents
        if ( !empty( $tempheaders ) ) {
            // Iterate through the raw headers
            foreach ( (array) $tempheaders as $header ) {
                if ( strpos($header, ':') === false ) {
                    if ( false !== stripos( $header, 'boundary=' ) ) {
                        $parts = preg_split('/boundary=/i', trim( $header ) );
                        $boundary = trim( str_replace( array( "'", '"' ), '', $parts[1] ) );
                    }
                    continue;
                }
                // Explode them out
                list( $name, $content ) = explode( ':', trim( $header ), 2 );

                // Cleanup crew
                $name    = trim( $name    );
                $content = trim( $content );

                switch ( strtolower( $name ) ) {
                    // Mainly for legacy -- process a From: header if it's there
                    case 'from':
                        $bracket_pos = strpos( $content, '<' );
                        if ( $bracket_pos !== false ) {
                            // Text before the bracketed email is the "From" name.
                            if ( $bracket_pos > 0 ) {
                                $from_name = substr( $content, 0, $bracket_pos - 1 );
                                $from_name = str_replace( '"', '', $from_name );
                                $from_name = trim( $from_name );
                            }

                            $from_email = substr( $content, $bracket_pos + 1 );
                            $from_email = str_replace( '>', '', $from_email );
                            $from_email = trim( $from_email );

                        // Avoid setting an empty $from_email.
                        } elseif ( '' !== trim( $content ) ) {
                            $from_email = trim( $content );
                        }
                        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 );
                            if ( false !== stripos( $charset_content, 'charset=' ) ) {
                                $charset = trim( str_replace( array( 'charset=', '"' ), '', $charset_content ) );
                            } elseif ( false !== stripos( $charset_content, 'boundary=' ) ) {
                                $boundary = trim( str_replace( array( 'BOUNDARY=', 'boundary=', '"' ), '', $charset_content ) );
                                $charset = '';
                            }

                        // Avoid setting an empty $content_type.
                        } elseif ( '' !== trim( $content ) ) {
                            $content_type = trim( $content );
                        }
                        break;
                    case 'cc':
                        $cc = array_merge( (array) $cc, explode( ',', $content ) );
                        break;
                    case 'bcc':
                        $bcc = array_merge( (array) $bcc, explode( ',', $content ) );
                        break;
                    default:
                        // Add it to our grand headers array
                        $headers[trim( $name )] = trim( $content );
                        break;
                }
            }
        }
    }

    // Empty out the values that may be set
    $phpmailer->ClearAllRecipients();
    $phpmailer->ClearAttachments();
    $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 ) )
        $from_name = 'WordPress';

    /* If we don't have an email from the input headers default to wordpress@$sitename
     * Some hosts will block outgoing mail from this address if it doesn't exist but
     * there's no easy alternative. Defaulting to admin_email might appear to be another
     * option but some hosts may refuse to relay mail from an unknown domain. See
     * https://core.trac.wordpress.org/ticket/5007.
     */

    if ( !isset( $from_email ) ) {
        // Get the site domain and get rid of www.
        $sitename = strtolower( $_SERVER['SERVER_NAME'] );
        if ( substr( $sitename, 0, 4 ) == 'www.' ) {
            $sitename = substr( $sitename, 4 );
        }

        $from_email = 'wordpress@' . $sitename;
    }

    /**
     * Filter the email address to send from.
     *
     * @since 2.2.0
     *
     * @param string $from_email Email address to send from.
     */
    $phpmailer->From = apply_filters( 'wp_mail_from', $from_email );

    /**
     * Filter the name to associate with the "from" email address.
     *
     * @since 2.3.0
     *
     * @param string $from_name Name associated with the "from" email address.
     */
    $phpmailer->FromName = apply_filters( 'wp_mail_from_name', $from_name );

    // Set destination addresses
    if ( !is_array( $to ) )
        $to = explode( ',', $to );

    foreach ( (array) $to as $recipient ) {
        try {
            // Break $recipient into name and address parts if in the format "Foo <[email protected]>"
            $recipient_name = '';
            if( preg_match( '/(.*)<(.+)>/', $recipient, $matches ) ) {
                if ( count( $matches ) == 3 ) {
                    $recipient_name = $matches[1];
                    $recipient = $matches[2];
                }
            }
            $phpmailer->AddAddress( $recipient, $recipient_name);
        } catch ( phpmailerException $e ) {
            continue;
        }
    }

    // If we don't have a charset from the input headers
    if ( !isset( $charset ) )
        $charset = get_bloginfo( 'charset' );

    // Set the content-type and charset

    /**
     * Filter the default wp_mail() charset.
     *
     * @since 2.3.0
     *
     * @param string $charset Default email charset.
     */
    $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 and charset
        // If we don't have a content-type from the input headers
        if ( !isset( $content_type ) )
            $content_type = 'text/plain';

        /**
         * Filter 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 ($type === 'text/html') {
                    $phpmailer->Body = $body;
                }
                elseif ($type === 'text/plain') {
                    $phpmailer->AltBody = $body;
                }
                else {
                    $phpmailer->AddAttachment($body, '', 'base64', $type);
                }
            }
        }
    }

    // Add any CC and BCC recipients
    if ( !empty( $cc ) ) {
        foreach ( (array) $cc as $recipient ) {
            try {
                // Break $recipient into name and address parts if in the format "Foo <[email protected]>"
                $recipient_name = '';
                if( preg_match( '/(.*)<(.+)>/', $recipient, $matches ) ) {
                    if ( count( $matches ) == 3 ) {
                        $recipient_name = $matches[1];
                        $recipient = $matches[2];
                    }
                }
                $phpmailer->AddCc( $recipient, $recipient_name );
            } catch ( phpmailerException $e ) {
                continue;
            }
        }
    }

    if ( !empty( $bcc ) ) {
        foreach ( (array) $bcc as $recipient) {
            try {
                // Break $recipient into name and address parts if in the format "Foo <[email protected]>"
                $recipient_name = '';
                if( preg_match( '/(.*)<(.+)>/', $recipient, $matches ) ) {
                    if ( count( $matches ) == 3 ) {
                        $recipient_name = $matches[1];
                        $recipient = $matches[2];
                    }
                }
                $phpmailer->AddBcc( $recipient, $recipient_name );
            } catch ( phpmailerException $e ) {
                continue;
            }
        }
    }

    // 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 ( !empty( $attachments ) ) {
        foreach ( $attachments as $attachment ) {
            try {
                $phpmailer->AddAttachment($attachment);
            } catch ( phpmailerException $e ) {
                continue;
            }
        }
    }

    /**
     * Fires after PHPMailer is initialized.
     *
     * @since 2.2.0
     *
     * @param PHPMailer &$phpmailer The PHPMailer instance, passed by reference.
     */
    do_action_ref_array( 'phpmailer_init', array( &$phpmailer ) );

    // Send!
    try {
        return $phpmailer->Send();
    } catch ( phpmailerException $e ) {
        return false;
    }
}

Jadi, jika Anda memasukkannya ke dalam file mis. "Wp-content / mu-plugins / functions.php" Anda, maka itu akan menggantikan versi WP. Ini memiliki penggunaan yang bagus tanpa bermain-main dengan header, misalnya:

// Set $to to an hotmail.com or outlook.com email
$to = "[email protected]";

$subject = 'wp_mail testing multipart';

$message['text/plain'] = 'Hello world! This is plain text...';
$message['text/html'] = '<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>

<p>Hello World! This is HTML...</p> 

</body>
</html>';

add_filter( 'wp_mail_from', $from_func = function ( $from_email ) { return '[email protected]'; } );
add_filter( 'wp_mail_from_name', $from_name_func = function ( $from_name ) { return 'Foo'; } );

// send email
wp_mail( $to, $subject, $message );

remove_filter( 'wp_mail_from', $from_func );
remove_filter( 'wp_mail_from_name', $from_name_func );

Harap dicatat saya belum menguji ini dengan email yang sebenarnya ...

bonger
sumber
Saya telah menambahkan ini ke harus memiliki plugin dan menjalankan kode tes; itu berhasil. Saya telah menguji pemberitahuan inti default (pemberitahuan pengguna baru, dll.) Dan ternyata berfungsi juga. Saya akan terus melakukan tes akhir pekan ini dan melihat bagaimana plugin akan bekerja dengan ini dan pada dasarnya jika semuanya berfungsi. Saya akan secara khusus melihat melalui data mentah pesan. Ini akan menjadi tugas yang sangat memakan waktu tetapi yakinlah, saya akan melaporkan kembali setelah selesai. Jika ada skenario di mana wp_mail () tidak akan berfungsi (padahal seharusnya), beri tahu saya. Terima kasih atas jawaban ini.
Christine Cooper
Bagus, saya sudah mengotak-atik output dan terlihat bagus - sebenarnya tambalan hanya membuat wp_mail menggunakan pemrosesan standar rock standar PHPMailer dalam hal melewati array, dan jika tidak default ke hal-hal WP cerdik (untuk kompatibilitas mundur) jadi itu harus baik (jelas pujian di sini pergi ke penulis tambalan) ... Saya akan menggunakannya mulai sekarang (dan retro-fitting akhirnya) - dan terima kasih kembali untuk info kembali menggunakan kedua html / polos untuk mengurangi kemungkinan menjadi spam ...
bonger
1
Kami telah mengujinya dalam semua skenario yang mungkin dan ini berhasil dengan baik. Kami akan mengeluarkan buletin besok dan kami akan melihat apakah kami menerima keluhan dari pengguna. Satu-satunya perubahan kecil yang perlu kami lakukan adalah membersihkan / membatalkan sanitasi array ketika sedang dimasukkan ke db (pesan ada di que di db di mana cron mengirimkannya dalam bulks kecil). Saya akan membiarkan pertanyaan ini tetap terbuka dan tertunda sampai karunia habis sehingga kami dapat membawa kesadaran untuk masalah ini. Semoga tambalan ini, atau alternatif akan ditambahkan ke inti. Atau yang lebih penting, mengapa tidak. Apa yang mereka pikirkan!
Christine Cooper
Saya melihat secara acak bahwa Anda melakukan pembaruan pada tiket trac yang tertaut. Apakah ini pembaruan untuk kode ini? Jika demikian, bisakah Anda memposting pembaruan ini dengan mengedit jawaban Anda di sini juga sehingga jawaban ini tetap uptodate? Terima kasih banyak.
Christine Cooper
Hai, tidak, itu hanya penyegaran patch terhadap bagasi saat ini sehingga bergabung tanpa konflik (dengan harapan mendapat perhatian), kodenya persis sama ...
bonger
4

Ini bukan bug WordPress sama sekali, ini adalah bug yang phpmailertidak memungkinkan header kustom ... jika Anda melihat class-phpmailer.php:

public function getMailMIME()
{
    $result = '';
    $ismultipart = true;
    switch ($this->message_type) {
        case 'inline':
            $result .= $this->headerLine('Content-Type', 'multipart/related;');
            $result .= $this->textLine("\tboundary=\"" . $this->boundary[1] . '"');
            break;
        case 'attach':
        case 'inline_attach':
        case 'alt_attach':
        case 'alt_inline_attach':
            $result .= $this->headerLine('Content-Type', 'multipart/mixed;');
            $result .= $this->textLine("\tboundary=\"" . $this->boundary[1] . '"');
            break;
        case 'alt':
        case 'alt_inline':
            $result .= $this->headerLine('Content-Type', 'multipart/alternative;');
            $result .= $this->textLine("\tboundary=\"" . $this->boundary[1] . '"');
            break;
        default:
            // Catches case 'plain': and case '':
            $result .= $this->textLine('Content-Type: ' . $this->ContentType . '; charset=' . $this->CharSet);
            $ismultipart = false;
            break;
    }

Anda dapat melihat kasus default yang menyinggung adalah apa yang mengeluarkan baris header tambahan dengan charset dan tanpa batas. Mengatur jenis konten berdasarkan filter tidak menyelesaikannya dengan sendirinya hanya karena altcase di sini diaktifkan message_typedengan memeriksa AltBodytidak kosong daripada jenis konten.

protected function setMessageType()
{
    $type = array();
    if ($this->alternativeExists()) {
        $type[] = 'alt';
    }
    if ($this->inlineImageExists()) {
        $type[] = 'inline';
    }
    if ($this->attachmentExists()) {
        $type[] = 'attach';
    }
    $this->message_type = implode('_', $type);
    if ($this->message_type == '') {
        $this->message_type = 'plain';
    }
}

public function alternativeExists()
{
    return !empty($this->AltBody);
}

Pada akhirnya apa artinya ini segera setelah Anda melampirkan file atau gambar sebaris, atau mengatur AltBody, bug yang menyinggung harus dilewati. Ini juga berarti tidak perlu secara eksplisit mengatur jenis konten karena segera setelah AltBodyitu diatur multipart/alternativeoleh phpmailer.

Jadi jawaban sederhananya adalah:

add_action('phpmailer_init','wp_mail_set_text_body');
function wp_mail_set_text_body($phpmailer) {
     if (empty($phpmailer->AltBody)) {$phpmailer->AltBody = strip_tags($phpmailer->Body);}
}

Maka Anda tidak perlu mengatur tajuk secara eksplisit, Anda cukup melakukannya:

 $message ='<html>
 <head>
     <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
 </head>
 <body>
     <p>Hello World! This is HTML...</p> 
 </body>
 </html>';

 wp_mail($to,$subject,$message);

Sayangnya banyak fungsi dan properti di phpmailerkelas dilindungi, jika bukan karena itu alternatif yang valid adalah dengan hanya memeriksa dan menimpa MIMEHeadersproperti melalui phpmailer_inithook sebelum mengirim.

majik
sumber
2

Saya baru saja merilis sebuah plugin untuk memungkinkan pengguna menggunakan templat html di WordPress dan saya bermain sekarang di versi dev untuk menambahkan fallback teks sederhana. Saya melakukan yang berikut dan dalam pengujian saya, saya hanya melihat satu batas ditambahkan dan email diterima dengan baik untuk Hotmail.

add_action( 'phpmailer_init', array($this->mailer, 'send_email' ) );

/**
* Modify php mailer body with final email
*
* @since 1.0.0
* @param object $phpmailer
*/
function send_email( $phpmailer ) {

    $message            =  $this->add_template( apply_filters( 'mailtpl/email_content', $phpmailer->Body ) );
    $phpmailer->AltBody =  $this->replace_placeholders( strip_tags($phpmailer->Body) );
    $phpmailer->Body    =  $this->replace_placeholders( $message );
}

Jadi pada dasarnya yang saya lakukan di sini adalah memodifikasi objek phpmailer, memuat pesan di dalam template html dan mengaturnya ke properti Body. Saya juga mengambil pesan asli dan mengatur properti AltBody.

chifliiiii
sumber
2

Solusi sederhana saya adalah dengan menggunakan html2text https://github.com/soundasleep/html2text dengan cara ini:

add_action( 'phpmailer_init', 'phpmailer_init' );

//http://wordpress.stackexchange.com/a/191974
//http://stackoverflow.com/a/2564472
function phpmailer_init( $phpmailer )
{
  if( $phpmailer->ContentType == 'text/html' ) {
    $phpmailer->AltBody = Html2Text\Html2Text::convert( $phpmailer->Body );
  }
}

Di sini https://gist.github.com/ewake/6c4d22cd856456480bd77b988b5c9e80 juga merupakan intisari.

bangun
sumber
2

Bagi siapa pun yang menggunakan kait 'phpmailer_init' untuk menambahkan 'AltBody' mereka sendiri:

Badan teks alternatif digunakan kembali untuk berbagai email berturut-turut yang dikirim, kecuali Anda menghapusnya secara manual! WordPress tidak menghapusnya di wp_mail () karena tidak mengharapkan properti ini digunakan.

Ini menghasilkan penerima yang berpotensi menerima email yang tidak dimaksudkan untuk mereka. Untungnya sebagian besar orang yang menggunakan klien email yang mendukung HTML tidak akan melihat versi teks, tetapi pada dasarnya ini masih merupakan masalah keamanan.

Untungnya ada perbaikan yang mudah. Ini termasuk bit pengganti altbody; perhatikan bahwa Anda membutuhkan pustaka PHP Html2Text:

add_filter( 'wp_mail', 'wpse191923_force_phpmailer_reinit_for_multiple_mails', -1 );
function wpse191923_force_phpmailer_reinit_for_multiple_mails( $wp_mail_atts ) {
  global $phpmailer;

  if ( $phpmailer instanceof PHPMailer && $phpmailer->alternativeExists() ) {
    // AltBody property is set, so WordPress must already have used this
    // $phpmailer object just now to send mail, so let's
    // clear the AltBody property
    $phpmailer->AltBody = '';
  }

  // Return untouched atts
  return $wp_mail_atts;
}

add_action( 'phpmailer_init', 'wpse191923_phpmailer_init_altbody', 1000, 1 );
function wpse191923_phpmailer_init_altbody( $phpmailer ) {
  if ( ( $phpmailer->ContentType == 'text/html' ) && empty( $phpmailer->AltBody ) ) {
    if ( ! class_exists( 'Html2Text\Html2Text' ) ) {
      require_once( 'Html2Text.php' );
    }
    if ( ! class_exists( 'Html2Text\Html2TextException' ) ) {
      require_once( 'Html2TextException.php' );
    }
    $phpmailer->AltBody = Html2Text\Html2Text::convert( $phpmailer->Body );
  }
}

Berikut juga inti untuk plugin WP yang saya modifikasi untuk memperbaiki masalah ini: https://gist.github.com/youri--/c4618740b7c50c549314eaebc9f78661

Sayangnya saya tidak dapat mengomentari solusi lain menggunakan kait yang disebutkan di atas, untuk memperingatkan mereka tentang hal ini, karena saya belum memiliki perwakilan yang cukup untuk berkomentar.

Tanuki
sumber
1

ini mungkin bukan jawaban yang tepat untuk posting awal di sini, tapi ini merupakan alternatif dari beberapa solusi yang disediakan di sini mengenai pengaturan alt body

pada dasarnya, saya perlu (ingin) mengatur altbody yang berbeda (yaitu plaintext) tambahan ke bagian html daripada mengandalkan beberapa konversi / striptag dan yang lainnya. jadi saya datang dengan ini yang tampaknya berfungsi dengan baik

/* setting the message parts for wp_mail()*/
$markup = array();
$markup['html'] = '<html>some html</html>';
$markup['plaintext'] = 'some plaintext';
/* message we are sending */    
$message = maybe_serialize($markup);


/* setting alt body distinctly */
add_action('phpmailer_init', array($this, 'set_alt_mail_body'));

function set_alt_mail_body($phpmailer){
    if( $phpmailer->ContentType == 'text/html' ) {
        $body_parts = maybe_unserialize($phpmailer->Body);

        if(!empty($body_parts['html'])){
            $phpmailer->MsgHTML($body_parts['html']);
        }

        if(!empty($body_parts['plaintext'])){
            $phpmailer->AltBody = $body_parts['plaintext'];
        }
    }   
}
Olly
sumber
0

Jika Anda tidak ingin membuat konflik kode apa pun di inti Wordpress, saya pikir alternatif atau solusi paling sederhana adalah menambahkan tindakan phpmailer_inityang akan dilakukan sebelum pengiriman surat yang sebenarnya di wp_mail. Untuk menyederhanakan penjelasan saya lihat contoh kode di bawah ini:

<?php 

$to = '';
$subject = '';
$from = '';
$body = 'The text html content, <html>...';

$headers = "FROM: {$from}";

add_action( 'phpmailer_init', function ( $phpmailer ) {
    $phpmailer->AltBody = 'The text plain content of your original text html content.';
} );

wp_mail($to, $subject, $body, $headers);

Jika Anda menambahkan konten di AltBodyproperti kelas PHPMailer maka jenis konten default secara otomatis akan diatur ke multipart/alternative.

Joshua Reyes
sumber