L'invio di email multipart (text/html) tramite wp_mail() potrebbe causare il ban del tuo dominio

18 giu 2015, 18:37:09
Visualizzazioni: 26.6K
Voti: 44

Riepilogo

A causa di un bug nel Core di WordPress, l'invio di email multipart (html/text) con wp_mail() (per ridurre la possibilità che le email finiscano nelle cartelle spam) ironicamente farà sì che il tuo dominio venga bloccato da Hotmail (e altre email Microsoft).

Questo è un problema complesso che cercherò di analizzare in dettaglio nel tentativo di aiutare qualcuno a trovare una soluzione funzionante che potrebbe eventualmente essere implementata nel core.

Sarà una lettura gratificante. Iniziamo...

Il bug

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

Multi-part (mime) si riferisce all'invio sia della parte HTML che di quella TESTO di un messaggio email in una singola email. Quando un client riceve un messaggio multipart, accetta la versione HTML se può renderizzare l'HTML, altrimenti presenta la versione in testo semplice.

Questo è dimostrato che funziona. Quando inviavamo a Gmail, tutte le nostre email finivano nelle cartelle spam finché non abbiamo cambiato i messaggi in multipart, quando sono arrivati nella casella principale. Ottimo risultato.

Ora, quando si inviano messaggi multipart tramite wp_mail(), viene emesso il Content Type (multipart/*) due volte, una volta con boundary (se impostato manualmente) e una volta senza. Questo comportamento fa sì che l'email venga visualizzata come messaggio raw e non multipart su alcune email, incluse tutte quelle Microsoft (Hotmail, Outlook, ecc...)

Microsoft contrassegnerà questo messaggio come spam, e i pochi messaggi che passano verranno segnalati manualmente dal destinatario. Sfortunatamente, gli indirizzi email Microsoft sono ampiamente utilizzati. Il 40% dei nostri iscritti li utilizza.

Questo è stato confermato da Microsoft tramite uno scambio di email che abbiamo avuto recentemente.

La segnalazione dei messaggi porterà al blocco completo del dominio. Ciò significa che i messaggi non verranno inviati alla cartella spam, non verranno proprio consegnati al destinatario.

Il nostro dominio principale è stato bloccato già 3 volte finora.

Poiché questo è un bug nel core di WordPress, ogni dominio che invia messaggi multipart viene bloccato. Il problema è che la maggior parte dei webmaster non sa perché. L'ho confermato durante la mia ricerca vedendo altri utenti discuterne nei forum ecc. Richiede di addentrarsi nel codice raw e avere una buona conoscenza di come funzionano questi tipi di messaggi email, che andremo ad analizzare ora...

Analizziamo il codice

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

// Imposta $to con un'email hotmail.com o outlook.com 
$to = "TuaEmail@hotmail.com";

$subject = 'test wp_mail multipart';

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

Ciao mondo! Questo è testo semplice...


------=_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>Ciao Mondo! Questo è 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"';


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

E se vuoi cambiare il content type 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 il codice sorgente completo del messaggio, noterai che il content type viene aggiunto due volte, una volta senza boundary:

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

Questo è il problema.

L'origine del problema si trova in pluggable.php - se guardiamo da qualche parte qui:

// Imposta Content-Type e charset
    // Se non abbiamo un content-type dagli header di input
    if ( !isset( $content_type ) )
        $content_type = 'text/plain';

    /**
     * Filtra il content type di wp_mail().
     *
     * @since 2.3.0
     *
     * @param string $content_type Content type predefinito di wp_mail().
     */
    $content_type = apply_filters( 'wp_mail_content_type', $content_type );

    $phpmailer->ContentType = $content_type;

    // Imposta se è testo semplice, in base a $content_type
    if ( 'text/html' == $content_type )
        $phpmailer->IsHTML( true );

    // Se non abbiamo un charset dagli header di input
    if ( !isset( $charset ) )
        $charset = get_bloginfo( 'charset' );

    // Imposta il content-type e charset

    /**
     * Filtra il charset predefinito di wp_mail().
     *
     * @since 2.3.0
     *
     * @param string $charset Charset email predefinito.
     */
    $phpmailer->CharSet = apply_filters( 'wp_mail_charset', $charset );

    // Imposta gli header personalizzati
    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

Ti starai chiedendo, perché non hai segnalato questo su trac? L'ho già fatto. Con mia grande sorpresa, un ticket diverso era stato creato 5 anni fa che descriveva lo stesso problema.

Ammettiamolo, sono passati cinque anni. In anni internet, è più come 30. Il problema è stato chiaramente abbandonato e praticamente 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 $headers personalizzati impostati.

È qui che ci blocchiamo ogni volta. O la versione multipart funziona bene e i messaggi normali senza $headers impostati no, 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;

    // Imposta se è plaintext, in base a $content_type
    if ( 'text/html' == $content_type )
        $phpmailer->IsHTML( true );

    // Se non abbiamo un charset dagli header di input
    if ( !isset( $charset ) )
        $charset = get_bloginfo( 'charset' );
}

// Imposta il content-type e charset

/**
 * Filtra il charset predefinito di wp_mail().
 *
 * @since 2.3.0
 *
 * @param string $charset Charset email predefinito.
 */
$phpmailer->CharSet = apply_filters( 'wp_mail_charset', $charset );

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

}

Sì, lo so, modificare i file core è tabù, siediti... questa era una correzione disperata e un tentativo poco efficace di fornire una correzione per il core.

Il problema con la nostra correzione è che le email predefinite come nuove registrazioni, commenti, reset password ecc. verranno consegnate come messaggi vuoti. Quindi abbiamo uno script wp_mail() funzionante che invierà messaggi multipart ma nient'altro.

Cosa fare

L'obiettivo qui è trovare un modo per inviare sia messaggi normali (testo semplice) che multipart usando la funzione wp_mail() del core (non una funzione sendmail personalizzata).

Quando si tenta di risolvere questo problema, il problema principale che incontrerai è la quantità di tempo che passerai a inviare messaggi di prova, controllare se vengono ricevuti e praticamente aprire una scatola di aspirina e maledire Microsoft perché sei abituato ai loro problemi con IE mentre il gremlin qui è sfortunatamente WordPress.

Aggiornamento

La soluzione pubblicata da @bonger permette a $message di essere un array contenente alternative con chiave content-type. Ho confermato che funziona in tutti gli scenari.

Permetteremo a questa domanda di rimanere aperta fino alla scadenza della taglia per sensibilizzare sul problema, forse a un livello tale da essere corretto nel core. Sentiti libero di pubblicare una soluzione alternativa dove $message può essere una stringa.

7
Commenti

Poiché la funzione wp_mail() è pluggable, non sarebbe una buona soluzione definire la tua sostituzione come un plugin must-use (in wp-content/mu-plugins) per te (e per tutti gli altri, in attesa di una correzione nel core)? In quale caso spostare il controllo multipart/boundary dopo l'impostazione di $phpmailer->ContentType = $content_type; (anziché usare else) non funzionerebbe?

bonger bonger
18 giu 2015 21:35:18

@bonger Potresti scrivere una risposta dettagliata con la tua soluzione?

Christine Cooper Christine Cooper
18 giu 2015 22:07:28

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

gmazzap gmazzap
19 giu 2015 04:41:45

@ChristineCooper Esito a farlo perché, come dici tu, i test sono una vera seccatura, ma guardando la patch https://core.trac.wordpress.org/ticket/15448 suggerita in trac da @rmccue/@MattyRob sembra davvero un'ottima strada da percorrere, quindi pubblicherò una risposta non testata basata su quella...

bonger bonger
19 giu 2015 08:17:54

Questo non è in alcun modo una soluzione al problema in questione, ma penso valga la pena sottolineare: se sei serio riguardo al fatto che le tue email raggiungano il destinatario previsto, dovresti considerare l'uso di un server SMTP esterno. Un plugin SMTP aggirerebbe completamente questo problema, il che potrebbe spiegare perché nessuno si è preoccupato di risolverlo per cinque anni.

Mathew Tinsley Mathew Tinsley
19 giu 2015 09:03:30

@ChristineCooper se semplicemente agganci phpmailer e imposti il corpo del testo in $phpmailer->AltBody si verifica lo stesso errore?

chifliiiii chifliiiii
10 lug 2015 19:52:50

Trovato un modo per usare wp_mail() così com'è senza modificare il core. Guarda la mia risposta qui sotto.

TheAddonDepot TheAddonDepot
20 dic 2020 01:08:31
Mostra i restanti 2 commenti
Tutte le risposte alla domanda 9
14
20

La seguente versione di wp_mail() include la patch applicata da @rmccue/@MattyRob nel ticket https://core.trac.wordpress.org/ticket/15448, aggiornata per la 4.2.2, che permette a $message di essere un array contenente alternative con chiavi per il content-type:

/**
 * Invia email, simile alla funzione mail di PHP
 *
 * Un valore di ritorno true non significa automaticamente che l'utente ha ricevuto
 * l'email con successo. Indica solo che il metodo utilizzato è stato in grado di
 * elaborare la richiesta senza errori.
 *
 * Utilizzando gli hook 'wp_mail_from' e 'wp_mail_from_name' è possibile creare
 * un indirizzo from come 'Nome <email@indirizzo.com>' quando entrambi sono impostati. Se
 * è impostato solo 'wp_mail_from', verrà utilizzato solo l'indirizzo email senza nome.
 *
 * Il content-type predefinito è 'text/plain' che non consente l'uso di HTML.
 * Tuttavia, è possibile impostare il content-type dell'email utilizzando il filtro
 * 'wp_mail_content_type'.
 *
 * Se $message è un array, la chiave di ciascun elemento viene utilizzata per aggiungere
 * un allegato con il valore usato come corpo. L'elemento 'text/plain' è utilizzato come
 * versione testo del corpo, mentre 'text/html' è usato come versione HTML del corpo.
 * Tutti gli altri tipi sono aggiunti come allegati.
 *
 * Il charset predefinito è basato sul charset utilizzato nel blog. Il charset può
 * essere impostato usando il filtro 'wp_mail_charset'.
 *
 * @since 1.2.1
 *
 * @uses PHPMailer
 *
 * @param string|array $to Array o lista separata da virgole di indirizzi email a cui inviare il messaggio.
 * @param string $subject Oggetto dell'email
 * @param string|array $message Contenuto del messaggio
 * @param string|array $headers Opzionale. Intestazioni aggiuntive.
 * @param string|array $attachments Opzionale. File da allegare.
 * @return bool Indica se il contenuto dell'email è stato inviato con successo.
 */
function wp_mail( $to, $subject, $message, $headers = '', $attachments = array() ) {
    // Compatta gli input, applica i filtri e li estrae nuovamente

    /**
     * Filtra gli argomenti di wp_mail().
     *
     * @since 2.2.0
     *
     * @param array $args Un array compatto degli argomenti di wp_mail(), inclusi i valori
     *                    "to", subject, message, headers e attachments.
     */
    $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;

    // (Ri)crea l'istanza, se mancante
    if ( ! ( $phpmailer instanceof PHPMailer ) ) {
        require_once ABSPATH . WPINC . '/class-phpmailer.php';
        require_once ABSPATH . WPINC . '/class-smtp.php';
        $phpmailer = new PHPMailer( true );
    }

    // Intestazioni
    if ( empty( $headers ) ) {
        $headers = array();
    } else {
        if ( !is_array( $headers ) ) {
            // Suddivide le intestazioni, così questa funzione può accettare sia
            // stringhe di intestazioni che array di intestazioni.
            $tempheaders = explode( "\n", str_replace( "\r\n", "\n", $headers ) );
        } else {
            $tempheaders = $headers;
        }
        $headers = array();
        $cc = array();
        $bcc = array();

        // Se ci sono effettivamente contenuti
        if ( !empty( $tempheaders ) ) {
            // Itera attraverso le intestazioni grezze
            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;
                }
                // Suddivide nome e contenuto
                list( $name, $content ) = explode( ':', trim( $header ), 2 );

                // Pulizia
                $name    = trim( $name    );
                $content = trim( $content );

                switch ( strtolower( $name ) ) {
                    // Principalmente per legacy -- processa un'intestazione From: se presente
                    case 'from':
                        $bracket_pos = strpos( $content, '<' );
                        if ( $bracket_pos !== false ) {
                            // Il testo prima dell'email tra parentesi è il nome "From".
                            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 );

                        // Evita di impostare un $from_email vuoto.
                        } elseif ( '' !== trim( $content ) ) {
                            $from_email = trim( $content );
                        }
                        break;
                    case 'content-type':
                        if ( is_array($message) ) {
                            // Email multipart, ignora l'intestazione content-type
                            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 = '';
                            }

                        // Evita di impostare un $content_type vuoto.
                        } 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:
                        // Aggiunge all'array principale delle intestazioni
                        $headers[trim( $name )] = trim( $content );
                        break;
                }
            }
        }
    }

    // Svuota i valori che potrebbero essere impostati
    $phpmailer->ClearAllRecipients();
    $phpmailer->ClearAttachments();
    $phpmailer->ClearCustomHeaders();
    $phpmailer->ClearReplyTos();

    $phpmailer->Body= '';
    $phpmailer->AltBody= '';

    // Email e nome del mittente
    // Se non abbiamo un nome dalle intestazioni di input
    if ( !isset( $from_name ) )
        $from_name = 'WordPress';

    /* Se non abbiamo un'email dalle intestazioni di input, usa wordpress@$sitename
     * Alcuni host bloccheranno l'invio di email da questo indirizzo se non esiste ma
     * non ci sono alternative semplici. Usare admin_email potrebbe sembrare un'altra
     * opzione ma alcuni host potrebbero rifiutare l'invio da un dominio sconosciuto. Vedi
     * https://core.trac.wordpress.org/ticket/5007.
     */

    if ( !isset( $from_email ) ) {
        // Ottiene il dominio del sito e rimuove www.
        $sitename = strtolower( $_SERVER['SERVER_NAME'] );
        if ( substr( $sitename, 0, 4 ) == 'www.' ) {
            $sitename = substr( $sitename, 4 );
        }

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

    /**
     * Filtra l'indirizzo email del mittente.
     *
     * @since 2.2.0
     *
     * @param string $from_email Indirizzo email del mittente.
     */
    $phpmailer->From = apply_filters( 'wp_mail_from', $from_email );

    /**
     * Filtra il nome associato all'indirizzo email "from".
     *
     * @since 2.3.0
     *
     * @param string $from_name Nome associato all'indirizzo email "from".
     */
    $phpmailer->FromName = apply_filters( 'wp_mail_from_name', $from_name );

    // Imposta gli indirizzi destinatari
    if ( !is_array( $to ) )
        $to = explode( ',', $to );

    foreach ( (array) $to as $recipient ) {
        try {
            // Suddivide $recipient in nome e indirizzo se nel formato "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;
        }
    }

    // Se non abbiamo un charset dalle intestazioni di input
    if ( !isset( $charset ) )
        $charset = get_bloginfo( 'charset' );

    // Imposta il content-type e il charset

    /**
     * Filtra il charset predefinito di wp_mail().
     *
     * @since 2.3.0
     *
     * @param string $charset Charset predefinito per le email.
     */
    $phpmailer->CharSet = apply_filters( 'wp_mail_charset', $charset );

    // Imposta oggetto e corpo dell'email
    $phpmailer->Subject = $subject;

    if ( is_string($message) ) {
        $phpmailer->Body = $message;

        // Imposta Content-Type e charset
        // Se non abbiamo un content-type dalle intestazioni di input
        if ( !isset( $content_type ) )
            $content_type = 'text/plain';

        /**
         * Filtra il content-type di wp_mail().
         *
         * @since 2.3.0
         *
         * @param string $content_type Content-type predefinito di wp_mail().
         */
        $content_type = apply_filters( 'wp_mail_content_type', $content_type );

        $phpmailer->ContentType = $content_type;

        // Imposta se è testo semplice, in base a $content_type
        if ( 'text/html' == $content_type )
            $phpmailer->IsHTML( true );

        // Per compatibilità all'indietro, le nuove email multipart dovrebbero usare
        // lo stile array per $message. Questo comunque non ha mai funzionato bene
        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);
                }
            }
        }
    }

    // Aggiunge eventuali destinatari CC e BCC
    if ( !empty( $cc ) ) {
        foreach ( (array) $cc as $recipient ) {
            try {
                // Suddivide $recipient in nome e indirizzo se nel formato "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 {
                // Suddivide $recipient in nome e indirizzo se nel formato "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;
            }
        }
    }

    // Imposta l'uso della funzione mail() di PHP
    $phpmailer->IsMail();

    // Imposta intestazioni personalizzate
    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;
            }
        }
    }

    /**
     * Azione eseguita dopo l'inizializzazione di PHPMailer.
     *
     * @since 2.2.0
     *
     * @param PHPMailer &$phpmailer L'istanza PHPMailer, passata per riferimento.
     */
    do_action_ref_array( 'phpmailer_init', array( &$phpmailer ) );

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

Quindi se inserisci questo nel tuo file, ad esempio "wp-content/mu-plugins/functions.php", sovrascriverà la versione di WP. Ha un utilizzo semplice senza dover modificare manualmente le intestazioni, ad esempio:

// Imposta $to con un'email hotmail.com o outlook.com
$to = "LaTuaEmail@hotmail.com";

$subject = 'Test multipart di wp_mail';

$message['text/plain'] = 'Ciao mondo! Questo è testo semplice...';
$message['text/html'] = '<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>

<p>Ciao Mondo! Questo è 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'; } );

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

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

Nota: non ho testato questo codice con email reali...

19 giu 2015 08:34:00
Commenti

Ho aggiunto questo ai plugin indispensabili e ho eseguito il codice di test; ha funzionato. Ho testato le notifiche predefinite del core (come la notifica per nuovo utente, ecc.) e ha funzionato anche quello. Continuerò a fare test questo fine settimana per vedere come i plugin funzioneranno con questo e fondamentalmente se tutto funziona. Controllerò specificamente i dati grezzi del messaggio. Sarà un compito molto dispendioso in termini di tempo, ma state certi che vi farò sapere quando avrò finito. Se c'è uno scenario in cui wp_mail() non funzionerà (quando invece dovrebbe), per favore fatemelo sapere. Grazie per questa risposta.

Christine Cooper Christine Cooper
20 giu 2015 18:43:49

Ottime cose, ho dato un'occhiata all'output e sembra buono - in effetti la patch fa semplicemente sì che wp_mail utilizzi l'elaborazione standard solida di PHPMailer nel caso in cui venga passato un array, mentre in caso contrario torna al materiale approssimativo di WP (per compatibilità all'indietro), quindi dovrebbe andare bene (ovviamente il merito qui va agli autori della patch)... Da ora in poi lo userò (e alla fine lo retrofitterò) - e grazie anche a te per le informazioni sull'uso sia di html che di plain per ridurre le possibilità di essere marchiati come spam...

bonger bonger
21 giu 2015 00:02:10

L'abbiamo testato in tutti gli scenari possibili e funziona alla grande. Manderemo una newsletter domani e vedremo se riceviamo lamentele dagli utenti. Le uniche piccole modifiche che abbiamo dovuto fare sono state sanificare/desanificare l'array quando viene inserito nel db (abbiamo i messaggi in una coda nel db dove un cron li invia in piccoli lotti). Lascerò questa domanda aperta e in attesa fino a quando il bounty scadrà, in modo da poter portare consapevolezza su questo problema. Speriamo che questa patch, o un'alternativa, venga aggiunta al core. O, ancora più importante, perché no. Cosa stanno pensando!

Christine Cooper Christine Cooper
21 giu 2015 18:53:57

Ho notato casualmente che hai eseguito un aggiornamento al ticket trac collegato. Si tratta di un aggiornamento a questo codice? Se è così, potresti cortesemente pubblicare questo aggiornamento modificando anche la tua risposta qui in modo che questa risposta rimanga aggiornata? Grazie mille.

Christine Cooper Christine Cooper
9 set 2015 02:49:04

Ciao, no è stato solo un refresh della patch rispetto all'attuale trunk in modo che si fonda senza conflitti (nella speranza che riceva un po' di attenzione), il codice è esattamente lo stesso...

bonger bonger
9 set 2015 07:05:13

In realtà l'ho appena modificato per renderlo esattamente identico (uno spazio dopo un foreach!)...

bonger bonger
9 set 2015 07:12:48

Eccellente, ottima iniziativa. Per favore continua con questo approccio. Forse alla fine lo aggiungeranno al core nel prossimo futuro... dovrebbero davvero farlo.

Christine Cooper Christine Cooper
9 set 2015 16:42:20

Ho eseguito il tuo codice attraverso PHPCS e ho pubblicato una versione aggiornata: https://gist.github.com/paulschreiber/a1057785f6117f72188f3b619e994702

Paul Schreiber Paul Schreiber
17 gen 2019 16:29:15

Nelle versioni più recenti di WordPress, questo genera un errore. apply_filters( 'wp_mail', ... ) chiama wp_staticize_emoji_for_email, che a sua volta chiama wp_staticize_emoji. Questa si aspetta che $message sia una stringa, mentre qui è un array.

Paul Schreiber Paul Schreiber
17 gen 2019 17:03:46

@PaulSchreiber La tua modifica risolve la parte che dici sia problematica? Grazie.

Christine Cooper Christine Cooper
19 feb 2020 12:54:21

@ChristineCooper Credo di sì. La mia versione non passa più 'message' a compact().

Paul Schreiber Paul Schreiber
19 feb 2020 18:06:30

@PaulSchreiber Potresti gentilmente pubblicare il tuo codice come risposta in modo da avere il codice aggiornato qui su WPSE? Grazie mille.

Christine Cooper Christine Cooper
19 feb 2020 19:27:34

@ChristineCooper fatto.

Paul Schreiber Paul Schreiber
19 feb 2020 23:29:38

Questo ottimo approccio non è ancora stato integrato nel core di WordPress? Non c'è menzione dell'uso degli array o del multipart nella documentazione.

Thomas Ebert Thomas Ebert
12 nov 2020 10:45:50
Mostra i restanti 9 commenti
5
11

TLDR, la soluzione semplice è:

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

In questo modo non è necessario impostare esplicitamente gli header, i limiti dei boundary verranno impostati correttamente automaticamente.

Continua a leggere per una spiegazione dettagliata del perché...

Questo non è realmente un bug di WordPress, ma piuttosto di phpmailer nel non gestire correttamente gli header personalizzati... se osservi 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:
            // Cattura i casi 'plain': e case '':
            $result .= $this->textLine('Content-Type: ' . $this->ContentType . '; charset=' . $this->CharSet);
            $ismultipart = false;
            break;
    }

Puoi vedere che il caso default problematico è quello che produce la riga extra dell'header con il charset e senza boundary. Impostare il content type tramite filter non risolve da solo perché il caso alt qui viene impostato su message_type verificando che AltBody non sia vuoto piuttosto che dal content type.

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 alleghi un file o un'immagine inline, o imposti AltBody, il bug problematico dovrebbe essere bypassato. Significa anche che non c'è bisogno di impostare esplicitamente il content type perché non appena c'è un AltBody viene impostato a multipart/alternative da phpmailer.

Quindi la soluzione semplice è:

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

In questo modo non devi impostare esplicitamente gli header, puoi semplicemente fare:

 $message ='<html>
 <head>
     <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
 </head>
 <body>
     <p>Ciao Mondo! Questo è HTML...</p> 
 </body>
 </html>';

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

Sfortunatamente molte delle funzioni e proprietà nella classe phpmailer sono protected, se non fosse così un'alternativa valida sarebbe semplicemente verificare e sovrascrivere la proprietà MIMEHeaders tramite l'hook phpmailer_init prima dell'invio.

24 mag 2016 17:50:33
Commenti

Non posso ringraziarti abbastanza per questa soluzione, ha funzionato immediatamente, subito pronta all'uso. Se fossi in te terrei solo lo 'snippet della risposta semplice' da solo alla fine del tuo post. :)

scooterlord scooterlord
8 gen 2021 17:14:00

@scooterlord Prego, è stato sicuramente un po' complicato risolvere questo problema! Ho aggiunto una soluzione riassuntiva all'inizio della risposta per chi vuole solo la correzione.

majick majick
12 gen 2021 06:37:12

Non è fantastico quando puoi semplicemente copiare/incollare snippet e funzionano subito senza problemi? :)

scooterlord scooterlord
12 gen 2021 09:56:45

wp_strip_all_tags potrebbe essere migliore di strip_tags. Quest'ultimo renderà il contenuto dei tag <style>, che sono comuni nelle email HTML.

James Beninger James Beninger
14 gen 2022 18:46:32

@JamesBeninger ah, ottima osservazione! Ho modificato la risposta per cambiare con questa.

majick majick
15 gen 2022 07:32:41
0

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

Il corpo alternativo del testo viene riutilizzato per diverse email consecutive inviate, a meno che non venga cancellato manualmente! WordPress non lo cancella in wp_mail() perché non si aspetta che questa proprietà venga utilizzata.

Ciò può portare a situazioni in cui i destinatari ricevono email non destinate a loro. Fortunatamente, la maggior parte delle persone che utilizzano client di posta con supporto HTML non vedrà la versione testuale, ma rimane comunque un problema di sicurezza.

Fortunatamente, c'è una soluzione semplice. Questo include anche la parte per sostituire l'altbody; nota che avrai 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() ) {
    // La proprietà AltBody è impostata, quindi WordPress deve aver già usato questo
    // oggetto $phpmailer per inviare la mail, quindi cancelliamo
    // la proprietà AltBody
    $phpmailer->AltBody = '';
  }

  // Restituiamo gli attributi non modificati
  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 un gist per un plugin WP che ho modificato per risolvere questo problema: https://gist.github.com/youri--/c4618740b7c50c549314eaebc9f78661

Sfortunatamente non posso commentare le altre soluzioni che utilizzano l'hook menzionato per avvisarli di questo problema, poiché non ho abbastanza reputazione per commentare.

16 ott 2018 14:33:36
0

Ho appena rilasciato un plugin per permettere agli utenti di utilizzare template HTML su WordPress e sto attualmente lavorando alla versione di sviluppo per aggiungere un semplice fallback testuale. Ho implementato il seguente codice e nei miei test vedo solo un boundary aggiunto e le email arrivano correttamente su Hotmail.

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

/**
* Modifica il body del php mailer con l'email finale
*
* @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 );
}

In pratica, ciò che faccio qui è modificare l'oggetto phpmailer, caricare il messaggio all'interno di un template HTML e impostarlo nella proprietà Body. Inoltre, prendo il messaggio originale e lo imposto nella proprietà AltBody.

10 lug 2015 21:43:30
0

La mia semplice soluzione è 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/frugan-it/6c4d22cd856456480bd77b988b5c9e80 trovi anche un gist sull'argomento.

11 ott 2016 13:50:18
0

Questo potrebbe non essere una risposta esatta al post iniziale qui, ma è un'alternativa ad alcune delle soluzioni fornite riguardo all'impostazione di un corpo alternativo (alt body).

In sostanza, avevo bisogno (e volevo) impostare un corpo alternativo distinto (cioè testo semplice) in aggiunta alla parte HTML invece di affidarmi a qualche conversione/striptags e simili.

Quindi ho ideato questa soluzione che sembra funzionare bene:

/* impostazione delle parti del messaggio per wp_mail() */
$markup = array();
$markup['html'] = '<html>qualche html</html>';
$markup['plaintext'] = 'qualche testo semplice';
/* messaggio che stiamo inviando */    
$message = maybe_serialize($markup);


/* impostazione distinta del corpo alternativo */
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'];
        }
    }   
}
25 gen 2017 16:56:04
1

Questa versione di wp_mail() è basata sul codice di @bonger. Include queste modifiche:

  • Correzioni dello stile del codice (tramite PHPCS)
  • Gestione dei casi in cui $message è un array o una stringa (garantisce compatibilità con WP 5.x)
  • Genera un'eccezione invece di restituire false
  • Sintassi abbreviata degli array
<?php
/**
 * Adattato da https://wordpress.stackexchange.com/a/191974/8591
 *
 * Invia email, simile alla funzione mail di PHP
 *
 * Un valore di ritorno true non significa automaticamente che l'utente abbia ricevuto
 * l'email con successo. Significa solo che il metodo utilizzato è stato in grado di
 * processare la richiesta senza errori.
 *
 * Utilizzando gli hook 'wp_mail_from' e 'wp_mail_from_name' è possibile creare
 * un indirizzo mittente come 'Nome <email@indirizzo.com>' quando entrambi sono impostati.
 * Se è impostato solo 'wp_mail_from', verrà utilizzato solo l'indirizzo email senza nome.
 *
 * Il tipo di contenuto predefinito è 'text/plain' che non consente l'uso di HTML.
 * Tuttavia, è possibile impostare il tipo di contenuto dell'email utilizzando
 * il filtro 'wp_mail_content_type'.
 *
 * Se $message è un array, la chiave di ogni elemento viene utilizzata per aggiungere
 * un allegato con il valore usato come corpo. L'elemento 'text/plain' viene usato
 * come versione testuale del corpo, mentre 'text/html' come versione HTML.
 * Tutti gli altri tipi vengono aggiunti come allegati.
 *
 * Il set di caratteri predefinito si basa su quello utilizzato nel blog.
 * Il set di caratteri può essere impostato usando il filtro 'wp_mail_charset'.
 *
 * @since 1.2.1
 *
 * @uses PHPMailer
 *
 * @param string|array $to Array o lista di indirizzi email separati da virgola a cui inviare il messaggio.
 * @param string $subject Oggetto dell'email
 * @param string|array $message Contenuto del messaggio
 * @param string|array $headers Opzionale. Intestazioni aggiuntive.
 * @param string|array $attachments Opzionale. File da allegare.
 * @return bool Indica se il contenuto dell'email è stato inviato con successo.
 */
public static function wp_mail( $to, $subject, $message, $headers = '', $attachments = [] ) {
    // Compatta l'input, applica i filtri e li estrae nuovamente

    /**
     * Filtra gli argomenti di wp_mail().
     *
     * @since 2.2.0
     *
     * @param array $args Un array compatto degli argomenti di wp_mail(), inclusi i valori
     *                    "to", subject, message, headers e attachments.
     */
    $atts = apply_filters( 'wp_mail', compact( 'to', 'subject', 'headers', 'attachments' ) );

    // Poiché $message è un array e wp_staticize_emoji_for_email() si aspetta stringhe, iteriamo un elemento alla volta
    if ( ! is_array( $message ) ) {
        $message = [ $message ];
    }
    foreach ( $message as $message_part ) {
        $message_part = apply_filters( 'wp_mail', $message_part );
    }
    $atts['message'] = $message;

    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;

    // (Ri)crea l'oggetto, se è mancante
    if ( ! ( $phpmailer instanceof PHPMailer ) ) {
        require_once ABSPATH . WPINC . '/class-phpmailer.php';
        require_once ABSPATH . WPINC . '/class-smtp.php';
        $phpmailer = new PHPMailer( true ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
    }

    // Intestazioni
    if ( empty( $headers ) ) {
        $headers = [];
    } else {
        if ( ! is_array( $headers ) ) {
            // Suddivide le intestazioni, così questa funzione può accettare sia
            // stringhe di intestazioni che array di intestazioni
            $tempheaders = explode( "\n", str_replace( "\r\n", "\n", $headers ) );
        } else {
            $tempheaders = $headers;
        }
        $headers = [];
        $cc      = [];
        $bcc     = [];

        // Se ci sono effettivamente contenuti
        if ( ! empty( $tempheaders ) ) {
            // Itera attraverso le intestazioni grezze
            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( [ "'", '"' ], '', $parts[1] ) );
                    }
                    continue;
                }
                // Suddivide nome e contenuto
                list( $name, $content ) = explode( ':', trim( $header ), 2 );

                // Pulizia
                $name    = trim( $name );
                $content = trim( $content );

                switch ( strtolower( $name ) ) {
                    // Principalmente per legacy - processa un'intestazione From: se presente
                    case 'from':
                        $bracket_pos = strpos( $content, '<' );
                        if ( false !== $bracket_pos ) {
                            // Il testo prima dell'email tra parentesi è il nome "From"
                            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 );

                            // Evita di impostare un $from_email vuoto
                        } elseif ( '' !== trim( $content ) ) {
                            $from_email = trim( $content );
                        }
                        break;
                    case 'content-type':
                        if ( is_array( $message ) ) {
                            // Email multipart, ignora l'intestazione content-type
                            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( [ 'charset=', '"' ], '', $charset_content ) );
                            } elseif ( false !== stripos( $charset_content, 'boundary=' ) ) {
                                $boundary = trim( str_replace( [ 'BOUNDARY=', 'boundary=', '"' ], '', $charset_content ) );
                                $charset  = '';
                            }

                            // Evita di impostare un $content_type vuoto
                        } 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:
                        // Aggiunge all'array principale delle intestazioni
                        $headers[ trim( $name ) ] = trim( $content );
                        break;
                }
            }
        }
    }

    // Svuota i valori che potrebbero essere impostati
    $phpmailer->ClearAllRecipients();
    $phpmailer->ClearAttachments();
    $phpmailer->ClearCustomHeaders();
    $phpmailer->ClearReplyTos();

    $phpmailer->Body    = ''; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
    $phpmailer->AltBody = ''; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase

    // Email e nome del mittente
    // Se non abbiamo un nome dalle intestazioni di input
    if ( ! isset( $from_name ) ) {
        $from_name = 'WordPress';
    }

    /* Se non abbiamo un'email dalle intestazioni di input, usiamo wordpress@$sitename
     * Alcuni host bloccheranno le email in uscita da questo indirizzo se non esiste,
     * ma non c'è un'alternativa semplice. Usare admin_email potrebbe sembrare un'opzione,
     * ma alcuni host potrebbero rifiutare di inoltrare email da un dominio sconosciuto.
     * Vedi https://core.trac.wordpress.org/ticket/5007.
     */

    if ( ! isset( $from_email ) ) {
        // Ottiene il dominio del sito e rimuove www.
        $sitename = isset( $_SERVER['SERVER_NAME'] ) ? strtolower( sanitize_text_field( wp_unslash( $_SERVER['SERVER_NAME'] ) ) ) : ''; // phpcs:ignore WordPress.VIP.SuperGlobalInputUsage.AccessDetected
        if ( substr( $sitename, 0, 4 ) === 'www.' ) {
            $sitename = substr( $sitename, 4 );
        }

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

    /**
     * Filtra l'indirizzo email del mittente.
     *
     * @since 2.2.0
     *
     * @param string $from_email Indirizzo email del mittente.
     */
    $phpmailer->From = apply_filters( 'wp_mail_from', $from_email ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase

    /**
     * Filtra il nome associato all'indirizzo email "from".
     *
     * @since 2.3.0
     *
     * @param string $from_name Nome associato all'indirizzo email "from".
     */
    $phpmailer->FromName = apply_filters( 'wp_mail_from_name', $from_name ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase

    // Imposta gli indirizzi destinatari
    if ( ! is_array( $to ) ) {
        $to = explode( ',', $to );
    }

    foreach ( (array) $to as $recipient ) {
        try {
            // Suddivide $recipient in nome e indirizzo se nel formato "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;
        }
    }

    // Se non abbiamo un set di caratteri dalle intestazioni di input
    if ( ! isset( $charset ) ) {
        $charset = get_bloginfo( 'charset' );
    }

    // Imposta il tipo di contenuto e il set di caratteri

    /**
     * Filtra il set di caratteri predefinito di wp_mail().
     *
     * @since 2.3.0
     *
     * @param string $charset Set di caratteri predefinito per le email.
     */
    $phpmailer->CharSet = apply_filters( 'wp_mail_charset', $charset ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase

    // Imposta oggetto e corpo dell'email
    $phpmailer->Subject = $subject; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase

    if ( is_string( $message ) ) {
        $phpmailer->Body = $message; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase

        // Imposta Content-Type e set di caratteri
        // Se non abbiamo un content-type dalle intestazioni di input
        if ( ! isset( $content_type ) ) {
            $content_type = 'text/plain';
        }

        /**
         * Filtra il tipo di contenuto di wp_mail().
         *
         * @since 2.3.0
         *
         * @param string $content_type Tipo di contenuto predefinito di wp_mail().
         */
        $content_type = apply_filters( 'wp_mail_content_type', $content_type );

        $phpmailer->ContentType = $content_type; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase

        // Imposta se è testo semplice, in base a $content_type
        if ( 'text/html' === $content_type ) {
            $phpmailer->IsHTML( true );
        }

        // Per compatibilità con le versioni precedenti, le nuove email multipart dovrebbero usare
        // lo stile array per $message. Comunque non ha mai funzionato bene
        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 ( 'text/html' === $type ) {
                    $phpmailer->Body = $body; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
                } elseif ( 'text/plain' === $type ) {
                    $phpmailer->AltBody = $body; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
                } else {
                    $phpmailer->AddAttachment( $body, '', 'base64', $type );
                }
            }
        }
    }

    // Aggiungi eventuali destinatari CC e BCC
    if ( ! empty( $cc ) ) {
        foreach ( (array) $cc as $recipient ) {
            try {
                // Suddivide $recipient in nome e indirizzo se nel formato "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 {
                // Suddivide $recipient in nome e indirizzo se nel formato "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;
            }
        }
    }

    // Imposta l'uso della funzione mail() di PHP
    $phpmailer->IsMail();

    // Imposta intestazioni personalizzate
    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;
            }
        }
    }

    /**
     * Azione eseguita dopo l'inizializzazione di PHPMailer.
     *
     * @since 2.2.0
     *
     * @param PHPMailer &$phpmailer L'istanza PHPMailer, passata per riferimento.
     */
    do_action_ref_array( 'phpmailer_init', [ &$phpmailer ] );

    // Invia!
    try {
        return $phpmailer->Send();
    } catch ( phpmailerException $e ) {
        return new WP_Error( 'email-error', $e->getMessage() );
    }
}
19 feb 2020 23:27:45
Commenti

Ciao Paul. La sintassi abbreviata degli array esiste già da molto prima di PHP 7+ ;)

kaiser kaiser
19 feb 2020 23:37:39
0

Se non vuoi creare alcun conflitto di codice nel core di WordPress, penso che la soluzione alternativa o più semplice sia aggiungere un'azione a phpmailer_init che verrà eseguita prima dell'effettivo invio dell'email nella funzione wp_mail(). Per semplificare la mia spiegazione, guarda l'esempio di codice qui sotto:

<?php 

$to = '';
$subject = '';
$from = '';
$body = 'Il contenuto html del testo, <html>...';

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

add_action( 'phpmailer_init', function ( $phpmailer ) {
    $phpmailer->AltBody = 'Il contenuto testuale semplice del tuo originale contenuto html.';
} );

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

Se aggiungi il contenuto nella proprietà AltBody della classe PHPMailer, il tipo di contenuto predefinito verrà automaticamente impostato su multipart/alternative.

18 set 2018 08:24:56
0

Ho esaminato attentamente l'implementazione di wp_mail($to, $subject, $message, $headers, $attachments) in pluggable.php e ho trovato una soluzione che non richiede modifiche al core.

La funzione wp_mail() controlla l'argomento $headers per un insieme specifico di tipi di intestazioni standard, ovvero from, content-type, cc, bcc e reply-to.

Tutti gli altri tipi sono considerati intestazioni personalizzate e vengono elaborati separatamente. Ma ecco il punto: quando viene definita un'intestazione personalizzata, come nel tuo caso in cui hai impostato l'intestazione MIME-Version, viene eseguito il seguente blocco di codice (all'interno di wp_mail()):

// Imposta intestazioni personalizzate
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 ) );
    }
}

Quell'istruzione if annidata nello snippet sopra è il colpevole. Fondamentalmente, viene aggiunta un'altra intestazione Content-Type come intestazione personalizzata nelle seguenti condizioni:

  1. È stata definita un'intestazione personalizzata (hai definito MIME-Version nello scenario descritto nel tuo post).
  2. Il tipo MIME dell'intestazione Content-Type contiene la stringa multipart.
  3. È stato impostato un boundary per le parti multiple.

La soluzione più rapida nel tuo caso è rimuovere l'intestazione MIME-Version. La maggior parte degli user agent la aggiunge automaticamente, quindi rimuoverla non dovrebbe essere un problema.

Ma cosa fare se vuoi aggiungere intestazioni personalizzate senza generare un'intestazione Content-Type duplicata?

SOLUZIONE: NON impostare esplicitamente l'intestazione Content-Type nell'array $headers quando aggiungi intestazioni personalizzate, fai invece quanto segue:

$headers = 'boundary="----=_Part_18243133_1346573420.1408991447668"\r\n';
$headers .= "MIME-Version: 1.0\r\n";
$headers .= "From: Foo <foo@bar.com>\r\n";

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

function set_charset( $char_set ) {
    return 'utf-8';
}

add_filter( 'wp_mail_content_type', 'set_content_type' );
add_filter( 'wp_mail_charset', 'set_charset' );

La prima riga dello snippet sopra potrebbe sembrare strana, ma la funzione wp_mail() imposterà internamente la sua variabile $boundary purché una definizione di boundary appaia su una riga da sola senza essere preceduta da Content-Type:. Quindi puoi utilizzare i filtri per impostare rispettivamente il content-type e il charset. In questo modo soddisfi le condizioni per eseguire il blocco di codice per l'impostazione delle intestazioni personalizzate senza aggiungere esplicitamente Content-Type: [mime-type]; [boundary];.

Non è necessario modificare l'implementazione core di wp_mail(), per quanto possa essere difettosa.

20 dic 2020 00:41:41