Надсилання багаточастинних (текст / html) електронних листів через wp_mail (), ймовірно, заборонить ваш домен


37

Підсумок

Через помилку в WP Core, надсилання багаточастинних електронних листів (html / текст) з wp_mail () (щоб зменшити ймовірність надходження електронних листів у папки спаму) іронічно призведе до того, що ваш домен буде заблокований Hotmail (та іншими електронними листами Microsoft).

Це складна проблема, яку я маю на меті розкрити докладно, намагаючись допомогти комусь знайти ефективне рішення, яке згодом може бути реалізовано в основі.

Це буде корисним читанням. Давайте почнемо...

Клоп

Найпоширеніша порада уникати надсилання електронних листів у папках із спамом - надсилати повідомлення з кількома частинами.

Багаточастинка (mime) стосується надсилання як HTML, так і TEXT частини електронного повідомлення в один електронний лист. Коли клієнт отримує багаточасткове повідомлення, він приймає версію HTML, якщо він може надати HTML, інакше він представляє звичайну текстову версію.

Це, як доведено, працює. При надсиланні на gmail всі наші електронні листи потрапляли у папки зі спамом, поки ми не змінили повідомлення на багаточастинні, коли вони перейшли до основної скриньки. Чудові речі.

Тепер, надсилаючи багаточастинні повідомлення через wp_mail (), він виводить тип вмісту (multipart / *) двічі, один раз з межею (якщо встановлено звичайно) та один раз без. Така поведінка призводить до того, що повідомлення електронної пошти відображатиметься як необроблене повідомлення, а не багатостороннє в деяких електронних листах, включаючи всі Microsoft (Hotmail, Outlook тощо).

Microsoft позначить це повідомлення як непотрібне, а кілька повідомлень, що надходять через, отримувач позначить вручну. На жаль , широко використовуються адреси електронної пошти Microsoft. 40% наших абонентів ним користуються.

Це підтверджує Microsoft через електронний обмін електронною поштою, який ми нещодавно мали.

Позначення повідомлень призведе до повного блокування домену . Це означає, що повідомлення не буде надіслано в папку зі спамом, вони навіть не будуть доставлені одержувачеві взагалі.

Досі у нас був заблокований основний домен 3 рази.

Оскільки це помилка в ядрі WP, кожен домен, який надсилає багаточастинні повідомлення, блокується. Проблема в тому, що більшість веб-майстрів не знають, чому. Я підтвердив це, коли роблю моє дослідження і бачу, як інші користувачі обговорюють це на форумах і т.д.

Розбимо його на код

Створіть обліковий запис Hotmail / Outlook. Потім запустіть такий код:

// Set $to to an hotmail.com or outlook.com email
$to = "YourEmail@hotmail.com";

$subject = 'wp_mail testing multipart';

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

Hello world! This is plain text...


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

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

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

</body>
</html>


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

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


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

А якщо ви хочете змінити тип вмісту за замовчуванням , скористайтеся:

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

Це надішле багатостороннє повідомлення.

Тож якщо ви перевірите повне вихідне джерело повідомлення, ви помітите, що тип вмісту додається двічі, один раз без меж:

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

В цьому і полягає проблема.

Джерело проблеми полягає в тому, pluggable.php- якщо ми десь тут заглянемо:

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

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

    $phpmailer->ContentType = $content_type;

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

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

    // Set the content-type and charset

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

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

        if ( false !== stripos( $content_type, 'multipart' ) && ! empty($boundary) )
            $phpmailer->AddCustomHeader( sprintf( "Content-Type: %s;\n\t boundary=\"%s\"", $content_type, $boundary ) );
    }

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

Потенційні рішення

Тож вам цікаво, чому ви не повідомили про це у trac ? У мене вже є . На превеликий подив, 5 років тому було створено інший квиток, який окреслює ту саму проблему.

Поміркуймо, минуло півтора десятиліття. В Інтернеті років це більше, як 30. Проблема, очевидно, була відмовлена ​​і в основному ніколи не буде вирішена (... якщо ми не вирішимо її тут).

Тут я знайшов чудову тему, яка пропонує рішення, але, поки його рішення працює, він розбиває електронні листи, які не мають налаштованих на замовлення $headers.

Ось де ми врізаємось кожен раз. Або версія з декількома версіями працює нормально, і звичайні невстановлені $headersповідомлення не стикаються, або стихія.

Ми придумали таке рішення:

if ( false !== stripos( $content_type, 'multipart' ) && ! empty($boundary) ) {
    $phpmailer->ContentType = $content_type . "; boundary=" . $boundary;
}
else {

        $content_type = apply_filters( 'wp_mail_content_type', $content_type );

    $phpmailer->ContentType = $content_type;

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

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

// Set the content-type and charset

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

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

}

Так, я знаю, що редагування основних файлів є табу, відкиньтесь ... це було відчайдушним виправленням і поганою спробою надати виправлення для ядра.

Проблема з нашим виправленням полягає в тому, що електронні листи за замовчуванням, такі як нові реєстрації, коментарі, скидання пароля тощо, будуть доставлені як порожні повідомлення. Отже, у нас є робочий скрипт wp_mail (), який надсилатиме багаточастинні повідомлення, але нічого іншого.

Що робити

Метою тут є пошук способу надсилання як звичайних (звичайний текст), так і багаточастинних повідомлень за допомогою основної функції wp_mail () (а не спеціальної функції sendmail).

При спробі вирішити цю проблему, головна проблема, з якою ви зіткнетеся, - це кількість часу, яке ви витратите на надсилання фіктивних повідомлень, перевірку того, чи отримано вони, і в основному відкрити вікно з аспірином і проклинати в Microsoft, оскільки ви звикли до їх Проблеми IE, поки гремлін тут, на жаль, WordPress.

Оновлення

Рішення, розміщене @bonger, дозволяє $message бути масивом, що містить замінники, що містять тип вмісту. Я підтвердив, що це працює у всіх сценаріях.

Ми дозволимо це питання залишатись відкритим, поки не закінчиться щедрість, щоб підвищити обізнаність про проблему, можливо, до рівня, коли вона буде зафіксована в основі. Не соромтеся розміщувати альтернативне рішення, де $messageможе бути рядок.


1
Оскільки wp_mail()функція підключається, чи не визначає вашу заміну як плагін, що використовується (у wp-content / mu-plugins), не для вас (і для всіх інших, не вдається виправити ядро)? У такому випадку не переміщення багаточастинної / граничної перевірки на те, щоб після встановлення $phpmailer->ContentType = $content_type;(а не про elsing) не працювало?
Бонгер

@bonger Чи можете ви, будь ласка, написати відповідь із детальним описом вашого рішення?
Крістін Купер

1
Йо не потрібно редагувати ядро, тому що wp_mailце плагін . Скопіюйте оригінальну функцію у плагін, відредагуйте її, як вам потрібно, та активуйте плагін. WordPress буде використовувати вашу відредаговану функцію замість оригінальної, не потребуючи редагування ядра.
gmazzap

@ChristineCooper Я вагаюся, щоб зробити це, як ви кажете, тестування - це така королівська біль, але дивлячись на патч core.trac.wordpress.org/ticket/15448, запропонований у trac від @ rmccue / @ MattyRob, що виглядає дуже гарним способом іди, тож я опублікую неперевірену відповідь, виходячи з цього ...
Бонґер

2
@ChristineCooper, якщо ви просто зачепите в phpmailer і встановите текстове тіло в $ phpmailer-> AltBody така ж помилка трапляється?
chifliiiii

Відповіді:


15

Наступна версія wp_mail()- із патчем, застосованим @ rmccue / @ MattyRob у квитку https://core.trac.wordpress.org/ticket/15448 , оновленому для 4.2.2, що дозволяє $messageбути масивом, що містить тип вмісту альтернативні ключі:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    // Set the content-type and charset

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

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

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

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

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

        $phpmailer->ContentType = $content_type;

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

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

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

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

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

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

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

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

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

Отже, якщо ви помістите це у свій, наприклад, файл "wp-content / mu-plugins / функции.php", він перевершить версію WP. Це приємне використання без будь-якого возитися із заголовками, наприклад:

// Set $to to an hotmail.com or outlook.com email
$to = "YourEmail@hotmail.com";

$subject = 'wp_mail testing multipart';

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

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

</body>
</html>';

add_filter( 'wp_mail_from', $from_func = function ( $from_email ) { return 'foo@bar.com'; } );
add_filter( 'wp_mail_from_name', $from_name_func = function ( $from_name ) { return 'Foo'; } );

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

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

Зауважте, я не перевіряв це на фактичних електронних листах ...


Я додав це, щоб мати плагіни та запустив тестовий код; це спрацювало. Я перевірив базові сповіщення за замовчуванням (нове сповіщення користувача тощо), і воно також працювало. Я продовжуватиму виконувати тести в ці вихідні і побачити, як плагіни будуть працювати з цим і в основному, якщо все працює. Я спеціально перегляну необроблені дані повідомлення. Це буде дуже трудомістким завданням, але будьте впевнені, я звітну, коли закінчу. Якщо є сценарій, коли wp_mail () не працюватиме (коли це має бути інакше), будь ласка, повідомте мене про це. Дякую за цю відповідь.
Крістін Купер

Хороший матеріал, я підвів очей вихід і це виглядає добре - насправді патч просто змушує wp_mail використовувати стандартну обробку твердої скелі PHPMailer у випадку проходження масиву та в іншому випадку за замовчуванням на хитрі речі WP (для зворотної сумісності) так що це повинно бути добре (очевидно, кудо тут йде до авторів патчів) ... я буду використовувати його відтепер (і в кінцевому підсумку ретро підходить) - і ще раз дякую за інформацію, використовуючи обидва html / plain для знизити шанси на те, що
тебе поцілять

1
Ми перевірили його у всіх можливих сценаріях, і він чудово працює. Ми завтра виведемо бюлетень і побачимо, чи отримаємо від користувачів будь-які скарги. Єдині незначні зміни, які нам потрібно було зробити, - це очистити / несанітизувати масив, коли він вставляється в db (мати повідомлення в черзі в db, куди крон відправляє його невеликими групами). Я дозволю, щоб це питання залишалося відкритим і не вирішеним, поки не закінчиться щедрість, щоб ми могли довести до цього питання. Сподіваємось, цей патч або альтернатива будуть додані до ядра. Або ще важливіше, чому ні. Що вони думають!
Крістін Купер

Я випадковим чином помітив, що ви виконували оновлення пов'язаного квитка. Це оновлення цього коду? Якщо так, чи можете ви опублікувати це оновлення, відредагувавши свою відповідь і тут, щоб ця відповідь залишалася оновленою? Велике спасибі.
Крістін Купер

Привіт, ні, це було просто оновлення виправлення проти поточної магістралі, щоб вона зливалася без конфліктів (у надії на те, що вона приверне певну увагу), код точно такий же ...
bonger

4

Це насправді зовсім не помилка WordPress, вона полягає в phpmailerтому, що не дозволяють користувацьким заголовкам ... якщо ви подивитесь class-phpmailer.php:

public function getMailMIME()
{
    $result = '';
    $ismultipart = true;
    switch ($this->message_type) {
        case 'inline':
            $result .= $this->headerLine('Content-Type', 'multipart/related;');
            $result .= $this->textLine("\tboundary=\"" . $this->boundary[1] . '"');
            break;
        case 'attach':
        case 'inline_attach':
        case 'alt_attach':
        case 'alt_inline_attach':
            $result .= $this->headerLine('Content-Type', 'multipart/mixed;');
            $result .= $this->textLine("\tboundary=\"" . $this->boundary[1] . '"');
            break;
        case 'alt':
        case 'alt_inline':
            $result .= $this->headerLine('Content-Type', 'multipart/alternative;');
            $result .= $this->textLine("\tboundary=\"" . $this->boundary[1] . '"');
            break;
        default:
            // Catches case 'plain': and case '':
            $result .= $this->textLine('Content-Type: ' . $this->ContentType . '; charset=' . $this->CharSet);
            $ismultipart = false;
            break;
    }

Ви можете бачити неприйнятний випадок за замовчуванням - це те, що виводить додатковий рядок заголовка із схемою та без меж. Встановлення типу вмісту за допомогою фільтра не вирішує це само по собі, оскільки altсправа тут увімкнена message_typeперевіркою AltBodyне порожнім, а не типом вмісту.

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

Зрештою, це означає, щойно ви додаєте файл чи вбудоване зображення або встановите AltBody, помилку, яка порушила, слід обійти. Це також означає , що немає необхідності явно встановити тип контенту , тому що , як тільки є AltBodyвін встановлений multipart/alternativeна phpmailer.

Тож проста відповідь:

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

Тоді не потрібно чітко встановлювати заголовки, ви можете просто зробити:

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

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

На жаль, багато функцій та властивостей у phpmailerкласі захищені, якби не справжньою альтернативою було б просто перевірити та замінитиMIMEHeaders властивість через phpmailer_initгачок перед відправкою.


2

Я щойно випустив плагін, щоб дозволити користувачам використовувати HTML-шаблони на WordPress та Im, що грають зараз у версії Dev, щоб додати простий запасний текст. Я зробив наступне, і в своїх тестах я бачу лише одну межу, і електронні листи надходять до Hotmail.

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

/**
* Modify php mailer body with final email
*
* @since 1.0.0
* @param object $phpmailer
*/
function send_email( $phpmailer ) {

    $message            =  $this->add_template( apply_filters( 'mailtpl/email_content', $phpmailer->Body ) );
    $phpmailer->AltBody =  $this->replace_placeholders( strip_tags($phpmailer->Body) );
    $phpmailer->Body    =  $this->replace_placeholders( $message );
}

Тому в основному те, що я тут роблю, - це змінити об'єкт phpmailer, завантажити повідомлення всередині шаблону HTML і встановити його у властивість Body. Також я взяв оригінальне повідомлення і встановив властивість AltBody.


2

Моє просто рішення - використовувати html2text https://github.com/soundasleep/html2text таким чином:

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

Тут https://gist.github.com/ewake/6c4d22cd856456480bd77b988b5c9e80 також суть о.


2

Для всіх, хто використовує гачок 'phpmailer_init', щоб додати свій власний 'AltBody':

Альтернативний текст тексту повторно використовується для надсилання різних послідовних повідомлень, якщо ви не очистите його вручну! WordPress не очищає його в wp_mail (), оскільки не очікує використання цього властивості.

Це призводить до того, що одержувачі можуть отримувати пошту, не призначену для них. На щастя, більшість людей, що використовують поштові клієнти з підтримкою HTML, не побачать текстову версію, але це все ще проблема безпеки.

На щастя, є легке виправлення. Сюди входить біт заміни altbody; зауважте, що вам потрібна бібліотека PHP Html2Text:

add_filter( 'wp_mail', 'wpse191923_force_phpmailer_reinit_for_multiple_mails', -1 );
function wpse191923_force_phpmailer_reinit_for_multiple_mails( $wp_mail_atts ) {
  global $phpmailer;

  if ( $phpmailer instanceof PHPMailer && $phpmailer->alternativeExists() ) {
    // AltBody property is set, so WordPress must already have used this
    // $phpmailer object just now to send mail, so let's
    // clear the AltBody property
    $phpmailer->AltBody = '';
  }

  // Return untouched atts
  return $wp_mail_atts;
}

add_action( 'phpmailer_init', 'wpse191923_phpmailer_init_altbody', 1000, 1 );
function wpse191923_phpmailer_init_altbody( $phpmailer ) {
  if ( ( $phpmailer->ContentType == 'text/html' ) && empty( $phpmailer->AltBody ) ) {
    if ( ! class_exists( 'Html2Text\Html2Text' ) ) {
      require_once( 'Html2Text.php' );
    }
    if ( ! class_exists( 'Html2Text\Html2TextException' ) ) {
      require_once( 'Html2TextException.php' );
    }
    $phpmailer->AltBody = Html2Text\Html2Text::convert( $phpmailer->Body );
  }
}

Ось також зміст плагіну WP, який я змінив, щоб виправити цю проблему: https://gist.github.com/youri--/c4618740b7c50c549314eaebc9f78661

На жаль, я не можу коментувати інші рішення, використовуючи вищезгаданий гачок, щоб попередити їх про це, оскільки у мене ще недостатньо представників для коментарів.


1

це може бути не точною відповіддю на початковий пост тут, але це альтернатива деяким наведеним тут рішенням щодо встановлення альт-тіла

По суті, мені потрібно було (хотілося) встановити чіткий albody (тобто простий текст) додатково до html-частини замість того, щоб покладатися на якісь конверсії / стриптиги та інше. тому я придумав це, що, здається, працює просто чудово

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


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

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

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

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

0

Якщо ви не хочете створювати будь-який конфлікт коду в ядрі Wordpress, я думаю, що альтернативним чи найпростішим рішенням є додати дії до того, phpmailer_initщо буде зроблено до фактичної відправки пошти в wp_mail. Для спрощення мого пояснення див. Приклад коду нижче:

<?php 

$to = '';
$subject = '';
$from = '';
$body = 'The text html content, <html>...';

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

add_action( 'phpmailer_init', function ( $phpmailer ) {
    $phpmailer->AltBody = 'The text plain content of your original text html content.';
} );

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

Якщо ви додасте вміст у AltBodyвластивість класу PHPMailer, то тип контенту за замовчуванням автоматично встановиться на multipart/alternative.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.