Custom Post Type, WP_Query and ‘orderby’

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.

Leave a Comment