【WordPress】カスタムフィールド・タクソノミーによる絞込検索を実装する/実装編

WordPress検索に、カスタム分類(タクソノミー)やカスタムフィールドによる絞込を追加しようとしてsearchform.phpなどを編集しても、なかなかうまくいきません。

でも、要領をつかめば幾らでも応用が効くようになります。

今回は、ざっと以下のステップで、カスタムフィールド・タクソノミーによる絞込検索を実装していきます。

  1. WordPressの検索機能の仕組みをさくっと理解する
  2. functions.phpにカスタムクエリ変数を仕込む
  3. 検索フォームをカスタマイズする
  4. 検索結果を表示する

前の記事でWordpress検索に機能を追加するための予備知識を解説しました。

ここからは実用的なコードを解説していきます。

具体的な例があったほうがイメージしやすいので、今回は簡単な物件検索システムを念頭に実装してみたいと思います。

実例の設定

以下の設定で検索機能を実装していきたいと思います。(括弧内はスラッグ)

  • プラグイン「Custom Post Type UI」でカスタム投稿タイプとタクソノミーを追加
  • プラグイン「Advanced Custom Fields」でカスタムフィールドを追加
  • カスタム投稿タイプ
    • 「物件」(property)【検索対象】
  • カスタムタクソノミー
    • 「エリア」(area)【セレクトボックスで1つ選択可】
    • 「種別」(kind)【チェックボックスで複数選択可】
  • カスタムフィールド
    • 「間取り」(room)【チェックボックスで複数選択可】
    • 「特徴」(feature)【チェックボックスで複数選択可】
    • 「賃料」(rent)【価格が安い順に表示】

家賃○○円~△△円で絞込とか、表示順を選択とかもできるんですが、手広くするとコードが長くなるので、今回はシンプルにこんな感じでいきます。

記事タイトルで「プラグインなし」って言ってますが、絞込機能に関してはってことで、ACFとかは当たり前のように使います。

カスタム検索のfunctions.php

まずは実際にクエリを処理するコードをfunctions.phpに書いていきます。

考え方は前の記事で書いた通りです。

functions.php

<?php
// custom taxonomy
function tax_list(){
    $tax = array(
        'area',
        'kind'
    );
    return $tax;
}
// custom field
function field_list(){
    $key = array(
        'room',
        'feature'
    );
    return $key;
}

// カスタムクエリ変数
function add_query_vars_filter( $vars ){
    foreach( tax_list() as $tax ){
        $vars[] = $tax;
    }
    foreach( field_list() as $key ){
        $vars[] = $key;
    }
    return $vars;
}
add_filter( 'query_vars', 'add_query_vars_filter' );

// 検索ページでのクエリ処理
function custom_query( $query ) {
    if( !is_admin() && $query->is_main_query() ){

        if( $query->is_search() ){
            // term
            $tax_query = array();
            foreach( tax_list() as $tax ){
                $terms = $query->get($tax);
                if( !$terms ) continue;
                $tax_query[] = array(
                    'taxonomy' => $tax,
                    'terms'=> $terms
                );
            }
            // custom field
            $meta_query = array();
            /*$meta_query = array( 'relation' => 'OR' ); // OR検索にする場合*/
            foreach( field_list() as $key ){
                $value = $query->get($key);
                if( !$value ) continue;
                $meta_query[] = array(
                    'key' => $key,
                    'value' => $value,
                );
            }
            // order
            $orderby = 'meta_value_num';
            $meta_key = 'rent';
            $order = 'ASC';

            $query->set( 'meta_query', $meta_query );
            $query->set( 'tax_query', $tax_query );
            $query->set( 'orderby', $orderby );
            $query->set( 'meta_key', $meta_key );
            $query->set( 'order', $order );
        }

    }
}
add_action( 'pre_get_posts', 'custom_query' );

上から説明していきます。

カスタムクエリ変数を追加する

まず、カスタムフィールドとタクソノミーの名前(スラッグ)は繰り返し使うので、配列に入れて関数にしておきます。

二重配列にして1個にまとめた方がコードはスッキリするかもしれないですが、ここは分かりやすさを優先しています。

query_varsフィルターフックで、上記のスラッグをクエリ変数として使えるようにします。

実質的にはこうしているのと同じです。

<?php
function add_query_vars_filter( $vars ){
    $vars[] = 'area';
    $vars[] = 'kind';
    $vars[] = 'room';
    $vars[] = 'feature';
    return $vars;
}
add_filter( 'query_vars', 'add_query_vars_filter' );

ちなみにCustom Post Type UIを使ってタクソノミーを追加している場合は、デフォルトで自動的にクエリ変数に追加されるのでこの処理が不要です。

手動でやりたいなら、CPT UIのタクソノミーの編集で「クエリ変数」を「偽(false)」にします。

pre_get_postsでクエリを処理する

pre_get_postsアクションフックで、検索ページのクエリを変更する処理を加えます。

各タクソノミーは、検索フォームからタームIDを受け取るようにして、タクソノミーごとに「tax_query」に収めていきます。

カスタムフィールドも同様に、検索フォームから受け取る値を「meta_query」に収めます。

(この例では使用しませんが、meta_queryの要素に「compare」を加えれば比較も可能なので、『○○以上/以下』とか『○○~△△』という絞込もできます。)

さらにカスタムフィールド賃料(rent)の値が小さい順に表示するため、「orderby」に「meta_value_num」、「meta_key」に「rent」、「order」に「ASC」を指定します。

検索キーワード「s」と投稿タイプ「post_type」は元のクエリに反映されているので、処理を追加する必要なし。

以上でfunctions.phpは完成です。

検索フォーム

次は検索フォームの実装です。

使用するテーマに大抵searchform.php的なものがあるので、それを元に書き加えていくと良いでしょう。

通常の検索ボックスと別にして使いたいなら、ファイル名をsearchform-custom.phpとかに変更して、以下のコードで呼び出します。

get_template_part('searchform-custom');

searchform-custom.php

見通しをよくするために、最低限のマークアップにしてあります。

とはいえさすがに長いです。

<?php
// 検索キーワードを取得
$s = isset($_GET['s']) ? $_GET['s'] : null;
// 投稿タイプを指定
$post_type = 'property';
?>
<form method="get" action="<?php echo home_url('/'); ?>">
    <input type="hidden" name="post_type" value="<?php echo $post_type; ?>">
    <div>
        <label for="keyword">物件名で検索</label>
        <input id="keyword" type="text" placeholder="<?php _e( 'キーワード', THEME_NAME ) ?>" name="s" value="<?php echo esc_attr($s); ?>">
    </div>
    <div>
        <label for="area">エリアで絞り込む</label>
        <select id="area" name="area">
            <option value="">都道府県を選択</option>
            <?php
            // 全termを取得
            $areas = get_terms( 'area', array(
                'orderby' => 'id',
                'hide_empty' => false
            ));

            // 選択済term あれば取得/なければ空文字
            $s_area = get_query_var('area', '');

            foreach( $areas as $area ){
                // 選択済termと一致したらchecked
                $selected = $area->term_id == $s_area ? ' selected' : '';
                // 0件だったらdisabled
                $disabled = $area->count < 1 ? ' disabled' : '';
                ?>
                <option value="<?php echo $area->term_id; ?>"<?php echo $selected. $disabled; ?>><?php echo $area->name; ?></option>
            <?php } ?>
        </select>
    </div>
    <div>条件で絞り込む</div>
    <?php
    checkbox_term(array( // taxonomy slug
        'kind'
    ));
    checkbox_custom_field(array( // field_labelかfield_key (ACF)
        'field_*************', //room
        'field_*************', //feature
    ));
    ?>
    <button type="submit">検索する</button>
</form>

<?php
// function
function checkbox_term($taxs){
    foreach( $taxs as $tax ):
        // 全termを取得
        $terms = get_terms( $tax , array(
            'orderby' => 'term_id'
        ));
        if( empty($terms) ) continue;
        
        // 選択済term あれば取得/なければ空の配列
        $s_term= get_query_var( $tax, [] );

        // taxonomyのラベル
        $label = get_taxonomy($tax)->label;
        ?>
        <div><?php echo $label; ?></div>
        <div>
            <?php
            foreach( $terms as $term ){
                // 選択済termと一致したらchecked
                $checked = in_array($term->term_id, $s_term) ? ' checked' : '';
                ?>
                <div>
                    <input type="checkbox" id="<?php echo $term->slug; ?>" name="<?php echo $tax; ?>[]" value="<?php echo $term->term_id; ?>"<?php echo $checked;?>>
                    <label for="<?php echo $term->slug; ?>"><?php echo $term->name; ?></label>
                </div>
            <?php } ?>
        </div>
    <?php
    endforeach;
}
function checkbox_custom_field($fields){
    foreach( $fields as $key ):
        // カスタムフィールドのデータを取得(ACF関数)
        $field = get_field_object($key);

        // 選択済フィールド値 あれば取得/なければ空の配列
        $s_field = get_query_var( $field['name'], [] );
        ?>
        <div><?php echo $field['label']; ?></div>
        <div>
            <?php
            foreach( $field['choices'] as $choice ){
                // 選択済フィールド値と一致したらchecked
                $checked = in_array($choice, $s_field) ? ' checked' : '';
                ?>
                <div>
                    <input type="checkbox" id="<?php echo $field['name'] . $choice; ?>" name="<?php echo $field['name']; ?>[]" value="<?php echo $choice; ?>"<?php echo $checked; ?>>
                    <label for="<?php echo $field['name'] . $choice; ?>"><?php echo $choice; ?></label>
                </div>
            <?php } ?>
        </div>
    <?php
    endforeach;
}
?>

解説していきまーす。

検索キーワードと投稿タイプ

検索クエリ変数「s」のくだりは基本なので説明不要でしょう。

投稿タイプは今回の例では「物件」で固定なので「property」が入れてあります。

Form要素の書き出し

<form>要素は定型ですね。

下記の隠し要素で投稿タイプを送信します。

<input type="hidden" name="post_type" value="<?php echo $post_type; ?>">

<form>を以下のようにすれば、検索先を投稿タイプアーカイブページにすることもできます。

<form method="get" action="<?php echo get_post_type_archive_link( $post_type ); ?>">

この場合は検索クエリにpost_typeを加える必要がなくなります。

タクソノミー(ターム)で絞り込む(セレクトボックス)

キーワード検索は普通なので説明飛ばして次に行きましょう。

エリアをセレクト要素で選択できるようにします。

<select>のname属性はタクソノミーのスラッグ「area」です。

まずはタクソノミー「area」に含まれるターム(要するに都道府県)をget_terms()で全部取得します。

タームID順に並べると良い感じになるという前提で「ordreby」は「id」にしてあります。

投稿数がゼロのタームも表示する場合は「hide_empty」を「false」にします。

get_query_var()でクエリ変数を取得する

検索結果ページ上で検索フォームを見たときに、現在の検索クエリがどうなっているのか分かるようになっていないと困りますよね。

そこでget_query_var()を使って現在の検索クエリに含まれているタームやフィールド値を取得していきます。

第1引数に変数名、第2引数にデフォルト値を指定します。値がなければデフォルト値を出力してくれます。

$s_area = get_query_var('area', '');

あとはforeach()で、各タームを<option>に入れていきます。

value属性にはタームIDを入れます。ここをスラッグにしたい場合は、functions.phpの方でtax_queryを調整する必要があります。

投稿数がゼロの時「disabled」、選択されているタームの時「selected」が入るようにします。

  <label for="<?php echo $tax; ?>">都道府県で絞り込む</label>
  <select id="<?php echo $tax; ?>" name="<?php echo $tax; ?>">
    <option value="">都道府県を選択</option>
    <?php
    $terms = get_terms( $tax, array(
      'orderby' => 'id',
      'hide_empty' => false
    ));
    foreach( $terms as $term ):
      $disabled = $term->count < 1 ? " disabled" : "";
      $selected = $term->term_id == $s_term ? ' selected' : '';
      ?>
      <option value="<?php echo $term->term_id; ?>"<?php echo $disabled. $selected; ?>><?php echo $term->name; ?></option>
    <?php endforeach; ?>
  </select>

タクソノミー(ターム)で絞り込む(チェックボックス)

後でタクソノミーを変えたり増やしたりできるように関数化しました。

基本的にはセレクトボックスの時と同じですね。

大きな違いは、複数値を取るため、値が配列になること。

<input>のname属性に「[]」を付ける

nameに「[]」を付けると値が配列に格納されます。この配列はtax_queryのtermsの値としてそのまま使えます。

チェックボックスの部分をhtmlにするとこんな感じになります。

<div>
    <input type="checkbox" id="apart" name="kind[]" value="101">
    <label for="apart">アパート</label>
</div>
<div>
    <input type="checkbox" id="mansion" name="kind[]" value="102">
    <label for="mansion">マンション</label>
</div>
<div>
    <input type="checkbox" id="house" name="kind[]" value="103">
    <label for="house">一戸建て</label>
</div>

選択済みタームのチェックにin_array()を使う

タームが配列になるので、都道府県の時には文字列の比較でこうしていたところを‥‥‥

$term->term_id == $s_term

in_array()を使ってこうします。

in_array($term->term_id, $s_term)

さらに、$s_termが空のときに単にnullだとin_array()がエラーを吐いてしまうので、空の配列になるようにしておきます。

$s_term= get_query_var( $tax, [] );

全タクソノミーを表示させる場合

以下のコードで、カスタム投稿タイプに紐づけられた全タクソノミーを表示できます。

$taxs = [];
$taxonomies = get_object_taxonomies( $post_type, 'objects' );
foreach ( $taxonomies as $slug => $taxonomy ){
    if( $slug == 'area' ) continue;
    $taxs[] = $slug;
}
checkbox_term( $taxs );

get_object_taxonomies()で、指定した投稿タイプで有効なタクソノミーのリストを取得します。

foreachで各タクソノミーのスラッグを$taxsに入れていきますが、既に表示済みのタクソノミー(エリア)はスキップします。

あとは$taxsをセットしてcheckbox_term()を実行すればOKです。

カスタムフィールドで絞り込む(チェックボックス)

カスタムフィールドはAdvanced Custom Fields(ACF)でチェックボックス形式にした前提となっています。

WordPressのカスタムフィールド関係の関数って、投稿に含まれるカスタムフィールドを出力する関数しかないので、ACF独自の関数を使うことにします。

get_field_object()でカスタムフィールドのデータを取得する

ACF関数get_field_object()で、各カスタムフィールドのデータ(フィールド名・フィールドラベル・選択肢など)を取得します。

ACF関数の使い方はこちらの記事が詳しいです。Advanced Custom Fieldsの関数の全部の使い方を調べてみた|エス技研

引数はフィールド名かフィールドキーで、ここではフィールドキーを使います。第2引数以降は省略。

フィールド名のほうが分かりやすいのに、なぜフィールドキーを使っているかというと‥‥‥

get_field_object()は「その投稿に保存されている、カスタムフィールド○○の情報を取得」する関数です。(第2引数で投稿IDを指定できる。)

よって、そのページでカスタムフィールドが使われていなければ何も取得できません。

検索フォームがどのページ上でも表示されるようにしたい場合、これでは不都合です。

でもこの関数、フィールドキーで指定すると、そのカスタムフィールドが使われているかどうかに関係なくデータを取得できる仕様になっているのです。

だから、フィールドキーを使えば、どのページでもカスタムフィールドの情報を取得できるというわけ!

フィールドキーは、ACFのフィールドグループの編集画面で確認できます。Keyが表示されない場合は表示オプションを変更してください。

あとはタクソノミーの時と大体同じです。

get_field_object()の返り値をうまく使って埋めていきます。

カスタムフィールドのタイプがSelectやラジオボタンやチェックボックスの時だけ「choices」という、選択肢のパラメータが表れます。

<input>のid属性をフィールド名+選択肢としたのは、異なるカスタムフィールドの間で選択肢の値が重複することもあり得るから。(「なし」「その他」とか。)

検索結果を表示する

以上でカスタムフィールド・タクソノミーによる絞込機能付きの検索フォームができました。

あとは検索ページ(search.php)側ですが、特に言うことはないですかね。

クエリの処理はfunctions.phpで完結しているので、これでちゃんと絞り込まれた物件の一覧が表示されるはずです。

検索結果や物件ページでは、get_the_terms()やget_field()などを使ってタクソノミーやカスタムフィールドを出力させます。

必要に応じて、上記のsearchform-custom.phpで使った論理を応用できるかもしれません。

まとめ

やりたいことだけ実装するなら、割と短いコードで絞込検索を実装することができます。

管理画面から手軽にアレンジできるようにするには、観念してVK Filter Search Proなどのプラグインを使いましょう。

特定の条件を想定してコードを書いたので、丸ごとコピペで使えるということはないと思いますが、参考になれば幸いです。

もしもお気づきの点や分からないことがありましたら、お問い合わせください。

コメント