3

I'm trying to re-order the search results using the pre_get_posts hook and set the orderby parameter to relevance. I also want to put priority posts, which is based on post meta _prioritize_s to be placed first in the results. Then all the posts after will be based on relevance as usual.

Here's the code I've been messing with:

//runs on pre_get_posts
function modify_search_results_order( $query ) {
        if ( $query->is_main_query() && is_search() && ! is_admin() ) {

            $query->query_vars['order']        = 'DESC';
            $query->query_vars['orderby']      = 'relevance';
            $query->query_vars['post_status']  = 'publish';
//          $query->query_vars['meta_query']   = [
//              'relation' => 'OR',
//              array(
//                  'key'     => '_prioritize_s',
//                  'compare' => 'EXISTS'
//              ),
//              array(
//                  'key'     => '_prioritize_s',
//                  'compare' => 'NOT EXISTS'
//              )
//          ];
        }

        return $query;
    }

I originally had the orderby set to meta_value relevance and it did put the priority posts on top with the meta_query uncommented. However, it doesn't keep the relevance sorting intact for posts afterwards. It seems like the meta query is affecting the overall order.

I have a feeling I might need something custom like a database query instead? Doesn't seem like I'm going to get what I need by just setting the $query->query_vars. Can anyone help out? Thanks in advance!


UPDATE: Since I haven't figured out how to alter the code I posted above, I'm trying an alternate solution. Let me know if it's the wrong way to go about it. Leaving the above code alone, I can use the found_posts hook to alter the search results query after my pre_get_posts function has already done it's magic. However my only issue is the second parameter that should give me the query is only giving me 10 posts when I know the results should be more than that. Why is it limiting it to 10 posts even if I set posts_per_page to -1 in my pre_get_posts function?

function modify_search_results_prioritized( $found_posts, $wp_query ) {
        if ( is_search() ) {
            //this says 10 - why?
            error_out($wp_query->query_vars['posts_per_page']);
        }

        return $found_posts;
    }
7
  • @Nicolai I did have the orderby as meta_value relevance and that didn't work. It's not in the code example but I wrote in my description that I had tried that. The relevance isn't meta, it's a WordPress orderby parameter you can set to sort posts by relevance for search. Hopefully that helps clear some things up. Let me know. Thanks.
    – RachieVee
    Commented May 6, 2019 at 16:16
  • I saw it, which is why I deleted my comment. Forgot about the relevance to be honest. Commented May 6, 2019 at 16:19
  • Out of curiosity: when you tried ordering by meta_value then relevance, what happened to the relevance of posts that were not priorities that didn't fit your needs?
    – moraleida
    Commented May 6, 2019 at 16:39
  • 1
    I didn't look further into details, but it is noted in the source »// Order search results by relevance only when another "orderby" is not specified in the query.« Commented May 6, 2019 at 16:51
  • 2
    Just a quick outline of an idea: Perform one query to get the priority posts, and another query for the other posts. Use the fields parameter with ids, so $query->posts contains an array of ids. The use of the s parameter makes WordPress handled it like every other search, so each array is ordered by relevance. Merge the arrays, and then use the combined array to set the parameter post__in and orderby with post__in. This is obviously untested, but I think it should work. Commented May 6, 2019 at 17:16

2 Answers 2

2

So after @Nicolai found that blurb in the WP source confirming that I can't use multiple parameters in the orderby if I am ordering by relevance I had to go a different route. However, this route only works if I don't care for pagination. There probably is a way to put it back, but I'm working on a different solution now and I no longer need to stay on the code I have in my question.

So I kept the function to alter the query on pre_get_posts and removed anything regarding post meta/meta queries:

function modify_search_results_order( $query ) {
        if ( $query->is_main_query() && is_search() && ! is_admin() ) {
            $get_expired_events = Event_Helpers::get_expired_event_ids();

            $query->query_vars['posts_per_page'] = - 1;
            $query->query_vars['order']          = 'DESC';
            $query->query_vars['is_search']      = true;
            $query->query_vars['post__not_in']   = $get_expired_events;
            $query->query_vars['orderby']        = 'relevance';
            $query->query_vars['post_status']    = 'publish';
        }

        return $query;
    }

Then in my search template, I call a function that alters the query after pre_get_posts to look for the priority posts, unset them from the main query, and place them back on top:

//in search.php
$wp_query->posts = Search_Modify_Query::rearrange_search_query_for_priority_relevance( $wp_query->posts );

//in PHP Class
public static function rearrange_search_query_for_priority_relevance( $query ) {
        //get prioritized ids
        $get_prioritized = self::get_priority_search_post_ids();
        $get_results_ids = [];

        if ( ! is_array( $get_prioritized ) || ! $get_prioritized ) {
            return $query;
        }

        //save all ids from current search results query
        foreach ( $query as $key => $post ) {
            $get_results_ids[ $key ] = $post->ID;
        }

        if ( ! $get_results_ids ) {
            return $query;
        }

        //check if there are priority posts in results
        if ( array_intersect( $get_prioritized, $get_results_ids ) ) {
            $priority_matches = array_intersect( $get_results_ids, $get_prioritized );
            $priority_query   = false;

            //if there are priority matches, pluck them out to put them on top
            if ( $priority_matches ) {
                foreach ( $priority_matches as $key => $priority_match ) {
                    //save these into new array first
                    $priority_query[ $key ] = $query[ $key ];
                    //then unset them from main query
                    unset( $query[ $key ] );
                }

                if ( $priority_query && is_array( $priority_query ) ) {
                    //then re-add them on top of main query
                    return array_merge( $priority_query, $query );
                } else {
                    return $query;
                }
            }
        }

        return $query;
    }

This works fine but because I needed to have ALL the results to do the comparison with my "priority post" ids vs the results ids, I had to set posts_per_page to -1. So while it works, unless I find a way to put back pagination or write something custom, the search results will give me all of the results on search.php whether it's 5 or 500. Still, I'm placing this code here in case it helps someone else in the long run.

For my case though, I've decided to do two separate queries and I confirmed we don't care about having duplicates. So I'll query just the priority posts matching the search term above the main search results query.

3
  • Just a note: I'm assuming you didn't utilize post__in, as mentioned in the comment, because post__not_in is already in use, as post__in and post__not_in can't be used in one query. Furthermore, if you go for the suggested approach, so getting the ids separately and then combining them for the search result query, you'd have pagination. Going this route you'd be able to exclude via post__not_in in the first two queries, and then use post__in to get your results. Commented May 6, 2019 at 21:33
  • @Nicolai I'm not sure I understand. Is this so I don't have to set the posts_per_page to -1 to do the priority posts comparison with the search result posts?
    – RachieVee
    Commented May 7, 2019 at 15:09
  • Exactly, and you get rid of the sub optimal iterating over the array. Commented May 7, 2019 at 15:53
1

Just to elaborate on what we've been talking about in the comments. I know you solved this already for your use case, so this is more for completeness’ sake and proof of concept purposes. Untested, for more information see the code comments.

add_action( 'pre_get_posts', 'prioritized_before_rest' );
function prioritized_before_rest( $q ) {
  if ( ! is_admin() && $q->is_main_query() && $q->is_search() ) {   
    // get the search term
    $search_term = $q->get( 's' );
    // get the prioritized items
    $prioritized = new WP_Query( [ 
      // it is a search, so it is ordered by relevance
      's' => $search_term,
      // I assumed that is your condition
      'meta_query' => [ [ 
        'key' => '_prioritize_s',
        'compare' => 'EXISTS',
       ], ],
      // we want them all
      'posts_per_page' => -1,
      // but only ids
      'fields' => 'ids',
    ] );
    // array of ids
    $prioritized_ids = $prioritized->posts;

    $rest = new WP_Query( [ 
      's' => $search_term,
      // get the rest by excluding above queried priorizied items
      'post__not_in' => $prioritized_ids,
      'posts_per_page' => -1,
      'fields' => 'ids',
    ] );
    $rest_ids = $rest->posts;

    // merge above queried ids, order matters
    $prioritized_before_rest = array_merge( $prioritized_ids, $rest_ids );

    // use merged array, this alone does nothing 
    // but to get all posts as would the unaltered query
    $q->set( 'post__in', $prioritized_before_rest );
    // we can set orderby to post__in though to preserve the order 
    // established with above queries and merging the arrays
    // this keeps standard posts_per_page setting and pagination alive
    $q->set( 'orderby', 'post__in' );
  }
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.