O envio de e-mails com várias partes (texto / html) via wp_mail () provavelmente banirá seu domínio

37

Sumário

Devido a um erro no WP Core, o envio de emails com várias partes (html / text) com wp_mail () (para reduzir a chance de emails terminarem em pastas de spam) ironicamente resultará em seu domínio ser bloqueado pelo Hotmail (e outros emails da Microsoft).

Esse é um problema complexo que tentarei detalhar em detalhes, na tentativa de ajudar alguém a encontrar uma solução viável que possa eventualmente ser implementada no núcleo.

Será uma leitura gratificante. Vamos começar...

O inseto

O conselho mais comum para evitar que os emails de seu boletim acabem nas pastas de spam é enviar mensagens com várias partes.

Múltiplas partes (mime) refere-se ao envio de uma parte HTML e TEXT de uma mensagem de email em um único email. Quando um cliente recebe uma mensagem com várias partes, ele aceita a versão HTML se puder renderizar HTML; caso contrário, apresenta a versão em texto sem formatação.

Está provado que isso funciona. Ao enviar para o gmail, todos os nossos emails foram parar em pastas de spam até mudarmos as mensagens para multipartes quando chegaram à caixa de entrada principal. Coisas boas.

Agora, ao enviar mensagens com várias partes via wp_mail (), ele gera o Tipo de conteúdo (multipart / *) duas vezes, uma com limite (se definido de maneira personalizada) e uma vez sem. Esse comportamento resulta com o email sendo exibido como uma mensagem bruta e não com várias partes em alguns emails, incluindo todos os Microsoft (Hotmail, Outlook, etc ...)

A Microsoft sinalizará esta mensagem como lixo eletrônico e as poucas mensagens enviadas serão sinalizadas manualmente pelo destinatário. Infelizmente , os endereços de email da Microsoft são amplamente usados. 40% dos nossos assinantes usam.

Isso é confirmado pela Microsoft por meio de uma troca de e-mails que tivemos recentemente.

A sinalização das mensagens resultará com o domínio sendo completamente bloqueado . Isso significa que a mensagem não será enviada para a pasta de spam, nem será entregue ao destinatário.

Até agora, nosso domínio principal foi bloqueado três vezes.

Como esse é um bug no núcleo do WP, todos os domínios que enviam mensagens com várias partes estão sendo bloqueados. O problema é que a maioria dos webmasters não sabe o porquê. Confirmei isso ao fazer minha pesquisa e ver outros usuários discutindo isso em fóruns, etc. Isso exige que você se aprofunde no código bruto e tenha um bom conhecimento de como esse tipo de mensagem de email funciona, o que vamos seguir ...

Vamos dividi-lo em código

Crie uma conta do hotmail / outlook. Em seguida, execute o seguinte código:

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

E se você deseja alterar o tipo de conteúdo padrão , use:

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

Isso enviará uma mensagem com várias partes.

Portanto, se você verificar a fonte bruta completa da mensagem, notará que o tipo de conteúdo é adicionado duas vezes, uma vez sem limite:

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

Essa é a questão.

A fonte do problema está pluggable.php- se olharmos para algum lugar aqui:

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

Soluções potenciais

Então você está se perguntando, por que você não relatou isso no trac ? Eu já tenho . Para minha grande surpresa, um bilhete diferente foi criado há 5 anos, descrevendo o mesmo problema.

Vamos ser sinceros, já faz meia década. Nos anos da internet, isso é mais ou menos 30. O problema foi claramente abandonado e basicamente nunca será resolvido (... a menos que se o resolvamos aqui).

Encontrei um ótimo tópico aqui oferecendo uma solução, mas enquanto a solução funciona, ele quebra e-mails que não têm um $headersconjunto personalizado .

É aí que batemos toda vez. A versão multipartes funciona bem, e as $headersmensagens não configuradas normais não funcionam ou vice-versa.

A solução que encontramos foi:

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

}

Sim, eu sei, editar arquivos principais é um tabu, sente-se ... essa foi uma correção desesperada e uma tentativa pobre de fornecer uma correção para o núcleo.

O problema com nossa correção é que os emails padrão, como novos registros, comentários, redefinição de senha, etc. serão entregues como mensagens em branco. Portanto, temos um script wp_mail () que envia mensagens com várias partes, mas nada mais.

O que fazer

O objetivo aqui é encontrar uma maneira de enviar mensagens normais (texto sem formatação) e multipartes usando a função principal wp_mail () (não uma função personalizada do sendmail).

Ao tentar resolver isso, o principal problema que você encontrará é a quantidade de tempo que você gastará no envio de mensagens falsas, verificando se elas são recebidas e basicamente abrindo uma caixa de aspirina e xingando na Microsoft porque você está acostumado a isso. Problemas do IE enquanto o gremlin aqui é infelizmente o WordPress.

Atualizar

A solução publicada por @bonger permite $messageser uma matriz que contém alternativas com chave do tipo de conteúdo. Eu confirmei que funciona em todos os cenários.

Permitiremos que essa questão permaneça em aberto até que a recompensa acabe, para aumentar a conscientização sobre o problema, talvez até um nível em que ele seja fixado no núcleo. Sinta-se à vontade para postar uma solução alternativa onde $messagepossa ser uma string.

Christine Cooper
fonte
11
Como a wp_mail()função é conectável, a definição de sua substituição como um plug-in obrigatório (no wp-content / mu-plugins) não é uma boa solução para você (e para todos os outros, com falha na correção do núcleo)? Nesse caso, não mover a verificação multipartes / limite para depois da configuração $phpmailer->ContentType = $content_type;(em vez de excluir) não funcionaria?
Bonger
@bonger Você pode escrever uma resposta detalhando sua solução?
Christine Cooper
11
Você não precisa editar o núcleo, porque ele wp_mailé plugável . Copie a função original em um plugin, edite-a conforme necessário e ative o plugin. O WordPress usará sua função editada em vez da original, sem a necessidade de editar o núcleo.
gmazzap
@ChristineCooper Hesito em fazer isso como você diz que o teste é uma dor real, mas olhando para o patch core.trac.wordpress.org/ticket/15448 sugerido no trac por @ rmccue / @ MattyRob, que parece uma maneira muito legal de então eu vou postar uma resposta não testada com base nisso ...
bonger
2
@ChristineCooper se você simplesmente conectar o phpmailer e definir o corpo do texto em $ phpmailer-> AltBody, o mesmo erro acontece?
21815 chifliiiii

Respostas:

15

A seguinte versão do wp_mail() é com o patch aplicado de @ rmccue / @ MattyRob no ticket https://core.trac.wordpress.org/ticket/15448 , atualizado para 4.2.2, que permite $messageser uma matriz que contém o tipo de conteúdo suplentes com chave:

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

Portanto, se você colocar isso no seu arquivo, por exemplo, "wp-content / mu-plugins / functions.php", ele substituirá a versão do WP. Ele tem um bom uso, sem mexer nos cabeçalhos, por exemplo:

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

Observe que não testei isso com e-mails reais ...

bonger
fonte
Eu adicionei isso para ter plugins e executei o código de teste; funcionou. Testei as notificações principais padrão (notificação de novos usuários, etc.) e funcionou também. Continuarei a realizar testes neste fim de semana e ver como os plugins funcionarão com isso e, basicamente, se tudo funcionar. Examinarei especificamente os dados brutos da mensagem. Esta será uma tarefa que consumirá muito tempo, mas tenha certeza, informarei quando terminar. Se houver um cenário em que o wp_mail () não funcione (quando deveria), informe-me. Obrigado por esta resposta.
Christine Cooper
Coisas boas, olhei a saída e parece boa - na verdade, o patch apenas faz o wp_mail usar o processamento sólido padrão do PHPMailer no caso de passar uma matriz e, por outro lado, usar como padrão o material desonesto do WP (para compatibilidade com versões anteriores) por isso deve ser bom (obviamente, os parabéns aqui vão para os autores do patch) ... eu vou usá-lo a partir de agora (e, eventualmente, adaptando-o eventualmente) - e obrigado novamente pelas informações sobre o uso de html / plain para reduzir as chances de ser asfaltada como spam ...
Bonger
11
Testamos em todos os cenários possíveis e está funcionando muito bem. Amanhã enviaremos um boletim informativo e veremos se recebemos alguma reclamação dos usuários. As únicas pequenas alterações que precisávamos fazer eram desinfetar / desanimar a matriz quando ela está sendo inserida no banco de dados (tem mensagens em uma fila no banco de dados em que um cron envia em pequenas quantidades). Permitirei que essa pergunta permaneça aberta e pendente até que a recompensa acabe, para que possamos conscientizar sobre esse assunto. Felizmente, esse patch ou uma alternativa será adicionado ao núcleo. Ou, mais importante, porque não. O que eles estão pensando!
Christine Cooper
Percebi aleatoriamente que você executou uma atualização no tíquete trac vinculado. Esta é uma atualização para este código? Em caso afirmativo, você poderia gentilmente postar esta atualização editando sua resposta aqui também para que essa resposta permaneça atualizada? Muito obrigado.
Christine Cooper
Oi, não era apenas uma atualização do patch contra o tronco atual para que ele se funde sem conflitos (na esperança de ele ficar um pouco de atenção), o código é exatamente o mesmo ...
Bonger
4

Este não é realmente um bug do WordPress, ele phpmailernão permite cabeçalhos personalizados ... se você olhar para 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;
    }

Você pode ver que o caso padrão ofensivo é o que está gerando a linha de cabeçalho extra com charset e sem limite. Definir o tipo de conteúdo por filtro não resolve isso por si só, apenas porque o altcaso aqui é ativado message_typepela verificação AltBodynão está vazio, e não o tipo de conteúdo.

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

No final, o que isso significa é que, assim que você anexa um arquivo ou imagem embutida, ou define o AltBodyerro, o bug incorreto deve ser ignorado. Isso também significa que não há necessidade de definir explicitamente o tipo de conteúdo, porque assim que houver um, AltBodyele será definido multipart/alternativepor phpmailer.

Portanto, a resposta simples é:

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

Então você não precisa definir os cabeçalhos explicitamente, basta:

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

Infelizmente, muitas das funções e propriedades da phpmailerclasse estão protegidas, se não fosse por isso, uma alternativa válida seria simplesmente verificar e substituir a MIMEHeaderspropriedade pelo phpmailer_initgancho antes de enviar.

majick
fonte
2

Acabei de lançar um plug - in para permitir que os usuários usem modelos html no WordPress e estou jogando agora na versão dev para adicionar um fallback de texto simples. Fiz o seguinte e, em meus testes, vejo apenas um limite adicionado e os emails estão chegando bem ao 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 );
}

Então, basicamente, o que faço aqui é modificar o objeto phpmailer, carregar a mensagem dentro de um modelo html e configurá-lo para a propriedade Body. Também peguei a mensagem original e defina a propriedade AltBody.

chifliiiii
fonte
2

Minha solução simples é usar o html2text https://github.com/soundasleep/html2text desta maneira:

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

Aqui também https://gist.github.com/ewake/6c4d22cd856456480bd77b988b5c9e80 .

acordar
fonte
2

Para quem estiver usando o gancho 'phpmailer_init' para adicionar seu próprio 'AltBody':

O corpo do texto alternativo é reutilizado para diferentes e-mails consecutivos enviados, a menos que você o limpe manualmente! O WordPress não o limpa em wp_mail () porque não espera que essa propriedade seja usada.

Isso resulta em destinatários potencialmente recebendo e-mails não destinados a eles. Felizmente, a maioria das pessoas que usa clientes de email habilitados para HTML não vê a versão em texto, mas ainda é basicamente um problema de segurança.

Felizmente, há uma solução fácil. Isso inclui o bit de substituição do altbody; Observe que você precisa da biblioteca 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 );
  }
}

Aqui está também uma síntese de um plugin WP que modifiquei para corrigir esse problema: https://gist.github.com/youri--/c4618740b7c50c549314eaebc9f78661

Infelizmente, não posso comentar sobre as outras soluções usando o gancho mencionado acima, para avisá-las disso, pois ainda não tenho representante suficiente para comentar.

Tanuki
fonte
1

isso pode não ser uma resposta exata para a postagem inicial aqui, mas é uma alternativa para algumas das soluções aqui fornecidas, relacionadas à configuração de um corpo alternativo

essencialmente, eu precisava (queria) definir um altbody distinto (ou seja, texto simples) adicionalmente à parte html, em vez de depender de algumas conversões / striptags e outros enfeites. então eu vim com isso que parece funcionar muito bem

/* 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
fonte
0

Se você não deseja criar nenhum conflito de código no núcleo do Wordpress, acho que a solução alternativa ou mais simples é adicionar uma ação phpmailer_initque será feita antes do envio real do e-mail no wp_mail. Para simplificar minha explicação, veja o exemplo de código abaixo:

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

Se você adicionar um conteúdo na AltBodypropriedade da classe PHPMailer , o tipo de conteúdo padrão será automaticamente definido como multipart/alternative.

Joshua Reyes
fonte