การส่งอีเมลหลายข้อความ (ข้อความ / html) ผ่าน wp_mail () อาจทำให้โดเมนของคุณถูกแบน


37

สรุป

เนื่องจากมีปัญหาใน WP หลักส่งmultipartอีเมล (HTML / ข้อความ) กับwp_mail () (เพื่อลดโอกาสของอีเมลสิ้นสุดในโฟลเดอร์สแปม) จะกระแนะกระแหนส่งผลกับโดเมนของคุณถูกบล็อกโดย Hotmail (และอีเมลอื่น ๆ ของ Microsoft)

นี่เป็นปัญหาที่ซับซ้อนซึ่งฉันจะตั้งเป้าหมายให้ละเอียดโดยละเอียดในความพยายามช่วยคนหาวิธีแก้ปัญหาที่ใช้การได้ซึ่งอาจนำไปใช้ในแกนกลางในที่สุด

มันจะเป็นการอ่านที่คุ้มค่า เอาล่ะ...

ข้อผิดพลาด

คำแนะนำที่พบบ่อยที่สุดในการหลีกเลี่ยงอีเมลจดหมายข่าวของคุณที่อยู่ในโฟลเดอร์สแปมคือการส่งข้อความหลายส่วน

หลายส่วน (mime) หมายถึงการส่งทั้งส่วน HTML และ TEXT ของข้อความอีเมลในอีเมลเดียว เมื่อไคลเอนต์ได้รับข้อความหลายส่วนก็ยอมรับเวอร์ชัน HTML หากมันสามารถแสดงผล HTML มิฉะนั้นจะแสดงรุ่นข้อความธรรมดา

นี่พิสูจน์แล้วว่าใช้ได้จริง เมื่อส่งไปที่ gmail อีเมลทั้งหมดของเราจะถูกส่งไปยังโฟลเดอร์สแปมจนกว่าเราจะเปลี่ยนข้อความเป็นหลายส่วนเมื่ออีเมลเหล่านั้นเข้าสู่กล่องจดหมายหลัก สิ่งที่ยอดเยี่ยม

ทีนี้เมื่อส่งข้อความแบบหลายส่วนผ่าน wp_mail () มันจะส่งออกประเภทเนื้อหา (หลายส่วน / *) สองครั้งโดยมีขอบเขต (ถ้าตั้งค่าแบบกำหนดเอง) และอีกครั้งโดยไม่มี ลักษณะการทำงานนี้ส่งผลให้อีเมลที่ถูกแสดงเป็นข้อความ raw และไม่ใช่ multipart ในบางอีเมลรวมถึงMicrosoft ทั้งหมด (Hotmail, Outlook, etc ... )

Microsoft จะทำเครื่องหมายข้อความนี้ว่าเป็นจดหมายขยะและผู้รับไม่กี่ข้อความที่ผ่านเข้ามาจะถูกทำเครื่องหมายด้วยตนเองโดยผู้รับ น่าเสียดายที่มีการใช้ที่อยู่อีเมล Microsoft อย่างกว้างขวาง 40% ของสมาชิกของเราใช้งาน

นี่คือการยืนยันโดย Microsoft ผ่านการแลกเปลี่ยนอีเมลที่เรามีเมื่อเร็ว ๆ นี้

การตั้งค่าสถานะของข้อความที่จะส่งผลให้มีโดเมนที่ถูกสมบูรณ์บล็อก ซึ่งหมายความว่าข้อความจะไม่ถูกส่งไปยังโฟลเดอร์สแปมพวกเขาจะไม่ถูกส่งไปยังผู้รับเลย

เราได้ปิดกั้นโดเมนหลักของเรา 3 ครั้งแล้ว

เนื่องจากนี่เป็นข้อผิดพลาดใน Core WP ทุกโดเมนที่ส่งข้อความหลายส่วนจะถูกบล็อก ปัญหาคือเว็บมาสเตอร์ส่วนใหญ่ไม่ทราบสาเหตุ ฉันยืนยันสิ่งนี้เมื่อทำการวิจัยของฉันและเห็นผู้ใช้รายอื่นคุยเรื่องนี้ในฟอรัม ฯลฯ มันต้องมีการตรวจสอบรหัสดิบและมีความรู้ที่ดีเกี่ยวกับการทำงานของข้อความอีเมลประเภทนี้ซึ่งเรากำลังจะไป ...

ลองแบ่งมันเป็นรหัส

สร้างบัญชี hotmail / outlook จากนั้นเรียกใช้รหัสต่อไปนี้:

// Set $to to an hotmail.com or outlook.com email
$to = "YourEmail@hotmail.com";

$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 <foo@bar.com>\r\n";
$headers .= 'Content-Type: multipart/alternative;boundary="----=_Part_18243133_1346573420.1408991447668"';


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

และหากคุณต้องการเปลี่ยนประเภทเนื้อหาเริ่มต้นให้ใช้:

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

นี่จะส่งข้อความหลายส่วน

ดังนั้นหากคุณตรวจสอบแหล่งที่มาแบบเต็มของข้อความคุณจะสังเกตเห็นว่ามีการเพิ่มประเภทเนื้อหาสองครั้งโดยไม่มีขอบเขต:

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

นั่นคือปัญหา

สาเหตุของปัญหาอยู่ที่pluggable.php- ถ้าเราดูที่นี่:

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

โซลูชั่นที่มีศักยภาพ

ดังนั้นคุณสงสัยทำไมคุณไม่รายงานเรื่องนี้ที่trac ? ผมมีอยู่แล้ว สำหรับความประหลาดใจที่ยิ่งใหญ่ของฉันตั๋วที่แตกต่างถูกสร้างขึ้นเมื่อ 5 ปีก่อนโดยสรุปปัญหาเดียวกัน

เรามาเจอกันมันเป็นครึ่งทศวรรษแล้ว ในปีอินเทอร์เน็ตนั่นคือ 30 ปีปัญหานี้ถูกยกเลิกอย่างชัดเจนและโดยทั่วไปจะไม่ได้รับการแก้ไข (... เว้นแต่ว่าเราจะแก้ไขที่นี่)

ฉันพบเธรดที่ยอดเยี่ยมที่นี่เสนอวิธีแก้ไขปัญหา แต่ในขณะที่โซลูชันของเขาใช้งานได้มันจะแบ่งอีเมลที่ไม่มี$headersชุดกำหนดเอง

นั่นคือสิ่งที่เราผิดพลาดทุกครั้ง อาจเป็นรุ่นที่มีหลายส่วนทำงานได้ดีและ$headersข้อความที่ไม่ได้ตั้งค่าตามปกติไม่สามารถใช้งานได้

วิธีแก้ปัญหาที่เราพบคือ:

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

}

ใช่ฉันรู้แล้วการแก้ไขไฟล์หลักเป็นข้อห้ามนั่งลง ... นี่เป็นการแก้ไขที่สิ้นหวังและความพยายามที่ไม่ดีในการจัดหาการแก้ไขสำหรับคอร์

ปัญหาเกี่ยวกับการแก้ไขของเราคืออีเมลเริ่มต้นเช่นการลงทะเบียนใหม่ความคิดเห็นการรีเซ็ตรหัสผ่านเป็นต้นจะถูกส่งเป็นข้อความเปล่า ดังนั้นเราจึงมีสคริปต์ wp_mail () ที่จะส่งข้อความหลายส่วน แต่ไม่มีอะไรอื่น

สิ่งที่ต้องทำ

จุดประสงค์ที่นี่คือการหาวิธีการส่งทั้งข้อความปกติ (ข้อความธรรมดา) และข้อความแบบหลายส่วนโดยใช้ฟังก์ชั่น core wp_mail () (ไม่ใช่ฟังก์ชั่น sendmail แบบกำหนดเอง)

เมื่อพยายามที่จะแก้ปัญหานี้ปัญหาหลักที่คุณจะพบคือระยะเวลาที่คุณใช้ในการส่งข้อความจำลองตรวจสอบว่าพวกเขาได้รับและเปิดกล่องแอสไพรินและสาปแช่งที่ Microsoft เพราะคุณคุ้นเคยกับพวกเขา ปัญหา IE ในขณะที่ gremlin ที่นี่เป็นที่น่าเสียดายว่า WordPress

ปรับปรุง

โซลูชันที่โพสต์โดย @bonger อนุญาตให้$messageเป็นอาร์เรย์ที่มีการสลับคีย์ชนิดเนื้อหา ฉันยืนยันว่ามันใช้งานได้ในทุกสถานการณ์

เราจะอนุญาตให้คำถามนี้ยังคงเปิดอยู่จนกว่าเงินรางวัลจะหมดเพื่อสร้างการรับรู้เกี่ยวกับปัญหาอาจจะอยู่ในระดับที่จะได้รับการแก้ไขในแกนกลาง อย่าลังเลที่จะโพสต์โซลูชันทางเลือกซึ่ง$messageสามารถเป็นสตริงได้


1
เนื่องจากwp_mail()ฟังก์ชั่นที่เสียบได้ไม่ได้กำหนดว่าการเปลี่ยนของคุณเป็นปลั๊กอินที่ต้องใช้ (ใน wp-content / mu-plugins) ไม่ใช่วิธีแก้ปัญหาที่ดีสำหรับคุณ (และคนอื่น ๆ ในกรณีใดจะไม่ย้ายการตรวจสอบหลายส่วน / ขอบเขตไปที่หลังจากการตั้งค่า$phpmailer->ContentType = $content_type;(แทนที่จะเป็นแบบอื่น) ไม่ทำงาน?
bonger

@bonger คุณช่วยกรุณาเขียนคำตอบรายละเอียดโซลูชันของคุณ?
Christine Cooper

1
โย่ไม่จำเป็นต้องแก้ไขแกนเพราะwp_mailมันเสียบได้ คัดลอกฟังก์ชั่นดั้งเดิมในปลั๊กอินแก้ไขเหมือนที่คุณต้องการและเปิดใช้งานปลั๊กอิน WordPress จะใช้ฟังก์ชั่นการแก้ไขของคุณแทนของเดิมโดยไม่จำเป็นต้องแก้ไขหลัก
gmazzap

@ChristineCooper ฉันลังเลที่จะทำสิ่งนี้ตามที่คุณบอกว่าการทดสอบนั้นช่างเป็นความเจ็บปวด แต่ดูที่ patch core.trac.wordpress.org/ticket/15448แนะนำใน trac โดย @ rmccue / @ MattyRob ที่ดูเป็นวิธีที่ดีจริงๆ ไปดังนั้นฉันจะโพสต์คำตอบที่ยังไม่ทดลองโดยอ้างอิงจาก ...
บ้อง ก.ย.

2
@ChristineCooper หากคุณเชื่อมต่อกับ phpmailer ง่ายๆและตั้งค่าเนื้อหาของข้อความเป็น $ phpmailer-> AltBody มีข้อผิดพลาดเกิดขึ้นหรือไม่
chifliiiii

คำตอบ:


15

เวอร์ชันต่อไปนี้wp_mail()คือด้วยโปรแกรมแก้ไขที่ใช้ของ @ rmccue / @ MattyRob ในตั๋วhttps://core.trac.wordpress.org/ticket/15448รีเฟรช 4.2.2 ซึ่งอนุญาตให้$messageเป็นอาร์เรย์ที่มีประเภทเนื้อหา คีย์สำรอง:

/**
 * 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@address.com>' 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 <bar@baz.com>"
            $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 <bar@baz.com>"
                $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 <bar@baz.com>"
                $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;
    }
}

ดังนั้นหากคุณใส่ไฟล์ดังกล่าวลงในไฟล์ "wp-content / mu-plugins / functions.php" มันจะแทนที่เวอร์ชั่น WP มันมีการใช้งานที่ดีโดยไม่ต้องยุ่งกับส่วนหัวเช่น:

// Set $to to an hotmail.com or outlook.com email
$to = "YourEmail@hotmail.com";

$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 'foo@bar.com'; } );
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 );

โปรดทราบว่าฉันยังไม่ได้ทดสอบกับอีเมลจริง ...


ฉันได้เพิ่มสิ่งนี้ต้องมีปลั๊กอินและเรียกใช้รหัสทดสอบ มันทำงานได้ ฉันได้ทดสอบการแจ้งเตือนหลักเริ่มต้น (การแจ้งเตือนผู้ใช้ใหม่และอื่น ๆ ) และใช้งานได้เช่นกัน ฉันจะทำการทดสอบต่อไปในสุดสัปดาห์นี้และดูว่าปลั๊กอินจะทำงานได้อย่างไรและโดยพื้นฐานแล้วหากทุกอย่างทำงาน ฉันจะดูข้อมูลดิบของข้อความโดยเฉพาะ นี่จะเป็นงานที่ต้องใช้เวลามาก แต่ให้ความมั่นใจฉันจะรายงานกลับมาเมื่อเสร็จสิ้น หากมีสถานการณ์ที่ wp_mail () ไม่ทำงาน (เมื่อเป็นอย่างอื่น) โปรดแจ้งให้เราทราบ ขอบคุณสำหรับคำตอบนี้
Christine Cooper

สิ่งที่ดีฉันได้ตาเอาท์พุทและมันก็ดูดี - ในความเป็นจริงแพทช์เพียงทำให้ wp_mail ใช้การประมวลผลที่มั่นคงหินมาตรฐานของ PHPMailer ในกรณีที่ผ่านอาร์เรย์และค่าเริ่มต้นสิ่ง WP หลบ (สำหรับความเข้ากันได้ย้อนหลัง) ดังนั้นมันควรจะดี (ความรุ่งโรจน์เห็นได้ชัดว่าที่นี่จะไปที่ผู้เขียนแพทช์) ... ฉันจะใช้มันต่อจากนี้ไป (และในที่สุดจะกระชับย้อนยุค) - และขอขอบคุณสำหรับข้อมูลที่ใช้ทั้ง html / plain ถึง ลดโอกาสในการถูกแปดเปื้อนเป็นสแปม ...
Bonger

1
เราได้ทดสอบในทุกสถานการณ์ที่เป็นไปได้และใช้งานได้ดี เราจะยิงจดหมายข่าวในวันพรุ่งนี้และเราจะดูว่าเราได้รับการร้องเรียนจากผู้ใช้หรือไม่ การเปลี่ยนแปลงเล็กน้อยเพียงอย่างเดียวที่เราต้องทำคือการทำให้ sanitize / unsanitize อาร์เรย์เมื่อมันถูกแทรกเข้าไปใน db (มีข้อความใน que ใน db ที่ cron ส่งออกไปใน bulks ขนาดเล็ก) ฉันจะอนุญาตให้คำถามนี้ยังคงเปิดอยู่และรอดำเนินการจนกว่าเงินรางวัลจะหมดเพื่อให้เราสามารถนำการรับรู้มาสู่ปัญหานี้ หวังว่าแพทช์นี้หรือทางเลือกจะถูกเพิ่มไปยังแกน หรือที่สำคัญกว่านั้นทำไมไม่ พวกเขากำลังคิดอะไรอยู่!
Christine Cooper

ฉันสุ่มสังเกตว่าคุณได้ทำการอัพเดทตั๋ว trac ที่เชื่อมโยง นี่เป็นการอัปเดตของรหัสนี้หรือไม่ ถ้าเป็นเช่นนั้นคุณช่วยกรุณาโพสต์อัปเดตนี้โดยแก้ไขคำตอบของคุณที่นี่เช่นกัน ขอบคุณมาก.
Christine Cooper

สวัสดีไม่มีมันเป็นเพียงการรีเฟรชของแพทช์กับลำตัวปัจจุบันเพื่อให้มันผสานโดยไม่มีความขัดแย้ง (ด้วยความหวังว่ามันจะได้รับความสนใจ) รหัสก็เหมือนกัน ...
bonger

4

นี่ไม่ใช่ข้อผิดพลาด WordPress เลยมันเป็นสิ่งที่phpmailerไม่อนุญาตให้มีส่วนหัวที่กำหนดเอง ... ถ้าคุณดูที่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;
    }

คุณสามารถดูกรณีเริ่มต้นที่กระทำผิดคือสิ่งที่ส่งออกบรรทัดส่วนหัวพิเศษด้วยชุดอักขระและไม่มีขอบเขต การตั้งค่าชนิดเนื้อหาตามตัวกรองไม่ได้แก้ปัญหานี้ด้วยตัวเองเท่านั้นเนื่องจากaltเคสในที่นี้ถูกตั้งค่าmessage_typeโดยการตรวจสอบAltBodyไม่ว่างเปล่าแทนที่จะเป็นประเภทเนื้อหา

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

ในที่สุดความหมายนี้จะเกิดขึ้นทันทีที่คุณแนบไฟล์หรือรูปภาพอินไลน์หรือตั้งค่าAltBodyข้อผิดพลาดที่ละเมิดควรถูกข้าม นอกจากนี้ยังหมายความว่ามีความจำเป็นที่จะต้องกำหนดอย่างชัดเจนชนิดเนื้อหาเพราะทันทีที่มีการไม่มีการAltBodyตั้งค่าไปโดยmultipart/alternativephpmailer

ดังนั้นคำตอบง่ายๆคือ:

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

จากนั้นคุณไม่จำเป็นต้องตั้งค่าส่วนหัวอย่างชัดเจนคุณสามารถทำได้:

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

ฟังก์ชั่นและคุณสมบัติหลายอย่างในphpmailerชั้นเรียนได้รับการป้องกันโดยไม่ได้ตั้งใจถ้าไม่ใช่เพราะทางเลือกที่ถูกต้องนั้นคือเพียงแค่ตรวจสอบและแทนที่MIMEHeadersคุณสมบัติผ่านทางphpmailer_initเบ็ดก่อนที่จะส่ง


2

ฉันเพิ่งเปิดตัวปลั๊กอินเพื่อให้ผู้ใช้สามารถใช้เทมเพลต html บน WordPress และ Im ได้แล้วตอนนี้ในเวอร์ชัน devเพื่อเพิ่มทางเลือกข้อความธรรมดา ฉันทำสิ่งต่อไปนี้และในการทดสอบของฉันฉันเห็นเพียงหนึ่งขอบเขตที่เพิ่มเข้ามาและอีเมลก็เข้ามาถึง 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 );
}

ดังนั้นโดยทั่วไปสิ่งที่ฉันทำในที่นี้คือการแก้ไขวัตถุ phpmailer โหลดข้อความภายในเทมเพลต html และตั้งเป็นคุณสมบัติ Body ฉันก็เอาข้อความต้นฉบับและตั้งค่าคุณสมบัติ AltBody


2

วิธีแก้ปัญหาง่ายๆของฉันคือการใช้ html2text https://github.com/soundasleep/html2textด้วยวิธีนี้:

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

ที่นี่https://gist.github.com/ewake/6c4d22cd856456480bd77b988b5c9e80ยังมีส่วนสำคัญเกี่ยวกับ


2

สำหรับทุกคนที่ใช้เบ็ด 'phpmailer_init' เพื่อเพิ่ม 'AltBody' ของตนเอง:

ข้อความที่เป็นทางเลือกจะถูกนำมาใช้ซ้ำสำหรับการส่งอีเมลติดต่อกันที่แตกต่างกันเว้นแต่คุณจะล้างมันด้วยตนเอง! WordPress ไม่ได้ล้างมันใน wp_mail () เพราะมันไม่ได้คาดหวังว่าคุณสมบัตินี้จะใช้

ซึ่งส่งผลให้ผู้รับอาจได้รับอีเมลที่ไม่ได้มีไว้สำหรับพวกเขา โชคดีที่คนส่วนใหญ่ที่ใช้โปรแกรมรับส่งเมลที่ใช้ HTML จะไม่เห็นเวอร์ชันข้อความ แต่โดยทั่วไปแล้วยังคงเป็นปัญหาด้านความปลอดภัย

โชคดีที่มีวิธีแก้ไขง่าย ๆ ซึ่งรวมถึงบิตเปลี่ยน altbody; โปรดทราบว่าคุณต้องใช้ไลบรารี Html2Text PHP:

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

นี่คือส่วนสำคัญสำหรับปลั๊กอิน WP ที่ฉันแก้ไขเพื่อแก้ไขปัญหานี้: https://gist.github.com/youri--/c4618740b7c50c549314eaebc9f78661

น่าเสียดายที่ฉันไม่สามารถแสดงความคิดเห็นเกี่ยวกับโซลูชันอื่น ๆ โดยใช้เบ็ดข้างต้นเพื่อเตือนพวกเขาเกี่ยวกับเรื่องนี้เนื่องจากฉันยังไม่มีตัวแทนเพียงพอที่จะแสดงความคิดเห็น


1

นี่อาจไม่ใช่คำตอบที่แน่นอนสำหรับการโพสต์เริ่มต้นที่นี่ แต่เป็นทางเลือกของโซลูชั่นที่ให้ไว้ในการตั้งค่าตัวถัง

เป็นหลักฉันต้องการ (อยาก) ตั้งค่า altbody ชัดเจน (เช่นธรรมดา) นอกจากนี้ในส่วน html แทนการพึ่งพาการแปลง / เปลื้องผ้าและ whatnot ดังนั้นฉันมากับสิ่งนี้ซึ่งดูเหมือนว่าจะทำงานได้ดี

/* 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'];
        }
    }   
}

0

หากคุณไม่ต้องการที่จะสร้างความขัดแย้งใด ๆ ในรหัสแกน Wordpress ผมคิดว่าทางเลือกหรือทางออกที่ง่ายที่สุดคือการเพิ่มการดำเนินการเพื่อที่จะทำก่อนที่จะเกิดขึ้นจริงการส่งจดหมายในphpmailer_init wp_mailหากต้องการทำให้คำอธิบายของฉันง่ายขึ้นดูตัวอย่างโค้ดด้านล่าง:

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

ถ้าคุณเพิ่มเนื้อหาในชั้นเรียน PHPMailer คุณสมบัติแล้วชนิดของเนื้อหาเริ่มต้นจะตั้งค่าโดยอัตโนมัติAltBodymultipart/alternative

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.