WordPressでAjaxする方法をざっくりまとめておく
Posted by admin at 8:08 日時 2013/12/02
アドベントカレンダーには参加してないけど今日も今日とてWordPressネタの更新です。今年に入ってから17記事目ですよ。concrete5 Japan Inc.なんてやってるのにおかしいですね。
それはさておき月末の追い込み時に調べる時間がないのにまとまった資料がなくて困った件の覚え書き。30分で終わらせようと思ったのに1時間以上かかってしまった…。WordPressのAjaxはやり方が多すぎてとりあえずこうしとけというのがまとまってないように思います。どうせ山ほどあるWordPress本にも書いてないだろうしな。これでいいのかよく分からないので、間違ってたらツッコミお願いします。
んで、どうも wp_ajax_{action_name}
または wp_ajax_nopriv_{action_name}
というアクションフックで処理するのがよさそうである。noprivの方はログアウト中、noprivがついてない方はログイン中のみ動作する。{action_name}
の部分は $_REQUEST['action']
と対応している。Nonceのチェックは check_ajax_referer() 関数で可能で、$_REQUEST['_ajax_nonce']
と対応している。
と言うわけで。以下が作例です。「送信」ボタンをクリックすると現在見ている投稿IDがWordPressにAjaxで送信されて、送った投稿IDの値がただ返ってきて、コンソールにそのまま表示するだけというシンプルな例です。実際には、ここからオリジナルのいいねボタンを作ったり評価ボタンを作ったり好きにすればいいさ!
フロント側の作例
<?php $post_id = get_the_ID(); // Ajaxは基本admin-ajax.phpを経由することになっている。 $admin_url = admin_url( 'admin-ajax.php', is_ssl() ? 'https' : 'http' ); // CSRF防止のためにNonceを生成する。 $ajax_nonce = wp_create_nonce('my_great_action'); // フォームではactionと_ajax_nonceを送る必要がある。 // でもnonceは無くても動いちゃう(強制すればいいのに) ?> <form id="my_great_action_form" method="post" action="<?php echo esc_js( esc_url_raw($admin_url) ); ?>"> <input type="hidden" name="action" value="my_great_action" /> <input type="hidden" name="_ajax_nonce" value="<?php echo $ajax_nonce;?>" /> <input type="hidden" name="post_id" value="<?php echo esc_attr($post_id) ?>" /> <input type="submit" value="送信" /> </form> <script type="text/javascript"> jQuery(document).ready(function($){ $('#my_great_action_form').submit(function(event){ event.preventDefault(); $.ajax({ url: $(this).attr("action"), type: $(this).attr("method"), data: $(this).serialize() }).done(function(response){ console.log(response); }); }); }); </script>
サーバー側の作例
<?php class My_WP_Ajax { // singleton instance private static $instance; public static function instance() { if ( isset( self::$instance ) ) return self::$instance; self::$instance = new My_WP_Ajax; self::$instance->run_init(); return self::$instance; } private function __construct() { /** Do nothing **/ } protected function run_init() { // wp_ajax_{action_name} というアクションフックが自動で作られる add_action( 'wp_ajax_my_great_action', array( $this, 'my_great_action' ) ); } public function my_great_action() { // Nonceのチェック。 check_ajax_referer( $action_name ) check_ajax_referer( 'my_great_action' ); // チェックしたら処理する $post_id = (int) $_REQUEST['post_id']; echo $post_id; die(); } } My_WP_Ajax::instance();
さて、次はconcrete5のAjaxについて調べておくか。
JSONとかセキュリティについて追記(2014-08-26)
JSONを返す際のヘッダについて
上記はあくまでざっくりしたサンプルなので echo $post_id; ってやってますが、この場合WordPressはヘッダで Content-Type: text/html; charset=UTF-8を送出します。JSONデータを返したい場合これでは困りますので、wp_send_json() 関数を使います。そうすると、ちゃんとヘッダで Content-Type: application/json; charset=UTF-8 が送出されるようになります。あと、admin_ajax.php を経由する場合は、処理の途中で send_nosniff_header() が記載されていますので、ヘッダで自動で X-Content-Type-Options: nosniff; が送出されています。このあたりはJSONデータをスクリプトとして実行されてしまう脆弱性の対策として必要ですが、ちゃんと配慮されているのはWordPressの良いところですね。
エスケープの件
wp_send_json() 関数内では json_encode() 関数が使われていて、JSONとしては正しいエスケープがなされていますが、レスポンスをHTMLとして評価する可能性がある場合は不十分でXSSが発生する可能性があります。そういう場合のオプションが json_encode() 関数には用意されているのですが、PHP5.3以降の実装となります。WordPressはPHP5.2をサポートしていますので json_encode() のオプションは使えません。
対策としてはJSONが意図せずスクリプトとして実行されるのが問題なので、jQuery.text() や jQuery.val() を使って、テキストノード、あるいは属性としてきちんと意図に合ったかたちでHTML内に埋め込まれるようにするのが良いと思います。文字列を+でつないだり横着するとダメ。あとは、サーバー側で esc_html() などを使ってHTMLエンティティとしてエスケープしておき、エスケープ済みの文字列をJSONで送るという手段もあります。あくまでHTML上での表示にしかデータを使わないよという場合であれば、後者でも問題なさそうです。
参考記事:PHPのイタい入門書を読んでAjaxのXSSについて検討した(1)