ISO-2022-JPエンコードのメールをPEAR::Mailで送る

基本的には、以下のコードでうまくいきます。(ファイルはもちろんUTF-8で保存してください。)

<?php
require_once "Mail.php";
require_once "Mail/mime.php";

$to = "to@example.com";

$mime = new Mail_Mime();
$mime->setTxtBody("ほんぶん");
$body = $mime->get(array(
	"head_charset" => 'UTF-8',
	"text_charset" => 'UTF-8',
));
$header = $mime->headers(array(
	"To" => $to,
	"From" => "ふろむ <from@example.com>",
	"Subject" => "さぶじぇくと",
));
$mail = Mail::factory();
$mail->send($to, $header, $body);

ただ、古いメーラを使っているユーザは、UTF-8なメールが扱えない。イマドキ、IE6並みに時代遅れなのではあるが、ユーザからのクレームにいちいち対応したくなければ、あと10年くらいはISO-2022-JPで送るのが無難かも。で、こうやってみる。

<?php
require_once "Mail.php";
require_once "Mail/mime.php";

$to = "to@example.com";

$mime = new Mail_Mime();
$mime->setTxtBody(mb_convert_encoding("ほんぶん", "ISO-2022-JP", "UTF-8"));
$body = $mime->get(array(
	"head_charset" => 'ISO-2022-JP',
	"text_charset" => 'ISO-2022-JP',
));
$header = $mime->headers(array(
	"To" => $to,
	"From" => mb_convert_encoding("ふろむ <from@example.com>", "ISO-2022-JP", "UTF-8"),
	"Subject" => mb_convert_encoding("さぶじぇくと", "ISO-2022-JP", "UTF-8"),
));
$mail = Mail::factory();
$mail->send($to, $header, $body);

しかし、Mail_Mimeの実装の都合上、上のコードでは、うまくいかない。

ソースを見てみると、0x80-0xffの文字だけをQエンコードしているのだが、ISO-2022-JPの文字列は0x00-0x7fだけでできていて、かつ、0x00-0x7fの範囲にある制御文字を含んでいる。Bエンコードを使うことの方が一般的な気もするが、とりあえずQエンコードを固定的に使う仕様を変えないとすると、こんな感じ。

<?php
class Mail_mime_JP extends Mail_mime {
	function _encodeHeaders($input) {
		foreach ($input as $hdr_name => $hdr_value) {
			$hdr_value = mb_convert_encoding($hdr_value, 'UTF-8', $this->_build_params['head_charset']);
			preg_match_all('/(\w*[\x80-\xFF]+\w*)/', $hdr_value, $matches);
			foreach ($matches[1] as $value) {
				$replacement = preg_replace('/(.)/e',
					'"=" .
					strtoupper(dechex(ord("\1")))',
					mb_convert_encoding($value, $this->_build_params["head_charset"], "UTF-8"));
				$hdr_value = str_replace($value, '=?' .
					$this->_build_params['head_charset'] .
					'?Q?' . $replacement . '?=',
					$hdr_value);
			}
			$input[$hdr_name] = $hdr_value;
		}
		return $input;
	}
}

使い方について、念のために書いておくと、

require_once 'Mail_mime.php';

の後で上記のコードをrequireして、

$mime = new Mail_Mime();

$mime = new Mail_Mime_JP();

に変える、というだけ。

mb_encode_mimeheader()を使ってMail_Mimeの外でエンコードを済ませてしまうやり方は、あちこちに転がってるけれど、美しくない気がします。

美しくないと言えば、そもそもMail_Mimeが美しくない! get()は、引数として渡しているhead_charsetとかをインスタンスのプロパティに上書きセットするという副作用があって、ここでhead_charsetをセットしているからその後のheaders()が正しく動くようになっています。そのため、get()を呼び出さずにheaders()を呼び出すと、うまくいきません。