Те, кто используют русскоязычную версию 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));
+ }
}
?>
Очень надеюсь, что решение кому-нибудь поможет
Я тут немного подумал и решил выложить пропатченные файлы.
Первый вариант
Второй вариант
Да, и резервные копии никто не отменял
Патч через ssh юзать надо?
Патч — по SSH, пропатченные файлы — если развернуть на своём компьютере — можно по FTP залить. Я сжал zip’ом пропатченные PHP-файлы только с той целью, чтобы у сервера не появилось желания их выполнить.
Поставил сначало второй вариант - не заработало…
Все равно, приходили письма вида “Проверьте, п ?жалуйста:”
А первый вариант заработал!
А почему бы вам не попробовать написать несколько статей по психологии, у вас отлично получается грамотно излагать свои мысли. Если что, заходите в гости…Буду рада помочь;)
Потому что я по специальности не психолог, а инженер-системотехник (ну еще и референт-переводчик). Если я стану писать статьи по психологии, это то же самое, что рассказывать хирургу, как правильно делать надрез
Вообще я стараюсь руководствоваться фразой Апеллеса: Ne sutor supra crepidam judicet
Мне кажется - это был спам ;), не было смысла отвечать на него…
Мне помог второй вариант. Автору респект за исправление ошибки!