2008-02-08
■[CakePHP]メール受信からのシェル機能実行

空メール送信での会員登録や、メールに添付されたファイルをサーバに自動でアップロードなど、メール受信からのスクリプト実行というのは様々なシーンで用途があると思います。
今回はそれをCakePHP 1.2系のシェル機能を使って実現してみました。(1.2.0.6311-betaで確認)
使用するもの
メールサーバはqmail、メールの解析処理はPEARのMail_mimeDecodeを使用します。
PEARを使用できるようにセットアップ
まずapp/vendorsの下にPEARフォルダを作り、PEARのコアクラス(PEAR.php)とMail_Mimeパッケージのクラスを置く。
次に、以下のソースをapp/vendors/pear_ini.phpとして保存。
<?php define('PEAR_PATH', dirname(__FILE__) . DS . 'PEAR'); set_include_path(PEAR_PATH . PATH_SEPARATOR . get_include_path()); ?>
セットアップ後の配置は上のスクリーンショットの様になっていればOKです。
シェル機能の実装
Mail_mimeDecodeを使ったスクリプトは、2008-01-23 - Stellaqua - TOMの技術日記に掲載されているスクリプトをベースにさせて頂きました。
以下のソースをapp/vendors/shells/receiver.phpとして保存。
<?php vendor('pear_ini'); require_once('Mail' . DS . 'mimeDecode.php'); class ReceiverShell extends Shell { function receiveMail() { $mail = ""; $image_filename = ""; $from = array(); $headers = array(); // 標準入力から受け取ったメールを取得 $stdin=$this->Dispatch->stdin; while( !feof($stdin) ){ $mail .= fgets($stdin,4096); } // デコード方法の指定 $params['include_bodies'] = true; $params['decode_bodies'] = true; $params['decode_headers'] = true; // デコード処理 $decoder = new Mail_mimeDecode($mail); $decoded = $decoder->decode($params); // 添付ファイルの有無で処理を分岐 if ( !empty($decoded->parts) ) { // bodyを取得 $body = $decoded->parts[0]->body; // 添付ファイルの数だけループ for($idx = 1; $idx < count($decoded->parts); $idx++) { $file = $decoded->parts[$idx]->body; $filetype = $decoded->parts[$idx]->ctype_secondary; /********************************************************** * ファイルタイプのチェックしたり、指定したディレクトリに * * 添付ファイルを保存したりの処理をここでする * **********************************************************/ } } else { // 添付ファイルが無い場合はbodyを取得するだけ $body = $decoded->body; } // ヘッダ部分を取得して漢字コードを変換する $headers = $decoded->headers; $from_text = mb_convert_encoding($headers['from'], mb_internal_encoding(),'ISO-2022-JP'); ereg("[0-9a-zA-Z_\.\-]+@[0-9a-zA-Z_\.\-]+",$from_text,$from); $from = $from[0]; $subject = mb_convert_encoding($headers['subject'], mb_internal_encoding(),'ISO-2022-JP'); $body = mb_convert_encoding($body, mb_internal_encoding(),'ISO-2022-JP'); /********************************************************** * DBに格納したり、メールを送り返したりの処理をここでする * **********************************************************/ } } ?>
シェルスクリプトファイル、.qmailファイルの作成
上記で実装したCakePHPのメール受信スクリプトを呼ぶシェルスクリプト(receiver.sh)は以下のように書きました。
#!/bin/sh cd /appフォルダまでのパス/app ../cake/console/cake receiver receiveMail
そして、メール受信時にそのシェルスクリプトを呼ぶように.qmailファイルは
|/シェルスクリプトまでのパス/receiver.sh
として、しかるべき場所に配置します。
以上で、
メール受信を契機に上記のスクリプトが実行されるコトが確認できました。
あとは用途によって、添付ファイルの保存やDBへのデータ格納など実装すれば良いかと。
ハマッた点
標準入力のオープン・クローズを以下のように自前でやると、標準入力からメールがうまく読み込めてなくてしばらくハマってしまいました。
$stdin=fopen("php://stdin",'r');
while( !feof($stdin) ){
$mailtext .= fgets($stdin,4096);
}
fclose($stdin);
ソースを追っていたら、ShellDispatcherクラスの__initEnvironmentメソッドで標準入力をオープンして$stdinにセット⇒ShellDispatcherのインスタンスがShellクラスの$Dispatchにセットされている、という事が分かったので標準入力は自前で開かずに$this->Dispatch->stdinを使ってみると、うまく読み込むことが出来ました。
- 12 http://www.syuhari.jp/blog/archives/184
- 7 http://reader.livedoor.com/reader/
- 7 http://www.syuhari.jp/blog/
- 7 http://www.syuhari.jp/blog/archives/181
- 7 http://www.syuhari.jp/blog/archives/185
- 5 http://www.google.co.jp/search?q=cakephp+eclipse&lr=lang_ja&ie=utf-8&oe=utf-8&aq=t&rls=org.mozilla:ja:official&client=firefox-a
- 4 http://b.hatena.ne.jp/add?mode=confirm&title=%u30E1%u30FC%u30EB%u53D7%u4FE1%u304B%u3089%u306E%u30B7%u30A7%u30EB%u6A5F%u80FD%u5B9F%u884C - Writing Some Code&url=http://d.hatena.ne.jp/ngtn/20080208/1202400164
- 4 http://b.hatena.ne.jp/add?mode=confirm&title=Eclipse PDT%u3067CakePHP%u958B%u767A%u3001%u307E%u305A%u8A2D%u5B9A%u3059%u3079%u304D%u3053%u3068 - Writing Some Code&url=http://d.hatena.ne.jp/ngtn/20080204/1202142255
- 3 http://www.google.co.jp/search?sourceid=navclient&aq=t&hl=ja&ie=UTF-8&rls=GGLR,GGLR:2006-40,GGLR:ja&q=cakePHP+eclipse
- 3 http://www.google.co.jp/search?sourceid=navclient&hl=ja&ie=UTF-8&rlz=1T4GZHZ_jaJP241JP242&q=cakephp+eclipse+thtml





1点後確認いただきたいことがあります。
// bodyを取得
$body = $decoded->parts[0]->body;
ここのコードは
$body = $decoded->body;
が正解ではないでしょうか。ご確認ください。
ひとつ質問なのですが、
receiveMail() が引数を持つfunctionの場合、
例:receiveMail($id=""){省略;}など
どのようにshellを記述すればよいのでしょうか?
亀レスですみませんが、
質問の意図としてはメソッドに外部から情報を与えたいということでしょうか?
それであれば、シェルを
../cake/console/cake receiver receiveMail 12345
とした場合、receiveMail()の中では$this->argsでコマンドライン引数から値を取得できると思います。