web-dev-qa-db-fra.com

Comment fixer la pagination pour les boucles personnalisées?

J'ai ajouté une requête personnalisée/secondaire à un modèle de fichier/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?

119
Chip Bennett

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_query global, 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();

Toutefois, les balises de modèle de pagination, notamment previous_posts_link() , next_posts_link() , posts_nav_link() et paginate_links() , basent leur sortie sur le objet de requête principal, $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’objet $wp_query principal sera composé uniquement d’un publication unique - de l’ID de la page à laquelle le modèle de page personnalisé est attribué.

Si le contexte actuel est un index d'archive d'une certaine sorte, le $wp_query principal peut être constitué de suffisamment de publications pour provoquer la pagination, ce qui conduit à la partie suivante du problème: pour l'objet principal $wp_query, WordPress passera un paramètre paged à la requête, basé sur la variable de requête paged URL. Lorsque la requête est extraite, ce paramètre paged 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 ne pourra en aucun cas 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 paramètre paged correct au tableau. Vous pouvez le faire en récupérant la variable de requête 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 couverture statique , veillez à utiliser page au lieu de paged, car une page de couverture statique utilise page et 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 génèrent le bon résultat (liens précédents/suivants/page relatifs à la requête personnalisée), WordPress doit obligatoirement reconnaître la requête personnalisée. Cela nécessite un peu de "hack": remplacer l'objet $wp_query principal par l'objet de requête personnalisé, $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 effectué avant d'appeler des fonctions 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 fonction previous_posts_link() 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 que next_posts_link() puisse être correctement généré. En effet, next_posts_link() utilise le paramètre max_num_pages:

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

Comme avec les autres paramètres de requête, la fonction utilisera par défaut max_num_pages pour l'objet $wp_query principal. Pour forcer next_posts_link() à prendre en compte l'objet $custom_query, vous devez passer le max_num_pages à la fonction. Vous pouvez récupérer cette valeur à partir de l'objet $custom_query: $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 de query_posts()?

query_posts() pour les boucles secondaires

Si vous utilisez query_posts() pour générer une boucle personnalisée, plutôt que d'instancier un objet séparé pour la requête personnalisée via WP_Query() , vous êtes alors _doing_it_wrong() et rencontrez plusieurs problèmes ( pas les les moins qui seront des problèmes de pagination). Pour résoudre ces problèmes, la première étape consistera à convertir l'utilisation impropre de query_posts() en un appel WP_Query() approprié.

Utilisation de 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é d'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 extraites}, via le hook pre_get_posts.

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'objet $wp_query déjà modifié lors de la détermination de la pagination, sans aucune modification de modèle.

Quand utiliser quelle fonction

Recherchez cette question et la réponse et cette question et la réponse pour comprendre comment et quand utiliser WP_Query , pre_get_posts et query_posts() .

205
Chip Bennett

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:

21
webvitaly

Génial comme toujours Chip. En tant qu'additif à cela, 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 très permaliens activés), vos liens de pagination seront par défaut à mysite.ca/page-slug/page/#, ce qui est charmant, mais jettera des erreurs 404, car WordPress ne le sait pas. structure d'URL particulière et recherchera en fait 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 ne s'applique qu'à ce slug de page "pseudo archive" qui accepte la structure /page/#/ et la réécrit en une chaîne de requête que WordPress PEUT comprendre, à savoir mysite.ca/?pagename=page-slug&paged=#. Notez pagename et paged pas name et 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 purgez 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 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 à casser cette noix.

5
Tom Auger

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 la variable $wp_the_query qui devrait être égale à la valeur globale $wp_query 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 requête, vous pouvez appeler les fonctions de pagination qui sont f($wp_query), - elles dépendent de $wp_query global.


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

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

2
prosti
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;
    ?>
1
ravi patel