今月は、私の課題としていたHTMLフォームと向き合う機会に恵まれました。その経験をみなさんと共有したいと思います。
フォームを活用する場面としては、応用編の課題が挙げられます。各フロアのショップをカテゴリーで絞り込む機能は、PHPで実装すべきだと以前から考えていました。
私の技術レベルでは難しかったものの、ようやく完成させることができました。
このブログで紹介しているコードは、ブロックテーマ、カスタム投稿タイプ、カスタムタクソノミーを使用していることを前提に書かれています。また、セキュリティ対策が十分ではありません。それらを考慮してお読みください。応用編課題のショップガイドを作ってみようと合わせて読むことをおすすめします。
HTMLフォームの基本
HTMLフォームは、Webページ上でユーザーから情報を入力してもらうための仕組みです。入力された情報は、サーバーに送信され、処理されます。詳しく学びたい方は、ウェブフォームの構成要素 – ウェブ開発を学ぶ | MDNを参照してください。
form要素
フォームのデータを送信する方法を定義する
属性を使用して、ユーザーが送信ボタンを押した際に送信されるリクエストを指定する
action属性:データを送信する先のURLを指定する(指定しない場合は、フォームが設置されているページのURLにデータを送信する)
method属性:データの送信方法を指定する
GETメソッド:指定したリソースを返すようサーバーに求める
POSTメソッド:HTTPリクエストの本文で提供したデータを見て適切な結果を返すようサーバーに求める
フォームコントロール
ユーザーから入力してもらったデータを収集するための要素(input要素やbutton要素など)
ファイル以外のフォームコントロールのname属性とvalue属性の値をname=value
のペアにし、アンパサンド(&)で結合して、サーバーに送る
name属性:サーバーで受け取る際の変数名、配列にすることも可能
value属性:送信される値
チェックボックス
チェックされた場合にのみ、値が送信される
チェックされた値がない場合、 nameがonという値で送信される
関連するチェックボックス項目には、同じname属性を使用する
送信ボタン
フォームのデータをサーバーに送信する
送信ボタンは、button要素とinput要素のどちらも同じように動作する
HTTPリクエストの表示
ブラウザの開発者ツールで、送信されたデータを確認できます。
- 開発者ツールを開く
- Networkタブを選択
- Nameタブからフォームが設置されているページのURLを選択
- Headersタブでリクエストヘッダーを確認
- Payloadタブで送信データを確認
セキュリティについて
HTMLフォームは、Webアプリケーションに対する最も一般的な攻撃経路の一つです。 自分自身を含めたユーザーからの入力は常に不確実なものとして扱い、サーバーに送信された全てのデータを検証し、適切に処理する必要があります。
HTMLフォームの脆弱性
XSS (クロスサイトスクリプティング):ユーザーが入力した内容がそのままWebページに表示され、他のユーザーがその内容を実行してしまう
SQLインジェクション:ユーザーが入力した内容がSQL文の一部として実行され、データベースを不正に操作される
CSRF (クロスサイトリクエストフォージェリ):他のWebサイトから不正なリクエストが送信され、ユーザーの権限で操作が行われてしまう
サーバーサイドでの対策
入力データの検証:入力されたデータが想定される範囲内であるか、不正な文字が含まれていないかなどをチェックする
サニタイジング:入力データのエスケープ処理などを行い、安全な形式に変換する
コードの前提条件
これから紹介するコードは、以下のタクソノミーとタームを作成している前提で書いています。
タクソノミー:shop_category、shop_floor
shop_categoryのターム:fashion、fashion-goods、lifestyle-goods-cosmetics、restaurant-cafe、sweets-foods、beauty-service
HTMLコード
複数のカテゴリーからひとつまたは複数を選択し、選択したカテゴリーに基づいて各フロアのショップを絞り込むためのフォームを作成します。
<form method="post">
<fieldset>
<legend>カテゴリーで絞り込む</legend>
<label><input type="checkbox" name="category[]" value="fashion">ファッション</label>
<label><input type="checkbox" name="category[]" value="fashion-goods">ファッショングッズ</label>
<label><input type="checkbox" name="category[]" value="lifestyle-goods-cosmetics">ライススタイルグッズ&コスメ</label>
<label><input type="checkbox" name="category[]" value="restaurant-cafe">レストラン&カフェ</label>
<label><input type="checkbox" name="category[]" value="sweets-foods">スイーツ&フード・フレッシュフード</label>
<label><input type="checkbox" name="category[]" value="beauty-service">ビューティ&サービス</label>
</fieldset>
<button type="submit" name="shop_category_filter" value="filtered">絞り込む</button>
</form>
フォーム
POSTメソッドを使用して、フォームが設置されているページに、選択したカテゴリーに基づく結果を返してもらう
フォームコントロール
fieldset要素:複数のチェックボックスをグループ化する
legend要素:グループ化したチェックボックスの内容を説明する
label要素:各チェックボックスにラベルを付ける
input要素 (type=”checkbox”):チェックボックスを作成し、複数のカテゴリを選択できるようにする
- name属性:選択されたカテゴリーの値を配列としてサーバーに送信するよう、すべてのチェックボックスに
category[]
という名前を付ける - value属性:各チェックボックスにカテゴリーのスラッグを割り当て、サーバー側でどのカテゴリが選択されたか判断できるようにする
button要素: フォームを送信するためのボタンを設置する
PHPコード
POSTリクエストから選択されたカテゴリーを取得し、それを基にshop_floorというタクソノミーのアーカイブページのメインクエリを変更して、投稿をフィルタリングします。
今回は、PHP EngineerというGPTにコードの作成とデバッグを手伝ってもらいました。
/**
* POSTリクエストから選択されたカテゴリーを取得する関数
*
* この関数は、POSTリクエストが送信された際に、'shop_category_filter'が設定されているか確認します。
* その後、選択されたカテゴリーがあれば配列として返します。
*
* @return array 選択されたカテゴリーのスラッグが入った配列を返します。
*/
function get_selected_categories() {
$selected_categories = [];
// リクエストメソッドがPOSTであり、'shop_category_filter'が設定されているか(ボタンがクリックされたか)確認
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['shop_category_filter'])) {
// 'category'が設定されているか、配列であるか確認
if (isset($_POST['category']) && is_array($_POST['category'])) {
// 選択されたカテゴリーを$selected_categories配列に設定
$selected_categories = $_POST['category'];
}
}
// 選択されたカテゴリーの配列を返す
return $selected_categories;
}
/**
* 選択されたショップカテゴリーで投稿をフィルタリングする関数
*
* この関数は、WordPressの 'pre_get_posts' アクションにフックし、
* メインクエリを選択されたカテゴリーでフィルタリングするために使用されます。
* 選択されたカテゴリーは 'get_selected_categories' 関数から取得されます。
*
* @param WP_Query $query メインのWordPressクエリオブジェクト。
*/
function filter_shop_by_category($query) {
// 管理ページのクエリではなく、メインクエリであるかを確認
if (!is_admin() && $query->is_main_query()) {
// 'shop_floor'タクソノミーのアーカイブページかを確認
if(is_tax('shop_floor')) {
// POSTリクエストから選択されたカテゴリーを取得
$selected_categories = get_selected_categories();
// 選択されたカテゴリーがある場合、クエリをそれに基づいてフィルタリング
if (!empty($selected_categories)) {
// 選択されたカテゴリーでフィルタリングするためのtax_query配列を作成
$tax_query_array = [
[
'taxonomy' => 'shop_category', // フィルタリング対象のタクソノミー
'field' => 'slug', // スラッグでフィルタリング
'terms' => $selected_categories, // 選択されたカテゴリーのスラッグ(配列)
]
];
// クエリにtax_queryを設定し、投稿をフィルタリング
$query->set('tax_query', $tax_query_array);
}
}
}
}
// 'filter_shop_by_category' 関数を 'pre_get_posts' アクションにフック
add_action('pre_get_posts', 'filter_shop_by_category');
HTTPメソッドとリクエストデータに関する関数
$_SERVER[‘REQUEST_METHOD’]:ページにアクセスする際に使用されたリクエストのメソッドを取得する
$_POST[‘キー’]:POSTメソッドで送信されたデータを取得する(’キー’の部分には、input要素のname属性に指定した値が入る)
変数と配列の判定に関する関数
isset($変数名):指定した変数が定義されていて、nullではないかどうかを調べる
is_array($変数名):指定した変数が配列かどうかを調べる
empty($変数名): 指定した変数が空かどうかを調べる
WordPress環境に関する関数
is_admin(): 現在のリクエストが管理インターフェイス ページに対するものかどうかを調べる
is_main_query():現在のクエリがメインクエリかどうかを調べる
is_tax():現在のクエリがカスタムタクソノミーのアーカイブページのものかどうかを調べる
WP_Queryクラスのタクソノミーパラメータ
tax_query (array):タクソノミーに関する条件を指定する
taxonomy (string):対象とするタクソノミーの名前
field (string):タクソノミータームを指定する方法(term_id、name、slug、term_taxonomy_id)
terms (int/string/array) :対象とするタクソノミーターム
詳しくは、WP_Queryクラスのタクソノミーパラメータを参照してください。
WordPressのフック
pre_get_posts:クエリが実行される直前に呼び出されるアクションフック
まとめ
HTMLフォームに加えて、WordPressのクエリも必要となるため、難しく感じた方もいらっしゃると思います。慣れるまで時間がかかるかもしれませんが、基本的な概念を理解すれば、あとは応用次第でさまざまなことができます。実際に手を動かして試してみることで、より深く理解できます。ぜひ、この記事を活かして、応用編の課題に挑戦してみてください。