Cum se repară paginarea pentru bucle personalizate?
Am adăugat o interogare personalizată/secundară într-un fișier template/șablon de pagină personalizat; cum pot face WordPress să folosească interogarea mea personalizată pentru paginare, în loc să folosească paginarea buclei principale?
Addendum
Am modificat interogarea buclei principale prin query_posts()
. De ce nu funcționează paginarea și cum o pot repara?

Problema
În mod implicit, în orice context dat, WordPress utilizează interogarea principală pentru a determina paginarea. Obiectul interogării principale este stocat în variabila globală $wp_query
, care este folosită și pentru afișarea buclei principale de interogare:
if ( have_posts() ) : while ( have_posts() ) : the_post();
Când utilizezi o interogare personalizată, creezi un obiect de interogare complet separat:
$custom_query = new WP_Query( $custom_query_args );
Și acea interogare este afișată printr-o buclă separată:
if ( $custom_query->have_posts() ) :
while ( $custom_query->have_posts() ) :
$custom_query->the_post();
Dar funcțiile de paginare, inclusiv previous_posts_link()
, next_posts_link()
, posts_nav_link()
și paginate_links()
, își bazează rezultatul pe obiectul interogării principale, $wp_query
. Acea interogare principală poate sau nu să fie paginată. Dacă contextul curent este un șablon de pagină personalizat, de exemplu, obiectul principal $wp_query
va consta doar dintr-un singur post - cel al ID-ului paginii căreia i se atribuie șablonul de pagină personalizat.
Dacă contextul curent este un index de arhivă de un anumit tip, interogarea principală $wp_query
poate consta din suficiente postări pentru a provoca paginare, ceea ce duce la următoarea parte a problemei: pentru obiectul principal $wp_query
, WordPress va transmite un parametru paged
către interogare, bazat pe variabila de interogare URL paged
. Când interogarea este preluată, acel parametru paged
va fi folosit pentru a determina care set de postări paginate să returneze. Dacă este apăsat un link de paginare afișat și este încărcată pagina următoare, interogarea ta personalizată nu va avea nicio modalitate de a ști că paginarea s-a schimbat.
Soluția
Transmiterea parametrului corect Paged către interogarea personalizată
Presupunând că interogarea personalizată folosește un array de argumente:
$custom_query_args = array(
// Parametrii interogării personalizate merg aici
);
Va trebui să transmiți parametrul corect paged
către array. Poți face acest lucru preluând variabila de interogare URL folosită pentru a determina pagina curentă, prin get_query_var()
:
get_query_var( 'paged' );
Apoi poți adăuga acel parametru în array-ul de argumente al interogării personalizate:
$custom_query_args['paged'] = get_query_var( 'paged' )
? get_query_var( 'paged' )
: 1;
Notă: Dacă pagina ta este o pagină frontală statică, asigură-te că folosești page
în loc de paged
, deoarece o pagină frontală statică folosește page
și nu paged
. Iată ce ar trebui să ai pentru o pagină frontală statică:
$custom_query_args['paged'] = get_query_var( 'page' )
? get_query_var( 'page' )
: 1;
Acum, când interogarea personalizată este preluată, va fi returnat setul corect de postări paginate.
Utilizarea obiectului de interogare personalizat pentru funcțiile de paginare
Pentru ca funcțiile de paginare să ofere rezultatul corect - adică linkuri anterioare/următoare/pagină relative la interogarea personalizată - WordPress trebuie forțat să recunoască interogarea personalizată. Acest lucru necesită un mic "hack": înlocuirea obiectului principal $wp_query
cu obiectul de interogare personalizat, $custom_query
:
Modificarea obiectului de interogare principal
- Salvează obiectul de interogare principal:
$temp_query = $wp_query
- Anulează obiectul de interogare principal:
$wp_query = NULL;
Înlocuiește interogarea personalizată în obiectul de interogare principal:
$wp_query = $custom_query;
$temp_query = $wp_query; $wp_query = NULL; $wp_query = $custom_query;
Acest "hack" trebuie făcut înainte de a apela orice funcții de paginare
Resetarea obiectului de interogare principal
După ce funcțiile de paginare au fost afișate, resetează obiectul de interogare principal:
$wp_query = NULL;
$wp_query = $temp_query;
Remedieri pentru funcțiile de paginare
Funcția previous_posts_link()
va funcționa normal, indiferent de paginare. Ea determină doar pagina curentă și apoi afișează linkul pentru page - 1
. Totuși, este necesară o remediere pentru ca next_posts_link()
să funcționeze corect. Acest lucru se întâmplă deoarece next_posts_link()
utilizează parametrul max_num_pages
:
<?php next_posts_link( $label , $max_pages ); ?>
Ca și în cazul altor parametri de interogare, în mod implicit funcția va folosi max_num_pages
pentru obiectul principal $wp_query
. Pentru a forța next_posts_link()
să țină cont de obiectul $custom_query
, va trebui să transmiți max_num_pages
către funcție. Poți prelua această valoare din obiectul $custom_query
: $custom_query->max_num_pages
:
<?php next_posts_link( 'Postări mai vechi' , $custom_query->max_num_pages ); ?>
Punând totul cap la cap
Următorul este un construct de bază al unei bucle de interogare personalizată cu funcții de paginare care funcționează corect:
// Definește parametrii interogării personalizate
$custom_query_args = array( /* Parametrii merg aici */ );
// Obține pagina curentă și adaugă la array-ul de parametri ai interogării personalizate
$custom_query_args['paged'] = get_query_var( 'paged' ) ? get_query_var( 'paged' ) : 1;
// Instanțiază interogarea personalizată
$custom_query = new WP_Query( $custom_query_args );
// Remediu pentru paginare
$temp_query = $wp_query;
$wp_query = NULL;
$wp_query = $custom_query;
// Afișează bucla interogării personalizate
if ( $custom_query->have_posts() ) :
while ( $custom_query->have_posts() ) :
$custom_query->the_post();
// Aici intră conținutul buclei
endwhile;
endif;
// Resetează datele postului
wp_reset_postdata();
// Paginarea buclei interogării personalizate
previous_posts_link( 'Postări mai vechi' );
next_posts_link( 'Postări mai noi', $custom_query->max_num_pages );
// Resetează obiectul de interogare principal
$wp_query = NULL;
$wp_query = $temp_query;
Anexă: Ce zici de query_posts()
?
query_posts()
pentru bucle secundare
Dacă folosești query_posts()
pentru a afișa o buclă personalizată, în loc să instanțiezi un obiect separat pentru interogarea personalizată prin WP_Query()
, atunci _doing_it_wrong()
și vei întâmpina mai multe probleme (printre care și probleme de paginare). Primul pas pentru rezolvarea acestor probleme va fi conversia utilizării improprii a query_posts()
la un apel corespunzător WP_Query()
.
Utilizarea query_posts()
pentru modificarea buclei principale
Dacă vrei doar să modifici parametrii pentru interogarea buclei principale - cum ar fi schimbarea numărului de postări pe pagină sau excluderea unei categorii - poți fi tentat să folosești query_posts()
. Dar tot nu ar trebui. Când folosești query_posts()
, forțezi WordPress să înlocuiască obiectul de interogare principal. (WordPress face de fapt o a doua interogare și suprascrie $wp_query
.) Problema, însă, este că face această înlocuire prea târziu în proces pentru a actualiza paginarea.
Soluția este să filtrezi interogarea principală înainte ca postările să fie preluate, prin hook-ul pre_get_posts
.
În loc să adaugi asta în fișierul șablonului de categorie (category.php
):
query_posts( array(
'posts_per_page' => 5
) );
Adaugă următoarele în functions.php
:
function wpse120407_pre_get_posts( $query ) {
// Testează pentru indexul de arhivă al categoriei
// și asigură-te că interogarea este interogarea principală
// și nu o interogare secundară (cum ar fi un meniu de navigare
// sau un widget de postări recente, etc.
if ( is_category() && $query->is_main_query() ) {
// Modifică numărul de postări pe pagină
$query->set( 'posts_per_page', 5 );
}
}
add_action( 'pre_get_posts', 'wpse120407_pre_get_posts' );
În loc să adaugi asta în fișierul șablonului indexului de blog (home.php
):
query_posts( array(
'cat' => '-5'
) );
Adaugă următoarele în functions.php
:
function wpse120407_pre_get_posts( $query ) {
// Testează pentru indexul principal de postări de blog
// și asigură-te că interogarea este interogarea principală
// și nu o interogare secundară (cum ar fi un meniu de navigare
// sau un widget de postări recente, etc.
if ( is_home() && $query->is_main_query() ) {
// Exclude categoria cu ID-ul 5
$query->set( 'category__not_in', array( 5 ) );
}
}
add_action( 'pre_get_posts', 'wpse120407_pre_get_posts' );
În acest fel, WordPress va folosi obiectul $wp_query
deja modificat pentru a determina paginarea, fără a fi necesară nicio modificare a șablonului.
Când să folosești ce funcție
Cercetează această întrebare și răspuns și această întrebare și răspuns pentru a înțelege cum și când să folosești WP_Query
, pre_get_posts
și query_posts()
.

Chip, mereu îmi economisești atât de mult timp! Dacă măcar Google ar rank-ui mai sus răspunsurile tale (semnătură pentru Googleri) înainte să înnebunesc căutând ;) mulțumesc.

Folosind exemplul tău, nu am putut face paginarea să funcționeze până când nu am folosit un bloc if-else, așa cum este găsit la mijlocul (în locul condiționalului ? :) acestei pagini: http://themeforest.net/forums/thread/pagination-on-wordpress-static-page-set-to-front-page/28120, foarte ciudat. În rest, acest răspuns m-a învățat multe.

Răspuns excelent - 1 lucru, aveam probleme cu rularea funcției de link pentru postările următoare/anterioare într-un apel ajax - pur și simplu nu funcționa - după o scurtă investigare, am descoperit că variabila globală paged
nu era actualizată (evident din cauza mediului admin-ajax.php), așa că am adăugat asta:
global $paged;
$paged = $custom_query_args['paged'];
și a funcționat :)

...Nu folosesc cuvântul 'erou' cu ușurință, dar tu ești cel mai mare erou din istoria Americii. - Lionel Hutz

Am creat acum paginare pentru bucla mea personalizată, pe o pagină principală, care afișează pagini copil. Încerc să accesez a doua pagină, prin http:example.com/main_page/page/2
, dar sunt redirecționat înapoi la http://example.com/main_page
. Aveți vreo idee de ce se întâmplă asta?

Și nu uitați să resetați structura de permalinkuri (resalvați setările structurii de permalinkuri)

Funcționează încă această soluție în WordPress 4.5+? Când urmez aceste instrucțiuni, rezultatul apare pe o "postări mai noi" pe pagina 1, dar când fac clic pentru a merge la pagina 2, rezultatele subinterogării sunt în continuare primele 10 postări.

M-am luptat cu paginarea mea pentru atât de mult timp, apoi am citit acest mic sfat: Notă: Dacă pagina ta este o pagină frontală statică, asigură-te că folosești page în loc de paged, deoarece o pagină frontală statică folosește page și nu paged. Acesta este lucrul pe care ar trebui să-l ai pentru o pagină frontală statică

Folosesc acest cod pentru o buclă personalizată cu paginare:
<?php
if ( get_query_var('paged') ) {
$paged = get_query_var('paged');
} elseif ( get_query_var('page') ) { // 'page' este folosit în loc de 'paged' pe Pagina Principală 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('F jS, Y') ?> de <?php the_author_posts_link() ?></small>
<div><?php the_excerpt(); ?></div>
</article>
<?php
endwhile;
?>
<?php if ($custom_query->max_num_pages > 1) : // paginare personalizată ?>
<?php
$orig_query = $wp_query; // remediu pentru ca paginarea să funcționeze
$wp_query = $custom_query;
?>
<nav class="prev-next-posts">
<div class="prev-posts-link">
<?php echo get_next_posts_link( 'Articole mai vechi', $custom_query->max_num_pages ); ?>
</div>
<div class="next-posts-link">
<?php echo get_previous_posts_link( 'Articole mai noi' ); ?>
</div>
</nav>
<?php
$wp_query = $orig_query; // remediu pentru ca paginarea să funcționeze
?>
<?php endif; ?>
<?php
wp_reset_postdata(); // resetare interogare
else:
echo '<p>'.__('Ne pare rău, nu există articole care să corespundă criteriilor dumneavoastră.').'</p>';
endif;
?>
Sursă:

Superb ca întotdeauna, Chip. Ca un adaos la asta, ia în considerare situația în care folosești un șablon de pagină global atașat unei Pagini pentru un "text introductiv", urmat de o subinterogare pe care dorești să fie paginată.
Folosind paginate_links() cum menționezi mai sus, cu setări predefinite în mare parte (și presupunând că ai permalink-uri frumoase activate), link-urile tale de paginare vor folosi în mod implicit structura mysite.ca/page-slug/page/#
, ceea ce e minunat, dar va genera erori 404
deoarece WordPress nu recunoaște această structură de URL și va încerca să găsească o pagină copil a "page" care este copil a "page-slug".
Trucul aici este să inserezi o regulă de rescriere inteligentă care se aplică doar acelui anumit "pseudo-archive page" cu un anumit slug, care acceptă structura /page/#/
și o rescrie într-un șir de interogare pe care WordPress ÎL POATE înțelege, și anume mysite.ca/?pagename=page-slug&paged=#
. Observă pagename
și paged
, nu name
și page
(ceea ce mi-a cauzat literalmente ORE de frustrare, motivând acest răspuns!).
Iată regula de redirecționare:
add_rewrite_rule( "page-slug/page/([0-9]{1,})/?$", 'index.php?pagename=page-slug&paged=$matches[1]', "top" );
Ca întotdeauna, când modifici regulile de rescriere, nu uita să actualizezi permalink-urile vizitând Setări > Permalink-uri în panoul de administrare.
Dacă ai mai multe pagini care se vor comporta astfel (de exemplu, când lucrezi cu multiple tipuri de postări personalizate), poți dori să eviți crearea unei noi reguli de rescriere pentru fiecare slug de pagină. Putem scrie o expresie regulată mai generică care funcționează pentru orice slug de pagină identificat.
O abordare este prezentată mai jos:
function wpse_120407_pseudo_archive_rewrite(){
// Adaugă slug-urile paginilor care folosesc un Șablon Global pentru a simula fi o pagină "arhivă"
$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' );
Dezavantaje / Limitări
Un dezavantaj al acestei abordări, care mă face să am o ușoară greață, este hardcodarea slug-ului Paginii. Dacă un administrator schimbă vreodată slug-ul acelei pagini pseudo-arhivă, ești terminat - regula de rescriere nu se va mai potrivi și vei primi temutul 404.
Nu sunt sigur că pot găsi o soluție pentru această metodă, dar ar fi frumos dacă șablonul global de pagină ar declanșa cumva regula de rescriere. Poate într-o zi voi reveni la acest răspuns dacă nimeni nu a rezolvat această problemă până atunci.

Am modificat interogarea principală a buclei prin
query_posts()
. De ce nu funcționează paginarea și cum o pot repara?
Răspunsul excelent creat de Chip trebuie actualizat astăzi.
De ceva vreme avem variabila $wp_the_query
care ar trebui să fie egală cu globalul $wp_query
imediat după execuția interogării principale.
De aceea, partea din răspunsul lui Chip:
Hack the main query object
nu mai este necesară. Putem uita de această parte cu crearea variabilei temporare.
// Fix pentru paginare
$temp_query = $wp_query;
$wp_query = NULL;
$wp_query = $custom_query;
Acum putem apela:
$wp_query = $wp_the_query;
sau chiar mai bine putem apela:
wp_reset_query();
Toate celelalte aspecte prezentate de Chip rămân valabile.
După acea parte de resetare a interogării, puteți apela funcțiile de paginare care sunt f($wp_query)
, — ele depind de globalul $wp_query
.
Pentru a îmbunătăți în continuare mecanismul de paginare și pentru a oferi mai multă libertate funcției query_posts
, am creat această posibilă îmbunătățire:

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();
//adaugă codul tău aici
endwhile;
wp_reset_query();
//gestionează paginarea pe baza interogării personalizate
$GLOBALS['wp_query']->max_num_pages = $query->max_num_pages;
the_posts_pagination(array(
'mid_size' => 1,
'prev_text' => __('Pagina anterioară', 'patelextensions'),
'next_text' => __('Pagina următoare', 'patelextensions'),
'before_page_number' => '<span class="meta-nav screen-reader-text">' . __('Pagina', 'patelextensions') . ' </span>',
));
else:
?>
<div class="container text-center"><?php echo _d('Rezultate negăsite','30'); ?></div>
<?php
endif;
?>
