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.

8 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.

Leave a Reply

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