ワードプレスでカスタムタクソノミー×カスタムフィールド×キーワード絞り込み検索機能を作る

# ワードプレスでカスタムタクソノミー×カスタムフィールド×キーワード絞り込み検索機能を作る
カスタムフィールドとタクソノミーの絞り込みプラグインって挙動が怪しかったり、有料だったりでなかなか見つからなかったので作りました。

## 1.使うphpファイル
- function.php
- searchform.php
- search.php

## 2.使うプラグイン（カスタムフィールド用）と環境
- Advanced Custom Fields (ACF)
- WP ver.6↑ php ver.8↑

## 3.前提条件
ACF使用
投稿タイプ「sample」内で絞り込みしたい。
絞り込み内容は４種類
- タクソノミー「point」
- カスタムフィールド（選択）「pref」
- カスタムフィールド（範囲）「wage」
- キーワード検索

## 4.function.phpでやること
- ### カスタムクエリ変数を追加する
```// 検索機能 各クエリ作成
function custom_query_vars_filter($vars)
{
    // クエリ変数として追加する項目のリスト
    $list = array(
        'point',      // タクソノミー名が 'point'
        'pref',     // カスタムフィールド名が 'pref'
        'min_num',    // 'min_num'（値の上限）
        'max_num'     // 'max_num'（値の下限）
    );
    
    // リスト内の各項目をクエリ変数に追加
    foreach ($list as $item) {
        $vars[] = $item;
    }
    return $vars;
}
// query_vars フィルターフックに custom_query_vars_filter 関数を追加
add_filter('query_vars', 'custom_query_vars_filter');
```

WordPressではクエリで受け取れる文字列があらかじめ決められているため、カスタムクエリ変数として上記の文字列を追加します。
URLの末尾にパラメータが付与され、値を受け取れるようになります。

- ### pre_get_postsで調整する
まず全文
```
function custom_set_search_query($query)
{
    // 管理画面ではなく、検索ページの場合に処理を実行
    if (!is_admin() && $query->is_search()) {
        // 投稿タイプを 'sample' に設定
        $query->set('post_type', 'sample');

        // カスタムタクソノミーの設定
        $taxs = array(
            'point',        // タクソノミー 'point'
            //'○○',   // タクソノミー '○○' タクソノミーが複数の時はここに追加
        );

        // 各タクソノミーの値を取得してクエリに追加
        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);
        }
        $value = array(
            'pref',    // カスタムフィールド 'pref'
            'wage'     // カスタムフィールド 'wage'
        );

        // 各カスタムフィールドの値を取得してクエリに追加
        foreach ($value as $value) {
            // 'wage' フィールドのメタクエリ設定
            if ($value == 'wage') {
                $min_num = get_query_var('min_num', 0); // 下限デフォルト:0
                $max_num = get_query_var('max_num', null);
                if ($min_num < $max_num) {
                    // 下限〜上限の範囲指定
                    $num_query[] = array(
                        'key' => 'wage',
                        'value' => array($min_num, $max_num),
                        'type' => 'numeric',
                        'compare' => 'BETWEEN',
                    );
                } else {
                    // 上限なしの場合
                    $num_query[] = array(
                        'key' => 'wage',
                        'value' => $min_num,
                        'type' => 'numeric',
                        'compare' => '>=',
                    );
                }
            }
            // その他のメタクエリ設定
            $term = get_query_var($value);
            if ($term) {
                $meta_query[] = array(
                    'key' => $value,
                    'value' => $term,
                    'compare' => '=' // 完全一致検索
                );
            }
        } 
        // num_query または meta_query をクエリに追加
        $meta_queries = array_filter([$num_query, $meta_query]);
        if ($meta_queries) {
            $query->set('meta_query', $meta_queries);
        }
    }
}
// pre_get_posts アクションフックに custom_set_search_query 関数を追加
add_action('pre_get_posts', 'custom_set_search_query');
```

順番に説明していきます。

```/ 絞り込み
function custom_set_search_query($query)
{
    // 管理画面ではなく、検索ページの場合に処理を実行
    if (!is_admin() && $query->is_search()) {
        // 投稿タイプを 'sample' に設定
        $query->set('post_type', 'sample');

        // カスタムタクソノミーの設定
        $taxs = array(
            'point',        // タクソノミー 'point'
            //'○○',   // タクソノミー '○○' タクソノミーが複数の時はここに追加
        );

        // 各タクソノミーの値を取得してクエリに追加
        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);
        }
```
最初のif文で、管理画面ではなく、かつ検索ページである場合にのみ以下の処理を実行することを記述します。
検索結果に表示する投稿タイプは「sample」に限定し、カスタムタクソノミーの値を取得して検索クエリに追加しています。
```        // カスタムフィールドの設定
        $value = array(
            'pref',    // カスタムフィールド 'pref'
            'wage'     // カスタムフィールド 'wage'
        );

        // 各カスタムフィールドの値を取得してクエリに追加
        foreach ($value as $value) {
            // 'wage' フィールドのメタクエリ設定
            if ($value == 'wage') {
                $min_num = get_query_var('min_num', 0); // 下限デフォルト:0
                $max_num = get_query_var('max_num', null);
                if ($min_num < $max_num) {
                    // 下限〜上限の範囲指定
                    $num_query[] = array(
                        'key' => 'wage',
                        'value' => array($min_num, $max_num),
                        'type' => 'numeric',
                        'compare' => 'BETWEEN',
                    );
                } else {
                    // 上限なしの場合
                    $num_query[] = array(
                        'key' => 'wage',
                        'value' => $min_num,
                        'type' => 'numeric',
                        'compare' => '>=',
                    );
                }
            }
            // その他のメタクエリ設定
            $term = get_query_var($value);
            if ($term) {
                $meta_query[] = array(
                    'key' => $value,
                    'value' => $term,
                    'compare' => '=' // 完全一致検索
                );
            }
        }
```
:::note warn
値のカスタムフィールドを使用しない場合は、
if ($value == 'wage') { から　} // その他のメタクエリ設定
までを削除してください。
:::

値のカスタムフィールド（今回はwage）があり、上限、下限のある検索を実現したい場合、
if ($value == 'wage') { 以下で上限、下限の範囲を指定し、num_query 配列に追加します。
選択のカスタムフィールド（今回はpref）は値を取得し、meta_query 配列に追加します。

```
        // num_query または meta_query をクエリに追加
        $meta_queries = array_filter([$num_query, $meta_query]);
        if ($meta_queries) {
            $query->set('meta_query', $meta_queries);
        }
    }
}
// pre_get_posts アクションフックに custom_set_search_query 関数を追加
add_action('pre_get_posts', 'custom_set_search_query');
```
:::note warn
値のカスタムフィールドを使用しない場合は、
```
if (!empty($meta_query)) {
$query->set('meta_query', $meta_query);
}    }
}
// pre_get_posts アクションフックにcustom_set_search_query 関数を追加
add_action('pre_get_posts', 'custom_set_search_query');
```
こちらのコードを使用してください。
:::

値のカスタムフィールドと選択のカスタムフィールドは検索条件が異なるため、違うクエリで作成します。
どちらか一方が空のまま格納すると機能しないので、array_filterを使用しました。

これでfunctions.phpでの設定は完了です。
続いて、検索に使うsearchform.phpの調整をします。
## 5.searchform.phpでやること
searchform.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="○○など">
<button type="submit">探す</button>
</form>
```
input name="s" ～にて、キーワード検索欄が出力されます。
カスタムフィールド'perf'と'wage'はseletタグでマークアップします。
```
<select name="pref">
<option value="北海道">北海道</option>
<option value="青森県">青森県</option>
<option value="岩手県">岩手県</option>
～～～
<option value="沖縄県">沖縄県</option>
</select>

```
値のカスタムフィールドは、上限・下限を別々に取得しているため、それぞれをselectタグでマークアップします。
```
<select name="min_num">
<option value="">下限なし</option>
<option value="1000">1000円</option>
<option value="1100">1100円</option>
</select>
<select name="max_num">
<option value="">上限なし</option>
<option value="2000">2000円</option>
<option value="2500">2500円</option>
</select>
```
カスタムタクソノミーのチェックボックスを作成します。
```
<?php
            //ポイントチェックボックス
            $tax = 'point';
            // 選択中のterm
            $term_checked = get_query_var($tax, []); // 複数なので配列
            // タームリストを出力
            $terms = get_terms(array(
              'taxonomy' => $tax,
              'orderby' => 'count', // 件数順にする場合
              'order' => 'DESC',
              'hide_empty' => false, // 空のタームも表示する場合
            ));
            //　出力
            $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;
            ?>
```
これらのコードを、form タグの中に入れると検索結果（search.php）ページで絞込検索が可能になります。

## 6.search.phpでやること
クエリはfunction.phpで調整しているため、特別なコードは必要ありません。
カスタムフィールドはいつも通りthe_field()で取得可能です。
```
<?php get_header(); ?>
<?php if (have_posts()) : ?>
<?php while (have_posts()) : the_post(); ?>
<!-- ここに記述 -->
<?php endwhile; ?>
<?php else : ?>
<h3>検索条件に該当する項目は、ありませんでした。</h3>
<?php endif; ?>
<?php get_footer(); ?>
```
## 7.まとめ
なんとか実装できましたが、カスタムフィールドは完全一致じゃないと動かなかったり、
orとandの調整をあきらめたりしました。
今回はじめてchatGTP君と共同作業をしましたが、同じ質問を何度しても怒らず答えてくれる点でとても頼もしかったです。便利～～！！
## 参考文献
この記事は以下の情報を参考に作成しました。

- [【WordPress】カスタムフィールド・タクソノミーによる絞込検索を実装する／実装編](https://codeisle.info/blog/516/)
- [絞り込み検索をプラグインを使わずに実装[WordPress]](https://kotori-blog.com/wordpress/refinement_search/)