Sorting the loop anyway you want to

Sort the posts in your website anyway you want to using a combination of the loop_start filter, the global $wp_query variable and the use of PHP’s usort function

Today I encountered multiple challenges when I tried to sort the posts on the homepage of Onvergetelijk Verleden to make sure that each row had either on large post or two smaller posts. No row (except for the last one) may contain one small post.

Since I couldn’t using the normal order options provide by query_posts (or WP_Query or get_posts) I had to modify the raw post data.

First step; discovering how the get to the data before it was being processed by The Loop. Obviously WordPress a filter for this called loop_start, unfortunately it’s fairly undocumented. The plugin API has this to say about it:  “Runs before the first post of the WordPress loop is processed”. With a bit of guess work, it accepts a WP_Query object as it’s first parameter which happens to be the same as the global $wp_query variable. Return the new WP_Query object to reflect the changes.

I won’t bore you with the sorting details I applied next, but it came down to creating a array with post ID’s in the order I needed and then using a nifty bit of code by Nicolas Kuttler using usort to sort the $wp_query->posts array. However, his code uses a function whereas I used a closure allowing me to pass the correct post ID order to the closure instead of using global variables.

To sum it up, here’s what you need to put in your functions.php file:

function loop_post_display_order($wpq)
  global $wp_query;
  $wpq = $wp_query;
  // $wpq->posts contains the raw post data

  $post_ids = array();  
  // do the sorting here and put the post ID's in $post_ids (in the correct order)


  // apply the sorting and pass the $post_ids variable to the closure
  usort($wpq->posts, post_id_sorter($post_ids));

  // return the new query
  return $wpq;

function post_id_sorter($ids) 
  return function ($a, $b) use ($ids) 
    $apos = array_search($a->ID, $ids);
    $bpos = array_search($b->ID, $ids);
    return ($apos < $bpos) ? -1 : 1;
add_filter('loop_start', 'loop_post_display_order');


Let me know in the comments below if you used this piece of code!