¿Cómo arreglar la paginación para bucles personalizados?

28 oct 2013, 21:00:53
Vistas: 136K
Votos: 144

He agregado una consulta personalizada/secundaria a un archivo de plantilla/plantilla de página personalizada; ¿cómo puedo hacer que WordPress use mi consulta personalizada para la paginación, en lugar de usar la paginación del bucle de la consulta principal?

Anexo

He modificado la consulta del bucle principal mediante query_posts(). ¿Por qué no funciona la paginación y cómo puedo arreglarla?

1
Comentarios

También, lee este tutorial: https://devnote.in/wordpress-paginate_links-how-to-use-it Será de gran ayuda.

Fefar Ravi Fefar Ravi
5 oct 2021 19:34:53
Todas las respuestas a la pregunta 5
11
247

El Problema

Por defecto, en cualquier contexto, WordPress utiliza la consulta principal para determinar la paginación. El objeto de la consulta principal se almacena en la variable global $wp_query, que también se utiliza para mostrar el bucle de la consulta principal:

if ( have_posts() ) : while ( have_posts() ) : the_post();

Cuando utilizas una consulta personalizada, creas un objeto de consulta completamente separado:

$custom_query = new WP_Query( $custom_query_args );

Y esa consulta se muestra mediante un bucle completamente separado:

if ( $custom_query->have_posts() ) : 
    while ( $custom_query->have_posts() ) : 
        $custom_query->the_post();

Pero las etiquetas de plantilla de paginación, incluyendo previous_posts_link(), next_posts_link(), posts_nav_link() y paginate_links(), basan su salida en el objeto de consulta principal, $wp_query. Esa consulta principal puede o no estar paginada. Si el contexto actual es una plantilla de página personalizada, por ejemplo, el objeto principal $wp_query consistirá en solo una sola publicación - la del ID de la página a la que está asignada la plantilla de página personalizada.

Si el contexto actual es un índice de archivo de algún tipo, la consulta principal $wp_query puede consistir en suficientes publicaciones para causar paginación, lo que lleva a la siguiente parte del problema: para el objeto principal $wp_query, WordPress pasará un parámetro paged a la consulta, basado en la variable de consulta URL paged. Cuando se recupera la consulta, ese parámetro paged se usará para determinar qué conjunto de publicaciones paginadas devolver. Si se hace clic en un enlace de paginación mostrado y se carga la siguiente página, tu consulta personalizada no tendrá forma de saber que la paginación ha cambiado.

La Solución

Pasar el Parámetro Paged Correcto a la Consulta Personalizada

Suponiendo que la consulta personalizada utiliza un array de argumentos:

$custom_query_args = array(
    // Parámetros de consulta personalizada van aquí
);

Necesitarás pasar el parámetro paged correcto al array. Puedes hacerlo obteniendo la variable de consulta URL utilizada para determinar la página actual, a través de get_query_var():

get_query_var( 'paged' );

Luego puedes agregar ese parámetro a tu array de argumentos de consulta personalizada:

$custom_query_args['paged'] = get_query_var( 'paged' ) 
    ? get_query_var( 'paged' ) 
    : 1;

Nota: Si tu página es una página de inicio estática, asegúrate de usar page en lugar de paged, ya que una página de inicio estática utiliza page y no paged. Esto es lo que deberías tener para una página de inicio estática:

$custom_query_args['paged'] = get_query_var( 'page' ) 
    ? get_query_var( 'page' ) 
    : 1;

Ahora, cuando se recupere la consulta personalizada, se devolverá el conjunto correcto de publicaciones paginadas.

Usar el Objeto de Consulta Personalizada para Funciones de Paginación

Para que las funciones de paginación generen la salida correcta - es decir, enlaces anterior/siguiente/página relativos a la consulta personalizada - WordPress necesita ser forzado a reconocer la consulta personalizada. Esto requiere un pequeño "hack": reemplazar el objeto principal $wp_query con el objeto de consulta personalizada, $custom_query:

Modificar el objeto de consulta principal

  1. Haz una copia de seguridad del objeto de consulta principal: $temp_query = $wp_query
  2. Anula el objeto de consulta principal: $wp_query = NULL;
  3. Intercambia la consulta personalizada en el objeto de consulta principal: $wp_query = $custom_query;

    $temp_query = $wp_query;
    $wp_query   = NULL;
    $wp_query   = $custom_query;
    

Este "hack" debe hacerse antes de llamar a cualquier función de paginación

Restablecer el objeto de consulta principal

Una vez que se hayan mostrado las funciones de paginación, restablece el objeto de consulta principal:

$wp_query = NULL;
$wp_query = $temp_query;

Correcciones en las Funciones de Paginación

La función previous_posts_link() funcionará normalmente, independientemente de la paginación. Simplemente determina la página actual y luego muestra el enlace para página - 1. Sin embargo, se requiere una corrección para que next_posts_link() se muestre correctamente. Esto se debe a que next_posts_link() utiliza el parámetro max_num_pages:

<?php next_posts_link( $label , $max_pages ); ?>

Al igual que otros parámetros de consulta, por defecto la función utilizará max_num_pages para el objeto principal $wp_query. Para forzar a next_posts_link() a tener en cuenta el objeto $custom_query, necesitarás pasar max_num_pages a la función. Puedes obtener este valor del objeto $custom_query: $custom_query->max_num_pages:

<?php next_posts_link( 'Publicaciones Antiguas' , $custom_query->max_num_pages ); ?>

Juntando Todo

Lo siguiente es una estructura básica de un bucle de consulta personalizada con funciones de paginación que funcionan correctamente:

// Definir parámetros de consulta personalizada
$custom_query_args = array( /* Parámetros van aquí */ );

// Obtener página actual y agregar al array de parámetros de consulta personalizada
$custom_query_args['paged'] = get_query_var( 'paged' ) ? get_query_var( 'paged' ) : 1;

// Instanciar consulta personalizada
$custom_query = new WP_Query( $custom_query_args );

// Corrección de paginación
$temp_query = $wp_query;
$wp_query   = NULL;
$wp_query   = $custom_query;

// Mostrar bucle de consulta personalizada
if ( $custom_query->have_posts() ) :
    while ( $custom_query->have_posts() ) :
        $custom_query->the_post();
        // Salida del bucle va aquí
    endwhile;
endif;
// Restablecer datos de publicación
wp_reset_postdata();

// Paginación del bucle de consulta personalizada
previous_posts_link( 'Publicaciones Antiguas' );
next_posts_link( 'Publicaciones Nuevas', $custom_query->max_num_pages );

// Restablecer objeto de consulta principal
$wp_query = NULL;
$wp_query = $temp_query;

Adenda: ¿Qué Pasa con query_posts()?

query_posts() para Bucles Secundarios

Si estás usando query_posts() para mostrar un bucle personalizado, en lugar de instanciar un objeto separado para la consulta personalizada mediante WP_Query(), entonces estás _doing_it_wrong(), y te encontrarás con varios problemas (no el menor de los cuales serán problemas de paginación). El primer paso para resolver esos problemas será convertir el uso incorrecto de query_posts() a una llamada adecuada de WP_Query().

Usar query_posts() para Modificar el Bucle Principal

Si solo quieres modificar los parámetros para la consulta del bucle principal - como cambiar las publicaciones por página o excluir una categoría - podrías sentirte tentado a usar query_posts(). Pero aun así no deberías. Cuando usas query_posts(), obligas a WordPress a reemplazar el objeto de consulta principal. (WordPress en realidad hace una segunda consulta y sobrescribe $wp_query). El problema, sin embargo, es que hace este reemplazo demasiado tarde en el proceso como para actualizar la paginación.

La solución es filtrar la consulta principal antes de que se recuperen las publicaciones, mediante el hook pre_get_posts.

En lugar de agregar esto al archivo de plantilla de categoría (category.php):

query_posts( array(
    'posts_per_page' => 5
) );

Agrega lo siguiente a functions.php:

function wpse120407_pre_get_posts( $query ) {
    // Comprobar índice de archivo de categoría
    // y asegurarse de que la consulta es la consulta principal
    // y no una consulta secundaria (como un menú de navegación
    // o salida de widget de publicaciones recientes, etc.
    if ( is_category() && $query->is_main_query() ) {
        // Modificar publicaciones por página
        $query->set( 'posts_per_page', 5 ); 
    }
}
add_action( 'pre_get_posts', 'wpse120407_pre_get_posts' );

En lugar de agregar esto al archivo de plantilla de índice de publicaciones del blog (home.php):

query_posts( array(
    'cat' => '-5'
) );

Agrega lo siguiente a functions.php:

function wpse120407_pre_get_posts( $query ) {
    // Comprobar índice principal de publicaciones del blog
    // y asegurarse de que la consulta es la consulta principal
    // y no una consulta secundaria (como un menú de navegación
    // o salida de widget de publicaciones recientes, etc.
    if ( is_home() && $query->is_main_query() ) {
        // Excluir categoría ID 5
        $query->set( 'category__not_in', array( 5 ) ); 
    }
}
add_action( 'pre_get_posts', 'wpse120407_pre_get_posts' );

De esta manera, WordPress utilizará el objeto $wp_query ya modificado al determinar la paginación, sin necesidad de modificar la plantilla.

Cuándo Usar Qué Función

Investiga esta pregunta y respuesta y esta pregunta y respuesta para entender cómo y cuándo usar WP_Query, pre_get_posts y query_posts().

28 oct 2013 21:00:53
Comentarios

Ojalá las páginas en el codex pudieran ser tan completas.

Pieter Goosen Pieter Goosen
3 abr 2014 15:41:19

¡Chip, me alegraste el día!

tepkenvannkorn tepkenvannkorn
8 sept 2014 11:57:18

¡Chip, siempre me ahorras tanto tiempo! Ojalá Google rankeara mejor tus respuestas (llamado a los de Google) antes de que me volviera loco buscando ;) gracias.

Sagive Sagive
14 sept 2014 18:56:33

Usando tu ejemplo no pude hacer que la paginación funcionara hasta que utilicé un bloque if-else como el que se encuentra a mitad de esta página (en lugar del operador condicional ? :): http://themeforest.net/forums/thread/pagination-on-wordpress-static-page-set-to-front-page/28120, muy extraño. Por lo demás, esta respuesta me enseñó mucho.

P a u l P a u l
8 dic 2014 08:52:43

Excelente respuesta - 1 detalle, estaba teniendo problemas al ejecutar la función de enlace de publicación siguiente/anterior en una llamada ajax - simplemente no funcionaba - después de investigar un poco descubrí que la variable global paged no se estaba actualizando (obviamente algo relacionado con el entorno admin-ajax.php) así que agregué esto: global $paged; $paged = $custom_query_args['paged']; y funcionó :)

rmorse rmorse
20 dic 2014 18:00:12

...No uso la palabra 'héroe' a la ligera, pero eres el héroe más grande en la historia de Estados Unidos. - Lionel Hutz

dgo dgo
5 ene 2015 20:05:26

Ahora tengo paginación para mi loop personalizado, en una página principal, mostrando páginas hijas. Al intentar acceder a la segunda página, mediante http:example.com/main_page/page/2, simplemente me redirige de vuelta a http://example.com/main_page. ¿Alguna idea de por qué ocurre esto?

Mr Pablo Mr Pablo
6 jul 2016 13:39:40

Y recuerda actualizar tu estructura de permalinks (vuelve a guardar la configuración de permalinks)

Dan. Dan.
28 nov 2016 21:31:54

¿Esta solución sigue funcionando en WordPress 4.5+? Cuando sigo estas instrucciones, el resultado muestra un "entradas más recientes" en la página 1, al hacer clic va a la página 2, pero los resultados de la subconsulta siguen siendo los primeros 10 posts.

Slam Slam
19 jul 2018 03:16:04

Estuve peleando con mi paginación por tanto tiempo, hasta que leí esta pequeña joya: Nota: Si tu página es una página frontal estática, asegúrate de usar page en lugar de paged ya que una página frontal estática usa page y no paged. Esto es lo que deberías tener para una página frontal estática

user1676224 user1676224
25 jun 2019 16:06:34

absolutamente perfecto, funciona de maravilla. una de las pocas soluciones a un problema que he visto explicada tan claramente y que realmente funciona :D

djack109 djack109
1 jun 2020 14:50:58
Mostrar los 6 comentarios restantes
0
22

Utilizo este código para un bucle personalizado con paginación:

<?php
if ( get_query_var('paged') ) {
    $paged = get_query_var('paged');
} elseif ( get_query_var('page') ) { // 'page' se usa en lugar de 'paged' en la Página de Inicio Estática
    $paged = get_query_var('page');
} else {
    $paged = 1;
}

$custom_query_args = array(
    'post_type' => 'post', 
    'posts_per_page' => get_option('posts_per_page'),
    'paged' => $paged,
    'post_status' => 'publish',
    'ignore_sticky_posts' => true,
    //'category_name' => 'custom-cat',
    'order' => 'DESC', // 'ASC'
    'orderby' => 'date' // modified | title | name | ID | rand
);
$custom_query = new WP_Query( $custom_query_args );

if ( $custom_query->have_posts() ) :
    while( $custom_query->have_posts() ) : $custom_query->the_post(); ?>

        <article <?php post_class(); ?>>
            <h3><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h3>
            <small><?php the_time('F jS, Y') ?> por <?php the_author_posts_link() ?></small>
            <div><?php the_excerpt(); ?></div>
        </article>

    <?php
    endwhile;
    ?>

    <?php if ($custom_query->max_num_pages > 1) : // paginación personalizada  ?>
        <?php
        $orig_query = $wp_query; // solución para que funcione la paginación
        $wp_query = $custom_query;
        ?>
        <nav class="prev-next-posts">
            <div class="prev-posts-link">
                <?php echo get_next_posts_link( 'Entradas anteriores', $custom_query->max_num_pages ); ?>
            </div>
            <div class="next-posts-link">
                <?php echo get_previous_posts_link( 'Entradas más recientes' ); ?>
            </div>
        </nav>
        <?php
        $wp_query = $orig_query; // solución para que funcione la paginación
        ?>
    <?php endif; ?>

<?php
    wp_reset_postdata(); // reiniciar la consulta 
else:
    echo '<p>'.__('Lo sentimos, no hay publicaciones que coincidan con tus criterios.').'</p>';
endif;
?>

Fuente:

9 nov 2015 18:27:40
1

Increíble como siempre Chip. Como un añadido a esto, considera la situación en la que estás usando una plantilla de página global asignada a una Página para algún "texto introductorio" y está seguido por una subconsulta que quieres que tenga paginación.

Usando paginate_links() como mencionas arriba, con configuraciones mayormente por defecto (y asumiendo que tienes los permalinks bonitos activados), tus enlaces de paginación por defecto serán misitio.ca/slug-de-pagina/page/# lo cual es encantador pero generará errores 404 porque WordPress no reconoce esa estructura de URL particular y en realidad buscará una página hija de "page" que sea hija de "slug-de-pagina".

El truco aquí es insertar una ingeniosa regla de reescritura que solo aplique a ese "slug de página pseudo-archivo" particular que acepte la estructura /page/#/ y la reescriba a una cadena de consulta que WordPress PUEDA entender, específicamente misitio.ca/?pagename=slug-de-pagina&paged=#. Nota pagename y paged no name y page (¡lo cual me causó literalmente HORAS de frustración, motivando esta respuesta aquí!).

Aquí está la regla de redirección:

add_rewrite_rule( "slug-de-pagina/page/([0-9]{1,})/?$", 'index.php?pagename=slug-de-pagina&paged=$matches[1]', "top" );

Como siempre, cuando cambies reglas de reescritura, recuerda limpiar tus permalinks visitando Ajustes > Permalinks en el panel de administración.

Si tienes múltiples páginas que van a comportarse de esta manera (por ejemplo, al trabajar con múltiples tipos de entrada personalizados), quizá quieras evitar crear una nueva regla de reescritura para cada slug de página. Podemos escribir una expresión regular más genérica que funcione para cualquier slug de página que identifiques.

Un enfoque es el siguiente:

function wpse_120407_pseudo_archive_rewrite(){
    // Agrega los slugs de las páginas que usan una Plantilla Global para simular ser una página de "archivo"
    $pseudo_archive_pages = array(
        "todas-las-peliculas",
        "todos-los-actores"
    );

    $slug_clause = implode( "|", $pseudo_archive_pages );
    add_rewrite_rule( "($slug_clause)/page/([0-9]{1,})/?$", 'index.php?pagename=$matches[1]&paged=$matches[2]', "top" );
}
add_action( 'init', 'wpse_120407_pseudo_archive_rewrite' );

Desventajas / Advertencias

Una desventaja de este enfoque que me hace sentir un poco de asco es la codificación fija del slug de la Página. Si un administrador alguna vez cambia el slug de página de esa página pseudo-archivo, estás perdido - la regla de reescritura ya no coincidirá y obtendrás el temido 404.

No estoy seguro de poder pensar en una solución alternativa para este método, pero sería agradable si fuera la plantilla global de página la que de alguna manera activara la regla de reescritura. Algún día quizá actualice esta respuesta si nadie más ha resuelto ese problema particular.

17 dic 2016 02:49:08
Comentarios

Podrías enlazar al guardar la publicación, verificar si la página tiene tu plantilla de archivo bajo la meta clave _wp_page_template, luego agregar otra reescritura y vaciar las reglas.

Milo Milo
17 dic 2016 02:54:39
0

He modificado la consulta del bucle principal mediante query_posts(). ¿Por qué no funciona la paginación y cómo lo soluciono?

La excelente respuesta que creó Chip necesita ser modificada hoy.
Desde hace tiempo tenemos la variable $wp_the_query que debería ser igual al global $wp_query justo después de que se ejecute la consulta principal.

Es por esto que la parte de la respuesta de Chip:

Hackear el objeto de consulta principal

ya no es necesaria. Podemos olvidarnos de esta parte con la creación de la variable temporal.

// Corrección de paginación
$temp_query = $wp_query;
$wp_query   = NULL;
$wp_query   = $custom_query;

Así que ahora podemos llamar:

$wp_query   = $wp_the_query;

o incluso mejor podemos llamar:

wp_reset_query();

Todo lo demás que describió Chip se mantiene. Después de esa parte de reinicio de consulta puedes llamar las funciones de paginación que son f($wp_query), — dependen del global $wp_query.


Para mejorar aún más la mecánica de paginación y dar más libertad a la función query_posts creé esta posible mejora:

https://core.trac.wordpress.org/ticket/39483

13 ene 2017 23:21:24
0
global $wp_query;
        $paged = get_query_var('paged', 1);

    $args = array( 
        'post_type' => '{tu_tipo_de_post}',
        'meta_query' => array('{agrega tu argumento de meta query si es necesario}'),  
        'orderby' => 'modified',
        'order' => 'DESC',
        'posts_per_page' => 20,
        'paged' => $paged 
    );
    $query = new WP_Query($args);

    if($query->have_posts()):
        while ($query->have_posts()) : $query->the_post();
            //agrega tu código aquí
        endwhile;
        wp_reset_query();

        //manejar paginación basada en Query personalizado
        $GLOBALS['wp_query']->max_num_pages = $query->max_num_pages;
        the_posts_pagination(array(
            'mid_size' => 1,
            'prev_text' => __('Página anterior', 'patelextensions'),
            'next_text' => __('Página siguiente', 'patelextensions'),
            'before_page_number' => '<span class="meta-nav screen-reader-text">' . __('Página', 'patelextensions') . ' </span>',
        ));
    else:
    ?>
        <div class="container text-center"><?php echo _d('No se encontraron resultados','30'); ?></div>
    <?php
        endif;
    ?>
6 mar 2018 11:39:57