Loading ...

実際に実装する


この原稿は技術評論社発刊のWEB+DB Press Vol.18内特集2の「おいしさいっぱいのPHPライブラリ徹底活用! PEAR実践入門」用に書かれた原稿です. この原稿はのちに校正などで修正されている可能性や,現在の状態に則していない情報も含まれているかもしれませんが,ご了承ください.

設定値の作成

今回$configという配列にメーラとしての各種設定をしておきます. 設定は主に

  • メール送信関係設定($config[ 'smtp'])
  • メール受信関係設定($config[ 'pop3'])
  • メール表示などメーラとしての設定($config[ 'mua'])

です.下記をcommon.inc.phpに追記します.

common.inc.phpに追記する内容
/**
 * SMTPの設定
 */
//  SMTPサーバのアドレス
$config[ 'smtp'][ 'host'] = 'localhost';
//  SMTPサーバのポート番号
$config[ 'smtp'][ 'port'] = 25;
//  SMTP認証を使用するか
$config[ 'smtp'][ 'auth'] = false;
/**
 * POP3の設定
 */
//  POP3サーバのアドレス
$config[ 'pop3'][ 'host'] = 'localhost';
//  POP3サーバのポート番号
$config[ 'pop3'][ 'port'] = 110;
//  POP3認証のアカウント名
$config[ 'pop3'][ 'user'] = 'popuser';
//  POP3認証のパスワード
$config[ 'pop3'][ 'pass'] = 'poppass';
//  認証にAPOPを使用するかどうか
$config[ 'pop3'][ 'apop'] = false;
/**
 * メーラの設定
 */
//  自分のメールアドレス
$config[ 'mua'][ 'fromAddress'] = 'popuser@example.com';
//  メール一覧で表示する個数
$config[ 'mua'][ 'showList'] = 10;
//  ドメイン名が省略された場合に付加するドメイン名
$config[ 'mua'][ 'defaultDomain'] = 'example.com';
//  表示上(HTML出力上)の文字コード
$config[ 'mua'][ 'showCharSet'] = 'euc-jp';
//  メールに使用する文字コード
$config[ 'mua'][ 'mailCharSet'] = 'iso-2022-jp';
//  アップロード可能なファイルサイズ
$config[ 'mua'][ 'uploadMaxSize'] = 1000000;

Net_POP3クラスの拡張

Net_POP3関数を用いた処理に$configを意識した処理を行うようにクラスを継承したcMyPop3というクラスを作成します.このクラスは例えばlogin()メソッドでいちいちアカウント名などを指定しなくてもいいようにします.

cMyPop3クラス
class cMyPop3 extends Net_POP3 {
  var $config;
   /**
   *  コンストラクタ
   */
  function cMyPop3() {
    global $config;
    $this->config =& $config;
    parent::Net_POP3();
  }
  /**
   *  接続処理を行う
   */
  function connect() {
    $config =& $this->config[ 'pop3'];
    return parent::connect( $config[ 'host'], $config[ 'port'], $config[ 'apop']);
  }
  /**
   *  認証を行う
   */
  function login() {
    $config =& $this->config[ 'pop3'];
    return parent::login( $config[ 'user'], $config[ 'pass'], $config[ 'apop']);
  }
}

新しく作成したcMyPop3クラスを用いるとメール一覧の取得は下記のようになります.

cMyPop3クラスの使用例
<?php
require_once 'common.inc.php';
$pop3 =& new cMyPop3;
$pop3->connect();
$pop3->login();
$list = $pop3->getListing();
$pop3->disconnect();
print_r( $list);
?>

ユーティリティ関数の作成

まず,値の連結関数を作成します. メソッドgetParsedHeaders()はヘッダフィールド毎に値を格納した連想配列を返しますが,複数の値が存在するフィールド(例えばRecievedなど)は配列にして返します. しかし,配列も表示上は文字列に変換する必要があるので,配列から文字列に変換する簡単な関数を用意します.

ヘッダの値を連結する関数
function mail_header_join( $value, $char = ' ') {
  return is_array( $value)? implode( $char, $value): $value;
}

下記が使用例です,

mail_header_joinの使用例
$headers = $pop3->getParsedHeaders( $msg_id);
$to = mail_header_join( $headers[ 'To'], ',');

日本語メールは通常ISO-2022-JP(JIS)を使用します.そしてメールヘッダはMIMEという形式でエンコードします.しかしメーラによってはその手続きをとらずに生のISO-2022-JPを記述するものがありますので,今回はその対策も簡単に入れてみました.

メールヘッダのMIMEを解析をし,文字コード変換を行う関数
/**
 *  配列化したメールヘッダの値を解析し,MIMEデコードする
 */
function mb_decode_mimeheaders( $headers, $toCharSet) {
  foreach ( $headers as $name=>$value) {
    if ( is_array( $value)) {
      array_walk( $value, 'mb_decode_mimeheaders_callback', $toCharSet);
    } else {
      mb_decode_mimeheaders_callback( $value, $toCharSet);
    }
    $headers[ $name] = $value;
  }
  return $headers;
}
/**
 *  MIMEヘッダの値をデコードする.生ISO-2022-JPもデコードする
 */
function mb_decode_mimeheaders_callback( &$value, $toCharSet) {
  $value = mb_decode_mimeheader( $value);
  if ( strstr( strtoupper( $value), "\x1B\$B")) {
    //  ISO-2022-JPの開始シーケンスなので変換(つまり生JIS)
    $value = mb_convert_encoding( $value, $toCharSet, 'iso-2022-jp');
  }
}

下記がこの関数を利用したMIMEデコードの例です.

mb_decode_mimeheadersの使用例
$headers = $pop3->getParsedHeaders( $msg_id);
//  MIMEデコードを行い,マルチバイト文字はEUC-JPにする
$headers = mb_decode_mimeheaders( $headers, 'euc-jp');

次に日付表記を日本語表記にする関数を追加します.

mail_header_dateの例
/**
 *  メールヘッダの日付を日本語表記にする
 */
function mail_header_date( $value) {
 return $value !='' ?
  strftime( "%Y年%m月%d日 %H:%M:%S", strtotime(  $value)): '不明';
}

メール一覧表示

メールの一覧表示はNet_POP3のgetParsedHeaders()メソッドの結果から「Subject」「From」「To」「Date」を抜き出してテーブルで表示します. この時に「From」と「To」に関してはコメントが日本語で記述されている可能性がありますから先ほど用意した関数を交えて出力します.

メール一覧表示の例
$muaConfig &= $config[ 'mua'];
for ( $i = $start + 1; $i <= $end; $i++) {
  $headers =
   mb_decode_mimeheaders( $pop3->getParsedHeaders( $i), $muaConfig[ 'showCharSet']);
  $subject = htmlspecialchars( mail_header_date( $headers[ 'Subject']), ' ');
  foreach ( array( 'To', 'From') as $name) {
    $$name = isset( $headers[ $name])?
     htmlspecialchars( mail_header_join( $headers[ $name], ',')): '';
  }
  $date = htmlspecialchars( mail_header_date( $headers[ 'Date']));
  print <<<EOD
<tr>
 <td><a href="$PHP_SELF?showMailIndex=$start&showMail=$i">$i</a></td>
 <td>$subject</td><td>$To</td><td>$From</td><td>$date</td>
</tr>
EOD;
}

メールアドレスの解析

メールアドレスの解析はMail_RFC822パッケージで容易に出来ます. Mail_RFC822クラスにはparseAddressList()というメソッドがあり,メールアドレスの羅列を調べて下記のようなオブジェクトをインスタンスして返します.

$address->commentコメント
$address->mailboxメールアドレスの「@」の左側
$address->hostメールアドレスの「@」の右側

また,第3引数をtrueとすることで複数のメールアドレスを配列で返してきますので,それをforeach文などで処理をすれば総てのメールアドレス群を処理することが出来ます.

下記は受信したメールの(例えばTo:フィールド)からコメント部分を抜き出す関数です. これもcommon.inc.phpに保存します.

メールアドレス群の記述からコメントを削除する
/**
 *  メールアドレスのリストからコメントを削除し,メールアドレス一覧だけにする
 */
function exclude_mail_comment( $addressList, $defaultDomain) {
  $parsedAddress = Mail_RFC822::parseAddressList( $addressList, $defaultDomain, true);
  $newList = array();
  foreach ( $parsedAddress as $info) {
    $newList[] = "{$info->mailbox}@{$info->host}";
  }
  return implode( ',', $newList);
}

メール表示

メールの表示はヘッダ・本文で分けて行います. 簡易的にはヘッダはgetParsedHeader()で,本文はgetBody()メソッドを使って取得した内容を利用します.

メール表示の例
$headers = $pop3->getParsedHeaders( $msg_id);
$body = $pop3->getBody( $msg_id);
print <<<EOD
Subject: {$headers[ 'Subject']}<br>
To: {$headers[ 'To']}<br>
From: {$headers[ 'From']}<br>
<br>
$body
EOD;

このままでは不等号の対処などを行っていないのでブラウザから「<こういう内容>」が表示されないなど,いくつかの問題も出てきます.

日本語の対処(メールの文字コード・表示の文字コード)

次にヘッダと本文の日本語化をとブラウザ表示対策をしてみましょう.

日本語対応の例
$headers = $pop3->getParsedHeaders( $msg_id)
//  ヘッダのMIMEをデコードする
$headers( mb_decode_mimeheaders( $headers);
$Subject = htmlspecialchars( $headers[ 'Subject']);
foreach ( array( 'To', 'From') as $name) {
  $$name = isset( $headers[ $name])?
   htmlspecialchars( mail_header_join( $headers[ $name], ',')): '';
}
$body = $pop3->getBody( $msg_id);
//  ヘッダから本文の文字コードを調べる
if ( eregi( "charset=['\"]?([a-z0-9\-]+)['\"]?", $headers[ 'Content-Type'], $eregarr)) {
  $content_type = $eregarr[ 1];
  $body = mb_convert_encoding( $body, 'euc-jp', $content_type);
}
print <<<EOD
Subject: {$headers[ 'Subject']}<br>
To: {$headers[ 'To']}<br>
From: {$headers[ 'From']}<br>
<br>
$body
EOD;

添付ファイルの対処

メールの添付ファイルは送信前にある形式でエンコードされ,メールの本文として保存してから送信します. その為メール表示時にはメールを解析し,本文テキストと添付を分ける必要があります. この処理には既に紹介したMail_mimeDecodeパッケージを利用します.

次に

  • 改めて本文は添付が表示されないようにする
  • 添付はファイル名のみ表示し,リンクを作成し,リンク先(download_attach.php)で表示する.

という処理に変更します.また,Net_POP3のgetParsedHeaders()メソッドは連想配列のキーはメールヘッダのそのまま(例えば['Subject'])でしたが,Mail_mimeDecodeだと総て英小文字(例えば['subject'])になる点に注意してください.

上記を踏まえ,変更点をピックアップしてみます.

添付ファイル名をリンク付きで表示する例
global $config;
$showCharSet = $config[ 'mua'][ 'showCharSet'];
$mailCharSet = $config[ 'mua'][ 'mailCharSet']; $params['include_bodies'] = true;
$params['decode_bodies']  = false;
$params['decode_headers'] = false;
$params['input'] = $pop3->getMsg( $msg_id);
$params['crlf'] = "\r\n";
// MIMEメールのデコード処理
$mailObj = Mail_mimeDecode::decode( $params);
// MIMEヘッダのデコード処理
$headers = mb_decode_mimeheaders( $mailObj->headers, $showCharSet);
$subject = htmlspecialchars( $headers[ 'subject']);
foreach ( array( 'to', 'cc', 'from') as $name) {
  $$name = isset( $headers[ $name])?
   htmlspecialchars( mail_header_join( $headers[ $name], ',')): '';
}
if ( isset( $mailObj->parts)) {
  // MIMEパートがあるメール
  $body = '';
  $attachs = array();
  foreach ( $mailObj->parts as $index=>$parts) {
    if ( $parts->ctype_primary == 'text') {
      $charSet = $parts->ctype_parameters[ 'charset'];
      $body .= mb_convert_encoding( $parts->body, $showCharSet, $charSet);
    } else {
      $attachs[] = <<<EOD
<a href="download_attach.php?showMail=$msg_id&attach=$index" target="_new">{$parts->d_parameters[\
'filename']}</a>
EOD;
    } 
  }
  $attach = implode( ' ', $attachs);
} else {
  //  通常のメール
  $contentType = $headers[ 'content-type'];
  if ( eregi( "charset=['\"]?([a-z0-9\-]+)['\"]?", $contentType, $eregarr)) {      $content_type = $eregarr[ 1];
    $body = mb_convert_encoding( $mailObj->body, 'euc-jp', $content_type);
  } else {
    $body = $mailObj->body;
  }
  $attach = '';
}
print <<<EOD
Attach: $attach
EOD;

添付ファイルのダウンロード

添付ファイルのダウンロードは先ほどのメール表示から得たメッセージ番号$msg_idとMIMEパート番号$attachを元に,先ほどと同じくMail_mimeDecodeパッケージにて行います. 先ほどは添付ファイル名のみ取り出して処理をしましたが,今回は総ての情報を用いてファイル名,ファイルの内容,MIMEタイプなどの処理を行います.

添付ファイルのダウンロード
<?php
require_once 'common.inc.php';

$pop3 = new cMyPOP3;
$pop3->connect();
$pop3->login();
$mail = isset( $_GET[ 'showMail'])? $pop3->getMsg( $_GET[ 'showMail']): '';
$pop3->disconnect();

if ( $mail != '') {
  //  メールの取得が出来た場合のみ
  $params['include_bodies'] = true;
  $params['decode_bodies']  = false;
  $params['decode_headers'] = true;
  $params['input'] = $mail;
  $structure = Mail_mimeDecode::decode( $params);
  if ( isset( $structure->parts[ $_GET[ 'attach']])) {
    // MIMEパート情報を取り出して処理
    $parts = $structure->parts[ $_GET[ 'attach']];
    $content_type = "{$parts->ctype_primary}/{$parts->ctype_secondary}";
    $name = isset( $parts->d_parameters[ 'filename'])? $parts->d_parameters[ 'filename']: '';
    //  HTTPヘッダを出力
    header( "Content-Type: $content_type;");
    header( "Content-Disposition: filename=\"$name\"");
    switch ( strtolower( $parts->headers[ 'content-transfer-encoding'])) {
    //  BASE64の場合の処理
    case 'base64':
      print base64_decode( $parts->body);
      break;
    //  Print-Quotableの場合の処理
    case 'print-quotable':
      print quoted_printable_decode( $parts->body);
      break;
    //  その他の場合の処理
    default:
      print $parts->body;
    }
  }  
}

メール作成部

次にメールの作成画面を作成します.メール作成時は通常のHTMLフォームを作成します.

メール作成部の例
<form action="create_mail.php" enctype="multipart/form-data" method="post">
<input type="hidden" name="MAX_FILE_SIZE" value="$uploadMaxSize">
件名: <input type="text" name="Subject" size="40"><br>
宛先: <input type="text" name="To" size="40"><br>
本文:<br>
<textarea name="body" cols="80" rows="20"></textarea><br>
<input type="file" name="attach"><br>
<input type="submit" value="送信">
</form>

添付ファイルなどはHTTPアップロードを使用しますので,MAX_FILE_SIZEをPOSTすることと,php.iniの設定,Red Hat 9を利用している人は/etc/httpd/conf.d/php.confの最大POSTサイズを調べておく必要があります.

メール送信部

メール送信はMailパッケージの紹介の通りの処理が基本になります. 日本語メールや添付ファイルは作成するメールヘッダやメールボディを加工していくことになります.

必須ヘッダの対処

現在のメールについて最新の規格はRFC2822になりますが,入力フォーム以外の必須ヘッダに「Message-Id」と「Date」フィールドがあります.前者はインターネット上で一意な文字列を,後者はメール送信時の日付を指定する必要があります.

これらを作成する関数を用意しましょう.

Message-Idフィールドの値を生成する関数
/**
 *  Message-Idフィールドの値を生成する
 */
function create_message_id( $baseString) {  return sprintf( "%s.%s", md5( microtime()), $baseString);
}
Dateフィールドの値を生成する関数
/**
 *  Dateフィールドの値を生成する
 */
function create_date( $time = null) {  $time = $time === null? time(): $time;
  return date( 'r', $time);
}

日本語メールの対処

日本語メールは通常文字コードをISO-2022-JPにします.メールヘッダはmbstringモジュールのmb_encode_mimeheader()関数を利用するれば容易に処理が出来ます.*1

mb_encode_mime_headerの使用例
$subject = mb_encode_mime_header( $_POST[ 'Subject']);

添付ファイルの追加

添付ファイルの追加処理はMail_mimePartパッケージを用います. 添付ファイルなど,メール本文以外のものを付加するときは通常テキスト文([ このメールはMIMEメッセージです」といった文章)を挿入します. そしてメールに挿入したい本文テキストは添付パートとして追加します.

メールの構成イメージ
ヘッダ
本文This is a multi-part message in MIME format.(MIMEメールだよ)
添付メールの本文
添付ファイル

Mail_mimePartパッケージを使用すると,下記のように処理をすれば上記は処理可能です.

添付対応の例
//  前のページに<input type="file" name="attach">とあるとする
$attach = $_FILES[ 'attach'];
$params = array();
$params[ 'content_type'] = 'multipart/mixed';
$mimePart = new Mail_mimePart( '', $params);
//  本文の追加
$params[ "content_type"] = "text/plain; charset=$mailCharSet";
$params[ "encoding"]  = "7bit";
$mimePart->addSubPart( $body, $params);
//  添付の追加
//  ファイルの読み込み
$file = file_get_contents( $attach[ 'tmp_name']);
$params[ "content_type"] = $attach[ "type"];
$params[ "encoding"]  = "base64";
$params[ "disposition"]  = "attachment";
$params[ "dfilename"] = $attach[ "name"];
$mimePart->addSubPart( $file, $params);
//  エンコード処理
$mail = $mimePart->encode();
$body = "This is a multi-part message in MIME format.\n\n{$mail[ 'body']}";
$headers = array_merge( $headers, $mail[ 'headers']);

この例でfile_get_contents()関数を用いていますが,PHP 4.3.0未満はではこの関数は存在しないので,下記関数で代用してください.

file_get_contentsの代替関数
if ( !function_extists( 'file_get_contents')) {
  function file_get_contents( $filename, $use_include_path = 0) {
    $fp = fopen( $pathname, "rb", $use_include_path);
    if ( $fp !== false) {
      return fread( $fp, filesize( $pathname));
    } else {
      return false;
    }
  }
}


*1 ただしmb_encode_mimeheader()はエンコード手続きが不十分で,メーラによっては読めないメールを作成することがあるようです
リロード   新規 編集 凍結 差分 添付 複製 改名   トップ 一覧 検索 最終更新 バックアップ   ヘルプ   最終更新のRSS
最終修正日時: Mon, 17 Nov 2003 13:22:06 JST (3001d)
文字数(HTML): 13409
文字数(Wiki): 13028
人気ブログランキング - よくきた wiki