L'invio di e-mail multipart (testo / html) tramite wp_mail () probabilmente farà sì che il tuo dominio venga bannato


37

Sommario

A causa di un bug in WP Core, l'invio di e-mail multipart (html / text) con wp_mail () (per ridurre la possibilità che le e-mail finiscano nelle cartelle spam) risulterà ironicamente con il blocco del dominio da parte di Hotmail (e altre e-mail Microsoft).

Questo è un problema complesso che mi proporrò di analizzare dettagliatamente nel tentativo di aiutare qualcuno a trovare una soluzione praticabile che alla fine potrebbe essere implementata nel core.

Sarà una lettura gratificante. Cominciamo...

Il bug

Il consiglio più comune per evitare che le e-mail della newsletter finiscano nelle cartelle spam è di inviare messaggi multipart.

Multi-part (mime) si riferisce all'invio di una parte HTML e TEXT di un messaggio e-mail in un'unica e-mail. Quando un client riceve un messaggio multipart, accetta la versione HTML se può eseguire il rendering HTML, altrimenti presenta la versione in testo normale.

Questo ha dimostrato di funzionare. Durante l'invio a Gmail, tutte le nostre e-mail sono finite in cartelle spam fino a quando non abbiamo cambiato i messaggi in multipart quando sono arrivati ​​alla posta in arrivo principale. Roba fantastica.

Ora, quando si inviano messaggi multipart tramite wp_mail (), viene emesso il Tipo di contenuto (multipart / *) due volte, una volta con limite (se impostato su misura) e una volta senza. Questo comportamento comporta che l'email viene visualizzata come messaggio non elaborato e non multipart su alcune e-mail, incluse tutte Microsoft (Hotmail, Outlook, ecc ...)

Microsoft contrassegnerà questo messaggio come spazzatura e i pochi messaggi che arrivano verranno contrassegnati manualmente dal destinatario. Sfortunatamente , gli indirizzi di posta elettronica Microsoft sono ampiamente utilizzati. Il 40% dei nostri abbonati lo utilizza.

Ciò è confermato da Microsoft tramite uno scambio di email che abbiamo avuto di recente.

La segnalazione dei messaggi comporterà il blocco completo del dominio . Ciò significa che il messaggio non verrà inviato alla cartella spam, non verrà nemmeno recapitato al destinatario.

Finora abbiamo bloccato il nostro dominio principale 3 volte.

Poiché si tratta di un bug nel core WP, ogni dominio che invia messaggi multipart viene bloccato. Il problema è che la maggior parte dei webmaster non sa perché. Ho confermato questo quando faccio le mie ricerche e vedo altri utenti che ne discutono nei forum ecc. Richiede approfondire il codice non elaborato e avere una buona conoscenza di come funzionano questi tipi di messaggi e-mail, che andremo al prossimo ...

Dividiamolo in codice

Crea un account hotmail / outlook. Quindi, esegui il seguente codice:

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

E se vuoi cambiare il tipo di contenuto predefinito , usa:

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

Questo invierà un messaggio multipart.

Quindi, se controlli la fonte raw completa del messaggio, noterai che il tipo di contenuto viene aggiunto due volte, una volta senza limiti:

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

Questo è il problema.

La fonte del problema sta nel pluggable.php: se guardiamo da qualche parte qui:

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

Potenziali soluzioni

Quindi ti stai chiedendo, perché non l'hai segnalato a trac ? L'ho già fatto . Con mia grande sorpresa, 5 anni fa è stato creato un biglietto diverso che delineava lo stesso problema.

Ammettiamolo, è stata una mezza decade. Negli anni di Internet, è più simile a 30. Il problema è stato chiaramente abbandonato e sostanzialmente non verrà mai risolto (... a meno che non lo risolviamo qui).

Ho trovato un ottimo thread qui che offre una soluzione, ma mentre la sua soluzione funziona, rompe le email che non hanno un $headersset personalizzato .

Ecco dove ci schiantiamo ogni volta. O la versione multipart funziona bene e i normali $headersmessaggi non impostati non funzionano, o viceversa.

La soluzione che abbiamo trovato è stata:

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

}

Sì, lo so, la modifica dei file core è un tabù, siediti ... questa è stata una soluzione disperata e uno scarso tentativo di fornire una soluzione per core.

Il problema con la nostra correzione è che le e-mail predefinite come nuove registrazioni, commenti, reimpostazione della password ecc. Verranno recapitate come messaggi vuoti. Quindi abbiamo uno script wp_mail () funzionante che invierà messaggi multipart ma nient'altro.

Cosa fare

Lo scopo qui è quello di trovare un modo per inviare sia messaggi normali (testo normale) che multipart utilizzando la funzione core wp_mail () (non una funzione sendmail personalizzata).

Quando tenterai di risolvere questo problema, il problema principale che incontrerai è la quantità di tempo che impiegherai per inviare messaggi fittizi, controllando se sono ricevuti e fondamentalmente aprendo una scatola di aspirina e imprecando contro Microsoft perché sei abituato al loro IE problemi mentre il gremlin qui è purtroppo WordPress.

Aggiornare

La soluzione pubblicata da @bonger consente $messagedi essere un array contenente alternative con chiave di tipo contenuto. Ho confermato che funziona in tutti gli scenari.

Consentiremo che questa domanda rimanga aperta fino a quando non si esaurirà la ricompensa per aumentare la consapevolezza del problema, forse a un livello in cui verrà risolto nel nucleo. Sentiti libero di pubblicare una soluzione alternativa dove $messagepuò essere una stringa.


1
Dato che la wp_mail()funzione è innestabile non sta definendo il tuo sostituto come un plugin da usare (in wp-content / mu-plugins) non è una buona soluzione per te (e per tutti gli altri, mancante correzione del core)? In tal caso, non spostare il controllo multipart / boundary dopo che l'impostazione $phpmailer->ContentType = $content_type;(anziché elsing) non funziona?
bonger,

@bonger Puoi per favore scrivere una risposta in dettaglio la tua soluzione?
Christine Cooper

1
Non è necessario modificare il core, perché wp_mailè collegabile . Copia la funzione originale in un plugin, modificala come ti serve e attiva il plugin. WordPress utilizzerà la tua funzione modificata anziché quella originale, senza bisogno di modificare il core.
gmazzap

@ChristineCooper Esito a farlo come dici tu che testare è un tale dolore reale, ma guardando la patch core.trac.wordpress.org/ticket/15448 suggerita in trac da @ rmccue / @ MattyRob che sembra davvero un bel modo di vai così
posterò

2
@ChristineCooper se si aggancia semplicemente phpmailer e si imposta il corpo del testo in $ phpmailer-> AltBody si verifica lo stesso errore?
chifliiiii,

Risposte:


15

La seguente versione di wp_mail()è con la patch applicata di @ rmccue / @ MattyRob nel ticket https://core.trac.wordpress.org/ticket/15448 , aggiornato per 4.2.2, che consente $messagedi essere un array contenente tipo di contenuto sostituti con chiave:

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

Quindi, se lo metti nel tuo file "wp-content / mu-plugins / Functions.php", ad esempio, sovrascriverà la versione di WP. Ha un buon uso senza fare confusione con le intestazioni, ad esempio:

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

Si prega di notare che non ho provato questo con e-mail reali ...


Ho aggiunto questo per avere plugin ed eseguito il codice di test; ha funzionato. Ho testato le notifiche di base predefinite (notifica di un nuovo utente ecc.) E ha funzionato anche. Continuerò a eseguire test questo fine settimana e vedrò come funzioneranno i plugin con questo e sostanzialmente se tutto funziona. Esaminerò specificamente i dati grezzi del messaggio. Sarà un compito che richiede molto tempo ma state tranquilli, riporterò indietro una volta terminato. Se esiste uno scenario in cui wp_mail () non funzionerà (quando altrimenti dovrebbe), per favore fatemelo sapere. Grazie per questa risposta
Christine Cooper

Roba buona, ho sbirciato l'output e sembra buono - in effetti la patch fa solo in modo che wp_mail usi l'elaborazione solida solida standard di PHPMailer nel caso di passaggio di un array, e altrimenti per impostazione predefinita al rozzo materiale WP (per compatibilità con le versioni precedenti) quindi dovrebbe essere buono (ovviamente i complimenti qui vanno agli autori delle patch) ... lo userò da ora in poi (e alla fine si adatta in modo retrò) - e grazie per l'informazione che uso sia html / plain per ridurre le possibilità di essere incastrato come spam ...
bonger

1
L'abbiamo testato in tutti gli scenari possibili e funziona alla grande. Domani pubblicheremo una newsletter e vedremo se riceviamo reclami dagli utenti. Le uniche piccole modifiche che dovevamo fare era di disinfettare / annullare laanizzazione dell'array quando veniva inserito nel db (avere messaggi in una coda nel db in cui un cron lo invia in piccole quantità). Lascerò che questa domanda rimanga aperta e in sospeso fino allo scadere della taglia in modo da poter sensibilizzare su questo problema. Spero che questa patch o un'alternativa vengano aggiunte al core. O ancora più importante, perché no. Cosa stanno pensando!
Christine Cooper

Ho notato casualmente che hai eseguito un aggiornamento al ticket trac collegato. È un aggiornamento a questo codice? In tal caso, potresti gentilmente pubblicare questo aggiornamento modificando la tua risposta anche qui in modo che questa risposta rimanga aggiornata? Grazie mille.
Christine Cooper

Ciao, no, era solo un aggiornamento della patch contro il trunk corrente in modo che si fondesse senza conflitti (nella speranza che ottenga un po 'di attenzione), il codice è esattamente lo stesso ...
bonger

4

Questo non è affatto un bug di WordPress, è phpmaileruno di quelli che non consentono le intestazioni personalizzate ... se guardi 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;
    }

È possibile vedere il caso predefinito offensivo è ciò che sta producendo la riga di intestazione aggiuntiva con set di caratteri e senza limiti. L'impostazione del tipo di contenuto in base al filtro non risolve questo problema da solo perché il altcaso qui è impostato message_typecontrollando che AltBodynon sia vuoto anziché il tipo di contenuto.

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

Alla fine, ciò significa che non appena si allega un file o un'immagine incorporata, o si imposta il AltBody, il bug incriminato dovrebbe essere bypassato. Significa anche che non è necessario impostare in modo esplicito il tipo di contenuto perché non appena AltBodyè stato impostato multipart/alternativeda phpmailer.

Quindi la semplice risposta è:

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

Quindi non è necessario impostare esplicitamente le intestazioni, puoi semplicemente fare:

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

Sfortunatamente molte delle funzioni e proprietà della phpmailerclasse sono protette, se non per quello una valida alternativa sarebbe semplicemente controllare e sovrascrivere la MIMEHeadersproprietà tramite l' phpmailer_inithook prima dell'invio.


2

Ho appena rilasciato un plug-in per consentire agli utenti di utilizzare modelli html su WordPress e sto giocando subito con la versione dev per aggiungere un semplice fallback di testo. Ho fatto quanto segue e nei miei test vedo solo un limite aggiunto e le e-mail arrivano su 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 );
}

Quindi sostanzialmente quello che faccio qui è modificare l'oggetto phpmailer, caricare il messaggio all'interno di un modello html e impostarlo sulla proprietà Body. Inoltre ho preso il messaggio originale e ho impostato la proprietà AltBody.


2

La mia semplice soluzione è quella di utilizzare html2text https://github.com/soundasleep/html2text in questo modo:

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

Qui https://gist.github.com/ewake/6c4d22cd856456480bd77b988b5c9e80 anche una sintesi.


2

Per chiunque utilizzi l'hook 'phpmailer_init' per aggiungere il proprio 'AltBody':

Il corpo del testo alternativo viene riutilizzato per diverse e-mail consecutive inviate, a meno che non lo si cancella manualmente! WordPress non lo cancella in wp_mail () perché non prevede l'utilizzo di questa proprietà.

Ciò comporta che i destinatari potenzialmente ricevono posta non destinata a loro. Fortunatamente la maggior parte delle persone che usano client di posta abilitati per HTML non vedranno la versione testuale, ma è ancora fondamentalmente un problema di sicurezza.

Fortunatamente c'è una soluzione semplice. Ciò include la punta di sostituzione dell'altbody; nota che hai bisogno della libreria 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 );
  }
}

Ecco anche una sintesi di un plug-in WP che ho modificato per risolvere questo problema: https://gist.github.com/youri--/c4618740b7c50c549314eaebc9f78661

Sfortunatamente non posso commentare le altre soluzioni usando il suddetto hook, per avvisarli di questo, dato che non ho ancora abbastanza rappresentanti per commentare.


1

questa potrebbe non essere una risposta esatta al post iniziale qui, ma è un'alternativa ad alcune delle soluzioni qui fornite riguardanti l'impostazione di un corpo alternativo

in sostanza, dovevo (volevo) impostare un altbody distinto (cioè un testo in chiaro) in aggiunta alla parte html invece di fare affidamento su alcune conversioni / striptag e quant'altro. quindi ho pensato a questo che sembra funzionare bene

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

Se non si desidera creare alcun conflitto di codice nel core di Wordpress, penso che la soluzione alternativa o più semplice sia quella di aggiungere un'azione phpmailer_initche farà prima dell'effettivo invio della posta nel wp_mail. Per semplificare la mia spiegazione, vedi l'esempio di codice seguente:

<?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 aggiungi un contenuto nella AltBodyproprietà della classe PHPMailer , il tipo di contenuto predefinito verrà automaticamente impostato su multipart/alternative.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.