1

On the search results page (search.php) I would like to perform an additional WP_Query to keep a specific order of search result types e.g. search results from authors should always be shown before the other results -

The first few pages should hold the author results, the other results should be shown on all the following pages only (not mixed on each page).

global $wp_query; //holds the results of the initial search query

$authorposts = $wp_query->posts;    
$query = new WP_Query( 'year=2012&monthnum=12&day=12' );
$otherposts = $query->posts;

$wp_query->posts = array_merge( $authorposts, $otherposts  );
$wp_query->post_count = count( $wp_query->posts );

if ( have_posts() ) : while ( have_posts() ) : the_post(); ?>
  //show results loop...

The problems appear with the behaviour of pagination - while the $authorposts seem to paginate correctly i.e. correct page numbers for author posts and pagination works as only those where existent. But the $otherposts on the other hand are shown at the bottom of each page, right under the author posts. And they are the same on every page! Also the page count reflects the count of the first query only.

I have also tried to merge with the plus operator:

$wp_query->posts = $authorposts + $otherposts;

but in this scenario the $otherposts don't show up at all and the page count stays the same (page count for author posts).

All the examples with similar goals using two separate queries I've seen looked quite the same -

So I'm wondering if the my way of extending the original search query results with that additional WP_Query is sufficient or if I'm missing some $wp_query fields to be populated (correctly)? What's the correct way doing this?

Update: It should be noted that I'm using the WP-PageNavi plugin to paginate but this should not be relevant in the first place as the first page is already rendered wrong. And it's also initialized with the query object: wp_pagenavi( array( 'query' => $wp_query ));

  • I don't understand keep a specific order of search result types (which types?) or search results from authors (all search results are posts with authors). – sam Nov 6 '13 at 3:01
  • The first search results (authors) are posts which have a specific author name in a Meta field (custom field, not the standard user/displayname). The other posts may also have a value in this field but not the exact same value or no value at all. Therefore I'm not able to keep the desired order by a simple SQL sort. – david Nov 6 '13 at 12:18
2
+50

I think the only solution is to skip the sql pagination and handle it only via php. My idea involves 2 functions, one hooked on pre_get_posts, the second to filter the_posts.

The first function does 2 things:

  1. unset the paged value for search, in this way all the posts are returned from the SQL query.
  2. save the queried paged value in global variable, in this way can be used in the second function that run later

Here the function:

function filter_my_search_query( $query ) {
  if ( is_search() && $query->is_main_query() && ! is_admin() ) {
    global $the_original_paged;
    $the_original_paged = $query->get('paged') ? $query->get('paged') : 1;
    $query->set('paged', NULL );
    $query->set('nopaging', TRUE );
  }
}
add_action('pre_get_posts', 'filter_my_search_query', 1);

Now the search query return all the posts, with no pagination, and the paged required is saved in the global variable $the_original_paged.

So we can filter the_posts merging the wanted additional posts, then get only the correct posts based on required page and posts per page setting, and finally reset the paged and other $wp_query properties to let pagination link works:

function add_posts_to_search_query( $posts ) {
  global $wp_query, $the_original_paged;
  if ( ! is_main_query() || is_admin() || ! is_search() || is_null($the_original_paged) )
      return $posts;
  // the wanted posts per page here setted on general settings
  $perpage = get_option('posts_per_page'); 
  remove_filter( 'the_posts', 'add_posts_to_search_query' );
  $new = new WP_Query( 'year=2012&monthnum=12&day=12&nopaging=1' );
  $merged = array_merge( $posts, $new->posts );
  $wp_query->found_posts += $new->found_posts;
  // getting the right posts based on current page and posts per page
  $wp_query->posts = array_slice($merged, ( $perpage * ($the_original_paged-1) ), $perpage );
  // set the paged and other wp_query properties to the right value, to make pagination work
  $wp_query->set('paged', $the_original_paged);
  $wp_query->post_count = count($wp_query->posts);
  $wp_query->max_num_pages = ceil( $wp_query->found_posts / $perpage ); 
  unset($the_original_paged); // clean up global variable
  return $wp_query->posts; 
}
add_filter('the_posts', 'add_posts_to_search_query');
  • Great, thanks, that one is working! Not the most performant solution (especially as 'year=2012&monthnum=12&day=12' is being replaced by the actual search term) but I really appreciate you found a way doing it. – david Nov 8 '13 at 22:22
  • 1
    Yes I know is not very performant. I hope search query and real additional query will not return too much posts... if this one is too heavy try to cache it, e.g. using transient. – gmazzap Nov 8 '13 at 22:39
1

Filter the main query to include the additional posts. You can add query params with pre_get_posts or modify the SQL with posts_request or posts_where.

Filter posts_results or the_posts to arrange the returned posts.

function filter_posts_where($where)
{
    $where .= 'OR …' /* post_date is 2012-12-12 */;

    return $where;
}

function filter_the_posts($posts)
{
    usort($posts, 'compare_posts');

    return $posts;
}

function compare_posts($a, $b)
{
    // compare "author" and dates?
    if     /* … */ return  1;
    elseif /* … */ return -1;
    else           return  0;
}
  • 2
    Problem with merging the two queries via pre_get_post is the wanted order are lost: OP want to put all the post from second query at the end. If the query are merged this can't be done. – gmazzap Nov 6 '13 at 6:31
  • Thanks for your answer sam! But as G.M. pointed out correctly, I want to keep the given order defined by the query execution (authors first, other posts at the end). I understand that the "$otherposts" are below because of the merge but I'd like to find a way to merge that those posts are appended on the last page. – david Nov 6 '13 at 12:14
  • I also assumed that I have to fake the pagination somehow and that I have to fake additional WP_Query properties. Still wondering what those properties are and if I have to merge the 2nd query "conditionaly" when the last page is reached? But there must be an easier way. And I've tried "@pre_get_posts" and "@posts_search" hooks which unfortunately work on the pre-query/SQL level only and therefore don't keep the order I intend. – david Nov 6 '13 at 12:15
  • I see; I'd thought the orderby params might be enough to give you the order you want. – sam Nov 7 '13 at 1:06
  • @david Updated my answer. – sam Nov 7 '13 at 1:36
0

I could not completly solve all the issues involved in this question but for reference I post the current state here:

function SQ_the_posts($posts, $q = false) {
    if( is_search() && is_main_query() ){

            global $wp_query;

            $authorposts = $wp_query->posts;
            $paged = get_query_var('paged');

            remove_filter( 'the_posts', 'SQ_the_posts' );
            $query = new WP_Query( 'year=2012&monthnum=12&day=12' );
            add_filter( 'the_posts', 'SQ_the_posts' );

            $otherposts = $query->posts;
            $mergedPosts = array_merge( $authorposts, $otherposts  );

            $wp_query->found_posts += $query->found_posts;
            $initialMaxNumPages = $wp_query->max_num_pages;
            if ($paged > $initialMaxNumPages) {
                    $wp_query->posts = $otherposts;
                    $wp_query->post_count = $query->post_count;
                    $posts = $otherposts;
            }

            $wp_query->max_num_pages = ($wp_query->found_posts > 0) ? $wp_query->found_posts / 10 : 0;
            $wp_query->max_num_pages += ($wp_query->found_posts % 10) ? 1 : 0;          
    }
    return $posts;
}
add_filter( 'the_posts', 'SQ_the_posts' );

What it does:

  • Queries the author and other posts
  • Calculates the correct amount of pages for both queries
  • Renders the first ten items of the second query when the max_num_pages of the first query is exceeded

What doesn't work:

  • If for example the first query (author search query) results in 9 pages and the second query returns 5 pages the pagination shows correctly 14 pages. But when I click on page 10 pagination shows "page 10 of 5" because the 9 pages of the first query are not available anymore on that page. Is there any way to fix this?
  • The second query needs to be enhanced that the current page gets translated to its correct paging equavalent e.g. page 10 for the total queries means a paged parameter of "1" for the second query.

Still I'm amazed that there is no easy approach. In case anybody is able to solve these issues based on my answer (copy+paste) I'd be happy to mark it as accepted!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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