WordPressで検索機能を自作する

ワードプレスのテーマtwentytwentyoneを基盤に、検索機能を自作する実装を行ったので、備忘録的にこの記事を残す。

# 環境情報

PHP：version 7.3.12
WordPress：version 5.6
WPテーマ：twentytwentyone

# 仕様

・検索対象の投稿タイプは search のみとする。

・フリーワードの検索対象は、タイトルと（本文と備考と）繰り返しフィールドの search_other_value の値の二つ

・「さらに条件を絞り込む」に指定があればキーワード検索と AND で繋ぎ絞り込む

・「さらに条件を絞り込む」の項目は、一つのブロック内は OR で繋ぎ、異なるブロック同士は AND で繋ぐ。

・地域の設定はカスタムタクソノミーでカテゴリーを作っているため、選択した地域名に該当するカテゴリーに属する会社名を検索に引っ掛ける。

・車種の登録はそれぞれ車種ごとに入力フィールド分かれており、台数が値として入っているため、「さらに条件を絞り込む」で任意の車種にチェックが入っていた場合には、その車種の台数が1以上の会社名を検索に引っ掛ける。

・対応言語の登録は、一つの入力フィールドでラジオボタンで登録する言語を選択しているため、配列の形で値が格納されている。そのため、「さらに条件を絞り込む」で任意の言語にチェックが入っていた場合には、その言語を配列に含む会社名を検索に引っ掛ける。

# 実装

## 検索ページ

今回は検索で表示させたい投稿の投稿タイプ名が search というもの一つだけだったので、検索ページを search のアーカイブページに設置しました。

```archive-search.php
<div class="search-box">
  <form role="search" method="get" id="searchform" action="/" >
    <input type="text" name="s" id="search-input" placeholder="○○内を検索" />
    <div>
      <label>さらに条件を絞り込む</label>
      <div>
        <label>エリア</label>
        <select name="category" style="margin-top:20px;">
          <option value="" selected>地域を選択</option>
          <?php
            $terms = get_terms( 'search_category',
              array(
                'parent' => 0,
                'orderby' => 'id',
                'order' => 'ASC'
              )
            );
            foreach ( $terms as $term ) :
          ?>
          <option value="<?php echo $term->slug; ?>"><?php echo $term->name; ?></option>
          <?php endforeach; ?>
        </select>
      </div>

      <div>
        <label>こだわり</label><br>
        <label><input type="checkbox" name="carmodel[]" value="general">一般タクシー</label><br>
        <label><input type="checkbox" name="carmodel[]" value="higher">ハイヤー</label><br>
        <label><input type="checkbox" name="carmodel[]" value="jumbo">ジャンボ</label><br>
        <label><input type="checkbox" name="carmodel[]" value="welfare">福祉</label><br>
      </div>

      <div>
        <label>外国語対応</label><br>
        <label><input type="checkbox" name="language[]" value="english">英語</label><br>
        <label><input type="checkbox" name="language[]" value="chinese">中国語</label><br>
        <label><input type="checkbox" name="language[]" value="korean">韓国語</label><br>
      </div>
    </div>

    <input type="hidden" name="post_type" value="search">
    <button type="submit" accesskey="f" class="search-btn">検索</button>
  </form>
</div>
```

## functions.php での処理

```functions.php
//======================================================================
// 検索機能
//======================================================================

// 検索時の表示ファイルの設定
function custom_search_template( $template ) {
  if ( is_search() ) {
    $post_types = get_query_var( 'post_type' );
    foreach ( ( array ) $post_types as $post_type )
      $templates[] = "search-{$post_type}.php";
      $templates[] = 'search.php';
      $template = get_query_template( 'search', $templates );
     }
  return $template;
}
add_filter( 'template_include', 'custom_search_template' );

// キーワード検索に特定のカスタムフィールドを追加
function custom_search($search, $wp_query) {
    global $wpdb;

    if (!$wp_query->is_search) return $search;
    if (!isset($wp_query->query_vars)) return $search;

    $search_words = explode(' ', isset($wp_query->query_vars['s']) ? $wp_query->query_vars['s'] : '');
    if ( count($search_words) > 0 ) {
        $search = '';
        $search .= "AND post_type = 'search'"; // 投稿タイプ名を設定
        for ( $i = 0; $i <= 9; $i++ ) {
            $custom_field_array[] = '"search_other_' . $i . '_search_other_value"';
        }
        $custom_field_list = implode( ', ', $custom_field_array );
        foreach ( $search_words as $word ) {
            if ( !empty($word) ) {
                $search_word = '%' . esc_sql( $word ) . '%';
                $search .= " AND (
                    {$wpdb->posts}.post_title LIKE '{$search_word}'
                    OR {$wpdb->posts}.post_content LIKE '{$search_word}'
                    OR {$wpdb->posts}.ID IN (
                        SELECT distinct post_id
                        FROM {$wpdb->postmeta}
                        WHERE {$wpdb->postmeta}.meta_key IN ( 
                            {$custom_field_list}
                        ) AND meta_value LIKE '{$search_word}'
                    )
                ) "; // INのカッコ内に対象としたいフィールド名をコンマ区切りで追加
            }
        }
    }
    return $search;
}
add_filter('posts_search','custom_search', 10, 2);

// 検索結果ページのメインクエリを変更
function change_search_main_query( $query ) {
    // 指定したページとメインクエリの場合に限定
    if ( $query->is_main_query() && ( $query->is_search() || isset( $_GET['category'] ) || isset( $_GET['carmodel'] ) || isset( $_GET['language'] ) ) ) {
        $s = $_GET['s'];
        $category = $_GET['category'];
        $carmodel = $_GET['carmodel'];
        $language = $_GET['language'];
        // 地域
        if ( $category ) {
            $taxquerysp[] = array(
                'taxonomy'         => 'search_category',
                'terms'            => $category,
                'include_children' => false,
                'field'            => 'slug',
                'operator'         => 'AND'
            );
        }

        $metaquerysp = [
            'relation' => 'AND',
        ];
        // 車種
        if ( $carmodel ) {
            $sub_metaquerysp = [
                'relation' => 'OR',
            ];
            foreach ( $carmodel as $val ) {
                $sub_metaquerysp[] = array(
                    'key'     => 'search_' . $val,
                    'value'   => 0,
                    'compare' => '>'
                );
            }
        }
        // 言語
        if ( $language ) {
            $sub_metaquerysp = [
                'relation' => 'OR',
            ];
            foreach ( $language as $val ) {
                $sub_metaquerysp[] = array(
                    'key'     => 'search_language',
                    'value'   => $val,
                    'compare' => 'LIKE',
                );
            }
        }
        $metaquerysp[] = $sub_metaquerysp;

        $query->set( 'post_type', 'search' );
        $query->set( 'tax_query', $taxquerysp );
        $query->set( 'meta_query', $metaquerysp );
        $query->set( 's', $s );
    }
}
add_action( 'pre_get_posts', 'change_search_main_query' );
```

## 検索結果ページ

```search-search.php
<?php
if ( have_posts() ) :
    while ( have_posts() ) : the_post();
?>
    <!-- ループ内の処理を記述 -->
<?php
    endwhile;
else :
?>
    該当なし
<?php
endif;
?>
```