Решение проблемы битых заголовков в email
Те, кто используют русскоязычную версию WordPress, наверняка не раз сталкивались с проблемой битого заголовка Subject в уведомлениях WordPress. Навреное, проще проиллюстрировать:
Очевидно, что это не хорошо Более того, битая кодировка может служить критерием для определения письма спамом.
Для того, чтобы убедиться, что такое отображение письма — это не ошибка почтового клиента, я написал маленький тестовый скрипт, который отправляет письма на GMail:
require_once('wp-config.php');
wp_mail('blablabla@gmail.com', '[1234567890] New Comment On: Пятерка порадовавших меня запросов', 'Test Message');
?>
Когда Google отобразил битый Subject, стало понятно, что виноват всё-таки WordPress.
Если посмотреть на исходный текст самого письма, то увидим такие строки:
=?UTF-8?B?v9C+0YDQsNC00L7QstCw0LLRiNC40YUg0LzQtdC90Y8g0LfQsNC/0YDQvtGB?=
=?UTF-8?B?0L7Qsg==?=
Она заслуживает пристального внимания. В соответствии с RFC 2822 WordPress (а точнее — PHP Mailer) разбил длинный заголовок на три фрагмента, каждый из которых не превышает 78 байт. Очевидно, что проблема заключается в том, что скрипт разбивал строку, закодированную BASE64, что привело к тому, что многобайтовые символы UTF-8 были разорваны.
Код это подтверждает:
$maxlen -= $maxlen % 4;
$encoded = trim(chunk_split($encoded, $maxlen, "\n"));
Есть два варианта исправления. Но оба сводятся к редактирования исходного текста WordPress. Иначе никак.
Простой вариант заключается в изменении верхней границы допустимой длины заголовка сообщения. В принципе, это не сильно противоречит стандарту:
There are two limits that this standard places on the number of characters in a line. Each line of characters MUST be no more than 998 characters, and SHOULD be no more than 78 characters, excluding the CRLF.
То есть, исправив длину, мы проигнорируем SHOULD, но будем принмать во внимание MUST. Лучше, чем ничего.
Итак, патч в формате unified diff (должен применяться к файлу wp-includes/class-phpmailer.php
):
+++ class-phpmailer.php 2008-09-27 02:39:26.000000000 +0300
@@ -1160,7 +1160,7 @@
if ($x == 0)
return ($str);
- $maxlen = 75 - 7 - strlen($this->CharSet);
+ $maxlen = 995 - 7 - strlen($this->CharSet);
// Try to select the encoding which should produce the shortest output
if (strlen($str)/3 < $x) {
$encoding = 'B';
Второе решение (тоже патч) более серьёзное и более надёжное:
+++ class-phpmailer.php 2008-09-27 07:54:25.000000000 +0300
@@ -655,6 +655,7 @@
*/
function WrapText($message, $length, $qp_mode = false) {
$soft_break = ($qp_mode) ? sprintf(" =%s", $this->LE) : $this->LE;
+ $is_utf8 = ("utf-8" == strtolower($this->CharSet));
$message = $this->FixEOL($message);
if (substr($message, -1) == $this->LE)
@@ -677,7 +678,9 @@
if ($space_left > 20)
{
$len = $space_left;
- if (substr($word, $len - 1, 1) == "=")
+ if ($is_utf8)
+ $len = $this->getUtf8CharBoundary($word, $len);
+ elseif (substr($word, $len - 1, 1) == "=")
$len--;
elseif (substr($word, $len - 2, 1) == "=")
$len -= 2;
@@ -695,7 +698,9 @@
while (strlen($word) > 0)
{
$len = $length;
- if (substr($word, $len - 1, 1) == "=")
+ if ($is_utf8)
+ $len = $this->getUtf8CharBoundary($word, $len);
+ elseif (substr($word, $len - 1, 1) == "=")
$len--;
elseif (substr($word, $len - 2, 1) == "=")
$len -= 2;
@@ -1164,9 +1169,14 @@
// Try to select the encoding which should produce the shortest output
if (strlen($str)/3 < $x) {
$encoding = 'B';
+ if (true == function_exists('mb_strlen') && strlen($str) > mb_strlen($str, $this->CharSet)) {
+ $encoded = $this->b64Multibyte($str);
+ }
+ else {
$encoded = base64_encode($str);
$maxlen -= $maxlen % 4;
$encoded = trim(chunk_split($encoded, $maxlen, "\n"));
+ }
} else {
$encoding = 'Q';
$encoded = $this->EncodeQ($str, $position);
@@ -1492,6 +1502,66 @@
function AddCustomHeader($custom_header) {
$this->CustomHeader[] = explode(":", $custom_header, 2);
}
+
+ function getUtf8CharBoundary($s, $max_len)
+ {
+ $lb = 3;
+ while (true) {
+ $x = substr($s, $max_len - $lb, $lb);
+ $pos = strpos($x, "=");
+ if (false !== $pos) {
+ $hex = substr($s, $max_len - $lb + $pos + 1, 2);
+ $dec = hexdec($hex);
+ if ($dec < 128) {
+ if ($pos > 0) {
+ $max_len = $max_len - $lb + $pos;
+ }
+
+ break;
+ }
+
+ if ($dec >= 192) {
+ $max_len = $max_len - $lb + $pos;
+ break;
+ }
+
+ $lb += 3;
+ }
+ else {
+ break;
+ }
+ }
+
+ return $max_len;
+ }
+
+ function b64MultiByte($s)
+ {
+ $start = "=?{$this->CharSet}?B?";
+ $end = "?=";
+ $encoded = "";
+
+ $mb_length = mb_strlen($s, $this->CharSet);
+ $str_len = strlen($s);
+ $length = 75 - strlen($start) - 2; //2 - strlen($end)
+ $step = floor(0.75 * $length * $mb_length/$str_len);
+ $average = $step;
+
+ for ($i=0; $i<$mb_length; $i+=$step) {
+ $lb = 0;
+
+ do {
+ $step = $average - $lb;
+ $tmp = base64_encode(mb_substr($s, $i, $step, $this->CharSet));
+ ++$lb;
+ }
+ while (strlen($tmp) > $length);
+
+ $encoded .= $tmp . $this->LE;
+ }
+
+ return substr($encoded, 0, -strlen($this->LE));
+ }
}
?>
Очень надеюсь, что решение кому-нибудь поможет
Я тут немного подумал и решил выложить пропатченные файлы.
Первый вариант
Второй вариант
Да, и резервные копии никто не отменял
актуально для всех версий WP?
Для всех до 2.7 – в 2.7 эту ошибку исправили.
Владимир, спасибо, полезная штука!
У меня такой вопрос.. то есть проблема была в классе PHPMailer, насколько правильно я понимаю. Если в WordPress 2.7 проблему пофиксили, то наверняка вышел новый PHPMailer или как-то по другому решили проблему?
В общем, у меня движок не WordPress, но хочу пофиксить трабл, какие инструкции предложишь?
Новый PHPMailer…
Я бы посоветовал обновить PHPMailer, а потом смотреть, не сломало ли что обновление. Но должно сработать.
Патч через ssh юзать надо?
Патч — по SSH, пропатченные файлы — если развернуть на своём компьютере — можно по FTP залить. Я сжал zip’ом пропатченные PHP-файлы только с той целью, чтобы у сервера не появилось желания их выполнить.
Поставил сначало второй вариант - не заработало…
Все равно, приходили письма вида “Проверьте, п ?жалуйста:”
А первый вариант заработал!
А почему бы вам не попробовать написать несколько статей по психологии, у вас отлично получается грамотно излагать свои мысли. Если что, заходите в гости…Буду рада помочь;)
Потому что я по специальности не психолог, а инженер-системотехник (ну еще и референт-переводчик). Если я стану писать статьи по психологии, это то же самое, что рассказывать хирургу, как правильно делать надрез Вообще я стараюсь руководствоваться фразой Апеллеса: Ne sutor supra crepidam judicet
Мне кажется - это был спам , не было смысла отвечать на него…
Мне помог второй вариант. Автору респект за исправление ошибки!
решенее неплохое, но как быть с обновлениями …
лазить в код неудобно
я тут решил эту проблемму этим плагином,
непомню где нашол, но он работает для всех верисй
С обновлениями всё в порядке, в WordPress 2.7 эта ошибка исправлена.
Хм. У меня 2.7.1 и такая же фигня. То есть есть вопросики в заголовке и в теле письма… Куда смотреть?
Кодировка блога UTF-8? WordPress родной или чья-то сборка?
кодировка UTF-8, сборка от Лекактуса. Но письма идут через плагин cforms. Таки думаю что там где-то что-то не так…
В cforms свой phpmail’ер ) в нем и нужно править )
У меня версия 2.8.2 и не корректно отображается буквы И, ш в комментариях и в записях и в рубриках…. вместо них квадратик и вопросик, подскажите пожалуйста как исправить проблему, кодировка сайта ютф-8
Стас, проверьте кодировку таблиц в базе данных. Соединение с базой тоже utf-8?