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;
}

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;
}
  • Li-An

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

    • Well, anywhere that gets executed is fine, but functions.php is the typical place.

  • scatter

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

    • Sure. I’ve added it as an example to the post.

  • Rich

    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.

    • If WP-eCommerce uses custom post types, just figure out the name of the post type and have Relevanssi index that post type.

  • 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?

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

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

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

          • I see. Make sense. Thanks for the help and tips.

        • kepstein01

          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!

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

          • kepstein01

            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.

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

          • kepstein01

            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.

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

          • kepstein01

            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.

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

  • Alex Ciarlillo

    Is this for Premium only? I cannot seem to get the filtet/hook to trigger.

  • Lore

    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
    */

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

  • Lore

    Dang, Mikko, I keep forgetting how to post code, can you fix it?
    Sorry!
    ๐Ÿ™‚

  • Dan

    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;
    }

  • Darren Douglas

    I have a custom field called prodcode – is there any way I can give this field extra weight?

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

  • Darren Douglas

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

  • 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?

    • Strange, a weight that big should push something to top. Anyway, if top rankings is what you want, then using relevanssi_hits_filter to directly modify the order of results is more effective. There’s an example of sorting by category in the user manual for relevanssi_hits_filter, modifying that to sort by tag should not be complicated.

  • Sebastian

    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;
    }

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

  • Laura Noir

    Hi, I’d like to have the post content weight be a lower priority, how do I code this?

    Cheers,
    Laura

    • You can’t, directly. You’ll just have to adjust everything else on a higher priority.

  • Erik Bertrand

    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.

    • Ah, indeed, users are missing in the settings. The code above does work, you can use it to adjust the weight for users. Users have a post type of “user”.

      • Erik Bertrand

        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!

        • I’ve added an example of increasing the weight of user profiles.

  • Neil Thomas

    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?

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

  • Scott Surber

    Is there a way to increase the weight by popularity or amount of views of the page/post/document.

    • 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!