WordPressのデフォルトでは、カテゴリー(タクソノミー)別のアーカイブ、日付別のアーカイブなどが用意されていますが、商品や物件、店舗などの多くの情報を掲載するサイトでは、複数条件での検索を実装したい場合があります。
こうした複数条件での検索を、pre_get_postsフィルターへのフック等を用いて実装する方法のご紹介です。
検索条件は極力タクソノミーにする
例えば、productsという投稿タイプに対して、item、brand、targetという3つのタクソノミーがあったとします。
これを、3つのタクソノミーの組み合わせで検索できるようにしてみましょう。こんな風に。
まずはテンプレートに、以下のような検索フォームを用意します。
※わかりやすいように、HTMLで記述しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
<form action="/products/" method="get"> <table> <tr> <th>ブランド</th> <td> <select name="brand"> <option value="">ブランドを選択</option> <option value="converse">CONVERSE</option> <option value="newbalance">NEW BALANCE</option> <option value="nike">NIKE</option> <option value="vans">VANS</option> </select> </td> </tr> <tr> <th>アイテム</th> <td> <label><input type="checkbox" name="item[]" value="slipon">スリッポン</label> <label><input type="checkbox" name="item[]" value="deckshoes">デッキシューズ</label> <label><input type="checkbox" name="item[]" value="highcut">ハイカット</label> <label><input type="checkbox" name="item[]" value="lowcut">ローカット</label> </td> </tr> <tr> <th>対象</th> <td> <label><input type="checkbox" name="target[]" value="ladies">LADIES</label> <label><input type="checkbox" name="target[]" value="mens">MENS</label> <label><input type="checkbox" name="target[]" value="unisex">UNISEX</label> </td> </tr> </table> <p><button type="submit">検索</button></p> </form> |
ポイントは、送信先(action)を、productsのアーカイブページである /products/ とすること。
(アーカイブテンプレートである archive-products.php には、標準ループが記述されていることが前提)
フォーム要素のinputやselectのnameを、タクソノミー名と合わせること。また、value値はタームのスラッグとすることです。
これだけで、サービス側は一切カスタマイズすることなく(!)、複数条件での検索が実装できます。
URLパラメータとクエリ変数(query_vars)
上記のようになるのは何故でしょうか?
上記の検索フォームで検索した結果のURLは、例えば以下のようになります。
http://example.com/products/?brand=vans&item%5B%5D=slipon&target%5B%5D=mens&target%5B%5D=unisex
この時、検索条件が格納されている、グローバル変数 $wp_query->query_vars (以下、クエリ変数)を見てみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
WP_Query Object ( [query_vars] => Array ( [post_type] => products, [brand] => vans, [target] => Array ( [0] => mens [1] => unisex ), [item] => Array ( [0] => slipon ), //以下略 ) //以下略 ) |
必要な検索条件が全てセットされています。
WordPressでは、パーマリンク設定がデフォルトのとき、
デフォルト投稿のシングルページ
http://example.com/?p=123
カスタム投稿のアーカイブページ
http://example.com/?post_type=products
タクソノミーのアーカイブページ
http://example.com/?item=slipon
のように、GETパラメータを用いてURLが表現されます。
これらのいくつかのパラメータやタクソノミー名などは、パーマリンク設定を変更している場合であっても、WordPressがURLを解析する際に、自動的にクエリ変数に取り込まれます。
これらのパラメータが複数設定されていても同様で、複数条件での検索が実行されるのです。
タクソノミーで表現できない検索条件
例えば、文字列検索、範囲(From/To)検索、など、リストからの選択以外の検索を行う場合は、カスタムフィールドを用います。
例えば、投稿タイプproductsに、カスタムフィールド code と price を追加し、全社を部分一致の文字列検索、後者を数値の範囲検索とする場合、
code
今回は、入力要素のnameを、WordPressのデフォルトパラメータと重ならないようにしなければなりません。
検索処理は、pre_get_postsフィルターに、クエリ変数(query_vars)を書き換える処理をフックすることで実装します。
カスタムフィールドを検索条件に加える場合は、クエリ変数 meta_query を使います。
直感的にわかりやすいのは、以下のような実装でしょう。
code
これでも良いのですが、メインループ以外でproductsのデータを取得するときに、GETパラメータの影響を受けてしまう可能性があったり、逆にメインループ以外でこれらの絞込み条件を使い回せないなどの課題があります。
以下のようにした方が、よりWordPressの「お作法」に乗っ取った実装といえるでしょう。
code
まず、query_varsフィルタにフックした products_query_vars() で、WP_Queryクラスのquery_varsプロパティに、新たなキーを追加しています。
これにより、これらのキーに一致するURLパラメータも、URL解析の対象となり、クエリ変数(query_vars)に格納されます。
これらのオリジナルのクエリ変数は、検索に影響しないので、pre_get_posts フィルタにフックした処理で、検索に利用できるクエリ変数に変換します。
ソート順・表示件数の操作
ついでに、ソート順や表示件数をフォームで指定できるようにする方法も見ておきましょう。
まずはテンプレート側。
ソート順はクエリ変数 order と orderby、表示件数はクエリ変数 posts_per_page に渡せば良いのですが、やはりこれらはURLパラメータに付与しても、デフォルトではクエリ変数に渡ってくれません(プライベートクエリ変数:private_query_vars)。
そこで、カスタムフィールド検索と同様に、以下のように実装します。
code
おまけ:タクソノミー検索用のフォーム入力要素
タクソノミー検索を実装する際、該当するタームの一覧をselect要素のoptionやcheckboxにすることが多いですが、頻出かつ意外と手間なので、以下のように関数化しておくと便利です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
function my_tax_selectbox($tax,$arg,$default=null) { $qv = get_query_var($tax); $terms = get_terms($tax,$arg); $output = ''; $output .= '<select name="'.$tax.'">'."\n"; if($default) { $output .= '<option value="">'.$default.'</option>'."\n"; } foreach($terms as $t) { $selected = ''; if(is_array($qv)) { if(in_array($t->slug,$qv)) $selected = ' selected'; } else { if($t->slug==$qv) $selected = ' selected'; } $output .= '<option value="'.$t->slug.'"'.$selected.'>'.$t->name.'</option>'."\n"; } $output .= '</select>'; echo $output; } function my_tax_checkbox($tax,$arg) { $qv = get_query_var($tax); $terms = get_terms($tax,$arg); $output = ''; foreach($terms as $t) { $checked = ''; if(is_array($qv)) { if(in_array($t->slug,$qv)) $checked = ' checked'; } else { if($t==$qv) $checked = ' checked'; } $output .= '<label><input type="checkbox" name="'.$tax.'[]" value="'.$t->slug.'"'.$checked.'>'.$t->name.'</label>'."\n"; } echo $output; } |
こんな風に使うと、冒頭に記載したようなフォーム要素が出力されます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
<form action="<?php echo get_post_type_archive_link('products'); ?>" method="get"> <table> <tr> <th>ブランド</th> <td> <?php $arg = array('hide_empty'=>false); echo my_tax_selectbox('brand',$arg,'ブランドを選択'); ?> </td> </tr> <tr> <th>アイテム</th> <td> <?php $arg = array('hide_empty'=>false); echo my_tax_checkbox('item',$arg); ?> </td> </tr> <tr> <th>対象</th> <td> <?php $arg = array('hide_empty'=>false); echo my_tax_checkbox('target',$arg); ?> </td> </tr> </table> <p><button type="submit">検索</button></p> </form> |