投稿を【ユーザーメタデータ】で検索したい
-
WordPress 3.8
Search Everything 8.0
———————————–いつも大変お世話になっております。
掲題ママなのですが、投稿(正確にはカスタム投稿タイプ)をuser_metaで検索したいのですが、どうすればいいのかほとほと困り果てております。
例えば、user_metaのキーが住所、値に関東と入力されており、
検索で「関東」と入力した場合、合致したユーザーのカスタム投稿が引っかかって欲しいのです。ニックネームができるので、きっと他のメタデータもできるはず…と思い調べているのですが、どうしてもユーザーの検索ばかり引っかかってしまい、私が希望している投稿の検索が出てきません。
また、過去にフォーラムにて質問されているものもあるのですが、
http://ja.forums.wordpress.org/topic/2722?replies=6
4年前の投稿で、プラグインのコードを見ても近しい部分や該当する部分がありませんでした…。そもそも本件可能なのでしょうか?
もしご存じの方がいらっしゃいましたら、何卒お助け下さい。
-
試してないのでできるかわかりませんがWP_User_Queryでメタキーを指定してユーザー検索できるので、それでユーザーを検出した後にそれを条件に投稿検索をかけたらいいんじゃないですかね?
もしくはwpdbでうまくSQL文を作れば一文でできるかもしれません。
早速の返信ありがとうございます!!
Search Everythingというプラグインの中で投稿をユーザーで検索できており、
$search .= “{$searchand}(u.display_name LIKE ‘{$n}{$term}{$n}’)”;
とあったので試しに
$search .= “{$searchand}(u.user_email LIKE ‘{$n}{$term}{$n}’)”;
とやったらメールアドレスでの検索が可能でした。
ですが、get_the_author_metaのパラメータで任意のキーってできませんよね…?————————-
頂いたヒントから、
$wpdb->usermeta WHERE meta_key=’キー名’
で上手くイケばいいなと思ったのですがダメでした。すみません、言い訳するのもアレですが、本当に初心者で色々試してはおりますが、もうどうしたらいいのか…。
あまりテストしていないので動くか自信がないですがfunctions.phpに
add_filter( 'posts_where', 'add_serach_user_meta_query' ); function add_serach_user_meta_query($where){ global $wpdb; $s = get_query_var('s'); if( is_search() ) { $where .= " OR post_author = (SELECT user_id FROM $wpdb->usermeta WHERE meta_key = 'メタキー名' AND meta_value LIKE '%$s%')"; } return $where; }
を追加してみてください。
ありがとうございます、ありがとうございます!
助けて頂けて本当に嬉しいです!!ご親切に、functionsに入れればいいようにコードまで書いて頂きまして、ありがとうございます。
早速試してみました。
試しにキーをdescriptionにしてみたところ、これがバッチリでした!!!が、何故か自分で追加したユーザーメタだと検索に引っかからず…。
——————————————–
// 管理画面のユーザーに項目追加 function update_profile_fields( $contactmethods ) { $contactmethods['address'] = '住所'; return $contactmethods; } add_filter('user_contactmethods','update_profile_fields',10,1);
——————————————–
上記のように、よく検索で引っかかるやり方で項目を追加しているのですが、
これがいけないのでしょうか?(いっそもうdescription欄をaddressとしてしまおうか…)
引き続き自分でも色々試してみますが、もしお手すきでしたらご助力頂けますととても嬉しいです。
キーのdescriptionを自分の指定したいメタキーに変更していますか?(上の例だとaddress)
メタキーを指定せずにユーザーメタ全体から探したい場合はmeta_key部分を削れば
そのように動作しますが関係ないのも引っかかりそうなのでオススメしません。勿論変更してるのです…。
他の自分で追加してみたメタキーにしてみたり、キーの名前自体を変えてみたりしてみたのですが、結果は自分で追加したものだけ引っ掛けることが出来ませんでした。
とりあえず、使用していないaimというキーに仮で値を入れたら引っ掛けることができたので、
aimというキーのの見た目上の表記を住所としました。gogowebさんのおかげで、無事に希望していた挙動が実現しております。
大変ご丁寧に色々とありがとうございます、何日も困っていたのでお助け頂き本当に嬉しいです。本件、解決とさせて頂きます。
何度もすみません…。
頂きましたコードですが、無事に動いた!
と思ったのですが、例えば関東に住む人間が2人いた場合Not foundになってしまったので、頂いたコードを色々試させて頂いて以下のようにしてみました。function custom_search($where, $wp_query) { //検索 global $wpdb; $s = get_query_var('s'); if( is_search() ) { $users = $wpdb->get_var("SELECT user_id FROM $wpdb->usermeta WHERE meta_key = 'メタキー名' AND meta_value LIKE '%$s%'"); $where .= "OR post_author = $users AND post_type = 'カスタム投稿タイプ名' AND post_status = 'publish'"; } return $where; } add_filter('posts_where','custom_search', 10, 2);
上記でNot foundではなくなったのですが、1人分のみ表示されます。
print_r($wpdb->last_result);でsqlのデータを確認した所、無事に複数件のユーザーIDは取れておりましたので、foreachで$usersをどうにかするのかしら、とは思っているものの、色々試しても上手く表示させることが出来ません…。
何度も申し訳ございませんが、詳しい方がご覧になられていましたら、どうかお助け頂きたく…。宜しくお願いいたします。
get_varは一つの値を返すので最初のデータのみが返されます。
複数の値をとるならget_resultsなどをつかってください。なお、SQLで複数条件を指定するならANDでpost_author=ユーザーIDを繰り返すか、INでまとめて指定する必要があります。
上のサンプルのエラーも複数結果が帰ってくるのを想定していなかったのが原因ですね。
ありがとうございます!ありがとうございます!!
無事、複数結果が出ても表示されるようになりました。gogowebさん、本当に、色々とありがとうございました。
function custom_search($where, $wp_query) {
global $wpdb;
$s = get_query_var(‘s’);
if( is_search() ) {
$users = $wpdb->get_results(“SELECT user_id FROM $wpdb->usermeta WHERE meta_key = メタキー名’ AND meta_value LIKE ‘%$s%'”);foreach ($users as $user){
$user_id = $user->user_id;
$where .= “OR post_author = $user_id AND post_type = ‘カスタム投稿タイプ名’ AND post_status = ‘publish'”;}
}return $where;
}
add_filter(‘posts_where’,’custom_search’, 10, 2);試行錯誤して行ったり来たりしてしまっています…。
お教え頂いたとおり、外に出してみました。
複数ユーザーの情報がとれるようになったのですが、検索の文字列が入ると他の検索条件が完全に無視されてしまいました…。
(カスタムタクソノミーの指定や、authorの指定をしております)
function custom_search($where, $wp_query) { //検索 global $wpdb; $s = get_query_var('s'); if( is_search() ) { $users = $wpdb->get_results("SELECT user_id FROM $wpdb->usermeta WHERE meta_key = 'aim' AND meta_value LIKE '%$s%'"); foreach ($users as $user){ $user_id = $user->user_id; } $where .= "OR post_author = $user_id AND post_type = 'カスタム投稿タイプ' AND post_status = 'publish' "; } //query['s']があったら検索ページ表示 if (isset($wp_query->query['s'])) { $wp_query->is_search = true; } return $where; } add_filter('posts_where','custom_search', 10, 2);
なお、検索時のURLは以下のようになります。
http://URL/?post_type=カスタム投稿タイプ名&author=16&cat=カテゴリー名&s=検索文字列
検索文字列が入らなければ無事に他の条件はAND検索になるのですが、
検索文字列が入ると
(カスタム投稿タイプ名 AND author AND カテゴリー名) OR (検索文字列に当てはまる投稿)
のように動いております。単純に
$where .= “OR post_author = $user_id
のORをANDに変えてもダメだったのは何故なのでしょうか?頼りにしてしまって申し訳ございません…勉強不足なのは承知ですが、お助け下さい‥。
Search Everything を使っていないので、推測で書きます。内容が少し難しいようなら、最後の結論だけを読んでいただくだけでもかまいません。
WordPress デフォルトの検索は、query string が投稿のタイトルと本文のどちらかに含まれるものを検索します。だから、使うテーブルは、
- wp_posts
だけです。検索文字列は外部からの入力なので、SQL インジェクションを回避するための処理をしたり、stop word を削除したり、文を語の単位に分解をしたりして、最終的に生成されるクエリステートメントの where 句は、
($wpdb->posts.post_title LIKE '%{$term}%') OR ($wpdb->posts.post_content LIKE '%{$term}%')
のようになります。これを検索語句の数だけループで回して、AND 結合します。ここで理解していただきたいのは、ユーザが入力した検索文字列を投稿やページに含まれる文字列と照合する SQL 文がこの段階で作成されてしまっているということです。
Search Everything は、Plugin Directory の記述を読む限り、これに加えて、カスタムタクソノミーやカテゴリやその他も含めて検索できる機能を提供するプラグインです。このため、利用するテーブルがさらに増えて、
- wp_postmeta
- wp_comments
- wp_term
- wp_term_taxonomy
- wp_term_relationships
- wp_users
が含まれるようです。これらは必要に応じて、JOIN で結合されるのではないかと思いますが(WordPress にフィルタが用意されています)、wp_usermeta テーブルはありません。temina さんがお示しのような、
$search .= "{$searchand}(u.display_name LIKE '{$n}{$term}{$n}')";
は、wp_users を JOIN で結合して始めて利用できる WHERE 句ですから、wp_usermeta を利用する meta_key や meta_value は使えないということになります。一方、description や aim は、wp_users に含まれるので、動作します。また、上の例と比べてみるとわかるように、これは WordPress が検索クエリを作るときとそっくり同じ、変数名まで同じものです。たぶん、この前には、
SELECT * FROM wp_posts AS p LEFT JOIN wp_users AS u ON u.user_id=p.ID
のような主文があるはずです。コメントを検索するには同じく、wp_comments を JOIN して、検索語句の数だけループを回して上のような WHERE 句を AND で結合するわけです。
細部は違うかもしれませんが、現象から考えて、大筋はこのようになっていると思います。そうすると、以下のような結論になります。
ユーザが入力した検索語句は、temina さんが functions.php に書き加えたコード、
$users = $wpdb->get_results(" SELECT user_id FROM $wpdb->usermeta WHERE meta_key = 'aim' AND meta_value LIKE '%$s%'");
で使われるだけでなく、実は他の部分でもすでに使われてしまっていて、それを制御する手段はありません。他の全ての設定を外しても、デフォルトの検索クエリだけは残るでしょうから、最低でも1か所で使われてしまいます。つまり、temina さんが入力した「関東」という語は、ユーザ ID を探すために使われると同時に、「関東」が含まれる投稿とタイトルを検索するためにも使われているということです。
さて、それでは、どうすればよいでしょうか?
「ユーザメタデータを検索のキーとして、該当ユーザのIDを含む投稿を列挙する」
という temina さんのやりたいことを実現するには、Search Everything のデフォルトの動作を停止する必要があります。また、WordPress デフォルトの検索クエリも作動しないようにしなければなりません。とすると、もう自分でメインクエリを書き換えるしかありません。しかも、WP_Query のインスタンスが作成されるところにまでさかのぼる必要があるので、pre_get_posts のようなフックも使えません。Hic Rhodus, hic salta.
そもそも本件可能なのでしょうか?
「可能です」が答えになると思いますが、何行ものコードを書く必要があります。思いつく方法は三つあります。
- Search Everything を改造すること。wp_usermeta を JOIN できるように追加のコードを書き、これを使って検索するときには、WHERE 句に投稿やタイトルを検索する部分が含まれないようすればよいはずです。
- 新たな、検索用 WP_Query のインスタンスを作り、post_join フィルタで wp_usermeta を追加して、必要なパラメータを指定すること。このとき、is_search() が true になると、WordPress の検索クエリが先にできてしまうので、search.php でメインクエリを置き換える操作が必要になります。
- $wpdb クラスを使って、生のクエリを自作し、search.php で自前の整形をする。
1 は大幅なコード追加になると予想できます。2 は、実質 Search Everything を捨てることを意味します。3 が一番お手軽だとは思いますが、WordPress で用意されている テンプレートタグが使えません。また、Search Everything の機能も使えません。これを使った暫定解です。functions.php に以下を書いて、
function search_with_usermeta() { global $wp_query, $wpdb; if (!(is_search() || isset($wp_query->query_vars['s']))) return; $query_string = stripslashes($wp_query->query_vars['s']); $query_string = str_replace(array("\r", "\n""), '', $query_string); $where = ''; if (preg_match_all('/".*?("|$)|((?<=[\t ",+])|^)[^\t ",+]+/', $query_string, $matches)) { $search_terms = $matches[0]; if (empty($search_terms)) { $search_terms = array($query_string); } } else { $search_terms = array($query_string); } foreach ($search_terms as $term) { $term = like_escape(esc_sql($term)); $where .= " AND (um.meta_key = 'メタキー' AND um.meta_value LIKE '%{$term}%')"; } $where .= " AND (p.post_type = 'ポストタイプ' AND p.post_status = 'publish')" $results = $wpdb->get_results(" SELECT * from $wpdb->posts AS p LEFT JOIN $wpdb->usermeta AS um ON p.post_author = um.user_id WHERE 1=1 $where ORDERBY p.post_date DESC "); return $results; }
search.php で下のように使ってください。
$my_posts = search_with_usermeta(); foreach ($my_posts as $post) { 出力整形 }
ページ送りなどは考慮していません。当然ですが、無保証です。
返信が遅くなり申し訳ございません。
大変後丁寧に検索部分の解説、ソースコードを書いて頂くことまでしていただき、ありがとうございます!このフォーラムには大変お優しい方ばかりいらっしゃって、本当に感謝しております。
・search everythingの停止の件につきまして
タクソノミーや著者で絞り込みたいと思い使用していましたが、
[s]部分では上記を検索する事は無いと後ほど気づき、search everythingを停止しても無事に絞り込みが出来ておりました。
(そもそも、タクソノミーや著者の絞り込み(検索で使用できるパラメータのauthorやcat)は、このプラグインでないと出来ないと勘違いしておりました)
ですので、捨ててしまって全く問題ございませんでした。ご報告できておらず、申し訳ございません…。解説いただいた事につきましては、
1)デフォルトの検索はタイトルと本文だけ
↓
2)このままだとデフォルトの検索も含んじゃってるから、そっちは止めないとダメ
↓
3)[s]でユーザーメタをキーにして検索できるようにする、wpdbから直接どの値を持ってくるか指定という3段階の理解が、私のやりたい事を実現するには必要だったという事。
※search everythingのu.display_nameの所は単純にwp_usermetaに変えても動かないよ
※search everythingだとデフォルトの検索も入るからやっぱり止める必要があるよという解説をして頂いた、という認識で合っておりますでしょうか…?
頂いたソースコードをトライし、ご報告するのが直近で月曜日になってしまいますが、
必ずご報告いたしますね。ありがとうございます。
解説の理解は、100%正解です。もうここまでわかったら、あとはそれをコーディングするだけですね。正直なところ、ちょっと難しいかな、と思ったのですが、temina さんの理解力に、こちらがびっくりです。
実は、様々な方法を試してみて、暫定解ではページングの情報をつけるのがちょっと難しいということがわかり、WordPress の機能をもう少し利用できるのでは、ということで、別解を作りました。少しトリッキーな方法になりますが、普通の検索と同様の出力ができて、テンプレート用の関数が通常どおり使えるようにするには、これしか思いつきませんでした。前の投稿の、2番目の解に近い方法です。
検索フォームを通常のものから変えて、
<form role="search" id="searchform" method="GET" action="<?php echo home_url('/'); ?>"> <label for="s">Search for:</label> <input type="text" value="" name="s" id="s" /> <label for="address">Address</label> <select name="address"> <option value="none">none</option> <option value="関東">関東</option> <option value="東北">東北</option> </select> <input type="hidden" name="use" value="usermeta" /> <input type="submit" id="searchsubmit" value="Search" /> </form>
こんな形にすることは可能ですか? 通常の検索ボックスに加えて、ユーザメタの meta_value をドロップダウンで選択するようにします。検索ボックスは通常どおりに動作させて、meta_value の値によって、AND 検索を実行する、というシナリオです。hidden フィールドはこの検索フォームからの検索のときだけ
以下のコードは、functions.php に書きます。
if (isset($_GET['use']) && $_GET['use'] == 'usermeta') { function replace_search_template($template) { return locate_template(array('search.php')); } add_filter('template_include', 'replace_search_template', 99); } function search_with_usermeta() { if (isset($_GET['address']) && $_GET['address'] != 'none') { global $wpdb, $wp_query; $query_strings = $_GET['s']; $post_types = "('post')"; // 投稿タイプ 複数指定可 $post_status = "publish"; // 投稿ステータス See WP_Query manual $address = $_GET['address']; // 住所 meta_value の値に使う $meta_key = "key"; // wp_usermeta の meta_key フィールド名 $limit = 5; // 1ページに表示する投稿数 $where = ''; $query_strings = stripslashes($query_strings); if (empty($query_strings)) { $search_terms = array(''); } else { $query_strings = urldecode($query_strings) $query_strings = str_replace(array("\r", "\n"), '', $query_strings); if (preg_match_all('/".*?("|$)|((?<=[\t ",+])|^)[^\t ",+]+/', $query_strings, $matches)) { foreach ($matches[0] as $match) { if (preg_match('/^".+"$/', $match)) $search_terms[] = trim($match, "\"'"; else $search_terms[] = trim($match, "\"' "); } if (empty($search_terms)) { $search_terms = array($query_strings); } } else { $search_terms = array($query_strings); } } foreach ($search_terms as $term) { $term = like_escape(esc_sql($term)); $where .= " AND ((p.post_content LIKE '%{$term}%') OR (p.post_title LIKE '%{$term}%'))"; } $where .= " AND (p.post_type IN $post_types AND p.post_status = '{$post_status}')"; $where .= " AND (um.meta_key = '{$meta_key}' AND um.meta_value = '{$address}')"; $sql = "SELECT * FROM $wpdb->posts AS p LEFT JOIN $wpdb->usermeta AS um ON p.post_author=um.user_id WHERE 1=1 $where ORDER BY p.post_date DESC LIMIT $limit"; $args = array( 'query' => $sql, 'posts_per_page' => $limit, 'paged' => get_query_var('paged') ); unset($wp_query); $wp_query = new WP_Query($args); } }
やっていることを少し説明します。
まず、ブラウザが GET 要求で検索クエリ(?s=関東)飛ばすと、これをもとにWordPress は通常の検索用 SQL でメインクエリを作ります。このとき、?s= の後が空白文字列だと、全ての投稿がマッチします。ところが、?s=&meta_value=関東 のような形になると、WordPress は meta_value を処理できないので、カテゴリと解釈し、category.php をテンプレートとして表示するようになります。このとき、meta_value というカテゴリがなければ、全てのカテゴリが表示されます。
is_category => true、is_search => false となるのですね。この動作をメインクエリが作成された後に変更することはできません。WP_Query クラスで定義された以外のものがブラウザからの要求に含まれていると、必ずこのような動作になります。
また、前に説明したとおり、WP_Query で Meta Query を使うと、wp_postmeta を使うことができ、WP_User_Query で Meta Query を使うと、wp_usermeta を使うことができますが、wp_posts と wp_usermeta、wp_users と wp_postmeta の組み合わせは使うことができません。temina さんのやりたいのは、前者となりますが、これを使いたい場合、WP_Query も WP_User_Query も使えません。したがって、query_posts() 関数も pre_get_posts フィルタも使えません。
だから、SQL 文を自前で作ることになるのですが、wpdb クラスを使うと、生のデータが返ってくるので、テンプレート関数が全て使えなくなります。ループで setup_postdata() を使えば一部は動作しますが、ページング情報は使えません。
そこで、SQL 文は自前で作るけれども、実行は、新たに作った WP_Query クラスのインスタンスで行い、すでに作られているメインクエリを破棄して、それに付け替えるということをしています。こんなことをするように作られたクラスではないので、どこかにひずみが出るかもしれませんが、確認した限りでは、通常のメインクエリと同様の動作をし、同様のデータの持ち方になるようです。こちらの方がお勧めなので、前の投稿のコードと差し替えてください。フォームに、author や tag などを追加していくのも難しくはないと思います。なお、フォームでselect を使ったのは、LIKE 検索よりも = の方がスピードが速いというのと、ユーザの入力チェックを省略するためです。
無保証ですが、動かない、使い方がわからない場合はまたポストしてください。
すいません、また修正です。上の投稿は、途中で文章が切れていたりして、さんざんですが、一言で言うと、手動で query_posts() を実行しているということです。また、カテゴリテンプレートの件は、誤りで、is_search => true となっているようです。以下修正版のコードです。
<form role="search" id="searchform" method="GET" action="<?php echo home_url('/'); ?>"> <label for="s">Search for:</label> <input type="text" value="" name="s" id="s" /> <label for="address">Address</label> <select name="address"> <option value="none">none</option> <option value="関東">関東</option> <option value="東北">東北</option> </select> <input type="submit" id="searchsubmit" value="Search" /> <?php wp_nonce_field('my_search', 'my_nonce'); ?> </form>
if (isset($_GET['my_nonce']) && wp_verify_nonce($_GET['my_nonce'], 'my_search') { if (isset($_GET['address']) && $_GET['address'] != 'none') { function replace_search_template($template) { return locate_template(array('search.php')); } add_filter('template_include', 'replace_search_template', 99); search_with_usermeta(); } } elseif (isset($_GET['address'])) { if (!isset($_GET['my_nonce']) || !wp_verify_nonce($_GET['my_nonce'], 'my_search')) { echo 'This operation is not allowed on this server'; exit; } } function search_with_usermeta() { global $wpdb, $wp_query; $query_strings = $_GET['s']; $post_types = "('post')"; // 投稿タイプ 複数指定可 $post_status = "publish"; // 投稿ステータス See WP_Query manual $address = urldecode($_GET['address']); // 住所 meta_value の値に使う $meta_key = "key"; // wp_usermeta の meta_key フィールド名 $limit = 5; // 1ページに表示する投稿数 $where = ''; $query_strings = stripslashes($query_strings); if (empty($query_strings)) { $search_terms = array(''); } else { $query_strings = urldecode($query_strings) $query_strings = str_replace(array("\r", "\n"), '', $query_strings); if (preg_match_all('/".*?("|$)|((?<=[\t ",+])|^)[^\t ",+]+/', $query_strings, $matches)) { foreach ($matches[0] as $match) { if (preg_match('/^".+"$/', $match)) $search_terms[] = trim($match, "\"'"; else $search_terms[] = trim($match, "\"' "); } if (empty($search_terms)) { $search_terms = array($query_strings); } } else { $search_terms = array($query_strings); } } foreach ($search_terms as $term) { $term = like_escape(esc_sql($term)); $where .= " AND ((p.post_content LIKE '%{$term}%') OR (p.post_title LIKE '%{$term}%'))"; } $where .= " AND (p.post_type IN $post_types AND p.post_status = '{$post_status}')"; $where .= " AND (um.meta_key = '{$meta_key}' AND um.meta_value = '{$address}')"; $sql = "SELECT * FROM $wpdb->posts AS p LEFT JOIN $wpdb->usermeta AS um ON p.post_author=um.user_id WHERE 1=1 $where ORDER BY p.post_date DESC LIMIT $limit"; $args = array( 'query' => $sql, 'posts_per_page' => $limit, 'paged' => get_query_var('paged') ); unset($wp_query); $wp_query = new WP_Query($args); }
- トピック「投稿を【ユーザーメタデータ】で検索したい」には新たに返信することはできません。