5

I'm trying to create a loop of explicity ordered posts, for example:

<?php $args = array(
    'include'         => '1,3,8,4,12' ); ?>

<?php get_posts( $args ); ?> 

The results are ordered by date by default, and there is no orderby option to return the posts in the order they were entered. There have been multiple bug/feature requests posted about this in Trac, but so far no luck. I've mucked around in the core files a bit but haven't gotten anywhere with it.

Can anyone suggest a workaround for this behavior?

Cheers, Dalton

7
  • @Dalton, is a post order plugin out of the question? You could then use 'orderby' => 'menu_order', 'order' => 'ASC' Commented Mar 3, 2011 at 18:12
  • In this case that wouldn't work - I'm actually returning attachments, each with different parents, so menu_order becomes irrelevant. Many thanks for the response! Commented Mar 3, 2011 at 18:20
  • @Dalton, perhaps I am misunderstanding your problem, but in the media uploader box, if you go to "gallery" you can reorder images by assigning them numbers and then again as suggested above use 'orderby' => 'menu_order, 'order' => 'ASC' Commented Mar 3, 2011 at 18:43
  • Just a sidenote: You can use multiple arguments with orderby separated by space: &order_by=date ID for ex.
    – kaiser
    Commented Mar 3, 2011 at 20:57
  • @eileen.carpenter: The problem in my case is that each attachment could theoretically have a different parent, making menu_order irrelevant. Each of the attachment IDs in my query could potentially have the same menu order. I'm passing the included posts via a shortcode, so I need to return the attachments in the order given. Commented Mar 3, 2011 at 23:02

5 Answers 5

5

Okay, I was determined to find a way to do this, and I think I've got it. I had hoped to find a simpler solution and avoid having to use a new WP_Query object, but it's just too ingrained into how the loop works. First, we have a couple of utility functions:

// Set post menu order based on our list  
function set_include_order(&$query, $list) {
    // Map post ID to its order in the list:
    $map = array_flip($list);

    // Set menu_order according to the list     
    foreach ($query->posts as &$post) {
      if (isset($map[$post->ID])) {
        $post->menu_order = $map[$post->ID];
      }
    }  
}

// Sort posts by $post->menu_order.                                 
function menu_order_sort($a, $b) {
  if ($a->menu_order == $b->menu_order) {
    return 0;
  }
  return ($a->menu_order < $b->menu_order) ? -1 : 1;
}

These will allow us to set the menu_order property based on our own list, and then sort the posts in a query object based on that.

Here's how we query and sort the posts:

$plist = array(21, 43, 8, 44, 12);
$args = array(
  'post_type' => 'attachment',
  'post_status' => 'any',
  'post__in' => $plist 
);

// Create a new query  
$myquery = new WP_Query($args);

// set the menu_order
set_include_order($myquery, $plist);

// and actually sort the posts in our query
usort($myquery->posts, 'menu_order_sort');

So now we have our own query object, and the $myquery->posts is sorted according to our custom menu_order_sort function. The only tricky part now, is that we must construct our loop using our custom query object:

while($myquery->have_posts()) : $myquery->the_post();
  ?>
    <div><a id="post_id_<?php the_ID(); ?>" class="nb" href="<?php the_permalink(); ?>"><?php the_title(); ?></a> Post ID: <?php the_ID(); ?>
    </div>
  <?php

endwhile;
wp_reset_postdata();

Obviously, you'd fix up the loop template code there.

I was hoping to find a solution that didn't require the use of a custom query object, perhaps by using query_posts() and replacing the posts propery on the global $wp_query, but I just couldn't get it to work right. With a little more time to work on it, that might have been doable.

Anyhow, see if that will get you where you need to go?

6
  • 2
    Bouy...looks a bit overkillish and doesn't work with pagination (as your sorting after querying), but +1 for a great effort to solve it without injecting something on the SQL level.
    – wyrfel
    Commented Mar 4, 2011 at 18:28
  • Amazing! That definitely works for posts and answers the original question. There's just one hitch, which is that I need to return attachments, not posts. I didn't realize that WP-Query() doesn't work with attachments, I've been using get_posts() ... is there any easy way to get this to work with get_posts()? I'm going to keep working on it and see what I can come up with. Commented Mar 4, 2011 at 18:42
  • Updated the code to fetch attachments. :) With attachments, you have to specify post_status as 'any', because they normally use status inherit instead of publish, to reflect their parent's state. Commented Mar 4, 2011 at 19:24
  • Oh, and for future folks reading this, please note @wyrfel's comment: this will not work well if you need to paginate results, since the sorting doesn't happen within MySQL. Commented Mar 4, 2011 at 19:41
  • @Dougal: Many thanks, your changes did the trick. Fortunately I am not paging these results so I am 100% set. Commented Mar 4, 2011 at 20:33
10

You can try this:

add_filter('posts_orderby', 'enforce_specific_order');
$posts = get_posts($args);
remove_filter( current_filter(), __FUNCTION__ );

function enforce_specific_order($orderby) {
    global $wpdb;
    return "FIND_IN_SET(".$wpdb->posts.".ID, '1,3,8,4,12') ASC";
}
0
10

I think this is the fastest way to return the results of a get_posts in a defined order. And besides that, it's a native solution, without hacks

<?php

$posts_order = array('1,3,8,4,12');
$args = array(
    'post__in' => $posts_order,
    'orderby' => 'post__in'
); 
get_posts( $args ); 

?> 
7
  • Please add an explanation to your answer: why could that solve the problem?
    – fuxia
    Commented Aug 3, 2013 at 21:53
  • 1
    It's a native solution, without hacks Commented Aug 5, 2013 at 15:01
  • Explain in your answer what is the diffenerece to the accepted answer which is using post__in too.
    – fuxia
    Commented Aug 5, 2013 at 16:39
  • 1
    On the accepted answer, @dougal suggested to create two functions: set_include_order() and menu_order_sort(). There is no need for that. And besides, he suggested to use WP_Query instead of get_posts, as the question's author wanted. And post__in only works if you include the 'orderby' parameter too, like i suggested Commented Aug 5, 2013 at 18:26
  • 4
    great answer - down vote wrong - 3.6.1 - simple and easy - Thanks!
    – Q Studio
    Commented Oct 17, 2013 at 17:32
3

As of WordPress 3.5, this feature is now in core. You can explicitly order posts using the "post__in" parameter. http://core.trac.wordpress.org/ticket/13729

0

How about just clearing the orderby with a filter? Right before you query your posts, put in:

add_filter('posts_orderby', '__return_false');

Then, after your loop is done:

remove_filter('posts_orderby', '__return_false');

The reason for removing the filter again is in case you have other loops on the page (such as from widgets) that will need their normal explicit ordering.

3
  • Dougal: There's actually an orderby=none option, but that returns the natural order of the posts in the database, essentially orderby ID. What I'm after is a way to return my posts in the order specified in the query. It's not supported by WP_Query, so I guess I'm looking for a workaround or hack. Commented Mar 3, 2011 at 23:09
  • Well, phooey. I figured that an 'include' query would return the results in the order given if you eliminated the orderby. But now that I'm thinking about it, it makes sense that MySQL would return the results in natural order (it's trying to be efficient). Commented Mar 4, 2011 at 14:18
  • 3
    I was about to delete this answer of mine, but I thought it might be useful to keep around to document the database-natural-order behavior when you eliminate the orderby. Commented Mar 4, 2011 at 19:45

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.