Posted on

Category filter for search results pages

Sometimes it’s nice to have a category filter on the search results pages: a simple dropdown where you can choose the category you want to show. It’s easy to create one using wp_dropdown_categories() but on a search results page, that’s slightly problematic, as it’ll include all categories, not just those included in the search results.

So, here’s a two-part function that fixes that. First, we need a filter that processes the search results and gets a list of categories in the results. This filter runs very late on the relevanssi_hits_filter hook, so that it runs after other filters on the same hook:

add_filter( 'relevanssi_hits_filter', 'rlv_gather_categories', 99 );
function rlv_gather_categories( $hits ) {
    global $rlv_categories_present;
    $rlv_categories_present = array();
    foreach ( $hits[0] as $hit ) {
        $terms = get_the_terms( $hit->ID, 'category' );
        if ( is_array( $terms ) ) {
            foreach ( $terms as $term ) {
                $rlv_categories_present[ $term->term_id ] = $term->name;
            }
        }
    }
    asort( $rlv_categories_present );
    return $hits;
}

Add this snippet to your site. If you want this to use WooCommerce product categories, replace the category in the get_the_terms() call with product_cat like this:

$terms = get_the_terms( $hit->ID, 'product_cat' );

Then we can create the function that displays the dropdown. It’s a simple dropdown that lists the categories in alphabetical order (that comes from the asort() in the first function, actually) and includes a bit of JavaScript that adds a cat parameter with the correct value to the current URL and reloads the page.

If a category parameter is already in place, there’s instead a link that removes all category filters.

function rlv_category_dropdown() {
    global $rlv_categories_present, $wp_query;
 
    if ( ! empty( $wp_query->query_vars['cat'] ) ) {
        $url = esc_url( remove_query_arg( 'cat' ) );
        echo "<p><a href='$url'>Remove category filter</a>.</p>";
    }
    else {
        $select = "<select id='rlv_cat' name='rlv_cat'><option value=''>Choose a category</option>";
        foreach ( $rlv_categories_present as $cat_id => $cat_name ) {
            $select .= "<option value='$cat_id'>$cat_name</option>";
        }
        $select .= "</select>";
        $url     = esc_url( remove_query_arg( 'paged' ) );
        if ( false !== strpos( $url, 'page' ) ) {
            $url = preg_replace( '/page\/\d+\//', '', $url );
        }
        $select .= <<<EOH
 
<script>
<!--
    var dropdown = document.getElementById("rlv_cat");
    function onCatChange() {
        if ( dropdown.options[dropdown.selectedIndex].value > 0 ) {
            location.href = "$url"+"&cat="+dropdown.options[dropdown.selectedIndex].value;
        }
    }
    dropdown.onchange = onCatChange;
-->
</script>
EOH;
 
        echo $select;
    }
}

For product categories, cat needs to be replaced with product_cat.

Once you’ve added both functions to your site, you can add a

<?php rlv_category_dropdown(); ?>

to your theme search results template wherever you think is suitable.

If you prefer having checkboxes instead of a dropdown, you can use this:

function rlv_category_dropdown() {
    global $rlv_categories_present, $wp_query;

    if ( ! empty( $wp_query->query_vars['cat'] ) ) {
        $url = esc_url( remove_query_arg( 'cat') );
        echo "<p><a href='$url'>Remove category filter</a>.</p>";
    }
    else {
        $select = "<div id='catCheckboxes'><p>Choose a category:</p>";
        foreach ( $rlv_categories_present as $cat_id => $cat_name ) {
			$select .= "<input type='checkbox' name='rlv_cat[]' value='$cat_id'> $cat_name<br />";
		}
		$select .= '</div>';
        $url     = esc_url( remove_query_arg( 'paged' ) );
        if ( false !== strpos( $url, 'page' ) ) {
            $url = preg_replace( '/page\/\d+\//', '', $url );
        }
        $select .= <<<EOH

<script>
<!--
	var catCheckboxes = document.querySelectorAll("#catCheckboxes");
	catCheckboxes.forEach(function(element) {
		element.addEventListener('change', (event) => {
			location.href = "$url"+"&cat="+event.target.value;
		})
	})
-->
</script>
EOH;
        echo $select;
    }
}

33 comments Category filter for search results pages

  1. The ability to filter out just posts from a certain category really helps the user to find what they need with just one extra click. This makes an already excellent plugin even more awesome! Thanks so much Mikko!

  2. Hi Mikko,

    Great plugin! I want to do exactly the same as in above code, but then for custom fields (meta_key/meta_value). Is that possible? How do I need to alter this code?

    (I also checked out the article about filters with custom fields, but this is for adding a field to the search form, and I want it outside the search form, just like filters in the left sidebar you see a lot).

    Thanks!
    Jason

      1. I was thinking in that direction. I changed the following lines of the code like this:

        //$terms = get_the_terms( $hit->ID, ‘category’ );
        $terms = get_post_meta( $hit->ID, ‘_wmw_mbe_age’ );

        // $rlv_categories_present[ $term->term_id ] = $term->name;
        $rlv_categories_present[ $term->meta_id ] = $term->meta_value;

        $terms contains the right values, but the array $rlv_categories_present is empty. But maybe I’m thinking too simple…

        I’m thinking about buying the developer license. Do you in that case give more support / help to get things like this done?

        Thanks!

        1. At least you should use $term->meta_key instead of $term->meta_id.

          So if you echo out the $term inside the foreach loop, you’re seeing the correct data? And if you print out $rlv_categories_present inside the loop, the data gets added there?

          But yeah, this is something where I can help if you buy a license.

  3. Yes, that’s right: in the foreach loop I see the correct data for $term. But if I print $rlv_categories_present I get Array ( [0] => ). So there’s something going wrong there. Also with your suggested change by the way.

    I just purchased a developer license with my business username. I totally understand that.

  4. Does this still work? I have added the functions to my custom function.php and called rlv_customfield_dropdown(); however, it throws the following error on my search page:

    Fatal error: Uncaught Error: Call to undefined function rlv_customfield_dropdown() in /search.php:44 Stack trace: #0 /template-loader.php(74): include() #1 /wp-blog-header.php(19): require_once(‘/home/against7/…’) #2 /index.php(17): require(‘/home/against7/…’) #3 {main} thrown in /search.php on line 44

    Any ideas?

    1. Joshua, you’re getting “undefined function” error, so the function is not defined when you try to use it. Is it in the right place? Theme functions.php should work, but perhaps your theme is doing something unusual and the file is not being loaded on the search results template for some reason.

      1. Wow, thanks for your fast reply. I am using Divi theme. I noticed that no matter where I place the rlv_customfield_dropdown(); it throws the error at that location. Also, I noticed that as long as the code is in functions.php (without calling rlv_customfield_dropdown(); at all), it returns “No results found”. I commented out the code in functions.php and my results returned.

      2. Nevermind my last reply. I figured it out (well, sort of). I copied the code from the GIT and that didn’t work. One reason was that I noticed I was calling rlv_customfield_dropdown(); instead of rlv_category_dropdown();. I came back and used the code in this post and it worked. I’m still not sure why I was getting “no results found”. That is resolved now, though.

  5. Is it possible to display the current filtered category in the page title?

    If I search for the term “apples” my title is currently “12 results found for ‘apples'”

    I want it to be “12 results found for ‘apples’ in the category ‘cat'”

    1. Tom, it depends on the exact method of how the category is filtered, but this is one approach that works if you use the cat query variable:

      global $wp_query;
      $category = get_category($wp_query->query_vars['cat']);
      echo $category->cat_name;
  6. Thanks Mikko.
    I was able to follow the instructions and get the intended result.

    However, is there any way the category filter to show only selected categories?
    If not, then can it show only the parent categories?

    Thanks.

    1. Yes. Inside the

      foreach ( $rlv_categories_present as $cat_id => $cat_name ) {
      $select .= "";
      }

      loop you can skip the ones you don’t want to include. Like this:

      foreach ( $rlv_categories_present as $cat_id => $cat_name ) {
      if ( ! in_array( $cat_name, array( 'Category A', 'Category B', 'Category C' ), true ) ) {
      continue;
      }
      $select .= "";
      }

      This would only include categories A, B and C.

    1. Joshua, author is not a taxonomy. Instead of

      $terms = get_the_terms( $hit->ID, 'author' );
      if (is_array($terms)) {
          foreach ( $terms as $term ) {
              $rlv_author_present[ $term->term_id ] = $term->name;
          }
      }

      you can just do

      $rlv_author_present[ $hit->post_author ] = get_the_author_meta( 'display_name', $hit->post_author );
  7. Is there a way to do this for attachment categories?

    I tried changing category to attachment_category but I knew it wouldn’t be that easy.

    1. Andrea, attachments don’t have categories in WP core, so the answer depends on how the categories have been added and what the name of the attachment category taxonomy is.

  8. Hi, Thanks for this amazing search option, I have tried this code with a custom taxonomy but I don’t get any results, I just see an empty select field with no options.
    is this code meant to work with custom taxonomies?

  9. Hi, Thanks for this amazing search option, I have tried this code with a custom taxonomy but I don’t get any results, I just see an empty select field with no options. is this code meant to work with custom taxonomies?
    I have replaced all ‘cats’ instances with my custom taxonomy name but I keep getting this result: ‘Warning: Invalid argument supplied for foreach() ‘ for this line:
    foreach ( $rlv_categories_present as $cat_id => $cat_name ) {

    When I var_dump the ‘$rlv_categories_present’ I receive NULL.
    is it possible that there is an issue with the global variable?

    1. Dave, can you post a link to a Gist or a Pastebin with your full code, both functions? If $rlv_categories_present is null, that’s definitely a problem, but since it’s something you have to define yourself, the problem is likely in your code. Show me the code, and I’ll take a look.

    1. Yes, and it’s simpler, too:

      add_filter( 'relevanssi_hits_filter', 'rlv_gather_post_types', 99 );
      function rlv_gather_post_types( $hits ) {
      global $rlv_post_types_present;
      $rlv_post_types_present = array();
      foreach ( $hits[0] as $hit ) {
      $rlv_post_types_present[] = $hit->post_type;
      }
      asort( $rlv_post_types_present );
      return $hits;
      }

  10. Hello, first I must say very good work, I love it. Thank you very much for it.
    Second, if you can help me with the category, it shows from my website only the second category, is it possible to show the complete category?

    1. Abdullah, what exactly do you mean? Show where? In the search results pages? Whatever shows up on your search results pages is up to your theme, so if you have access to theme support, asking there might be more helpful.

  11. Hello! First of all, thank you so much for this wonderful plugin.

    With the default taxonomy ( $hit->ID, ‘category’ ), the filter works perfectly. I search for a term, I hit a checkbox and the results narrow down appropriately.

    With a custom taxonomy ( $hit->ID, ‘book-category’ ), the results do not filter down when a checkbox is selected. Regardless of what category I select, the same complete set of results still appear. Besides the ID of the taxonomy, the code is entirely the same. The custom taxonomy is set up using the ACF plugin.

    Any ideas about what could be going wrong?

    Thank you very much!

Leave a Reply to Jason Cancel reply

Are you a Relevanssi Premium customer looking for support? Please use the Premium support form.

Your email address will not be published. Required fields are marked *