Отправка и перекодирование писем
Приступим ко второй части нашей задачи — напишем функцию PostMail(), которая будет отправлять письмо адресату, преобразовав его предварительно в нужную кодировку. Вот какие возможности она будет обеспечивать:
r вставку заголовка From
в письмо, если он еще не присутствует в сообщении;
r преобразование письма в нужную кодировку кириллицы;
r вставку соответствующего значения в заголовок Content-type, чтобы письмо было "понятно" любой почтовой программе;
r поддержку функций мини-шаблонизатора, который мы уже написали.
В листинге 32.2 приведен исходный код функции. Как обычно, мы помещаем функцию в отдельный модуль библиотекаря (библиотекарь описан в главе 29). Этот модуль будет использовать возможности, предоставляемые библиотекой Minitemplate.phl.
Листинг 32.2. Функция PostMail(): Mail.phl
<?
Uses("Minitemplate");
// Кодировка по умолчанию для исходного текста.
define("DefaultCode","w");
// Функция возвращает строку $st, переведенную из кодировки
// $from в кодировку $to. Возможные значения этих параметров:
// w[indows] — windows-1251
// k[oi8-r] — koi8-r
// m[ac] — x-mac-cyrillic
// i[so] — iso-8859-5
// t[ranslit] — translit ("английскими"
буквами — "русские" слова)
// Замечание: квадратными скобками помечены необязательные символы.
// параметр $from не может равняться "t", потому что трудно
// восстанавливать текст из транслита (хотя эта задача и разрешима).
// Функция полезна и сама по себе, но все-таки чаще всего ее
// применяют для работы с почтой. Именно поэтому я включаю
// ее в этот модуль.
function EncodeString($st,$to,$from=DefaultCode)
{ // Оставляем только первые буквы названий кодировок
$from=strtolower(substr($from,0,1));
$to =strtolower(substr($to,0,1));
// Пытаемся воспользоваться встроенной в PHP функцией
if($to!="t") return convert_cyr_string($st,$from,$to);
// Иначе нужно преобразовать строку в Translit, что придется
// делать "вручную" — при помощи strtr().
// Сначала заменяем "односимвольные" фонемы.
$st=strtr($st,"абвгдеёзийклмнопрстуфхъыэ",
"abvgdeeziyklmnoprstufh'ie");
$st=strtr($st,"АБВГДЕЁЗИЙКЛМНОПРСТУФХЪЫЭ",
"ABVGDEEZIYKLMNOPRSTUFH'IE");
// Затем — "многосимвольные".
$st=strtr($st,array(
"ж"=>"zh", "ц"=>"ts", "ч"=>"ch", "ш"=>"sh",
"щ"=>"shch","ь"=>"", "ю"=>"yu", "я"=>"ya",
"Ж"=>"ZH", "Ц"=>"TS", "Ч"=>"CH", "Ш"=>"SH",
"Щ"=>"SHCH","Ь"=>"", "Ю"=>"YU", "Я"=>"YA"
));
// Возвращаем результат.
return $st;
}
// Значения параметра Content-tyep charset в зависимости от
// односимвольного названия кодировки.
global $CoderCharset;
$CoderCharset["w"]="windows-1251";
$CoderCharset["i"]="iso-8859-5";
$CoderCharset["k"]="koi8-r";
$CoderCharset["m"]="x-mac-cyrillic";
$CoderCharset["t"]="koi8-r";
// Разделитель тела и заголовков (таких как From: и т. д.) в письме.
define("MailDivider","~StartOfMail");
// Посылает письмо $msg по заданному адресу $to, перед этим
// преобразовав его в кодировку $encTo. Проставляет поле
// charset и правильно обрабатывает имя получателя (если
// в теле письма уже указано "To: Вася", то в результате
// получается "To: Вася <vasya@pupkin.ru>"). Если работа происходит
// в Win32, то письмо не посылается, а создается отладочный файл,
// в котором будет содержаться текст письма.
// Письмо должно состоять из заголовков и тела, разделенных
// маркером ~StartOfMail.
function SendMail($to,$msg,$encTo=DefaultCode,$encFrom=DefaultCode)
{ global $CoderCharset;
// Перекодируем
$msg=EncodeString($msg,$encTo,$encFrom); // тело письма
$head=""; // заголовки
// Если есть заголовки, выделяем их.
if(strpos($msg,MailDivider)!==false) {
$regs=split(MailDivider."\r?\n?",$msg,2); // тело и заголовки
$head=trim($regs[0]);
$msg=$regs[1];
}
// Работаем с заголовками. Разбиваем их на строки.
if($head) $Lines=split("[\r\n]+",$head); else $Lines=array();
$HasContType=0; // число найденных заголовков Content-type
$chs="charset=$CoderCharset[$encTo]";
$subject="";
for($i=0; $i<count($Lines); $i++) {
$l=&$Lines[$i];
// Проставляем текущую кодировку у письма. Для этого
// проверяем, задан ли в нем заголовок Content-type и,
// если задан, то модифицируем его, а если нет —
// добавляем этот заголовок в начало и конец письма.
if(eregi("^Content-type:",$l)) {
if(eregi("charset *=",$l))
$l=eregi_Replace("charset *= *[^;,\n]+",$chs,$l);
else
$l.="; $chs";
$HasContType++;
}
// Проверяем значение поля "to" в письме — там может быть имя
// получателя. В этом случае добавляем к нему еще и адрес.
if(eregi("^to:([^\r\n]*)",$l,$regs)) {
$to=trim($regs[1])." <$to>";
$l="";
}
// Проверяем заголовок Subject. В некоторых верcиях PHP
// передача пустого второго параметра в функцию mail()
// приводит к нежелательным последствиям. Указывая в заголовке
// значение Subject из письма, мы решаем проблему.
if(eregi("^subject:([^\r\n]*)",$l,$regs)) {
$subject=trim($regs[1]);
}
}
// Нет заголовка Content-type — добавляем его в конец.
if(!$HasContType) $Lines[]="Content-type: text/plain; $chs";
// Соединяем строки опять вместе.
$head=ereg_Replace("\n\n+","\n",join("\n",$Lines));
// Посылаем письмо.
$Result=@mail($to,$subject,$msg,$head)!=0;
// В Windows параллельно ведем журнал писем (для отладки).
if(getenv("COMSPEC")) {
if(!@is_dir("debug")) mkdir("debug",0755);
$f=fopen("debug/_debug_mail.txt","a+");
fputs($f,"> to: $to\n");
fputs($f,"$head\n--------\n");
fputs($f,"$msg\n-----------------------------------------\n\n");
fclose($f);
}
return $Result;
}
// Функция PostMail() "разворачивает" шаблон $msg, делая доступным для
// него переменные из массива $Vars (см. описание функций
// ExpandTemplate() и ExpandFile()). Затем она переводит результирующий
// текст в кодировку, заданную в $encTo (сам текст при этом
// рассматривается в кодировке $encFrom), и посылает его по электронной
// почте по адресу $to. Если строка $msg начинается с префикса
// file:, за которым следует имя файла, то шаблон письма загружается из
// этого файла при помощи ExpandFile(). В противном случае в качестве
// шаблона рассматривается сам параметр $msg.
function PostMail($to,$msg,$encTo=DefaultCode,
$Vars=false,$encFrom=DefaultCode)
{ if(eregi("^file:(.*)(\n|\$)",$msg,$P))
$Text=ExpandFile(trim($P[1]),$Vars);
else
$Text=ExpandTemplate($msg,$Vars);
// Посылаем письмо.
return SendMail($to,$Text,$encTo,$encFrom);
}
?>
Отличительной особенностью функции EncodeString() (а также всех остальных почтовых функций) является то, что она умеет перекодировать текст в транслит.
Термин "транслит" (сокращение от "транслитерация") означает такую кодировку кириллицы, при которой все "русские" буквы контекстно заменяются на записанные в соответствии с английской транскрипцией. Например, vot stroka, zapisannaya translitom. Эта кодировка особенно полезна для пользователей Unix, которые забыли установить у себя "русскую" таблицу символов.