7

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.

0

6 Answers 6

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

7
  • The code works, but unfortunately it returns two times the same parent post. When running a WP_Query with the custom attribute.
    – luukvhoudt
    Mar 30, 2016 at 9:22
  • I retested my code and I cannot replicate your issue Mar 30, 2016 at 11:58
  • have a look at my code and output. Hopefully this will make you understand the problem better. Try to put the query on the parent or child page itself.
    – luukvhoudt
    Mar 30, 2016 at 12:13
  • I had a look at the var_dump(). There are two posts returned in $q->posts, post ID's 2126 and 2116, which seems to be correct, no duplicate post there. I think what you are seeing is post 2116 also available in $q->post, which is also correct. That is the $post global value for that specific query and will always be the first post in $q->posts by default Mar 30, 2016 at 12:54
  • 1
    Unfortunately I cannot help you here because, as I said, everything works on my side as intended. Start by deactivating plugins, dump all your variables and make sure they are what they should be, and switch to a bundled theme Mar 30, 2016 at 13:44
6

If all you want is results from the "page" post_type then do as @birgire suggested.

Alternatively you can adapt the following to give you a similar result for not only the page post_type but any custom post type.

$parent = 2;      //change as desired
$type   = 'page'; //change as desired

$child_args = array( 
    'post_type'   => $type, 
    'post_parent' => $parent 
);

$ids = array( $parent );
$ids = array_merge( $ids, array_keys( get_children( $child_args ) ));

$query = new WP_Query( 
    array( 
        'post_type'      => 'page', 
        'post_status'    => 'publish', 
        'post__in'       => $ids, 
        'posts_per_page' => -1 
    ) 
);

The above is essentially the same thing as hooking onto the posts_where filter and parsing the SQL clause however, this achieves exactly the same thing.

1
    $args = array(
        'post_type' => 'tribe_events',
        'posts_per_page' => '-1',
        'orderby' => 'ID',
        'order' => 'ASC',
        'post_parent' => $postID,
    );

    $children = new WP_Query($args);
    $parent[] = get_post($postID);
    $family = array_merge($parent, $children->get_posts());

This seems to work. Comments?

2
  • True, but it won't be as efficient as the accepted answer. The accepted answers approach only requires one query to be executed, your approach will result in two queries being executed (if not already cached).
    – luukvhoudt
    Oct 23, 2017 at 11:21
  • I think your answer is much better !!! so much more simple/readable !!! And to save what... 1 query ? out of 100s in WordProut context... and it's been cache 30 time before it got to your code.... on top of that the save query is one WHERE ID=123 .... 0.0001s Apr 28, 2022 at 6:49
0

Using global $wpdb combined with [get_results()][1] is an option as well. Performance wise I think this is the best solution since it only runs one query.

Here is my final code.

<ul class="tabs"><?php

    global $wpdb, $post;

    $parent = count(get_post_ancestors($post->ID))-1 > 0 ? $post->post_parent : $post->ID;

    $sql = "SELECT ID FROM `{$wpdb->prefix}posts`";
    $sql.= " WHERE ID='{$parent}' OR post_parent='{$parent}' AND post_type='page'";
    $sql.= " ORDER BY `menu_order` ASC";

    $tabs = $wpdb->get_results($sql);

    $output = '';
    foreach ($tabs as $tab) {
        $current = $post->ID == $tab->ID ? ' class="active"' : '';

        $output .= '<li'.$current.'>';
        $output .= empty($current) ? '<a href="'.get_permalink($tab->ID).'">' : '';
        $output .=   get_the_post_thumbnail($tab->ID, 'menu-24x24');
        $output .=   '<span>'.get_the_title($tab->ID).'</span>';
        $output .= empty($current) ? '</a>' : '';
        $output .= '</li>';
    }
    print $output;

?></ul>
1
  • 1
    You would think that would be the case, however WP_Query under the hood will cache query results, so for example, a call to get_children() or get_post_ancestors() in addition to WP_Query should not include any additional performance hit.
    – Adam
    Mar 23, 2016 at 12:56
0

If I understand you correctly you want to get the ID's of both the parent and any subsequent children pages. Wordpress has functions that fetch the children of pages, such as this one:

https://codex.wordpress.org/Function_Reference/get_page_children

It is my understanding, that since you are performing a WP_Query, you are already fetching the ID's of the parent pages, so all you would need to do is pass in the relevant ID to the the above function to get what you desire.

Note: I should point out that this function doesn't do a DB query, so better performance wise as you are only making on query to the DB.

1
  • I understand why you mention this function but, this answer is more a comment then an answer. Anyway I can't find a reason why this function could be useful since WP_Query already allows you to specify to a list of a post it's children with the argument: post_parent. So why is this function not deprecated?
    – luukvhoudt
    Mar 23, 2016 at 19:37
-2

You can use get_pages() with child_of parameter like below :

<?php
   get_pages( array(
     'child_of' => $parent_page_id;
   ) );
?>
5
  • That's not what I want to achieve, I don't want to use wp_list_pages because the output isn't fulfilling my needs.
    – luukvhoudt
    Mar 23, 2016 at 11:48
  • See my updated answer. Hope this will help to you. Mar 23, 2016 at 11:54
  • Thank you for the update, unfortunately this doesn't include an explanation on how to include the parent post.
    – luukvhoudt
    Mar 23, 2016 at 12:00
  • you can refer codex.wordpress.org/Function_Reference/get_pages for detailed explanation Mar 23, 2016 at 12:06
  • According to the documentation: The child_of parameter is not applied to the SQL query for pages. It is applied to the results of the query. As well as it won't return the parent.
    – luukvhoudt
    Oct 23, 2017 at 11:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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