Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Sort Wordpress Search by Relevance

I've created a pretty advanced search for a Wordpress site I'm working on atm. Allowing visitors to filter results by different taxonomies, sort them by date or custom fields as well as the normal free text search (which WP offers by default).

I've accomplished this using the pre_get_posts filter and by simply adding my stuff to the query, something like this: (note that I've left out some sanity checks and other code)

<?php
add_filter('pre_get_posts', 'my_search');

function my_search ($qry) {
    # Include more post types
    $qry->set('post_type', array('foo', 'bar'));

    if ($_GET['myorder'] == 'price') {
        $qry->set('orderby', 'meta_value_num');
        $qry->set('meta_key', 'price');
        $qry->set('order', 'ASC');
    }
    else {
        $qry->set('orderby', 'date');
        $qry->set('order', 'DESC');
    }
}

Now I would like to add another way to sort the posts, namely by relevance. I understand this is a really common request and most solutions to the problem I've seen include using the Relevanssi plugin. Seeing as I've already written my own "plugin" (well, code at least) and have all my search forms and listings set up to use that, switching to Relevanssi won't be too easy at this point.

Sooo, I'd like to know if anyone knows of some (preferably) easy way to add this with the code I've already got?

As far as I understand, WP does its search using LIKE instead of MATCH() and that's why it doesn't even have a relevance score to sort on. If this is correct I assume I would have to write my own query altogether? How can I do this without messing up WP's pagination etc? Or could I add something like $qry->set('WHERE', "MATCH(post_content) AGAINST('$q' IN BOOLEAN MODE) AS relevance"); $qry->set('sortby', 'relevance') do you reckon?

like image 519
powerbuoy Avatar asked Jul 18 '13 00:07

powerbuoy


3 Answers

I think you can use Relevanssi without any problem but need to use a filter hook, check relevanssi_modify_wp_query for more details, check following code

function func_reli($qry){
    $qry->set( 'post_type', array( 'post', 'page', 'restaurant', 'products' ) );

    if ($_GET['myorder'] == 'price') {
        $qry->set('orderby', 'meta_value_num');
        $qry->set('meta_key', 'price');
        $qry->set('order', 'ASC');
    }
    else {
        $qry->set('orderby', 'date');
        $qry->set('order', 'DESC'); // <-- set (in your code 'add' is used)
    }
    return $qry; // <-- must return, in your code you didn't
}
add_filter('relevanssi_modify_wp_query', 'func_reli');

I've tested this code and just works fine. In this example I've used 'restaurant' and 'products' custom post types and search works really fine, just the relevant results. You can also use if php 5.3+ is installed

add_filter('relevanssi_modify_wp_query', function($qry){
    // Same code
});

Also, if you use Relevanssi plugin, you can use it's setup page to set post_type instead of using this ($qry->set( 'post_type', array(...) )) in your functions.php file.

Scree shot given below

enter image description here

like image 132
The Alpha Avatar answered Nov 07 '22 14:11

The Alpha


Ok so I ended up using Relevanssi after all. But with quite a few modifications.

First of all, filtering on taxonomies was quite easy as this is built into Relevanssi, all I needed to do was change the names of my <select> elements to the taxonomy names + change the values to the slugs:

<select name="custom_taxonomy">
    <option value="some-term">Some term</option>
    ...
</select>

Second, to allow sorting by a custom field, I had to use the relevanssi_hits_filter filter. Unlike pre_get_posts or relevanssi_modify_wp_query (which Sheikh Heera kindly suggested), this filter gets passed the array of posts and not a WP_Query object. When I tried to use the WP_Query object the order simply refused to change. Here's basically how to do your own sort with relevanssi_hits_filter:

<?php
add_filter('relevanssi_hits_filter', 'h5b_hits_filter');

function h5b_hits_filter ($hits) {
    global $wp_query;

    if (isset($wp_query->query_vars['orderby']) and $wp_query->query_vars['orderby'] == 'price') {
        if (count($hits[0])) {
            usort($hits[0], 'h5b_sort_by_price');
        }
    }

    return $hits;
}

function h5b_sort_by_price ($a, $b) {
    $priceKey   = 'price';
    $aPrice     = get_post_meta($a->ID, $priceKey, true);
    $bPrice     = get_post_meta($b->ID, $priceKey, true);
    $aPrice     = $aPrice ? $aPrice : 10000000;
    $bPrice     = $bPrice ? $bPrice : 10000000;

    if ($aPrice == $bPrice) {
        return 0;
    }

    return ($aPrice < $bPrice) ? -1 : 1;
}

This also helped with a previous problem I had in that posts that lacked the "price" key didn't show up in searches. Now they do, and the reason I give them a price of "10000000" is so that they show up after the ones that do have a price.

Finally, I also needed empty searches to work. According to the Relevanssi dev this is only supported in the Premium version, but I think I managed to work around it. First I forced WP to display the search page even if ?s was empty:

<?php
add_filter('request', 'h5b_allow_empty_search');

function h5b_allow_empty_search ($qryVars) {
    if (isset($_GET['s']) and empty($_GET['s'])) {
        $qryVars['s'] = ' ';
    }

    return $qryVars;
}

Second, I told Relevanssi to fetch all posts if ?s was empty, by default it won't fetch any posts at all. Unfortunately, in doing this the default behaviour with custom taxonomies stopped working (my code over wrote it) so I had to manually check for taxonomies in this code as well:

<?php
add_filter('relevanssi_hits_filter', 'h5b_allow_empty_search_filter');

function h5b_allow_empty_search_filter ($hits) {
    if (isset($_GET['s']) and empty($_GET['s']) and !count($hits[0])) {
        $taxQry = array('relation' => 'AND');

        if (!empty($_GET['custom_taxonomy'])) {
            $taxQry[] = array(
                'taxonomy' => 'custom_taxonomy', 
                'field' => 'slug', 
                'terms' => $_GET['custom_taxonomy']
            );
        }

        $args = array(
            'numberposts' => -1, 
            'post_type' => 'any'
        );

        if (count($taxQry) > 1) {
            $args['tax_query'] = $taxQry;
        }

        $hits[0] = get_posts($args);
    }

    return $hits;
}

And that's it. Everything seems to work and hopefully this will help someone who has similar requests in the future.

I really hope WP improves its ridiculous search in the future. Sorting by relevance should be something that's available in the world's most popular CMS imo.

Thanks @Sheikh Heera for pointing me in the right direction. I'm not sure who to give the right answer to though seeing as I really couldn't use the stuff you suggested.

like image 41
powerbuoy Avatar answered Nov 07 '22 14:11

powerbuoy


Just found out that WP already has this in core.

In https://developer.wordpress.org/reference/classes/wp_query/, look at the "orderby" => "relevance" parameter.

Order by search terms in the following order: First, whether the entire sentence is matched. Second, if all the search terms are within the titles. Third, if any of the search terms appear in the titles. And, fourth, if the full sentence appears in the contents.

like image 32
anjoalre Avatar answered Nov 07 '22 12:11

anjoalre