WordPress(ワードプレス)のサイト内検索の仕組み
まずは簡単に、search.phpを使ったサイト内検索の基本的な仕組みについて把握しておきましょう
WordPress(ワードプレス)でサイト内検索する場合、GETパラメータ「s」を付与すればsearch.phpの内容が表示されます
search.phpではpost_typeが、
- post
- page
- attachment
の3つを検索のターゲットとしています
そして、「s」の値がそれら3つのターゲットの、
- タイトル(post_title)
- 記事の中身(post_content)
- 抜粋(post_excerpt)
に含まれているか検索し(これを、あいまい検索またはLIKE検索といいます)、ヒットしたものを抽出します
たとえば、「ドメイン/?s=ho」と入力された場合、
先ほど挙げた3つのターゲットのpost_title、post_content、post_excerptのいずれかに、「s」の値の文字列「ho」が含まれているかLIKE検索(あいまい検索)します
以上、ここまでが基本となります
タイトルだけにあいまい検索(LIKE検索)をする方法
デフォルトのままだと、タイトルや記事の中身も検索対象としてしまいますが、
タイトルだけに検索をかけたい(記事の中身や抜粋については無視したい)場合があります。
そこで、検索ターゲットを投稿(post_type=post)だけとし、
投稿のタイトル(post_title)だけにあいまい検索をかける方法をご紹介します
HTML
まず、検索フォームのHTML構造が以下のようになっているとします。
<form action="<?php echo home_url('/'); ?>" method="get">
<input type="hidden" name="s">
<input type="text" name="my_title">
<button type="submit">検索する</button>
</form>
2行目がポイントです!
これでテキストフィールドに何かしらの値をいれて送信ボタンを押すと、URLは以下のようになります
上記の通り、URLにパラメータ「s」と「my_title」が付与されていますね!
このように、実際の検索項目としてはユーザーには表示させないけど、パラメータを付けるためにinput[type=”hidden”]を使うことがよくあるので覚えておきましょう)
search.php
続いて、search.phpに以下のように記述します(重要な部分のみ抜粋)
<?php
get_header();
if (isset($_GET['my_title']) && $_GET['my_title']) {
$my_title = htmlspecialchars($_GET['my_title'], ENT_QUOTES, 'UTF-8');
}
$args = array(
'post_type' => array('post'),//検索対象は投稿のみ
'post_status' => 'publish',
'posts_per_page' => -1,
);
if($my_title){
$args += array('_title_like' => $my_title);
}
$the_query = new WP_Query($args);
7行目:
my_titleというGETパラメータを取得して、htmlspecialchars
でエスケープしたものを$my_title変数に格納します。
ちゃんとエスケープしないとセキュリティ的に危ないので(SQLインジェクションなど)、必ずエスケープします。
11~15行目:
22行目でサブクエリを作成するためのargsの基本形を作成しています
★18~20行目:
ここがポイントです。
$my_titleがあるときは、$argsに「_title_like
」という独自のキーを設定します
このキーはあとで使用します
24行目:
サブクエリを作成します
functions.php
最後に、functions.phpに以下を記述します。
add_action( 'pre_get_posts', function( $q )
{
if( $title = $q->get( '_title_like' ) )
{
add_filter( 'posts_where', function ( $where ) use ( $title ) {
global $wpdb;
$where .= ' AND (' . " {$wpdb->posts}.post_title LIKE '%%$title%%' " . ')';
return $where;
} );
}
});
ちょっと難しいのですが、1つずつ解説していきます。
1行目:
「pre_get_posts
」というアクションフックに無名関数①をひっかけています
この無名関数には、$qという変数が参照としてわたってきます
3行目:
「$q->get('_title_like')
」によって、先ほど$argsに追加した_title_like
の値を取得しています
$qというのは、$queryの略で、中身はWP_Queryオブジェクトです。
5行目:
「posts_where
」というフィルターフックに無名関数②をひっかけています
この無名関数には、$whereという変数がわたってきます
また、use
を使って、この無名関数のスコープ外にある、先ほど取得した$titleを渡しています
use
を使わずに無名関数②の中で$titleを使うことができないので注意しましょう
8行目:
$whereに、「post_titleにLIKE検索するSQL文」を文字列連結しています
「' AND ('
」と、
「" {$wpdb->posts}.post_title LIKE '%%$title%%' "
」と、
「 ')'
」
を連結させ、$whereに文字列連結しています
「" {$wpdb->posts}.post_title LIKE '%%$title%%' "
」が少しポイントです
Point01
PHPではダブルクォーテーションとシングルクォーテーションで、その中にある文字がどのように扱われるか変わってきます
たとえば、以下の例ではechoした結果は「hello」ではなく「$str」という文字列が画面に出力されます
<?php
$str = 'Hello';
echo '$str'; //->> $str
?>
一方で、ダブルクォーテーションでechoした場合は、$strの中身が出力されます
<?php
$str = 'Hello';
echo "$str"; //->> Hello
?>
しかし、以下のように書いたとした場合は何も出力されません
<?php
$str = 'Hello';
echo "$strWorld"; //->>何も出力されない
?>
これは、「$strWorld」という変数が定義されていないためです
これを期待通り「HelloWorld」と出力するためには「{ }」で変数を囲みます
<?php
$str = 'Hello';
echo "{$str}World"; //->>HelloWorld
?>
「{ }」で囲まれている部分が1つの変数の塊として認識してくれるようになるめです
Point02
2つ目のポイントは「%%$title%%
」です
通常のSQL文だと、あいまい検索する場合は「where 項目名 like '%値%'
」と書きますが
WordPressの場合は「%%」という感じで、パーセントを2つ重ねます
応用
これを応用した例を1つご紹介します
たとえば、以下のような規則性がある製品名があるとします
※ただし、製品名は投稿のタイトルとして設定されているとする
- 【製品名】製品番号hoge
【グループ】HOGE - 【製品名】製品番号ほげ
【グループ】HOGE
- 【製品名】製品番号fuga
【グループ】FUGA - 【製品名】製品番号ふが
【グループ】FUGA
上記のうえ2つと、した2つはそれぞれ同じグループに属しているとします
検索項目はselectで選ぶかたちになっており、項目名はグループ名のHOGEとFUGAです
そのためHTML構造は以下のようなかたちになります
<form action="<?php echo home_url('/'); ?>" method="get">
<input type="hidden" name="s">
<select name="my_title">
<option value="HOGE">HOGE</option>
<option value="FUGA">FUGA</option>
</select>
<button type="submit">検索する</button>
</form>
期待する挙動としては、HOGEが選択されたら、製品名に「hoge」または「ほげ」が含まれている製品を検索します
反対に、FUGAが選択されたら、製品名に「fuga」または「ふが」が含まれている製品を検索します
セレクトボックスから選択して検索ボタンを押すと以下のようなパラメータが付与されます
冒頭でお伝えした通り、パラメータに「s」がつくとsearch.phpが適用されます
search.phpでは、以下のように記述をします
<?php
get_header();
if (isset($_GET['my_title']) && $_GET['my_title']) {
$my_title = htmlspecialchars($_GET['my_title'], ENT_QUOTES, 'UTF-8');
}
$args = array(
'post_type' => array('product'),//検索対象はカスタム投稿「製品」(スラッグはproduct)
'post_status' => 'publish',
'posts_per_page' => -1,
);
if ($my_title) {
switch ($my_title) {
case 'HOGE':
$value = array('hoge', 'ほげ');
break;
case 'FUGA':
$value = array('fuga', 'ふが');
break;
default:
break;
}
$args += array('_title_like' => $value);
}
$the_query = new WP_Query($args);
19~29行目がポイントです
$my_titleの中身が「HOGE」だったら、「hoge」と「ほげ」を含む配列を用意しています
反対に、$my_titleの中身が「FUGA」だったら、「fuga」と「ふが」を含む配列を用意しています
この配列は、のちほどfuctions.phpで使用します
functions.phpでは以下のように記述します
add_action( 'pre_get_posts', function( $q )
{
if( $titles = $q->get( '_title_like' ) )
{
add_filter( 'posts_where', function ( $where ) use ( $titles ) {
global $wpdb;
$or_where = array();
if(!empty($titles)){
foreach ( $titles as $title ) {
$or_where[] = " {$wpdb->posts}.post_title LIKE '%%$title%%' ";
}
$where .= ' AND (' . implode(' OR ', $or_where) . ')';
}
return $where;
} );
}
});
ポイントは、9~15行目です
以下は、search.phpの36行目の直下でprint_r($the_query)
した際に結果です
※見やすいように改行していますが、実際はなが~い1行です
以下の、8-12行目のSQL文を生成するために、functions.phpの9~15行目の記述をしています
[request] => SELECT
wp_test_posts.* FROM wp_test_posts
WHERE 1=1
AND wp_test_posts.post_type = 'post'
AND ((wp_test_posts.post_status = 'publish'))
AND (
wp_test_posts.post_title LIKE '%%hoge%%'
OR
wp_test_posts.post_title LIKE '%%ほげ%%'
)
ORDER BY wp_test_posts.post_date DESC
これで、製品名だけを検索ターゲットとし、期待通りの挙動になります