Take the 2-minute tour ×
WordPress Development Stack Exchange is a question and answer site for WordPress developers and administrators. It's 100% free, no registration required.

I do have a custom post type with the following setup:

$supports = array(
    'title'
    , 'editor'
    , 'thumbnail'
    , 'revisions'
    , 'page-attributes'
);

$args = array(
  'hierarchical' => true
  , 'supports' => $supports
  [...]
);

register_post_type('myType', $args);

I would like to show all posts and have them sorted like in the wp-admin area (indention is for readability):

1, 
2, 
3, 
   1, (parent 3)
   2, (parent 3)
4

Therefore I tried the following query with the order-type set to 'menu_order':

$loop = new WP_Query( array(
       'post_type' => 'myType'
     , 'posts_per_page' => 50
     , 'orderby' => 'menu_order'
     , 'order' => 'ASC'
));

Unfortunately all post are sorted by menu_order, but by menu_order exclusively, ignoring the parent relationship (post_parent attribute). So I get something like this

1,
1, (parent 3)
2, 
2, (parent 3)
3,
4

Changing the query to 'orderby' => 'parent menu_order' leads to the following

1,
2, 
3,
4
1, (parent 3)
2, (parent 3)

So all in all it looks to me, as everything works as designed and the orderby-value is directly translated to the corresponding SQL 'Order By'.

Question

What is the easiest way to get the desired order?

SQL

I assume this is the main sql-query wordpress creates:

SELECT SQL_CALC_FOUND_ROWS wp_2_posts.ID 
FROM wp_2_posts 
WHERE 1=1 AND wp_2_posts.post_type = 'inhalt' AND (wp_2_posts.post_status = 'publish' OR wp_2_posts.post_status = 'private') 
ORDER BY wp_2_posts.post_parent, wp_2_posts.menu_order ASC LIMIT 0, 50

which is then followed up with:

SELECT wp_2_posts.* 
FROM wp_2_posts 
WHERE ID IN (40,42,44,46,48,50,52,54,56,58,60,76,62,65,69,71,74)

SELECT post_id, meta_key, meta_value 
FROM wp_2_postmeta 
WHERE post_id IN (40,42,44,46,48,50,52,54,56,58,60,62,65,74,69,71,76)

Workaround

A known workaround, but not an answer is to give all posts higher and more "spaced" order values, like

100,
200, 
300,
   310,
   320,
400
share|improve this question
add comment

2 Answers

up vote 4 down vote accepted
+50

As far as I can tell, there is no work-around to this at the database level. This is a problem I run into somewhat often, which is the case you need to turn an list with structure references into an an ordered array with children elements appearing immediately after their parents. This can be accomplished in PHP, but while this solution is fairly compact, it isn't terribly straight-forward.

The following solution adds a filter to the the_posts filter, which structures and then flattens the result set with a recursive function.

// Add each level's child posts to the result list, in order
function recursively_flatten_list( $list, &$result ) {
    foreach( $list as $node ) {
        $result[] = $node['post'];
        if( isset( $node['children'] ) )
            recursively_flatten_list( $node['children'], $result );
    }
}

function my_sort_posts( $posts, $query ) {
    // Don't do outside admin. Only operate on main query. Only operate on queries for pages.
    if( is_admin() || !$query->is_main_query() || $query->get( 'post_type' ) != 'page' )
        return;

    $refs = $list = array();
    // Make heirarchical structure in one pass.
    // Thanks again, Nate Weiner:
    // http://blog.ideashower.com/post/15147134343/create-a-parent-child-array-structure-in-one-pass
    foreach( $posts as $post ) {
        $thisref = &$refs[$post->ID];

        $thisref['post'] = $post;

        if( $post->post_parent == 0)
            $list[$post->ID] = &$thisref;
        else
            $refs[$post->post_parent]['children'][$post->ID] = &$thisref;
    }

    // Create single, sorted list
    $result = array();
    recursively_flatten_list( $list, $result );

    return $result;
}
add_filter( 'the_posts', 'my_sort_posts', 10, 2 );

I've tested this solution and it's general enough for arbitrary page hierarchies.

This code assumes that the posts are already ordered by menu_order. If you are to use this solution, be sure to change the orderby parameter to just "menu_order" where you make the call to new WP_Query.

share|improve this answer
    
Kewl! I had to modify it to $loop->posts = my_sort_posts($loop->posts); without adding the filter, but this way I works like a charm. THX! –  SunnyRed Sep 7 '12 at 12:18
add comment

See the codex for other options, but it looks like you would want to be using 'parent' as a primary sort in this case.

$loop = new WP_Query( array(
       'post_type' => 'myType'
     , 'posts_per_page' => 50
     , 'orderby' => 'parent menu_order'
     , 'order' => 'ASC'
));

This will primarily sort by parent, with a secondary sort of menu order. This should give you your desired result.

share|improve this answer
    
Thx eric, but unfortunately this leads to 1, 2, 3, 4, 1(parent 3), 2 (parent 3). –  SunnyRed Aug 30 '12 at 13:42
    
Hmm bizzare. Try swapping the two? Or only using parent? I did not test this, just going by the codex. –  Eric Holmes Aug 30 '12 at 14:39
    
+1 Finally someone who figured out that a space allows multiple arguments. –  kaiser Aug 30 '12 at 16:57
1  
@SunnyRed Could you please post the exact query string coming out of that? (Hint: Debug Bar Plugin + Extensions). –  kaiser Aug 30 '12 at 16:57
2  
The reason for this behavior is because the value of 'parent' for top-level categories will be 0, and the ordering is thus correct, as all of the entries whose parent is '0' will come out first. –  Bendoh Sep 5 '12 at 1:38
show 3 more comments

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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