¿Cómo obtener una publicación padre con sus hijos usando WP_Query?
En algunos casos puede ser útil usar múltiples parámetros de posts y páginas en tu objeto WP_Query
. En mi caso, me gustaría mostrar los hijos de una página padre incluyendo la página padre misma.
Visualización de lo que quiero lograr. Imagina las siguientes páginas ordenadas jerárquicamente así:
- página A
- página B
- Página hija A
- Página hija B
- Página hija C
- página C
Los elementos en negrita son los posts/páginas que quiero recuperar.
Mis primeros pensamientos van hacia usar estos dos parámetros para WP_Query
:
$args = array(
'post_id' => $parent->ID,
'post_parent' => $parent->ID,
);
Desafortunadamente, aquí solo usará un parámetro. Con los $args
anteriores (corríjanme si me equivoco) mostrará todas las publicaciones hijas de la publicación padre y no la publicación padre en sí.
Este problema podría resolverse reuniendo todas las publicaciones necesarias y colocándolas en el parámetro post__in
así:
$args = array(
'post__in' => $children_and_parent_ids,
);
Sin embargo, existe wp_list_pages()
que te permite include
una o más publicaciones y especificar la publicación de la cual quieres incluir los hijos (child_of
). ¿Por qué esto no es posible con WP_Query
?
Un ejemplo de lo que estoy tratando de lograr usando wp_list_pages()
:
wp_list_pages(array(
'include' => $parent->ID,
'child_of' => $parent->ID,
));
Echa un vistazo a la documentación de WP_Query
.

Si lo único que deseas son resultados del tipo de publicación "page", entonces haz como sugirió @birgire.
Alternativamente, puedes adaptar lo siguiente para obtener un resultado similar no solo para el tipo de publicación page
sino para cualquier tipo de publicación personalizado.
$parent = 2; //cambiar según se desee
$type = 'page'; //cambiar según se desee
$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
)
);
El código anterior es esencialmente lo mismo que engancharse al filtro posts_where
y analizar la cláusula SQL, sin embargo, esto logra exactamente lo mismo.

Podemos filtrar la cláusula posts_where
del SQL generado para que también devuelva la entrada/página padre y no solo los hijos del padre. Aquí estableceremos nuestro propio argumento personalizado llamado wpse_include_parent
, que, cuando se establece en true
, modificará el SQL generado en consecuencia.
Todo lo que necesitamos hacer dentro de nuestro filtro posts_where
es verificar si nuestro argumento personalizado está establecido y que el argumento post_parent
también esté definido. Luego obtenemos ese valor y lo pasamos al filtro para extender nuestra consulta SQL. Lo bueno aquí es que post_parent
acepta un solo valor entero, por lo que solo necesitamos validar el valor como un entero.
LA CONSULTA
$args = [
'wpse_include_parent' => true,
'post_parent' => 256,
'post_type' => 'page'
// Agregar argumentos adicionales
];
$q = new WP_Query( $args );
Como puedes ver, hemos establecido 'wpse_include_parent' => true
para "activar" nuestro filtro.
EL FILTRO
add_filter( 'posts_where', function ( $where, \WP_Query $q ) use ( &$wpdb )
{
if ( true !== $q->get( 'wpse_include_parent' ) )
return $where;
/**
* Obtener el valor pasado al post_parent y validarlo
* post_parent solo acepta un valor entero, así que solo necesitamos validar
* el valor como un entero
*/
$post_parent = filter_var( $q->get( 'post_parent' ), FILTER_VALIDATE_INT );
if ( !$post_parent )
return $where;
/**
* Incluyamos también al padre en nuestra consulta
*
* Como ya hemos validado el valor de $post_parent, no
* necesitamos usar el método prepare() aquí
*/
$where .= " OR $wpdb->posts.ID = $post_parent";
return $where;
}, 10, 2 );
Puedes extender esto según lo necesites y consideres adecuado, pero esta es la idea básica. Esto devolverá el padre pasado a post_parent
y sus hijos.

El código funciona, pero desafortunadamente devuelve dos veces la misma publicación principal. Al ejecutar un WP_Query
con el atributo personalizado.

echa un vistazo a mi código y salida. Espero que esto te ayude a entender mejor el problema. Intenta poner la consulta en la página principal o secundaria misma.

Revisé el var_dump()
. Hay dos publicaciones devueltas en $q->posts
, los ID's de publicación 2126 y 2116, lo cual parece correcto, no hay duplicados allí. Creo que lo que estás viendo es la publicación 2116 también disponible en $q->post
, lo cual también es correcto. Ese es el valor global $post
para esa consulta específica y por defecto siempre será la primera publicación en $q->posts

De acuerdo, pero siempre está devolviendo dos publicaciones padre, incluso cuando se visualiza la publicación hija con este código del padre (por lo que el global $post
contiene el objeto de la publicación hija). La pregunta es ¿por qué está iterando 3 veces cuando solo hay 2 publicaciones para iterar?

¿por qué está iterando 3 veces cuando solo hay 2 publicaciones para iterar? Entonces eso no tiene nada que ver con la consulta. En el objeto de consulta, hay dos publicaciones. Si hay una tercera publicación, se inyecta en esta consulta mediante algún filtro personalizado. Todo en tu consulta parece correcto, aunque la consulta SQL se ve extraña debido a otros filtros que actúan sobre la consulta. Debes depurar esto, asegurarte de que $post
es lo que esperas que sea, verificar si tu bucle es correcto y si los otros filtros utilizados se usan correctamente en este contexto. En una instalación limpia, todo funciona como se espera en mi caso.

$args = array(
'post_type' => 'tribe_events', // Tipo de post: eventos
'posts_per_page' => '-1', // Mostrar todos los posts
'orderby' => 'ID', // Ordenar por ID
'order' => 'ASC', // Orden ascendente
'post_parent' => $postID, // ID del post padre
);
$children = new WP_Query($args); // Consulta para obtener posts hijos
$parent[] = get_post($postID); // Obtener el post padre
$family = array_merge($parent, $children->get_posts()); // Combinar padre e hijos en un array
Esto parece funcionar. ¿Comentarios?

Cierto, pero no será tan eficiente como la respuesta aceptada. El enfoque de la respuesta aceptada solo requiere una consulta, mientras que tu enfoque resultará en dos consultas ejecutadas (si no están ya en caché).

¡Creo que tu respuesta es mucho mejor! ¡Mucho más simple/legible! ¿Y para ahorrar qué... 1 consulta? Entre las cientos que hay en el contexto de WordPress... y ya ha sido cacheada 30 veces antes de llegar a tu código...
Además, la consulta que ahorras es un simple WHERE ID=123
... 0.0001s

Usar global $wpdb
combinado con [get_results()][1]
también es una opción. En términos de rendimiento, creo que esta es la mejor solución ya que solo ejecuta una consulta.
Aquí está mi código final.
<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>

Uno pensaría que ese sería el caso, sin embargo, WP_Query
internamente almacenará en caché los resultados de las consultas, por lo que, por ejemplo, una llamada a get_children()
o get_post_ancestors()
además de WP_Query
no debería incluir ningún impacto adicional en el rendimiento.

Si te entiendo correctamente, quieres obtener los IDs tanto de la página padre como de cualquier página hija subsecuente. WordPress tiene funciones que recuperan los hijos de las páginas, como esta:
https://codex.wordpress.org/Function_Reference/get_page_children
Según mi entendimiento, ya que estás realizando un WP_Query, ya estás obteniendo los IDs de las páginas padre, así que todo lo que necesitarías hacer es pasar el ID relevante a la función mencionada arriba para obtener lo que deseas.
Nota: Debo señalar que esta función no realiza una consulta a la base de datos, por lo que es mejor en términos de rendimiento ya que solo estás haciendo una consulta a la BD.

Entiendo por qué mencionas esta función, pero esta respuesta es más un comentario que una respuesta. De todos modos, no encuentro una razón por la que esta función podría ser útil, ya que WP_Query
ya te permite especificar una lista de publicaciones y sus hijos con el argumento: post_parent
. Entonces, ¿por qué esta función no está obsoleta?

Eso no es lo que quiero lograr, no quiero usar wp_list_pages
porque el resultado no cumple con mis necesidades.

Gracias por la actualización, desafortunadamente esto no incluye una explicación sobre cómo incluir la publicación padre.

puedes consultar https://codex.wordpress.org/Function_Reference/get_pages para una explicación detallada
