Manul Tech

WordPressのカスタム検索

WordPressで検索項目のカスタムなどを依頼された。
複数のカスタム投稿タイプに渡る検索で、ヒットさせたい検索項目は

・記事タイトル
・記事本文
・カスタムフィールド
・検索用キーワード(タグ)

のどれか。しかも出力結果は指示されたカスタム投稿タイプでまとめて優先順位も決めたいらしい。
調べた結果カスタムクエリじゃ無理そうなので、自分でSELECT文を書くことに。
SQL文は得意じゃないんだけど。

最終的なコード

// joinフィルタ
add_filter('posts_join', 'custom_search_join');
function custom_search_join($join)
{
    if (is_search()) {
        global $wpdb;

        // 検索用キーワード用
        $join = "LEFT JOIN {$wpdb->term_relationships} T ON ({$wpdb->posts}.ID = T.object_id)";

        // カスタムフィールド用
        $join .= " LEFT JOIN {$wpdb->postmeta} M ON ({$wpdb->posts}.ID = M.post_id)";
    }

    return $join;
}

// whereフィルタ
add_filter('posts_where', 'custom_search_where');
function custom_search_where($where)
{
    if (is_search()) {
        global $wpdb;

        // 複数のキーワードで検索された時用
        $search_word = str_replace(' ', ' ', get_search_query());
        $search_word = explode(' ', $search_word);

        if (!empty($search_word)) {
            $where_list = [];
            foreach ($search_word as $word) {
                if (empty($word)) continue;

                // ヒットした検索用キーワードのterm_idを取得しとく
                $keyword_ids = [];
                $terms = get_terms([
                    'taxonomy' => '_search_keywords',
                    'name__like' => $word,
                    'fields' => 'ids',
                ]);

                if (!empty($terms)) {
                    foreach ($terms as $term) {
                        $keyword_ids[] = $term;
                    }
                }

                // 記事タイトル
                $w = "({$wpdb->posts}.post_title collate utf8_general_ci LIKE '%{$word}%')";

                // 記事本文
                $w .= " OR ({$wpdb->posts}.post_content collate utf8_general_ci LIKE '%{$word}%')";

                // カスタムフィールド
                $w .= " OR (M.meta_value collate utf8_general_ci LIKE '%{$word}%')";

                // 検索用キーワード
                if (!empty($keyword_ids)) {
                    $keyword_ids_str = implode(',', $keyword_ids);
                    $w .= " OR (T.term_taxonomy_id IN ({$keyword_ids_str}))";
                }

                $where_list[] = $w;
            }

            if (!empty($where_list)) {
                $where = ' AND (';

                if (count($where_list) > 1) {
                    // 複数キーワードがあった場合はAND検索
                    foreach ($where_list as $index => $w) {
                        if ($index > 0) {
                            $where .= ' AND ';
                        }
                        $where .= "({$w})";
                    }
                } else {
                    $where .= $where_list[0];
                }

                $where .= ')';

                // 検索対象のカスタム投稿タイプを絞る
                $where .= " AND {$wpdb->posts}.post_type IN ('customposttype_A', 'customposttype_B', 'customposttype_C')";

                // 投稿ステータスを絞る
                $where .= " AND {$wpdb->posts}.post_status = 'publish'";
            }
        }
    }

    return $where;
}

// groupbyフィルタ
add_filter('posts_groupby', 'custom_search_groupby');
function custom_search_groupby($groupby)
{
    if (is_search()) {
        global $wpdb;

        // ヒットする投稿IDが被らないように
        $groupby = "{$wpdb->posts}.ID";
    }

    return $groupby;
}

// orderbyフィルタ
add_filter('posts_orderby', 'custom_search_filter');
function custom_search_filter($orderby)
{
    if (is_search()) {
        global $wpdb;

        // こう書くことでcustomposttype_A,customposttype_B,customposttype_Cの順で出力できるらしい
        // 同じカスタム投稿タイプ内では日付が新しい順
        $orderby = "field({$wpdb->posts}.post_type, 'customposttype_A', 'customposttype_B', 'customposttype_C'), {$wpdb->posts}.post_date DESC";
    }

    return $orderby;
}

where句に collate utf8_general_ci を追加することでひらがな/カタカナを区別してくれるそうな。
元々utf8_general_ciでテーブルを作っていれば問題ないんだろうけど、本件ではutf8mb4_general_ciで作成されていたので。

他、orderby句にcaseってのを利用する方法もあるんだとか。

Qiita
MySQLで任意の順序でソートしたい場合

思い通りの検索結果にはなったけど

SQL文が苦手なこともあり、もっと良い方法があるような気がしてならない。

コメント0