Como corrigir a paginação para loops personalizados?


122

Adicionei uma consulta personalizada / secundária a um arquivo de modelo / modelo de página personalizada; como posso fazer o WordPress usar minha consulta personalizada para paginação, em vez de usar a paginação do loop de consulta principal?

Termo aditivo

Eu modifiquei a consulta de loop principal via query_posts(). Por que a paginação não está funcionando e como faço para corrigi-la?

Respostas:


215

O problema

Por padrão, em qualquer contexto, o WordPress usa a consulta principal para determinar a paginação. O objeto de consulta principal é armazenado no $wp_queryglobal, que também é usado para gerar o loop de consulta principal:

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

Ao usar uma consulta personalizada , você cria um objeto de consulta totalmente separado:

$custom_query = new WP_Query( $custom_query_args );

E essa consulta é emitida através de um loop totalmente separado:

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

Mas template tags pagination, incluindo previous_posts_link(), next_posts_link(), posts_nav_link(), e paginate_links(), baseiam a sua saída no objeto de consulta principal , $wp_query. Essa consulta principal pode ou não ser paginada. Se o contexto atual for um modelo de página personalizado, por exemplo, o $wp_queryobjeto principal consistirá em apenas uma única postagem - a do ID da página à qual o modelo de página personalizado está atribuído.

Se o contexto atual for um índice de arquivo de algum tipo, o principal $wp_querypoderá consistir em postagens suficientes para causar paginação, o que leva à próxima parte do problema: para o $wp_queryobjeto principal , o WordPress passará um paged parâmetro para a consulta, com base no pagedVariável de consulta de URL. Quando a consulta é buscada, esse pagedparâmetro será usado para determinar qual conjunto de postagens paginadas retornar. Se um link de paginação exibido for clicado e a próxima página carregada, sua consulta personalizada não terá como saber que a paginação foi alterada .

A solução

Passando o parâmetro paginado correto para a consulta personalizada

Supondo que a consulta personalizada use uma matriz args:

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

Você precisará passar o pagedparâmetro correto para a matriz. Você pode fazer isso buscando a variável de consulta da URL usada para determinar a página atual, via get_query_var():

get_query_var( 'paged' );

Em seguida, você pode anexar esse parâmetro à sua matriz de argumentos de consulta personalizada:

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

Nota: Se a sua página for uma página estática , certifique-se de usá-la, e pagenão pagedcomo a página inicial estática pagee não paged. Isto é o que você deve ter para uma primeira página estática

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

Agora, quando a consulta personalizada for buscada, o conjunto correto de postagens paginadas será retornado.

Usando objeto de consulta personalizado para funções de paginação

Para que as funções de paginação produzam a saída correta - por exemplo, links anterior / seguinte / página relativos à consulta personalizada - o WordPress precisa ser forçado a reconhecer a consulta personalizada. Isso requer um pouco de um "hack": substituindo o principal $wp_queryobjeto com o objeto de consulta personalizada, $custom_query:

Hackear o objeto de consulta principal

  1. Faça backup do objeto de consulta principal: $temp_query = $wp_query
  2. Anule o objeto de consulta principal: $wp_query = NULL;
  3. Troque a consulta personalizada no objeto de consulta principal: $wp_query = $custom_query;

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

Este "hack" deve ser feito antes de chamar qualquer função de paginação

Redefinir o objeto de consulta principal

Após a saída das funções de paginação, redefina o objeto de consulta principal:

$wp_query = NULL;
$wp_query = $temp_query;

Correções da função de paginação

A previous_posts_link()função funcionará normalmente, independentemente da paginação. Apenas determina a página atual e, em seguida, gera o link para page - 1. No entanto, é necessária uma correção para next_posts_link()a saída correta. Isso ocorre porque next_posts_link()usa o max_num_pagesparâmetro:

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

Como com outros parâmetros de consulta, por padrão, a função será usada max_num_pagespara o $wp_queryobjeto principal . Para forçar next_posts_link()a contabilizar o $custom_queryobjeto, você precisará passar o max_num_pagespara a função. Você pode buscar esse valor no $custom_queryobjeto $custom_query->max_num_pages:

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

Juntando tudo

A seguir, é apresentada uma construção básica de um loop de consulta personalizado com funções de paginação funcionando corretamente:

// 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;

Adendo: Sobre o quê query_posts()?

query_posts() para loops secundários

Se você estiver usando query_posts()para gerar um loop personalizado, em vez de instanciar um objeto separado para a consulta personalizada por meio de WP_Query(), então você estará _doing_it_wrong()e terá vários problemas ( dos quais o menor será problemas de paginação). O primeiro passo para resolver esses problemas será converter o uso inadequado de query_posts()em uma WP_Query()chamada adequada .

Usando query_posts()para modificar o loop principal

Se você deseja apenas modificar os parâmetros para a consulta de loop principal - como alterar as postagens por página ou excluir uma categoria -, você pode se sentir tentado a usá-lo query_posts(). Mas você ainda não deveria. Ao usar query_posts(), você força o WordPress a substituir o objeto de consulta principal. (O WordPress realmente faz uma segunda consulta e sobrescreve $wp_query.) O problema, porém, é que ele faz essa substituição tarde demais no processo para atualizar a paginação.

A solução é filtrar a consulta principal antes que as postagens sejam buscadas , através do pre_get_postsgancho.

Em vez de adicionar isso ao arquivo de modelo de categoria ( category.php):

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

Adicione o seguinte 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' );

Em vez de adicionar isso ao arquivo de modelo de índice de postagens do blog ( home.php):

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

Adicione o seguinte 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' );

Dessa forma, o WordPress usará o $wp_queryobjeto já modificado ao determinar a paginação, sem necessidade de modificação de modelo.

Quando usar qual função

Pesquisa esta pergunta e resposta e esta pergunta e resposta para compreender como e quando usar WP_Query, pre_get_postse query_posts().


31
As páginas de desejo no códice podem ser tão completas.
Pieter Goosen

Chip, você fez o meu dia!
Tepkenvannkorn

1
Chip, você sempre economiza tanto tempo! se apenas o google classificasse suas respostas mais altas (texto explicativo para os usuários do google) antes de eu enlouquecer a pesquisa;) obrigado.
Sagive SEO

Usando seu exemplo, não consegui fazer a paginação funcionar até que eu usei um bloco if-else como encontrado no meio do caminho (em vez de?: Condicional) nesta página: themeforest.net/forums/thread/… , muito estranho. Caso contrário, essa resposta me ensinou muito.
P aul

2
Ótima resposta - uma coisa, eu estava tendo problemas para executar a função de link de postagem seguinte / anterior em uma chamada ajax - isso não seria necessário - depois de uma rápida pesquisa, descobri que o global pagednão estava sendo atualizado (obv algo a ver com o administrador- (ajax.php)), então eu adicionei isso: global $paged; $paged = $custom_query_args['paged']; e funcionou :)
acSlater

21

Eu uso esse código para loop personalizado com paginação:

<?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;
?>

Fonte:


1
Outro bom tutorial com uma variante desta resposta: callmenick.com/post/custom-wordpress-loop-with-pagination
mrwweb

5

Incrível como sempre Chip. Como um adendo, considere a situação em que você está usando um modelo de página global anexado a uma Página para algum "texto de introdução" e é seguido por uma subconsulta que você deseja que seja paginada.

Usando paginate_links () como você mencionou acima, com a maioria dos padrões (e supondo que você tenha permalinks bastante ativados), seus links de paginação serão padronizados, o mysite.ca/page-slug/page/#que é adorável, mas gerará 404erros porque o WordPress não conhece essa estrutura de URL específica e realmente procure uma página filha de "página" que seja filha de "página-lesma".

O truque aqui é inserir uma regra de reescrita bacana que se aplique apenas àquela lesma de página "pseudo archive page" que aceita a /page/#/estrutura e a reescreve em uma string de consulta que o WordPress PODE entender, a saber mysite.ca/?pagename=page-slug&paged=#. Note pagenamee pagednão namee page(o que me causou literalmente HORAS de tristeza, motivando esta resposta aqui!).

Aqui está a regra de redirecionamento:

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

Como sempre, ao alterar as regras de reescrita, lembre-se de liberar seus links permanentes visitando Configurações> Links permanentes no back-end do administrador.

Se você tiver várias páginas que se comportarão dessa maneira (por exemplo, ao lidar com vários tipos de postagem personalizados), convém evitar a criação de uma nova regra de reescrita para cada slug de página. Podemos escrever uma expressão regular mais genérica que funcione para qualquer página lesada que você identificar.

Uma abordagem está abaixo:

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' );

Desvantagens / Advertências

Uma desvantagem dessa abordagem que me faz vomitar um pouco na boca é a codificação da lesma da página. Se um administrador alterar a lesma de página dessa página de pseudo-arquivo, você está brindando - a regra de reescrita não será mais correspondente e você obterá o temido 404.

Não sei se consigo pensar em uma solução alternativa para esse método, mas seria bom se fosse o modelo de página global que de alguma forma acionou a regra de reescrita. Algum dia, posso revisitar essa resposta se ninguém mais tiver quebrado essa noz em particular.


1
Você pode conectar o save post, verificar se a página tem seu modelo de arquivo morto sob a meta-chave _wp_page_templatee adicionar outras regras de reescrita e liberação.
Milo

2

Eu modifiquei a consulta de loop principal via query_posts(). Por que a paginação não está funcionando e como faço para corrigi-la?

Ótima resposta que o Chip criado precisa ser modificado hoje.
Por algum tempo, temos $wp_the_queryvariáveis ​​que devem ser iguais à $wp_queryglobal logo após a execução da consulta principal.

É por isso que esta é a parte da resposta do Chip:

Hackear o objeto de consulta principal

não é mais necessário. Podemos esquecer esta parte ao criar a variável temporária.

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

Então agora podemos ligar para:

$wp_query   = $wp_the_query;

ou melhor ainda, podemos chamar:

wp_reset_query();

Tudo o que Chip descreveu permanece. Após essa consulta-redefinir-parte, você pode chamar as funções de paginação que são f($wp_query), - elas dependem de $wp_queryglobal.


Para melhorar ainda mais a mecânica da paginação e dar mais liberdade à query_postsfunção, criei esta possível melhoria:

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;
    ?>
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.