How can I hide children of draft pages using wp_list_pages()?

I’m displaying a simple sitemap with wp_list_pages();

$args = array(
    'sort_column' => 'menu_order',
    'title_li' => '',
    'post_status'  => 'publish'
);

wp_list_pages( $args );

The problem is that by default this also shows the published children of draft pages, like so :

Page 1 (published) -> displayed

— Page 2 (draft) -> not displayed

—— Page 3 (published) -> displayed

What I would like to achieve is :

Page 1 (published) -> displayed

— Page 2 (draft) -> not displayed

—— Page 3 (published) -> not displayed

I suspect a custom Walker would do the trick, but I could never really understand how those work..

Is there a way to hide those child pages without having to set them all to draft ?

Edit:

To clarify, let’s try some imagery. So you have a tree with the complete hierarchy of your pages. We are climbing up the tree. The moment we encounter a a draft branch, we cut it down. Naturally all the other branches attached to it further along are also discarded (no matter if they are drafts or not). I hope that explains it better.

Here is an example with a somewhat deep hierarchy :

Page 1 (published) -> displayed

— Page 2 (draft) -> not displayed <- Cut here and exclude all further children

—— Page 3 (published) -> not displayed

——— Page 4 (published) -> not displayed

———— Page 5 (draft) -> not displayed

————— Page 6 (published) -> not displayed

4 s
4

Great answers above. I took on the challenge trying to find yet another way to solve this.

The exclude parameter:

We could try:

'exclude' => wpse_exclude_drafts_branches()

where:

function wpse_exclude_drafts_branches()
{
    global $wpdb;
    $exclude = array();
    $results = $wpdb->get_col( "SELECT ID FROM {$wpdb->posts} where post_status="draft" AND post_type="page" " );
    $exclude = array_merge( $exclude, $results) ;
    while ( $results ):
        $results = $wpdb->get_col( "SELECT DISTINCT ID FROM {$wpdb->posts} WHERE post_type="page" AND post_status="publish" AND post_parent > 0 AND post_parent IN (" .  join( ',', $results ) . ") " );
        $exclude = array_merge( $exclude, $results) ;
    endwhile;
    return join( ',', $exclude );
}

and the number of queries depends on the tree depth.

Update:

The exclude_tree parameter:

I just noticed the exclude_tree parameter mentioned on the Codex page, so I wonder if this would work (untested) to exclude the whole of the draft nodes branches:

$exclude = get_posts(
    array( 
        'post_type'      => 'page',
        'fields'         => 'ids',
        'post_status'    => 'draft',
        'posts_per_page' => -1,
   )
);

and then use:

'exclude_tree' => join( ',', $exclude ),

with wp_list_pages().

Leave a Comment