🏷️

WordPress で カテゴリーをチェックボックスで絞る

2023/05/30に公開

概要

ECサイトなどではよくある実装ですが、一覧ページで絞り込んで表示したい場合があると思います。
WordPress の場合、カテゴリーで絞り込みたいことがよくあると思います。

前提

カスタム投稿「blog」、カスタムタクソノミー「blog_cat」を発行し、カスタムタクソノミーを絞り込んで表示させます。OR検索ではなくAND検索です。

HTML

まずチェックボックスを発行します。

functions.php
/**
 * ブログカテゴリー:チェックボックスを出力
 *
 * @return string
 **/
function blogTaxCheckbox(): string
{
    $output = '';
    $args = [
        'hide_empty' => false,
    ];
    $terms = get_terms('blog_cat', $args);
    if (is_wp_error($terms) || !$terms) return '';

    // ここでGETパラメータを取得し、サニタイズ
    $selected_cats = isset($_GET['blog_search']) ? $_GET['blog_search'] : null;
    $selected_cats = is_array($selected_cats) ? array_map('sanitize_text_field', $selected_cats) : sanitize_text_field($selected_cats);


    foreach ($terms as $term) {
        // チェックボックスの値と一致するかチェック
        $checked = is_array($selected_cats) && in_array($term->term_id, $selected_cats) ? 'checked' : '';

        $output .= '
		<span class="mwform-checkbox-field horizontal-item">
			<label>
				<input type="checkbox" name="blog_search[]" value="' . $term->term_id . '" ' . $checked . '>
				<span class="mwform-checkbox-field-text">' . esc_html($term->name) . '</span>
			</label>
		</span>
        ';
    }
    return $output;
}
<form method="get" action="<?php echo esc_url(home_url('/')); ?>">
	<div class="select-area02">
		<div class="checkbox-list contact-form">
			<?php echo blogTaxCheckbox(); ?>
		</div>
		<div class="btn-search">
			<input type="submit" value="このカテゴリーで検索">
		</div>
	</div>
	<!-- Hidden input for post type -->
	<input type="hidden" name="post_type" value="blog" />
</form>

こんな感じのチェックボックスが発行されます。

pre_get_posts

GETで渡された値に対して、pre_get_posts でクエリを発行します

functions.php
function change_posts_per_page($query)
{
  /* 管理画面,メインクエリに干渉しないために必須 */
  if (is_admin() || !$query->is_main_query()) {
    return;
  }

  if (isset($_GET['post_type']) && $_GET['post_type'] === 'blog') {
    $query->set('post_type', 'blog');
    $tax_query = blogTaxSearchQuery();
    if ($tax_query) {
      $query->set('tax_query', $tax_query);
    }

    return $query;
  }
}
add_action('pre_get_posts', 'change_posts_per_page');

/**
 * ブログカテゴリー:チェックボックスによる絞り込み検索
 * GETで渡ってきたidをpre_get_postsに渡す
 *
 **/
function blogTaxSearchQuery()
{

  $categories = isset($_GET['blog_search']) ? $_GET['blog_search'] : null;

  if (!$categories) return;

  $categories = is_array($categories) ? array_map('sanitize_text_field', $categories) : sanitize_text_field($categories);


  if (!is_array($categories)) return;
  $tax_query = [
    [
      'taxonomy'  => 'blog_cat',
      'field'     => 'term_id',
      'terms'     => $categories,
      'operator'  => 'AND',
    ],
  ];
  return $tax_query;
}

ハマりどころ

チェックボックスの name をタクソノミー名と同じにしていたところ、大いにハマりました。
pre_get_posts で設定した tax_query の他に何故かもうひとつ tax_query が発行され、そちらが優先されてしまい、期待する投稿が表示されませんでした。

WordPress では様々なルールによるルーティングが設定されていますが、URLによるルーティングももちろん行われます。
そのため、チェックボックス name を blog_cat にした場合、
?blog_cat%5B%5D=7&blog_cat%5B%5D=1&post_type=blog
のようなクエリ列になり、カスタムタクソノミーblog_cat のルーティングが走ってしまいます。
pre_get_posts で設定した tax_query の後に走ってしまうため、意図したルーティングにならない現象で大いに悩みました。
解決はシンプルで、タクソノミー名と同じ name を使わないだけです。

ちなみにコードは ChatGPT に質問しながら作りました。
先ほどのハマりどころで落ち着いて考えれば解決出来たのですが、有能な ChatGPT ならそのまま解決出来るかもと投げた結果、沼にハマった次第です

Discussion