さくらインターネットでメールを受信すると、それをトリガーにして、CakePHPのControllerを起動させます。簡単に言えば、さくらインターネットの指定のメールアドレスにメールを送信すると、折り返し、送信元のアドレスに自動返信したり、メールを受けると、それを指定の数カ所に転送したり、空メールが登録できるようになります。この機能をCakePHPを用いて実装します。

動作環境

  • cakephp-2.5.2
  • PEAR-1.9.4
  • さくらインターネット

まずはメール側の話をします。さくらインターネットでメールを受信できるように設定すると、「aaa@アカウント名.sakura.ne.jp」なら「/home/アカウント名/Mailbox/aaa」としてサーバにフォルダが作成され、その直下に.mailfilterファイルが作成されます。.mailfilterファイルに以下のように受信したメールを処理するための記述を設定します。ここでは、receivers_contoroller.phpに記述されたReceiversContorollerを起動することを意図してみます。

to “| /usr/local/bin/php -q /home/アカウント名/www/cake/receiverscontoroller/index.php /receivers_contoroller”

次にCakePHP側の話をします。さくらインターネットの受信メールでCakePHPを起動させるには、次の設定が必要となります。

  • /www/cake/receiverscontoroller/index.php :ここには、Webrootのindex.phpのディレクトリを記述します。
  • /receivers_contoroller :コントローラのアクションを動作させる場合、半角スペース+コントローラ名とアクション名を記述します。

メールを受け取った CakePHPのControllerのアクション(この場合、index)で、標準入力の内容を読めば、メール受信時の処理を行なえます。今回の設定には、PEAR のMail_mimeDecodeを使用します。

class ReceiversContoroller extends AppController {
	public function index() {
		$path = '/(rootからPEARまでのパス名)”/';
		set_include_path(get_include_path() . PATH_SEPARATOR .$path);

		require_once 'Mail/mimeDecode.php';
		#-- メールデータ取得
		$params['include_bodies'] = true;
		$params['decode_bodies']  = true;
		$params['decode_headers'] = true;
		$params['input'] = file_get_contents("php://stdin"); // 標準入力
		$params['crlf'] = "\r\n";
		
		$mail_data = Mail_mimeDecode::decode($params);

		#-- From フィールドの取得
		$FromAddress = $mail_data->headers['from']; 

		#-- To フィールドの取得
		$ToAddress = $mail_data->headers['to']; 

		#-- Subject フィールドの取得
		$Subject = $mail_data->headers['subject'];
		$Subject = mb_convert_encoding($Subject,"UTF-8","JIS");

		#-- 本文の取得
		$MailBody = $mail_data->body;
		$MailBody = mb_convert_encoding($MailBody,"UTF-8","JIS");
	}
}

CakePHPでPEARのパッケージを利用する場合、次の方法が考えられます。

  1. ディレクトリ Vendorsに格納して、「App::import()」で呼び出す
  2. CakePHPとは無関係なPEAR格納用ディレクトリを指定して、「App::import()」で呼び出す
  3. include(あるいはrequire)で呼び出す

今回は、3番目の方法で、アクションの先頭でrequire_onceを利用する方法で、アクションの先頭でパスを設定し、require_onceを使用して、mimeDecodeを呼び出しました。

ここまでの設定で、/home/アカウント名/www/cake/receiverscontoroller/index.phpには制御が移り、index.phpでDispatcherのインスタンスが生成されるところまでは正常に動作します。しかし、ReceiversContorollerが起動されません。この確認には、トレーサーとしてデバッグ用のファイルを作成し、このファイル実行された部分を示す番号を書き込み、どこまで実行されたかを確認しました。なおこのデバッグ用のファイルは、「/home/アカウント名/Mailbox/aaa」に作成されます。

index.phpからコントローラーまで

/home/アカウント名/www/cake/receiverscontoroller/index.phpには制御が移ったが、ReceiversContorollerが起動されないので、CakePHPでexample.com/controller/actionの様な形式のURLが、どう扱われて、コントローラーのアクションが呼ばれるのか、ソースコードを読んで追いかけてみました。

index.php

CakePHPへのアクセスは、mod_rewriteで全部app/webroot/index.phpに集約されて、Dispatcherオブジェクトでdispatchされます。

$Dispatcher = new Dispatcher();
$Dispatcher->dispatch(
  new CakeRequest(),
  new CakeResponse()
);

CakeRequest – cakephp\lib\Cake\Network\CakeRequest.php

Dispatcherに渡されるCakeRequestオブジェクトが生成される時、スーパーグローバル変数の$_SERVERから、コントローラー名やアクションを表すパス部分を取り出してます。パス部分は、.htaccessでindex.phpに引き継がないが、PATH_INFOやREQUEST_URIなどには残っています。また、$_SERVERから取得しているため、.htaccessで、コントローラー名やアクションを表すパス部分を引き継いでいません。

protected function _url() {
	if (!empty($_SERVER['PATH_INFO'])) {
		return $_SERVER['PATH_INFO'];
	} elseif (isset($_SERVER['REQUEST_URI']) && strpos($_SERVER['REQUEST_URI'], '://') === false) {
		$uri = $_SERVER['REQUEST_URI'];
	} elseif (isset($_SERVER['REQUEST_URI'])) {
		$qPosition = strpos($_SERVER['REQUEST_URI'], '?');
		if ($qPosition !== false && strpos($_SERVER['REQUEST_URI'], '://') > $qPosition) {
			$uri = $_SERVER['REQUEST_URI'];
		} else {
			$uri = substr($_SERVER['REQUEST_URI'], strlen(Configure::read('App.fullBaseUrl')));
		}
	} elseif (isset($_SERVER['PHP_SELF']) && isset($_SERVER['SCRIPT_NAME'])) {
		$uri = str_replace($_SERVER['SCRIPT_NAME'], '', $_SERVER['PHP_SELF']);
	} elseif (isset($_SERVER['HTTP_X_REWRITE_URL'])) {
		$uri = $_SERVER['HTTP_X_REWRITE_URL'];
	} elseif ($var = env('argv')) {
		$uri = $var[0];
	}

	$base = $this->base;

	if (strlen($base) > 0 && strpos($uri, $base) === 0) {
		$uri = substr($uri, strlen($base));
	}
	if (strpos($uri, '?') !== false) {
		list($uri) = explode('?', $uri, 2);
	}
	if (empty($uri) || $uri === '/' || $uri === '//' || $uri === '/index.php') {
		$uri = '/';
	}
	$endsWithIndex = '/webroot/index.php';
	$endsWithLength = strlen($endsWithIndex);
	if (
		strlen($uri) >= $endsWithLength &&
		substr($uri, -$endsWithLength) === $endsWithIndex
	) {
		$uri = '/';
	}
	return $uri;
}

CakePHPをさくらインターネットの受信メールで起動させるには、次のロジックの変更が必要となります。通常のWebブラウザからの起動では、上記に示すfunction _url()の4行目の条件を満足します。

  • function _url()の13行目:$_SERVER[‘PHP_SELF’]と$_SERVER[‘SCRIPT_NAME’]にはデータが入っているので、条件を満足して、設定されていないデータで処理するため、正常動作しない。
  • function _url()の18行目:$uri = $var[0]を$uri = $var[1]に変更します。.mailfilterに設定されたコントローラ名は、$var[1]に入ってきます。

変更した部分を次に示します。コメントにしている部分と上記のfunction _url()とを比較して参考にしてください。

if (!empty($_SERVER['PATH_INFO'])) {
  return $_SERVER['PATH_INFO'];
} elseif (isset($_SERVER['REQUEST_URI']) && strpos($_SERVER['REQUEST_URI'], '://') === false) {
  $uri = $_SERVER['REQUEST_URI'];
} elseif (isset($_SERVER['REQUEST_URI'])) {
  $qPosition = strpos($_SERVER['REQUEST_URI'], '?');
  if ($qPosition !== false && strpos($_SERVER['REQUEST_URI'], '://') > $qPosition) {
    $uri = $_SERVER['REQUEST_URI'];
  } else {
    $uri = substr($_SERVER['REQUEST_URI'], strlen(Configure::read('App.fullBaseUrl')));
  }
//} elseif (isset($_SERVER['PHP_SELF']) && isset($_SERVER['SCRIPT_NAME'])) {
//  $uri = str_replace($_SERVER['SCRIPT_NAME'], '', $_SERVER['PHP_SELF']);
} elseif (isset($_SERVER['HTTP_X_REWRITE_URL'])) {
  $uri = $_SERVER['HTTP_X_REWRITE_URL'];
} elseif ($var = env('argv')) {
// $uri = $var[0];
  $uri = $var[1];
}