Come risolvere la paginazione per i loop personalizzati?
Ho aggiunto una query personalizzata/secondaria a un file template/modello di pagina personalizzato; come posso fare in modo che WordPress utilizzi la mia query personalizzata per la paginazione, invece di utilizzare la paginazione del loop della query principale?
Nota Aggiuntiva
Ho modificato la query del loop principale tramite query_posts()
. Perché la paginazione non funziona e come posso risolvere?

Il Problema
Per impostazione predefinita, in qualsiasi contesto, WordPress utilizza la query principale per determinare l'impaginazione. L'oggetto della query principale è memorizzato nella variabile globale $wp_query
, che viene utilizzata anche per generare il loop della query principale:
if ( have_posts() ) : while ( have_posts() ) : the_post();
Quando si utilizza una query personalizzata, si crea un oggetto query completamente separato:
$custom_query = new WP_Query( $custom_query_args );
E quella query viene generata tramite un loop completamente separato:
if ( $custom_query->have_posts() ) :
while ( $custom_query->have_posts() ) :
$custom_query->the_post();
Ma i tag template per l'impaginazione, inclusi previous_posts_link()
, next_posts_link()
, posts_nav_link()
e paginate_links()
, basano il loro output sull'oggetto query principale, $wp_query
. Quella query principale potrebbe essere impaginata o meno. Se il contesto corrente è un template di pagina personalizzato, ad esempio, l'oggetto principale $wp_query
consisterà in un singolo post - quello dell'ID della pagina a cui è assegnato il template personalizzato.
Se il contesto corrente è un indice di archivio di qualche tipo, la query principale $wp_query
potrebbe contenere abbastanza post da causare l'impaginazione, il che porta alla parte successiva del problema: per l'oggetto query principale $wp_query
, WordPress passerà un parametro paged
alla query, basato sulla variabile di query URL paged
. Quando la query viene recuperata, quel parametro paged
verrà utilizzato per determinare quale insieme di post impaginati restituire. Se viene cliccato un link di impaginazione visualizzato e viene caricata la pagina successiva, la tua query personalizzata non avrà alcun modo di sapere che l'impaginazione è cambiata.
La Soluzione
Passare il Parametro Paged Corretto alla Query Personalizzata
Supponendo che la query personalizzata utilizzi un array di argomenti:
$custom_query_args = array(
// Parametri della query personalizzata vanno qui
);
Sarà necessario passare il parametro paged
corretto all'array. Puoi farlo recuperando la variabile di query URL utilizzata per determinare la pagina corrente, tramite get_query_var()
:
get_query_var( 'paged' );
Puoi quindi aggiungere quel parametro al tuo array di argomenti della query personalizzata:
$custom_query_args['paged'] = get_query_var( 'paged' )
? get_query_var( 'paged' )
: 1;
Nota: Se la tua pagina è una pagina frontale statica, assicurati di utilizzare page
invece di paged
poiché una pagina frontale statica utilizza page
e non paged
. Questo è ciò che dovresti avere per una pagina frontale statica
$custom_query_args['paged'] = get_query_var( 'page' )
? get_query_var( 'page' )
: 1;
Ora, quando la query personalizzata viene recuperata, verrà restituito il set corretto di post impaginati.
Utilizzare l'Oggetto Query Personalizzata per le Funzioni di Impaginazione
Affinché le funzioni di impaginazione producano l'output corretto - cioè i link precedenti/successivi/pagina relativi alla query personalizzata - WordPress deve essere forzato a riconoscere la query personalizzata. Ciò richiede un piccolo "hack": sostituire l'oggetto query principale $wp_query
con l'oggetto query personalizzato, $custom_query
:
Modificare l'oggetto query principale
- Eseguire il backup dell'oggetto query principale:
$temp_query = $wp_query
- Annullare l'oggetto query principale:
$wp_query = NULL;
Sostituire la query personalizzata nell'oggetto query principale:
$wp_query = $custom_query;
$temp_query = $wp_query; $wp_query = NULL; $wp_query = $custom_query;
Questo "hack" deve essere fatto prima di chiamare qualsiasi funzione di impaginazione
Ripristinare l'oggetto query principale
Una volta che le funzioni di impaginazione sono state generate, ripristinare l'oggetto query principale:
$wp_query = NULL;
$wp_query = $temp_query;
Correzioni alle Funzioni di Impaginazione
La funzione previous_posts_link()
funzionerà normalmente, indipendentemente dall'impaginazione. Determina semplicemente la pagina corrente e poi genera il link per pagina - 1
. Tuttavia, è necessaria una correzione per next_posts_link()
per generare correttamente l'output. Questo perché next_posts_link()
utilizza il parametro max_num_pages
:
<?php next_posts_link( $label , $max_pages ); ?>
Come per altri parametri di query, per impostazione predefinita la funzione utilizzerà max_num_pages
per l'oggetto query principale $wp_query
. Per forzare next_posts_link()
a considerare l'oggetto $custom_query
, sarà necessario passare max_num_pages
alla funzione. Puoi recuperare questo valore dall'oggetto $custom_query
: $custom_query->max_num_pages
:
<?php next_posts_link( 'Post più vecchi' , $custom_query->max_num_pages ); ?>
Mettendo tutto insieme
Quello che segue è un costrutto di base di un loop di query personalizzato con funzioni di impaginazione che funzionano correttamente:
// Definire i parametri della query personalizzata
$custom_query_args = array( /* Parametri vanno qui */ );
// Ottenere la pagina corrente e aggiungerla all'array dei parametri della query personalizzata
$custom_query_args['paged'] = get_query_var( 'paged' ) ? get_query_var( 'paged' ) : 1;
// Istanzare la query personalizzata
$custom_query = new WP_Query( $custom_query_args );
// Correzione dell'impaginazione
$temp_query = $wp_query;
$wp_query = NULL;
$wp_query = $custom_query;
// Generare il loop della query personalizzata
if ( $custom_query->have_posts() ) :
while ( $custom_query->have_posts() ) :
$custom_query->the_post();
// Output del loop va qui
endwhile;
endif;
// Ripristinare i dati del post
wp_reset_postdata();
// Impaginazione del loop della query personalizzata
previous_posts_link( 'Post più vecchi' );
next_posts_link( 'Post più recenti', $custom_query->max_num_pages );
// Ripristinare l'oggetto query principale
$wp_query = NULL;
$wp_query = $temp_query;
Addendum: E query_posts()
?
query_posts()
per Loop Secondari
Se stai utilizzando query_posts()
per generare un loop personalizzato, anziché istanziare un oggetto separato per la query personalizzata tramite WP_Query()
, allora stai _doing_it_wrong()
e incorrerai in diversi problemi (non il minimo dei quali saranno problemi di impaginazione). Il primo passo per risolvere questi problemi sarà convertire l'uso improprio di query_posts()
in una chiamata corretta a WP_Query()
.
Utilizzare query_posts()
per Modificare il Loop Principale
Se vuoi semplicemente modificare i parametri per la query del loop principale - come cambiare il numero di post per pagina o escludere una categoria - potresti essere tentato di utilizzare query_posts()
. Ma non dovresti comunque. Quando utilizzi query_posts()
, forzi WordPress a sostituire l'oggetto query principale. (WordPress in realtà effettua una seconda query e sovrascrive $wp_query
.) Il problema, però, è che fa questa sostituzione troppo tardi nel processo per aggiornare l'impaginazione.
La soluzione è filtrare la query principale prima che i post vengano recuperati, tramite l'hook pre_get_posts
.
Invece di aggiungere questo al file del template della categoria (category.php
):
query_posts( array(
'posts_per_page' => 5
) );
Aggiungi quanto segue a functions.php
:
function wpse120407_pre_get_posts( $query ) {
// Test per l'indice dell'archivio della categoria
// e assicurarsi che la query sia la query principale
// e non una query secondaria (come l'output di un menu di navigazione
// o di un widget di post recenti, ecc.
if ( is_category() && $query->is_main_query() ) {
// Modificare i post per pagina
$query->set( 'posts_per_page', 5 );
}
}
add_action( 'pre_get_posts', 'wpse120407_pre_get_posts' );
Invece di aggiungere questo al file del template dell'indice dei post del blog (home.php
):
query_posts( array(
'cat' => '-5'
) );
Aggiungi quanto segue a functions.php
:
function wpse120407_pre_get_posts( $query ) {
// Test per l'indice principale dei post del blog
// e assicurarsi che la query sia la query principale
// e non una query secondaria (come l'output di un menu di navigazione
// o di un widget di post recenti, ecc.
if ( is_home() && $query->is_main_query() ) {
// Escludere la categoria ID 5
$query->set( 'category__not_in', array( 5 ) );
}
}
add_action( 'pre_get_posts', 'wpse120407_pre_get_posts' );
In questo modo, WordPress utilizzerà l'oggetto $wp_query
già modificato quando determina l'impaginazione, senza necessità di modifiche al template.
Quando utilizzare quale funzione
Studia questa domanda e risposta e questa domanda e risposta per capire come e quando utilizzare WP_Query
, pre_get_posts
e query_posts()
.

Chip, risparmi sempre così tanto tempo! Se solo Google classificasse le tue risposte più in alto (un appello per i googler) prima che io impazzissi a cercare ;) grazie.

Usando il tuo esempio non sono riuscito a far funzionare l'impaginazione finché non ho utilizzato un blocco if-else come quello trovato a metà pagina (invece del condizionale ? :) su questa pagina: http://themeforest.net/forums/thread/pagination-on-wordpress-static-page-set-to-front-page/28120, molto strano. A parte questo, questa risposta mi ha insegnato molto.

Ottima risposta - 1 cosa, stavo avendo problemi nell'eseguire la funzione next/previous post link in una chiamata ajax - semplicemente non funzionava - dopo una rapida ricerca ho scoperto che la variabile globale paged
non veniva aggiornata (ovviamente qualcosa legato all'ambiente admin-ajax.php) quindi ho aggiunto questo:
global $paged;
$paged = $custom_query_args['paged'];
e ha funzionato :)

...Non uso la parola 'eroe' con leggerezza, ma tu sei il più grande eroe nella storia americana. - Lionel Hutz

Ora ho la paginazione per il mio loop personalizzato, su una pagina principale, che mostra le pagine figlie. Quando provo ad accedere alla seconda pagina tramite http:example.com/main_page/page/2
, viene semplicemente reindirizzato a http://example.com/main_page
. Qualcuno sa perché?

E ricorda di ripulire la struttura dei permalink (risalva le impostazioni della struttura dei permalink)

Questa soluzione funziona ancora in WordPress 4.5+? Quando seguo queste istruzioni il risultato mostra un "post più recenti" nella pagina 1, cliccandoci si va alla pagina 2, ma i risultati della sottoquery sono ancora i primi 10 post.

Ho lottato con la mia paginazione per così tanto tempo, poi ho letto questa piccola pepita: Nota: Se la tua pagina è una pagina frontale statica, assicurati di usare page invece di paged poiché una pagina frontale statica utilizza page e non paged. Questo è ciò che dovresti avere per una pagina frontale statica

Utilizzo questo codice per un loop personalizzato con paginazione:
<?php
if ( get_query_var('paged') ) {
$paged = get_query_var('paged');
} elseif ( get_query_var('page') ) { // 'page' viene usato invece di 'paged' nella Pagina Frontale Static
$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('j F Y') ?> di <?php the_author_posts_link() ?></small>
<div><?php the_excerpt(); ?></div>
</article>
<?php
endwhile;
?>
<?php if ($custom_query->max_num_pages > 1) : // paginazione personalizzata ?>
<?php
$orig_query = $wp_query; // fix per far funzionare la paginazione
$wp_query = $custom_query;
?>
<nav class="prev-next-posts">
<div class="prev-posts-link">
<?php echo get_next_posts_link( 'Post più vecchi', $custom_query->max_num_pages ); ?>
</div>
<div class="next-posts-link">
<?php echo get_previous_posts_link( 'Post più recenti' ); ?>
</div>
</nav>
<?php
$wp_query = $orig_query; // fix per far funzionare la paginazione
?>
<?php endif; ?>
<?php
wp_reset_postdata(); // resetta la query
else:
echo '<p>'.__('Spiacenti, nessun post corrisponde ai tuoi criteri.').'</p>';
endif;
?>
Fonte:

Fantastico come sempre Chip. Come aggiunta a questo, considera la situazione in cui stai utilizzando un template di pagina globale associato a una Pagina per del "testo introduttivo" seguito da una subquery che desideri sia impaginata.
Utilizzando paginate_links() come menzioni sopra, con per lo più impostazioni predefinite, (e assumendo che tu abbia i permalink accattivanti attivati) i tuoi link di impaginazione saranno predefiniti come mysite.ca/page-slug/page/#
che è carino ma genererà errori 404
perché WordPress non conosce quella particolare struttura di URL e cercherà effettivamente una pagina figlia di "page" che è figlia di "page-slug".
Il trucco qui è inserire una regola di riscrittura astuta che si applica solo a quel particolare "slug di pagina pseudo-archivio" che accetta la struttura /page/#/
e la riscrive in una stringa di query che WordPress PUÒ comprendere, ovvero mysite.ca/?pagename=page-slug&paged=#
. Nota pagename
e paged
non name
e page
(che mi ha causato letteralmente ORE di problemi, motivando questa risposta qui!).
Ecco la regola di reindirizzamento:
add_rewrite_rule( "page-slug/page/([0-9]{1,})/?$", 'index.php?pagename=page-slug&paged=$matches[1]', "top" );
Come sempre, quando modifichi le regole di riscrittura, ricorda di svuotare i tuoi permalink visitando Impostazioni > Permalink nel pannello di amministrazione.
Se hai più pagine che si comporteranno in questo modo (ad esempio, quando hai a che fare con più tipi di post personalizzati), potresti voler evitare di creare una nuova regola di riscrittura per ogni slug di pagina. Possiamo scrivere un'espressione regolare più generica che funzioni per qualsiasi slug di pagina che identifichi.
Un approccio è il seguente:
function wpse_120407_pseudo_archive_rewrite(){
// Aggiungi gli slug delle pagine che utilizzano un Template Globale per simulare di essere una pagina "archivio"
$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' );
Svantaggi / Avvertenze
Uno svantaggio di questo approccio che mi fa venire un po' la nausea è la codifica fissa dello slug della Pagina. Se un amministratore cambia mai lo slug della pagina di quel pseudo-archivio, sei fritto - la regola di riscrittura non corrisponderà più e otterrai il temuto 404.
Non sono sicuro di poter pensare a una soluzione alternativa per questo metodo, ma sarebbe bello se fosse il template di pagina globale a innescare in qualche modo la regola di riscrittura. Un giorno potrei rivisitare questa risposta se nessun altro avrà risolto questo particolare problema.

Ho modificato la query principale del loop con
query_posts()
. Perché la paginazione non funziona e come posso risolvere?
La grande risposta creata da Chip necessita di una modifica oggi.
Da qualche tempo abbiamo la variabile $wp_the_query
che dovrebbe essere uguale alla globale $wp_query
subito dopo l'esecuzione della query principale.
Questo è il motivo per cui la parte della risposta di Chip:
Modifica l'oggetto della query principale
non è più necessaria. Possiamo dimenticarci di questa parte con la creazione della variabile temporanea.
// Correzione paginazione
$temp_query = $wp_query;
$wp_query = NULL;
$wp_query = $custom_query;
Quindi ora possiamo chiamare:
$wp_query = $wp_the_query;
o ancora meglio possiamo chiamare:
wp_reset_query();
Tutto il resto che Chip ha delineato rimane valido.
Dopo quella parte di reset della query puoi chiamare le funzioni di paginazione che sono f($wp_query)
, — dipendono dalla globale $wp_query
.
Per migliorare ulteriormente la meccanica della paginazione e dare più libertà alla funzione query_posts
ho creato questo possibile miglioramento:

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();
//aggiungi il tuo codice qui
endwhile;
wp_reset_query();
//gestisci la paginazione basata sulla Query personalizzata.
$GLOBALS['wp_query']->max_num_pages = $query->max_num_pages;
the_posts_pagination(array(
'mid_size' => 1,
'prev_text' => __('Pagina precedente', 'patelextensions'),
'next_text' => __('Pagina successiva', 'patelextensions'),
'before_page_number' => '<span class="meta-nav screen-reader-text">' . __('Pagina', 'patelextensions') . ' </span>',
));
else:
?>
<div class="container text-center"><?php echo _d('Nessun risultato trovato','30'); ?></div>
<?php
endif;
?>
