Trimiterea email-urilor multipart (text/html) prin wp_mail() vă poate duce la blocarea domeniului

18 iun. 2015, 18:37:09
Vizualizări: 26.6K
Voturi: 44

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.

7
Comentarii

Deoarece funcția wp_mail() este pluggable, nu ar fi o soluție bună să definești înlocuirea ta ca un plugin must-use (în wp-content/mu-plugins) pentru tine (și pentru toți ceilalți, în cazul în care core fix eșuează)? În ce caz nu ar funcționa mutarea verificării multipart/boundary după setarea $phpmailer->ContentType = $content_type; (mai degrabă decât folosirea else-ului)?

bonger bonger
18 iun. 2015 21:35:18

@bonger Poți, te rog, să scrii un răspuns detaliat cu soluția ta?

Christine Cooper Christine Cooper
18 iun. 2015 22:07:28

Nu trebuie să editezi core, deoarece wp_mail este pluggable. Copiază funcția originală într-un plugin, modifică-o după nevoile tale și activează plugin-ul. WordPress va folosi funcția ta modificată în locul celei originale, fără a fi nevoie să editezi core.

gmazzap gmazzap
19 iun. 2015 04:41:45

@ChristineCooper Ezit să fac asta, așa cum spui, testarea este o adevărată corvoadă, dar uitându-mă la patch-ul https://core.trac.wordpress.org/ticket/15448 sugerat în trac de @rmccue/@MattyRob, pare o cale foarte bună, așa că voi posta un răspurs netestat bazat pe asta...

bonger bonger
19 iun. 2015 08:17:54

Aceasta nu este în niciun fel o soluție la problema actuală, dar cred că merită menționat: Dacă vrei ca e-mailurile tale să ajungă la destinatarul dorit, ar trebui să iei în considerare utilizarea unui server SMTP extern. Un plugin SMTP ar ocoli complet această problemă, ceea ce poate explica de ce nimeni nu s-a obosit să o repare timp de cinci ani.

Mathew Tinsley Mathew Tinsley
19 iun. 2015 09:03:30

@ChristineCooper dacă pur și simplu te conectezi la phpmailer și setezi corpul textului în $phpmailer->AltBody, aceeași eroare apare?

chifliiiii chifliiiii
10 iul. 2015 19:52:50

Am găsit o metodă de a folosi wp_mail() așa cum este fără a modifica nucleul. Verifică răspunsul meu de mai jos.

TheAddonDepot TheAddonDepot
20 dec. 2020 01:08:31
Arată celelalte 2 comentarii
Toate răspunsurile la întrebare 9
14
20

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...

19 iun. 2015 08:34:00
Comentarii

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.

Christine Cooper Christine Cooper
20 iun. 2015 18:43:49

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...

bonger bonger
21 iun. 2015 00:02:10

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!

Christine Cooper Christine Cooper
21 iun. 2015 18:53:57

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.

Christine Cooper Christine Cooper
9 sept. 2015 02:49:04

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...

bonger bonger
9 sept. 2015 07:05:13

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

bonger bonger
9 sept. 2015 07:12:48

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ă.

Christine Cooper Christine Cooper
9 sept. 2015 16:42:20

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

Paul Schreiber Paul Schreiber
17 ian. 2019 16:29:15

Î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.

Paul Schreiber Paul Schreiber
17 ian. 2019 17:03:46

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

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

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

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

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

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

@ChristineCooper gata.

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

Această abordare excelentă încă nu a fost integrată în nucleul WordPress? În documentație nu se menționează utilizarea array-ului sau partea multipart.

Thomas Ebert Thomas Ebert
12 nov. 2020 10:45:50
Arată celelalte 9 comentarii
5
11

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.

24 mai 2016 17:50:33
Comentarii

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 scooterlord
8 ian. 2021 17:14:00

@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.

majick majick
12 ian. 2021 06:37:12

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

scooterlord scooterlord
12 ian. 2021 09:56:45

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.

James Beninger James Beninger
14 ian. 2022 18:46:32

@JamesBeninger ah, observație bună! Am editat răspunsul pentru a schimba la aceasta.

majick majick
15 ian. 2022 07:32:41
0

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.

16 oct. 2018 14:33:36
0

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.

10 iul. 2015 21:43:30
0

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.

11 oct. 2016 13:50:18
0

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'];
        }
    }   
}
25 ian. 2017 16:56:04
1

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() );
    }
}
19 feb. 2020 23:27:45
Comentarii

Salut Paul. Sintaxa scurtă pentru array-uri a apărut cu mult înainte de PHP 7+ ;)

kaiser kaiser
19 feb. 2020 23:37:39
0

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.

18 sept. 2018 08:24:56
0

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:

  1. Un antet personalizat a fost definit (ai definit MIME-Version în scenariul descris).
  2. Tipul MIME al antetului Content-Type conține șirul multipart.
  3. 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.

20 dec. 2020 00:41:41