Comment fixer la pagination pour les boucles personnalisées?

122

J'ai ajouté une requête personnalisée / secondaire à un fichier de modèle / modèle de page personnalisé; Comment faire en sorte que WordPress utilise ma requête personnalisée pour la pagination, au lieu d'utiliser la pagination de la boucle de requête principale?

Addenda

J'ai modifié la requête de la boucle principale via query_posts(). Pourquoi la pagination ne fonctionne-t-elle pas et comment puis-je la réparer?

Chip Bennett
la source

Réponses:

215

Le problème

Par défaut, dans n'importe quel contexte, WordPress utilise la requête principale pour déterminer la pagination. L'objet de requête principal est stocké dans le $wp_queryglobal, qui est également utilisé pour générer la boucle de requête principale:

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

Lorsque vous utilisez une requête personnalisée , vous créez un objet de requête entièrement séparé:

$custom_query = new WP_Query( $custom_query_args );

Et cette requête est sortie via une boucle entièrement séparée:

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

Mais les balises de modèle, y compris de la pagination previous_posts_link(), next_posts_link(), posts_nav_link()et paginate_links(), la base de leur sortie sur le principal objet de requête , $wp_query. Cette requête principale peut ou non être paginée. Si le contexte actuel est un modèle de page personnalisé, par exemple, l' $wp_queryobjet principal ne sera composé que d'une seule publication , celle de l'ID de la page à laquelle le modèle de page personnalisé est affecté.

Si le contexte actuel est un index d'archive d'une certaine sorte, l' $wp_queryélément principal peut comporter suffisamment de publications pour provoquer la pagination, ce qui conduit à la partie suivante du problème: pour l' $wp_queryobjet principal , WordPress passera un paged paramètre à la requête, en fonction du pagedVariable de requête URL. Lorsque la requête est extraite, ce pagedparamètre sera utilisé pour déterminer quel ensemble de publications paginées à renvoyer. Si vous cliquez sur un lien de pagination affiché et que la page suivante est chargée, votre requête personnalisée n'aura aucun moyen de savoir que la pagination a été modifiée .

La solution

Passage du paramètre paginé correct à la requête personnalisée

En supposant que la requête personnalisée utilise un tableau args:

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

Vous devrez passer le pagedparamètre correct au tableau. Vous pouvez le faire en récupérant la variable de requête d'URL utilisée pour déterminer la page en cours, via get_query_var():

get_query_var( 'paged' );

Vous pouvez ensuite ajouter ce paramètre à votre tableau args de requête personnalisé:

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

Remarque: Si votre page est une page de garde statique , veillez à l'utiliser pageau lieu d' utiliser pagedcomme page de garde statique pageet non paged. Voici ce que vous devriez avoir pour une page de garde statique

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

Désormais, lorsque la requête personnalisée est extraite, l'ensemble correct des publications paginées est renvoyé.

Utilisation d'un objet de requête personnalisé pour les fonctions de pagination

Pour que les fonctions de pagination produisent le résultat correct, c'est-à-dire les liens précédent / suivant / page relatifs à la requête personnalisée, WordPress doit obligatoirement reconnaître la requête personnalisée. Cela nécessite un peu d'un « hack »: remplacer le principal $wp_queryobjet avec l'objet de requête personnalisée, $custom_query:

Pirater l'objet de requête principal

  1. Sauvegardez l'objet de requête principal: $temp_query = $wp_query
  2. Null l'objet de requête principal: $wp_query = NULL;
  3. Échangez la requête personnalisée dans l'objet de requête principal: $wp_query = $custom_query;

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

Ce "hack" doit être fait avant d'appeler une fonction de pagination

Réinitialiser l'objet de requête principal

Une fois les fonctions de pagination générées, réinitialisez l’objet de requête principal:

$wp_query = NULL;
$wp_query = $temp_query;

Corrections de la fonction de pagination

La previous_posts_link()fonction fonctionnera normalement, quelle que soit la pagination. Il détermine simplement la page en cours, puis génère le lien pour page - 1. Cependant, un correctif est requis pour next_posts_link()pouvoir générer correctement. C'est parce que next_posts_link()utilise le max_num_pagesparamètre:

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

Comme avec d'autres paramètres de requête, la fonction utilisera par défaut max_num_pagespour l' $wp_queryobjet principal . Afin de forcer next_posts_link()à rendre compte de l' $custom_queryobjet, vous devrez passer le max_num_pagesà la fonction. Vous pouvez extraire cette valeur de l' $custom_queryobjet $custom_query->max_num_pages:

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

Mettre tous ensemble

Voici une construction de base d'une boucle de requête personnalisée avec des fonctions de pagination fonctionnant correctement:

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

Addendum: Qu'en est-il query_posts()?

query_posts() pour les boucles secondaires

Si vous utilisez query_posts()la sortie d' une boucle personnalisée, plutôt instanciation alors un objet distinct pour la requête personnalisée via WP_Query(), alors vous êtes _doing_it_wrong(), et se déroulera en plusieurs problèmes (pas moins de ce qui sera des questions paginations). La première étape pour résoudre ces problèmes consistera à convertir l'utilisation inappropriée de query_posts()en un WP_Query()appel approprié .

Utiliser query_posts()pour modifier la boucle principale

Si vous souhaitez simplement modifier les paramètres de la requête de la boucle principale , par exemple modifier les publications par page ou exclure une catégorie, vous pouvez être tenté de l’utiliser query_posts(). Mais vous ne devriez toujours pas. Lorsque vous utilisez query_posts(), vous forcez WordPress à remplacer l'objet de requête principal. (WordPress effectue en réalité une deuxième requête et écrase $wp_query.) Le problème, cependant, est qu'il effectue ce remplacement trop tard dans le processus pour mettre à jour la pagination.

La solution consiste à filtrer la requête principale avant que les publications ne soient récupérées , via le pre_get_postshook.

Au lieu de l'ajouter au fichier de modèle de catégorie ( category.php):

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

Ajoutez ce qui suit à 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' );

Au lieu d’ajouter ceci au fichier de modèle d’index des articles de blog ( home.php):

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

Ajoutez ce qui suit à 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 cette façon, WordPress utilisera l' $wp_queryobjet déjà modifié lors de la détermination de la pagination, sans aucune modification de modèle.

Quand utiliser quelle fonction

La recherche de cette question et la réponse et cette question et la réponse à comprendre comment et quand utiliser WP_Query, pre_get_postset query_posts().

Chip Bennett
la source
31
Les pages de souhaits dans le codex pourraient être si complètes.
Pieter Goosen
Chip, tu as fait ma journée!
tepkenvannkorn
1
Chip, vous gagnez toujours beaucoup de temps! si seulement google classerait vos réponses plus haut (légende pour les googlers) avant que je devienne fou de chercher;) merci.
Sagive SEO
En utilisant votre exemple, je ne pouvais pas faire fonctionner la pagination tant que je n'avais pas utilisé un bloc if-else tel que trouvé à mi-chemin (au lieu du?: Conditionnel) dans cette page: themeforest.net/forums/thread/… , très étrange. Sinon, cette réponse m'a beaucoup appris.
P aul
2
Excellente réponse - une chose, j’avais des problèmes pour exécuter la fonction de lien suivant / précédent dans un appel ajax - cela ne prendrait tout simplement pas - après une rapide recherche, j’ai trouvé que le pagedfichier global n’était pas mis à jour (quelque chose à voir avec admin- ajax.php environment) donc j’ai ajouté ceci: global $paged; $paged = $custom_query_args['paged']; et cela a fonctionné :)
acSlater
21

J'utilise ce code pour une boucle personnalisée avec pagination:

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

La source:

webvitaly
la source
1
Un autre bon tutoriel avec une variante de cette réponse: callmenick.com/post/custom-wordpress-loop-lo-pagination
mrwweb
5

Génial comme toujours Chip. En tant qu'additif à ceci, considérons la situation dans laquelle vous utilisez un modèle de page global attaché à une page pour un "texte d'introduction" suivi d'une sous-requête que vous souhaitez paginer.

En utilisant paginate_links () comme vous le dites ci-dessus, avec la plupart des valeurs par défaut (et en supposant que vous ayez de jolis permaliens activés), vos liens de pagination seront par défaut mysite.ca/page-slug/page/#très jolis, mais généreront des 404erreurs car WordPress ne connaît pas cette structure d'URL particulière et cherche une page enfant de "page" qui est un enfant de "page-slug".

L'astuce consiste ici à insérer une règle de réécriture astucieuse qui s'applique uniquement à ce slug de page "pseudo archive" qui accepte la /page/#/structure et la réécrit en une chaîne de requête que WordPress peut comprendre, à savoir mysite.ca/?pagename=page-slug&paged=#. Notez pagenameet pagednon nameet page(ce qui m'a littéralement causé des HEURES de chagrin, motivant cette réponse ici!).

Voici la règle de redirection:

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

Comme toujours, lorsque vous modifiez les règles de réécriture, n'oubliez pas de vider vos permaliens en accédant à Paramètres> Permaliens dans le back-end de l'administrateur.

Si vous avez plusieurs pages qui vont se comporter de cette manière (par exemple, lorsque vous traitez avec plusieurs types de publication personnalisés), vous pouvez éviter de créer une nouvelle règle de réécriture pour chaque slug de page. Nous pouvons écrire une expression régulière plus générique qui fonctionne pour n'importe quel slug de page que vous identifiez.

Une approche est ci-dessous:

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

Inconvénients / mises en garde

Un inconvénient de cette approche qui me fait vomir un peu dans la bouche est le codage en dur de la limace de page. Si un administrateur change jamais le slug de page de cette page de pseudo-archive, vous êtes grillé - la règle de réécriture ne correspond plus et vous obtenez le redoutable 404.

Je ne suis pas sûr de pouvoir imaginer une solution de contournement pour cette méthode, mais ce serait bien si c'était le modèle de page global qui avait déclenché la règle de réécriture. Un jour, je pourrai revoir cette réponse si personne d'autre n'a réussi à percer ce problème.

Tom Auger
la source
1
Vous pouvez accrocher après la sauvegarde, vérifier si la page contient votre modèle d'archive sous la clé méta _wp_page_template, puis ajouter une autre règle de réécriture et de vidage.
Milo
2

J'ai modifié la requête de la boucle principale via query_posts(). Pourquoi la pagination ne fonctionne-t-elle pas et comment puis-je la réparer?

Excellente réponse La puce créée doit être modifiée aujourd'hui.
Pendant un certain temps, nous avons une $wp_the_queryvariable qui devrait être égale à la variable $wp_queryglobale juste après l'exécution de la requête principale.

C'est pourquoi cette partie de la réponse de la puce:

Pirater l'objet de requête principal

n'est plus nécessaire. Nous pouvons oublier cette partie avec la création de la variable temporaire.

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

Alors maintenant, nous pouvons appeler:

$wp_query   = $wp_the_query;

ou mieux encore, nous pouvons appeler:

wp_reset_query();

Tout ce que Chip décrit ci-dessus reste. Après cette partie de réinitialisation de requête, vous pouvez appeler les fonctions de pagination qui sont f($wp_query), elles dépendent de $wp_queryglobal.


Afin d'améliorer encore les mécanismes de pagination et de donner plus de liberté à la query_postsfonction, j'ai créé cette amélioration possible:

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

prosti
la source
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;
    ?>
ravi patel
la source