L'envoi d'e-mails en plusieurs parties (texte / html) via wp_mail () entraînera probablement l'interdiction de votre domaine.

37

Sommaire

En raison d'un bogue dans WP de base, l' envoi multipart e - mails (html / texte) avec wp_mail () (pour réduire la chance d'e - mails qui se terminent dans les dossiers de spam) sera ironiquement résultat avec votre domaine bloqué par Hotmail (et d' autres e - mails Microsoft).

C’est un problème complexe que je vais tenter d’exploiter de manière très détaillée pour tenter d’aider une personne à trouver une solution viable pouvant éventuellement être mise en œuvre de manière centralisée.

Ce sera une lecture enrichissante. Commençons...

L'insecte

Le conseil le plus courant pour éviter que vos e-mails de bulletin d'information finissent dans des dossiers de courrier indésirable est d'envoyer des messages en plusieurs parties.

Multi-part (mime) fait référence à l'envoi simultané d'une partie HTML et d'une partie TEXT d'un message électronique. Lorsqu'un client reçoit un message en plusieurs parties, il accepte la version HTML s'il peut restituer le code HTML. Sinon, il présente la version en texte brut.

Ceci est prouvé pour fonctionner. Lors de l'envoi à gmail, tous nos courriels se retrouvaient dans des dossiers de courrier indésirable jusqu'à ce que nous changions les messages en plusieurs parties lorsqu'ils arrivaient dans la boîte de réception principale. Super truc.

Désormais, lors de l'envoi de messages en plusieurs parties via wp_mail (), le type de contenu (multipart / *) est généré deux fois, une fois avec une limite (si défini de manière personnalisée) et une fois sans. Ce comportement a pour résultat que l'e-mail est affiché en tant que message brut et non en plusieurs parties sur certains courriels, y compris tous les Microsoft (Hotmail, Outlook, etc.).

Microsoft signalera ce message comme courrier indésirable et les quelques messages reçus seront signalés manuellement par le destinataire. Malheureusement , les adresses électroniques de Microsoft sont largement utilisées. 40% de nos abonnés l'utilisent.

Ceci est confirmé par Microsoft via un échange de courrier électronique que nous avons eu récemment.

Le signalement des messages entraînera le blocage complet du domaine . Cela signifie que le message ne sera pas envoyé dans le dossier spam, ils ne seront même pas remis au destinataire.

Nous avons eu notre domaine principal bloqué 3 fois jusqu'à présent.

Comme il s’agit d’un bogue dans le noyau de WP, chaque domaine qui envoie des messages en plusieurs parties est bloqué. Le problème est que la plupart des webmasters ne savent pas pourquoi. Je l'ai confirmé lors de mes recherches et lorsque j'ai vu d'autres utilisateurs en discuter sur des forums, etc. Cela nécessite de fouiller dans le code brut et de bien connaître le fonctionnement de ce type de messages électroniques, que nous passons ensuite à ...

Décomposons en code

Créez un compte hotmail / outlook. Ensuite, exécutez le code suivant:

// Set $to to an hotmail.com or outlook.com email
$to = "[email protected]";

$subject = 'wp_mail testing multipart';

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

Hello world! This is plain text...


------=_Part_18243133_1346573420.1408991447668
Content-Type: text/html; charset=UTF-8

<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>

<p>Hello World! This is HTML...</p> 

</body>
</html>


------=_Part_18243133_1346573420.1408991447668--';

$headers = "MIME-Version: 1.0\r\n";
$headers .= "From: Foo <[email protected]>\r\n";
$headers .= 'Content-Type: multipart/alternative;boundary="----=_Part_18243133_1346573420.1408991447668"';


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

Et si vous souhaitez modifier le type de contenu par défaut , utilisez:

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

Cela enverra un message en plusieurs parties.

Ainsi, si vous vérifiez la source brute complète du message, vous remarquerez que le type de contenu est ajouté deux fois, une fois sans limite:

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

C'est le problème.

La source du problème réside dans pluggable.php- si nous regardons quelque part ici:

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

Solutions potentielles

Vous vous demandez donc pourquoi vous n'avez pas signalé cela à Trac ? J'ai déjà . À ma grande surprise, un billet différent a été créé il y a 5 ans, soulignant le même problème.

Regardons les choses en face, cela fait une demi-décennie. Dans les années Internet, cela correspond plus à 30 ans. Le problème a clairement été abandonné et ne sera fondamentalement jamais résolu (... sauf si nous le résolvons ici).

J'ai trouvé ici un bon fil offrant une solution, mais si sa solution fonctionne, elle casse les courriers électroniques qui n'ont pas de $headersconfiguration personnalisée .

C'est là que nous nous écrasons à chaque fois. Soit la version en plusieurs parties fonctionne bien, et les $headersmessages normaux non définis ne le sont pas, soit un verset.

La solution que nous avons trouvée était:

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

}

Oui, je sais, éditer des fichiers core est tabou, asseyez-vous… c’était un correctif désespéré et une mauvaise tentative de fournir un correctif pour le core.

Le problème avec notre solution est que les emails par défaut tels que les nouvelles inscriptions, les commentaires, la réinitialisation du mot de passe, etc. seront livrés sous forme de messages vierges. Nous avons donc un script de travail wp_mail () qui envoie des messages en plusieurs parties, mais rien d’autre.

Que faire

Le but ici est de trouver un moyen d’envoyer des messages normaux (texte brut) et multipart à l’aide de la fonction principale wp_mail () (et non d’une fonction sendmail personnalisée).

Lorsque vous tentez de résoudre ce problème, le principal problème que vous rencontrerez est le temps que vous passerez à envoyer des messages factices, à vérifier s'ils sont reçus et à ouvrir une boîte d'aspirine et à maudire Microsoft car vous êtes habitué à leurs messages. Problèmes IE alors que le gremlin ici est malheureusement WordPress.

Mise à jour

La solution publiée par @bonger permet $messaged’être un tableau contenant des substituts avec une clé de type contenu. J'ai confirmé que cela fonctionne dans tous les scénarios.

Nous laisserons cette question ouverte jusqu'à épuisement des primes pour sensibiliser le public au problème, peut - être à un niveau où elles seront résolues de manière fondamentale. N'hésitez pas à poster une solution alternative où $messagepeut être une chaîne.

Christine Cooper
la source
1
Étant donné que la wp_mail()fonction est connectable, votre remplaçant n'est pas défini comme un plug-in à utiliser absolument (dans wp-content / mu-plugins), ce n'est pas une bonne solution pour vous (et tous les autres, le correctif principal défaillant)? Dans quel cas le déplacement de la vérification multipartie / limite après réglage $phpmailer->ContentType = $content_type;(plutôt que elsing) ne fonctionnerait-il pas?
Bonger
@bonger Pouvez-vous s'il vous plaît rédiger une réponse détaillant votre solution?
Christine Cooper
1
Vous n'avez pas besoin d'éditer le noyau, car il wp_mailest connectable . Copiez la fonction originale dans un plugin, modifiez-la comme vous le souhaitez et activez le plugin. WordPress utilisera votre fonction modifiée au lieu de celle d'origine, sans avoir besoin de modifier le coeur.
gmazzap
@ChristineCooper J'hésite à faire cela, comme vous dites que les tests sont une douleur royale, mais en regardant le patch core.trac.wordpress.org/ticket/15448 suggéré dans trac par @ rmccue / @ MattyRob, qui a l'air d'être un très bon moyen de allez donc je
posterai
2
@ChristineCooper si vous vous connectez simplement à phpmailer et définissez le corps du texte dans $ phpmailer-> AltBody, la même erreur se produit-elle?
chifliiiii

Réponses:

15

La version suivante de wp_mail()contient le correctif appliqué de @ rmccue / @ MattyRob dans le ticket https://core.trac.wordpress.org/ticket/15448 , actualisé pour la version 4.2.2, ce qui permet$message de constituer un tableau contenant le type de contenu. remplaçants à clé:

/**
 * Send mail, similar to PHP's mail
 *
 * A true return value does not automatically mean that the user received the
 * email successfully. It just only means that the method used was able to
 * process the request without any errors.
 *
 * Using the two 'wp_mail_from' and 'wp_mail_from_name' hooks allow from
 * creating a from address like 'Name <[email protected]>' when both are set. If
 * just 'wp_mail_from' is set, then just the email address will be used with no
 * name.
 *
 * The default content type is 'text/plain' which does not allow using HTML.
 * However, you can set the content type of the email by using the
 * 'wp_mail_content_type' filter.
 *
 * If $message is an array, the key of each is used to add as an attachment
 * with the value used as the body. The 'text/plain' element is used as the
 * text version of the body, with the 'text/html' element used as the HTML
 * version of the body. All other types are added as attachments.
 *
 * The default charset is based on the charset used on the blog. The charset can
 * be set using the 'wp_mail_charset' filter.
 *
 * @since 1.2.1
 *
 * @uses PHPMailer
 *
 * @param string|array $to Array or comma-separated list of email addresses to send message.
 * @param string $subject Email subject
 * @param string|array $message Message contents
 * @param string|array $headers Optional. Additional headers.
 * @param string|array $attachments Optional. Files to attach.
 * @return bool Whether the email contents were sent successfully.
 */
function wp_mail( $to, $subject, $message, $headers = '', $attachments = array() ) {
    // Compact the input, apply the filters, and extract them back out

    /**
     * Filter the wp_mail() arguments.
     *
     * @since 2.2.0
     *
     * @param array $args A compacted array of wp_mail() arguments, including the "to" email,
     *                    subject, message, headers, and attachments values.
     */
    $atts = apply_filters( 'wp_mail', compact( 'to', 'subject', 'message', 'headers', 'attachments' ) );

    if ( isset( $atts['to'] ) ) {
        $to = $atts['to'];
    }

    if ( isset( $atts['subject'] ) ) {
        $subject = $atts['subject'];
    }

    if ( isset( $atts['message'] ) ) {
        $message = $atts['message'];
    }

    if ( isset( $atts['headers'] ) ) {
        $headers = $atts['headers'];
    }

    if ( isset( $atts['attachments'] ) ) {
        $attachments = $atts['attachments'];
    }

    if ( ! is_array( $attachments ) ) {
        $attachments = explode( "\n", str_replace( "\r\n", "\n", $attachments ) );
    }
    global $phpmailer;

    // (Re)create it, if it's gone missing
    if ( ! ( $phpmailer instanceof PHPMailer ) ) {
        require_once ABSPATH . WPINC . '/class-phpmailer.php';
        require_once ABSPATH . WPINC . '/class-smtp.php';
        $phpmailer = new PHPMailer( true );
    }

    // Headers
    if ( empty( $headers ) ) {
        $headers = array();
    } else {
        if ( !is_array( $headers ) ) {
            // Explode the headers out, so this function can take both
            // string headers and an array of headers.
            $tempheaders = explode( "\n", str_replace( "\r\n", "\n", $headers ) );
        } else {
            $tempheaders = $headers;
        }
        $headers = array();
        $cc = array();
        $bcc = array();

        // If it's actually got contents
        if ( !empty( $tempheaders ) ) {
            // Iterate through the raw headers
            foreach ( (array) $tempheaders as $header ) {
                if ( strpos($header, ':') === false ) {
                    if ( false !== stripos( $header, 'boundary=' ) ) {
                        $parts = preg_split('/boundary=/i', trim( $header ) );
                        $boundary = trim( str_replace( array( "'", '"' ), '', $parts[1] ) );
                    }
                    continue;
                }
                // Explode them out
                list( $name, $content ) = explode( ':', trim( $header ), 2 );

                // Cleanup crew
                $name    = trim( $name    );
                $content = trim( $content );

                switch ( strtolower( $name ) ) {
                    // Mainly for legacy -- process a From: header if it's there
                    case 'from':
                        $bracket_pos = strpos( $content, '<' );
                        if ( $bracket_pos !== false ) {
                            // Text before the bracketed email is the "From" name.
                            if ( $bracket_pos > 0 ) {
                                $from_name = substr( $content, 0, $bracket_pos - 1 );
                                $from_name = str_replace( '"', '', $from_name );
                                $from_name = trim( $from_name );
                            }

                            $from_email = substr( $content, $bracket_pos + 1 );
                            $from_email = str_replace( '>', '', $from_email );
                            $from_email = trim( $from_email );

                        // Avoid setting an empty $from_email.
                        } elseif ( '' !== trim( $content ) ) {
                            $from_email = trim( $content );
                        }
                        break;
                    case 'content-type':
                        if ( is_array($message) ) {
                            // Multipart email, ignore the content-type header
                            break;
                        }
                        if ( strpos( $content, ';' ) !== false ) {
                            list( $type, $charset_content ) = explode( ';', $content );
                            $content_type = trim( $type );
                            if ( false !== stripos( $charset_content, 'charset=' ) ) {
                                $charset = trim( str_replace( array( 'charset=', '"' ), '', $charset_content ) );
                            } elseif ( false !== stripos( $charset_content, 'boundary=' ) ) {
                                $boundary = trim( str_replace( array( 'BOUNDARY=', 'boundary=', '"' ), '', $charset_content ) );
                                $charset = '';
                            }

                        // Avoid setting an empty $content_type.
                        } elseif ( '' !== trim( $content ) ) {
                            $content_type = trim( $content );
                        }
                        break;
                    case 'cc':
                        $cc = array_merge( (array) $cc, explode( ',', $content ) );
                        break;
                    case 'bcc':
                        $bcc = array_merge( (array) $bcc, explode( ',', $content ) );
                        break;
                    default:
                        // Add it to our grand headers array
                        $headers[trim( $name )] = trim( $content );
                        break;
                }
            }
        }
    }

    // Empty out the values that may be set
    $phpmailer->ClearAllRecipients();
    $phpmailer->ClearAttachments();
    $phpmailer->ClearCustomHeaders();
    $phpmailer->ClearReplyTos();

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

    // From email and name
    // If we don't have a name from the input headers
    if ( !isset( $from_name ) )
        $from_name = 'WordPress';

    /* If we don't have an email from the input headers default to wordpress@$sitename
     * Some hosts will block outgoing mail from this address if it doesn't exist but
     * there's no easy alternative. Defaulting to admin_email might appear to be another
     * option but some hosts may refuse to relay mail from an unknown domain. See
     * https://core.trac.wordpress.org/ticket/5007.
     */

    if ( !isset( $from_email ) ) {
        // Get the site domain and get rid of www.
        $sitename = strtolower( $_SERVER['SERVER_NAME'] );
        if ( substr( $sitename, 0, 4 ) == 'www.' ) {
            $sitename = substr( $sitename, 4 );
        }

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

    /**
     * Filter the email address to send from.
     *
     * @since 2.2.0
     *
     * @param string $from_email Email address to send from.
     */
    $phpmailer->From = apply_filters( 'wp_mail_from', $from_email );

    /**
     * Filter the name to associate with the "from" email address.
     *
     * @since 2.3.0
     *
     * @param string $from_name Name associated with the "from" email address.
     */
    $phpmailer->FromName = apply_filters( 'wp_mail_from_name', $from_name );

    // Set destination addresses
    if ( !is_array( $to ) )
        $to = explode( ',', $to );

    foreach ( (array) $to as $recipient ) {
        try {
            // Break $recipient into name and address parts if in the format "Foo <[email protected]>"
            $recipient_name = '';
            if( preg_match( '/(.*)<(.+)>/', $recipient, $matches ) ) {
                if ( count( $matches ) == 3 ) {
                    $recipient_name = $matches[1];
                    $recipient = $matches[2];
                }
            }
            $phpmailer->AddAddress( $recipient, $recipient_name);
        } catch ( phpmailerException $e ) {
            continue;
        }
    }

    // If we don't have a charset from the input headers
    if ( !isset( $charset ) )
        $charset = get_bloginfo( 'charset' );

    // Set the content-type and charset

    /**
     * Filter the default wp_mail() charset.
     *
     * @since 2.3.0
     *
     * @param string $charset Default email charset.
     */
    $phpmailer->CharSet = apply_filters( 'wp_mail_charset', $charset );

    // Set mail's subject and body
    $phpmailer->Subject = $subject;

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

        // Set Content-Type and charset
        // If we don't have a content-type from the input headers
        if ( !isset( $content_type ) )
            $content_type = 'text/plain';

        /**
         * Filter the wp_mail() content type.
         *
         * @since 2.3.0
         *
         * @param string $content_type Default wp_mail() content type.
         */
        $content_type = apply_filters( 'wp_mail_content_type', $content_type );

        $phpmailer->ContentType = $content_type;

        // Set whether it's plaintext, depending on $content_type
        if ( 'text/html' == $content_type )
            $phpmailer->IsHTML( true );

        // For backwards compatibility, new multipart emails should use
        // the array style $message. This never really worked well anyway
        if ( false !== stripos( $content_type, 'multipart' ) && ! empty($boundary) )
            $phpmailer->AddCustomHeader( sprintf( "Content-Type: %s;\n\t boundary=\"%s\"", $content_type, $boundary ) );
    }
    elseif ( is_array($message) ) {
        foreach ($message as $type => $bodies) {
            foreach ((array) $bodies as $body) {
                if ($type === 'text/html') {
                    $phpmailer->Body = $body;
                }
                elseif ($type === 'text/plain') {
                    $phpmailer->AltBody = $body;
                }
                else {
                    $phpmailer->AddAttachment($body, '', 'base64', $type);
                }
            }
        }
    }

    // Add any CC and BCC recipients
    if ( !empty( $cc ) ) {
        foreach ( (array) $cc as $recipient ) {
            try {
                // Break $recipient into name and address parts if in the format "Foo <[email protected]>"
                $recipient_name = '';
                if( preg_match( '/(.*)<(.+)>/', $recipient, $matches ) ) {
                    if ( count( $matches ) == 3 ) {
                        $recipient_name = $matches[1];
                        $recipient = $matches[2];
                    }
                }
                $phpmailer->AddCc( $recipient, $recipient_name );
            } catch ( phpmailerException $e ) {
                continue;
            }
        }
    }

    if ( !empty( $bcc ) ) {
        foreach ( (array) $bcc as $recipient) {
            try {
                // Break $recipient into name and address parts if in the format "Foo <[email protected]>"
                $recipient_name = '';
                if( preg_match( '/(.*)<(.+)>/', $recipient, $matches ) ) {
                    if ( count( $matches ) == 3 ) {
                        $recipient_name = $matches[1];
                        $recipient = $matches[2];
                    }
                }
                $phpmailer->AddBcc( $recipient, $recipient_name );
            } catch ( phpmailerException $e ) {
                continue;
            }
        }
    }

    // Set to use PHP's mail()
    $phpmailer->IsMail();

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

    if ( !empty( $attachments ) ) {
        foreach ( $attachments as $attachment ) {
            try {
                $phpmailer->AddAttachment($attachment);
            } catch ( phpmailerException $e ) {
                continue;
            }
        }
    }

    /**
     * Fires after PHPMailer is initialized.
     *
     * @since 2.2.0
     *
     * @param PHPMailer &$phpmailer The PHPMailer instance, passed by reference.
     */
    do_action_ref_array( 'phpmailer_init', array( &$phpmailer ) );

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

Donc, si vous le mettez dans votre fichier "wp-content / mu-plugins / functions.php", par exemple, il remplacera la version de WP. Il a un bon usage sans déconner avec les en-têtes, par exemple:

// Set $to to an hotmail.com or outlook.com email
$to = "[email protected]";

$subject = 'wp_mail testing multipart';

$message['text/plain'] = 'Hello world! This is plain text...';
$message['text/html'] = '<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>

<p>Hello World! This is HTML...</p> 

</body>
</html>';

add_filter( 'wp_mail_from', $from_func = function ( $from_email ) { return '[email protected]'; } );
add_filter( 'wp_mail_from_name', $from_name_func = function ( $from_name ) { return 'Foo'; } );

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

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

Veuillez noter que je n'ai pas testé cela avec les emails actuels ...

bonger
la source
J'ai ajouté ceci à doit avoir des plugins et couru le code de test; ça a marché. J'ai testé les notifications de base par défaut (notification des nouveaux utilisateurs, etc.) et cela a également fonctionné. Je continuerai à effectuer des tests ce week-end pour voir comment les plugins fonctionneront et si tout fonctionne. Je vais spécifiquement regarder à travers les données brutes du message. Ce sera une tâche très fastidieuse mais soyez rassuré, je vous ferai rapport une fois terminé. S'il y a un scénario où wp_mail () ne fonctionnera pas (alors qu'il devrait le faire autrement), veuillez me le faire savoir. Merci pour cette réponse.
Christine Cooper
Bonne chose, j'ai la bonne idée de la sortie et ça a l'air bien - en fait, le correctif oblige wp_mail à utiliser le traitement standard des solides de PHPMailer dans le cas du passage d'un tableau, et sinon, passe par défaut à la chose louche (pour des raisons de compatibilité ascendante) donc ça devrait être bon (évidemment, les félicitations vont ici aux auteurs du correctif) ... Je vais l'utiliser à partir de maintenant (et l'adapter ultérieurement) - et merci pour l'info concernant l'utilisation de html / plain to réduire les chances d'être taré comme spam ...
bonger
1
Nous l'avons testé dans tous les scénarios possibles et cela fonctionne très bien. Nous lancerons une lettre d'information demain et nous verrons si nous recevons des plaintes d'utilisateurs. Les seuls changements mineurs que nous devions faire étaient de nettoyer / désanalyser le tableau lorsqu'il était inséré dans la base de données (avoir des messages dans un que dans la base de données lorsqu'un cron l'envoie dans de petits volumes). Je laisserai cette question rester en suspens jusqu'à ce que la prime soit épuisée afin que nous puissions sensibiliser le public à cette question. Espérons que ce patch, ou une alternative, sera ajouté à core. Ou plus important encore, pourquoi pas. Que pensent-ils!
Christine Cooper
J'ai remarqué au hasard que vous avez effectué une mise à jour du ticket de liaison lié. Est-ce une mise à jour de ce code? Si tel est le cas, pourriez-vous, s'il vous plaît, publier cette mise à jour en éditant votre réponse ici aussi afin que cette réponse reste à jour? Merci beaucoup.
Christine Cooper
Bonjour, non, il s'agissait simplement d'un rafraîchissement du correctif par rapport au tronc actuel, de sorte qu'il fusionne sans conflit (dans l'espoir qu'il obtienne une certaine attention), le code est exactement le même ...
bonger le
4

Ce n'est pas vraiment un bogue WordPress, c'est un bogue phpmailerqui ne permet pas les en-têtes personnalisés ... si vous regardez 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;
    }

Vous pouvez voir que le cas par défaut incriminé est la sortie de la ligne d’en-tête supplémentaire avec charset et aucune limite. La définition du type de contenu par filtre ne résout pas le problème en soi, car le altcas est activé message_typeen cochant que ce AltBodyn'est pas vide, mais plutôt le type de contenu.

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

En fin de compte, cela signifie que dès que vous attachez un fichier ou une image en ligne, ou définissez-le AltBody, le bogue incriminé doit être ignoré. Cela signifie également qu'il n'est pas nécessaire de définir explicitement le type de contenu, car dès qu'il y en a un, AltBodyil est défini surmultipart/alternative par phpmailer.

Donc, la réponse simple est:

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

Ensuite, vous n'avez pas besoin de définir explicitement les en-têtes, vous pouvez simplement faire:

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

Malheureusement, de nombreuses fonctions et propriétés de la phpmailerclasse sont protégées. Sinon, une alternative valable serait simplement de vérifier et de remplacer la MIMEHeaderspropriété via le point d' phpmailer_initancrage avant de l'envoyer.

majick
la source
2

Je viens de publier un plug-in pour permettre aux utilisateurs d'utiliser des modèles HTML sur WordPress et je joue actuellement sur la version dev pour ajouter un simple repli de texte. J'ai fait ce qui suit et dans mes tests, je ne vois qu'une limite ajoutée et les courriels arrivent bien à 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 );
}

En gros, ce que je fais ici, c'est modifier l'objet phpmailer, charger le message dans un modèle html et le définir sur la propriété Body. De plus, j'ai pris le message d'origine et défini la propriété AltBody.

chifliiiii
la source
2

Ma solution simple consiste à utiliser html2text https://github.com/soundasleep/html2text de cette façon:

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

Ici, https://gist.github.com/ewake/6c4d22cd856456480bd77b988b5c9e80 est également un élément essentiel.

réveiller
la source
2

Pour ceux qui utilisent le hook 'phpmailer_init' pour ajouter leur propre 'AltBody':

Le corps de texte alternatif est réutilisé pour différents courriers consécutifs envoyés, à moins que vous ne le supprimiez manuellement! WordPress ne l'efface pas dans wp_mail () car il ne s'attend pas à ce que cette propriété soit utilisée.

Cela a pour conséquence que les destinataires reçoivent potentiellement des courriers qui ne leur sont pas destinés. Heureusement, la plupart des utilisateurs de clients de messagerie utilisant HTML ne verront pas la version texte, mais il s'agit toujours d'un problème de sécurité.

Heureusement, il y a une solution facile. Cela inclut le bit de remplacement altbody; notez que vous avez besoin de la bibliothèque 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 );
  }
}

Voici également l'essentiel d'un plugin WP que j'ai modifié pour résoudre ce problème: https://gist.github.com/youri--/c4618740b7c50c549314eaebc9f78661

Malheureusement, je ne peux pas commenter les autres solutions utilisant le crochet susmentionné, pour les en avertir, car je n'ai pas encore assez de représentants pour commenter.

Tanuki
la source
1

cela pourrait ne pas être une réponse exacte au message initial ici, mais c'est une alternative à certaines des solutions fournies ici concernant la définition d'un corps alt

Essentiellement, je devais (je voulais) définir un altbody distinct (c'est-à-dire un texte en clair) en plus de la partie html au lieu de compter sur des conversions / striptags et autres. donc je suis venu avec ce qui semble fonctionner très bien

/* setting the message parts for wp_mail()*/
$markup = array();
$markup['html'] = '<html>some html</html>';
$markup['plaintext'] = 'some plaintext';
/* message we are sending */    
$message = maybe_serialize($markup);


/* setting alt body distinctly */
add_action('phpmailer_init', array($this, 'set_alt_mail_body'));

function set_alt_mail_body($phpmailer){
    if( $phpmailer->ContentType == 'text/html' ) {
        $body_parts = maybe_unserialize($phpmailer->Body);

        if(!empty($body_parts['html'])){
            $phpmailer->MsgHTML($body_parts['html']);
        }

        if(!empty($body_parts['plaintext'])){
            $phpmailer->AltBody = $body_parts['plaintext'];
        }
    }   
}
Olly
la source
0

Si vous ne souhaitez pas créer de conflit de code dans le noyau de Wordpress, je pense que la solution la plus simple ou la solution la plus simple consiste à ajouter une action à phpmailer_initfaire avant l'envoi effectif du courrier dans le fichier wp_mail. Pour simplifier mon explication, voir l'exemple de code ci-dessous:

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

Si vous ajoutez un contenu dans la AltBodypropriété de la classe PHPMailer , le type de contenu par défaut sera automatiquement défini sur multipart/alternative.

Joshua Reyes
la source