• 投稿日:2022年02月17日 12時52分46秒
  • 更新日:2022年03月03日 09時32分10秒
メール受信クラス

メール受信クラス

MailReceive

class MailReceive
{
    private $mailDir_receive =  "mail\\receive\\";
    public $imap;
    public $Id_all = array();
    public $errId_GetMailData = array();
    public $errId_InsertDate = array();
    public $errMsg = '';
    private $mailSetInfo = array("host"=>"",
                                 "port"=>"",
                                 "address"=>"",
                                 "pwd"=>"",
    );
    /**
     * コンストラクタ
     */
    function __construct($host, $port, $address, $pwd) { 

            $this->mailSetInfo["host"] = $host;
            $this->mailSetInfo["port"] = $port;
            $this->mailSetInfo["address"] = $address;
            $this->mailSetInfo["pwd"] = $pwd;

            $str_cnt = '{' . $host . ':' . $port . '/novalidate-cert/imap/ssl}' . "INBOX";
            $this->imap = imap_open($str_cnt, $address, $pwd);
    }
    /**
     * デストラクタ
     */
    function __destruct() {
            imap_close($this->imap);
    }
    /***
     * 受信処理実行
     */
    function main($criteria = "NEW"){

            if($this->imap == false){
                $this->errMsg("メールサーバへの接続に失敗しました。[" . $address . "]");
                return false;
            }

            mb_internal_encoding("UTF-8");

            try{
                    
                $messageIds = imap_search($this->imap ,$criteria); // 受信処理を実行
                if($messageIds == false){
                    return true; // 新着無し
                }

                // 取得したメール分ループを回して処理をする
                foreach((array)$messageIds as $messageId) {

                    $mainInfo_head = $this->Get_MailData($messageId); // 内容取得処理
                    if($mainInfo_head == false){
                        continue;
                    }

                    $mainInfo_body = $this->Get_bodyContent($messageId); // 本文パートを処理
                    if($mainInfo_body == false){
                        continue;
                    }

                    $res = $this->InsertDate($mainInfo_head, $mainInfo_body); // 取得内容の保存処理
                    if($res == false){
                        continue;
                    }

                }
            
                return true;

            } catch (Exception $e) {
                $this->errMsg("例外が発生しました。[" . $e->getMessage() . "]");
                return false;
            }
    }
    /**
     * 受信メールの内、IDを指定して以下を取得
     * From, To, CC, BCC, 日付, 件名
     */
    private function Get_MailData($messageId){

            // ヘッダ情報をセットする変数
            $mainInfo_ary = array(
                'message-id' => '',
                'from' => '',
                'to' => '',
                'cc' => '',
                'bcc' => '',
                'date' => '',
                'subject' => '',
                'plain' => '',
                'html' => '',
                'file-name' => array(),
            );

            try{
                
                $header = imap_headerinfo($this->imap ,$messageId);
                
                // メールキーの取得
                $mainInfo_ary['message-id'] = $this->Get_str_between_greter_and_less($header->message_id);
                array_push( $this->Id_all, $mainInfo_ary["message-id"]);

                //From: To: Message-Id:
                if(property_exists($header, "from")){
                    $mainInfo_ary['from'] = $this->Get_address_csv($header->from);
                }
                if(property_exists($header, "to")){
                    $mainInfo_ary['to'] = $this->Get_address_csv($header->to);
                }
                if(property_exists($header, "cc")){
                    $mainInfo_ary['cc'] = $this->Get_address_csv($header->cc);
                }
                if(property_exists($header, "bcc")){
                    $mainInfo_ary['bcc'] = $this->Get_address_csv($header->bcc);
                }
                
                // 日付を取得
                $mainInfo_ary["date"] = date("Y-m-d H:i:s", strtotime($header->udate));

                // 件名を取得
                if(property_exists($header, "subject")){
                    $mainInfo_ary["subject"] = mb_decode_mimeheader($header->subject);
                }

                return $mainInfo_ary;
                    
            } catch (Exception $e) {
                
                array_push( $this->errId_GetMailData, $mailData["message-id"]);
                return false;
            }

    }
    /**
     * ヘッダ取得:$strにそのまま入る+<>に囲まれた部分を$valに入れる
     */
    private function Get_str_between_greter_and_less($str){
            $val = '';
            $lpos = strpos($str, '<');
            $rpos = strpos($str, '>');
            if ($lpos !== false && $rpos !== false) {
                $lpos++;
                $val = mb_substr($str, $lpos, $rpos - $lpos);
            } else {
                $val = $str;
            }

            return $val;
    }
    /**
     * メールソースのメールアドレスをCSV形式に
     */
    private function Get_address_csv($adrs){

            $csv = "";
            foreach($adrs as $adr){
                
                $csv = $csv . "<" . $adr->mailbox . "@" . $adr->host . ">,";
            }
            $csv = mb_substr($csv , 0, -1); // 最後の文字を削除

            return  $csv;
    }
    /**
     * メールソースbodyの内容を取得または保存する
     */
    private function Get_bodyContent($messageId){

            // ヘッダ情報をセットする変数
            $resultAry = array(
                'plain' => '',
                'html' => '',
                'file-name' => array(),
            );

            try{

                // message-idを使用して添付ファイルなどを保存するディレクトリを作成
                $mailInfo_ID = str_replace( "\\" , "-" , $messageId);
                $mailInfo_ID = str_replace( "/" , "-" , $mailInfo_ID);
                $mailInfo_ID = str_replace( ":" , "-" , $mailInfo_ID);
                $mailInfo_ID = str_replace( "*" , "-" , $mailInfo_ID);
                $mailInfo_ID = str_replace( "?" , "-" , $mailInfo_ID);
                $mailInfo_ID = str_replace( "\"" , "-" , $mailInfo_ID);
                $mailInfo_ID = str_replace( "<" , "-" , $mailInfo_ID);
                $mailInfo_ID = str_replace( ">" , "-" , $mailInfo_ID);
                $mailInfo_ID = str_replace( "|" , "-" , $mailInfo_ID);
                $rec_dil = $this->mailDir_receive . $mailInfo_ID;
                if (!file_exists($rec_dil)) {
                    mkdir($rec_dil , 0777, true);
                }

                $structure = imap_fetchstructure($this->imap, $messageId); // bodyを取得
                $attachments = array();
                if(isset($structure->parts) && count($structure->parts)) {
                    // マルチパート処理

                    for($i = 0; $i < count($structure->parts); $i++) {

                        $attachments[$i] = array(
                            'is_attachment' => false,
                            'filename' => '',
                            'name' => '',
                            'attachment' => ''
                        );

                        if($structure->parts[$i]->subtype == "PLAIN" or $structure->parts[$i]->subtype == "HTML"){
                            // タイプはplain または html の場合
                            $body = imap_fetchbody($this->imap, $messageId, $i + 1, FT_INTERNAL); // 内容を取得
                            $charset = "auto";                    
                            $ary = (array)$structure->parts[$i]->parameters;
                            if(0 < count($ary)){
                                $charset = $ary[0]->value;
                            }
                                
                            $encoding = $structure->parts[$i]->encoding;
                            $str_body = $this->Get_bodytext_encoding($encoding, $charset , $body); // UTF-8の文字を返してもらう

                            if($structure->parts[$i]->subtype == "PLAIN"){
                                $resultAry[mb_strtolower($structure->parts[$i]->subtype)] = $str_body; //本文をセット
                            }else if ($structure->parts[$i]->subtype == "HTML"){
                                file_put_contents($rec_dil . "\\body.html", $str_body); // ファイル保存
                                $resultAry[mb_strtolower($structure->parts[$i]->subtype)] = "body.html";
                            }

                            continue; // 次へ
                        }

                        if($structure->parts[$i]->ifdparameters) {
                            foreach($structure->parts[$i]->dparameters as $object) {
                                if(strtolower($object->attribute) == 'filename') {
                                    $attachments[$i]['is_attachment'] = true;
                                    $attachments[$i]['filename'] = $object->value;
                                }
                            }
                        }

                        if($structure->parts[$i]->ifparameters) {
                            foreach($structure->parts[$i]->parameters as $object) {
                                if(strtolower($object->attribute) == 'name') {
                                    $attachments[$i]['is_attachment'] = true;
                                    $attachments[$i]['name'] = $object->value;
                                }
                            }
                        }

                        // filenameまたはnameがある場合は添付ファイルと判断
                        if($attachments[$i]['is_attachment']) {
                            $attachments[$i]['attachment'] = imap_fetchbody($this->imap, $messageId, $i+1);
                            if($structure->parts[$i]->encoding == 3) { // 3 = BASE64
                                $attachments[$i]['attachment'] = base64_decode($attachments[$i]['attachment']);
                            }
                            elseif($structure->parts[$i]->encoding == 4) { // 4 = QUOTED-PRINTABLE
                                $attachments[$i]['attachment'] = quoted_printable_decode($attachments[$i]['attachment']);
                            }
                        }
                    }

                    // ファイル保存用のディレクトリを作成
                    if(0 < count($attachments)){
                        foreach ($attachments as $key => $attachment) {

                            if($attachment['is_attachment'] == false){
                                continue;
                            }
                            $contents = $attachment['attachment'];

                            $filename = mb_decode_mimeheader($attachment['filename']);
                            array_push($resultAry["file-name"], $filename);
                            file_put_contents($rec_dil . "\\" . $filename, $contents);
                        }
                    }                

                }
                else{
                    // マルチパートではない場合
                    $charset = $structure->parameters[0]->value;
                    $encoding = $structure->encoding;
                    $body = imap_fetchbody($this->imap, $messageId, 1, FT_INTERNAL); // 本文パート1つ目を取得
                    $resultAry[mb_strtolower("PLAIN")] = $this->Get_bodytext_encoding($encoding, $charset , $body); // UTF-8の文字を返してもらう
                }

                return $resultAry;

            } catch (Exception $e) {
                echo($e);
                return false;
            }
    }
    /**
     * 本文を指定の文字コードで取得
     */
    private function Get_bodytext_encoding($encoding, $charset, $body ){

            //エンコード方式に従いデコードする
            switch ($encoding) {
                case 1://8bit
                    $body = imap_8bit($body);
                    break;
                case 3://Base64
                    $body = imap_base64($body);
                    break;
                case 4: //Quoted-Printable
                    $body = imap_qprint($body);
                    break;
                case 0: //7bit
                    //$body = imap_7bit($body);
                case 2: //Binary
                    //$body = imap_binary($body);
                case 5: //other
                default:
                    //7bitやBinaryは何もしない
            }

            return mb_convert_encoding($body, 'UTF-8', mb_strtoupper($charset));

    }    
    /**
     * 登録・保存処理
     */
    function InsertDate($mainInfo_head, $mainInfo_body){
                
            try{

                return true;

            } catch (Exception $e) {
                $this->errMsg("受信メッセージ登録処理に失敗しました。[" . $mainInfo_head["message-id"] . "]");
                return false;
            }
    }
}

説明

コンストラクタ

引数に接続情報をもらい、接続を試みます。

コンストラクタでは接続を試みるだけで、失敗しても何もしません。というか好きな値を返せないのでどうしようもありません。実行処理の最初に判定を行うか、インスタンス作成後判定(クラス変数imapがfalseなら失敗)するかする必要があります。

main関数

start関数ではメールの受信処理(imap_search関数)を実行し、受信したメール内容を1件ずつ処理(ここではGet_MailData関数とInsertDate関数)するということをしています。

また引数で受信方法を変更することが出来るようにしており、指定がなければ未受信のみ受信するようにしています。詳しくは受信方法の指定についてはPHPマニュアルサイトのimap_search関数を参照してください。

Get_MailData関数

引数で受け取ったUIDを持つメールのヘッダ情報を取得(imap_headerinfo関数)し配列に入れて返します。その過程で取得内容をゴニョゴニョする関数(Get_str_between_greter_and_less関数とGet_address_csv関数)を作ったりもしています。

また件名をデコードして取得するためにmb_decode_mimeheader関数なんてものも使用しています。あまり見慣れないかと思うので紹介しておきます。

Get_bodyContent関数

引数で受け取ったUIDを持つメールのボディ情報を取得(imap_fetchstructure関数)し、内容によって以下の通りの処理をします。マルチパートでない場合は「PLAIN」であるとして処理しています。

処理しているパートのタイプ 処理内容
PLAIN 「PLAIN」をキーとして連想配列にセットします。
HTML 受信フォルダのメッセージIDフォルダに「body.html」というファイル名で保存し、メールフォルダからのパスを「HTML」をキーとして連想配列にセットします。
その他 ファイル名を取得できた場合添付ファイルと判断し、受信フォルダのメッセージIDフォルダに保存する。その後メールフォルダからのパスをファイル名をキーとして連想配列にセットします。

ファイルへの保存があるため最初にフォルダ名を作成しており、禁則文字は”-“に置換するという処理もしています。

詳細

使い方

MailReceiveクラスのインスタンスを作成し、start関数を実行します。

$cls = new MailReceive('imap.gmail.com', 587, 'xxx@gmail.com', 'xxxxxx');
$cls->start();

最後に

メール受信処理について調べると結構古い情報ばかり引っかかる気がします。おかげでimap関数にたどり着くまで時間がかかりました。そもそもそんなに需要がないんだろうな。

だからこそ備忘録は大事と思い込んでます。誰かのお役に立てることを祈っています。

最後に

SPECIAL THANKS

  1. PHPでIMAP接続してメールを取得する -ハマログ 株式会社イーツー・インフォの社員ブログ-
  2. php - IMAPおよびPHPで添付ファイルサーバーを保存する -ITツールネット-
Laravelでリバースプロキシを使った場合のホスト名の指定方法
【Laravel】configファイルについて
Laravelを使って開発したものを本番環境へ
Laravelをインストールし開発環境を整備する
Laravelのコマンドを色々まとめ
UbuntuにNginx, MySQL, php をインストールする
Ubuntuに古いphp7.1をインストールする
KV(PC・SP)設定用カスタム設定コード