Return parent post with its children using WP_Query?

In some cases it might be useful to use multiple post & page parameters in your WP_Query object. In my case, I would like to display children of a parent page including the parent page itself.

Visualization of what I want to achieve. Imagine the following pages hierarchically sorted as following:

  • page A
  • page B
    • Child page A
    • Child page B
    • Child page C
  • page C

The bold list items are the posts/pages I want to retrieve.

My first thoughts go out using these two parameters for WP_Query:

$args = array(
   'post_id' => $parent->ID,
   'post_parent' => $parent->ID,
);

Unfortunately, here it will only use one parameter. With the $args above (correct me if I’m wrong) it will output all children posts of the parent post and not the parent post itself as well.

This problem might be solved by gathering all posts needed and putting them in parameter post__in like so:

$args = array(
   'post__in' => $children_and_parent_ids,
);

However there is wp_list_pages() allowing you to include a post(s) and specify the post where you want to include the children of (child_of). Why is this not possible with WP_Query?

An example of what I’m trying to achieve using wp_list_pages():

wp_list_pages(array(
    'include' => $parent->ID,
    'child_of' => $parent->ID,
));

Have a look at the documentation of WP_Query.

6 s
6

We can filter the posts_where clause of the generated SQL to also return the parent post/page and not just the parent’s children. Here we will set our own custom argument called wpse_include_parent, which, when set to true, will alter the generated SQL accordingly.

All we need to do inside our posts_where filter is to check if our custom argument is set and that the post_parent argument is set. We then get that value and pass it to the filter to extend our SQL query. What is nice here, post_parent excepts a single integer value, so we only need to validate the value as an integer.

THE QUERY

$args = [
    'wpse_include_parent' => true,
    'post_parent'         => 256,
    'post_type'           => 'page'
    // Add additional arguments
];
$q = new WP_Query( $args );

As you can see, we have set 'wpse_include_parent' => true to “activate” our filter.

THE FILTER

add_filter( 'posts_where', function ( $where, \WP_Query $q ) use ( &$wpdb )
{
    if ( true !== $q->get( 'wpse_include_parent' ) )
        return $where;

    /**
     * Get the value passed to from the post parent and validate it
     * post_parent only accepts an integer value, so we only need to validate
     * the value as an integer
     */
    $post_parent = filter_var( $q->get( 'post_parent' ), FILTER_VALIDATE_INT );
    if ( !$post_parent )
        return $where;

    /** 
     * Lets also include the parent in our query
     *
     * Because we have already validated the $post_parent value, we 
     * do not need to use the prepare() method here
     */
    $where .= " OR $wpdb->posts.ID = $post_parent";

    return $where;
}, 10, 2 );

You can extent this as you need and see fit, but this is the basic idea. This will return the parent passed to post_parent and it’s children

Leave a Comment