検索対象にカスタムタクソノミーの文字列も含めたい

同じ問題にハマって検索したら自分の記事が出てきて内容が古くて唖然としたのでアップデート。

Search Everythingを使う。

このプラグインで、カスタムタクソノミ―を検索対象にできる。

インストール後、Search Everythingの設定で、Search custom taxonomiesにチェックをする。
カスタムフィールドを検索にチェックを入れれば、カスタムフィールドも、対象にできる。
ちなみに検索キーワードをハイライトにチェックを入れていたら、昔不具合があったのでそれ以来ここはチェックを外している。

さらに、今回は通常の投稿と、カスタム投稿タイプとで検索結果を別々に表示したかったため、以下の作業も行った。

ワードプレスの検索ボックスのフォームのソースに、以下を追加

<input type="hidden" value="作成したカスタム投稿タイプ" name="post_type" id="post_type">

これで指定したカスタム投稿タイプのみが検索対象になる。

検索結果画面のレイアウトなども、カスタム投稿タイプによって変更したい場合は、
テーマの中にある、search.phpを複製し、search-作成したカスタム投稿タイプ.php
にして行けるかと思ったら、駄目だった。
singleとかarchiveは行けるのに。

検索したら、functions.phpに追記する必要があるらしい。
こちらのサイトでやり方が紹介されています。

カスタム投稿タイプ毎に検索結果を分岐させる方法

大変助かる。

以下は古いので参考にはならない。

カスタム投稿タイプを使っているのだが、検索してみてあることに気づいた。
カスタム投稿タイプのカテゴリやタグ(正確にはタクソノミー)は、Wordpress標準の検索機能だと対象外になっている。

例えばこんな投稿をした場合。

記事タイトル:サラブレッド
カテゴリ:動物
タグ:馬

この場合、「サラブレッド」で検索すると、検索にヒットするが、「動物」や「馬」だとヒットしない。
それはそれでありなのかもしれないけど、自分的にはヒットして欲しいのでカスタマイズしてみる。

カスタマイズするファイル

WordPressで検索した場合、テーマ内にsearch.phpがあると優先的に表示される。
ここに渡される検索の文字列は、get_search_query()で取得できる。
取得した文字列から、検索対象をカテゴリやタグにまで広げてヒットするようにしようと思う。
始める前に、search.phpは別名保存してバックアップしておく。

検索する対象

・カスタム投稿タイプで投稿した記事のタイトル
・カテゴリ
・タグ
の文字列が対象

今回のカスタム投稿タイプでは、記事を表示しないちょっと特殊なケースなので、この3つだけ。
カテゴリとタグは、正確にはregister_taxonomyで作成したカスタムタクソノミー。
カテゴリっぽく見せいてるのは、hierarchicalをtrueにしたもの。
タグっぽく見せているのは、hierarchicalをfalseにしたもの。

これらの3つをどうやって同時に検索するかあれこれ悩んだのだけど、結局直接クエリでやるしかないかなーと思った。

・「カスタム投稿タイプで投稿した記事のタイトル」は、wp_postsのpost_titleフィールド。
・「カテゴリ」と「タグ」は、wp_termsのnameとslugフィールド。

記事投稿ページからタグを追加すると、nameとslugには同じ文字列が入るけど、後からslugだけ英語にするとかは容易に想定される。

ここで問題なのは、wp_postsテーブルとwp_termsテーブルを直接結びつけることの出来るフィールドが無いこと。
なので、間にこの二つのテーブルが必要になる。
wp_term_relationships
wp_term_taxonomy

つまり、4つのテーブルを結びつけた検索クエリが必要となるわけだ・・・。
SQLは勉強不足なので心配だなあ・・・。
※ちなみにWordpressのインストール時に接頭辞を変えていた場合、テーブル名はwp_ではないです。

実際に書いてみる

<?php

//検索キーワードを取得
$keys = get_search_query();
//スペースが全角の場合、半角に変換
$keys = str_replace(' ', ' ', $keys);
//SQLインジェクション用
$keys = mysql_real_escape_string($keys);
//検索キーワードを配列に変換
$keys_array = explode(' ', $keys);

//キーワードの数だけWHERE節を作成する
$sql_where = '';
foreach ($keys_array as $key => $value) {
	if(empty($sql_where)){
		$pre_str = 'WHERE ';
	}else{
		$pre_str = 'AND ';
	}
	$sql_where .= $pre_str . "(0<LOCATE('{$value}',posts.post_title) OR 0<LOCATE('{$value}',t_terms.name) OR 0<LOCATE('{$value}',t_terms.slug)) ";
}

//クエリを実行	
$objResult = $wpdb->get_results(
	$wpdb->prepare(
		"SELECT * FROM {$wpdb->posts} posts LEFT JOIN {$wpdb->term_relationships} t_rel  ON posts.ID = t_rel.object_id
		LEFT JOIN {$wpdb->term_taxonomy} t_term_tx ON t_rel.term_taxonomy_id = t_term_tx.term_taxonomy_id  
		LEFT JOIN {$wpdb->terms} t_terms ON t_term_tx.term_id = t_terms.term_id 
		"
		. $sql_where
));


//検索結果が0でなければ、ヒットした投稿のIDを指定してquery_postsで抽出する。

if (!empty($objResult)){
	
	$id_array = '';
	$i=0;
	foreach ($objResult as $key => $value) {
		$id_array[$i] = $value->ID;
		$i++;
	}
	
	$arg = array(
		'post__in'=>$id_array,
		'post_type'=>'対象のカスタム投稿タイプ' //対象のカスタム投稿タイプ
	);
	
	$posts = query_posts( $arg );

}

//検索結果が0なら、query_postsを行わない。その場合、標準の検索機能が自動的に動作する。
//そのため、何もヒットしなければ、標準の「ヒットしませんでした」画面も自動的に表示される。

?>

重要

このコードは、search.phpの文中にある
if ( have_posts() )
より上に記述する。

私の環境では、これで動作確認が取れた。

詰まったところ

検索の際、完全一致ではなく部分一致でも通るようにしたが、ここでハマった。
最初、WHERE節でLIKEを使用したのだが、何故かワイルドカードの「%」を完全に無視する・・・泣。
クエリを実行する際に、wordpressの機能である$wpdbを利用しているせいかどうか知らないが、結局この問題は解決できなかった。

SQLが未熟な私は色々検索して、結局LOCATEを使うことで問題を回避できた。
解決じゃないのがちょっと残念。
ちなみにLOCATEは初めて使った(・・・というか初めて知った 汗)。

もっと簡単な方法とかありそうだなー。
知らないというのは、損だなー。

シェアする

  • このエントリーをはてなブックマークに追加

フォローする