WordPress как на ладони
wordpress jino

Как использовать параметр offset не ломая пагинацию

Эта статья объясняет как правильно использовать параметр offset в запросах.

Подразумевается, что вы уже знакомы с тем, как использовать фильтры WordPress и хорошо представляете работу с классом wpdb.

Частый случай, когда использование параметра offset приводит к неправильной работе пагинации (разбиение информации на страницы). WP использует offset для построения пагинации и неправильное его использование обязательно повлияет на вывод. Чтобы обойти эту проблему, нужно в ручную внедрится в пагинацию: нужно определить имеет ли цикл дополнительные страницы пагинации и динамически определить параметр offset для текущей страницы. Делается это через фильтр pre_get_posts. Также, нужно отрегулировать вычисление пагинации самим WP, чтобы исключить посты которые мы хотим "пропустить" параметром offset. Это можно сделать, используя фильтр found_posts, который поможет нам "отминусовать" нужное количество постов.

Суть проблемы

offset полезен тем, что позволяет разработчикам пропускать указанное количество постов перед тем как начать их выводить. Но многие начинающие разработчики сталкиваются с проблемой, когда при использовании offset параметра в запросе ломается пагинация на сайте.

Такое поведение логично, если понять что происходит. Аргумент offset используется WP при построении пагинации и когда разработчики устанавливают его вручную, пагинация перестает работать, потому что этот аргумент переписан, а WP меняет его динамически на каждой странице пагинации.

Решение проблемы

Чтобы вы могли использовать параметр offset в запросах WP и при этом пагинация работала как надо, нужно указывать offset динамически для каждой страницы, с учетом начальной "сдвижки" постов. Также нужно пересчитать количество страниц в пагинации. Сделать это можно, используя 2 хука:

pre_get_posts - позволят внедрятся в запрос до того как он запускается. Тут мы должны убедиться, что наш offset применяется только на первой странице.

found_posts - позволяет скорректировать подсчет результата запроса. Тут мы должны убедиться, что наш offset применяется при подсчете на всех других страницах, кроме первой.

offset и корректировка пагинации

Первый шаг.

Использование хука pre_get_posts. Так как этот хук является действием, а не фильтром (фильтр подразумевает обработку и возврат обработанных данных) нам нужно передать объект $query в нашу произвольную функцию по ссылке, это позволит нам изменять объект напрямую внутри класса, не возвращая никаких данных.

Чтобы убедиться, что мы изменяем нужный нам запрос, нам нужна небольшая предварительная проверка в начале $query->is_posts_page. В этом примере мы хотим, чтобы на главной странице сайта было пропущено 10 первых постов:

add_action('pre_get_posts', 'myprefix_query_offset', 1 );
function myprefix_query_offset(&$query) {
	// проверим что это нужный нам запрос...
	if ( ! $query->is_posts_page ) {
		return;
	}

	// определим нужный нам отступ...
	$offset = 10;

	// определим сколько постов на странице мы будет выводить (получим данные из настроек)
	$ppp = get_option('posts_per_page');

	// если это страница пагинации...
	if ( $query->is_paged ) {

		// вычислим отступ на странице пагинации (offset + текущая страница (минус один) x количество постов на странице)
		$page_offset = $offset + ( ($query->query_vars['paged']-1) * $ppp );

		// применим вычисленный отступ (offset)
		$query->set('offset', $page_offset );

	}
	else // если это не страница пагинации (первая страница)
	{

		// используем только отступ (offset)...
		$query->set('offset',$offset);

	}
}

Второй шаг.

WP не учтет наш отступ, когда будет подсчитывать количество постов в запросе. Теперь, посты будут выводиться как надо, но когда WP будет строить номера пагинации, он будет считать что постов на 10 больше и добавит одну лишнюю страницу пагинации, если предположить, что на странице выводится 10 постов. Последняя страница пагинации будет существовать, но перейдя по ссылке наш измененный запрос вернет пустой результат. Упс!

Эта маленькая проблема пагинации решается с помощью фильтра found_posts:

add_filter('found_posts', 'myprefix_adjust_offset_pagination', 1, 2 );
function myprefix_adjust_offset_pagination($found_posts, $query) {

	// опять определяем наш отступ...
	$offset = 10;

	// опять убедимся, что мы редактируем нужный нам запрос (на главной)...
	if ( $query->is_posts_page ) {
		// изменим количество найденных постов, уменьшим на наш отступ (10)...
		return $found_posts - $offset;
	}
}

Теперь, все готово! Пагинация и нужный нам отступ должны работать корректно.

Создание сайта интернет магазина

Web Responsive PRO - создание сайта интернет магазина по доступным ценам.

wrp.ru

Как использовать параметр offset не ломая пагинацию 15 комментариев
Полезные 1 Все
  • yurec cайт: pvp-games.ru

    Напишите пожалуйста подробную статью о сниппетах, привязку авторства + код структурированных данных. Как правильно все оформить, чтоб получить картинку с мордочкой возле своего сниппета... Подробной инфы не могу найти

    Ответить4.2 года назад #
    • Kama4464

      Я не очень хорошо в этом разбираюсь. Но спасибо за предложение, есть желание разобраться. Очень возможно напишу нечто подобное.

      Ответить4.2 года назад #
  • Дмитрий @

    Помогите пожалуйста довести до ума пагинацию при выводе главной страницы.
    Используется тема aggregate от elegantthemes. Нужно вывести в списке постов все, кроме первых 5. Сдвиг делал через offset. Для главной вызывается отдельный файл, отвечающий за вывод постов через includes/entry-homе.
    Код дом. страницы (часть):

    <div id="left-area">
    		<h4 class="main-title"><?php esc_html_e('Most Recent Articles','Aggregate'); ?></h4>
    		<div id="entries">
    			<?php get_template_part('includes/entry-home','home'); ?>
    		</div> <!-- end #entries -->
    	</div> <!-- end #left-area -->

    А вот сам вывод постов (упрощенно):

    <?php    
    
    	query_posts($query_string.'&offset=5');
    
    	if (have_posts()) : while (have_posts()) : the_post(); ?>
    	выводим посты
    	<!-- end .post-->
    <?php endwhile; ?>
    
    <?php if(function_exists('wp_pagenavi')) { wp_pagenavi(); }
    	else { ?>
    		 <?php get_template_part('includes/navigation','entry-home'); ?>
    	<?php } ?>
    
    <?php
    // if(function_exists('kama_pagenavi')) { kama_pagenavi(); }
    //  else { get_template_part('includes/navigation','entry-home');  } 
    ?>
    
    <?php else : ?>
    	<?php get_template_part('includes/no-results','entry-home'); ?>
    <?php endif; ?>

    Причем эксперименты с кодом типа:

    $page = (get_query_var('paged')) ? get_query_var('paged') : 1;
    
    query_posts( array( 'offset' => 5, 'paged' => get_query_var('paged') ) );

    Ничего не дает. На 2 и далее страницах выводится то самое, что и на первой. Независимо от того, чей плагин пагинации. Если прописать в функциях код из этой статьи, то пагинация вообще не выводится.
    Также на главной выводится слайдер с первыми 5 постами. Если перед циклом включить код типа reset_query - результат не меняется.
    Пока писал, родилась идея временно отключить слайдер. Если изменения будут - напишу сюда.

    Помогите пожалуйста советомsmile

    Ответить4.1 года назад #
    • Дмитрий @

      Решение пришло случайно.
      В коде автора в статье исправил проверки с is_posts_page на is_home (у меня же это в шаблоне прописано как главная).
      Также удалил код перед циклом с офсетом. Все сразу начало работать.
      В общем, спасибо за код в статье и за справочник по функциям вордпресса. 2 дня перечитывал.

      Ответить4.1 года назад #
  • Дмитрий @

    Столкнулся с проблемой, как определить срабатывание кода на главной и на страницах определенных категорий?
    Если прописать в условии

    if ((  !$query->is_home ) || (  !$query->is_category(array(8,7)) ) )
    • то не срабатывает код. Офсета нет, пагинация работает. Если в коде внести что-то одно (или категории или дом. страницу) - работает.
      В условии в фильтре правки соответственно.
    if ((  $query->is_home ) || (  $query->is_category(array(8,7)) ) )

    Помогите пожалуйста разобраться.

    Ответить4.1 года назад #
    • Kama4464

      Немного странная логика в коде, почему не так:

      if ( $query->is_home || $query->is_category(array(8,7)) ){
         // код для главной или категорий 7,8
      }
      else{
         // код для остальных страниц
      }

      В вашем случае, если все правильно работает по отдельности, при отрицании, нужно использовать &&:

      if ( !$query->is_home && !$query->is_category(array(8,7)) ){
         // код не для главной и не для категорий 7,8
      }
      Ответить4.1 года назад #
      • Дмитрий @

        Спасибо за ответ.
        Используя код из последнего блока офсет работает, но исчезает пагинацияsad
        Используя код с проверкой основного запроса работает и пагинация и сдвиг на 5 постов там, где нужно. Но при этом и в админке не выводится 5 последних постов.
        Получается, что основной запрос использует слайдер, который выводится перед списком постов. Там их 5 последних. Пробовал разные варианты, то основной запрос меняется, то нет. Помогите пожалуйста.

        полный код:

        add_action('pre_get_posts', 'myprefix_query_offset', 1 );
        function myprefix_query_offset(&$query) {
        	// проверим что это нужный нам запрос...
        if ( !$query->is_main_query() && !$query->is_home )      return;    
        if ( !$query->is_main_query() && !$query->is_category(array(8,7,16,10,15)))      return;  
        
        	// определим нужный нам отступ...
        	$offset = 5;
        
        	// определим сколько постов на странице мы будет выводить (получим данные из настроек)
        	$ppp = get_option('posts_per_page');
        	//$ppp = 8;
        
        	// если это страница пагинации...
        	if ( $query->is_paged ) {
        
        		// вычислим отступ на странице пагинации (offset + текущая страница (минус один) x количество постов на странице)
        		$page_offset = $offset + ( ($query->query_vars['paged']-1) * $ppp );
        
        		// применим вычисленный отступ (offset)
        		$query->set('offset', $page_offset );
        
        	}
        	else // если это не страница пагинации (первая страница)
        	{
        
        		// используем только отступ (offset)...
        		$query->set('offset',$offset);
        
        	}
        }
        
        add_filter('found_posts', 'myprefix_adjust_offset_pagination', 1, 2 );
        function myprefix_adjust_offset_pagination($found_posts, $query) {
        
        	// опять определяем наш отступ...
        	$offset = 5;
        
        	// опять убедимся, что мы редактируем нужный нам запрос (на главной)...
         if (  $query->is_main_query() && $query->is_home )   return $found_posts - $offset;    
        if (  $query->is_main_query() && $query->is_category(array('8','7','16','10','15')))    return $found_posts - $offset;
        Ответить4.1 года назад #
        • Дмитрий @

          Решил. good Добавил к коду выше:

          if( !is_admin() ){

          , а в конце кода добавил

          }

          .
          В админке код не срабатывает, пагинация в списке статей не обрезается, а на сайте все работает как надо.
          Извините, если морочил голову не разобравшись. dash Но в поиске решения столько нового узнал, особенно о функциях вордпресса.
          Подобное решение может пригодиться тем, кто задумает использовать слайдеры SlideDeck 2 с выводом последних постов и обрезанием их от общей ленты в выводе страницы. Все это в теме Aggregate от elegantthemes. Скорее всего подобная особенность есть и в других их темах, поскольку уж много своих функций и "улучшений" они вшили.

          Ответить4.1 года назад #
  • Максим

    Здравствуйте, такая проблема. Необходимо пропустить на главной 7 первых постов, т.к. они выводятся в специальном блоке при помощи виджетов. Представленный пример не работает. Если заменить is_posts_page на is_home в примере автора, тогда все пропускается как надо, но при этом пропускаются и посты которые выводятся при помощи виджетов. Как сделать так, чтобы виджеты так и продолжали отображать первые 7 новостей, а основной блок постов пропускал эти 7 записей и отображал следующие? Заранее спасибо.

    Ответить3.7 года назад #
    • Kama4464

      ммм... Попробуйте определить то что есть в основном запросе и нет в запросе виджета, они ведь разные, а значит какое-то отличие должно быть в глобальной переменной $wp_query. И на основе этого отличия вставьте отступ только к основному запросу, добавив дополнительное условие к

      if ( ! $query->is_posts_page && $query->отличная_переменна ) {
      	return;
      }

      Чтобы увидеть переменные попробуйте оборвать и вывести на экран объект в момент срабатывания хука:

      add_action('pre_get_posts', 'myprefix_query_offset', 1 );
      function myprefix_query_offset(&$query) {
      	die( print_r($query) );
      ...

      тут думаю нужно будет отключить сначала виджет, чтобы посмотреть основной запрос, затем наоборот...

      Ответить3.7 года назад #
      • Максим

        Спасибо большое за ответ smile Только не получилось. Не понимаю я как найти отличающуюся переменную. Вашим методом попробовал, только разницы в объекте при включенном и выключенном виджете нет. Одни и те же переменные остаются. Виджет удалял, отключал, удалял вручную панель виджетов, результат один.

        Вообще циклы немного отличаются. Если в основном:

        <?php  if ( have_posts() )  : ?>
        <?php while ( have_posts() ) :  the_post(); ?>

        То в цикле виджета еще передается массив с данными для вывода:

        <?php if( $npc_query->have_posts() ) : ?>
        <?php while( $npc_query->have_posts() ) : $npc_query->the_post(); ?>

        Вот один из примеров такого массива:

        $npc_query = new WP_Query( array(
        	 'cat' => $cat,
        	 'posts_per_page' => $number,
        	 'post_not_in' => $sticky,
        	 'ignore_sticky_posts' => 1,
        	 'orderby' => 'date',
        	 'order' => 'DESC'
        					));

        Может это как-то может помочь определить переменную с которой можно разделить циклы вывода новостей виджета и основной ленты? Заранее спасибо.

        Ответить3.6 года назад #
        • Kama4464

          Попробуйте указать $query->is_main_query()

          if ( ! $query->is_posts_page || ! $query->is_main_query() ) {
          	return;
          }

          Так ваш офсет будет относиться только если это основной запрос. А виджеты как я вижу создают дополнительный экземпляр класса.

          Еще один вариант указать в запросах виджетов аргумент suppress_filters=true. Так запросы виджетов станут нечувствительны к изменениям через хуки вроде pre_get_posts.

          Ответить3.6 года назад #
          • Максим

            Тимур, все отлично, предложенный вами вариант работает так, как надо! smile Огромное Вам спасибо yes

            Полное решение моей проблемы оказалось таким, мало ли кому пригодится:

            // проверим что это нужный нам запрос...
            	if ( ! $query->is_home || ! $query->is_main_query() ) {
            	return;
            }
            // опять убедимся, что мы редактируем нужный нам запрос (на главной)...
               if ( $query->is_home ) {
               return $found_posts - $offset;
            }

            Заменил только эти две части, остальная часть кода такая же, как в статье.

            Ответить3.6 года назад #
  • Виталий @

    Здравствуйте. Очень полезная статья и весьма кстати, но почему-то у меня пропадает пагинация совсем. Если я Вас правильно понял, то мне нужно вставить код из вашего примера в файл functions.php
    Мой цикл я использую в шаблоне front-page.php. Вот его пример, буду рад любому совету.

    $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();
    		echo '<h1>' . get_the_title() . '_' . get_the_id() .  '</h1>';
    		// Loop output goes here
    	endwhile;
    endif;
    // Reset postdata
    wp_reset_postdata();
    
    the_posts_pagination();
    
    // Reset main query object
    $wp_query = NULL;
    $wp_query = $temp_query;
    • Виталий @

      Решил самостоятельно, нужно лишь правильно сделать проверку страницы. Заменил Ваш, is_posts_page на is_home() smile

Здравствуйте, !

Ваш комментарий