Posted on

Custom weighing with relevanssi_match hook

If you want to fine-tune the post weights, version 1.4.5 introduces a new hook for that. The relevanssi_match hook lets you modify the matches found for queries. It passes a match object, which has the post id ($match->doc), number of hits found in different parts of the post and the weight assigned to the post ($match->weight).

From version 1.7.4 onwards, there’s another parameter included. That parameter contains the IDF value used for calculating the weight.

Here’s how the weight is calculated:

$match->tf =
    $match->title * $title_boost +
    $match->content +
    $match->comment * $comment_boost +
    $match->tag * $tag_boost +
    $match->link * $link_boost +
    $match->author +
    $match->category +
    $match->excerpt +
    $match->taxonomy +
    $match->customfield;
 
$match->weight = $match->tf * $idf;

Where $idf is the inverse document frequency, aka the rarity of the term in question. The bigger the weight, the higher the document will appear on the search results list. Weights are calculated for each term in the search query and multiplied to get the final weight for the document.

So, now, if you want to say adjust the weights by freshness, you could fetch the post date based on post_id and multiply the $match->weight with some boost factor if the publication date falls within some range (say within the last week) and with another boost factor if it falls outside that range. Want to boost the weight if a certain custom field is present? Easy!

This filter will give you lots of tools to adjust and fine-tune the weights given to documents.

Date-based weight

Here’s an example function that will give double weight to posts published within one week and half weight to posts older than that.

add_filter( 'relevanssi_match', 'date_weights' );
function date_weights( $match ) {
	$post_date = strtotime( get_the_time( 'Y-m-d', $match->doc ) );
	if ( time() - $post_date < 60 * 60 * 24 * 7 ) {
 		$match->weight = $match->weight * 2;
	} else {
		$match->weight = $match->weight / 2;
	}
	return $match;
}

Custom field -based weight

This function will double the weight of posts with the custom field “featured” set to 1.

add_filter( 'relevanssi_match', 'custom_field_weights' );
function custom_field_weights( $match ) {
	$featured = get_post_meta( $match->doc, 'featured', true );
	if ( '1' === $featured ) {
 		$match->weight = $match->weight * 2;
	} else {
		$match->weight = $match->weight / 2;
	}
	return $match;
}

With Relevanssi Premium, you also have the customfield_detail field, which allows you to filter results by custom field. If you want a search that only targets one specific custom field, you can do a filter like this, where field_name is the name of the field.

add_filter( 'relevanssi_match', 'custom_field_weights' );
function custom_field_weights( $match ) {
	$custom_field_detail = json_decode( $match->customfield_detail );
	if ( null === $custom_field_detail || ! isset( $custom_field_detail->field_name ) ) {
		$match->weight = 0;
	}
	return $match;
}

This will set the weight of the match to 0, if match is not made in a custom field value, or is made in a wrong custom field. For a positive boost for matches in a particular custom field:

add_filter( 'relevanssi_match', 'custom_field_weights' );
function custom_field_weights( $match ) {
	$custom_field_detail = json_decode( $match->customfield_detail );
	if ( null !== $custom_field_detail && isset( $custom_field_detail->field_name ) ) {
		$match->weight *= 2;
	}
	return $match;
}

Adding extra weight to users

Relevanssi Premium doesn’t have an easy setting to increase the weight of users in the results. This function will double the weight of user profiles:

add_filter( 'relevanssi_match', 'extra_user_weight' );
function extra_user_weight( $match ) {
	$post_type = relevanssi_get_post_type( $match->doc );
	if ( 'user' === $post_type ) {
		$match->weight = $match->weight * 2;
	}
	return $match;
}

Weight from taxonomy terms

Here’s how you can add extra weight to posts that have a particular taxonomy term. Just add this function to the functions.php and replace term with the slug of the term and taxonomy with the slug of the taxonomy (for example category or post_tag).

add_filter( 'relevanssi_match', 'rlv_boost_one_term' );
function rlv_boost_one_term( $match ) {
	if ( has_term( 'term', 'taxonomy', $match->doc ) ) {
		$match->weight = $match->weight * 5;
	}
	return $match;
}

has_term() also works without the first parameter, in which case it will boost the weight if any term from the taxonomy is present (even if the term doesn’t match the search query).

add_filter( 'relevanssi_match', 'rlv_boost_all_terms' );
function rlv_boost_all_terms( $match ) {
	if ( has_term( '', 'taxonomy', $match->doc ) ) {
		$match->weight = $match->weight * 5;
	}
	return $match;
}

Weight from tags

Relevanssi Premium has a simple weight setting for adding weight to tags. If you want that in free Relevanssi, you have to use relevanssi_match to add extra weight to tags.

add_filter( 'relevanssi_match', 'rlv_tag_boost' );
function rlv_boost_tags( $match ) {
	if ( $match->tag > 0 ) {
		$match->weight = $match->weight * 5 * $match->tag;
	}
	return $match;
}

Weight from excerpts

To add more weight to hits from excerpts, you can do something like this:

add_filter( 'relevanssi_match', 'rlv_boost_excerpts' );
function rlv_boost_excerpts( $match ) {
	if ( $match->excerpt > 0 ) {
		$match->weight = $match->weight * 2;
	}
	return $match;
}

This will make any hits that match the excerpt more significant.

Weighting multisite subsites

If you want to give extra weight to results from particular subsite in multisite searches, you can do this:

add_filter( 'relevanssi_match', 'rlv_boost_subsite' );
function rlv_boost_subsite( $match ) {
	if ( 3 === get_current_blog_id() ) {
		$match->weigh = $match->weight * 10;
	}
	return $match;
}

This will give a boost to all results from the blog 3.

60 comments Custom weighing with relevanssi_match hook

  1. Very interesting feature. I have to take a look on this and try to make it works (I suppose the function is to be put in functions.php).

  2. Would you be so nice and post a simple help how to boost search results by custom_field for exampele custom_field called “featured”. I have some posts with customfield “featured” value “1” and other with no customfield. I’ve bought your premium edition of relevanssi but I don’t know how to make it work. THANKS !!!

  3. Hi Mikko,
    I want to use relevanssi with wp-ecommerce. I know that wp-ecommerce uses custom post types and tags. Relevanssi ‘out of the box’ does not (obviously) work with wp-commerce. I was wondering if there is something or somewhere in the code that could be changed so relevanssi can use wp-ecommerce product tags and titles.

  4. Can you add a multiplier to something like the custom field? aka:
    *****
    add_filter(‘relevanssi_match’, ‘add_weight_to_cf’);
    function add_weight_to_cf($match) {
    $match->customfield = $match->customfield * 2;
    return $match;
    }
    *****

    I’m not sure if the $match->weight is calculated dynamically or not. If this isn’t possible, it is possible to add a weight to a custom field with this hook or another?

    1. $match->weight is calculated before the filter, so you’d need to recalculate it, if you want to something like that. However, now that I look at it, that’s going to be tricky, because calculating the weight (tf * idf) needs the idf component as well, and that’s a bit tricky to get. I’ll add this to my to-do list for the next version, to have the idf passed to the filter as well.

      Actually, you can do it like this. $match->weight is tf * idf. Well, tf is

      $match->tf =
      $match->title * $title_boost +
      $match->content +
      $match->comment * $comment_boost +
      $match->link * $link_boost +
      $match->author +
      $match->excerpt +
      $match->taxonomy_score +
      $match->customfield +
      $match->mysqlcolumn;

      Calculate that, then you can divide the weight with that to get the idf. Then just calculate a new tf with the modifiers you want and multiply it with the idf to get new value for $match->weight.

      But yeah, I’ll make this easier in the next version. There are reasons why the weight is calculated before the filter (some filters may want to adjust the weight directly).

      1. Mikko, Got it. That solution will work great for now.

        As an aside, it would be nice to be able to also weight each custom field separately. But, I am probably one of the few who really wants to do that.

        Thanks again.

        1. That information is stored โ€” the Relevanssi MySQL database has a column “customfield_detail”, which tells which custom fields have the hit. You’ll have to dig that up yourself, but the information is there and you can use it.

        2. I would love to be able to set individual weights on my custom fields too. If you add that, I’ll be a premium customer for sure!

          1. That is possible in Premium. There’s no ready option for it, so you have to create a filter function that adjusts the weights, but I can write a knowledge base article about how to do that. It’s not particularly complicated.

          2. I grabbed the sample code from the knowledge base and adapted it a little. I’m wondering if there’s a better way to to what I’ve done below. Basically custom fields are almost exclusively what’s important to me in my posts, so I want them to have all have a higher weight.

            add_filter(‘relevanssi_match’, ‘custom_field_weights’);

            function custom_field_weights($match) {

            $fields=array(‘Cust Field 1′,’ Cust Field 1 ,’Cust Field 1′,’Cust Field 1′,’Cust Field 1′,’Cust Field 1′);

            foreach ($fields as $field) {
            $custom_field = get_post_meta($match->doc, $field, true);
            if (‘1’ == $custom_field) {
            $match->weight = $match->weight * 2;
            }
            else {
            $match->weight = $match->weight / 2;
            }
            }
            return $match;
            }

            Is there a better way to do what I’m doing above? I’ve been reviewing the Premium features. I probably will go ahead either way and get premium.

          3. That’s how you do it. Relevanssi Premium provides you with an array of custom fields that match the search query, but if you don’t care about that and only care about whether the custom field is present or has some fixed value, then you won’t need that.

          4. I don’t need to worry about whether or not the custom field exists, I know they exist. I’m looking for posts that have matches for the search terms in the custom fields.

            You could take a look at my site http://www.aviationphotographic.com users might search for something like “qantas boeing 747 lax”. When you do that search, 1 result is returned. However I know there’s a couple more than that. I’m not sure why the others don’t show up.

            My results are pretty mixed. Something they come back really relevant, and other times not so much. I use “AND – require all items” as my default operator for search, and I disabled “OR fallback”. I do allow fuzzy results if there’s no hits.

          5. Ok, in that case you can do it with some modifications to the code you posted, or easier with Relevanssi Premium.

            As for why you’re not fing all Qantas Boeing 747 LAX photos, are you using the limit searches feature? If you are, try disabling that feature and see if that helps. I made some searches, and you should definitely find at least two photos with that search phrase.

          6. I disabled the “limit searches” feature, and it did improve the results (i did notice that the option kept re-enabling it’s self until I did a full re-index). It got a bit slower too, but I expected that. In terms of performance, is there an easy way to integrate relevanssi to use memcache? That would be an awesome option, and I think it would improve performance a fair amount.

          7. Hmm, I’ll have to check the option. It shouldn’t re-enable itself.

            Re: memcache, I have to admit I have no idea what that would require and if it is a good idea.

  5. I’ve got my version of your function working, but need a clarification.

    I have checkboxes representing my Custom Fields. The function reads my checkboxes from my Advanced Search Form, and eliminates posts containing the search term only in unchecked Custom Fields. So it appears to the user as if he’s searching only within his chosen Custom Fields. Something like that.

    The question is that I cannot get results which occur in the title to move to the bottom. I set the title weight to 4 in Admin Settings, but I think my function is making it 5 again. I’m not even certain that matches in the Title should move to the bottom of the results (thinking about it), but anyway here’s the code in case someone needs it:

    add_filter('relevanssi_match', 'cfdetail');
    function cfdetail($match) {
    	//html sample 
     
    	$checked_fields = $_GET['checkers'];  
     
    	$checkboxes = array(
    	0=&gt; "meta_results", 
    	1=&gt; "meta_inputs", 
    	2=&gt; "meta_purpose", 
    	3=&gt; "meta_special", 
    	4=&gt; "meta_references",  
    	);
     
    	$cf = unserialize($match-&gt;customfield_detail);
     
    	if ($cf &amp;&amp; $checked_fields) {
    		//do subtraction here, in case they unchecked all the boxes, prevent an error message.
    		$unchecked_fields = array_diff($checkboxes, $checked_fields);
     
      		foreach ($unchecked_fields as $u) {
    			if (!in_array( $u, array_keys($cf)) ) {
          			$match-&gt;weight = 5;
      	    		}
            		else {
            			$match-&gt;weight = 0;
            		}
    		}
    	// 'else' statement is not required because the function will return unfiltered results if they unchecked all boxes.
    	}
    	return $match;    
    }

    PS – If anyone else is as clueless as me, you may also want to know that you create the Advanced Search Form by adding comment to top of your own PHP, & then using it as “template” for a page design in the Admin. Or at least, that worked, I don’t know if it’s the best way.
    /**
    * Template Name: Advanced Search
    */

    1. You should probably adjust the weights to make the differences more pronounced. Difference between weight 5 and weight 4 is very small, much smaller than differences between the weights of the terms themselves. Make it 10 and 2, and you’ll see more of a difference.

  6. Here’s how I got Relevanssi Premium to assign different weights to results based on their categories, if it’s helpful for anyone else:

    Simply change the value of $category_id to the category you want to affect, then change the value of $match->weight to whatever you want the weighting to be (eg: ‘$match->weight * 5’ will multiply the weight of that category by five, ‘$match->weight /5’ will divide the weight by five, etc). Place this code within your functions.php and it should work…
    Thanks for the help Mikko!

    add_filter(‘relevanssi_match’, ‘category_weight’);
    function category_weight($match) {
    $category = get_the_category($match->doc);
    $category_id = $category[0]->cat_ID;
    if ($category_id == ’22’){
    $match->weight = $match->weight / 10;
    }
    elseif ($category_id == ‘3’) {
    $match->weight = $match->weight * 1;
    }
    elseif ($category_id == ‘4’) {
    $match->weight = $match->weight * 4;
    }
    else {
    }
    return $match;
    }

    1. Yes, but it requires some programming. Filter ‘relevanssi_match’ lets you modify weights. The $match->customfield_detail shows where the custom field hit was made. See the example code in Dan’s comment.

  7. Ahh – additional info: You probably need to know that that field is also on a custom post type called products ๐Ÿ˜‰

  8. Hi Mikko,

    I would like to use this function to make pages with a specific -tag- appear at the top of the results. I’m trying the following:

    add_filter(‘relevanssi_match’, ‘custom_tag_weights’);
    function custom_tag_weights($match) {
    $global_option = has_tag(‘global_option’, $match->doc);
    if ( $global_option ) {
    $match->weight = $match->weight * 1000;
    }
    else {
    $match->weight = $match->weight / 2;
    }
    return $match;
    }

    …this -seems- to work, but it never brings the results to the -top-. Page Title always seems to be weighted higher. In my Indexing Options, I have Title weighted as ’10’. How can I adjust this?

  9. I want to change the taxonomy weight. In backend I can change the weights for post_tags and categories but not for taxonomies.

    I tried it that way without success:

    add_filter(‘relevanssi_match’, ‘custom_tax_weights’);

    function custom_tax_weights($match) {
    $match->taxonomy = $match->taxonomy * 50;
    return $match;
    }

    1. Sebastian, that’s not doing anything. Relevanssi sorts by $match->weight, and unless you change that, you’re not going see any results. Take a look at lib/search.php and see how Relevanssi calculates $match->tf. Copy that to your function, with the added weight to $match->taxonomy_score, recalculate $match->weight from $match->tf and $idf ($idf comes as a parameter for relevanssi_match filter).

  10. Cool feature, thanks Mikko. I’m wondering, though, if it’s possible to weigh results based on a post type of “user”? We’re including users along with a few usermeta fields in our index, and we’d like users to come back with a much higher priority. (We’re only displaying users that are “members”, through custom code on the search results side.)

    I do not see post type “user” in the Weights section of the Relevanssi Premium settings, and I’m not sure of the code above would work with user post types.

      1. Thanks Mikko. I am just not understanding where I would specify a post type in the code here. There are the date-based and custom field-based weights, but is there a “post type” weight hook? A code example would be awesome if you have one. Thank you!

  11. When I built my site, I use pages to house products. Is there a way to set a custom weight per page? For EG: I want to search for a product name, but I have 5 other pages with the same keyword that are sub-pages, and my sub-pages are showing up before my parent page. Is there a way I can set a weight for parent pages?

    1. With Relevanssi Premium, an easy solution would be to pin the parent page for the keyword. That would guarantee that when someone searches for that keyword, the parent page comes up first.

      Other solutions involve writing functions that either adjust the weights using the relevanssi_match filter hook or manipulate the order of the results using the relevanssi_hits_filter filter hook.

    1. Yes, it’s described on this very page. Create a relevanssi_match filter that adjusts the weight based on the data. If you can get the popularity based on post ID and can tell how popular a post is compared to other posts, it’s possible to create a filter that boosts the weight of popular posts. Not a bad idea!

  12. Awesome plugin! I see a comment from several years ago that this is a Premium feature only- is that still true? Or am I just doing this wrong? Can’t seem to get priority on the search term being in page/post/CPT titles.

    Thanks!

    1. No, that was five years ago. It’s also available in free version. But you shouldn’t need any code – just up the title weight to, say, 1000 in Relevanssi settings, and you should see the titles given much higher priority.

  13. Just installed Relevanssi and built the index. Is it possible (as it says in the description of the plugin) to increase the search score for a post where the search term(s) matches the post’s tags ?

    1. PeO, in Relevanssi Premium there’s a simple setting for that. In the free version, follow the instructions on this page under “Weight from taxonomy terms”, as tags are taxonomy terms in the taxonomy “post_tag”.

      1. Added that code, and increased multiplier to 5000 to be sure it has some effect.. did no difference. Is there some search result caching in the tables added by Relevanssi ? Is there any way to clear that cache (this might been in another plugin, which stated there is a cache that is used an hour from the last search with the same term).

        Is it possible to add some index to the Relevanssi-tables to speed up the search ? It’s night over in US where the site I’m working with is located, still the searches take several seconds. Might annoy users.

      2. Might have found a bug or feature.. When I do a search, while not logged in (as an ordinary user), I get the two posts matching the term at the top. Logging in with my user at the site (not admin), I get another result (51 matches instead of only 8 not logged in), and those matching the tags are no longer the two first results.

      3. Found the reason for different number of hits. We are using s2Member, so my user has access to all the posts, while the anonymous one has not.
        We want to include search hits to the protected articles in the result.
        The scoring problem is still relevant, when I get the full search result of 51 posts, those with the tags are located at the third and fifth page.

        1. In order to disable the s2member filter, you need to add a new filter that overrides the default filter.

          add_filter('relevanssi_post_ok', 'rlv_disable_s2member', 11, 2);
          function rlv_disable_s2member($ok) {
          	$status = relevanssi_get_post_status($doc);
          	if ('publish' != $status) {
          		$ok = false;
          	}
          	// only show drafts, pending and future posts in admin search
          	if (in_array($status, apply_filters('relevanssi_valid_admin_status', array('draft', 'pending', 'future'))) && is_admin()) {
          		$ok = true;
          	}
          	return $ok;
          }

          That should show you all posts.

          Relevanssi doesn’t have any caching, so that’s not the issue. If you enable the search results breakdowns, does adjusting the value increase the relevancy score? If not, then there’s a mistake in the code. You have the correct taxonomy there?

          Relevanssi already has the indices it needs. How big is your site, how many posts approximately in the index? Make sure “Throttle searches” is enabled on the Searching tab in the Relevanssi settings. That should speed up the search if it’s not enabled. Also, make sure your excerpts are based on words and not characters. Character-based excerpts can be really slow.

  14. Adding that exact code broke the search, just giving a default “post” with the title “Untitled” and a “read more” button on the result page (how do I fix this, before Relevanssi, I had a page telling the user that no results was found, and presented a new search form ?) when not logged in.

    The code for changing weight for posts with matching tags does work, but 5000 seems like a too small value to compensate for the other articles that matches when logged in. For some reason these get the a score above 4 million when the code is active:

    add_filter('relevanssi_match', 'rlv_boost_all_terms');
    function rlv_boost_all_terms($match) {
    if (has_term("", "post_tag", $match->doc)) $match->weight = $match->weight * 5000;
    return $match;
    }

    The two first hits are scored about 1000 when I have commented out the filter code for increasing weight, and those posts aren’t even tagged with the word I search for (no tags at all in these, which could be the problem)
    The index contains more or less statically around 1200 posts.

  15. So what exactly is the result you want to get when a non-member searches for a protected post?

    The rlv_boost_all_terms() function currently gives the boost for all posts that have any tags. Perhaps that’s not what you wanted in the first place?

    add_filter('relevanssi_match', 'rlv_boost_term_matches');
    function rlv_boost_term_matches($match) {
        if ($match->taxonomy > 0) $match->weight = $match->weight * 100;
        return $match;
    }

    This will boost posts where the search term matches any taxonomy (except category). There’s no easy way in the free Relevanssi to know which taxonomy matches the search; that’s a Premium feature.

  16. I want the same result for non-members as for members, the non-members will then be able to see a short excerpt (as generated by Relevanssi where the term was found), but not the full article.

    Now that suggested code (for disabling s2Member) breaks the search and gives no results at all (instead of the 8 posts that are open), not even logged in as site admin (which indicates broken code).

    ‘rlv_boost_term_matches’ seems to do the correct increasing of the score for now. I’ve also added in ‘rlv_boost_all_terms’, so that posts with any tag will count as a better match (just *2).

  17. I created this snippet to add weight to author names in search results. Make sure you check the box to index author display names!

    Searching for Mikko is more likely to show posts by Mikko, than posts who mention Mikko in the content. ๐Ÿ™‚

    function aa_relevanssi_weight_authors( $match ) {
    	$post = get_post( $match->doc );
    	if ( empty($post->post_author) ) return $match;
     
    	$display_name = get_the_author_meta( 'display_name', $post->post_author );
    	$first_name = get_the_author_meta( 'first_name', $post->post_author );
    	$last_name = get_the_author_meta( 'last_name', $post->post_author );
     
    	// Improve relevance if the search matches a post author's display name, or first/last name.
    	if ( stripos($display_name, $match->term) !== false ) { $match->weight *= 2; }
    	else if ( stripos($first_name, $match->term) !== false ) { $match->weight *= 1.5; }
    	else if ( stripos($last_name, $match->term) !== false ) { $match->weight *= 1.5; }
     
    	return $match;
    }
    add_filter( 'relevanssi_match', 'aa_relevanssi_weight_authors' );
  18. I think I got it … you can clean out the comments and write a summary how to include results from protected s2Member pages and posts..

    Just a setting in s2Member:
    Under s2Member settings, “Restriction Options”:
    Alternate View Protection (feeds, search results, etc.)
    * Uncheck “Filter all WordPress queries”
    * Check everything but “Searches” (or as you wish)

    The filter ‘rlv_disable_s2member’ for ‘relevanssi_post_ok’ is not needed.

  19. Can I give a users with a certain role a higher weight than other users posts?
    I need it to still show in order of search term matches, but then if two users posts have the same amount of matches then it show the users with the role premium before the user with the role member.
    Is this possible?
    If not, is it possible to do same as above but with post types? So for example if I had post-types of: Premium-posts and basic-posts, Still going by search results match, but show the premium-posts before basic-posts when both posts have same amount of matches?

    Thanks – LOVE THIS PLUGIN BY THE WAY!

    1. Chantelle, sure. It’s pretty difficult to do it as a secondary sorting parameter, but giving a small extra boost (like 1.1 multiplier) is easy to do, and has pretty much the same effect.

      You can do this with the relevanssi_match filter: the post ID is in $match->doc, so you can use that to get the post author ($post = get_post($match->doc); $user = get_userdata($post->post_author);) and with the user object you can get the role and so on.

  20. Thanks for this! Was looking for the Date-based weight filter and customized it to fit my needs making the effect over time be more gentle.

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.