PHPでIMAPを使う
以前Roundcubeを設定したことがありましたが、このアプリケーションの一部はPHPでのIMAPの実装といえるでしょう。
今回はそのようなIMAPを自身のPHPのコーディングに加えてみます。
例示に使う環境は64bitのDebian11です。
IMAP拡張ライブラリ
PHPには、IMAP拡張ライブラリが用意されています。
DebianでAPTでインストールする場合は特に問題ないと思いますが、そうでない場合に注意が必要なのは、IMAPモジュールはNTS(Non Thread Safe)なので、スレッドセーフな環境で使うとトラブルの元です。
環境を確認するには、php -v でNTSの文字があるかで確認をします。NTSとあればNon Thread Safe版ですので、IMAPモジュールとの齟齬はありません。
$ php -v PHP 7.4.33 (cli) (built: Feb 22 2023 20:07:47) ( NTS ) Copyright (c) The PHP Group Zend Engine v3.4.0, Copyright (c) Zend Technologies with Zend OPcache v7.4.33, Copyright (c), by Zend Technologies
IMAPライブラリの追加は、PHPのバージョンが7でよければAPTで行うことができます。
ビルドをする場合は configure で --with-imap を指定するのですが、別途 c-client が必要になります。c-clientは C言語で記述されたPOP3やIMAP用のライブラリです。これは libc-client2007e-dev として取得できます(2007eの部分は可変)。
--with-imapオプションに連動して--with-kerberos(ケルベロス認証)と、--with-imap-sslの指定も求められます。
ケルベロス認証はlibkrb5-devとしてAPTで取得可能です。
# apt upddate ... #apt install php-imap ...
基本的なコード
基本的な使い方は次のようになります。
sample1.php
// サーバーの接続情報
$imap_server = '{imap.example.jp:993/imap/ssl}INBOX';
$username = 'user';
$password = 'password';
$imap_connection = imap_open($imap_server, $username, $password);
// メールボックス内の未読メールを検索 ALL だと全件
$emails = imap_search($imap_connection, 'UNSEEN');
if ($emails) {
// メールが見つかった場合、ループして各メールを処理
foreach ($emails as $email_number) {
// メールのヘッダ情報を取得
$headers = imap_headerinfo($imap_connection, $email_number);
// メールの本文を取得
$body = imap_body($imap_connection, $email_number);
// メールを既読にする
imap_setflag_full($imap_connection, $email_number, "\\Seen");
}
}
// IMAPサーバーから切断
imap_close($imap_connection);
imap_open で接続を開始しますが、この時の第1引数に渡す文字列は次のようになります。
アドレスとポートはいいと思います。オプションは決められた文字列があります。先の例では imap(他にpop3なども指定可能です)と ssl を指定しています。
STARTTLSで接続する際は、sslの部分を tls とします。また、ssl 指定時にはデフォルトでサーバーの証明書を検証しますが、検証しない場合は novalidate-cert を指定します。
プレーンテキスト認証(sslとは別)を禁止したい場合は、secure オプションも存在します。
メールボックス名はデフォルトでは INBOX となります。これはカレントユーザーの個人メールボックスを意味する特殊な予約語です。
imap_search ではメールボックスからメールを抽出する作業をしています。この第2引数に UNSEEN を設定する事で未読メールを抽出します。全件抽出の場合は ALL とします。
メールのヘッダを imap_headerinfo で、ボディを imap_body で読み込んだ後は、imap_setflag_full に \\Seen フラグを渡してメールを既読にしています。
マルチバイト文字
先の処理ではBase64やQuoted Printableでエンコーディングされた文字列がそのまま読み出されてしまいます。また、マルチパートにも対応していません。
それらに対応するにはループ内でメッセージを処理する際に imap_fetchstructure を使って次のようにします。
sample2.php
...
foreach ($emails as $email_number) {
// メールのヘッダ情報を取得
$headers = imap_headerinfo($imap_connection, $email_number);
// タイトルを取得
$title = "";
foreach(imap_mime_header_decode($headers->subject) as $v) {
$title.=$v->text;
}
// メッセージの構造を取得
$structure = imap_fetchstructure($imap_connection, $email_number);
// メール本文の取得
$message = '';
if (isset($structure->parts)) {
// マルチパートの場合、パートごとに処理
foreach ($structure->parts as $part_number => $part) {
if ($part->type == 0) {
// テキストメールの場合
$message = imap_fetchbody($imap_connection, $email_number, $part_number+1);
if ($part->encoding == 1) {
// 8bitエンコーディングの場合、UTF-8に変換
$message = imap_utf8($message);
} elseif ($part->encoding == 4) {
// Quoted Printableエンコーディングの場合
$message = quoted_printable_decode($message);
} elseif ($part->encoding == 3) {
// Base64エンコーディングの場合
$message = base64_decode($message);
}
} elseif ($part->type == 3) {
// 添付ファイルの場合
}
}
} else {
// シングルパートの場合
$message = imap_body($imap_connection, $email_number);
if ($structure->encoding == 1) {
// 8bitエンコーディングの場合、UTF-8に変換
$message = imap_utf8($message);
} elseif ($structure->encoding == 4) {
// Quoted Printableエンコーディングの場合、デコード
$message = quoted_printable_decode($message);
} elseif ($structure->encoding == 3) {
// Base64エンコーディングの場合、デコード
$message = base64_decode($message);
}
}
// メールを既読にする
imap_setflag_full($imap_connection, $email_number, "\\Seen");
}
...
ここでの imap_fetchstructureの処理は、上記の例は少し乱暴な書き方をしています。
$partをループで回す部分で、typeプロパティでデータ種を判定していますが、この値は公式ページに次のように載っているように、本来定数で扱うべきです。
- 0:TYPETEXT
- 1:TYPEMULTIPART
- 2:TYPEMESSAGE
- 3:TYPEAPPLICATION
- 4:TYPEAUDIO
- 5:TYPEIMAGE
- 6:TYPEVIDEO
- 7:TYPEMODEL
- 8:TYPEOTHER
また、上記のコードではマルチパート部がネストしていた場合を考慮していません。つまり、type=1の値の時は何も処理していません。また、多くの添付ファイルが、Applicationであるという前提でtype=3を添付ファイルとして保存しています。
エンコーディング部も定数が用意されていますので、参考までに書き残しておきます。
- 0:ENC7BIT
- 1:ENC8BIT
- 2:ENCBINARY
- 3:ENCBASE64
- 4:ENCQUOTEDPRINTABLE
- 5:ENCOTHER
サーバー内のCAの証明書情報が古くてエラーになる場合は、「 update-ca-certificates 」を実行します。
またこの imap_open(ライブラリ内の他のものもそうなのかもしれません)で発生する NOTICE や ERROR は @をつけてもブラウザに出力されてしまうようなので、本番環境では「 error_reporting(0); 」を設定しましょう。