続・WordPressの検索をカスタマイズしてみた
Posted by admin at 9:04 日時 2013/03/15
以前フォーラムの投稿をきっかけにWordPressのデフォルトの検索をカスタマイズするという記事を書きましたが、実務で使う機会が来て色々やってみたのでシェアしておきます。ええ、まだWordPressの仕事もほそぼそやってるんですよ。
検索結果から特定のカテゴリーを除外する
is_searchで検索結果ページを表示している時に限定し、category__not_in パラメーターをセットして、IDが 1 のカテゴリーを検索結果から除外してみた。
function my_custom_query($query) { if ( is_admin() || ! $query->is_main_query() ) return; if ( $query->is_search() ) { $query->set( 'category__not_in', array(1) ); } } add_action( 'pre_get_posts', 'my_custom_query' );
この辺りのパラメーターはCodexのWP_Queryのページに記載がある。
管理画面と、メインクエリー以外(サイドバーの新着記事一覧など)には反映してほしくないので、冒頭でチェックして該当する場合は処理しないようにしている。
カスタムフィールドの内容も検索対象にする
フィルターでJOIN句を足せるようになっていて、ここではカスタムフィールドのテーブルを結合してみた。
function my_search_join( $join, $query ) { if ( is_admin() || ! $query->is_main_query() ) return $join; global $wpdb; if( $query->is_search() ) { $join .= " LEFT JOIN $wpdb->postmeta ON " . "$wpdb->posts.ID = $wpdb->postmeta.post_id "; } return $join; } function my_search_where( $where, $query ) { if ( is_admin() || ! $query->is_main_query() ) return $where; global $wpdb; if ( $query->is_search() ) { $where = preg_replace( "/\(\s*$wpdb->posts\.post_title\s+LIKE\s*(\'[^\']+\')\s*\)/", "($wpdb->posts.post_title LIKE $1) OR ($wpdb->postmeta.meta_value LIKE $1)", $where ); } return $where; } add_filter( 'posts_join', 'my_search_join', 10, 2 ); add_filter( 'posts_where', 'my_search_where', 10, 2 );
別にwp_usersテーブルでも、何らかのプラグインのテーブルでも、結合することができる。WHERE句の方は、正規表現で置換しているが、これは単にくっつけるだけだと、複数の語句での検索に対応できないのでこうしている。
こちらの場合も、メインクエリーかどうかを判別して該当しない場合は処理を行わないようにしている。Codexのサンプルコードではこの処理は入っていないし、Search Everything プラグインでも同様にメインクエリーのチェックは入っていないのだが、どうもメインクエリーのチェックは入れたほうが良いと思う。検証していたら他のクエリーへの影響があったので。
日付で絞り込み検索できるようにする
サイト内検索にパラメーターを追加する方法。ここでは、
http://example.com/?s=hoge&start_date=2013-01-01&end_date=2013-12-31
のように、日付の範囲を指定し、その範囲に公開日が入る投稿だけが検索にひっかかるようにしている。
function my_query_vars( $public_query_vars ) { $public_query_vars[] = 'start_date'; $public_query_vars[] = 'end_date'; return $public_query_vars; } function my_parse_query( $query ) { if ( $query->get('start_date') && $query->get('end_date') ) { $query->is_search = true; $query->is_home = false; } return $query; } function my_search_where( $where, $query ) { if ( is_admin() || ! $query->is_main_query() ) return $where; global $wpdb; if ( $query->is_search() ) { if ( $query->get('start_date') && $query->get('end_date') ) { $start_date = date('Y-m-d', strtotime($query->get('start_date'))); $end_date = date('Y-m-d', strtotime($query->get('end_date'))); $where .= " AND $wpdb->posts.post_date >= '$start_date' AND $wpdb->posts.post_date <= '$end_date'"; } } return $where; } add_filter( 'query_vars', 'my_query_vars'); add_filter( 'parse_query', 'my_parse_query'); add_filter( 'posts_where', 'my_search_where', 10, 2 );
まず、start_date や end_date はURLにパラメーターとして追加しても、WordPressは処理しない。WordPressが処理対象にするパラメーターはWPクラス内で定義されている。
var $public_query_vars = array(‘m’, ‘p’, ‘posts’, ‘w’, ‘cat’, ‘withcomments’, ‘withoutcomments’, ‘s’, ‘search’, ‘exact’, ‘sentence’, ‘calendar’, ‘page’, ‘paged’, ‘more’, ‘tb’, ‘pb’, ‘author’, ‘order’, ‘orderby’, ‘year’, ‘monthnum’, ‘day’, ‘hour’, ‘minute’, ‘second’, ‘name’, ‘category_name’, ‘tag’, ‘feed’, ‘author_name’, ‘static’, ‘pagename’, ‘page_id’, ‘error’, ‘comments_popup’, ‘attachment’, ‘attachment_id’, ‘subpost’, ‘subpost_id’, ‘preview’, ‘robots’, ‘taxonomy’, ‘term’, ‘cpage’, ‘post_type’);
query_vars フィルターで行なっているのは、まずこのリストに新たなパラメーターである start_date と end_date を追加する作業だ。
次に、日付範囲の指定があれば検索ワードが空でも該当する投稿を取得したかったので、parse_query フィルターで start_date と end_date のパラメーターが指定されている場合は、検索結果として処理されるようにフラグを変更している。また、is_home が true になってしまっているので、 false に戻している。なんでそんなことをする必要があるのか、詳しくはWP_Query::parse_queryのソースを参照。
最後に posts_where フィルターでSQL文を足している。これで完成。検索フォームの方は適当に作ってください。
例によって @jim0912 さんに色々教えていただきました。ありがとうございます。
@HissyNC is_serch を true にするなら、parse_query での方がよいかも。
— まがぞん (@jim0912) March 14, 2013
こちらの記事も参考になりました。WordPress でカテゴリのリンクを変更する via @wokamoto
色々検証してみて、メインクエリーのチェックは常に必須と考えたほうがいいなあと。バグの温床の可能性。切り分けも非常に難しいし。現場からは以上です。