Smart Custom Fields を使用した複合検索カスタマイズのサンプルを子テーマとして作ってみたのでメモ。
前提
今回は次のような前提で考えました。
- 使用するフィールド:
- テキスト、テキストエリア、ラジオ、選択、チェックボックスの5つ
- それ以外は使用しない
- 検索フォーム:
- カスタムフィールドの項目によってフォームの個数や項目は自動的に増減
- キーワード検索:
- 標準の機能(記事タイトル・本文)は使用しない
- 代わりにカスタムフィールドの「テキスト」または「テキストエリア」の文字列に対して行う
- カスタムフィールドで項目が複数ある場合はOR検索
- スペース区切りによるORやAND検索はしない
- 項目検索:
- キーワードのみ複数のフィールドをまたがってのOR検索だが、それ以外はAND検索
- チェックボックスの項目に対しても検索時に選択できるのは1つの項目のみとする
テーマ
テーマは Twenty Twenty-One の子テーマとして作成。
twentytwentyone-child/
├ sfc_init/
│ └ sfc-init.php
│
├ footer.php
├ search.php
├ searchform.php
└ style.css
今回はサンプルなので最低限のファイルのみで構成しました。
style.css
/*
Theme Name: Twenty Twenty-One Child
Version: 1.5
Template: twentytwentyone
*/
style.css
はテーマ認識のために用意。特に何かをするわけではありません。
sfc_init/sfc-init.php
<?php
return XXX;
XXX
は Smart Custom Fields の設定が保存された投稿の投稿IDです。今回はサンプルなので安直にハードコーディングで済ませました。
footer.php
<?php get_template_part( 'template-parts/footer/footer-widgets' ); ?>
<?php get_template_part( 'searchform' ); ?> <!-- 追記 -->
<footer id="colophon" class="site-footer">
こちらも安直に、 footer.php
の中に検索フォームを読み込むように1行追記。
searchform.php
<?php
/**
* The searchform.php template.
*
* Used any time that get_search_form() is called.
*
* @link https://developer.wordpress.org/reference/functions/wp_unique_id/
* @link https://developer.wordpress.org/reference/functions/get_search_form/
*
* @package WordPress
* @subpackage Twenty_Twenty_One
* @since Twenty Twenty-One 1.0
*/
/*
* Generate a unique ID for each form and a string containing an aria-label
* if one was passed to get_search_form() in the args array.
*/
$twentytwentyone_unique_id = wp_unique_id( 'search-form-' );
$twentytwentyone_aria_label = ! empty( $args['aria_label'] ) ? 'aria-label="' . esc_attr( $args['aria_label'] ) . '"' : '';
$sfc_settings_id = require( __DIR__ . '/sfc_init/sfc-init.php' );
$scf_ettings = get_post_meta($sfc_settings_id, 'smart-cf-setting'); // 投稿IDは決め打ち
$scf_ettings_array = maybe_unserialize($scf_ettings)[0];
?>
<form role="search" <?php echo $twentytwentyone_aria_label; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Escaped above. ?> method="get" class="search-form" action="<?php echo esc_url( home_url( '/' ) ); ?>">
<div style="display:block;width:100%;">
<label for="<?php echo esc_attr( $twentytwentyone_unique_id ); ?>"><?php _e( 'Search…', 'twentytwentyone' ); // phpcs:ignore: WordPress.Security.EscapeOutput.UnsafePrintingFunction -- core trusts translations ?></label>
<input type="hidden" id="<?php echo esc_attr( $twentytwentyone_unique_id ); ?>" class="search-field" value="<?php echo get_search_query(); ?>" name="s" />
</div>
<?php
foreach ($scf_ettings_array as $key => $val) {
?>
<?php
if ( $val['fields'][0]['type'] === 'select' || $val['fields'][0]['type'] === 'check' ) {
$str = str_replace(
[
"\r\n",
"\r",
"\n",
],
"\n",
$val['fields'][0]['choices']
);
$val_array = explode("\n", $str)
?>
<div style="display:block;width:100%;">
<label for="sfc-search-<?= esc_attr($val['fields'][0]['name']); ?>"><?= esc_attr($val['fields'][0]['label']); ?></label>
<select name="sfc-search-<?= esc_attr($val['fields'][0]['name']); ?>" id="sfc-search-<?= esc_attr($val['fields'][0]['name']); ?>">
<?php foreach ($val_array as $va_key => $va_val) { ?>
<option
value="<?= esc_attr($va_val); ?>"
<?= $va_key === 0 ? ' selected' : ''; ?>
>
<?= esc_attr($va_val); ?>
</option>
<?php } ?>
</select>
</div>
<?php
}
else if ( $val['fields'][0]['type'] === 'radio' ) {
$str = str_replace(
[
"\r\n",
"\r",
"\n",
],
"\n",
$val['fields'][0]['choices']
);
$val_array = explode("\n", $str)
?>
<div style="display:block;width:100%;">
<label for="sfc-search-<?= esc_attr($val['fields'][0]['name']); ?>_0"><?= esc_attr($val['fields'][0]['label']); ?></label>
<?php foreach ($val_array as $va_key => $va_val) { ?>
<label for="sfc-search-<?= esc_attr($val['fields'][0]['name']); ?>_<?= esc_attr($va_key); ?>">
<input
type="radio"
name="sfc-search-<?= esc_attr($val['fields'][0]['name']); ?>"
id="sfc-search-<?= esc_attr($val['fields'][0]['name']); ?>_<?= esc_attr($va_key); ?>"
value="<?= esc_attr($va_val); ?>"
<?= $va_key === 0 ? ' checked' : ''; ?>
><?= esc_attr($va_val); ?>
</label>
<?php } ?>
</div>
<?php
}
}
?>
<div style="display:block;width:100%;">
<label for="sfc-search-freewords">フリーワード</label>
<input
type="search"
name="sfc-search-freewords"
id="sfc-search-freewords"
>
</div>
<?php
// query reset
wp_reset_postdata();
?>
<div style="display:block;width:100%;">
<input type="submit" class="search-submit" value="<?php echo esc_attr_x( 'Search', 'submit button', 'twentytwentyone' ); ?>" />
</div>
</form>
本題の検索フォーム本体。
- 最初に
$sfc_settings_id = require( __DIR__ . '/sfc_init/sfc-init.php' );
で Smart Custom Fields の設定の投稿IDを取得- 続けて
$scf_ettings = get_post_meta($sfc_settings_id, 'smart-cf-setting');
で設定を投稿から読み込み maybe_unserialize()
でシリアライズされたレコードのデータから設定を配列にパース
- 続けて
- パースされた設定の配列を
foreach
でループ- 選択、チェックボックスならば改行(複数考えられる改行コードを
\n
で統一しつつ)ごとに項目を分解してセレクトボックスとして展開。1つ目の項目はselected
を付けておく - ラジオボタンの場合もほぼ同様の処理。1つ目の項目は
checked
を付けておく
- 選択、チェックボックスならば改行(複数考えられる改行コードを
- ループ後に決め打ちでキーワード検索のテキストボックスを設置
- 複数のフィールドにまたがる想定なので、ループで該当フィールドごとに生成するとテキストボックスが増産されてしまうため
search.php
<?php
/**
* The template for displaying search results pages
*
* @link https://developer.wordpress.org/themes/basics/template-hierarchy/#search-result
*
* @package WordPress
* @subpackage Twenty_Twenty_One
* @since Twenty Twenty-One 1.0
*/
function search_param_get() {
//GETパラメータの取得とエスケープ(ない場合はNULL)
if(isset($_GET)) {
$s_param = $_GET;
array_walk_recursive($s_param, function(&$item, $key){ $item = esc_html($item); });
}
else {
$s_param = [];
}
return $s_param;
}
function search_procedure() {
$sfc_settings_id = require( __DIR__ . '/sfc_init/sfc-init.php' );
$scf_ettings = get_post_meta($sfc_settings_id, 'smart-cf-setting'); // 投稿IDは決め打ち
$scf_ettings_array = maybe_unserialize($scf_ettings)[0];
$s_param = search_param_get();
if($s_param !== NULL) {
//クエリ用のパラメータを作成
$args = [];
$meta_query = [];
$args_freewords = [];
$meta_query_freewords = [];
foreach ($scf_ettings_array as $key => $val) {
if(array_key_exists('sfc-search-' . esc_attr($val['fields'][0]['name']), $s_param)) {
$compare_val = '=';
$value_val = $s_param['sfc-search-' . esc_attr($val['fields'][0]['name'])];
if(
esc_attr($val['fields'][0]['type'] === 'select')
|| esc_attr($val['fields'][0]['type'] === 'check' )
|| esc_attr($val['fields'][0]['type'] === 'radio' )
) {
if(mb_strlen(
$s_param['sfc-search-' . esc_attr($val['fields'][0]['name'])],
'UTF-8'
) > 0
&& $s_param['sfc-search-' . esc_attr($val['fields'][0]['name'])] !== '未選択') {
$meta_query[] = [
[
'key' => esc_attr($val['fields'][0]['name']),
'value' => $value_val,
'type' => 'CHAR',
'compare' => $compare_val,
],
];
}
}
}
else if(
esc_attr($val['fields'][0]['type'] === 'text')
|| esc_attr($val['fields'][0]['type'] === 'textarea' )
) {
if(
array_key_exists('sfc-search-freewords', $s_param)
&& mb_strlen(
$s_param['sfc-search-freewords'],
'UTF-8'
) > 0
) {
$compare_val = 'LIKE';
$value_val = $s_param['sfc-search-freewords'];
$meta_query_freewords[] = [
[
'key' => esc_attr($val['fields'][0]['name']),
'value' => $value_val,
'type' => 'CHAR',
'compare' => $compare_val,
],
];
}
}
}
if(count($meta_query) > 0 || count($meta_query_freewords) > 0) {
$args = [
'meta_query' => [
'relation' => 'AND',
],
];
$args['meta_query'] = $args['meta_query'] + $meta_query;
}
if(count($meta_query_freewords) > 0) {
$args_freewords = [
'meta_query' => [
'relation' => 'OR',
],
];
$args_freewords['meta_query'] = $args_freewords['meta_query'] + $meta_query_freewords;
$args['meta_query'] = $args['meta_query'] + $args_freewords;
}
$args = $args + [
'post_type' => 'post',
'post_status' => 'publish',
'order' => 'DESC',
'orderby' => 'date',
];
}
else {
$args = [];
}
//定義したargsでクエリ発行
$the_query = new WP_Query($args);
return $the_query;
}
$the_query = search_procedure();
$s_param = search_param_get();
get_header();
if ( $the_query->have_posts() ) {
?>
<header class="page-header alignwide">
<h1 class="page-title">
<?php
printf(
/* translators: %s: Search term. */
esc_html__( 'Results for "%s"', 'twentytwentyone' ),
'<span class="page-description search-term">' . isset($s_param['sfc-search-freewords']) && !empty($s_param['sfc-search-freewords']) ? esc_html( $s_param['sfc-search-freewords'] ) : '' . '</span>'
);
?>
</h1>
</header><!-- .page-header -->
<div class="search-result-count default-max-width">
<?php
printf(
esc_html(
/* translators: %d: The number of search results. */
_n(
'We found %d result for your search.',
'We found %d results for your search.',
(int) $the_query->found_posts,
'twentytwentyone'
)
),
(int) $the_query->found_posts
);
?>
</div><!-- .search-result-count -->
<?php
// Start the Loop.
while ( $the_query->have_posts() ) {
$the_query->the_post();
/*
* Include the Post-Format-specific template for the content.
* If you want to override this in a child theme, then include a file
* called content-___.php (where ___ is the Post Format name) and that will be used instead.
*/
get_template_part( 'template-parts/content/content-excerpt', get_post_format() );
} // End the loop.
// Previous/next page navigation.
twenty_twenty_one_the_posts_navigation();
// If no content, include the "No posts found" template.
} else {
get_template_part( 'template-parts/content/content-none' );
}
wp_reset_postdata();
get_footer();
こちらは検索結果ページ。
search_param_get()
: GETパラメータを分解して検索項目を配列にするsearch_procedure()
: 検索処理$sfc_settings_id = require( __DIR__ . '/sfc_init/sfc-init.php' );
で Smart Custom Fields の設定の投稿IDを取得して以下同search_param_get()
で検索パラメータをパースforeach
で検索パラメータをループmeta_query
でAND
条件を指定- 選択、チェック、ラジオの場合は該当するフィールド名の値が文字列一致しているか判定。検索フォームで「未選択」の文字列が送られてきた場合は条件判定に加えずスルーする
- テキスト、テキストエリアの場合は文字列を LIKE 検索。
meta_query
は他の項目よりも1段階深い入れ子になっており、ここではOR
条件を指定している
大体このようなフローにしました。
検証
データの準備
![Smart Custom Fields の設定](https://labor.ewigleere.net/wp-content/uploads/2022/02/wp-sfc-search-customize-20220221_1-355x1024.jpg)
例えばこのように Smart Custom Fields を設定します。内容は適当に、「この技術を試してみたい」というトピックを扱うものとします。
![Smart Custom Fields を設定した投稿データ](https://labor.ewigleere.net/wp-content/uploads/2022/02/wp-sfc-search-customize-20220221_2-1024x648.jpg)
上述の Smart Custom Fields の項目に従って投稿を追加します。
検索フォーム
これで作成した子テーマに切り替えると……
![Smart Custom Fields から生成された検索フォーム](https://labor.ewigleere.net/wp-content/uploads/2022/02/wp-sfc-search-customize-20220221_3.jpg)
上述の Smart Custom Fields の設定に従って、検索フォームが生成されました。
検索
いくつかのパターンで検索してみます。
![検索内容1](https://labor.ewigleere.net/wp-content/uploads/2022/02/wp-sfc-search-customize-20220221_4.jpg)
![検索結果1](https://labor.ewigleere.net/wp-content/uploads/2022/02/wp-sfc-search-customize-20220221_5.jpg)
きちんとヒットしました。複数の条件で絞り込まれています。
![検索内容2](https://labor.ewigleere.net/wp-content/uploads/2022/02/wp-sfc-search-customize-20220221_6.jpg)
![検索結果2](https://labor.ewigleere.net/wp-content/uploads/2022/02/wp-sfc-search-customize-20220221_7.jpg)
別パターン。こちらもヒットしました。
![検索内容3](https://labor.ewigleere.net/wp-content/uploads/2022/02/wp-sfc-search-customize-20220221_8.jpg)
![検索結果3](https://labor.ewigleere.net/wp-content/uploads/2022/02/wp-sfc-search-customize-20220221_9-1024x426.jpg)
フリーワード検索。こちらもヒット。
軽く試験した感じではきちんと動作していることが確認できました。
参考
Smart Custom Fields
- WordPress のカスタムフィールドを簡単・便利に使えるようになるプラグイン「Smart Custom Fields」作った。 – モンキーレンチ
- Smart Custom Fieldsの繰り返し(グループ)機能の出力タグ一覧 – クモのようにコツコツと
- Smart Custom Fields の基本的な使い方 | オレインデザイン – 岐阜県岐阜市のホームページ専門家
- 【WordPress】カスタムフィールドならこれ!Smart Custom Fields(SCF)の使い方完全まとめ – 東京のホームページ制作 \/ WEB制作会社 BRISK
表示
カスタマイズ
- 【WordPress】プラグイン Smart Custom Fields でカスタムフィールドを半自作する | バシャログ。
- 【WordPress】Smart Custom Fieldsをコード定義で管理する|notes by SHARESL
- SmartCustomFieldsのコードでの定義についてのまとめ – Qiita
検索フォーム
WP_Query
meta_query の複合条件検索
ANDとORを組み合わせて使うことも可。