WordPress(ワードプレス)のサイト内検索の仕組み

まずは簡単に、search.phpを使ったサイト内検索の基本的な仕組みについて把握しておきましょう

WordPress(ワードプレス)でサイト内検索する場合、GETパラメータ「s」を付与すればsearch.phpの内容が表示されます

【WordPress】タイトルだけにあいまい検索(LIKE検索)する方法

search.phpではpost_typeが、

  • post
  • page
  • attachment

の3つを検索のターゲットとしています

そして、「s」の値がそれら3つのターゲットの、

  • タイトル(post_title)
  • 記事の中身(post_content)
  • 抜粋(post_excerpt)

に含まれているか検索し(これを、あいまい検索またはLIKE検索といいます)、ヒットしたものを抽出します

【WordPress】タイトルだけにあいまい検索(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は以下のようになります

【WordPress】タイトルだけにあいまい検索(LIKE検索)する方法

上記の通り、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>
【WordPress】タイトルだけにあいまい検索(LIKE検索)する方法

期待する挙動としては、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 

これで、製品名だけを検索ターゲットとし、期待通りの挙動になります