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;
}
  • http://www.marcefx.com Marce

    Hi,

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

    Thanks!

  • http://www.mikkosaari.fi/ Mikko Saari

    Yes, this works both in free and Premium.

    • http://www.marcefx.com Marce

      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!

      • http://www.mikkosaari.fi/ Mikko Saari

        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

  • http://www.marcefx.com Marce

    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

    • http://www.marcefx.com Marce

      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;

    }

    • http://www.mikkosaari.fi/ Mikko Saari

      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

  • http://www.facebook.com/giuliano.meneghello.5 Giuliano Meneghello

    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

    • http://www.mikkosaari.fi/ Mikko Saari

      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.

        • http://www.mikkosaari.fi/ Mikko Saari

          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.

          • http://www.mikkosaari.fi/ Mikko Saari

            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

    • http://www.mikkosaari.fi/ Mikko Saari

      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!

        • http://www.mikkosaari.fi/ Mikko Saari

          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

    • http://www.mikkosaari.fi/ Mikko Saari

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

        • http://www.mikkosaari.fi/ Mikko Saari

          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. :-(

          • http://www.mikkosaari.fi/ Mikko Saari

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

          • http://www.mikkosaari.fi/ Mikko Saari

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

          • http://www.mikkosaari.fi/ Mikko Saari

            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?

    • http://www.mikkosaari.fi/ Mikko Saari

      You can add this before the array_push():

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

      • jefffassnacht

        Thank you!

  • http://www.trekkingpartners.com/ TrekkingPartners

    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