Как исправить пагинацию для пользовательских циклов?
Я добавил пользовательский/вторичный запрос в файл шаблона/пользовательский шаблон страницы; как сделать так, чтобы WordPress использовал мой пользовательский запрос для пагинации вместо пагинации основного цикла запросов?
Дополнение
Я модифицировал запрос основного цикла с помощью query_posts()
. Почему пагинация не работает и как это исправить?
Проблема
По умолчанию 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
будет состоять только из одной записи — той, чей ID соответствует странице, к которой прикреплен пользовательский шаблон.
Если текущий контекст — это архивный индекс какого-либо типа, основной $wp_query
может содержать достаточно записей для пагинации, что приводит к следующей части проблемы: для основного объекта $wp_query
WordPress передаст параметр paged
в запрос, основываясь на URL-параметре paged
. При выполнении запроса этот параметр paged
будет использоваться для определения, какой набор разбитых на страницы записей вернуть. Если будет нажата ссылка пагинации и загружена следующая страница, ваш пользовательский запрос не будет знать, что пагинация изменилась.
Решение
Передача правильного параметра paged в пользовательский запрос
Предположим, что пользовательский запрос использует массив аргументов:
$custom_query_args = array(
// Пользовательские параметры запроса здесь
);
Вам нужно передать правильный параметр 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
:
Изменение основного объекта запроса
- Создаем резервную копию основного объекта запроса:
$temp_query = $wp_query
- Обнуляем основной объект запроса:
$wp_query = NULL;
Заменяем основной объект запроса на пользовательский:
$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( 'Старые записи' , $custom_query->max_num_pages ); ?>
Объединяем все вместе
Ниже приведена базовая структура пользовательского цикла запроса с правильно работающими функциями пагинации:
// Определяем пользовательские параметры запроса
$custom_query_args = array( /* Параметры здесь */ );
// Получаем текущую страницу и добавляем в массив параметров запроса
$custom_query_args['paged'] = get_query_var( 'paged' ) ? get_query_var( 'paged' ) : 1;
// Создаем пользовательский запрос
$custom_query = new WP_Query( $custom_query_args );
// Исправление пагинации
$temp_query = $wp_query;
$wp_query = NULL;
$wp_query = $custom_query;
// Вывод пользовательского цикла
if ( $custom_query->have_posts() ) :
while ( $custom_query->have_posts() ) :
$custom_query->the_post();
// Здесь вывод цикла
endwhile;
endif;
// Сбрасываем данные поста
wp_reset_postdata();
// Пагинация для пользовательского цикла
previous_posts_link( 'Старые записи' );
next_posts_link( 'Новые записи', $custom_query->max_num_pages );
// Восстановление основного объекта запроса
$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 ) {
// Проверяем, является ли это архивом категории
// и убеждаемся, что запрос основной
// а не вторичный (например, меню навигации
// или виджет последних записей и т.д.
if ( is_category() && $query->is_main_query() ) {
// Изменяем количество записей на странице
$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 ) {
// Проверяем, является ли это главной страницей блога
// и убеждаемся, что запрос основной
// а не вторичный (например, меню навигации
// или виджет последних записей и т.д.
if ( is_home() && $query->is_main_query() ) {
// Исключаем категорию с 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()
.

Чип, ты всегда так экономишь время! Если бы только Google ранжировал твои ответы выше (призыв к гуглерам), прежде чем я сойду с ума от поиска ;) спасибо.

Используя ваш пример, мне не удалось заставить пагинацию работать, пока я не использовал блок if-else, как описано в середине этой страницы (вместо условного оператора ? :): http://themeforest.net/forums/thread/pagination-on-wordpress-static-page-set-to-front-page/28120, очень странно. В остальном этот ответ многому меня научил.

Отличный ответ - есть один момент: у меня были проблемы с использованием функции next/previous post link в ajax-запросе - она просто не срабатывала. После быстрого поиска я обнаружил, что глобальная переменная paged
не обновлялась (очевидно, из-за окружения admin-ajax.php), поэтому я добавил это:
global $paged;
$paged = $custom_query_args['paged'];
и это сработало :)

...Я не использую слово 'герой' легкомысленно, но вы величайший герой в американской истории. - Лайонел Хатц

У меня сейчас есть пагинация для моего пользовательского цикла на главной странице, которая отображает дочерние страницы. При попытке перейти на вторую страницу через http:example.com/main_page/page/2
происходит перенаправление обратно на http://example.com/main_page
. Есть идеи, почему так происходит?

И не забудьте сбросить структуру постоянных ссылок (пересохраните настройки структуры постоянных ссылок)

Работает ли это решение в WordPress 4.5+? Когда я следую этим инструкциям, результат отображается как "новые записи" на первой странице, при клике переход на вторую страницу происходит, но результаты подзапроса всё равно показывают первые 10 записей.

Долго боролся со своей пагинацией, пока не наткнулся на эту важную деталь: Примечание: Если ваша страница является статической главной страницей, убедитесь, что используете page вместо paged, так как статическая главная страница использует page, а не paged. Вот что должно быть для статической главной страницы

Я использую этот код для создания пользовательского цикла с пагинацией:
<?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') ?> от <?php the_author_posts_link() ?></small>
<div><?php the_excerpt(); ?></div>
</article>
<?php
endwhile;
?>
<?php if ($custom_query->max_num_pages > 1) : // пользовательская пагинация ?>
<?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( 'Более старые записи', $custom_query->max_num_pages ); ?>
</div>
<div class="next-posts-link">
<?php echo get_previous_posts_link( 'Более новые записи' ); ?>
</div>
</nav>
<?php
$wp_query = $orig_query; // исправление для работы пагинации
?>
<?php endif; ?>
<?php
wp_reset_postdata(); // сброс запроса
else:
echo '<p>'.__('Извините, нет записей, соответствующих вашим критериям.').'</p>';
endif;
?>
Источник:

Отлично, как всегда, Chip. В качестве дополнения к этому, рассмотрим ситуацию, когда вы используете глобальный шаблон страницы, прикрепленный к Странице для некоторого "вводного текста", за которым следует подзапрос, который вы хотите разбить на страницы.
Используя paginate_links(), как вы упомянули выше, в основном с настройками по умолчанию (и предполагая, что у вас включены красивые постоянные ссылки), ваши ссылки пагинации по умолчанию будут выглядеть как mysite.ca/page-slug/page/#
, что прекрасно, но вызовет ошибки 404
, потому что WordPress не знает об этой конкретной структуре URL и будет искать дочернюю страницу "page", которая является дочерней для "page-slug".
Трюк здесь заключается в добавлении удобного правила перезаписи, которое применяется только к этому конкретному "псевдо-архивному" 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" );
Как всегда, при изменении правил перезаписи не забудьте сбросить ваши постоянные ссылки, посетив Настройки > Постоянные ссылки в админ-панели.
Если у вас есть несколько страниц, которые будут вести себя таким образом (например, при работе с несколькими пользовательскими типами записей), вы можете избежать создания нового правила перезаписи для каждого slug страницы. Мы можем написать более общее регулярное выражение, которое работает для любого slug страницы, который вы укажете.
Один из подходов приведен ниже:
function wpse_120407_pseudo_archive_rewrite(){
// Добавьте slugs страниц, которые используют Глобальный Шаблон для имитации "архивной" страницы
$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' );
Недостатки / Предостережения
Один недостаток этого подхода, от которого мне немного подташнивает, — это жесткое указание slug страницы. Если администратор когда-либо изменит slug этой псевдо-архивной страницы, вы в беде — правило перезаписи больше не сработает, и вы получите ужасную ошибку 404.
Не уверен, что могу придумать обходной путь для этого метода, но было бы здорово, если бы глобальный шаблон страницы каким-то образом активировал правило перезаписи. Возможно, однажды я вернусь к этому ответу, если никто не найдет решение этой проблемы.

Я изменил основной цикл запроса с помощью
query_posts()
. Почему пагинация не работает и как это исправить?
Отличный ответ, созданный Chip, требует обновления на сегодняшний день.
Уже некоторое время у нас есть переменная $wp_the_query
, которая должна быть равна глобальной переменной $wp_query
сразу после выполнения основного запроса.
Поэтому часть из ответа Chip:
Хак основного объекта запроса
больше не нужна. Мы можем забыть эту часть с созданием временной переменной.
// Исправление пагинации
$temp_query = $wp_query;
$wp_query = NULL;
$wp_query = $custom_query;
Теперь мы можем просто вызвать:
$wp_query = $wp_the_query;
или, что еще лучше, мы можем вызвать:
wp_reset_query();
Все остальное, что описал Chip, остается в силе.
После этой части сброса запроса вы можете вызывать функции пагинации, которые зависят от глобальной переменной $wp_query
.
Для дальнейшего улучшения механики пагинации и предоставления большей свободы функции query_posts
я создал это возможное улучшение:

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}'), // Добавьте аргументы meta_query если необходимо
'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();
// Добавьте ваш код вывода записей здесь
endwhile;
wp_reset_query();
// Управление пагинацией на основе кастомного запроса.
$GLOBALS['wp_query']->max_num_pages = $query->max_num_pages;
the_posts_pagination(array(
'mid_size' => 1, // Количество страниц по бокам от текущей
'prev_text' => __('Предыдущая страница', 'patelextensions'), // Текст для предыдущей страницы
'next_text' => __('Следующая страница', 'patelextensions'), // Текст для следующей страницы
'before_page_number' => '<span class="meta-nav screen-reader-text">' . __('Страница', 'patelextensions') . ' </span>', // Текст перед номером страницы
));
else:
?>
<div class="container text-center"><?php echo _d('Результаты не найдены','30'); ?></div>
<?php
endif;
?>
