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;
}

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' );

Now that’s in place (put both of these functions to theme functions.php, or some other suitable place), 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 (strpos($url, 'page') !== false) {
            $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:

function rlv_category_dropdown() {
    global $rlv_categories_present, $wp_query;
 
    if (!empty($wp_query->query_vars['product_cat'])) {
        $url = esc_url(remove_query_arg('product_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 (strpos($url, 'page') !== false) {
            $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"+"&product_cat="+dropdown.options[dropdown.selectedIndex].value;
        }
    }
    dropdown.onchange = onCatChange;
-->
</script>
EOH;
 
        echo $select;
    }
}

Once both functions are included in your theme, you can just add a

<?php rlv_category_dropdown(); ?>

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

14 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;

Leave a Reply

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.