WP_Query и пагинация на главной странице WordPress

По умолчанию WordPress использует main query для построения и вывода пагинации. Основной объект запроса хранится в
глобальной переменной $wp_query, которая также используется для вывода цикла main 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(), основывают свой вывод на запросе main query$wp_query. Этот основной запрос main query может быть или не быть разбит на страницы. Например, если текущий контекст представляет собой пользовательский шаблон страницы, основной объект $wp_query будет состоять только из одной записи — страницы, ID которой назначен пользовательский шаблон страницы.

Если текущий контекст представляет собой некоторый архивный индекс, запрос $wp_queryможет состоять из достаточного количества записей, чтобы вызвать разбиение на страницы, что приводит к следующей части проблемы: для основного объекта $wp_query WordPress передает в запрос параметр paged на основе paged URL переменной запроса. При вызове запроса этот параметр paged будет использоваться для определения того, какой набор постраничных постов должен возвращаться. Если нажать на одну из ссылок пагинации и загрузить следующую страницу, пользовательский запрос не сможет узнать, что нумерация страниц изменилась.

Решение

Передача правильного параметра 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, так как статическая главная страница не работает с 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 без необходимости изменения шаблона.

Пример пользовательского цикла с пагинацией:

<?php
if ( get_query_var('paged') ) {
    $paged = get_query_var('paged');
} elseif ( get_query_var('page') ) { // на статической главной странице используется 'page' вместо 'paged' 
    $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; // исправление для работы пагинации
        $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; // исправление для работы пагинации
        ?>
    <?php endif; ?>

<?php
    wp_reset_postdata(); // сброс запроса 
else:
    echo '<p>'.__('Sorry, no posts matched your criteria.').'</p>';
endif;
?>

6 Comments

  1. Михаил10.02.2019

    Спасибо за пример в конце!, использовал его подправив немного, под стили шаблона и смог вывети пагинацию на главной странице, с десяток вариантов перепробовал, часов 6 наверное убил на поиски рабочего варианта.

    Ответить
  2. Сергей26.03.2019

    А что и насчет использования offset, не пробовали? Никак не получается найти решение по работе пагинации… Отступ и количество постов на главной корректно выводиться:

    6, 'offset' => 4),
    $wp_query->query
    )
    );
    if (have_posts()) : ?>


    и страницы пагинации открываются, но с тем же содержимым. Убираю offset и все в норму приводиться. Не подскажите как решить?

    Ответить
    1. Алексей31.07.2019

      У меня похожая проблема была с выводом записей через произвольный цикл. Я решил ее так:

      $args = array( … );

      if ( $args[‘offset’] == 0 ) {
      $args[‘offset’] = ( $args[‘paged’] — 1 ) * $args[‘posts_per_page’];
      }

      if ( $args[‘offset’] > 0 ) {
      $args[‘offset’] = ( $args[‘paged’] — 1 ) * $args[‘posts_per_page’] + $args[‘offset’];
      }

      Я не уверен, что это правильно, но у меня работает…

      Ответить
  3. Atkham17.07.2019

    Спасибо, работает

    Ответить
  4. Максим04.08.2019

    Помогите с пагинацией на главной. Купил тему, а в ней некорректно работает пагинация.

    Ответить
  5. Евгений26.04.2021

    Никогда не пишу комменты, но тут. Дружище ты реально спас своим решением)

    Ответить

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Scroll to top