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.

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