Làm thế nào để sửa lỗi phân trang cho các vòng tùy chỉnh?


122

Tôi đã thêm một truy vấn tùy chỉnh / phụ vào tệp mẫu / mẫu trang tùy chỉnh; Làm cách nào để WordPress có thể sử dụng truy vấn tùy chỉnh của tôi để phân trang, thay vì sử dụng phân trang của vòng truy vấn chính?

Phụ lục

Tôi đã sửa đổi truy vấn vòng lặp chính thông qua query_posts(). Tại sao phân trang không hoạt động và làm cách nào để khắc phục?

Câu trả lời:


215

Vấn đề

Theo mặc định, trong bất kỳ bối cảnh cụ thể nào, WordPress sử dụng truy vấn chính để xác định phân trang. Đối tượng truy vấn chính được lưu trữ trong $wp_querytoàn cục, cũng được sử dụng để xuất vòng lặp truy vấn chính:

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

Khi bạn sử dụng truy vấn tùy chỉnh , bạn tạo một đối tượng truy vấn hoàn toàn riêng biệt:

$custom_query = new WP_Query( $custom_query_args );

Và truy vấn đó là đầu ra thông qua một vòng lặp hoàn toàn riêng biệt:

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

Nhưng pagination mẫu thẻ, bao gồm previous_posts_link(), next_posts_link(), posts_nav_link(), và paginate_links(), căn đầu ra của họ trên đối tượng truy vấn chính , $wp_query. Đó là truy vấn chính có thể hoặc không được phân trang. Ví dụ, nếu bối cảnh hiện tại là một mẫu trang tùy chỉnh, thì $wp_queryđối tượng chính sẽ chỉ bao gồm một bài đăng duy nhất - đó là ID của trang mà mẫu trang tùy chỉnh được gán.

Nếu bối cảnh hiện tại là một chỉ mục lưu trữ thuộc loại nào đó, thì chính $wp_querycó thể bao gồm đủ các bài đăng để gây ra phân trang, dẫn đến phần tiếp theo của vấn đề: đối với $wp_queryđối tượng chính , WordPress sẽ chuyển một paged tham số cho truy vấn, dựa trên pagedBiến truy vấn URL. Khi truy vấn được tìm nạp, pagedtham số đó sẽ được sử dụng để xác định tập hợp các bài đăng được phân trang nào sẽ trả về. Nếu liên kết phân trang được hiển thị được nhấp và trang tiếp theo được tải, truy vấn tùy chỉnh của bạn sẽ không có cách nào để biết rằng phân trang đã thay đổi .

Giải pháp

Truyền tham số phân trang chính xác cho truy vấn tùy chỉnh

Giả sử rằng truy vấn tùy chỉnh sử dụng một mảng args:

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

Bạn sẽ cần phải truyền pagedtham số chính xác cho mảng. Bạn có thể làm như vậy bằng cách tìm nạp biến truy vấn URL được sử dụng để xác định trang hiện tại, thông qua get_query_var():

get_query_var( 'paged' );

Sau đó, bạn có thể nối tham số đó vào mảng args truy vấn tùy chỉnh của mình:

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

Lưu ý: Nếu trang của bạn là một trang tĩnh , hãy chắc chắn để sử dụng pagethay vì pagednhư một trang tĩnh sử dụng pagevà không paged. Đây là những gì bạn nên có cho một trang đầu tĩnh

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

Bây giờ, khi truy vấn tùy chỉnh được tìm nạp, tập hợp chính xác các bài đăng được phân trang sẽ được trả về.

Sử dụng đối tượng truy vấn tùy chỉnh cho các chức năng phân trang

Để các chức năng phân trang mang lại đầu ra chính xác - tức là các liên kết trước / tiếp theo / trang liên quan đến truy vấn tùy chỉnh - WordPress cần phải buộc phải nhận ra truy vấn tùy chỉnh. Điều này đòi hỏi một chút "hack": thay thế $wp_queryđối tượng chính bằng đối tượng truy vấn tùy chỉnh , $custom_query:

Hack đối tượng truy vấn chính

  1. Sao lưu đối tượng truy vấn chính: $temp_query = $wp_query
  2. Không có đối tượng truy vấn chính: $wp_query = NULL;
  3. Hoán đổi truy vấn tùy chỉnh vào đối tượng truy vấn chính: $wp_query = $custom_query;

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

Việc "hack" này phải được thực hiện trước khi gọi bất kỳ chức năng phân trang nào

Đặt lại đối tượng truy vấn chính

Khi các chức năng phân trang đã được xuất ra, hãy đặt lại đối tượng truy vấn chính:

$wp_query = NULL;
$wp_query = $temp_query;

Sửa lỗi chức năng

Các previous_posts_link()chức năng sẽ hoạt động bình thường, bất kể phân trang. Nó chỉ xác định trang hiện tại và sau đó xuất liên kết cho page - 1. Tuy nhiên, một sửa chữa là cần thiết next_posts_link()để đầu ra đúng. Điều này là do next_posts_link()sử dụng max_num_pagestham số:

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

Cũng như các tham số truy vấn khác, theo mặc định, hàm sẽ sử dụng max_num_pagescho $wp_queryđối tượng chính . Để buộc next_posts_link()phải tính đến $custom_queryđối tượng, bạn sẽ cần truyền max_num_pageshàm cho hàm. Bạn có thể lấy giá trị này từ $custom_queryđối tượng $custom_query->max_num_pages::

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

Để tất cả chúng cùng nhau

Sau đây là cấu trúc cơ bản của vòng lặp truy vấn tùy chỉnh với các chức năng phân trang hoạt động đúng:

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

Phụ lục: Thế còn query_posts()?

query_posts() cho vòng lặp thứ cấp

Nếu bạn đang sử dụng query_posts()để xuất một vòng lặp tùy chỉnh, sau đó khởi tạo một đối tượng riêng cho truy vấn tùy chỉnh thông qua WP_Query(), thì bạn _doing_it_wrong()sẽ gặp phải một số vấn đề (không phải là ít nhất trong số đó là vấn đề phân trang). Bước đầu tiên để giải quyết những vấn đề đó sẽ là chuyển đổi việc sử dụng không đúng cách query_posts()thành một WP_Query()cuộc gọi thích hợp .

Sử dụng query_posts()để sửa đổi vòng lặp chính

Nếu bạn chỉ muốn sửa đổi các tham số cho truy vấn vòng lặp chính - chẳng hạn như thay đổi bài đăng trên mỗi trang hoặc loại trừ một danh mục - bạn có thể bị cám dỗ sử dụng query_posts(). Nhưng bạn vẫn không nên. Khi bạn sử dụng query_posts(), bạn buộc WordPress thay thế đối tượng truy vấn chính. (WordPress thực sự tạo một truy vấn thứ hai và ghi đè lên $wp_query.) Tuy nhiên, vấn đề là nó thay thế quá muộn trong quá trình cập nhật phân trang.

Giải pháp là lọc truy vấn chính trước khi bài viết được tìm nạp , thông qua pre_get_postshook.

Thay vì thêm phần này vào tệp mẫu danh mục ( category.php):

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

Thêm vào như sau 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' );

Thay vì thêm phần này vào tệp mẫu chỉ mục bài đăng trên blog ( home.php):

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

Thêm vào như sau 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' );

Bằng cách đó, WordPress sẽ sử dụng $wp_queryđối tượng đã được sửa đổi khi xác định phân trang, không yêu cầu sửa đổi mẫu.

Khi nào sử dụng chức năng gì?

Nghiên cứu câu hỏi này và câu trả lờicâu hỏi này và câu trả lời để hiểu làm thế nào và khi nào thì sử dụng WP_Query, pre_get_postsquery_posts().


31
Các trang mong muốn trong codex có thể được hoàn thành.
Pieter Goosen

Chip, bạn đã làm cho ngày của tôi!
tepkenvannkorn

1
Chip, bạn luôn tiết kiệm rất nhiều thời gian! nếu chỉ google sẽ xếp hạng bạn câu trả lời cao hơn (chú thích cho nhân viên Google) trước khi tôi phát điên tìm kiếm;) cảm ơn.
SEO Sagive

Sử dụng ví dụ của bạn, tôi không thể làm cho phân trang hoạt động cho đến khi tôi sử dụng một khối if-if như tìm thấy giữa chừng (thay vì ?: Có điều kiện) xuống trang này: themeforest.net/forums/thread/ trộm , rất lạ. Nếu không thì câu trả lời này đã dạy tôi rất nhiều.
P aul

2
Câu trả lời tuyệt vời - 1 điều, tôi đã gặp vấn đề khi chạy chức năng liên kết bài đăng tiếp theo / trước trong một cuộc gọi ajax - điều đó sẽ không xảy ra - sau khi tìm hiểu nhanh, tôi thấy toàn cầu pagedkhông được cập nhật (phải làm gì đó với quản trị viên- môi trường ajax.php) vì vậy tôi đã thêm cái này: global $paged; $paged = $custom_query_args['paged']; và nó đã hoạt động :)
acSlater

21

Tôi sử dụng mã này cho vòng lặp tùy chỉnh với phân trang:

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

Nguồn:


1
Một hướng dẫn hay khác với một biến thể của câu trả lời này: callmenick.com/post/custom-wordpress-loop-with-pagination
mrwweb

5

Tuyệt vời như mọi khi Chip. Là một phụ lục cho điều này, hãy xem xét tình huống trong đó bạn đang sử dụng mẫu trang toàn cầu được đính kèm với Trang cho một số "văn bản giới thiệu" và theo sau đó là một truy vấn con mà bạn muốn được phân trang.

Sử dụng paginate_links () như bạn đã đề cập ở trên, với phần lớn là mặc định, (và giả sử bạn đã bật permalinks), các liên kết phân trang của bạn sẽ mặc định mysite.ca/page-slug/page/#rất đáng yêu nhưng sẽ gây ra 404lỗi vì WordPress không biết về cấu trúc URL cụ thể đó và thực sự sẽ biết hãy tìm một trang con của "trang" đó là con của "trang sên".

Mẹo ở đây là chèn một quy tắc viết lại tiện lợi chỉ áp dụng cho slug trang "giả trang lưu trữ" cụ thể đó chấp nhận /page/#/cấu trúc và viết lại nó thành một chuỗi truy vấn mà WordPress CÓ THỂ hiểu, cụ thể là mysite.ca/?pagename=page-slug&paged=#. Lưu ý pagenamepagedkhông namepage(điều này gây ra cho tôi nghĩa đen GIỜ đau buồn, thúc đẩy câu trả lời này ở đây!).

Đây là quy tắc chuyển hướng:

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

Như mọi khi, khi thay đổi quy tắc viết lại, hãy nhớ xóa permalinks của bạn bằng cách truy cập Cài đặt> Permalinks trong back-end của Quản trị viên.

Nếu bạn có nhiều trang sẽ hành xử theo cách này (ví dụ: khi xử lý nhiều loại bài đăng tùy chỉnh), bạn có thể muốn tránh tạo quy tắc viết lại mới cho mỗi sên trang. Chúng tôi có thể viết một biểu thức chính quy chung hơn hoạt động cho bất kỳ sên trang nào bạn xác định.

Một cách tiếp cận dưới đây:

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

Nhược điểm / Hãy cẩn thận

Một nhược điểm của phương pháp này khiến tôi khó chịu trong miệng một chút là mã hóa cứng của sên Trang. Nếu quản trị viên thay đổi sên trang của trang lưu trữ giả đó, bạn sẽ nướng - quy tắc viết lại sẽ không còn phù hợp và bạn sẽ nhận được 404 đáng sợ.

Tôi không chắc mình có thể nghĩ ra cách giải quyết cho phương pháp này, nhưng sẽ rất tuyệt nếu đó là mẫu trang toàn cầu bằng cách nào đó đã kích hoạt quy tắc viết lại. Một ngày nào đó tôi có thể xem lại câu trả lời này nếu không có ai khác bẻ khóa hạt đó.


1
Bạn có thể móc lưu bài đăng, kiểm tra xem trang có mẫu lưu trữ của bạn dưới khóa meta hay không _wp_page_template, sau đó thêm quy tắc viết lại và xóa.
Milo

2

Tôi đã sửa đổi truy vấn vòng lặp chính thông qua query_posts(). Tại sao phân trang không hoạt động và làm cách nào để khắc phục?

Câu trả lời tuyệt vời Chip tạo ra cần phải được sửa đổi ngày hôm nay.
Trong một thời gian, chúng ta có $wp_the_querybiến phải bằng với $wp_querytoàn cục ngay sau khi truy vấn chính thực thi.

Đây là lý do tại sao đây là một phần từ câu trả lời của Chip:

Hack đối tượng truy vấn chính

không cần thiết nữa. Chúng ta có thể quên phần này với việc tạo biến tạm thời.

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

Vì vậy, bây giờ chúng ta có thể gọi:

$wp_query   = $wp_the_query;

hoặc thậm chí tốt hơn chúng ta có thể gọi:

wp_reset_query();

Tất cả mọi thứ khác Chip phác thảo ở lại. Sau phần truy vấn-đặt lại đó, bạn có thể gọi các hàm phân trang f($wp_query), - chúng phụ thuộc vào $wp_querytoàn cục.


Để cải thiện hơn nữa cơ chế phân trang và để tự do hơn cho query_postschức năng, tôi đã tạo ra cải tiến có thể này:

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;
    ?>
Licensed under cc by-sa 3.0 with attribution required.