5

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.

5

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

|improve this answer|||||
  • The code works, but unfortunately it returns two times the same parent post. When running a WP_Query with the custom attribute. – Fleuv Mar 30 '16 at 9:22
  • I retested my code and I cannot replicate your issue – Pieter Goosen Mar 30 '16 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. – Fleuv Mar 30 '16 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 – Pieter Goosen Mar 30 '16 at 12:54
  • Okay but it is always is returning two parent posts, even when viewing the child post with this code of this parent (so the global $post contains the child post object). The question is why is it iterating 3 times when there are only 2 posts to iterate through? – Fleuv Mar 30 '16 at 13:14
5

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($keys, 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.

|improve this answer|||||
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>
|improve this answer|||||
  • 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 '16 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.

|improve this answer|||||
  • 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? – Fleuv Mar 23 '16 at 19:37
0
    $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?

|improve this answer|||||
  • 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). – Fleuv Oct 23 '17 at 11:21
-2

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

<?php
   get_pages( array(
     'child_of' => $parent_page_id;
   ) );
?>
|improve this answer|||||
  • 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. – Fleuv Mar 23 '16 at 11:48
  • See my updated answer. Hope this will help to you. – Lalji Nakum Mar 23 '16 at 11:54
  • Thank you for the update, unfortunately this doesn't include an explanation on how to include the parent post. – Fleuv Mar 23 '16 at 12:00
  • you can refer codex.wordpress.org/Function_Reference/get_pages for detailed explanation – Lalji Nakum Mar 23 '16 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. – Fleuv Oct 23 '17 at 11:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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