Sort search results with relevanssi_hits_filter

Here’s how you can sort your search results with relevanssi_hits_filter. It’s simple and a good way to give more attention to particular type of content. There are lots of possible ways to do this, I’m going to give some examples and hopefully you’ll be able to figure out how to do what it is you want to do.

Sorting by category

Here’s an actual example from one of my sites. It’s a board game site, with reviews, news, articles and so on. I wanted to make the reviews stand up. One way to do that would be to give extra weight to posts in that particular category, but since I can’t do that on the settings page, I’ll just go ahead and make sure the review posts float on top of the searches no matter what. Here’s what I added to the functions.php of my theme:

add_filter('relevanssi_hits_filter', 'reviews_first');
function reviews_first($hits) {
	$reviews = array();
	$everything_else = array();
	foreach ($hits[0] as $hit) {
		$review = false;
		foreach (get_the_category($hit->ID) as $cat) {
			if ($cat->cat_ID == 3) {
				$review = true;
				break;
			}
		}
		$review ? array_push($reviews, $hit) : array_push($everything_else, $hit);
	}
 
	$hits[0] = array_merge($reviews, $everything_else);
	return $hits;
}

First, create arrays to which the posts are sorted. Then, process the hits. For each post, go through all the categories for the post (I could do a shortcut here, as all my posts are in exactly one category, but it’s better to be thorough) and if the post is in the review category (which is category 3, and I really should use an obvious constant like REVIEW_CATEGORY here), it is marked as a review by setting the $review flag true.

Then, depending on the flag (which I did remember to set to false for each post to start with) the post goes either to $reviews or $everything_else. This kind of sorting automatically retains the original order of the posts, so the relevancy order isn’t badly messed up.

Then we just merge the arrays (in the correct order, mind you) and return the complete array. Done. When doing the array_merge(), make sure all arrays are actually arrays (so initialize them first). If there’s a NULL (for example when doing arrays by post_type, and one post_type is missing), the whole array_merge() will fail.

However, I want to really make the reviews stand out and while my search results template does show the category of the posts right after the post title, I want to add some headings. So, then I’ll take a look at my search results template.

$reviews = false;
if (have_posts()) : while (have_posts()) : the_post();
	foreach (get_the_category($post->ID) as $cat) {
		if (($post->post_type == 'page' || $cat->cat_ID != 3) && $reviews) {
			echo "<h3>Everything else</h3>";
			$reviews = false;
			break;
		}
		if ($cat->cat_ID == 3 && !$reviews) {
			echo "<h3>Reviews</h3>";
			$reviews = true;
			break;
		}
	}

Before the change, this part only had the usual have_posts() loop code. Now, I’ve added some new stuff. I start by setting a review flag to false.

If a post is in the category 3 (again, I should use the REVIEW_CATEGORY constant here, so if the review category somehow changes, I can change it in one place and spare myself lots of trouble) and the review flag is off, the review header is shown and the review flag is set to true.

If the review flag is set and we come across a post that is not in the category or a page (if the first result after the reviews is a page, it’ll still show the review category), then set the review flag to false and show the other header.

Sorting by post type

WP e-Commerce users like to get products first, then posts or pages. Here’s an example that sorts products first, then posts, then pages.

add_filter('relevanssi_hits_filter', 'products_first');
function products_first($hits) {
    $types = array();
 
    $types['page'] = array();
    $types['post'] = array();
    $types['product'] = array();
 
    // Split the post types in array $types
    if (!empty($hits)) {
        foreach ($hits[0] as $hit) {
            array_push($types[$hit->post_type], $hit);
        }
    }
 
    // Merge back to $hits in the desired order
    $hits[0] = array_merge($types['product'], $types['post'], $types['page']);
    return $hits;
}

Sorting by meta field or comment count

This function, provided by Max Hodges, will sort the results by meta field value (hidden meta field _likes) or by WordPress comment count, based on orderby query variable. This is a very good example of a more complicated sorting function.

add_filter('relevanssi_hits_filter', 'order_the_results');
function order_the_results($hits) {
    global $wp_query;
 
	switch ($wp_query->query_vars['orderby']) {
		case 'likes':
	        $likes = array();
    		foreach ($hits[0] as $hit) {
        		$likecount = get_post_meta($hit->ID, '_likes', true);
	        	if (!isset($likes[$likecount])) $likes[$likecount] = array();
    	    			array_push($likes[$likecount], $hit);
        		}
 
			if ($wp_query->query_vars['order'] == 'asc') {
				ksort($likes);
			} else {
				krsort($likes);
			}
 
	      		$sorted_hits = array();
			foreach ($likes as $likecount => $year_hits) {
   	     		$sorted_hits = array_merge($sorted_hits, $year_hits);
	        }
		$hits[0] = $sorted_hits;
		break;
 
	case 'comments':
		$comments = array();
		foreach ($hits[0] as $hit) {
			$commentcount = wp_count_comments($hit->ID)->total_comments;
			if (!isset($comments[$commentcount])) $comments[$commentcount] = array();
			array_push($comments[$commentcount], $hit);
		}
 
		if ($wp_query->query_vars['order'] == 'asc') {
			ksort($comments);
		} else {
			krsort($comments);
		}
 
		$sorted_hits = array();
		foreach ($comments as $commentcount => $year_hits) {
			$sorted_hits = array_merge($sorted_hits, $year_hits);
		}
		$hits[0] = $sorted_hits;
 
		break;
 
	case 'relevance':
		//do nothing
		break;
 
	 }
    return $hits;
}

Sorting by post title

Sorting by post title is really simple and doesn’t require using relevanssi_hits_filter:

add_filter('relevanssi_modify_wp_query', 'rlv_sort_by_title');
function rlv_sort_by_title($q) {
	$q->set('orderby', 'post_title');
	$q->set('order', 'asc');
	return $q;
}
  • Hi,

    Does the “Sorting by category” example works with the free version of the plugin?

    Thanks!

  • Yes, this works both in free and Premium.

    • Thanks, Mikko. I guess I did something wrong, since it’s not working. The only value I need to change is the category ID, right? I’ll try to figure it out.

      Cheers!

      • Yes. The code has some problems, if there are pages involved, by the way, but otherwise it should work as posted. Does it do anything?

  • Pingback: Anonymous()

  • Not really. I can’t see anything happening. I tested it with the same keywords, but I got the same results, no matter what category I picked. I’m going to double check, just in case. I’ll let you know.

    Thank you

    • Btw, there’s no pages involved. My settings tells Relevanssi to index posts only 🙂

  • Rok

    Hello, i have a few more categories, and would like to make categories list in certain order. I tried with tweaking the code, it puts the four categories on top, but it mixes them up. I would like to display all posts from first category first, second category second and so on. Can somebody please help me tweak this code. Thank you.

    add_filter(‘relevanssi_hits_filter’, ‘reviews_first’);

    function reviews_first($hits) {

    $reviews = array();

    $everything_else = array();

    foreach ($hits[0] as $hit) {

    $review = false;

    foreach (get_the_category($hit->ID) as $cat) {

    if ($cat->cat_ID == 1811) {

    $review = true;

    break;

    }

    if ($cat->cat_ID == 1812) {

    $review = true;

    break;

    }

    if ($cat->cat_ID == 1813) {

    $review = true;

    break;

    }

    if ($cat->cat_ID == 1814) {

    $review = true;

    break;

    }

    }

    $review ? array_push($reviews, $hit) : array_push($everything_else, $hit);

    }

    $hits[0] = array_merge($reviews, $everything_else);

    return $hits;

    }

    • You need to put posts from each category to a separate array, then combine them in correct order. So, put the posts from the first category to array $first, second to array $second and so on, then combine them: array_merge($first, $second, $third, $fourth, $everything_else);

      • Rok

        Got it, it’s working now. Thank you 1000x

  • Hi there, i place the Max Hodges code in my function.php page but order don’t works.
    How can i order by custom field (tipologia)?
    Thanks

    • The code by Max Hodges does work, if you just changes “_likes” to “tipologia”, but it only activates with the correct value of orderby parameter. In the sample code, to order the results by the custom field, orderby needs to be set to “likes”.

      If you want the results always ordered by custom field, remove the whole switch {} structure and just keep the case “likes” part.

      • Poe

        Hi Mikko Saari, I replace the “_likes” with my custom field but the result is not change. As per your advise we need to set orderby need to be set to “likes”. Could you please advise where is the setting for this “orderby ” to be “like”. Thanks in advance.

        • Add this to your search form:

          • Poe

            Thanks for your replay. I had add

            to the search form.
            But the result is still the same. Could you please advise if i miss some more thing to add.

          • By the way – as a paying customer, you can use the customer support form. That provides faster, more reliable answers.

            Do you always want to order by the custom field? In that case you don’t even need to hidden input field: just use the part of the code between “case: ‘likes'” and “break”.

            Does that help?

          • Poe

            Thanks for your great support and plugin. It work. 🙂

  • Nicole

    Hi Mikko,

    Your plugin is awesome – thank you very much for your help! I’ve been trying to adapt the code you gave above to work with woocommerce, to de-emphasise sold items. (I’d like them to still appear in the search results, but behind items currently for sale.) And I’m struggling – I thought it might be time to admit defeat and see if you had a second to look over my code and see what I’ve done wrong!

    I’ve currently got the below in my functions.php:

    add_filter(‘relevanssi_hits_filter’, ‘sold_last’);
    function sold_last($hits) {
    $solds = array();
    $everything_else = array();
    foreach ($hits[0] as $hit) {
    $sold = false;
    foreach (get_the_terms($hit->ID, ‘product_cat’) as $term) {
    if ($term->product_cat_ID == 807) {
    $sold = true;
    break;
    }
    }
    $sold ? array_push($solds, $hit) : array_push($everything_else, $hit);
    }

    $hits[0] = array_merge($everything_else, $solds);
    return $hits;
    }

    But it’s not affecting the order of the results. I know I’m doing something wrong, but I don’t know enough about this to be able to see what it is…

    Thank you very much for any help you can offer!

    Nicole

    • Nicole, this looks correct, except for this: if ($term->product_cat_ID == 807)

      Are you sure that’s the correct attribute?

      I’d debug this in these steps:

      1. Check the code is running and processing posts.
      2. Check that the code is noticing the sold posts.
      3. Check that the individual arrays contain what they should.

      • Nicole

        Thanks for getting back to me so quickly Mikko!

        No, I’m not at all sure that’s the right attribute – I’ve tried a couple of different variations, but realised eventually that I could quite easily try a million options and later find it was something else that was broken.

        I guessed based on your use of cat_ID in the original, and thought product_cat_ID would be the equivalent. (807 is definitely the category ID.) But if that’s not the right thing, at least I now know where the problem is and can try to find the right answer!

        As for your steps, the search returns results in line with the plugin settings, so I assume step 1 is working. It isn’t filtering out the sold items, so I suspect the problem is at step 2 – which would fit with me having used the wrong attribute to identify them. I shall work on it some more…

        Thanks again for your help!

        • The attribute is actually probably cat_ID, but it’s easy to check: just do a var_dump($term); and you’ll see all the attributes.

          • Nicole

            Fixed! Apparently I needed to use term_taxonomy_id instead of product_cat_id – it now works perfectly! Thank you so much for your help, I was pulling my hair out trying to make this work like I knew it should…

  • Nicole

    Hi Mikko,

    I’m really sorry to bother you again – but is there anything obvious you can think of that would mean that some search terms weren’t being recognised by relevanssi? (Or maybe aren’t being processed by relevanssi?)

    I’ll explain using an example, because I don’t know the right words to explain in abstract! My site sells jewellery – if I search, say “copper” or “chainmaille”, I get all the right results, in the order I wanted (with blog posts behind product listings) thanks to your helpful filter. Including necklace listings. But if I search “necklace” it comes up with no results, even if the listings that should have come up were picked up by another search term. So the product posts are clearly being indexed, just not found via all the tags, only some of them – any idea why/how I could fix it?

    If you need more info from me to make sensible suggestions, just shout. I don’t know if the above is at all enough to go on!

    Thanks again for all your help with this,

    Nicole

    • Is ‘necklace’ a stop word, by any chance? that’s the most common explanation for this kind of behaviour.

      • Nicole

        Sadly not, according to the settings page – and trial and error suggests there’s a few terms that aren’t being registered, even though they’re used as product tags (which are supposed to be indexed – and seem to be picked up on other searches).

        • Hard to say, then. I’d check the database to see if the word appears there. That would tell whether the problem is in indexing or in searching.

          • Nicole

            The words I’m searching for all seem to be in the database – it’s driving me
            mad, because I love everything about this plugin, and if I
            can’t sort this out, I won’t be able to use it. 🙁

          • Well, if you had a Premium license, I could take a closer look at this.

          • Nicole

            Fair point, well made – I will buy one when money’s a tiny bit less tight, and then come back to you. I really do appreciate all your help so far, and I wasn’t expecting you to work in depth on something for free! (I was sort of hoping it was something obviously stupid that I’ve done that’s easily fixed; looks like it’s not.)

          • It still might be something simple, but I’d have to investigate before I could say for sure. The missing words are appearing in post content, right, and not just meta data like tags and categories?

          • Nicole

            Yep, in post content and title, as well as tags/categories.

            Sticking with the example of necklace, it’s picked up (and highlighted in the search results) with “chainmaille necklace”, and a search for chainmaille on its own works, but not necklace on its own. (There are other words that should have results that don’t, but that’s the one that’s bugging me most at the moment, since it’s one of my main product categories.)

          • Ok. That sounds very much like a stopword issue, hence my initial question, but if it’s not (and if the words appear in Relevanssi db table, it’s not), it’s hard to say.

          • Nicole

            I shall buy a premium licence as soon as possible! Thanks again for thinking about this!

  • jefffassnacht

    Hi Mikko,

    Thanks for the great plugin! When using the snippet above to sort by post type, I’m getting an error on the search result page. The error refers to the line array_push($types[$hit->post_type], $hit); and I believe is happening when it encounters a result type that hasn’t been explicitly defined in the $types array, such as $types[‘product’] = array(); How would I create a catch-all for any type not defined?

    • You can add this before the array_push():

      if (!is_array($types[$hit->post_type])) $types[$hit->post_type] = array();

      • jefffassnacht

        Thank you!

  • Hi Mikko,

    I’ve wrangled the above code ‘sorting by meta field’ to order my search results by meta value ‘start_date’.

    I modified the code slightly – and it’s working – but I was hoping you could take a quick look for errors. https://gist.github.com/aburi/02101531bf828749fbd0

    Thanks for all your help.
    Alex

  • AlexanderBog

    Hi!
    Please show an example of how to do woocommerce sorting by price (ascending – descending). How to make that as a result of the search is running this plugin https://wordpress.org/plugins/yith-woocommerce-ajax-navigation/ or standard. In the standard search this plugin works.

    • Hi,

      I don’t know how to do that, as I don’t know how WooCommerce stores the price information. That AJAX Navigation plugin will almost certainly not work with Relevanssi.

  • I’m trying to alphabetize search results based on a meta field “alphabetize”. But my attempt to modify Max’s script doesn’t work, and PHP complains about the closing bracket. Can you help me correct? I’ve purchased Premium:

    add_filter(‘relevanssi_hits_filter’, ‘order_the_results’);

    function order_the_results($hits) {

    global $wp_query;

    $alphabetize = array();

    foreach ($hits[0] as $hit) {

    $alphacount = get_post_meta($hit->ID, ‘alphabetize’, true);

    if (!isset($alphabetize[$alphacount])) $alphabetize[$alphacount] = array();

    array_push($alphabetize[$alphacount], $hit);

    }

    if ($wp_query->query_vars[‘order’] == ‘asc’) {

    ksort($alphabetize);

    } else {

    krsort($alphabetize);

    }

    $sorted_hits = array();

    $hits[0] = $sorted_hits;

    }

    return $hits;

    }

    • You actually have too many closing brackets in this code. Remove the one before “return $hits” and it should work better.

      • Thanks, Mikko. But the script itself isn’t returning any results. Without the function, Relevanssi returns results just fine, but chronologically. I need them sorted by the value in an added field called “alphabetize.”

      • Ah. I left out a bit. It’s working now. But what does this mean?

        foreach ($alphabetize as $alphavalue => $year_hits) {
        $sorted_hits = array_merge($sorted_hits, $year_hits);
        }

        I don’t want to limit hits to just the current year or something.

        • It’s just a name of the variable. Name it $alpha_hits if it bothers you =)

          • I’m almost there, Mikko, just one last question, thanks! I’m trying to integrate your post_type sorting script, but it throws a PHP error if one of the types has no hits. I’m sure this is super basic, but how do I add an “if” to the merge, so if there are no posts, for example, it doesn’t hiccup?

            add_filter(‘relevanssi_hits_filter’, ‘order_the_results’);

            function order_the_results($hits) {

            global $wp_query;

            $alphabetize = array();

            if (!empty($hits)) {

            foreach ($hits[0] as $hit) {

            $alphavalue = get_post_meta($hit->ID, ‘alphabetize’, true);

            if (!isset($alphabetize[$alphavalue])) $alphabetize[$alphavalue] = array();

            array_push($alphabetize[$alphavalue], $hit);

            }

            // sortby ascending

            ksort($alphabetize);

            $sorted_hits = array();

            foreach ($alphabetize as $alphavalue => $alpha_hits) {

            $sorted_hits = array_merge($sorted_hits, $alpha_hits);

            }

            $hits[0] = $sorted_hits;

            foreach ($hits[0] as $hit) {

            if (!is_array($types[$hit->post_type])) $types[$hit->post_type] = array();

            array_push($types[$hit->post_type], $hit);

            }

            // Merge back to $hits in the desired order

            $hits[0] = array_merge($types[‘project’], $types[‘post’]);

            }

            return $hits;

            }

          • Merging empty arrays shouldn’t be a problem, so it should be enough if you add

            $types[‘project’] = array();
            $types[‘post’] = array();

            somewhere before the loop.

          • That did it! THANK YOU SO MUCH! Here’s the pastebin if anyone else needs something similar: http://pastebin.com/jmjWZik1

  • Barbara House

    I currently have a need to filter the results of the search to remove certain custom taxonomy terms based on user role permissions. I’m thinking this hits filter could be the answer – basically do a “if logged in user has this permission, exclude these tax terms, else etc.” Does that sound like the best way to do this?

    • I’d use ‘relevanssi_modify_wp_query’ to set a tax_query depending on user permissions. That way you don’t have to spend resources on fetching and processing posts the user cannot see.

  • J_avila

    Hi, I’m trying to sort the search results by custom tax. It’s an doctors listing but I want to positioning an specific doctor, with an tax called “nivel” (with 3 different levels), inside of a custom post type called “medicos” I want to order the search results (lvl1, lvl2, lvl3) can you help me is kind of urgent.

    • See the example “Sorting by category”. Instead of get_the_category(), you can use get_the_terms() to get custom taxonomy terms.

  • Very weird. When I use the post_type bit of code, it returns the correct results but pooches my theme layout. Very weird. I update my functions file and boom, layout pooched. I remove it and save it and it’s all good again. Put the code back in, save it and boom, it’s pooched (but returns the correct results).

    I find what I need to show Woo Products first in my search results and I can’t get it to not break my theme. I’ve looked under the hood and find nothing wrong.

    What happens is that my main content area gets centered and my sidebar gets layered on top of the right edge of the content area and it’s about 50% of it’s normal size.

    I certainly don’t expect a fix, I’m just recording an odd behaviour in case anyone else has experienced it.

    • Broken divs in the excerpts, perhaps?

      • hadn’t considered that. Thanks, I’ll look into it. Don’t typically use div’s in the excerpts but my partner may have done something I didn’t catch

  • Marcelo Lewin

    I would like to group my search results by category ID. Is there an easy way to do this?

    • If easy means “no programming involved”, then no. But the programming involved is not particularly tricky. Anyway, you do have a support request about this in the works, and I’m working on it; it’s just Christmas and this one man company is currently on Christmas vacation.

  • Leo Kolt

    What could be the problem? I put both codes in the file functions.php to sort the results by category, but when you insert the second year (), stops responding server showing error “500”…

    • 500 error means some kind of generic error. A typing mistake, likely.

      • Leo Kolt

        I need to insert these two codes entirely for each other? Not one instead of another and replacing one code is part of another code, right?

        • Yes, for the first part; the first one to theme functions.php, the second to the search results template. The second one will replace some existing code.

          • Leo Kolt

            Please help! Here is my search.php. I inserted the code in different places, but the search page gives an error 500. where to insert your code

          • Leo Kolt

          • Leo Kolt

            the code is not correctly inserted. here is a link to all the code

            http://pastebin.com/NRkPjN00

          • You need to edit the blog style template, not that template.

          • Leo Kolt

            Oh, no. It have such a file search.php. This code completely from search.php

          • Yes, but the code that actually displays the search results comes from the blog style template, not from the search.php template.

          • Leo Kolt

            So where must I paste your second code?

          • I don’t know the exact name of the template. Also, with your theme, you can’t just paste it, you need to edit the template. If you don’t understand how, I suggest you get help from someone who understands WP templates and can do the necessary changes for you.

  • Leo Kolt

    thanks for the concern. I found the file where to insert the code. But now I have another problem. I made the entries in the search for the two categories and the Everything else. But the posts from “Everything else”, unfortunately, are shown on the search page twice. There is an option to disable it? This link – http://pastebin.com/WCx5funX

    • Sounds like the problem is in the relevanssi_hits_filter filter.

      • Leo Kolt

        Is it possible to fix it?

        • Yes; I mean the problem is in your relevanssi_hits_filter function code.

          • Leo Kolt

            Can see what’s wrong with her? Thank you! http://pastebin.com/AjwHCStL

          • You’re merging $everything_else twice. You can do it all at once like this:

            $hits[0] = array_merge($reviews, $releases, $everything_else);

          • Leo Kolt

            This not working 🙁

          • Did you remove the second array_merge() line? How is it not working?

          • Leo Kolt

            Yes, removed. He mixes all the posts and the category after that, but leaves all the posts by two ((

          • Please show me the complete function again.

          • Leo Kolt
          • You had another hole in the logic, all posts were added to $everything_else. This should be correct: http://pastebin.com/CGgcV9iV

          • Leo Kolt

            Almost all the works! Thank you! Only now stopped working for a second code pattern . It does not show the title for releases (

            http://pastebin.com/h4j4vWsu

          • You have the three if blocks there, and they’re in wrong order. Move the first one last, then it should work.

          • Leo Kolt

            Oh thanks! Everything is working!!

  • Jim

    Looking for a way to sort the output simply by post title. Am a little confused at the moment on it

    • Add this to your theme functions.php:

      add_filter(‘relevanssi_modify_wp_query’, ‘rlv_sort_by_title’);
      function rlv_sort_by_title($q) {
      $q->set(‘orderby’, ‘post_title’);
      $q->set(‘order’, ‘asc’);
      return $q;
      }

      • Jim

        Thanks – that is brilliant – much appreciated
        I know this removes some of the relevance advantages of the search but this is for a large number of pdf’s and for this I only need the titles and a search on a custom field – thanks again

  • Julja

    firstly started to do sorting by myself, but just in time decided read more documentation about relevanssi!
    Thanks a lot for info!
    But, as you, probably , guess, i have some issue)
    Can you just look at my code and tell me is there something wrong?
    http://pastebin.com/tB5mwciF
    in html i try to sort with , where name&value – meta_key name

    php i know bad)
    Can you help me?

    • Nothing obviously wrong in your code.

      • Julja

        weird. Maybe i have wrong understanding how it should look in html?
        i have no select, it’s buttons

        1
        2
        3

        and the loop absolutely standard
        if (have_posts()) {while( have_posts()) :the_post();
        get_template_part(‘content-loop’, get_post_type());
        endwhile;}
        any suggestions?

        • Have you debugged this at all? What’s going on in the filter? Is it triggering? Where does it fail? Line-by-line debugging is often the best way to figure out the problem.

          • Julja

            Undefined index: orderby
            how is that possible?

          • Have you defined it? Does your search results page URL have a &orderby=value in it?

  • Linus

    I must be stupid? I can’t get a single filter to fire. I’m doing some stuff in the top of my search.php file in my theme, but where i do it should not matter?
    Copy/paste any of the examples and nothing happens.
    Example;
    add_filter(‘relevanssi_hits_filter’, ‘products_first’);
    function products_first($hits) { var_dump($hits); }
    Does completely nothing.
    function products_first($hits) { ?>console.log(‘Test’);<?php }
    same thing.
    Why?

    • Try theme functions.php, top of the search.php may be too late.