WordPressで絞り込み検索!検索機能を拡張してカスタムフィールド・複数カテゴリ・日付範囲で検索できるようにする

カスタム

WordPressの検索機能ではキーワード検索が可能ですが、
商品情報や店舗情報などを掲載するサイトの場合、たくさんのカテゴリ、タグ、カスタムフィールドを持つ投稿を検索させたいケースは必ずと言っていいほどあります。
特にカスタムフィールドは頻繁に使うものだと思います。
検索機能を充実させることで、使いやすく閲覧者に優しいサイト作りをしたいものです。

この検索機能の拡張ですが、プラグインを使うのもありですが自分で実装することも可能です。

WordPressの検索機能を拡張する方法

検索機能を使うには、テンプレートに下記のようなフォームを表示させます。

<form method="get" action="<?php bloginfo('url'); ?>" class="form-horizontal">
	<div class="form-group row">
		<label class="col-sm-3 control-label">キーワード</label>
		<div class="col-sm-9">
			<input class="form-control" name="s" id="s" type="text" placeholder="キーワードを入力"/>
		</div>
	</div>
	<div class="form-group row">
		<div class="col-sm-offset-4 col-sm-8">
			<button id="submit" type="submit" class="btn btn-default">検索</button>
		</div>
	</div>
</form>

s はキーワード検索のテキストフィールドです。
この検索フォームを拡張していくのですが、その拡張したフォームデータを検索条件に含むようカスタマイズをする場合にはフィルターフックを使います。

posts_search

検索条件をカスタマイズしてカスタムフィールドを含ませる

以下の例は、投稿のカスタムフィールドに「番号(number)」「商品コード(item_code)」「商品名(item_name)」の3項目が登録されている場合のケースです。
下記のように検索フォームに「番号」「商品コード」「商品名」を追加してみました。
name="" にはカスタムフィールドの「名前」を入力することで、紐づく「値」が検索条件に含まれます。

「番号」「商品コード」は完全一致検索、
「商品名」は部分一致検索(あいまい検索)させたい場合は、以下の様なコードになります。

テーマファイル(sidebar.php等)にカスタムフィールド項目を追加

<form method="get" action="<?php bloginfo('url'); ?>" class="form-horizontal">
	<div class="form-group row">
		<label class="col-sm-3 control-label">キーワード</label>
		<div class="col-sm-9">
			<input class="form-control" name="s" id="s" type="text" placeholder="キーワードを入力"/>
		</div>
	</div>

	<div class="form-group row">
		<label class="col-sm-3 control-label">番号</label>
		<div class="col-sm-9">
			<input class="form-control" type="text" name="number" />
		</div>
	</div>
	<div class="form-group row">
		<label class="col-sm-3 control-label">商品コード</label>
		<div class="col-sm-9">
			<input class="form-control" type="text" name="item_code" />
		</div>
	</div>
	<div class="form-group row">
		<label class="col-sm-3 control-label">商品名</label>
		<div class="col-sm-9">
			<input class="form-control" type="text" name="item_name" />
		</div>
	</div>
</form>

これだけでは検索結果には反映されません。
functions.php でフィルターフックを使い、検索条件をカスタマイズします。
今回は例で関数 custom_field_search() を作りました。

functions.php でSQLを追記して検索条件をカスタマイズ

<?php

// カスタムフィールドを検索条件に含む
function custom_field_search($search, $query) 
{
	// キーワードがなくても検索ページを表示させる
	if ( isset($wp_query->query['s']) )
		$wp_query->is_search = TRUE;

	// 検索ページ以外はカスタマイズしない
	if ( !$query->is_search )
		return;

	// 完全一致検索(nunmerかcodeが入力されたら検索)
	foreach ( ['number', 'item_code'] as $_key )
	{
		if ( !empty($_REQUEST[$_key]) ) 
		{
			$search .= " AND wp_postmeta.meta_key = '$_key'";
			$search .= " AND wp_postmeta.meta_value = '$_REQUEST[$_key]'";
		}
	}

	// 部分一致検索(item_nameが入力されたら検索)
	foreach ( ['item_name'] as $_key )
	{
		if ( !empty($_REQUEST[$_key]) ) 
		{
			$search .= " AND wp_postmeta.meta_key = '$_key'";
			$search .= " AND wp_postmeta.meta_value LIKE '%$_REQUEST[$_key]%'";
		}
	}

	return $search;
}
add_filter('posts_search','custom_field_search', 10, 2);

// フォームからカスタムフィールドが渡された場合、joinに wp_postmeta を追加
function custom_search_join($join)
{
	foreach ( ['number', 'item_code', 'item_name'] as $_key )
	{
		if ( !empty($_REQUEST[$_key]) )
		{
			$join .= 'INNER JOIN wp_postmeta ON (wp_posts.ID = wp_postmeta.post_id)';
			break;
		}
	}

	return $join;
}
add_filter('posts_join', 'custom_search_join');

?>

これでカスタムフィールドの3項目で検索ができるようになりました。
完全一致検索や部分一致検索したい項目を増やしたい場合は、以下のループに渡している配列に値を増やすだけでOKです。

<?php
	foreach ( ['number', 'item_code', 'xxx', 'xxx'] as $_key )
?>

今度はカテゴリで検索させてみます。

検索条件にカテゴリを含ませる

カテゴリ検索については functions.php を触る必要なく、フォームに追加するだけでOKです。

テーマファイルにカテゴリのセレクトボックスを追加

<form method="get" action="<?php bloginfo('url'); ?>" class="form-horizontal">
	<div class="form-group row">
		<label class="col-sm-3 control-label">カテゴリ</label>
		<div class="col-sm-9">
			<?php wp_dropdown_categories('orderby=name&hide_empty=1&depth=0&show_option_all=全て&class=form-control'); ?>
		</div>
	</div>
</form>

カテゴリについてはセレクトボックスで出力してくれる関数 wp_dropdown_categories() を使います。
セレクトボックスのname属性が cat になっているのが確認できます。
フォームでこの cat を指定することでカテゴリを検索条件に含めることができます。

検索条件をカスタマイズして複数カテゴリを含ませる

今度は複数のカテゴリを条件に入れたい場合です。
複数の場合も functions.php を触る必要はありません。
その代わりname属性を少し変えます。下記の例ではカテゴリをチェックボックスで選択できる様にしています。
get_categories() で全カテゴリを取得していますが、この関数は引数を指定しなくても
登録された記事がないカテゴリは表示されないようになっています。

テーマファイルにカテゴリのチェックボックスを追加

<form method="get" action="<?php bloginfo('url'); ?>" class="form-horizontal">
	<div class="form-group row">
		<label class="col-sm-3 control-label">カテゴリ(複数)</label>
		<div class="col-sm-9">
		<?php
		// 全カテゴリを取得する
        $categoties = get_categories();

		if ( !empty($categoties) ):
        foreach( $categoties as $_cat ): ?> 
			<div class="form-check">
			<input class="form-check-input" type="checkbox" value="<?php echo $_cat->cat_ID ?>" name="cat[]" id="cat_<?php echo $_cat->cat_ID ?>"" />
			<label class="form-check-label col-form-label" for="cat_<?php echo $_cat->cat_ID ?>"><?php echo esc_html($_cat->cat_name) ?></label>
			</div>
        <?php endforeach;
		endif; ?> 
		</div>
	</div>
</form>

name属性が cat[] になっています。これで配列としてcatを複数渡すことができます。
値については、カテゴリID(cat_ID)をセットします。
これだけでOKです。

検索条件に複数タグを含ませる

タグの場合もカテゴリとほとんど同じです。
複数検索させるため、配列として渡します。

テーマファイルにタグのチェックボックスを追加

<form method="get" action="<?php bloginfo('url'); ?>" class="form-horizontal">
	<div class="form-group row">
		<label class="col-sm-3 control-label">タグ(複数)</label>
		<div class="col-sm-9">
		<?php
        $tags = get_tags(); 

        foreach( $tags as $_tag ): ?> 
			<div class="form-check">
				<input class="form-check-input" type="checkbox" value="<?php echo $_tag->slug ?>" name="tag[]" id="tag_<?php echo $_tag->slug ?>"" />
				<label class="form-check-label col-form-label" for="tag_<?php echo $_tag->slug ?>"><?php echo esc_html($_tag->name) ?></label>
			</div>
        <?php endforeach; ?> 
		</div>
	</div>
</form>

タグ検索の場合、 tag で渡すとタグで絞り込んだ検索ができます。
複数の場合は tag[] です。

検索条件をカスタマイズして日付範囲を指定して検索させる

日付については functions.php のカスタマイズが必須になります。
ある日付からある日付までを含む、という検索条件なので、SQLのBETWEENを使います。

テーマファイルに2つの日付フィールドを追加

<form method="get" action="<?php bloginfo('url'); ?>" class="form-horizontal">
	<div class="form-group row">
		<label class="col-sm-3 control-label">日付</label>
		<div class="col-sm-9">
			<input class="form-control" type="date" name="date_from" />〜
			<input class="form-control" type="date" name="date_to" />
		</div>
	</div>
</form>

例1:記事投稿日を日付範囲検索させる

記事の投稿日時を範囲検索する場合、検索対象がカスタムフィールドではないので
上にあったような wp_postmeta をJOINする必要はありません。

<?php

// 投稿日時を検索条件に含む
function custom_field_search($search, $query) 
{
	// キーワードがなくても検索ページを表示させる
	if ( isset($wp_query->query['s']) )
		$wp_query->is_search = TRUE;

	// 検索ページ以外は変更しない
	if ( !$query->is_search )
		return;

	// 期間検索
	if ( !empty($_REQUEST['date_from']) && !empty($_REQUEST['date_to']) ) 
	{
		$from = "'". date('Y-m-d',  strtotime($_REQUEST['date_from'])). "'";
		$to   = "'". date('Y-m-d',  strtotime($_REQUEST['date_to'])). "'";

		$search .= " AND DATE(post_date) BETWEEN $from AND $to";
	}

	return $search;
}
add_filter('posts_search','custom_field_search', 10, 2);

?>

例2:カスタムフィールドを日付範囲検索させる

こちらはカスタムフィールドなので、忘れずにJOINしてください。
item_date というカスタムフィールドで日付検索をする場合は下記の通りです。

<?php

// 投稿日時を検索条件に含む
function custom_field_search($search, $query) 
{
	// キーワードがなくても検索ページを表示させる
	if ( isset($wp_query->query['s']) )
		$wp_query->is_search = TRUE;

	// 検索ページ以外は変更しない
	if ( !$query->is_search )
		return;

	// 期間検索(item_dateで日付範囲検索)
	if ( !empty($_REQUEST['date_from']) && !empty($_REQUEST['date_to']) ) 
	{
		$from = "'". date('Y-m-d',  strtotime($_REQUEST['date_from'])). "'";
		$to   = "'". date('Y-m-d',  strtotime($_REQUEST['date_to'])). "'";

		$search .= " AND wp_postmeta.meta_key = 'item_date'";
		$search .= " AND DATE(wp_postmeta.meta_value) BETWEEN $from AND $to";
	}

	return $search;
}
add_filter('posts_search','custom_field_search', 10, 2);

// フォームからカスタムフィールドが渡された場合、joinにpostmetaを追加
function custom_search_join($join)
{
	foreach ( ['date_from', 'date_to'] as $_key )
	{
		if ( !empty($_REQUEST[$_key]) )
		{
			$join .= 'INNER JOIN wp_postmeta ON (wp_posts.ID = wp_postmeta.post_id)';
			break;
		}
	}

	return $join;
}
add_filter('posts_join', 'custom_search_join');

?>

custom_search_join() のループで判別させている値はフォームでのname属性なので、
フォームでのnameとカスタムフィールドの名前が一致しない場合はお気をつけください。

おわりに

こんな風にフィルターフックとSQLを駆使することでわりかし何でもできます。
いくつか例を挙げましたが、まだまだあると思いますので試してみてはいかがでしょうか。

カスタム

関連記事