Отправка многочастных (текстовых / html) электронных писем через wp_mail (), скорее всего, заблокирует ваш домен

37

Резюме

Из-за ошибки в WP Core отправка составных писем (html / text) с помощью wp_mail () (чтобы уменьшить вероятность попадания писем в папки со спамом) будет иронично приведет к тому, что ваш домен будет заблокирован Hotmail (и другими электронными письмами Microsoft).

Это сложная проблема, которую я постараюсь подробно описать, пытаясь помочь кому-то найти работоспособное решение, которое в конечном итоге может быть реализовано в ядре.

Это будет полезное чтение. Давайте начнем...

Ошибка

Самый распространенный совет, чтобы избежать попадания электронных писем в новостные рассылки в спам-папки, - отправлять многочастные сообщения.

Multi-part (mime) относится к отправке как HTML, так и TEXT части сообщения электронной почты в одном письме. Когда клиент получает составное сообщение, он принимает версию HTML, если он может отображать HTML, в противном случае он представляет версию в виде простого текста.

Это доказано, чтобы работать. При отправке в gmail все наши электронные письма помещались в папки со спамом, пока мы не изменили сообщения на multipart, когда они пришли в основной почтовый ящик. Отличный материал.

Теперь при отправке многокомпонентных сообщений через wp_mail (), он выводит тип контента (multipart / *) дважды, один раз с границей (если установлен отдельно) и один раз без. Это приводит к тому, что электронное письмо отображается в виде необработанного сообщения, а не в нескольких сообщениях в некоторых электронных письмах, включая все Microsoft (Hotmail, Outlook и т. Д.)

Microsoft пометит это сообщение как нежелательное, а полученное сообщение будет помечено получателем вручную. К сожалению , адреса электронной почты Microsoft широко используются. 40% наших подписчиков используют его.

Это подтверждается Microsoft через обмен электронной почтой, который мы недавно имели.

Пометка сообщений приведет к полной блокировке домена . Это означает, что сообщение не будет отправлено в папку со спамом, оно даже не будет доставлено получателю вообще.

Наш основной домен заблокирован 3 раза.

Поскольку это ошибка в ядре 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;
            }
        }
    }

Потенциальные решения

Итак, вы задаетесь вопросом, почему вы не сообщили об этом на трассе ? Я уже есть . К моему большому удивлению, 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 (), который будет отправлять многокомпонентные сообщения, но не более того

Что делать

Цель здесь - найти способ отправки как обычных (в виде обычного текста), так и составных сообщений, используя базовую функцию wp_mail () (а не пользовательскую функцию sendmail).

При попытке решить эту проблему, основная проблема, с которой вы столкнетесь, - это количество времени, которое вы потратите на отправку фиктивных сообщений, проверку их получения и, в основном, открытие ящика аспирина и проклятия в Microsoft, потому что вы привыкли к их IE выдает, в то время как гремлин здесь, к сожалению, WordPress.

Обновить

Решение, опубликованное @bonger, позволяет $messageсоздать массив, содержащий альтернативы с ключом типа содержимого. Я подтвердил, что это работает во всех сценариях.

Мы позволим этому вопросу оставаться открытым до тех пор, пока щедрость не иссякнет, чтобы повысить осведомленность о проблеме, возможно, до уровня, на котором она будет решена в основном. Не стесняйтесь размещать альтернативное решение, где $messageможет быть строка.

Кристина Купер
источник
1
Поскольку эта wp_mail()функция является подключаемой, не означает ли ее замену как обязательный плагин (в wp-content / mu-plugins), не является ли хорошим решением для вас (и для всех остальных неудачное исправление ядра)? В каком случае не сработает проверка $phpmailer->ContentType = $content_type;множественности / границы после установки (а не удаления)?
Бонгер
@bonger Можете ли вы написать ответ, подробно описывающий ваше решение?
Кристина Купер
1
Вам не нужно редактировать ядро, потому что wp_mailоно подключаемо . Скопируйте оригинальную функцию в плагин, отредактируйте ее, как вам нужно, и активируйте плагин. WordPress будет использовать вашу отредактированную функцию вместо оригинальной, без необходимости редактировать ядро.
gmazzap
@ChristineCooper Я не решаюсь сделать это, так как, как вы говорите, тестирование - это настоящая королевская боль, но, глядя на патч core.trac.wordpress.org/ticket/15448, предложенный в trac @rmccue / @ MattyRob, который выглядит действительно хорошим способом иди, так что я отправлю непроверенный ответ, основанный на этом ...
Бонгер
2
@ChristineCooper, если вы просто подключитесь к phpmailer и установите текстовое тело в $ phpmailer-> Alt. Может ли произойти такая же ошибка?
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 () не будет работать (если это не так), пожалуйста, дайте мне знать. Спасибо за этот ответ.
Кристина Купер
Хорошие вещи, я сглаживаю вывод, и он выглядит хорошо - на самом деле патч просто заставляет wp_mail использовать стандартную твердую обработку PHPMailer в случае передачи массива, а в других случаях по умолчанию используется хитроумный WP (для обратной совместимости) так что это должно быть хорошо (очевидно, что почесть здесь идет к авторам патчей) ... Я собираюсь использовать его с этого момента (и, в конце концов, переоснастить) - и спасибо за информацию, использующую оба html / plain для уменьшить вероятность быть спамом как спам ...
Бонгер
1
Мы протестировали его во всех возможных сценариях, и он отлично работает. Завтра мы выпустим новостную рассылку и посмотрим, получим ли мы какие-либо жалобы от пользователей. Единственные незначительные изменения, которые нам нужно было сделать, - это очистить / отменить очистку массива при его вставке в БД (иметь сообщения в очереди в БД, где cron отправляет его небольшими партиями). Я позволю этому вопросу оставаться открытым и ожидающим рассмотрения до тех пор, пока не закончится щедрость, чтобы мы могли привлечь внимание к этой проблеме. Надеюсь, этот патч или альтернатива будут добавлены в ядро. Или, что более важно, почему бы и нет. О чем они думают!
Кристина Купер
Я случайно заметил, что вы выполнили обновление связанного билета trac. Это обновление для этого кода? Если да, не могли бы вы опубликовать это обновление, отредактировав также свой ответ, чтобы этот ответ оставался актуальным? Большое спасибо.
Кристина Купер
Привет, нет, это было просто обновление патча для текущего транка, так что он сливается без конфликтов (в надежде на то, что он получит некоторое внимание), код точно такой же ...
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/alternative на phpmailer.

Итак, простой ответ:

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ловушки перед отправкой.

majick
источник
2

Я только что выпустил плагин, позволяющий пользователям использовать HTML-шаблоны на WordPress, и я сейчас играю на 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.

chifliiiii
источник
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 также суть о.

ewake
источник
2

Для тех, кто использует ловушку phpmailer_init, чтобы добавить свою собственную AltBody:

Альтернативное текстовое тело используется повторно для разных последовательных отправляемых писем, если вы не очистите его вручную! WordPress не очищает его в wp_mail (), потому что не ожидает использования этого свойства.

Это приводит к тому, что получатели могут получать письма, не предназначенные для них. К счастью, большинство людей, использующих почтовые клиенты с поддержкой HTML, не увидят текстовую версию, но в основном это проблема безопасности.

К счастью, это легко исправить. Это включает бит замены altbody; обратите внимание, что вам нужна 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 );
  }
}

Вот также суть плагина WP, который я модифицировал для решения этой проблемы: https://gist.github.com/youri--/c4618740b7c50c549314eaebc9f78661

К сожалению, я не могу комментировать другие решения, используя вышеупомянутую ловушку, чтобы предупредить их об этом, поскольку у меня еще недостаточно представителей, чтобы комментировать.

Тануки
источник
1

это может быть не точный ответ на первоначальный пост здесь, но это альтернатива некоторым решениям, представленным здесь, относительно установки alt body

по сути, мне нужно (хотелось) установить отдельное altbody (т.е. открытый текст) дополнительно к html-части вместо того, чтобы полагаться на некоторые преобразования / стрип-теги и еще много чего. так что я пришел с этим, который, кажется, работает просто отлично

/* 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
источник
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);

Если вы добавите контент в AltBodyсвойство класса PHPMailer, тип контента по умолчанию будет автоматически установлен на multipart/alternative.

Джошуа Рейес
источник