<?php

// (Shirai187): 電子メールでブログを投稿する機能 (2010/05/12)
// function mail_receive($user, $pwd)は
// 以下のページを参考にしました．
// http://shoji.blog1.fc2.com/blog-entry-31.html
// 電子メールのデコードに使用するコード（CodeZineフォルダ以下）は，
// 以下のURLのWebページよりダウンロードしたものです．
// 「GPS携帯を使った口コミサイト構築 - PHPによるメールの受信処理 -」
// http://codezine.jp/article/detail/4749
// POP3の応答に関しては以下のページを参照
// http://research.nii.ac.jp/~ichiro/syspro98/pop.html

if (empty($CFG)) require_once('../../../config.php');
require_once($CFG->dirroot.'/blog/lib.php');
@include_once('Mail.php');
@include_once('Mail/mimeDecode.php');
if (class_exists('Mail_mimeDecode')) {
    include_once('CodeZine/MailDecoder.class.php');
}

class mail2blog {

    ///////////////////
    // メールのデコード
    ///////////////////

    // メールを受信して解析すると同時に削除する
    function check_mail_and_delete()
    {
        return $this->check_mail(true);
    }

    // メールを受信して解析するのみ（削除しない）
    function check_mail_only()
    {
        return $this->check_mail(false);
    }

    // メールを受信して解析する
    // $delete == trueの場合は同時に削除する
    function check_mail($delete = false)
    {
        global $fsCFG;

        // パラメータ設定
        $params = array();
        $params['include_bodies'] = true;
        $params['decode_bodies']  = true;
        $params['decode_headers'] = true;

        // 全受信
        $data = array();
        $msgs = $this->mail_receive($delete);
        if (!$msgs) return $data;
        $no = 1;
        if (count($msgs) > 0) {
            foreach ($msgs as $msg) {
                $entry = null;
                // MailDecoderクラスでメールデータを解析
// Assigning the return value of new に対して（2016.12.26 T.Shirai）
//              $decoder     = & new MailDecoder($msg);
                $decoder     = new MailDecoder($msg);
                // メール件名を取得
                $entry->subject     = $decoder->getDecodedHeader('subject');
                // メール送信日を取得
                $entry->date        = $decoder->getDecodedHeader('date');
                // メール本文を取得
                $entry->body        = $decoder->getBodyText();
                // 送信元メールアドレスを取得
                $entry->fromAddress = $decoder->getFromAddress();
                // 添付ファイルを取得
                $entry->attachments = $decoder->getAttach();

                $data[] = $entry;
                $no++;
            }
        }
        return $data;
    }

    //////////////////////////////
    // メールの全受信（および削除）
    //////////////////////////////

    // メールサーバへのログインのチェックのみ
    // 失敗した場合はfalseを返す
    function m2b_mail_login_check()
    {
        if (!($fp = $this->m2b_mail_login())) return false;
        fputs($fp, "QUIT\r\n");
        fclose($fp);
        return true;
    }

    // メールサーバへのログインチェック
    // 失敗した場合はソケットを閉じてfalseを返す
    function m2b_mail_login()
    {
        global $fsCFG;

        $host = $fsCFG->m2b_host;
        $port = $fsCFG->m2b_port;
        $user = $fsCFG->m2b_user;
        $pwd  = $fsCFG->m2b_pass;
        $fp = @fsockopen($host, $port);
        if (!$fp) return false;
        // ログイン
        $line = fgets($fp, 512);    
        fputs($fp, "USER $user\r\n");   // USER名
        $line = fgets($fp, 512);
        fputs($fp, "PASS $pwd\r\n");    // パスワード
        $line = fgets($fp, 512);
        if( !eregi("OK", $line) )       // ログイン失敗
        {
            fclose($fp);
            return false;
        }
        return $fp;
    }

    // メールを受信して配列に格納して返すと同時に削除する
    function mail_receive_and_delete()
    {
        return $this->mail_receive(true);
    }

    // メールを受信して配列に格納して返すのみ（削除しない）
    function mail_receive_only()
    {
        return $this->mail_receive(false);
    }

    // メールを受信して配列に格納して返す関数
    // $delete == trueならば同時に削除する
    // メールボックス内にメールが無いならばfalseを返す
    // 事前にログインチェックはしておく．もしログインに失敗した場合はdieする．
    function mail_receive($delete = false)
    {
        global $fsCFG;

        $this->log_message('<< メールの一括受信 >>');
        $this->log_message('処理開始', true);
        if (!($fp = $this->m2b_mail_login())) {
            $this->error("メールサーバへのログインに失敗しました．", true);
            die;
        }
        // メールボックス内の未受信メール数の確認（STATA）
        fputs($fp, "STAT\r\n");
        $line = fgets($fp, 512);
        list($stat, $num, $size) = explode(' ', $line);
        if( 0+$num == 0 ) {     // 未読メールは無い
            fclose($fp);
            return false;
        }
        // それぞれ受信して、配列に納める
        for($id=1; $id <= $num; $id++)
        {
            fputs($fp, "RETR $id\r\n");
            $line = fgets($fp);
            $msg[$id] = "";
            while (1) {
                $line = fgets($fp, 512);
                // メール終端を検知（'.'+CRLFの行）したらバッファに入れない
                if (eregi("^\.\r?\n", $line)) break;
                $msg[$id] .= $line;
            }
            // 受信したメールを削除
            if ($delete) {
                fputs($fp, "DELE $id\r\n");
                $this->log_message("メールを削除しました：$id");
                $line = fgets($fp, 512);
            }
        }
        // 終了処理
        fputs($fp, "QUIT\r\n");
        fclose($fp);
        $this->log_message("$id 件のメールを受信しました", true);
        $this->log_message("<< 受信処理完了 >>\n");
        return $msg;
    }

    // メールアドレス（セカンドメールアドレスも含めて）からユーザIDを取得する関数
    // 既にユーザIDの候補がある場合は$candidateで渡すことができる．
    // セカンドメールアドレスの場合は重複登録を禁止していないため．
    function search_uid_by_emailaddress($address, $candidate = '')
    {
        if (empty($address)) return false;
        // ユーザプロファイルを探索
        if (($data = fs_get_record('user', 'email', $address))) return $data->id;

        // セカンドメールアドレスを探索
        if (!($users = get_uids_basedon_userProfileField_value('secondaddress'))) return false;
        // 基本のプロファイルと違って電子メールアドレスの重複を禁止している訳では無いので，
        // 複数のユーザがマッチしてしまう可能性がある
        foreach ($users as $user) {
            $userid = $user->userid;
            $value  = $user->data;
            if ($value == $address) {
                if (!empty($candidate)) {
                    if ($userid == $candidate) return $userid;
                } else return $userid;
            }
        }
        return false;
    }

    // 題名や本文に含まれるユーザ識別コードを探し，ユーザ識別ID（ユーザ名含む）を返す．
    // ユーザプロファイルフィールドのチェックも必要な場合（コードＣ）は行う．
    function get_uid_by_idstring($entry)
    {
        global $fsCFG;

        // 認証方式１の場合は電子メールアドレスのみを信じるのでこの関数が呼ばれることはない
        if ($fsCFG->m2b_confirmlevel == 1) return false;

        $sep = $fsCFG->m2b_codeseparator;
        // 探索対象は題名と本文
        $targets = array();
        $targets[] = $entry->subject;
        $targets[] = $entry->body;
        foreach ($targets as $target) {
            while (mb_strlen($target) > 0) {
                // まず一つ目の識別子を捜す
                if (($pos = mb_strpos($target, $sep)) === false) break;
                $target = mb_substr($target, $pos + mb_strlen($sep));
                // 二つ目の識別子を捜す
                if (($pos = mb_strpos($target, $sep)) === false) break;
                // 間に挟まれている文字列をユーザ識別コードの情報として抜き出す
                $str = mb_substr($target, 0, $pos);
                $str = trim($str, ' ');
                switch ($fsCFG->m2b_confirmlevel) {
                    case 2 :    // コードＢ（ユーザ名のみ）
                    case 3 :
                        // この場合は$strがユーザIDのみの可能性が大
                        $username = $str;
                        if (($data = fs_get_record('user', 'username', $username))) return $data->id;
                        // レベル2,3の時にコードＣを送ってくることも許すためにbreakしない．
                    case 4 :    // コードＣ（ユーザ名:識別コード）
                    case 5 :
                        // ユーザ識別コードを':'で分解
                        if (($pos2 = mb_strpos($str, ':')) === false) continue;
                        $username = trim(mb_substr($str, 0, $pos2), ' ');
                        // もしそのようなユーザが存在しないならばNG
                        if (!($data = fs_get_record('user', 'username', $username))) break;
                        // レベル2,3の場合は***:***の形式のユーザIDさえ発見できればOK
                        if (($fsCFG->m2b_confirmlevel == 2) or ($fsCFG->m2b_confirmlevel == 3)) return $data->id;
                        // ユーザの登録した鍵と一致するかをチェック
                        $code = trim(mb_substr($str, $pos2 + 1), ' ');
                        if (trim(get_userProfileField_value($data->id, M2B_USERFIELDNAME_KEY), ' ') == $code) return $data->id;
                        break;
                    default : return false;
                }
            }
        }
        return false;
    }

    // 題名や本文に含まれるユーザ識別コードを探し，ユーザ識別ID（ユーザ名含む）を返す．
    // ユーザプロファイルフィールドのチェックも行う．
    // 設定されているconfirmlevelとは無関係に．
    function estimate_uid_by_idstring($entry)
    {
        global $fsCFG;

        $sep = $fsCFG->m2b_codeseparator;
        $targets = array();
        $targets[] = $entry->subject;
        $targets[] = $entry->body;
        foreach ($targets as $target) {
            while (mb_strlen($target) > 0) {
                if (($pos = mb_strpos($target, $sep)) === false) break;
                $target = mb_substr($target, $pos + mb_strlen($sep));
                if (($pos = mb_strpos($target, $sep)) === false) break;
                $str = mb_substr($target, 0, $pos);
                $str = trim($str, ' ');
                $username = $str;
                if (($data = fs_get_record('user', 'username', $username))) return $data->id;
                if (($pos2 = mb_strpos($str, ':')) === false) continue;
                $username = trim(mb_substr($str, 0, $pos2), ' ');
                if (($data = fs_get_record('user', 'username', $username))) return $data->id;
            }
        }
        return false;
    }

    // ユーザ識別子などを用いて投稿してきたユーザを識別する．
    // 識別できなかった場合はfalseを返す
    function get_uid($entry)
    {
        global $fsCFG;
        switch ($fsCFG->m2b_confirmlevel) {
            case 1 :    // 電子メールアドレスのみで推定
                $from = $entry->fromAddress;
                if (!($uid = $this->search_uid_by_emailaddress($from))) {
                    $this->error('この送信メールアドレス（'.$from.'）は登録されていません．');
                    return false;
                }
                return $uid;
            case 2 :    // ユーザ識別コード（ユーザ名のみ）で推定
            case 3 :    // ユーザ識別コード（ユーザ名のみ）＋電子メールアドレスで推定
                if (!($uid = $this->get_uid_by_idstring($entry))) return false;
                if ($fsCFG->m2b_confirmlevel == 3) {
                    $from = $entry->fromAddress;
                    if (!($this->search_uid_by_emailaddress($from, $uid))) {
                        $this->error('この送信メールアドレス（'.$from.'）は登録されていません．');
                        return false;
                    }
                }
                return $uid;
            case 4 :    // ユーザ識別コード（ユーザ名＋登録鍵）で推定
            case 5 :    // ユーザ識別コード（ユーザ名＋登録鍵）で推定＋電子メールアドレスで推定
                if (!($uid = $this->get_uid_by_idstring($entry))) return false;
                if ($fsCFG->m2b_confirmlevel == 3) {
                    $from = $entry->fromAddress;
                    if (!($this->search_uid_by_emailaddress($from, $uid))) {
                        $this->error('この送信メールアドレス（'.$from.'）は登録されていません．');
                        return false;
                    }
                }
                return $uid;
            default :
                return false;
        }
        return false;
    }

    // 受信した電子メールをブログに投稿する機能
    // 呼び出しはこの関数を用いること
    function execute_mail2blog($delete = false)
    {
        global $CFG,$fsCFG;
        if (!fs_mail2blog_enable()) return false;

        $cnt = $totalcnt = 0;
        $this->log_message('<<< 受信した電子メールをブログに登録 >>>');
        $this->log_message('処理開始', true);
        // メールの読み出し
        if (($maildata = $this->check_mail($delete))) {
//          require_once($CFG->dirroot.'/blog/edit_form.php');
            foreach ($maildata as $entry) {
                $totalcnt++;
                $post           = null;
                $subject        = $entry->subject;
                $from           = $entry->fromAddress;
                $body           = mb_convert_encoding($entry->body, 'UTF-8'); // 一応，念のために
                $post->created  = strtotime($entry->date);  // ユーザがメールを送信した日時

                $this->notice($totalcnt.":$subject, $from, ".strftime('%Y/%m/%d %H:%M:%S', $post->created).'-->');
                // 受信メールの送信元のユーザのユーザIDを認証コードなどから推定する
                if (!($uid = $this->get_uid($entry))) {
                    $this->error('認証に失敗しました．');
                    // 認証に失敗したよ，と返信する
                    if (!($uid = $this->search_uid_by_emailaddress($from))) {
                        // $fromから推定できなかった場合
                        $uid = $this->estimate_uid_by_idstring($entry);
                    }
                    if ($uid) {
                        $this->send_fail_reply($uid, $from, $post->created, $subject, "　ユーザ認証に失敗しました．\n　電子メールアドレスやユーザ識別コードから，送信元をあなただと推測しました．\n　もしブログを電子メールで送信した記憶が無い場合はMoodleサイトの管理者に連絡して下さい．");
                        $this->error("ユーザID＝ $uid のユーザにエラーメールを送信します（送信する設定のとき）．");
                    } else {
                        $this->error("ユーザIDが推定できませんでしたのでユーザに対してエラーメールを送信できません．");
                    }
                    continue;
                }
                // ユーザデータの取得
                $user = fs_get_record('user', 'id', $uid);
                if (empty($user)) {
                    $this->error("ユーザデータが取得できませんでしたブログ登録できません．");
                    continue;
                }

                // ブログ登録開始
                $post->userid = $uid;
                // 添付ファイルのチェック（複数，添付されても一つだけ）
                if (count($entry->attachments) >= 1) {
                    $attachment = array_shift($entry->attachments);
                    $attachment['name'] = clean_filename($attachment['name']);
                    $fname  = $attachment['name'];
                    // ファイルサイズのチェック
                    $size   = strlen($attachment['binary']);
                    $sizeover = false;
                    if ($size > $fsCFG->m2b_maxfilesize) {
                        // 種別（画像か否か）で処理が異なる
                        $pathinfo = fs_pathinfo($fname);
                        if (empty($pathinfo['extension']))  $pathinfo['extension'] = '';
                        switch (strtoupper($pathinfo['extension'])) {
                            case 'JPG' :
                            case 'PNG' :
                            case 'GIF' :
                                if (fs_function_enable('FS_ENABLE_MAIL2BLOGRESIZEPICTURE')) {
                                    // リサイズ
                                    if (($newbinary = $this->resize_picture($attachment)) === false) {
                                        $this->error('リサイズに失敗しました．');
                                        $sizeover = true;
                                        break;
                                    }
                                    $attachment['binary'] = $newbinary;
                                    $oldsize = $size;
                                    $size = strlen($attachment['binary']);
                                    $this->error("添付ファイル（{$fname}）はサイズの制限を越えていましたのでリサイズしました（".display_size($oldsize).'->'.display_size($size).'）．');
                                    break;
                                }
                            default :
                                $sizeover = true;
                        }
                    }
                    $post->attachment = $attachment;
                    if ($sizeover) {
                        $this->error("添付ファイル（{$fname}）はサイズの制限を越えましたので無視します（".display_size($size).'）．');
                        $post->attachment = null;
                    }
                } else {
                    $post->attachment = null;
                }
                // 0:Moodleオートフォーマット(default), 1:HTMLフォーマット，
                // 2:プレインテキストフォーマット, 4: Markdownフォーマット
                $format_str = array(0 => 'Moodleオートフォーマット', 1 => 'HTMLフォーマット', 2 => 'プレインテキストフォーマット', 4 => 'Markdownフォーマット');
                if ($fsCFG->m2b_textformat == 9) {
                    if ($user->htmleditor) $post->format = 0;
                    else                   $post->format = 2;
                } else $post->format  = $fsCFG->m2b_textformat;
                // ユーザ識別コードの除去（題名）
                $sep = $fsCFG->m2b_codeseparator;
                if (($pos = mb_strpos($subject, $sep)) !== false) $subject = 'No title';
                // ユーザ識別コードの除去（本文）
                $bodies = explode("\n", $body);
                $newbodies = array();
                foreach ($bodies as $linedata) {
                    if (mb_strpos($linedata, $sep) === false) $newbodies[] = $linedata;
                }
                $body = implode("\n", $newbodies);
                // 整形
                $post->subject = clean_text($subject);
                $post->summary = clean_text($body, $post->format);
                // 以下の整形処理は検証が不十分
                switch ($post->format) {
                    case 2: // プレインテキストフォーマット
                        break;
                    case 0: // Moodleオートフォーマット
                        $post->summary = str_replace("\n", '', nl2br($post->summary));
                        break;
                    default:
                        $post->summary = '<p>'.str_replace("\n", '', nl2br(htmlentities($post->summary, ENT_NOQUOTES, 'UTF-8'))).'</p>';
                        break;
                }
                // 公開対象の設定："draft", "site"(default), "public"
                $post->publishstate = $fsCFG->m2b_pubstate;

                // 実行
                if (!$this->do_add($post)) {
                    $this->error('ブログの登録に失敗しました．');
                    // 登録に失敗したよ，と返信する
                    $this->send_fail_reply($uid, $from, $post->created, $subject);
                    continue;
                }
                // 登録に成功
                $this->notice('ブログの登録に成功しました．フォーマットは'.$format_str[$post->format].'です．');
                $this->send_success_reply($uid, $from, $post->created, $subject);
                $cnt++;
            }
        } else {
            $this->error('メールが読み出せませんでした．');
        }
        $this->log_message('処理終了', true);
        $this->log_message("<<< 受信した電子メールをブログに登録完了 >>>\n");
        return $cnt;
    }

    ///////////////////////////////////////////////////////////////////////////////////
    // 登録結果のユーザへのメール送信（送信するかしないかはsend_reply()内で一括して判断）
    ///////////////////////////////////////////////////////////////////////////////////

    // ブログ登録成功を返信
    function send_success_reply($uid, $from, $date, $subject, $option = '')
    {
        return $this->send_reply(true, $uid, $from, $date, $subject, $option);
    }
    // ブログ登録失敗を返信
    function send_fail_reply($uid, $from, $date, $subject, $option = '')
    {
        return $this->send_reply(false, $uid, $from, $date, $subject, $option);
    }
    // 実際にメール返信を行う関数
    // 送信したらtrue，失敗あるいは送信しなかった場合はfalseを返す
    function send_reply($success, $uid, $from, $date, $subject, $option = '')
    {
        global $fsCFG, $CFG;
        if (!fs_function_enable('FS_ENABLE_MAIL2BLOGREPLYMAIL')) return false;
        if ($uid == 0) return false;    // 対策は後日考えよう
        switch ($fsCFG->m2b_replytiming) {
            case 0: // 返信メールを送信しません
                return false;
            case 1: // ブログの登録に成功した時のみ返信
                if (!$success) return false;
                break;
            case 2: // ブログの登録に失敗した時のみ返信
                if ($success)  return false;
                break;
            case 3: // ブログの登録に成功した時も失敗した時も返信
                break;
            default: return false;
        }
        $title = 'ブログ登録結果';
        $messagetext = '　あなたがMoodleサイトに送信した以下のブログの登録が';
        if ($success) {
            $title .= '（登録成功）';
            $messagetext .= "完了しました．\n";
            $messagetext .= "　こちらのURIで確認できます： ".$CFG->wwwroot.'/blog/index.php?userid='.$uid." \n";
        } else {
            $title .= '（登録失敗）';
            $messagetext .= "失敗しました．\n";
            $messagetext .= "　ブログが投稿されたのは以下のMoodleサイトです．\n";
            $messagetext .= "　".$CFG->wwwroot."\n";
        }
        $messagetext .= "\n";
        $messagetext .= "送信元アドレス： $from\n";
        $messagetext .= "送信日時： ".strftime('%Y/%m/%d %H:%M:%S', $date)."\n";
        $messagetext .= "ブログタイトル： $subject\n";
        $messagetext .= "\n$option\n";
        // Moodleに登録された電子メールアドレスに対して結果を送信する
        // メール送信元ではない点に留意（SPAMばらまきに加担しないために）
        return email_to_user(get_complete_user_data('id', $uid), get_admin(), $title, $messagetext);
    }

    // 引数渡しされたブログデータをデータベースに新規登録する関数
    // blog/edit.phpのdo_add()をベースに改造
//  function do_add($post, $blogeditform) {
    function do_add($post) {
        global $CFG, $USER, $returnurl;
        global $fsCFG;  // add

        // $post->userid, $post->created は上位の関数でせっとすること
        if (empty($post->userid)) return false;
        if (empty($post->created)) $post->created = time();
        $post->module       = 'blog';
        $post->lastmodified = time();

        // Insert the new blog entry.
        if ($id = insert_record('post', $post)) {
            $post->id = $id;
            // add blog attachment
            if (!empty($post->attachment) and ($newfilename = $this->upload_attachment($post))) {
                set_field("post", "attachment", $newfilename, "id", $post->id);
            }
// タグは電子メールから登録の場合は付けられない
//          add_tags_info($post->id);
            add_to_log(SITEID, 'blog', 'add', 'index.php?userid='.$post->userid.'&postid='.$post->id, $post->subject);

        } else {
            error('There was an error adding this post in the database', $returnurl);
            return false;  // add
        }
        return true;  // add
    }

    // 画像のリサイズ
    // あくまで大雑把な縮小
    function resize_picture($attachment)
    {
        global $fsCFG;

        // メモリ上での操作が困難なので一旦，ファイルに書き出す
        // 作業用フォルダの確認
        if (!($fullpath = fs_make_temp_folder('mail2blog'))) return false;
        // 拡張子を取得
        $pathinfo = fs_pathinfo($attachment['name']);
        if (empty($pathinfo['extension'])) $pathinfo['extension'] = '';
        $ext = strtoupper($pathinfo['extension']);
        switch ($ext) {
            case 'JPG' :
                $funcname = 'imagejpeg';
                break;
            case 'PNG' :
                $funcname = 'imagepng';
                break;
            case 'GIF' :
                $funcname = 'imagegif';
                break;
            default :
                return false;
        }

        // 一時的に書き出し（画像のサイズを知りたい）
        $org_fullpath = $fullpath.'/original.'.$ext;
        if (fs_file_put_contents($org_fullpath, $attachment['binary']) === false) return false;
        if (!fs_file_exists($org_fullpath)) return false;
        if (($org_filesize = fs_filesize($org_fullpath)) == 0) return false;
        $org_imagesize = fs_getimagesize($org_fullpath);
        $ratio = sqrt($fsCFG->m2b_maxfilesize / $org_filesize);
        $new_imagesize = $org_imagesize;
        $new_imagesize[0] = (int)($org_imagesize[0] * $ratio);
        $new_imagesize[1] = (int)($org_imagesize[1] * $ratio);
        // 作業用ファイルの削除
        @fs_unlink($org_fullpath);

        // イメージリソースを作成
        $org_im = imagecreatefromstring($attachment['binary']);
        if ($org_im === false) return false;
        if ($ext == 'GIF') {    // GIFの場合
            $new_im = imagecreate($new_imagesize[0], $new_imagesize[1]);
        } else {    // それ以外の場合はフルカラーのバッファを用意しないと256色になってしまう
            $new_im = imagecreatetruecolor($new_imagesize[0], $new_imagesize[1]);
        }
        if ($new_im === false) return false;
        // リサイズ
        if (imagecopyresampled($new_im, $org_im, 0, 0, 0, 0, $new_imagesize[0], $new_imagesize[1], $org_imagesize[0], $org_imagesize[1]) === false) return false;

        // イメージリソースを画像データとして出力してリターンする
        ob_start();
        $funcname($new_im);
        return ob_get_clean();
    }

    // 添付ファイルのアップロード
    function upload_attachment($post)
    {
        global $CFG;

        $attachment = $post->attachment;
        if (empty($attachment['binary'])) return false;
        // フォルダ作成
        $dir = blog_file_area_name($post);
        $fullpath = $CFG->dataroot.'/'.$dir;
        if (!@fs_mkdir($fullpath, $CFG->directorypermissions)) return false;
        if (!is_dir($fullpath)) return false;
        // ファイルの作成
        $newfilename = clean_filename($attachment['name']);
        $fullpath .= '/'.$newfilename;
        if (@fs_file_put_contents($fullpath, $attachment['binary']) === false) return false;
        return $newfilename;
    }

    // UNIX形式の日時を日本風の日時に変換（文字列→TIME→文字列）
    function str2time2str($timestr)
    {
        $tim = strtotime($timestr);
        return strftime('%Y/%m/%d&nbsp;&nbsp;%H:%M:%S', $tim);
    }

    // 受信メール一覧表示
    function m2b_display_list($maildata)
    {
        global $fsCFG;

        $subjectlength  = 25;   // 題名の最大長さ
        $bodylength     = 25;   // 本文の最大長さ
        if (count($maildata) < 1) return;
        echo '<table align=center cellpadding=5pix frame=border style="border: 1px solid #000000">'."\n";
        echo '認証方式＝'.color_blue($fsCFG->m2b_confirmlevel.'&nbsp;:&nbsp;'.get_fs_cfg_select_description('m2b_confirmlevel', $fsCFG->m2b_confirmlevel));
        echo "<tr bgcolor=#F0F0F0>\n";
        echo "<th>No.</th>\n";
        echo "<th>送信日</th/>\n";
        echo "<th>題名（{$subjectlength}文字）</th>\n";
        echo "<th>送信元</th>\n";
        echo "<th>本文（{$bodylength}文字）</th>\n";
        echo "<th>添付ファイル</th>\n";
        echo "<th>認証</th>\n";
        echo "</tr>\n";
        $no = 1;
        foreach ($maildata as $entry) {
            echo "<tr align=center>\n";
            echo "<td>$no</td>\n";
            echo '<td align="left">'.$this->str2time2str($entry->date)."</td>\n";
            echo '<td align="left">'.mb_substr($entry->subject, 0, $subjectlength)."</td>\n";
            echo '<td align="left">'."{$entry->fromAddress}&nbsp;/&nbsp;uid&nbsp;=&nbsp;".
                    $this->search_uid_by_emailaddress($entry->fromAddress)."</td>\n";
            echo '<td align="left">'.s(mb_substr($entry->body, 0, $bodylength))."</td>\n";
            $attachment = array_shift($entry->attachments);
            if (!empty($attachment)) {
                echo "<td>".$attachment['name'].'&nbsp;('.$attachment['type'].'&nbsp;)&nbsp;/&nbsp;'.display_size(strlen($attachment['binary']))."</td>\n";
            } else {
                echo "<td> </td>\n";
            }
            if (($uid = $this->get_uid($entry))) {
                echo '<td align="left">'."認証ＯＫ：uid&nbsp;=&nbsp;$uid</td>\n";
            } else {
                echo '<td align="left">'.color_red('認証ＮＧ')."</td>\n";
            }
            echo "</tr>\n";
            $no++;
        }
        echo '</table>';
    }

    //////////////////////////////////////////////////////
    // エラーおよびログファイルへの出力（cron.phpにも対応）
    //////////////////////////////////////////////////////

    // エラー出力（ログ含む）
    function error($message, $now = false)
    {
        $this->message($message, true);
        $this->log_message('ERR:'.$message, $now);
    }
    // 単なる出力（ログ含む）
    function notice($message, $now = false)
    {
        $this->message($message);
        $this->log_message($message, $now);
    }

    // エラー出力（画面出力のみ）
    function message($message, $error = false)
    {
        if (defined('FULLME') and FULLME === 'cron') {
            // cronの際にはエラーメッセージ以外は出力しない
            if ($error) mtrace($message);
        } else          echo $message."<br />\n";
    }

    // ログ出力
    // $nowをtrueとした場合はタイムスタンプを行頭に出力する
    function log_message($message, $now = false)
    {
        global $CFG, $fsCFG;
        if (fs_function_enable('FS_ENABLE_MAIL2BLOGLOG')) {
            // ログフォルダの確認（および作成）
            if (!($logpath = fs_make_work_folder('log'))) return false;
            // ログの出力
            $logpath .= '/'.get_detaillog_filename('m2b_log_filenameformat');
            if (!($fp = fopen($logpath, 'a'))) return false;
            // 日時の出力が必要か？
            if ($now) fputs($fp, '['.strftime('%Y/%m/%d %H:%M:%S', time()).'] ');
            // メッセージの追記
            fputs($fp, $message."\n");
            fclose($fp);
        }
    }
}

// --------------------------------------------------------------

// 電子メールでブログを投稿する機能が有効か？　最低限のチェック
function fs_mail2blog_enable($ignore_switch = false)
{
    global $fsCFG;

    if (!$ignore_switch and !fs_function_enable('FS_ENABLE_MAIL2BLOGFUNCTION')) return false;
    // 必須のパラメータ（接続できるかどうかのチェックはオーバーヘッドになるので行わない）
    if (empty($fsCFG->m2b_host)) return false;
    if (empty($fsCFG->m2b_user)) return false;
    if (empty($fsCFG->m2b_pass)) return false;
    // 各種設定に矛盾が無いかチェック
    $existsuserfield = is_exist_userProfileField(M2B_USERFIELDNAME_KEY);
    switch ($fsCFG->m2b_confirmlevel) {
        case 1 :
        case 2 :
        case 3 :
            break;
        case 4 :
        case 5 :
            if (!$existsuserfield) return false;
            break;
        default :
            return false;
    }
    if (fs_function_enable('FS_ENABLE_MAIL2BLOGREPLYMAIL')) {
        if (!isset($fsCFG->m2b_replytiming)) return false;
        switch ($fsCFG->m2b_replytiming) {
            case 0 :
            case 1 :
            case 2 :
            case 3 :
                break;
            default :
                return false;
        }
    }
    // PEARライブラリのチェック
    if (!class_exists('Mail')) return false;
    if (!class_exists('Mail_mimeDecode')) return false;

    return true;
}

// ------------------------------------------------------------------------
// cron.phpに関する関数群

define('CRONSTOPFILENAME', 'm2b_cronstop.txt');

// cron.phpによる自動登録を一時的に無効にする
function fs_mail2blog_cronstop($sec)
{
    global $CFG;
    if ($sec <= 0) return false;
    if (!fs_mail2blog_enable()) return false;

    // ログフォルダの確認（および作成）
    if (!($logpath = fs_make_work_folder('log'))) return false;
    // 停止リミット時刻（自動解除）をファイルに記録する
    $logpath .= '/'.CRONSTOPFILENAME;
    if (!($fp = fopen($logpath, 'w'))) return false;
    // 日時の出力
    $time2stop = time() + $sec;
    fputs($fp, $time2stop);
    fclose($fp);
    return $time2stop;
}

// cron.phpによる自動登録の一時停止を解除する
function fs_mail2blog_cronstart()
{
    global $CFG;
    if (!fs_mail2blog_enable()) return false;

    // ログフォルダの確認（および作成）
    if (!($logpath = fs_make_work_folder('log'))) return false;
    // 停止リミット時刻（自動解除）のファイルが存在しない
    $logpath .= '/'.CRONSTOPFILENAME;
    if (!file_exists($logpath)) return true;
    // 停止リミット時刻（自動解除）のファイルを削除する
    if (!unlink($logpath)) return false;
    return true;
}

// cron.phpによる自動登録が一時的に無効か？
// 指定されていないならばfalseを返し，
// 指定されているならば停止リミット時刻を返す
function fs_mail2blog_is_cronstop()
{
    global $CFG;
    if (!fs_mail2blog_enable()) return false;

    // ログフォルダの確認（および作成）
    if (!($logpath = fs_make_work_folder('log'))) return false;
    // 停止リミット時刻（自動解除）のファイルが存在しない
    $logpath .= '/'.CRONSTOPFILENAME;
    if (!file_exists($logpath)) return false;
    // 停止リミット時刻（自動解除）をファイルから読み出す
    if (!($fp = fopen($logpath, 'r'))) return false;  // どうかな
    // 日時の読み込み
    $time2stop = (int)fgets($fp);
    fclose($fp);
    if ($time2stop <= 0) return false; // エラーかな
    if (time() > $time2stop) {
        // リミットを過ぎているのでファイルを削除する
        if (!unlink($logpath)) return false; // エラーかな
        return false;
    }
    return $time2stop;
}

?>
