Cómo paginar WP_Query en un loop secundario WordPress
Aunque no soy partidario de paginar loops secundarios, salvo casos muy excepcionales y luego explicaré por qué, es una pregunta tan frecuente que le voy a dedicar un post a explicar como se hace. Vamos a ello.
Para crear un listado paginado necesitamos:
- el parámetro
post_per_page
deWP_Query
para establecer el número de posts que se muestran en cada página. - el parámetro
paged
para establecer el número de la página actual (opage
si estamos un static front page). - las funciones
previous_posts_link()
/next_posts_link()
para mostrar un enlace a la página anterior/siguiente o la funciónpaginate_links()
para mostrar un set enlaces a la primera/última página, a la siguiente/anterior y a un conjunto de páginas intermedias.
Imaginemos que tenemos este loop secundario para obtener «eventos» próximos según una fecha almacenada en un meta field:
$args = array(
'post_type' => 'event',
'meta_key' => 'event_start_date',
'order' => 'ASC',
'orderby' => 'meta_value',
'meta_query' => array(
array(
'key' => 'event_start_date',
'value' => date( "Y-m-d" ),
'compare' => '>=',
'type' => 'DATE'
)
)
);
$events_query = new WP_Query( $args );
if( $events_query->have_posts() ) { ?>
<h1><?php _e( 'Upcoming events', 'textdomain' ); ?></h1>
<?php while( $events_query->have_posts() ) {
$events_query->the_post(); ?>
<h2><?php the_title(); ?></h2>
<?php }
wp_reset_postdata();
} else {
_e( 'No results found', 'textdomain' );
}
Si queremos paginarlo:
$current_page = get_query_var( 'paged' ) ? get_query_var( 'paged' ): 1;
$args = array(
'post_type' => 'event',
'meta_key' => 'event_start_date',
'order' => 'ASC',
'orderby' => 'meta_value',
'meta_query' => array(
array(
'key' => 'event_start_date',
'value' => date( "Y-m-d" ),
'compare' => '>=',
'type' => 'DATE'
)
),
// Paginación
'posts_per_page' => get_option('posts_per_page'),
'paged' => $current_page,
);
$events_query = new WP_Query( $args );
if( $events_query->have_posts() ) { ?>
<h1><?php _e( 'Upcoming events', 'textdomain' ); ?></h1>
<?php while( $events_query->have_posts() ) {
$events_query->the_post(); ?>
<h2><?php the_title(); ?></h2>
<?php }
echo paginate_links( array(
'current' => $current_page,
'total' => $events_query->max_num_pages
) );
wp_reset_postdata();
} else {
_e( 'No results found', 'textdomain' );
}
¿Qué hemos hecho?
- Hemos obtenido la página actual con
get_query_var( 'paged' )
y su valor lo hemos puesto como argumentopaged
en nuestroWP_Query
. (Recuerda utilizarpage
si estás en un static front page). - Hemos establecido el número de posts por página en el argumento
post_per_page
. Lo hemos puesto según el valor configurado para todo el sitio en Ajustes->Lectura pero puedes poner el número que quieras. - Finalmente hemos utilizado la función
paginate_links()
y le hemos pasado los valores de la página actual y, aquí viene lo más importante, el número de páginas de nuestro query.
El punto tres es dónde más confusión suele haber. De forma predeterminada la función paginate_links()
utiliza los datos del query principal pero nosotros estamos paginando un query secundario, así que tenemos que pasar los datos de este query secundario a la función:
$current_page = get_query_var( 'paged' ) ? get_query_var( 'paged' ): 1;
$args = array(
// ....
'posts_per_page' => get_option('posts_per_page'),
'paged' => $current_page
);
$mi_query = new WP_Query( $args );
// ...
echo paginate_links( array(
'current' => $current_page,
'total' => $mi_query->max_num_pages
) );
Con previous_posts_link()
/next_posts_link()
ocurre algo similar. previous_posts_link()
no necesita nada adicional pero next_posts_link()
necesita conocer el número de páginas totales del query:
previous_posts_link( __( 'Previous page', 'textdomain' ) );
next_posts_link( __( 'Next page', 'textdomain' ), $mi_query->max_num_pages );
¿Cuándo paginar un loop secundario?
Prácticamente nunca. Cuándo se crean listados paginados suelen ir en el contenido principal de la página y si lo piensas un segundo te darás cuenta que «contenido principal» y «query secundario» chocan, son contradictorios.
Si te ves en la necesidad de modificar el contenido principal en cualquier parte de WordPress en la que intervengan posts de cualquier tipo, utiliza el action pre_get_posts
para modificar el query principal según tus necesidades. Y como se trabaja con el query principal, las funciones de paginación no necesitan ninguna atención especial.
add_action( 'pre_get_posts', 'cyb_set_upcoming_events_query' );
function cyb_set_upcoming_events_query( $query ) {
if ( $query->is_main_query() && ! is_admin() && alguna_otra_condicion_que_necesites() ) {
$meta_query = array(
array(
'key' => 'event_start_date',
'value' => date( "Y-m-d" ),
'compare' => '>=',
'type' => 'DATE'
)
);
$query->set( 'post_type', 'event' );
$query->set( 'meta_key', 'event_start_date' );
$query->set( 'order', 'ASC' );
$query->set( 'orderby', 'meta_value' );
$query->set( 'meta_query', $meta_query );
}
}
Además, al utilizar el action pre_get_posts
le decimos a WordPress la solicitud que debe hacer a la base de datos desde el principio. De lo contrario, WordPress hará la solicitud que tenga que hacer (por ejemplo para obtener una página) y luego descartamos los datos de ese query para hacer nuestro query secundario y ponerlo como contenido principal. Hacemos queries extra sin necesidad.
Por estos motivos, si el query secundario es el contenido principal, modifica el query principal y olvídate del query secundario. Parece obvio, ¿verdad?