Tipo de publicación personalizada, WP_Query y 'orderby'

30 ago 2012, 13:31:32
Vistas: 18.9K
Votos: 4

Tengo un tipo de publicación personalizada con la siguiente configuración:

$supports = array(
    'title'
    , 'editor'
    , 'thumbnail'
    , 'revisions'
    , 'page-attributes'
);

$args = array(
  'hierarchical' => true
  , 'supports' => $supports
  [...]
);

register_post_type('myType', $args);

Me gustaría mostrar todas las publicaciones ordenadas como en el área de administración de WordPress (la sangría es para legibilidad):

1, 
2, 
3, 
   1, (padre 3)
   2, (padre 3)
4

Para ello intenté la siguiente consulta con el tipo de orden establecido en 'menu_order':

$loop = new WP_Query( array(
       'post_type' => 'myType'
     , 'posts_per_page' => 50
     , 'orderby' => 'menu_order'
     , 'order' => 'ASC'
));

Desafortunadamente, todas las publicaciones se ordenan por menu_order, pero exclusivamente por menu_order, ignorando la relación padre (post_parent). Así obtengo algo como esto:

1,
1, (padre 3)
2, 
2, (padre 3)
3,
4

Cambiar la consulta a 'orderby' => 'parent menu_order' produce lo siguiente:

1,
2, 
3,
4
1, (padre 3)
2, (padre 3)

En general, parece que todo funciona como está diseñado y el valor orderby se traduce directamente al 'Order By' SQL correspondiente.

Pregunta

¿Cuál es la forma más fácil de obtener el orden deseado?

SQL

Supongo que esta es la consulta SQL principal que WordPress crea:

SELECT SQL_CALC_FOUND_ROWS wp_2_posts.ID 
FROM wp_2_posts 
WHERE 1=1 AND wp_2_posts.post_type = 'inhalt' AND (wp_2_posts.post_status = 'publish' OR wp_2_posts.post_status = 'private') 
ORDER BY wp_2_posts.post_parent, wp_2_posts.menu_order ASC LIMIT 0, 50

que luego se complementa con:

SELECT wp_2_posts.* 
FROM wp_2_posts 
WHERE ID IN (40,42,44,46,48,50,52,54,56,58,60,76,62,65,69,71,74)

SELECT post_id, meta_key, meta_value 
FROM wp_2_postmeta 
WHERE post_id IN (40,42,44,46,48,50,52,54,56,58,60,62,65,74,69,71,76)

Solución alternativa

Una solución alternativa conocida, pero no una respuesta, es asignar a todas las publicaciones valores de orden más altos y "espaciados", como:

100,
200, 
300,
   310,
   320,
400
0
Todas las respuestas a la pregunta 2
8

Consulta el codex para otras opciones, pero parece que en este caso querrías usar 'parent' como orden principal.

$loop = new WP_Query( array(
       'post_type' => 'myType'
     , 'posts_per_page' => 50
     , 'orderby' => 'parent menu_order'
     , 'order' => 'ASC'
));

Esto ordenará principalmente por el elemento padre, con un orden secundario según el menú. Esto debería darte el resultado deseado.

30 ago 2012 16:27:25
Comentarios

Gracias Eric, pero desafortunadamente esto lleva a 1, 2, 3, 4, 1(padre 3), 2 (padre 3).

SunnyRed SunnyRed
30 ago 2012 16:42:11

Hmm, extraño. ¿Prueba intercambiando los dos? ¿O usando solo parent? No probé esto, solo siguiendo el codex.

Eric Holmes Eric Holmes
30 ago 2012 17:39:07

+1 Finalmente alguien que descubrió que un espacio permite múltiples argumentos.

kaiser kaiser
30 ago 2012 19:57:10

@SunnyRed ¿Podrías publicar la cadena de consulta exacta que resulta de eso? (Pista: Debug Bar Plugin + Extensiones).

kaiser kaiser
30 ago 2012 19:57:38

Gracias Kaiser. Tu sugerencia de la barra de depuración ya hizo que valiera la pena hacer mi pregunta. He añadido el SQL - espero que sea el correcto, ya que hay bastante a primera vista.

SunnyRed SunnyRed
30 ago 2012 20:54:46

Por lo que parece en la sentencia SQL, el orden debería cambiarse a: 'orderby' => 'parent menu_order', ya que la sentencia SQL los está intercambiando.

Eric Holmes Eric Holmes
30 ago 2012 21:24:49

Hola, Erik. Lo siento, esto fue porque intenté diferentes enfoques. WP y el orden SQL están sincronizados.

SunnyRed SunnyRed
31 ago 2012 12:27:33

La razón de este comportamiento es porque el valor de 'parent' para las categorías de nivel superior será 0, y el orden es por lo tanto correcto, ya que todas las entradas cuyo padre es '0' aparecerán primero.

Bendoh Bendoh
5 sept 2012 04:38:28
Mostrar los 3 comentarios restantes
1

Hasta donde sé, no hay una solución alternativa a nivel de base de datos para esto. Este es un problema con el que me encuentro con cierta frecuencia, que es cuando necesitas convertir una lista con referencias de estructura en un array ordenado donde los elementos hijos aparecen inmediatamente después de sus padres. Esto se puede lograr en PHP, pero aunque esta solución es bastante compacta, no es exactamente sencilla.

La siguiente solución agrega un filtro al hook the_posts, que estructura y luego aplana el conjunto de resultados con una función recursiva.

// Agrega los posts hijos de cada nivel a la lista de resultados, en orden
function recursively_flatten_list( $list, &$result ) {
    foreach( $list as $node ) {
        $result[] = $node['post'];
        if( isset( $node['children'] ) )
            recursively_flatten_list( $node['children'], $result );
    }
}

function my_sort_posts( $posts, $query ) {
    // No hacer nada fuera del área de administración. Solo operar en la consulta principal. Solo operar en consultas de páginas.
    if( is_admin() || !$query->is_main_query() || $query->get( 'post_type' ) != 'page' )
        return;

    $refs = $list = array();
    // Crea estructura jerárquica en una sola pasada.
    // Gracias nuevamente a Nate Weiner:
    // http://blog.ideashower.com/post/15147134343/create-a-parent-child-array-structure-in-one-pass
    foreach( $posts as $post ) {
        $thisref = &$refs[$post->ID];

        $thisref['post'] = $post;

        if( $post->post_parent == 0)
            $list[$post->ID] = &$thisref;
        else
            $refs[$post->post_parent]['children'][$post->ID] = &$thisref;
    }

    // Crea una única lista ordenada
    $result = array();
    recursively_flatten_list( $list, $result );

    return $result;
}
add_filter( 'the_posts', 'my_sort_posts', 10, 2 );

He probado esta solución y es lo suficientemente genérica para jerarquías de páginas arbitrarias.

Este código asume que los posts ya están ordenados por menu_order. Si vas a usar esta solución, asegúrate de cambiar el parámetro orderby a simplemente "menu_order" donde hagas la llamada a new WP_Query.

5 sept 2012 06:37:32
Comentarios

¡Genial! Tuve que modificarlo a $loop->posts = my_sort_posts($loop->posts); sin agregar el filtro, pero de esta manera funciona perfectamente. ¡Gracias!

SunnyRed SunnyRed
7 sept 2012 15:18:39