¿Cómo obtener una publicación padre con sus hijos usando WP_Query?

23 mar 2016, 12:57:14
Vistas: 26.7K
Votos: 8

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.

0
Todas las respuestas a la pregunta 6
0

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.

23 mar 2016 14:10:26
7

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.

23 mar 2016 20:18:49
Comentarios

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

luukvhoudt luukvhoudt
30 mar 2016 12:22:31

He vuelto a probar mi código y no puedo replicar tu problema

Pieter Goosen Pieter Goosen
30 mar 2016 14:58:26

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.

luukvhoudt luukvhoudt
30 mar 2016 15:13:25

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

Pieter Goosen Pieter Goosen
30 mar 2016 15:54:17

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?

luukvhoudt luukvhoudt
30 mar 2016 16:14:33

¿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.

Pieter Goosen Pieter Goosen
30 mar 2016 16:42:38

Lamentablemente no puedo ayudarte aquí porque, como dije, todo funciona correctamente en mi entorno. Comienza desactivando los plugins, vuelca todas tus variables y asegúrate de que son lo que deberían ser, y cambia a un tema incluido por defecto

Pieter Goosen Pieter Goosen
30 mar 2016 16:44:07
Mostrar los 2 comentarios restantes
2
    $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?

22 oct 2017 19:31:35
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é).

luukvhoudt luukvhoudt
23 oct 2017 14:21:23

¡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

Antony Gibbs Antony Gibbs
28 abr 2022 09:49:02
1

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>
23 mar 2016 14:45:45
Comentarios

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.

Adam Adam
23 mar 2016 14:56:22
1

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.

23 mar 2016 21:11:04
Comentarios

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?

luukvhoudt luukvhoudt
23 mar 2016 21:37:12
5
-2

Puedes usar get_pages() con el parámetro child_of como se muestra a continuación:

<?php
   get_pages( array(
     'child_of' => $parent_page_id;
   ) );
?>
23 mar 2016 13:45:41
Comentarios

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

luukvhoudt luukvhoudt
23 mar 2016 13:48:30

Mira mi respuesta actualizada. Espero que esto te ayude.

Lalji Nakum Lalji Nakum
23 mar 2016 13:54:49

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

luukvhoudt luukvhoudt
23 mar 2016 14:00:39

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

Lalji Nakum Lalji Nakum
23 mar 2016 14:06:38

Según la documentación: El parámetro child_of no se aplica a la consulta SQL para páginas. Se aplica a los resultados de la consulta. Además, no devolverá el elemento padre.

luukvhoudt luukvhoudt
23 oct 2017 14:17:34