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

Wordpress

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

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

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

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

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

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

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

実例の設定

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

  • カスタム投稿タイプ
    • 「物件」(property)【検索対象】
  • カスタムタクソノミー
    • 「エリア」(area)【セレクトボックスで1つ選択可】
    • 「種別」(kind)【チェックボックスで複数選択可】
  • カスタムフィールド
    • 「賃料」(rent)【範囲を指定。価格が安い順に表示】

私の場合、カスタム投稿タイプ・カスタムタクソノミー・カスタムフィールドはAdvanced Custom Fieldsで設定しています。別のプラグインやノープラグインでも構いません。

ACFでタクソノミーを設定する時の注意点

ここで紹介しているコードが全然機能せず「なんでやーっ!」とハマってしまいました。

原因はACFでタクソノミーを登録すると付いてくる便利機能のせいでした。

タクソノミー編集画面で、高度な設定>URL>「Publicly Queryable」を無効にしたら大丈夫でした。

カスタム検索のfunctions.php

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

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

functions.php

<?php
/* カスタムクエリ変数の設定 */
function custom_query_vars_filter($vars){
  $list = array(
    'area',
    'kind',
    'min_num',
    'max_num',
  );
  foreach($list as $item){
    $vars[] = $item;
  }
  return $vars;
}
add_filter( 'query_vars', 'custom_query_vars_filter' );

/* search.php に出力する */
function custom_set_search_query( $query ) {
  if( !is_admin() && $query->is_search() ){
    /* 投稿タイプ */
    $query->set('post_type', 'property');

    /* タクソノミー */
    $taxs = array(
      'area',
      'kind',
    );
    foreach($taxs as $tax){
      $term = get_query_var($tax, '');
      if($term){
        $tax_query[] = array(
          'taxonomy' => $tax,
          'terms' => $term
        );
      }
    }
    if(!empty($tax_query)){
      $query->set('tax_query', $tax_query);
    }

    /* カスタムフィールド(範囲指定) */
    $num = 'rent'; // 範囲適用するカスタムフィールド名
    $min_num = get_query_var('min_num', 0); // 下限デフォルト:0
    $max_num = get_query_var('max_num', null);
    if($min_num < $max_num){
      // 下限〜上限
      $meta_query = array(
        array(
          'key' => $num,
          'value' => array( $min_num, $max_num ),
          'type' => 'numeric',
          'compare' => 'BETWEEN',
        ),
      );
    }else{
      // 上限なし
      $meta_query = array(
        array(
          'key' => $num,
          'value' => $min_num,
          'type' => 'numeric',
          'compare' => '>=',
        )
      );
    }
    $query->set('meta_query', $meta_query);
    /* 表示順 */
    $orderby = 'meta_value_num';
    $meta_key = $num;
    $order = 'ASC'; // 小さい順
  }
}
add_action( 'pre_get_posts', 'custom_set_search_query' );

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

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

query_varsフィルターフックで、2つのタクソノミーと範囲指定のための下限値・上限値を有効なクエリ変数に追加します。

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

<?php
function custom_query_vars_filter( $vars ){
    $vars[] = 'area';
    $vars[] = 'kind';
    $vars[] = 'min_num';
    $vars[] = 'max_num';
    return $vars;
}
add_filter( 'query_vars', 'custom_query_vars_filter' );

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

pre_get_postsでクエリを処理する

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

まず投稿タイプ「property」の中で検索するので「post_type」をセットします。

次にタクソノミーです。検索フォームからタームIDを受け取り、タクソノミーごとに「tax_query」に収めていきます。

そしてカスタムフィールドの賃料を範囲指定する以下のステップ。

  • 下限値min_numと上限値max_numを取得
  • meta_queryで、カスタムフィールドrentの値が2つの数の間に該当する投稿を指定
  • 条件分岐で上限がない場合のmeta_queryも用意

ここでは下限値が未入力の場合は「0」になるようにしてあります。上限値が下限値より少ない場合は無視するようにしてあります。

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

検索キーワード「s」は検索クエリに入っているので、処理を追加する必要はありません。

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

検索フォーム

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

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

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

get_template_part('searchform', 'custom');

searchform-custom.php

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

<form role="search" method="get" id="searchform" action="<?php echo esc_url( home_url( '/' ) ); ?>">
  <input name="s" type="text" value="<?php echo get_search_query(); ?>" placeholder="キーワード">
  
  <?php
  /* セレクトボックス */
  $tax = 'area';
  $placeholder = 'エリアを選ぶ';

  // 選択中のterm
  $term_selected = get_query_var($tax, ''); 

  // タームリストを出力
  $terms = get_terms(array(
    'taxonomy' => $tax,
    'hide_empty' => false, // 空のタームも表示する場合
    'pad_counts' => true, // 子タームの件数を含める
  ));

  // 出力
  $html = "<select name='{$tax}'><option value=''>{$placeholder}</option>";
  foreach($terms as $term){
    $term_id = $term->term_id;
    $selected = $term_id == $term_selected ? ' selected' : ''; // 選択中のtermならselected
    $disabled = $term->count == 0 ? ' disabled' : ''; // 0件のタームはdisabled
    $term_name = $term->parent > 0 ? ' '.$term->name : $term->name; // 子termにインデントを付ける      
    $html .= "<option value='{$term_id}'{$selected}{$disabled}>{$term_name}</option>";
  }
  $html .= "</select>";
  echo $html;

  /* チェックボックス */
  $tax = 'kind';

  // 選択中のterm
  $term_selected = get_query_var($tax, []); // 複数なので配列

  // タームリストを出力
  $terms = get_terms(array(
    'taxonomy' => $tax,
    'orderby' => 'count', // 件数順にする場合
    'order' => 'DESC',
  ));

  // 出力
  $html = "<div>";   
  foreach($terms as $term){
    $term_id = $term->term_id;
    $checked = $term_id == $term_checked ? ' checked' : ''; // 選択中のtermならchecked      
    $term_name = $term->name;      
    $html .= "<label><input name='{$tax}[]' type='checkbox' value='{$term_id}'{$checked}>{$term_name}</label>";
  }
  $html .= "</div>";
  echo $html;

  /* 範囲指定 */
  $min_num = 'min_num';
  $max_num = 'max_num';

  // 入力された数値
  $min_value = get_query_var($min_num, '');
  $max_value = get_query_var($max_num, '');

  // 出力
  echo "<div>
    <input name='{$min_num}' type='number' min='0' step='10' value='{$min_value}'>〜
    <input name='{$max_num}' type='number' min='0' step='10' value='{$max_value}'>
    </div>";
  ?>

  <button type="submit">検索する</button>
</form>

解説していきまーす。

検索キーワード

検索クエリの「s」はget_search_queryで取得できます。

検索クエリで投稿タイプを指定する場合

今回はfunctions.phpの方で検索対象を物件に絞っているので不要ですが、もしここで投稿タイプを送信するなら、以下のように指定します。

<input type="hidden" name="post_type" value="property">

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

<form method="get" action="<?php echo get_post_type_archive_link('property'); ?>">

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

get_query_varでパラメータを取得する

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

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

$value = get_query_var('var', '');

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

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

タクソノミー検索するためのselect要素を作ります。

まずはタクソノミー「area」の全ターム(全エリア)をget_terms()で取得します。

並び順は手抜きでデフォルトのままです。

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

親タームも選べるようにしたいので「pad_counts」を「true」にします。

select要素のname属性にタクソノミーのスラッグ(area)を設定します。

あとはforeachで、各タームをoption要素に入れていきます。

value属性にはタームIDを入れます。ここをスラッグにする場合は、functions.phpの方でtax_queryを調整する必要があります。(’field’ => ‘slug’)

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

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

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

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

input要素のname属性に「[]」を付ける

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

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

<label><input name='kind[]' type='checkbox' value='111' checked>アパート</label>

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

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

$term_id == $term_selected

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

in_array($term_id, $term_checked)

さらに、選択済みタームがない場合、つまり$term_checkedが空の場合、単にnullだとin_array()がエラーを吐いてしまうので、デフォルト値を空の配列にしておきます。

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

カスタムフィールドの値で範囲指定

functions.phpで設定した下限値min_numと上限値max_numの入力欄を作ります。

input要素のnumberタイプでは最小値「min」やステップ「step」を設定できます。

ここはセレクトボックス風にしてみたり、上限値が下限値以下にならないようにjavascriptとかで制限してみたり、工夫のしどころかも。

送信ボタン

submitボタンを設置して完成です。

検索結果を表示する

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

あとは検索結果ページに適宜タクソノミーやカスタムフィールドが表示されるようにしましょう。

ここも最低限のマークアップで。

search.php

if(have_posts()):
	while(have_posts()): the_post();
		echo "<a href=". get_the_permalink().">";
		the_title('<h3>', '</h3>');
		echo "<table><tbody>";

		// タクソノミー
		$taxs = array(
			'area' => 'エリア',
			'kind' => '種別',
		);
		foreach($taxs as $tax => $label){
			echo "<tr><th>". $label."</th><td>";
			$terms = get_the_terms($post->ID, $tax);
			if($terms){
				$term_list = [];
				foreach($terms as $term){
					$term_list[] = $term->name;
				}
				echo implode('・', $term_list);
			}
			echo "</td></tr>";
		}
		// カスタムフィールド
		$num = get_post_meta($post->ID, 'rent', true);
		echo "<tr><th>賃料</th><td>". $num. "</td></tr>";

		echo "</tbody></table>";
		echo "</a>";

	endwhile;
else:
	echo "no post.";
endif;

まとめ

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

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

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

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

スポンサーリンク
スポンサーリンク
Wordpress
codeisleをフォローする

コメント

  1. 匿名 より:

    こちらを参考に実装を試みましたところ、
    ACFを用いたチェックボックス(今回のケースでいう「間取り」と「特徴」)が
    検索結果画面で絞り込みできず(何も表示されない)コメントさせていただきました。
    数日あれこれ試してみましたがどうにも解消できず躓いてます。
    チェックボックスは複数選択ですので、DBへ格納時にシリアライズされた値が原因により、
    $meta_queryに引っ掛けての検索が正常に機能しないのではないかという結論に至りました。

    表示デモがないため、
    Code Island様の環境で問題なく動作しているか気になるところではございますが、、

    ちなみクエリ変数は問題なく登録されていることは確認済みですので、
    クエリの処理($meta_queryの記述部分)の部分が疑わしいと思ったわけですが、
    何か解消策はございますでしょうか。
    ※タクソノミーで処理するなど別の方法もございますが、今回はカスタムフィールドのチェックボックスを用いた方法での解決策を模索しております。

    検証環境
    PHP:ver7.4.21
    wordpress: ver6.2
    ACF: ver6.1.4

    どうぞよろしくお願いいたします。

  2. pochi より:

    ご丁寧に教えて頂きありがとうございます。
    ACF初心者の為、とても勉強になります。
    「検索結果を表示する」→検索ページ(search.php)側の出力部分も解説していただけるとありがたいです。どうかよろしくお願い致します。

  3. deco より:

    search.phpでの表記例を教えていただけないでしょうか?

    • codeisle より:

      記事をご覧いただき、コメントまでしてくださりありがとうございます。
      せっかく参考にしていただいたのに、中途半端な終わりですみません。
      ただいま多忙につきブログ更新が滞っております。いつか内容を改定する際にはsearch.phpまで書きたいと思います。

      • deco より:

        お忙しい中、ご返信くださりありがとうございます。
        タクソノミー(ターム)のでの絞込検索の結果の表示はできるのですが、カスタムフィールドの絞込の検索結果の方がうまく出来なくて…。
        私の知識不足なのが問題なのですが、また内容を改定された際は参考にさせてください。

  4. codeisle より:

    本日、内容を大幅改訂いたしました。
    これまで当記事のACFのチェックボックス周りで不正確な記述があったようで、混乱を招いてしまいました。
    本来であればそこを修正すべきところではありますが、当記事ではWordpressの検索機能を自力で実装することにフォーカスするため、この度はACFに依存する部分を削除することにしました。もしその部分でつまずいてしまった方で、このコメントを見ている方がおられたなら‥‥ごめんなさい。逃げました!
    その代わりと言ってはなんですが、カスタムフィールドの数値を範囲で絞り込む機能を追加しました。

タイトルとURLをコピーしました