Blog

concrete5の外部フォームの使い方

Posted by admin at 7:17 日時 2013/08/12

#

concrete5の標準ブロックの中でダントツの意味不明さを誇る「外部フォーム」ブロック。編集モードで割りと上の位置にあるのでクリックしたことがある方も多いと思いますが、クリックするとこのようなウィンドウが開きます(この画面の誤訳も意味不明さに拍車をかけている。5.6.2から直します。すいません)。

新規 外部フォーム ウィンドウ

ブロックを追加すると、冒頭のようなテキストフィールドが1つだけのフォームが出てきます。フォームに入力して送信してみても、何も起こりません!で、これは何なのかというと、ひとことで言うと「自分でPHPで作ったメールフォームを埋め込めるブロック」です。

標準ブロックの中には他にも「フォーム」ブロックがあり、簡単操作でフォームを作成することができます。

フォームブロック編集画面

フォームブロックはマウス操作だけでフォームの入力項目を追加・編集・並び替えが可能で、とても便利ですが、その反面、融通が効きません。デザイン上の自由度もあまりありませんし、機能も用意されたものしかありません…。より自由なデザイン・挙動でフォームを作ろうと思った場合は、外部フォームを利用するのがおすすめというわけです。

外部フォームブロックの構造

外部フォームブロックのハンドルは「external_form」ですので、外部フォーム関連のファイルは concrete5 / blocks / external_form ディレクトリーに格納されています。外部フォームブロックが他と違うのは、forms というディレクトリーが中にあり、この中のPHPファイルを外部フォームの追加時に選択できます。初期状態では、test_form.php というファイルだけがあります。また、同じ階層に controllers ディレクトリーも存在し、その中にやはり同名の test_form.php というファイルがあります。前者がビューで、後者がコントローラーです。このように外部フォームはVとCだけあれば動作します。

外部フォームブロックディレクトリー

それでは、Test Formのビューとコントローラーをそれぞれ見ていきましょう。ビューはこんな内容になっています。

concrete / blocks / external_form / forms / test_form.php

<?php  $form = Loader::helper('form');  defined('C5_EXECUTE') or die("Access Denied.");  if (isset($response)) { ?>  	<?php	echo $response?>  <?php	 } ?>  <form method="post" action="<?php	echo $this->action('test_search')?>">    <p><?php	echo t("This is just an example of how a custom form works.")?></p>    <?php	echo $form->text('test_text_field')?>    <input type="submit" name="submit" value="submit" />    </form>

先頭の $form = Loader::helper(‘form’); でフォーム・ヘルパーを呼んでいます。

defined(‘C5_EXECUTE’) or die(“Access Denied.”); はconcrete5のPHPでは必ず先頭に書く決まりです。

次に引数 $response を受け取って出力しています。これはお礼メッセージを表示するところですね。

本体の form 要素の action 属性の値には、 echo $this->action(‘test_search’) が指定されています。test_search部分は任意の値に変更できます。

フォーム部品のテキストフィールドは、 echo $form->text(‘test_text_field’) のようにフォーム・ヘルパーを使って出力しています。フォーム・ヘルパーは外部フォームに限らずconcrete5でフォームを作る際には必要になり、このようにかんたんな記法でフォーム部品を出力できる便利なAPIです。

フォーム・ヘルパーの使い方は Standard Widgetsconcrete5 Widgets を参考にしてください。テキストフィールド以外にテキストエリアやラジオボタンなど基本的なフォーム部品だけでなく、カラーピッカーや日付ピッカーもかんたんに利用できます(超便利)。日本語ドキュメントは…間に合ってません…ドキュメント協力者募集中です!

続いてコントローラーの内容です。

concrete / blocks / external_form / forms / controllers / test_form.php

<?php  defined('C5_EXECUTE') or die("Access Denied.");  class TestFormExternalFormBlockController extends BlockController {    	public function action_test_search() {    		$this->set('response', t('Thanks!'));  		return true;    	}    }

コントローラーでは BlockController を継承した TestFormExternalFormBlockController クラスが定義されています。ファイル名 test_form.php のアンダーバーを抜いて単語の先頭を大文字にした TestFormExternalFormBlockController をつなげたクラス名にするのが決まりです。また、ビューとコントローラーのファイル名も同一である必要があります。

クラス内のメソッドは action_test_search のみ定義されています。このメソッド名はビュー側で $this->action(‘test_search’) として指定した test_searchaction_ のあとにつなげたもので、これも決まりになっています。

action_test_search メソッド内ではビューに response という変数で、’Thanks!’(翻訳後は「ありがとうございます!」)という文字列を渡している。以上。なので、特にメール送信はしていません。

外部フォームはかんたんに使えてしかもCSRF対策済み

で、それが何?テキストフィールドを1つ出力し、ありがとうメッセージを表示しているだけ。それならPHPを素で書いているのと変わらない、と思われたかもしれません。もちろん、concrete5のブロックとして有効な形式になり、編集モードで配置し、移動もできるということはメリットです。ですが、さらなるメリットとしては、CSRF対策済み、ということがあります。

CSRFとは、正規の手順を踏まないリクエストを受け付けてしまう脆弱性です。この場合の正規の手順とは、外部フォームが設置されているページにアクセスして、ブラウザでフォームに内容を入力し、送信ボタンを押すという手順です。不正なリクエストとは、入力画面をスキップして送信内容を直接concrete5に送りつける方法です。これを許してしまうと、機械的にいくらでもフォームを送信し放題になってしまいます。CSRF対策は重要な処理の乗っ取りを防ぐために必要と言われることがあるとおもいますが、ただメールを送信するだけのフォームであっても対策しておかないと、セキュリティ診断を受けた際に迷惑メールの踏み台になる可能性があると指摘されてしまいます。

外部フォームを正しく使うと、下記の図のようにURL(GETパラメーター)内に正しいトークンが存在する場合のみ処理が行われ、トークンが指定されていない場合、またはトークンが間違っている場合は処理が行われません。

自動でCSRF対策してくれる

トークンの作り方が md5(time:userID:action:salt) なのでもっと強度あげようよという話はあるかと思いますし、実際にサイトにアクセスしちゃえば有効なトークンをゲットすることも可能ですし、ワンタイムトークンではないので、まあ不正利用しようと思えばできちゃうわけですが、メールフォームなので十分かなと。トークンがURLに見えているのはもやっとしますが、送信内容をPOSTにするためなのでしかたない。そこまで気にするなら全処理自分で書け!ということですね…。

一応トークンにはユーザーIDが含まれるので、ログインしていればユーザーごとに固有のトークンになります。本当に重要な処理であればログインさせるようにしてください。また、この場合のログインしないと見えないという設定もすごく楽にできる!これがconcrete5のいいところ。もしログインさせずに不正利用を防ぎたいんや!という方は、Captcha用のヘルパーも用意されていてかんたんに実装できます。

サンプルフォーム:メルマガ購読申し込み

それでは、外部フォームを使ってメールフォームを作成してみましょう。今回はよくあるメルマガ購読申し込みフォーム的なものを作ってみます。

メルマガ登録フォーム的なもの

フォーム要素はチェックボックスとテキストフィールドで、テキストフィールドにはメールアドレスを入れてもらいます。メルマガにチェックを入れずに送信した場合、または適切なメールアドレスが入力されていない場合にはエラーメッセージを表示しましょう。

エラーメッセージ

正しく値が入力された場合には、メルマガ登録完了メールが入力されたメールアドレスと管理者あてに届くという仕様です。

登録完了メール

外部フォームの追加テンプレートは blocks / external_form / forms ディレクトリーに設置します。ビューはこのようになりました。

blocks / external_form / forms / mail_mag.php

<?php  $form = Loader::helper('form');  defined('C5_EXECUTE') or die("Access Denied.");    // エラーメッセージの表示  if (isset($errorArray) && is_array($errorArray) && count($errorArray) > 0) {  	?>  	<div style="border:1px solid red">  	<?php  	foreach ($errorArray as $e){  		?><p style="color:red"><?php	echo $e?></p><?php  	}  	?>  	</div>  	<?php  }    // お礼メッセージの表示  if (isset($response)) {  	echo $response;  }  ?>  <form method="post" action="<?php	echo $this->action('mail_mag_submit')?>">    <dl>  <dt>購読したいメルマガを選択してください</dt>  <dd><?php	echo $form->checkbox('magazine[]', 'ピックアップ商品情報(週刊)')?> ピックアップ商品情報(週刊)</dd>  <dd><?php	echo $form->checkbox('magazine[]', '新着商品情報(日刊)')?> 新着商品情報(日刊)</dd>  <dt>メールアドレス</dt>  <dd><?php	echo $form->text('email')?></dd>  </dl>    <?php	echo $form->submit('submit','送信'); ?>    </form>

Test Formと違いエラーメッセージを表示しています。また、 $form->checkbox() でチェックボックスを表示しています。フォーム要素ごとの書き方はヘルプを参照。

次に、コントローラーの内容です。

blocks / extermal_form / forms / controllers / mail_mag.php

<?php  defined('C5_EXECUTE') or die("Access Denied.");  class MailMagExternalFormBlockController extends BlockController {    	public function action_mail_mag_submit() {    		// validation/form ヘルパーを呼び出し  		$val = Loader::helper('validation/form');  		// postデータをバリデーションに登録  		$val->setData($this->post());  		// 必須項目ルールの追加  		$val->addRequired('magazine', '1つ以上のメルマガを選択してください。');  		// メールアドレスの検証  		$val->addRequiredEmail('email', '有効なメールアドレスを入力してください。');    		// テスト実行  		if (!$val->test()) {  			// テストに通らなかった場合はビューにエラーを渡す  			$errorArray = $val->getError()->getList();  			$this->set('errorArray', $errorArray);  		} else {  			// テストに通った場合の処理  			$mh = Loader::helper('mail');    			// FROMアドレスの設定  			$mh->from(EMAIL_DEFAULT_FROM_ADDRESS, SITE);  			// Toアドレスの設定  			$mh->to($this->post('email'));    			// 管理者ユーザー情報を取得  			$adminUserInfo = UserInfo::getByID(USER_SUPER_ID);  			if (is_object($adminUserInfo)) {  				// 管理者メールアドレスをBCCに設定  				$mh->bcc($adminUserInfo->getUserEmail());  			}    			// メール変数を設定  			$mh->addParameter('magazine', $this->post('magazine'));    			// メールテンプレートを設定  			$mh->load('mail_mag_complete');    			// メール送信  			$mh->sendMail();    			$this->set('response', 'お申し込みありがとうございました。');  		}    		return true;    	}    }

mail_mag.php だから MailMagExternalFormBlockController というクラス名になります、これはお約束。ちゃんと合っていないとエラーになります。メソッド名が action_mail_mag_submit() になっているのは、ビューで指定しているアクションが $this->action(‘mail_mag_submit‘) だからですね。

その他はだいたいコメントに記載した通りの処理内容ですが、ポイントを解説。

バリデーションは専用のバリデーション・ヘルパーで行なっています。使い方は公式サイトドキュメントの Basic Validaion (やはり英語)を参照。ヘルパーを使って magazine パラメーターを必須項目として、email パラメーターを必須項目かつメールアドレスとして有効かどうかのチェックを行なっています。

メール送信も専用のメール・ヘルパーで行なっています。使い方は公式サイドドキュメントの Sending Mail (うん、だいたい分かってきたと思うけど英語しかないよね)を参照。まあ、TOやFROMなどメールのパラメーターを渡しているだけなので、分かりやすいです。EMAIL_DEFAULT_FROM_ADDRESS と SITE はそれぞれ定数で、concrete5から送信するメールのデフォルトのFROMアドレス、サイト名を指します。EMAIL_DEFAULT_FROM_ADDRESS は config/site.php で変更できます。

メールテンプレートとして mail_mag_complete を指定していますが、これはどこに置くかというと、mail ディレクトリーにおきます。

mail / mail_mag_complete.php

<?php  defined('C5_EXECUTE') or die(_("Access Denied."));    $subject = 'メールマガジン登録';    $magazine = implode(', ', $magazine);    $body = sprintf('下記のメールマガジンの登録を受け付けました。    %s',$magazine);

concrete / mail ディレクトリーにはconcrete5が使っているメールテンプレートがありますので参考にしてください。

そんなこんなでビューとコントローラーとメールテンプレートが作成できました。作成した外部フォームはサーバーの適切なディレクトリーにアップロードされているだけで選択できるようになります。

追加した外部フォームが選択できるようになった

どうでしょうか。面倒なプログラミングを必要とせず、かんたんにメールフォームが作れてしまうことがお分かりいただけたかと思います。もちろん、標準のフォームブロックに比べて一覧で保存されてエクセル形式でダウンロードできるなどの高度な機能はありません。ただ、外部フォームはデザインも完全に自由、そして機能も自由。選択肢によってメールの送信先を変えるなんてのももちろんできますし、APIを経由して他のシステムにデータを送るということも思いのままです。

また、フォームって別にメールフォームとは限らないわけです。ちょっとした検索フォームをサイトに付けたかったりする場合も、独自ブロックを開発する手間を省いてさくっとPHPを書いてサイトに置くための土台として使えます。外部フォームを使うことでconcrete5のCMSとしての幅が広がりますので、ぜひ活用してみてください!


Share this entry