wordpressのget_postsは検索結果ページでposts_searchフィルターが掛かる

投稿者: | 2014年6月19日

結論はタイトルの通りですが、wordpressの検索をカスタマイズしていて引っ掛かったことがあったので書き留めます。
<wordpressの検索機能について>
wordpressの検索機能の検索対象は、投稿・固定ページのタイトル・本文です。
ブログとしての利用であればそれで十分ですが、企業サイトや商品サイト等では、よく下記の様なニーズがあり、物足りません。
・複数カテゴリで検索したい
・カスタムフィールドに限って検索したい
・カスタム投稿を検索に含めたい
・チェックボックスで検索したい
・OR検索したい
等々。
プラグインでも検索対象を広げたり、ある程度のカスタマイズが可能ですが、
wordpressのフィルターフックを利用すると、SQLをいじれるため、広範にわたるカスタマイズが可能です。
※フィルターフックについてはこちら
<カスタム投稿・カスタムフィールドをOR検索したい>
今回、下記の検索要件に沿って実装しました。
・カスタム投稿「hoge」のみを検索
・カスタムフィールド「checkbox」を検索
・チェックボックスを使ったOR検索
検索画面は下記のような感じです。

<form action="<?php bloginfo( 'url' ); ?>;" method="get">
<ul>
<li><label><input type="checkbox" name="checkbox[]" value="チェック1" />チェック1</label></li>
<li><label><input type="checkbox" name="checkbox[]" value="チェック2" />チェック2</label></li>
<li><label><input type="checkbox" name="checkbox[]" value="チェック3" />チェック3</label></li>
<li><label><input type="checkbox" name="checkbox[]" value="チェック4" />チェック4</label></li>
</ul>
<input type="hidden" name="s" value="" /> <input id="submit" type="submit" value="検索" /></form>

functions.phpに下記を記入します。

//カスタムフィールドを検索するため、postmetaテーブルを結合
function custom_search_join($join){
if( is_search() ) {
$join .= "INNER JOIN wp_postmeta ON (wp_posts.ID = wp_postmeta.post_id)";
}
return $join;
}
add_filter( 'posts_join', 'custom_search_join' );
//同じ投稿が表示されないようグループ化
function custom_search_groupby($groupby) {
if( is_search() ) {
$groupby = "wp_posts.ID";
}
return $groupby;<
}
add_filter( 'posts_groupby', 'custom_search_groupby' );
//検索対象をカスタム投稿「hoge」に限定
function search_filter($query) {
if ( !is_admin() && $query->is_main_query() ) {
if ($query->is_search) {
$query->set('post_type', 'hoge' );
}
}
}
add_action('pre_get_posts','search_filter');
//SQLカスタマイズ
function custom_search($search, $wp_query ) {
//query['s']があったら検索ページとする
if ( isset($wp_query->query['s']) && !is_admin() && $wp_query->is_main_query() ) $wp_query->is_search = true;
//SQL WHERE節カスタマイズ
if ($wp_query->is_search) {
$search .= " AND (";
$checkbox = $_REQUEST['checkbox'];
if($checkbox && is_array($checkbox)){
//カスタムフィールド「checkbox」を指定
$search .= "(wp_postmeta.meta_key = 'checkbox' AND CAST(wp_postmeta.meta_value AS CHAR)";
//OR検索
$search .= " IN(";
foreach($checkbox as $var) {
$var = mysql_real_escape_string($var);
$search .= "'{$var}',";
}
$search = preg_replace('/,$/', '', $search);
$search .= "))";
}
$search .= ") ";
}
$search = preg_replace('/AND \(\)/', '', $search);
return $search;
}
add_filter('posts_search','custom_search', 10, 2);

posts_joinでメインクエリにpostmetaテーブルを結合し、
posts_groupbyで同一投稿をまとめ、
pre_get_postsで検索対象をカスタム投稿に絞り、
posts_searchでカスタムフィールドをOR検索するように調整しています。
ちなみに複数のカスタムフィールドを検索する場合は、38行目~50行目の処理をORもしくはANDでつないでいくだけです。
これで要件を満たす検索結果が出力されます、が。
<問題発生> 関係ないSQLが加工される!
テンプレートでget_postsを使っている状態で検索結果ページを見ると、ちゃんと検索されるものの、SQL構文エラーが出ます。
get_postsが出力したSQLをよく見ると、posts_searchフィルターだけが掛かっているようです。
例:get_posts(‘post_type’ => ‘hogehoge’,’posts_per_page’ => -1)としていると下記の様なSQLが出力される
SELECT wp_posts.* FROM wp_posts WHERE 1=1 AND ((wp_postmeta.meta_key = ‘checkbox’ AND CAST(wp_postmeta.meta_value AS CHAR) IN(‘チェック1’,’チェック3’))) AND wp_posts.post_type = ‘hogehoge’ AND (wp_posts.post_status = ‘publish’) ORDER BY wp_posts.post_date DESC
ちなみにquery_postsやWP_Queryで取得する場合は、メインクエリからの変更なので全てのフィルターが掛かります。
<対処法>
対症療法ですが、下記方法で対応できます。
get_postsを使う箇所を下記のように変更。

removed_filter('posts_search','custom_search', 10, 2);
get_posts( $arg );
add_filter('posts_search','custom_search', 10, 2);

そもそもなぜget_postsにposts_searchフィルターが掛かるのか、他のフィルターは掛からないのか、今後検証していきたいと思います。
※一部元のソースコードから改変しているため、動作は保証できません。
 また、このコードを利用することにより、何らかの損失を被ったとしても弊社は責任を負いかねます。
ヴィンテージはwordpress案件も多数扱っております。
Webサイト構築、Webシステム構築に関してのご依頼、ご質問等がある場合は弊社ホームページ上のお問い合わせフォームもしくは下記連絡先までお問い合わせ下さい。
お問い合わせフォーム
株式会社ヴィンテージ Webシステム事業部
TEL:093-513-7255