Trimiterea email-urilor multipart (text/html) prin wp_mail() vă poate duce la blocarea domeniului
Sumar
Din cauza unui bug din WordPress Core, trimiterea email-urilor multipart (html/text) cu wp_mail() (pentru a reduce șansa ca email-urile să ajungă în folderele spam) va duce ironic la blocarea domeniului tău de către Hotmail (și alte email-uri Microsoft).
Aceasta este o problemă complexă pe care voi încerca să o detaliez pentru a ajuta pe cineva să găsească o soluție funcțională care ar putea fi implementată în core.
Va fi o lectură utilă. Să începem...
Bug-ul
Cel mai comun sfat pentru a evita ca newsletter-urile să ajungă în folderele spam este să trimiți mesaje multipart.
Multi-part (mime) se referă la trimiterea atât a părții HTML cât și TEXT într-un singur email. Când un client primește un mesaj multipart, acceptă versiunea HTML dacă poate reda HTML, altfel prezintă versiunea text.
S-a dovedit că funcționează. Când trimiteam către gmail, toate email-urile noastre ajungeau în spam până când am schimbat mesajele în multipart și au ajuns în inbox-ul principal. Minunat.
Acum, când trimiți mesaje multipart prin wp_mail(), acesta afișează Content Type (multipart/*) de două ori, o dată cu boundary (dacă este setat custom) și o dată fără. Acest comportament face ca email-ul să fie afișat ca mesaj brut și nu multipart pe unele clienturi de email, inclusiv pe toate cele Microsoft (Hotmail, Outlook, etc...)
Microsoft va marca acest mesaj ca spam, iar puținele mesaje care ajung vor fi marcate manual de către destinatar. Din păcate, adresele de email Microsoft sunt foarte utilizate. 40% din abonații noștri le folosesc.
Acest lucru este confirmat de Microsoft printr-un schimb de email-uri recent.
Marcarea mesajelor va duce la blocarea completă a domeniului. Asta înseamnă că mesajele nu vor fi trimise în folderul spam, nu vor fi livrate deloc destinatarului.
Am avut domeniul nostru principal blocat de 3 ori până acum.
Pentru că acesta este un bug în WordPress core, fiecare domeniu care trimite mesaje multipart este blocat. Problema este că majoritatea administratorilor web nu știu de ce. Am confirmat acest lucru când am făcut cercetări și am văzut alți utilizatori discutând despre asta pe forumuri etc. Este nevoie să intri în codul sursă și să ai cunoștințe bune despre cum funcționează acest tip de mesaje email, despre care vom discuta în continuare...
Să analizăm codul
Creează un cont hotmail/outlook. Apoi, rulează următorul cod:
// Setează $to la un email hotmail.com sau outlook.com
$to = "YourEmail@hotmail.com";
$subject = 'wp_mail testare multipart';
$message = '------=_Part_18243133_1346573420.1408991447668
Content-Type: text/plain; charset=UTF-8
Salut lume! Acesta este text simplu...
------=_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>Salut Lume! Acesta este 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"';
// trimite email
wp_mail( $to, $subject, $message, $headers );
Și dacă vrei să schimbi tipul de conținut implicit, folosește:
add_filter( 'wp_mail_content_type', 'set_content_type' );
function set_content_type( $content_type ) {
return 'multipart/alternative';
}
Acest lucru va trimite un mesaj multipart.
Deci dacă verifici sursa completă a mesajului, vei observa că tipul de conținut este adăugat de două ori, o dată fără boundary:
MIME-Version: 1.0
Content-Type: multipart/alternative;
boundary="====f230673f9d7c359a81ffebccb88e5d61=="
MIME-Version: 1.0
Content-Type: multipart/alternative; charset=
Aceasta este problema.
Sursa problemei se află în pluggable.php
- dacă ne uităm pe undeva pe aici:
// Setează Content-Type și charset
// Dacă nu avem un content-type din header-ele de intrare
if ( !isset( $content_type ) )
$content_type = 'text/plain';
/**
* Filtrează tipul de conținut wp_mail().
*
* @since 2.3.0
*
* @param string $content_type Tipul de conținut implicit wp_mail().
*/
$content_type = apply_filters( 'wp_mail_content_type', $content_type );
$phpmailer->ContentType = $content_type;
// Setează dacă este text simplu, în funcție de $content_type
if ( 'text/html' == $content_type )
$phpmailer->IsHTML( true );
// Dacă nu avem un charset din header-ele de intrare
if ( !isset( $charset ) )
$charset = get_bloginfo( 'charset' );
// Setează tipul de conținut și charset
/**
* Filtrează charset-ul implicit wp_mail().
*
* @since 2.3.0
*
* @param string $charset Charset-ul implicit pentru email.
*/
$phpmailer->CharSet = apply_filters( 'wp_mail_charset', $charset );
// Setează header-e personalizate
if ( !empty( $headers ) ) {
foreach( (array) $headers as $name => $content ) {
$phpmailer->AddCustomHeader( sprintf( '%1$s: %2$s', $name, $content ) );
}
if ( false !== stripos( $content_type, 'multipart' ) && ! empty($boundary) )
$phpmailer->AddCustomHeader( sprintf( "Content-Type: %s;\n\t boundary=\"%s\"", $content_type, $boundary ) );
}
if ( !empty( $attachments ) ) {
foreach ( $attachments as $attachment ) {
try {
$phpmailer->AddAttachment($attachment);
} catch ( phpmailerException $e ) {
continue;
}
}
}
Soluții potențiale
Așadar te întrebi, de ce nu ai raportat asta pe trac? Am făcut-o deja. Spre marea mea surpriză, un ticket diferit a fost creat în urmă cu 5 ani descriind aceeași problemă.
Să fim sinceri, au trecut cinci ani și jumătate. În ani de internet, asta înseamnă mai degrabă 30. Problema a fost în mod clar abandonată și practic nu va fi niciodată rezolvată (...dacă nu o rezolvăm aici).
Am găsit un thread excelent aici care oferă o soluție, dar deși soluția lui funcționează, strică email-urile care nu au setat custom $headers
.
Aici ne împotmolim de fiecare dată. Fie versiunea multipart funcționează bine și mesajele normale cu $headers
nesetate nu, fie invers.
Soluția la care am ajuns a fost:
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;
// Setează dacă este text simplu, în funcție de $content_type
if ( 'text/html' == $content_type )
$phpmailer->IsHTML( true );
// Dacă nu avem un charset din header-ele de intrare
if ( !isset( $charset ) )
$charset = get_bloginfo( 'charset' );
}
// Setează tipul de conținut și charset
/**
* Filtrează charset-ul implicit wp_mail().
*
* @since 2.3.0
*
* @param string $charset Charset-ul implicit pentru email.
*/
$phpmailer->CharSet = apply_filters( 'wp_mail_charset', $charset );
// Setează header-e personalizate
if ( !empty( $headers ) ) {
foreach( (array) $headers as $name => $content ) {
$phpmailer->AddCustomHeader( sprintf( '%1$s: %2$s', $name, $content ) );
}
}
Da, știu, editarea fișierelor core este tabu, stați jos... aceasta a fost o rezolvare disperată și o încercare slabă de a oferi o soluție pentru core.
Problema cu soluția noastră este că email-urile implicite precum înregistrări noi, comentarii, resetare parolă etc vor fi livrate ca mesaje goale. Deci avem un script wp_mail() funcțional care va trimite mesaje multipart dar nimic altceva.
Ce este de făcut
Scopul aici este să găsim o modalitate de a trimite atât mesaje normale (text simplu) cât și multipart folosind funcția core wp_mail() (nu o funcție sendmail personalizată).
Când încerci să rezolvi asta, principala problemă cu care te vei confrunta este cantitatea de timp pe care o vei petrece trimițând mesaje de test, verificând dacă sunt primite și practic deschizând o cutie de aspirină și înjurând Microsoft pentru că ești obișnuit cu problemele lor IE în timp ce gremlina aici este din păcate WordPress.
Actualizare
Soluția postată de @bonger permite ca $message
să fie un array conținând alternative cu chei de tip conținut. Am confirmat că funcționează în toate scenariile.
Vom permite acestei întrebări să rămână deschisă până când expiră recompensa pentru a crește gradul de conștientizare despre problemă, poate până la un nivel unde va fi rezolvată în core. Simțiți-vă liberi să postați o soluție alternativă unde $message
poate fi un string.
Următoarea versiune a funcției wp_mail()
include patch-ul aplicat de @rmccue/@MattyRob din tichetul https://core.trac.wordpress.org/ticket/15448, actualizat pentru versiunea 4.2.2, care permite ca $message
să fie un array ce conține alternative cheiate după tipul de conținut:
/**
* Trimite email, similar cu funcția mail din PHP
*
* O valoare de return true nu înseamnă automat că utilizatorul a primit
* emailul cu succes. Înseamnă doar că metoda folosită a putut procesa
* cererea fără erori.
*
* Folosirea filtrelor 'wp_mail_from' și 'wp_mail_from_name' permite crearea
* unei adrese de la (from) de genul 'Nume <email@adresa.com>' când ambele sunt setate. Dacă
* doar 'wp_mail_from' este setat, atunci va fi folosită doar adresa de email fără
* nume.
*
* Tipul de conținut implicit este 'text/plain' care nu permite folosirea HTML.
* Totuși, poți seta tipul de conținut al emailului folosind filtrul
* 'wp_mail_content_type'.
*
* Dacă $message este un array, cheia fiecărui element este folosită pentru a adăuga un atașament
* cu valoarea folosită ca corp. Elementul 'text/plain' este folosit ca versiunea
* text a corpului, iar elementul 'text/html' este folosit ca versiunea HTML
* a corpului. Toate celelalte tipuri sunt adăugate ca atașamente.
*
* Setul de caractere implicit este bazat pe setul de caractere folosit în blog. Setul de caractere poate
* fi setat folosind filtrul 'wp_mail_charset'.
*
* @since 1.2.1
*
* @uses PHPMailer
*
* @param string|array $to Array sau listă separată prin virgulă a adreselor de email către care se trimite mesajul.
* @param string $subject Subiectul emailului
* @param string|array $message Conținutul mesajului
* @param string|array $headers Opțional. Antete adiționale.
* @param string|array $attachments Opțional. Fișiere de atașat.
* @return bool Dacă conținutul emailului a fost trimis cu succes.
*/
function wp_mail( $to, $subject, $message, $headers = '', $attachments = array() ) {
// Compactează input-ul, aplică filtrele și extrage înapoi valorile
/**
* Filtrează argumentele funcției wp_mail().
*
* @since 2.2.0
*
* @param array $args Un array compactat cu argumentele wp_mail(), inclusiv valorile
* pentru "to", subiect, mesaj, antete și atașamente.
*/
$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)creează obiectul, dacă lipsește
if ( ! ( $phpmailer instanceof PHPMailer ) ) {
require_once ABSPATH . WPINC . '/class-phpmailer.php';
require_once ABSPATH . WPINC . '/class-smtp.php';
$phpmailer = new PHPMailer( true );
}
// Antete
if ( empty( $headers ) ) {
$headers = array();
} else {
if ( !is_array( $headers ) ) {
// Desparte antetele, astfel încât această funcție să poată accepta atât
// antete sub formă de string cât și array de antete.
$tempheaders = explode( "\n", str_replace( "\r\n", "\n", $headers ) );
} else {
$tempheaders = $headers;
}
$headers = array();
$cc = array();
$bcc = array();
// Dacă există conținut
if ( !empty( $tempheaders ) ) {
// Iterează prin antetele brute
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;
}
// Desparte antetul
list( $name, $content ) = explode( ':', trim( $header ), 2 );
// Curăță valorile
$name = trim( $name );
$content = trim( $content );
switch ( strtolower( $name ) ) {
// Pentru compatibilitate - procesează un antet From: dacă există
case 'from':
$bracket_pos = strpos( $content, '<' );
if ( $bracket_pos !== false ) {
// Textul dinaintea emailului între paranteze este numele "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 );
// Evită setarea unui $from_email gol.
} elseif ( '' !== trim( $content ) ) {
$from_email = trim( $content );
}
break;
case 'content-type':
if ( is_array($message) ) {
// Email multipart, ignoră antetul 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 = '';
}
// Evită setarea unui $content_type gol.
} 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:
// Adaugă la array-ul principal de antete
$headers[trim( $name )] = trim( $content );
break;
}
}
}
}
// Golește valorile care ar putea fi setate
$phpmailer->ClearAllRecipients();
$phpmailer->ClearAttachments();
$phpmailer->ClearCustomHeaders();
$phpmailer->ClearReplyTos();
$phpmailer->Body= '';
$phpmailer->AltBody= '';
// Adresa și numele de la (from)
// Dacă nu avem un nume din antetele de input
if ( !isset( $from_name ) )
$from_name = 'WordPress';
/* Dacă nu avem un email din antetele de input, folosește implicit wordpress@$sitename
* Unele hosturi vor bloca trimiterea de emailuri de la această adresă dacă nu există, dar
* nu există o alternativă simplă. Folosirea implicită a admin_email ar părea o altă
* opțiune, dar unele hosturi pot refuza retransmiterea emailurilor de la un domeniu necunoscut. Vezi
* https://core.trac.wordpress.org/ticket/5007.
*/
if ( !isset( $from_email ) ) {
// Obține domeniul site-ului și elimină www.
$sitename = strtolower( $_SERVER['SERVER_NAME'] );
if ( substr( $sitename, 0, 4 ) == 'www.' ) {
$sitename = substr( $sitename, 4 );
}
$from_email = 'wordpress@' . $sitename;
}
/**
* Filtrează adresa de email de la care se trimite.
*
* @since 2.2.0
*
* @param string $from_email Adresa de email de la care se trimite.
*/
$phpmailer->From = apply_filters( 'wp_mail_from', $from_email );
/**
* Filtrează numele asociat cu adresa de email "from".
*
* @since 2.3.0
*
* @param string $from_name Numele asociat cu adresa de email "from".
*/
$phpmailer->FromName = apply_filters( 'wp_mail_from_name', $from_name );
// Setează adresele destinație
if ( !is_array( $to ) )
$to = explode( ',', $to );
foreach ( (array) $to as $recipient ) {
try {
// Desparte $recipient în nume și adresă dacă este în formatul "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;
}
}
// Dacă nu avem un set de caractere din antetele de input
if ( !isset( $charset ) )
$charset = get_bloginfo( 'charset' );
// Setează tipul de conținut și setul de caractere
/**
* Filtrează setul de caractere implicit al funcției wp_mail().
*
* @since 2.3.0
*
* @param string $charset Setul de caractere implicit pentru email.
*/
$phpmailer->CharSet = apply_filters( 'wp_mail_charset', $charset );
// Setează subiectul și corpul emailului
$phpmailer->Subject = $subject;
if ( is_string($message) ) {
$phpmailer->Body = $message;
// Setează Content-Type și setul de caractere
// Dacă nu avem un tip de conținut din antetele de input
if ( !isset( $content_type ) )
$content_type = 'text/plain';
/**
* Filtrează tipul de conținut al funcției wp_mail().
*
* @since 2.3.0
*
* @param string $content_type Tipul de conținut implicit al wp_mail().
*/
$content_type = apply_filters( 'wp_mail_content_type', $content_type );
$phpmailer->ContentType = $content_type;
// Setează dacă este text simplu, în funcție de $content_type
if ( 'text/html' == $content_type )
$phpmailer->IsHTML( true );
// Pentru compatibilitate, noile emailuri multipart ar trebui să folosească
// formatul array pentru $message. Acest lucru nu a funcționat niciodată prea bine oricum
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);
}
}
}
}
// Adaugă orice destinatari CC și BCC
if ( !empty( $cc ) ) {
foreach ( (array) $cc as $recipient ) {
try {
// Desparte $recipient în nume și adresă dacă este în formatul "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 {
// Desparte $recipient în nume și adresă dacă este în formatul "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;
}
}
}
// Setează să folosească funcția mail() din PHP
$phpmailer->IsMail();
// Setează antete personalizate
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;
}
}
}
/**
* Acțiune declanșată după inițializarea PHPMailer.
*
* @since 2.2.0
*
* @param PHPMailer &$phpmailer Instanța PHPMailer, transmisă prin referință.
*/
do_action_ref_array( 'phpmailer_init', array( &$phpmailer ) );
// Trimite!
try {
return $phpmailer->Send();
} catch ( phpmailerException $e ) {
return false;
}
}
Deci dacă pui asta în fișierul tău, de exemplu "wp-content/mu-plugins/functions.php", atunci va suprascrie versiunea WP. Are o utilizare frumoasă fără să fie nevoie să te complici cu antete, de exemplu:
// Setează $to la o adresă de email de la hotmail.com sau outlook.com
$to = "AdresaTaDeEmail@hotmail.com";
$subject = 'Testare wp_mail multipart';
$message['text/plain'] = 'Salut lume! Acesta este text simplu...';
$message['text/html'] = '<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
<p>Salut Lume! Acesta este 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'; } );
// trimite email
wp_mail( $to, $subject, $message );
remove_filter( 'wp_mail_from', $from_func );
remove_filter( 'wp_mail_from_name', $from_name_func );
Te rog să iei în considerare că nu am testat acest lucru cu emailuri reale...

Am adăugat acest lucru la pluginurile esențiale și am rulat codul de test; a funcționat. Am testat notificările implicite din nucleu (notificare pentru utilizator nou etc.) și a funcționat și acolo. Voi continua să efectuez teste în acest weekend și voi vedea cum vor funcționa pluginurile cu aceasta și practic dacă totul funcționează. Voi analiza în mod specific datele brute ale mesajului. Aceasta va fi o sarcină care va dura mult timp, dar fiți siguri că voi raporta înapoi când voi termina. Dacă există un scenariu în care wp_mail() nu va funcționa (atunci când ar trebui să funcționeze), vă rog să mă anunțați. Mulțumesc pentru acest răspuns.

Lucruri bune, am verificat rezultatul și arată bine - de fapt, patch-ul face ca wp_mail să folosească procesarea standard robustă a PHPMailer în cazul în care se transmite un array, iar în rest revine la metodele mai puțin sigure ale WP (pentru compatibilitate inversă), deci ar trebui să fie ok (evident, meritul aici revine autorilor patch-ului)... De acum înainte voi folosi acest patch (și îl voi implementa retroactiv eventual) - și mulțumesc și eu pentru informația despre folosirea atât a html cât și a plain text pentru a reduce șansele de a fi marcat ca spam...

Am testat în toate scenariile posibile și funcționează excelent. Vom trimite un newsletter mâine și vom vedea dacă primim plângeri de la utilizatori. Singurele modificări minore pe care a trebuit să le facem au fost să sanificăm/desanificăm array-ul atunci când este introdus în baza de date (avem mesaje într-o coadă în baza de date unde un cron le trimite în loturi mici). Voi lăsa această întrebare deschisă și în așteptare până când bounty-ul expiră, astfel încât să putem crește conștientizarea asupra acestei probleme. Sperăm că acest patch, sau o alternativă, va fi adăugat în nucleu. Sau mai important, de ce nu. La ce se gândesc!

Am observat întâmplător că ai efectuat o actualizare la ticketul Trac menționat. Este aceasta o actualizare a acestui cod? Dacă da, ai putea să postezi această actualizare prin editarea răspunsului tău aici, astfel încât acest răspuns să rămână la zi? Mulțumesc mult.

Salut, nu, a fost doar o reîmprospătare a patch-ului față de versiunea curentă a trunk, astfel încât să se poată integra fără conflicte (în speranța că va primi ceva atenție), codul este exact același...

De fapt, tocmai l-am editat pentru a fi exact la fel (un spațiu după un foreach!)...

Excelent, măreță inițiativă. Vă rugăm să continuați cu această abordare. Poate că în curând o vor adăuga în nucleu... chiar ar trebui să o facă.

Am rulat codul tău prin PHPCS și am postat o versiune actualizată: https://gist.github.com/paulschreiber/a1057785f6117f72188f3b619e994702

În versiunile mai noi de WordPress, acest lucru cauzează probleme. apply_filters( 'wp_mail', ... ) apelează wp_staticize_emoji_for_email, care la rândul său apelează wp_staticize_emoji. Aceasta se așteaptă ca $message să fie un șir de caractere, dar aici este o matrice.

@PaulSchreiber Rezolvă modificarea ta partea pe care susții că se strică? Mulțumesc.

@ChristineCooper Cred că da. Versiunea mea nu mai folosește 'message' în compact().

@PaulSchreiber Poți posta te rog codul tău ca răspuns, ca să avem codul actualizat aici pe WPSE? Mulțumesc mult.

TLDR, soluția simplă este:
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);}
}
Apoi nu este necesar să setați explicit anteturile, limitele de antet sunt setate corect pentru dumneavoastră.
Continuați lectura pentru o explicație detaliată despre motiv...
Aceasta nu este cu adevărat o problemă a WordPress, ci una a phpmailer
care nu permite anteturile personalizate... dacă priviți în 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:
// Captează cazurile 'plain': și '':
$result .= $this->textLine('Content-Type: ' . $this->ContentType . '; charset=' . $this->CharSet);
$ismultipart = false;
break;
}
Puteți observa că cazul implicit ofensator este cel care afișează linia suplimentară de antet cu charset și fără limită. Setarea tipului de conținut prin filtru nu rezolvă aceasta singură doar pentru că cazul alt
aici este setat pe message_type
prin verificarea că AltBody
nu este gol, nu prin tipul de conținut.
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);
}
În final, aceasta înseamnă că de îndată ce atașați un fișier sau o imagine inline, sau setați AltBody
, problema ofensatoare ar trebui să fie ocolită. De asemenea, înseamnă că nu este necesar să setați explicit tipul de conținut, deoarece de îndată ce există un AltBody
, acesta este setat la multipart/alternative
de către phpmailer
.
Deci răspunsul simplu este:
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);}
}
Apoi nu este necesar să setați anteturile explicit, puteți face pur și simplu:
$message ='<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
<p>Salut Lume! Acesta este HTML...</p>
</body>
</html>';
wp_mail($to,$subject,$message);
Din păcate, multe dintre funcțiile și proprietățile din clasa phpmailer
sunt protejate, dacă nu ar fi fost așa, o alternativă validă ar fi fost să verificăm și să suprascriem proprietatea MIMEHeaders
prin hook-ul phpmailer_init
înainte de trimitere.

Nu pot să-ți mulțumesc suficient pentru această soluție, a funcționat imediat, chiar din prima. Dacă aș fi în locul tău, aș păstra doar 'fragmentul simplu de răspuns' singur la sfârșitul postării tale. :)

@scooterlord Cu plăcere, a fost cu siguranță un pic de muncă să rezolv asta! Am adăugat un rezumat al soluției la începutul răspunsului pentru cei care doresc doar remedierea.

Nu e minunat când doar copiezi/lipești fragmente de cod și funcționează din prima? :)

wp_strip_all_tags ar putea fi mai bun decât strip_tags. Ultima va afișa conținutul tag-urilor <style>, care sunt comune în e-mailurile HTML.

Pentru cei care folosesc hook-ul phpmailer_init
pentru a adăuga propriul lor 'AltBody':
Corpul alternativ de text este reutilizat pentru diferite email-uri consecutive trimise, decât dacă îl ștergeți manual! WordPress nu îl șterge în wp_mail()
deoarece nu se așteaptă ca această proprietate să fie utilizată.
Aceasta duce la situații în care destinatarii pot primi email-uri care nu erau destinate lor. Din fericire, majoritatea oamenilor care folosesc clienți de email cu suport HTML nu vor vedea versiunea text, dar tot rămâne o problemă de securitate.
Din fericire, există o soluție simplă. Aceasta include și partea de înlocuire a altbody; rețineți că aveți nevoie de biblioteca PHP Html2Text:
add_filter( 'wp_mail', 'wpse191923_force_phpmailer_reinit_for_multiple_mails', -1 );
function wpse191923_force_phpmailer_reinit_for_multiple_mails( $wp_mail_atts ) {
global $phpmailer;
if ( $phpmailer instanceof PHPMailer && $phpmailer->alternativeExists() ) {
// Proprietatea AltBody este setată, deci WordPress a folosit deja acest
// obiect $phpmailer acum pentru a trimite email, așa că
// ștergem proprietatea AltBody
$phpmailer->AltBody = '';
}
// Returnăm atributele nemodificate
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 );
}
}
Aici este și un gist pentru un plugin WP pe care l-am modificat pentru a rezolva această problemă: https://gist.github.com/youri--/c4618740b7c50c549314eaebc9f78661
Din păcate, nu pot comenta pe celelalte soluții care folosesc hook-ul menționat, pentru a le avertiza despre această problemă, deoarece nu am suficiente puncte de reputație pentru a comenta.

Tocmai am lansat un plugin care permite utilizatorilor să folosească șabloane HTML în WordPress și acum testez versiunea de dezvoltare pentru a adăuga o variantă simplă de text. Am făcut următoarele și în testele mele văd doar o singură graniță adăugată, iar e-mailurile ajung corect în Hotmail.
add_action( 'phpmailer_init', array($this->mailer, 'send_email' ) );
/**
* Modifică corpul e-mailului în php mailer cu versiunea finală
*
* @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 );
}
Deci, practic, aici modific obiectul phpmailer, încarc mesajul într-un șablon HTML și îl setez ca proprietate Body. De asemenea, am luat mesajul original și l-am setat ca proprietate AltBody.

Soluția mea simplă este să folosesc html2text https://github.com/soundasleep/html2text în acest fel:
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 );
}
}
Aici https://gist.github.com/frugan-it/6c4d22cd856456480bd77b988b5c9e80 găsiți și un gist despre asta.

Acesta poate să nu fie un răspuns exact la postarea inițială de aici, dar este o alternativă la unele dintre soluțiile oferite aici cu privire la setarea unui alt body.
În esență, am avut nevoie (am dorit) să setez un altbody distinct (adică text simplu) în plus față de partea HTML, în loc să mă bazez pe o conversie/striptags sau altele asemenea.
Așa că am venit cu această soluție care pare să funcționeze foarte bine:
/* setarea părților mesajului pentru wp_mail() */
$markup = array();
$markup['html'] = '<html>un html</html>';
$markup['plaintext'] = 'un text simplu';
/* mesajul pe care îl trimitem */
$message = maybe_serialize($markup);
/* setarea distinctă a alt body */
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'];
}
}
}

Această versiune a funcției wp_mail()
este bazată pe codul lui @bonger. Are următoarele modificări:
- Corecții de stil în cod (prin PHPCS)
- Gestionarea cazurilor în care $message este fie un array, fie un șir de caractere (asigură compatibilitate cu WP 5.x)
- Aruncă o excepție în loc să returneze false
- Sintaxă scurtă pentru array-uri
<?php
/**
* Adaptat din https://wordpress.stackexchange.com/a/191974/8591
*
* Trimite email, similar cu funcția mail din PHP
*
* O valoare de return true nu înseamnă neapărat că utilizatorul a primit
* emailul cu succes. Înseamnă doar că metoda folosită a putut procesa
* cererea fără erori.
*
* Folosind hook-urile 'wp_mail_from' și 'wp_mail_from_name' se poate crea
* o adresă de expediere în formatul 'Nume <email@adresa.com>' când ambele
* sunt setate. Dacă doar 'wp_mail_from' este setat, atunci va fi folosită
* doar adresa de email fără nume.
*
* Tipul implicit de conținut este 'text/plain', care nu permite folosirea HTML.
* Totuși, poți seta tipul de conținut al emailului folosind filtrul
* 'wp_mail_content_type'.
*
* Dacă $message este un array, cheia fiecărui element este folosită pentru a
* adăuga un atașament, iar valoarea este folosită drept corp. Elementul
* 'text/plain' este folosit ca versiune text a corpului, iar 'text/html'
* este folosit ca versiune HTML a corpului. Toate celelalte tipuri sunt
* adăugate ca atașamente.
*
* Setul de caractere implicit este bazat pe cel folosit în blog. Setul de
* caractere poate fi schimbat folosind filtrul 'wp_mail_charset'.
*
* @since 1.2.1
*
* @uses PHPMailer
*
* @param string|array $to Array sau listă de adrese de email separate prin virgulă pentru trimitere mesaj.
* @param string $subject Subiectul emailului
* @param string|array $message Conținutul mesajului
* @param string|array $headers Opțional. Anteturi suplimentare.
* @param string|array $attachments Opțional. Fișiere de atașat.
* @return bool Dacă conținutul emailului a fost trimis cu succes.
*/
public static function wp_mail( $to, $subject, $message, $headers = '', $attachments = [] ) {
// Compactează input-ul, aplică filtrele și extrage valorile înapoi
/**
* Filtrează argumentele funcției wp_mail().
*
* @since 2.2.0
*
* @param array $args Un array compactat cu argumentele wp_mail(), inclusiv valorile
* pentru "to", subiect, mesaj, anteturi și atașamente.
*/
$atts = apply_filters( 'wp_mail', compact( 'to', 'subject', 'headers', 'attachments' ) );
// Deoarece $message este un array, iar wp_staticize_emoji_for_email() așteaptă șiruri de caractere, parcurgem fiecare element individual
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;
// (Re)creează obiectul, dacă lipsește
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
}
// Anteturi
if ( empty( $headers ) ) {
$headers = [];
} else {
if ( ! is_array( $headers ) ) {
// Desparte anteturile, astfel încât funcția să accepte atât șiruri de anteturi, cât și array-uri
$tempheaders = explode( "\n", str_replace( "\r\n", "\n", $headers ) );
} else {
$tempheaders = $headers;
}
$headers = [];
$cc = [];
$bcc = [];
// Dacă există conținut
if ( ! empty( $tempheaders ) ) {
// Parcurge anteturile brute
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;
}
// Desparte în nume și conținut
list( $name, $content ) = explode( ':', trim( $header ), 2 );
// Curăță valorile
$name = trim( $name );
$content = trim( $content );
switch ( strtolower( $name ) ) {
// Pentru compatibilitate - procesează antetul From: dacă există
case 'from':
$bracket_pos = strpos( $content, '<' );
if ( false !== $bracket_pos ) {
// Textul dinaintea emailului între <> este numele "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 );
// Evită setarea unui $from_email gol.
} elseif ( '' !== trim( $content ) ) {
$from_email = trim( $content );
}
break;
case 'content-type':
if ( is_array( $message ) ) {
// Email multipart, ignoră antetul 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 = '';
}
// Evită setarea unui $content_type gol.
} 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:
// Adaugă la array-ul general de anteturi
$headers[ trim( $name ) ] = trim( $content );
break;
}
}
}
}
// Resetează valorile care ar putea fi setate
$phpmailer->ClearAllRecipients();
$phpmailer->ClearAttachments();
$phpmailer->ClearCustomHeaders();
$phpmailer->ClearReplyTos();
$phpmailer->Body = ''; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
$phpmailer->AltBody = ''; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
// Adresa și numele expeditorului
// Dacă nu avem un nume din anteturile de input
if ( ! isset( $from_name ) ) {
$from_name = 'WordPress';
}
/* Dacă nu avem o adresă de email din anteturile de input, folosim implicit wordpress@$sitename
* Unele gazde vor bloca trimiterea de emailuri de la această adresă dacă nu există, dar
* nu există o alternativă ușoară. Folosirea admin_email ar părea o altă opție, dar
* unele gazde pot refuza retransmiterea de emailuri de la un domeniu necunoscut. Vezi
* https://core.trac.wordpress.org/ticket/5007.
*/
if ( ! isset( $from_email ) ) {
// Obține domeniul site-ului și elimină 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;
}
/**
* Filtrează adresa de email de la care se trimite.
*
* @since 2.2.0
*
* @param string $from_email Adresa de email de la care se trimite.
*/
$phpmailer->From = apply_filters( 'wp_mail_from', $from_email ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
/**
* Filtrează numele asociat cu adresa de email "from".
*
* @since 2.3.0
*
* @param string $from_name Numele asociat cu adresa de email "from".
*/
$phpmailer->FromName = apply_filters( 'wp_mail_from_name', $from_name ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
// Setează adresele destinate
if ( ! is_array( $to ) ) {
$to = explode( ',', $to );
}
foreach ( (array) $to as $recipient ) {
try {
// Desparte $recipient în nume și adresă dacă este în formatul "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;
}
}
// Dacă nu avem un set de caractere din anteturile de input
if ( ! isset( $charset ) ) {
$charset = get_bloginfo( 'charset' );
}
// Setează tipul de conținut și setul de caractere
/**
* Filtrează setul de caractere implicit al funcției wp_mail().
*
* @since 2.3.0
*
* @param string $charset Setul de caractere implicit pentru email.
*/
$phpmailer->CharSet = apply_filters( 'wp_mail_charset', $charset ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
// Setează subiectul și corpul emailului
$phpmailer->Subject = $subject; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
if ( is_string( $message ) ) {
$phpmailer->Body = $message; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
// Setează Content-Type și setul de caractere
// Dacă nu avem un content-type din anteturile de input
if ( ! isset( $content_type ) ) {
$content_type = 'text/plain';
}
/**
* Filtrează tipul de conținut al funcției wp_mail().
*
* @since 2.3.0
*
* @param string $content_type Tipul de conținut implicit al funcției wp_mail().
*/
$content_type = apply_filters( 'wp_mail_content_type', $content_type );
$phpmailer->ContentType = $content_type; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
// Setează dacă este text simplu, în funcție de $content_type
if ( 'text/html' === $content_type ) {
$phpmailer->IsHTML( true );
}
// Pentru compatibilitate înapoi, emailurile multipart noi ar trebui să folosească
// formatul array pentru $message. Acest lucru nu a funcționat niciodată foarte bine
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 );
}
}
}
}
// Adaugă orice destinatari CC și BCC
if ( ! empty( $cc ) ) {
foreach ( (array) $cc as $recipient ) {
try {
// Desparte $recipient în nume și adresă dacă este în formatul "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 {
// Desparte $recipient în nume și adresă dacă este în formatul "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;
}
}
}
// Setează să folosească funcția mail() din PHP
$phpmailer->IsMail();
// Setează anteturile personalizate
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;
}
}
}
/**
* Acțiune care rulează după ce PHPMailer este inițializat.
*
* @since 2.2.0
*
* @param PHPMailer &$phpmailer Instanța PHPMailer, transmisă prin referință.
*/
do_action_ref_array( 'phpmailer_init', [ &$phpmailer ] );
// Trimite!
try {
return $phpmailer->Send();
} catch ( phpmailerException $e ) {
return new WP_Error( 'email-error', $e->getMessage() );
}
}

Dacă nu doriți să creați niciun conflict de cod în nucleul WordPress, cred că soluția alternativă sau cea mai simplă este să adăugați o acțiune la phpmailer_init
care se va executa înainte de trimiterea efectivă a e-mailului în funcția wp_mail()
. Pentru a simplifica explicația mea, consultați exemplul de cod de mai jos:
<?php
$to = '';
$subject = '';
$from = '';
$body = 'Conținutul text HTML, <html>...';
$headers = "FROM: {$from}";
add_action( 'phpmailer_init', function ( $phpmailer ) {
$phpmailer->AltBody = 'Conținutul text simplu al conținutului HTML original.';
} );
wp_mail($to, $subject, $body, $headers);
Dacă adăugați conținut în proprietatea AltBody
a clasei PHPMailer, atunci tipul de conținut implicit va fi setat automat la multipart/alternative
.

Am analizat în detaliu implementarea funcției wp_mail($to, $subject, $message, $headers, $attachments)
din fișierul pluggable.php
și am găsit o soluție care nu necesită modificarea nucleului WordPress.
Funcția wp_mail()
verifică argumentul $headers
pentru un set specific de tipuri de anteturi standard, și anume: from
, content-type
, cc
, bcc
și reply-to
.
Toate celelalte tipuri sunt desemnate ca anteturi personalizate și procesate separat. Dar iată problema: când este definit un antet personalizat, ca în cazul tău unde ai setat antetul MIME-Version
, următorul bloc de cod este executat (în interiorul funcției wp_mail()
):
// Setează anteturile personalizate
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 ) );
}
}
Această instrucțiune if
imbricată din fragmentul de mai sus este cauza problemei. Practic, un alt antet Content-Type
este adăugat ca antet personalizat în următoarele condiții:
- Un antet personalizat a fost definit (ai definit
MIME-Version
în scenariul descris). - Tipul MIME al antetului
Content-Type
conține șirulmultipart
. - A fost setată o graniță (boundary) pentru conținutul multipart.
Cea mai rapidă soluție în cazul tău este eliminarea antetului MIME-Version
. Majoritatea agenților utilizator îl adaugă automat, așa că eliminarea lui nu ar trebui să cauzeze probleme.
Dar dacă vrei să adaugi anteturi personalizate fără a genera un antet Content-Type
duplicat?
SOLUȚIE:
NU seta explicit antetul Content-Type
în array-ul $headers
atunci când adaugi anteturi personalizate. Folosește următoarea abordare:
$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' );
Prima linie din fragmentul de mai sus poate părea ciudată, dar funcția wp_mail()
va seta intern variabila $boundary
atâta timp cât definiția graniței apare pe linie separată, fără a fi prefixată cu Content-Type:
. Apoi poți folosi filtrele pentru a seta content-type
și charset
. Astfel, satisfaci condițiile pentru execuția blocului de cod care setează anteturile personalizate fără a adăuga explicit Content-Type: [mime-type]; [boundary];
.
Nu este nevoie să modifici implementarea funcției wp_mail()
din nucleu, deși are acest bug.
