Jak naprawić podział na strony dla niestandardowych pętli?


122

Dodałem niestandardowe / dodatkowe zapytanie do pliku szablonu / niestandardowego szablonu strony; jak mogę zmusić WordPress do korzystania z mojego niestandardowego zapytania do paginacji, zamiast korzystania z paginacji głównej pętli zapytań?

Uzupełnienie

Zmodyfikowałem zapytanie głównej pętli za pomocą query_posts(). Dlaczego podział na strony nie działa i jak to naprawić?

Odpowiedzi:


215

Problem

Domyślnie w dowolnym kontekście WordPress używa głównego zapytania do ustalenia podziału na strony. Główny obiekt zapytania jest przechowywany w $wp_queryglobalnym, który służy również do wyprowadzania głównej pętli zapytania:

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

Po użyciu zapytania niestandardowego , należy utworzyć całkowicie odrębny przedmiot zapytania:

$custom_query = new WP_Query( $custom_query_args );

I to zapytanie jest generowane przez całkowicie oddzielną pętlę:

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

Ale znaczniki szablonów stronicowanie, w tym previous_posts_link(), next_posts_link(), posts_nav_link(), i paginate_links(), opierają swoje wyjście na główny obiekt zapytania , $wp_query. To główne zapytanie może, ale nie musi, być paginowane. Jeśli bieżącym kontekstem jest na przykład niestandardowy szablon strony, główny $wp_queryobiekt będzie składał się tylko z jednego posta - identyfikatora strony, do której przypisany jest niestandardowy szablon strony.

Jeśli bieżącym kontekstem jest pewnego rodzaju indeks archiwalny, główny $wp_querymoże składać się z wystarczającej liczby postów, aby spowodować podział na strony, co prowadzi do następnej części problemu: dla głównego $wp_queryobiektu WordPress przekaże paged parametr do zapytania na podstawie pagedZmienna zapytania URL. Po pobraniu zapytania pagedparametr ten zostanie użyty do ustalenia, który zestaw stronicowanych postów ma zostać zwrócony. Jeśli kliknięty zostanie wyświetlony link do stronicowania, a następna strona zostanie załadowana, zapytanie niestandardowe nie będzie w żaden sposób wiedziało, że podział stronicowania się zmienił .

Rozwiązanie

Przekazywanie poprawnego parametru stronicowanego do zapytania niestandardowego

Zakładając, że niestandardowe zapytanie korzysta z tablicy args:

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

Musisz przekazać poprawny pagedparametr do tablicy. Możesz to zrobić, pobierając zmienną zapytania URL używaną do ustalenia bieżącej strony, poprzez get_query_var():

get_query_var( 'paged' );

Następnie możesz dołączyć ten parametr do niestandardowej tablicy argumentów zapytania:

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

Uwaga: jeśli twoja strona jest statyczną stroną główną , pamiętaj, aby używać jej pagezamiast pagedjako statycznej strony głównej, pagea nie paged. Właśnie to powinieneś mieć dla statycznej strony głównej

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

Teraz po pobraniu niestandardowego zapytania zostanie zwrócony prawidłowy zestaw stronicowanych postów.

Korzystanie z niestandardowego obiektu zapytania dla funkcji paginacji

Aby funkcje stronicowania dawały prawidłowe dane wyjściowe - tj. Linki poprzedni / następny / stron w stosunku do niestandardowego zapytania - WordPress musi zostać zmuszony do rozpoznania niestandardowego zapytania. Wymaga to trochę „włamania”: zastąpienie głównego $wp_queryobiektu niestandardowym obiektem zapytania $custom_query:

Włam się do głównego obiektu zapytania

  1. Wykonaj kopię zapasową głównego obiektu zapytania: $temp_query = $wp_query
  2. Null główny obiekt zapytania: $wp_query = NULL;
  3. Zamień niestandardowe zapytanie na główny obiekt zapytania: $wp_query = $custom_query;

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

Ten „hack” należy wykonać przed wywołaniem jakichkolwiek funkcji stronicowania

Zresetuj główny obiekt zapytania

Po wygenerowaniu funkcji podziału na strony zresetuj główny obiekt zapytania:

$wp_query = NULL;
$wp_query = $temp_query;

Poprawki funkcji paginacji

previous_posts_link()Funkcja będzie działać normalnie, niezależnie od paginacji. Określa tylko bieżącą stronę, a następnie wyświetla link do page - 1. Jednak next_posts_link()do poprawnego wydrukowania jest wymagana poprawka . Jest tak, ponieważ next_posts_link()używa max_num_pagesparametru:

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

Podobnie jak w przypadku innych parametrów zapytania, funkcja będzie domyślnie używana max_num_pagesdla głównego $wp_queryobiektu. Aby wymusić next_posts_link()rozliczenie $custom_queryobiektu, musisz przekazać max_num_pagesfunkcję. Możesz pobrać tę wartość z $custom_queryobiektu $custom_query->max_num_pages:

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

Kładąc wszystko razem

Oto podstawowa konstrukcja niestandardowej pętli zapytań z poprawnie działającymi funkcjami paginacji:

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

Dodatek: Co powiesz na query_posts()?

query_posts() dla pętli wtórnych

Jeśli używasz query_posts()do wygenerowania niestandardowej pętli, zamiast tworzenia instancji oddzielnego obiektu dla niestandardowego zapytania za pośrednictwem WP_Query(), to masz _doing_it_wrong()i napotkasz kilka problemów ( z których przynajmniej będą problemy z paginacją). Pierwszym krokiem do rozwiązania tych problemów będzie konwersja niewłaściwego użycia query_posts()na prawidłowe WP_Query()połączenie.

Używanie query_posts()do modyfikowania głównej pętli

Jeśli chcesz jedynie zmodyfikować parametry zapytania w głównej pętli - na przykład zmienić posty na stronie lub wykluczyć kategorię - możesz mieć ochotę użyć query_posts(). Ale nadal nie powinieneś. Kiedy używasz query_posts(), zmuszasz WordPress do zastąpienia głównego obiektu zapytania. (WordPress faktycznie wykonuje drugie zapytanie i zastępuje $wp_query.) Problem polega jednak na tym, że zastępuje to zbyt późno w celu zaktualizowania stronicowania.

Rozwiązaniem jest odfiltrowanie głównego zapytania przed pobraniem postów za pomocą pre_get_postshaka.

Zamiast dodawać to do pliku szablonu kategorii ( category.php):

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

Dodaj następujące elementy do 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' );

Zamiast dodawać to do pliku szablonu indeksu postów na blogu ( home.php):

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

Dodaj następujące elementy do 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' );

W ten sposób WordPress użyje już zmodyfikowanego $wp_queryobiektu podczas określania stronicowania, bez konieczności modyfikacji szablonu.

Kiedy używać jakiej funkcji

Badania na to pytanie i odpowiedź i na to pytanie i odpowiedź , aby zrozumieć, w jaki sposób i kiedy należy używać WP_Query, pre_get_postsi query_posts().


31
Strony życzeń w kodeksie mogą być tak kompletne.
Pieter Goosen

Chip, sprawiłeś, że mój dzień!
tepkenvannkorn

1
Chip, zawsze oszczędzasz tyle czasu! gdyby tylko Google umieściłoby Cię w rankingu wyżej odpowiedzi (objaśnienie dla googlerów), zanim zwariowałem na szukaniu;) dzięki.
Sagive SEO

Korzystając z twojego przykładu, nie mogłem uruchomić stronicowania, dopóki nie użyłem bloku if-else znalezionego w połowie (zamiast?: Warunkowego) w dół tej strony: themeforest.net/forums/thread/… , bardzo dziwne. W przeciwnym razie ta odpowiedź wiele mnie nauczyła.
P aul

2
Świetna odpowiedź - jedna rzecz: miałem problemy z uruchomieniem funkcji linku do następnego / poprzedniego postu w wywołaniu ajax - to po prostu nie zajęłoby - po szybkim przeszukaniu okazało się, że globalny pagednie jest aktualizowany (obv coś wspólnego z administracją- środowiska ajax.php), więc dodałem to: global $paged; $paged = $custom_query_args['paged']; i zadziałało :)
acSlater

21

Używam tego kodu do niestandardowej pętli z paginacją:

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

Źródło:


1
Kolejny fajny samouczek z wariantem tej odpowiedzi: callmenick.com/post/custom-wordpress-loop-wagpagination
mrwweb

5

Niesamowite jak zawsze Chip. Jako dodatek do tego, rozważ sytuację, w której używasz globalnego szablonu strony dołączonego do strony dla jakiegoś „tekstu wprowadzającego”, a po nim następuje podzapytanie, które chcesz stronicować.

Używając paginate_links () jak wspomniałeś powyżej, z większością domyślnych ustawień (i zakładając, że masz całkiem włączone bezpośrednie linki), twoje linki do stronicowania będą domyślnie ustawione na mysite.ca/page-slug/page/#piękne, ale będą zgłaszać 404błędy, ponieważ WordPress nie wie o tej konkretnej strukturze URL i faktycznie poszukaj strony potomnej „strony”, która jest dzieckiem „strony-ślimaka”.

Sztuczka polega na tym, aby wstawić fajną regułę przepisywania, która ma zastosowanie tylko do tej konkretnej informacji o stronie „pseudoarchiwum”, która akceptuje /page/#/strukturę i przepisuje ją do ciągu zapytania zrozumiałego dla WordPress CAN, a mianowicie mysite.ca/?pagename=page-slug&paged=#. Uwaga pagenamei pagednie namei page(co spowodowało dosłownie GODZINY żalu, motywując tę ​​odpowiedź tutaj!).

Oto reguła przekierowania:

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

Jak zawsze, zmieniając reguły przepisywania, pamiętaj, aby opróżnić linki bezpośrednie , odwiedzając Ustawienia> Łącza stałe w zapleczu administratora.

Jeśli masz wiele stron, które będą się tak zachowywać (na przykład w przypadku wielu niestandardowych typów postów), możesz chcieć uniknąć tworzenia nowej reguły przepisywania dla każdej strony. Możemy napisać bardziej ogólne wyrażenie regularne, które będzie działać dla każdego identyfikatora strony, który zidentyfikujesz.

Jedno podejście jest poniżej:

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

Wady / zastrzeżenia

Jedną wadą tego podejścia, która sprawia, że ​​trochę się wymiotuję w ustach jest twarde kodowanie ślimaka strony. Jeśli administrator kiedykolwiek zmieni informacje o stronie tej pseudoarchiwizowanej strony, toast - reguła przepisywania nie będzie już pasować i otrzymasz przerażające 404.

Nie jestem pewien, czy mogę wymyślić obejście tej metody, ale byłoby miło, gdyby to globalny szablon strony w jakiś sposób uruchomił regułę przepisywania. Pewnego dnia mogę ponownie przeczytać tę odpowiedź, jeśli nikt inny nie złamał tego konkretnego orzecha.


1
Możesz zaczepić post save, sprawdzić, czy strona ma szablon archiwum pod kluczem meta _wp_page_template, a następnie dodać kolejne reguły przepisywania i czyszczenia.
Milo,

2

Zmodyfikowałem zapytanie głównej pętli za pomocą query_posts(). Dlaczego podział na strony nie działa i jak to naprawić?

Świetna odpowiedź Chip stworzony dzisiaj musi zostać zmodyfikowany.
Od pewnego czasu mamy $wp_the_queryzmienną, która powinna być równa $wp_queryglobalnej zaraz po wykonaniu głównego zapytania.

Oto dlaczego część odpowiedzi Chipa:

Włam się do głównego obiektu zapytania

nie jest już potrzebny. Możemy zapomnieć o tej części, tworząc zmienną tymczasową.

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

Teraz możemy zadzwonić:

$wp_query   = $wp_the_query;

lub jeszcze lepiej możemy zadzwonić:

wp_reset_query();

Wszystko inne zarysowane chipy zostają. Po tej części dotyczącej resetowania zapytania można wywoływać funkcje podziału na strony f($wp_query), które zależą od $wp_queryglobalnego.


Aby jeszcze bardziej ulepszyć mechanikę paginacji i dać więcej swobody query_postsfunkcji, stworzyłem to możliwe ulepszenie:

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;
    ?>
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.