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


122

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 consulta principal?

Apéndice

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

Respuestas:


215

El problema

Por defecto, en cualquier contexto dado, WordPress usa la consulta principal para determinar la paginación. El objeto de consulta principal se almacena en el $wp_queryglobal, que también se utiliza para generar el bucle de consulta principal:

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

Cuando utiliza una consulta personalizada , crea un objeto de consulta completamente separado:

$custom_query = new WP_Query( $custom_query_args );

Y esa consulta se genera a través de un bucle completamente separado:

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

Pero etiquetas de plantilla de paginación, incluyendo previous_posts_link(), next_posts_link(), posts_nav_link(), y paginate_links(), basan su producción en el principal objeto de consulta , $wp_query. Esa consulta principal puede o no ser paginada. Si el contexto actual es una plantilla de página personalizada, por ejemplo, el $wp_queryobjeto principal consistirá en una sola publicación : la ID de la página a la que se asigna la plantilla de página personalizada.

Si el contexto actual es un índice de archivo de algún tipo, el principal $wp_querypuede consistir en suficientes publicaciones para causar paginación, lo que lleva a la siguiente parte del problema: para el $wp_queryobjeto principal , WordPress pasará un paged parámetro a la consulta, basado en el pagedVariable de consulta de URL. Cuando se recupera la consulta, ese pagedparámetro 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, su consulta personalizada no tendrá forma de saber que la paginación ha cambiado .

La solución

Pasar el parámetro paginado correcto a la consulta personalizada

Suponiendo que la consulta personalizada utiliza una matriz de argumentos:

$custom_query_args = array(
    // Custom query parameters go here
);

Deberá pasar el pagedparámetro correcto a la matriz. Puede hacerlo buscando la variable de consulta de URL utilizada para determinar la página actual, a través de get_query_var():

get_query_var( 'paged' );

Luego puede agregar ese parámetro a su matriz de argumentos de consulta personalizada:

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

Nota: Si su página es una portada estática , asegúrese de usarla en pagelugar de pagedcomo usa una portada estática pagey no paged. Esto es lo que debe tener para una portada estática

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

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

Uso de objetos de consulta personalizados para funciones de paginación

Para que las funciones de paginación produzcan el resultado correcto, es decir, enlaces anteriores / siguientes / de página relacionados con la consulta personalizada, WordPress debe verse obligado a reconocer la consulta personalizada. Esto requiere un poco de un "hack": reemplazar el principal $wp_queryobjeto con el objeto de consulta personalizada, $custom_query:

Hackear el objeto de consulta principal

  1. Copia de seguridad del objeto de consulta principal: $temp_query = $wp_query
  2. Anula el objeto de consulta principal: $wp_query = NULL;
  3. Intercambie 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 han generado las funciones de paginación, restablezca el objeto de consulta principal:

$wp_query = NULL;
$wp_query = $temp_query;

Correcciones de funciones de paginación

La previous_posts_link()función funcionará normalmente, independientemente de la paginación. Simplemente determina la página actual y luego genera el enlace para page - 1. Sin embargo, se requiere una solución para next_posts_link()que salga correctamente. Esto se debe a que next_posts_link()usa el max_num_pagesparámetro:

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

Al igual que con otros parámetros de consulta, por defecto la función se usará max_num_pagespara el $wp_queryobjeto principal . Para forzar next_posts_link()a dar cuenta del $custom_queryobjeto, deberá pasarlo max_num_pagesa la función. Puede obtener este valor del $custom_queryobjeto $custom_query->max_num_pages::

<?php next_posts_link( 'Older Posts' , $custom_query->max_num_pages ); ?>

Poniendolo todo junto

La siguiente es una construcción básica de un bucle de consulta personalizado con funciones de paginación que funcionan correctamente:

// Define custom query parameters
$custom_query_args = array( /* Parameters go here */ );

// Get current page and append to custom query parameters array
$custom_query_args['paged'] = get_query_var( 'paged' ) ? get_query_var( 'paged' ) : 1;

// Instantiate custom query
$custom_query = new WP_Query( $custom_query_args );

// Pagination fix
$temp_query = $wp_query;
$wp_query   = NULL;
$wp_query   = $custom_query;

// Output custom query loop
if ( $custom_query->have_posts() ) :
    while ( $custom_query->have_posts() ) :
        $custom_query->the_post();
        // Loop output goes here
    endwhile;
endif;
// Reset postdata
wp_reset_postdata();

// Custom query loop pagination
previous_posts_link( 'Older Posts' );
next_posts_link( 'Newer Posts', $custom_query->max_num_pages );

// Reset main query object
$wp_query = NULL;
$wp_query = $temp_query;

Anexo: ¿Qué pasa query_posts()?

query_posts() para bucles secundarios

Si está utilizando query_posts()a la salida de un bucle de encargo, en lugar de crear instancias de un objeto separado para la consulta personalizada a través de WP_Query(), a continuación, usted es _doing_it_wrong(), y se encontrará con varios problemas (no al menos, de los cuales serán cuestiones de paginación). El primer paso para resolver esos problemas será convertir el uso inadecuado de query_posts()una WP_Query()llamada adecuada .

Usar query_posts()para modificar el bucle principal

Si simplemente desea modificar los parámetros para la consulta del bucle principal , como cambiar las publicaciones por página o excluir una categoría, puede tener la tentación de usarla query_posts(). Pero aún no deberías. Cuando lo usas query_posts(), obligas a WordPress a reemplazar el objeto de consulta principal. (WordPress en realidad hace una segunda consulta y la sobrescribe $wp_query). Sin embargo, el problema es que hace este reemplazo demasiado tarde en el proceso para actualizar la paginación.

La solución es filtrar la consulta principal antes de recuperar las publicaciones , a través del pre_get_postsgancho.

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

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

Agregue lo siguiente a functions.php:

function wpse120407_pre_get_posts( $query ) {
    // Test for category archive index
    // and ensure that the query is the main query
    // and not a secondary query (such as a nav menu
    // or recent posts widget output, etc.
    if ( is_category() && $query->is_main_query() ) {
        // Modify posts per page
        $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 de blog ( home.php):

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

Agregue lo siguiente a functions.php:

function wpse120407_pre_get_posts( $query ) {
    // Test for main blog posts index
    // and ensure that the query is the main query
    // and not a secondary query (such as a nav menu
    // or recent posts widget output, etc.
    if ( is_home() && $query->is_main_query() ) {
        // Exclude category ID 5
        $query->set( 'category__not_in', array( 5 ) ); 
    }
}
add_action( 'pre_get_posts', 'wpse120407_pre_get_posts' );

De esa manera, WordPress usará el $wp_queryobjeto ya modificado al determinar la paginación, sin necesidad de modificar la plantilla.

Cuándo usar qué función

La investigación esta pregunta y respuesta , y esta pregunta y respuesta para entender cómo y cuándo usar WP_Query, pre_get_postsy query_posts().


31
Las páginas de deseos en el códice podrían ser tan completas.
Pieter Goosen el

Chip, me alegraste el día!
tepkenvannkorn

1
Chip, ¡siempre ahorras tanto tiempo! si solo google clasificara tus respuestas más altas (llamada para googlers) antes de volverme loco buscando;) gracias.
Sagive SEO

Usando su ejemplo, no pude hacer que la paginación funcionara hasta que usé un bloque if-else como se encuentra a mitad de camino (en lugar de?: Condicional) en esta página: themeforest.net/forums/thread/… , muy extraño. De lo contrario, esta respuesta me enseñó mucho.
P aul

2
Gran respuesta: 1 cosa, estaba teniendo problemas para ejecutar la función de enlace de publicación siguiente / anterior en una llamada ajax, simplemente no tomaría, después de una pagedbúsqueda rápida descubrí que el global no se estaba actualizando (obviamente algo que tiene que ver con admin- ajax.php) así que agregué esto: global $paged; $paged = $custom_query_args['paged']; y funcionó :)
acSlater

21

Yo uso este código para bucle personalizado con paginación:

<?php
if ( get_query_var('paged') ) {
    $paged = get_query_var('paged');
} elseif ( get_query_var('page') ) { // 'page' is used instead of 'paged' on Static Front Page
    $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') ?> by <?php the_author_posts_link() ?></small>
            <div><?php the_excerpt(); ?></div>
        </article>

    <?php
    endwhile;
    ?>

    <?php if ($custom_query->max_num_pages > 1) : // custom pagination  ?>
        <?php
        $orig_query = $wp_query; // fix for pagination to work
        $wp_query = $custom_query;
        ?>
        <nav class="prev-next-posts">
            <div class="prev-posts-link">
                <?php echo get_next_posts_link( 'Older Entries', $custom_query->max_num_pages ); ?>
            </div>
            <div class="next-posts-link">
                <?php echo get_previous_posts_link( 'Newer Entries' ); ?>
            </div>
        </nav>
        <?php
        $wp_query = $orig_query; // fix for pagination to work
        ?>
    <?php endif; ?>

<?php
    wp_reset_postdata(); // reset the query 
else:
    echo '<p>'.__('Sorry, no posts matched your criteria.').'</p>';
endif;
?>

Fuente:


1
Otro buen tutorial con una variante de esta respuesta: callmenick.com/post/custom-wordpress-loop-with-pagination
mrwweb

5

Impresionante como siempre Chip. Como anexo a esto, considere la situación en la que está utilizando una plantilla de página global adjunta a una página para algún "texto de introducción" y le sigue una subconsulta que desea que se pagine.

Usando paginate_links () como mencionó anteriormente, con la mayoría de los valores predeterminados (y suponiendo que tenga los enlaces permanentes activados), sus enlaces de paginación estarán predeterminados, lo mysite.ca/page-slug/page/#cual es encantador, pero arrojará 404errores porque WordPress no conoce esa estructura URL particular y realmente busque una página secundaria de "página" que sea secundaria de "page-slug".

El truco aquí es insertar una ingeniosa regla de reescritura que solo se aplica a esa ficha de página de "pseudo archivo" particular que acepta la /page/#/estructura y la reescribe en una cadena de consulta que WordPress PUEDE entender, a saber mysite.ca/?pagename=page-slug&paged=#. Nota pagenamey pagedno namey page(¡lo que me causó literalmente HORAS de dolor, motivando esta respuesta aquí!).

Aquí está la regla de redireccionamiento:

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

Como siempre, al cambiar las reglas de reescritura, recuerde eliminar sus enlaces permanentes visitando Configuración> Enlaces permanentes en el back-end de Admin.

Si tiene varias páginas que se comportarán de esta manera (por ejemplo, cuando se trata de múltiples tipos de publicaciones personalizadas), es posible que desee evitar crear una nueva regla de reescritura para cada ficha de página. Podemos escribir una expresión regular más genérica que funcione para cualquier babosa de página que identifique.

Un enfoque está abajo:

function wpse_120407_pseudo_archive_rewrite(){
    // Add the slugs of the pages that are using a Global Template to simulate being an "archive" page
    $pseudo_archive_pages = array(
        "all-movies",
        "all-actors"
    );

    $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 vomitar un poco en mi boca es la codificación dura de la babosa de la página. Si un administrador alguna vez cambia el slug de la página de esa página de pseudoarchivo, eres tostado: 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 bueno que fuera la plantilla de página global la que activara la regla de reescritura. Algún día podría volver a visitar esta respuesta si nadie más ha roto esa tuerca en particular.


1
Puede enganchar la publicación guardada, verificar si la página tiene su plantilla de archivo debajo de la meta clave _wp_page_template, luego agregar otras reglas de reescritura y vaciado.
Milo

2

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

Gran respuesta Chip creado debe modificarse hoy.
Durante algún tiempo tenemos una $wp_the_queryvariable que debería ser igual a la $wp_queryglobal justo después de que se ejecute la consulta principal.

Es por eso que esta es la parte de la respuesta del Chip:

Hackear el objeto de consulta principal

Ya no es necesario. Podemos olvidar esta parte con la creación de la variable temporal.

// Pagination fix
$temp_query = $wp_query;
$wp_query   = NULL;
$wp_query   = $custom_query;

Entonces ahora podemos llamar:

$wp_query   = $wp_the_query;

o incluso mejor podemos llamar:

wp_reset_query();

Todo lo demás que Chip describió se queda. Después de esa parte de consulta-restablecimiento, puede llamar a las funciones de paginación que son f($wp_query), dependen de $wp_queryglobal.


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

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


1
global $wp_query;
        $paged = get_query_var('paged', 1);

    $args = array( 
        'post_type' => '{your_post_type_name}',
        'meta_query' => array('{add your meta query argument if need}'),  
        '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();
            //add your code here
        endwhile;
        wp_reset_query();

        //manage pagination based on custom Query.
        $GLOBALS['wp_query']->max_num_pages = $query->max_num_pages;
        the_posts_pagination(array(
            'mid_size' => 1,
            'prev_text' => __('Previous page', 'patelextensions'),
            'next_text' => __('Next page', 'patelextensions'),
            'before_page_number' => '<span class="meta-nav screen-reader-text">' . __('Page', 'patelextensions') . ' </span>',
        ));
    else:
    ?>
        <div class="container text-center"><?php echo _d('Result not found','30'); ?></div>
    <?php
        endif;
    ?>