Blog

concrete5でAjaxする方法をざっくりまとめておく

Posted by admin at 20:47 日時 2014/01/18

concrete5でAjaxを使って情報を取得する要件がありまして、concrete5でAjaxは使ったことがなかったんですが、やってみたらすごく簡単だったので、作例付きで解説してみます。考えてみれば、concrete5の編集モードは巨大なAjaxアプリケーションなので、Ajaxがやりやすいようにそもそも設計されているわけですね。

まず基本ですが、Ajaxリクエストを投げる先になるphpは tools に設置します。Tools はconcrete5のコンポーネントの中でも、Ajaxや何らかの処理のトリガーになるなど、通常のページへのアクセスとは異なるリクエストを処理する目的全般に使用します。

使い方は簡単で、/tools ディレクトリに置いたPHPはそのまま動作します。例えば、

/tools/tools_test.php

にPHPを設置した場合、次のURLでアクセスすることができます。

/index.php/tools/required/tools_test/

例えば、tools_test.php の中に次のようなコードを記述すると、上記のURLにアクセスした際にサイト名が表示されます。

<?php  defined('C5_EXECUTE') or die("Access Denied.");    echo SITE;

定数 SITE を何の処理もなく使えたことに注目してください。単にPHPを置いただけなのに、toolsではすでにconcrete5のAPIが使える状態になっています。この単にPHPを置いただけでURLが発行されてすぐに使える、というのが便利なところです。

ログインユーザーによって権限をチェックする場合は、UserオブジェクトやPermissionオブジェクトを使って権限をチェックしてください。権限のチェック方法に付いては、過去記事「concrete5の権限の実装を知る&ページ権限をPHPから変更する」も参考にしてください。

権限の検証例:

// リクエストパラメーターの検証  if (!Loader::helper('validation/numbers')->integer($_REQUEST['cID'])) {  	die(t('Access Denied'));  }    // リクエストパラメーターを元にPageオブジェクトを取得  $c = Page::getByID($_REQUEST['cID']);    // 権限オブジェクトを取得  $p = new Permissions($c);  // 読み込み権限の検証  if (!$p->canRead()) {  	die(t('Access Denied'));  }    // 権限がある場合の処理を続けて書く

また、何かをデータベースに保存する場合は、権限の検証だけでなく、トークンを使ってCSRFを予防することが必須です。権限の検証だけで保存処理を通してしまうと、不正なJavascriptを埋め込むことにより、管理者権限で知らないうちにデータを消したりといった攻撃が可能になってしまいます。

トークンは、まずAjaxリクエストを送る側で生成します。トークンのキーは任意ですが、できるだけ行なう処理に固有のキーを指定した方がいいです。下記の例でのキーは “my_request_key” です。

// validation/token ヘルパーの呼び出し  $valt = Loader::helper('validation/token');    // hiddenタグで出力  echo $valt->output('my_request_key');    // getパラメーターとして渡す場合  $url = REL_DIR_FILES_TOOLS_REQUIRED   . '/tools_test?cID=' . $cID   . '&' . $valt->getParameter('my_request_key');

次に、Ajaxリクエストを受け取る側でトークンを検証します。

$valt = Loader::helper('validation/token');  if ($valt->validate('my_request_key')) {  // 検証に通った場合の処理  } else {  // エラー  }

そんなに難しいものではありません。トークンを発行して、検証する、これだけ守っておけばOKです。

参考:Use tokens to secure transactions

以上が概略になりますが、これだけではピンと来ないと思いますので、動く作例も用意してみました。今回作成したのはAjaxで動くページリストのカスタムテンプレートです。ページリストはURLに ?ccm_paging_p_b00=2 のようにパラメーターを付けることでページ送りができますが、フルページキャッシュを使うと動的な処理が行なわれないので、パラメーターが無視されてしまいます(そのため、ページリストブロックはフルページキャッシュの設定が「該当のページ上のブロックで許可されていれば」の設定の時、そのページのフルページキャッシュの生成をキャンセルします)。Ajaxを使えば、フルページキャッシュがオンの状態でもページ送りを動かすことができちゃいます。

まず、ページリストブロックタイプのカスタムテンプレートを「ajax」という名前で作成することにします。

/blocks/page_list/templates/ajax/view.php

<?php  defined('C5_EXECUTE') or die("Access Denied.");    $b = $this->getBlockObject();  $bid = $b->getBlockID();    ?>  <div id="ccm-ajax-page-list" data-bid="<?php echo $bid; ?>"></div>  <div id="ccm-ajax-page-list-loading">Loading...</div>

中身はAjaxを使って読み込むので、「Loading…」という表示だけのテンプレートになっています。
また、ブロックIDを取得して data-bid="<?php echo $bid; ?>" というコードで表示しています。

Ajax処理のためにJavascriptが必要ですので、カスタムテンプレートのフォルダにjsファイルを設置します。カスタムテンプレートのフォルダに設置した view.js と view.css は自動で読み込まれます。

/blocks/page_list/templates/ajax/view.js

$(function(){  	$.ajax({  		url: CCM_TOOLS_PATH + '/page_list',  		type: 'get',  		data: {  			bID: $('#ccm-ajax-page-list').attr('data-bid')  		}  	}).done(function(response){  		$('#ccm-ajax-page-list-loading').hide();  		$('#ccm-ajax-page-list').append(response);  		ccm_ajaxPageListPagination();  	});  });    var ccm_ajaxPageListPagination = function(){  	$('#ccm-ajax-page-list #pagination a').click(function(e){  		e.preventDefault();  		  		$('#ccm-ajax-page-list').empty();  		$('#ccm-ajax-page-list-loading').show();  		  		var linkurl = $(this).attr('href');  		  		$.ajax({  			url: linkurl  		}).done(function(response){  			$('#ccm-ajax-page-list-loading').hide();  			$('#ccm-ajax-page-list').append(response);  			ccm_ajaxPageListPagination();  		});  	});  }

CCM_TOOLS_PATH は、headタグ内で出力されているJavascript用の定数で、上記コードの場合 /index.php/tools/required/page_list になります。PHPでtoolsのURLを取得するには、次のように書けます。

$uh = Loader::helper('concrete/urls');  $toolURL = $uh-&gt;getToolsURL('page_list');

リクエストパラメーターとして、view.php で出力しているブロックIDを送っています。Ajaxリクエストが成功した場合、Loading表示を隠し、取得したHTMLを div id="ccm-ajax-page-list" の中に入れています。

ccm_ajaxPageListPagination 関数は、Ajaxで取得したページリストのページ送りのaタグを、リンクではなくAjaxリクエストに変換しているものです。

以上で、Ajaxリクエストを送る側の処理は以上です。次に受け取り側のtoolsを作成します。

/tools/page_list.php

&lt;?php  defined('C5_EXECUTE') or die(&quot;Access Denied.&quot;);    // リクエストパラメーターの検証  if (!Loader::helper('validation/numbers')-&gt;integer($_REQUEST['bID'])) {  	die(t('Access Denied'));  }    // Blockオブジェクトの取得  $bID = $_REQUEST['bID'];  $b = Block::getByID($bID);    // Blockが存在する場合  if (is_object($b) &amp;&amp; !$b-&gt;isError()) {  	  	// BlockTypeコントローラーを取得  	$btc = $b-&gt;getController();  	  	// ページリストブロックタイプのコントローラーの  	// getPageListメソッドを使ってページを取得します  	$pl = $btc-&gt;getPageList();  	  	if ($pl-&gt;getItemsPerPage() &gt; 0) {  		$pages = $pl-&gt;getPage();  	} else {  		$pages = $pl-&gt;get();  	}  	  	$nh = Loader::helper('navigation');  	  	// ページネーションを生成します。  	// ページリストブロックタイプのコントローラーに  	// 書いてある処理と同じです  	$showPagination = false;  	$paginator = null;  	if ($btc-&gt;paginate &amp;&amp; $btc-&gt;num &gt; 0 &amp;&amp; is_object($pl)) {  		$description = $pl-&gt;getSummary();  		if ($description-&gt;pages &gt; 1) {  			$showPagination = true;  			$paginator = $pl-&gt;getPagination();  		}  	}  	  	// データを配列にセットします  	$data = array(  		'pl' =&gt; $pl,  		'pages'	 =&gt; $pages,  		'nh' =&gt; $nh,  		'showPagination' =&gt; $showPagination,  		'paginator' =&gt; $paginator  	);  	  	// データをelementに渡します  	Loader::element('page_list', $data);    }

コメントに書いた通りの処理ですが、基本的にはページリストブロックタイプのコントローラーに書いてある処理を持ってきただけです。そして、最後にデータをelementに渡しているのがポイントです。elementとは、その名の通り、concrete5で使われる要素、パーツ単位の見た目でテーマにもブロックテンプレートにも当てはまらないものを指すコンポーネントです。この作例では、page_list というelementに値を渡しています。

/element/page_list.php

このファイルの中身は、concrete/blocks/page_list/page_list/view.php と同じものです。つまり、ブロックテンプレートをそのまま使っていると言うわけです。

このようにして、無事ページリストブロックの表示をAjaxにすることができました!

なお、上記の作例では説明を簡略化するため、このブロックの閲覧権限の有無のチェックと、そもそも指定されたブロックIDがページリストブロックなのかの検証を省略しています。上級権限モードに対応するならチェックした方が良いでしょう。権限のチェックまで行なったサンプルコードはGitHubにアップしていますので、こちらもご参考までに。

https://github.com/hissy/c5_ajax_page_list

それでは、よいconcrete5ライフを!


Share this entry