Як виправити сторінку з користувацькими циклами?


122

Я додав спеціальний / вторинний запит до файлу шаблону / шаблону користувацької сторінки; як я можу змусити WordPress використовувати мій власний запит для пагинації, замість того, щоб використовувати основний цикл запитів на пагінацію?

Додаток

Я змінив основний запит циклу через query_posts(). Чому пагинація не працює, і як це виправити?

Відповіді:


215

Проблема

За замовчуванням у будь-якому даному контексті WordPress використовує основний запит для визначення сторінки. Основний об'єкт запиту зберігається в $wp_queryглобальному масштабі, який також використовується для виведення основного циклу запитів:

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

При використанні для користувача запиту , необхідно створити абсолютно окремий об'єкт запиту:

$custom_query = new WP_Query( $custom_query_args );

І цей запит виводиться через повністю окремий цикл:

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

Але шаблон розбиття на сторінки теги, в тому числі previous_posts_link(), next_posts_link(), posts_nav_link(), і paginate_links(), засновують свій висновок на основний об'єкт запиту , $wp_query. Цей головний запит може бути, а може і не бути болісним. Якщо поточний контекст - це шаблон користувальницької сторінки, наприклад, основний $wp_queryоб’єкт буде складатися лише з одного повідомлення - ідентифікатора сторінки, якій призначений шаблон користувацької сторінки.

Якщо поточний контекст є якимось індексом архіву, головний $wp_queryможе складатися з достатньої кількості публікацій, щоб викликати сторінки, що призводить до наступної частини проблеми: для основного $wp_queryоб'єкта WordPress передасть paged параметр запиту на основі pagedЗмінна запит URL-адреси. Коли запит буде отримано, цей pagedпараметр буде використовуватися для визначення, який набір докладених публікацій повертати. Якщо натиснуто відображене посилання на сторінку, а наступна сторінка завантажена, ваш користувальницький запит не зможе дізнатись, що сторінка змінилася .

Рішення

Передача правильного параметризованого параметра до спеціального запиту

Якщо припустити, що користувацький запит використовує масив args:

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

Вам потрібно буде передати правильний pagedпараметр масиву. Ви можете зробити це, отримавши змінну URL-запиту, яка використовується для визначення поточної сторінки, за допомогою get_query_var():

get_query_var( 'paged' );

Потім ви можете додати цей параметр до власного масиву аргументів запитів:

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

Примітка. Якщо ваша сторінка є статичною титульною сторінкою , не забудьте використовувати pageїї pagedяк замість статичної титульної сторінки, pageа не paged. Це те, що ви повинні мати для статичної титульної сторінки

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

Тепер, коли користувацький запит буде отримано, буде повернути правильний набір докладених публікацій.

Використання спеціального об’єкта запиту для функцій сторінки

Для того щоб функції пагінації давали правильний вихід - тобто попередні / наступні / посилання на сторінку щодо користувацького запиту, WordPress потрібно змусити розпізнавати спеціальний запит. Для цього потрібен трохи "хак": заміна головного $wp_queryоб'єкта на спеціальний об'єкт запиту $custom_query:

Зламати головний об’єкт запиту

  1. Створіть резервну копію основного об’єкта запиту: $temp_query = $wp_query
  2. Недійсний основний об’єкт запиту: $wp_query = NULL;
  3. Замініть спеціальний запит на основний об’єкт запиту: $wp_query = $custom_query;

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

Цей "злом" необхідно зробити перед тим, як викликати будь-які функції сторінки

Скиньте основний об’єкт запиту

Після того, як були створені функції пагінації, скиньте основний об’єкт запиту:

$wp_query = NULL;
$wp_query = $temp_query;

Виправлення функції сторінки

previous_posts_link()Функція буде працювати в звичайному режимі, незалежно від пагінацію. Він просто визначає поточну сторінку, а потім виводить посилання для page - 1. Однак для next_posts_link()правильного виводу потрібен виправлення . Це тому, що next_posts_link()використовується max_num_pagesпараметр:

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

Як і в інших параметрах запиту, за замовчуванням функція буде використовуватись max_num_pagesдля основного $wp_queryоб'єкта. Для того, щоб змусити next_posts_link()обліковувати $custom_queryоб’єкт, вам потрібно буде передати max_num_pagesфункцію. Ви можете отримати це значення з $custom_queryоб'єкта $custom_query->max_num_pages:

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

Збираючи все це разом

Нижче наведено основну конструкцію певного циклу запитів із належним чином функціонування сторінки:

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

Додаток: Про що query_posts()?

query_posts() для вторинних петель

Якщо ви використовуєте query_posts()для виведення користувальницького циклу, замість того, щоб створювати окремий об'єкт для користувацького запиту через WP_Query(), тоді ви _doing_it_wrong()і зіткнетеся з декількома проблемами (не останньою з них будуть проблеми з пагинацією). Першим кроком до вирішення цих проблем буде перетворення неналежного використання query_posts()на належний WP_Query()виклик.

Використання query_posts()для зміни основного циклу

Якщо ви просто хочете змінити параметри для основного запиту циклу - наприклад, зміни публікацій на сторінці або виключення категорії - ви можете спокуситись використовувати query_posts(). Але ти все одно не повинен. Під час використання query_posts()ви змушуєте WordPress замінювати основний об’єкт запиту. (WordPress насправді робить другий запит і перезаписує $wp_query.) Однак проблема полягає в тому, що він робить цю заміну занадто пізно в процесі оновлення сторінки.

Рішення полягає у фільтруванні основного запиту до отримання публікацій через pre_get_postsгачок.

Замість того, щоб додавати це до файлу шаблону категорії ( category.php):

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

Додайте до 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' );

Замість того, щоб додавати це до файлу шаблону індексу публікацій блогу ( home.php):

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

Додайте до 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' );

Таким чином, WordPress буде використовувати вже модифікований $wp_queryоб'єкт при визначенні сторінки, не потребуючи модифікації шаблону.

Коли використовувати яку функцію

Дослідження на це питання і відповідь і на це питання і відповідь , щоб зрозуміти , як і коли використовувати WP_Query, pre_get_postsі query_posts().


31
Сторінки бажань у кодексі можуть бути настільки повними.
Пітер Гусен

Чіп, ти зробив мій день!
tepkenvannkorn

1
Чіп, ти завжди економиш стільки часу! якби тільки Google поклав би на вас відповіді вище (опис для googlers), перш ніж я пішов з розуму на пошуки;) дякую.
Вибачте SEO

Використовуючи ваш приклад, я не міг змусити підкачки працювати, поки я не використав блок if-else як знайдений посередині (замість?: Conditional ) внизу цієї сторінки: topicforest.net/forums/thread/… , дуже дивно. Інакше ця відповідь мене багато чому навчила.
П авл

2
Чудова відповідь - 1 річ, у мене виникли проблеми із запуском функції наступного / попереднього посилання в дзвінку Ajax - це просто не знадобиться - після швидкого перекопування я виявив, що глобальна pagedне оновлюється (OBV щось спільне з адміністратором- середовище ajax.php), тому я додав це: global $paged; $paged = $custom_query_args['paged']; і він працював :)
acSlater

21

Я використовую цей код для користувальницького циклу з пагінацією:

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

Джерело:


1
Ще один приємний підручник з варіантом цієї відповіді: callmenick.com/post/custom-wordpress-loop-with-pagination
mrwweb

5

Дивовижний, як завжди Чіп. Як доповнення до цього, розгляньте ситуацію, коли ви використовуєте шаблон глобальної сторінки, приєднаний до сторінки для деякого «вступного тексту», а за ним слідує підзапит, який ви хочете прописати на сторінці.

Використовуючи paginate_links (), як ви згадували вище, з переважно за замовчуванням (і якщо припустити, що ви ввімкнули досить постійні посилання), ваші посилання на сторінку для замовчування будуть типовими, mysite.ca/page-slug/page/#але це чудово, але призведе до 404помилок, оскільки WordPress не знає про цю конкретну структуру URL-адреси і насправді шукайте дочірню сторінку "page", яка є дочірньою "page-slug".

Трюк тут полягає в тому, щоб вставити чудове правило перезапису, яке стосується лише тієї конкретної сторінки "псевдоархівної сторінки", яка приймає /page/#/структуру, і переписує її в рядок запиту, який WordPress МОЖЕ зрозуміти, а саме mysite.ca/?pagename=page-slug&paged=#. Зауважте, pagenameа pagedне nameі page(що викликало в мене буквально ЧАСУТЬ горе, мотивуючи цю відповідь тут!).

Ось правило переадресації:

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

Як завжди, змінюючи правила перезапису, не забудьте стерти свої постійні посилання , відвідавши Налаштування> Постійні посилання на задній панелі адміністратора.

Якщо у вас є кілька сторінок, які будуть вести себе таким чином (наприклад, при роботі з декількома спеціальними типами публікацій), можливо, ви захочете уникнути створення нового правила перезапису для кожного сторінки. Ми можемо написати більш загальний регулярний вираз, який працює для будь-якої сторінки, яку ви ідентифікуєте.

Один із підходів нижче:

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

Недоліки / застереження

Одним з недоліків такого підходу, який змушує мене трохи засунутись у рот, є жорстке кодування сторінки «слимак». Якщо адміністратор коли-небудь змінить куточок сторінки цієї псевдоархівної сторінки, тост - правило переписання більше не збігатиметься, і ви отримаєте боязнь 404.

Я не впевнений, що можу придумати спосіб вирішення цього методу, але було б добре, якби шаблон глобальної сторінки певним чином спровокував правило перезапису. Якось я можу переглянути цю відповідь, якщо ніхто інший горіх не зламав.


1
Ви можете підключити збереження допису, перевірте, чи є на сторінці ваш архівний шаблон під мета-клавішею _wp_page_template, а потім додайте ще правила перезапису та стирання.
Міло

2

Я змінив основний запит циклу через query_posts(). Чому пагинація не працює, і як це виправити?

Чудова відповідь Створений чіп потрібно сьогодні змінити.
Деякий час ми маємо $wp_the_queryзмінну, яка повинна дорівнювати $wp_queryглобальній відразу після виконання основного запиту.

Ось чому ця частина відповіді Чіпа:

Зламати головний об’єкт запиту

більше не потрібен. Ми можемо забути цю частину, створивши тимчасову змінну.

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

Тож тепер ми можемо зателефонувати:

$wp_query   = $wp_the_query;

а ще краще ми можемо зателефонувати:

wp_reset_query();

Все, що накреслено, чіп залишається. Після цього запиту-скидання-частини ви можете зателефонувати до функцій сторінки, які є f($wp_query), - вони залежать від $wp_queryглобальної.


Для подальшого вдосконалення механіки розбиття сторінок і надання більшої свободи query_postsфункції я створив таке можливе вдосконалення:

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;
    ?>
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.