WordPress как на ладони
Недорогой хостинг для сайтов на WordPress: wordpress.jino.ru Шаблоны сайтов на русском

pre_get_posts хук-событие . WP 2.0

Событие срабатывает перед каждым запросом WP_Query. До того, как был сделан запрос в базу. Используется для изменения запроса.

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

ВАЖНО! pre_get_posts срабатывает для каждого запроса WP_Query: основной запрос, дополнительный запрос, запрос в админ-панели, запросы в виджетах и т.д.

Поэтому нужно убедитесь, что вы изменяете именно нужны вам запрос, для этого перед изменением запроса используйте всевозможные Условные теги, чтобы точно ограничить изменение (см. примеры).

Часто используемые функции внутри хука pre_get_posts:

Использование

add_action( 'pre_get_posts', 'action_function_name_11' );
function action_function_name_11( $query ) {
	// Действия...
}

Параметры

$query(объект)
Объект WP_Query.

Заметки

Аргумент передается по ссылке

Объект $query предается по ссылке, поэтому нет необходимости определять глобальную переменную. Любые изменения $query внутри функции влияют сразу на оригинальный объект.

Запросы постоянных страниц

pre_get_posts нельзя использовать для изменения запросов связанных с постоянными страницами, потому что 'is_page', 'is_singular', 'pagename' и другие параметры (зависимые к ЧПУ) уже установлены в объекте. Рекомендуется использовать new WP_Query в шаблоне постоянной страницы, чтобы изменить запрос.

Определение нужного запроса

При использовании pre_get_posts, нужно точно определить, в какой конкретно запрос вы вносите изменения. Полезный метод для этого: $query->is_main_query() - он поможет вам убедиться, что вносимые изменения будут влиять только на основные запросы. Используйте его в связке с условными тегами. Чтобы изменять запрос только для страниц, которые вам нужны.

Например, мы хотим изменить запрос на странице категорий и не делаем проверку is_category(), тогда наши изменения будут влиять на формирования запроса в админ-панели, на других страницах сайта и где угодно еще. Поэтому четко определяйте, для какого запроса вы вносите изменения через действие-хук pre_get_posts.

Использование в админ-панели

Этот фильтр можно также использовать для изменения запросов в админ-панели. В таких случаях, убедитесь что внесенные изменения будут работать на странице вывода записей. Например, проверка $query->is_main_query() и is_post_type_archive('movie') (изменяем запрос для лицевой части для записей типа movie), изменит также запрос на страницы edit.php?post_type=movie. Чтобы этого не произошло, нужно использовать еще проверку ! is_admin().

Внимание! Условные теги

pre_get_posts срабатывает до того, как объект WP_Query полностью определится. Поэтому некоторые условные теги, опирающиеся на данные WP_Query еще не работают. Например, is_front_page() не работает, тогда как is_home() будет работать. Поэтому вам лучше работать с данными объекта напрямую, например: $query->is_search.

Весь список свойств которые можно использовать вместо условного тега:

$query->is_404
$query->is_admin
$query->is_archive
$query->is_attachment
$query->is_author
$query->is_category
$query->is_comments_popup
$query->is_comment_feed
$query->is_date
$query->is_day
$query->is_feed
$query->is_home
$query->is_month
$query->is_page
$query->is_paged
$query->is_posts_page
$query->is_post_type_archive
$query->is_preview
$query->is_robots
$query->is_search
$query->is_single
$query->is_singular
$query->is_tag
$query->is_tax
$query->is_time
$query->is_trackback
$query->is_year

// функции
$query->is_front_page()
$query->is_main_query()

Все остальные условные теги нужно заменить проверкой или методом класса. Например, is_front_page(), нужно заменить на такую проверку:

if( $query->is_home || ($query->get('page_id') == get_option('page_on_front')) ){
	// это front_page
}

или такой метод:

if( $query->is_front_page() ){
	// это front_page
}

is_main_query() не рекомендуется использовать внутри этого хука. Нужно отдать предпочтение методу $query->is_main_query().

Отступы и пагинация

Использование аргумента offset в любом запросе может сломать пагинацию. Поэтому, если вы используете offset, то вам нужно изменить запрос для всех страниц пагинации, опираясь на первоначальный отступ (offset). Подробнее об этом я писал в этой статье: Как использовать параметр offset не ломая пагинацию.

Примеры

#1 Исключение категорий на главной

Этот пример показывает как убрать посты из указанных категорий из вывода на главной странице блога. Например, у нас есть 2 категории с ID 1 и 1347, которые нам не нужно показывать на главной. Чтобы исключить эти категории из запроса, используйте такой код в плагине или в теме:

function exclude_category( $query ) {
	if ( $query->is_front_page() && $query->is_main_query() ) {
		$query->set( 'cat', '-1,-1347' );
	}
}
add_action( 'pre_get_posts', 'exclude_category' );

#2 Исключение постоянных страниц из результатов поиска

Когда пользователи вашего сайта ищут что-либо, часто в результатах поиска могут попадаться постоянные страницы, которые в принципе совсем не нужны в результатах поиска, и которые можно исключить из поиска насовсем. Используйте хук pre_get_posts, чтобы исключить из результатов поиска постоянных страницы:

add_action( 'pre_get_posts', 'search_filter' );
function search_filter( $query ){
	if( ! is_admin() && $query->is_main_query() ){
		if( $query->is_search ){
			$query->set('post_type', 'post');
		}
	}
}

#3 Включение произвольного типа записей в результаты поиска

Включать в поиск произвольный тип записи или нет, устанавливается при регистрации типа записи, в аргументах функции register_post_type(): аргумент public=true добавляет в результат поиска произвольный тип записи.

Если, произвольный тип не включен в поиск, но нужно чтобы он участвовал в поиске, то используйте такой код, аналог предыдущего:

add_action('pre_get_posts', 'get_posts_search_filter');
function get_posts_search_filter( $query ){
	if ( ! is_admin() && $query->is_main_query() && $query->is_search ) {
		$query->set('post_type', array('post', 'movie') );
	}
}

#4 Изменение количества выводимых на странице постов

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

Этот пример, показывает как перезаписать параметр posts_per_page для страницы архивов произвольного типа записи movie:

add_action('pre_get_posts', 'hwl_home_pagesize', 1 );
function hwl_home_pagesize( $query ) {
	// Выходим, если это админ-панель или не основной запрос.
	if( is_admin() || ! $query->is_main_query() )
		return;

	if( is_home() ){
		// Выводим только 1 пост на главной странице
		$query->set( 'posts_per_page', 1 );
	}

	if( $query->is_post_type_archive('movie') ){
		// Выводим 50 записей если это архив типа записи 'movie'
		$query->set( 'posts_per_page', 50 );
	}
}

Пример объекта WP_Query

Для того, чтобы быстро разобраться как и что использовать, ниже пример объекта WP_Query (global $wp_query), который передается в хук по ссылке (&$query):

WP_Query Object
(
	[query_vars] => Array
		(
			[page] => 0
			[pagename] => s
			[error] => 
			[m] => 
			[p] => 0
			[post_parent] => 
			[subpost] => 
			[subpost_id] => 
			[attachment] => 
			[attachment_id] => 0
			[name] => s
			[static] => 
			[page_id] => 0
			[second] => 
			[minute] => 
			[hour] => 
			[day] => 0
			[monthnum] => 0
			[year] => 0
			[w] => 0
			[category_name] => 
			[tag] => 
			[cat] => 
			[tag_id] => 
			[author] => 
			[author_name] => 
			[feed] => 
			[tb] => 
			[paged] => 0
			[comments_popup] => 
			[meta_key] => 
			[meta_value] => 
			[preview] => 
			[s] => 
			[sentence] => 
			[fields] => 
			[menu_order] => 
			[category__in] => Array
				(
				)

			[category__not_in] => Array
				(
				)

			[category__and] => Array
				(
				)

			[post__in] => Array
				(
				)

			[post__not_in] => Array
				(
				)

			[tag__in] => Array
				(
				)

			[tag__not_in] => Array
				(
				)

			[tag__and] => Array
				(
				)

			[tag_slug__in] => Array
				(
				)

			[tag_slug__and] => Array
				(
				)

			[post_parent__in] => Array
				(
				)

			[post_parent__not_in] => Array
				(
				)

			[author__in] => Array
				(
				)

			[author__not_in] => Array
				(
				)

			[ignore_sticky_posts] => 
			[suppress_filters] => 
			[cache_results] => 
			[update_post_term_cache] => 1
			[update_post_meta_cache] => 1
			[post_type] => 
			[posts_per_page] => 10
			[nopaging] => 
			[comments_per_page] => 10
			[no_found_rows] => 
			[order] => DESC
		)

	[tax_query] => 
	[meta_query] => WP_Meta_Query Object
		(
			[queries] => Array
				(
				)

			[relation] => 
		)

	[date_query] => 
	[post_count] => 1
	[current_post] => -1
	[in_the_loop] => 
	[comment_count] => 0
	[current_comment] => -1
	[found_posts] => 1
	[max_num_pages] => 0
	[max_num_comment_pages] => 0
	[is_single] => 
	[is_preview] => 
	[is_page] => 1
	[is_archive] => 
	[is_date] => 
	[is_year] => 
	[is_month] => 
	[is_day] => 
	[is_time] => 
	[is_author] => 
	[is_category] => 
	[is_tag] => 
	[is_tax] => 
	[is_search] => 
	[is_feed] => 
	[is_comment_feed] => 
	[is_trackback] => 
	[is_home] => 
	[is_404] => 
	[is_comments_popup] => 
	[is_paged] => 
	[is_admin] => 
	[is_attachment] => 
	[is_singular] => 1
	[is_robots] => 
	[is_posts_page] => 
	[is_post_type_archive] => 
	[query_vars_hash] => c248b13e251e8fba33892e0bd7a5bd98
	[query_vars_changed] => 
	[thumbnails_cached] => 
	[stopwords:WP_Query:private] => 
	[query] => Array
		(
			[page] => 
			[pagename] => s
		)

	[queried_object] => WP_Post Object
		(
			[ID] => 19
			[post_author] => 1
			[post_date] => 2010-04-01 20:09:20
			[post_date_gmt] => 2010-04-01 16:09:20
			[post_content] => 
			[post_title] => Страница для исполнения
			[post_excerpt] => 
			[post_status] => private
			[comment_status] => open
			[ping_status] => closed
			[post_password] => 
			[post_name] => s
			[to_ping] => 
			[pinged] => 
			[post_modified] => 2011-04-25 17:44:42
			[post_modified_gmt] => 2011-04-25 13:44:42
			[post_content_filtered] => 
			[post_parent] => 0
			[guid] => http://site.ru/s
			[menu_order] => 0
			[post_type] => page
			[post_mime_type] => 
			[comment_count] => 0
			[filter] => raw
		)

	[queried_object_id] => 19
	[request] => SELECT   wp_posts.* FROM wp_posts  WHERE 1=1  
				 AND (wp_posts.ID = '19') AND wp_posts.post_type = 'page'  
				 ORDER BY wp_posts.post_date DESC 
	[posts] => Array
		(
			[0] => WP_Post Object
				(
					[ID] => 19
					[post_author] => 1
					[post_date] => 2010-04-01 20:09:20
					[post_date_gmt] => 2010-04-01 16:09:20
					[post_content] => 
					[post_title] => Страница для исполнения
					[post_excerpt] => 
					[post_status] => private
					[comment_status] => open
					[ping_status] => closed
					[post_password] => 
					[post_name] => s
					[to_ping] => 
					[pinged] => 
					[post_modified] => 2011-04-25 17:44:42
					[post_modified_gmt] => 2011-04-25 13:44:42
					[post_content_filtered] => 
					[post_parent] => 0
					[guid] => http://site.ru/s
					[menu_order] => 0
					[post_type] => page
					[post_mime_type] => 
					[comment_count] => 0
					[filter] => raw
				)

		)

	[post] => WP_Post Object
		(
			[ID] => 19
			[post_author] => 1
			[post_date] => 2010-04-01 20:09:20
			[post_date_gmt] => 2010-04-01 16:09:20
			[post_content] => 
			[post_title] => Страница для исполнения
			[post_excerpt] => 
			[post_status] => private
			[comment_status] => open
			[ping_status] => closed
			[post_password] => 
			[post_name] => s
			[to_ping] => 
			[pinged] => 
			[post_modified] => 2011-04-25 17:44:42
			[post_modified_gmt] => 2011-04-25 13:44:42
			[post_content_filtered] => 
			[post_parent] => 0
			[guid] => http://site.ru/s
			[menu_order] => 0
			[post_type] => page
			[post_mime_type] => 
			[comment_count] => 0
			[filter] => raw
		)

)

Где вызывается хук

WP_Query::get_posts()
pre_get_posts
wp-includes/class-wp-query.php 1736
do_action_ref_array( 'pre_get_posts', array( &$this ) );

Где используется хук (в ядре WP)

Не используется.
19 комментов
Вопросы 1 Все
  • @ Печник Роман cайт: kladka-kamina.ru

    Изменение количества выводимых на странице постов,
    В новой версии WP 3.8 не работает, может есть какой другой способ

    1
    Ответить5.1 лет назад #
  • campusboy3281 cайт: www.youtube.com/c/wpplus

    Как правильно будет исключить посты с определенным произвольным полем?

    Ответить2.5 года назад #
    • Kama7365

      Через meta_query норм:

      $query->set( 'meta_query', array([ 'key'=>'__ПОЛЕ__', 'compare'=>'NOT EXISTS' ]) );
      2
      Ответить2.5 года назад #
  • valdus

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

    function SearchExlude($query) 
    {    
     if ($query->is_search) 
      { $query->set('cat','-27'); }    
     return $query;
     }
    add_filter('pre_get_posts','SearchExlude');

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

    Первый поиск находится в шапке сайта, второй в сайдбаре

    Ответить2.4 года назад #
    • campusboy3281 cайт: www.youtube.com/c/wpplus

      Приветствую. Передавайте в форме ещё один параметр в дополнение к поисковому запросу, к примеру, по которому уже можно решить, в каком случае исключать одно, а в каком другое.

      1
      Ответить2.4 года назад #
    • Kama7365

      Если эти разные поиски выводятся на разных страницах, то campusboy вам ответил уже.

      Если оба поиска нужно вывести на одной странице в разных блоках. То вам нужно создать 2 запроса. Первый пусть будет основной и в нем вы уже исключили рубрику 27. А второй создавайте с помощью WP_Query там укажите параметр s и рубрику 27, т.е. искать только в рубрике 27...

      Ответить2.4 года назад #
  • Сёмка cайт: site-style.by

    Доброй ночи.
    Может подскажете, как можно через pre_get_posts вывести посты определенного автора и посты из определенной категории?
    Не посты определенного автора в определенной категории, а именно определенного автора и из определенной категории

    1
    Ответить1.4 года назад #
  • Использую pre_get_posts, чтобы ограничить кол-во результатов стандартного поиска WP, т.е. нужна одна страница, без пагинации. Но не вижу полностью корректного способа. Варианта нашёл только 2:
    1) posts_per_page = N + скрыть пагинацию при помощи условного тега
    2) вывести все посты при помощи nopaging = true или posts_per_page = -1

    Но оба эти варианта - костыли. Хотя если создать экземпляр WP_Query напрямую, то там есть свойство post_count, которое отлично бы мне подошло, но его нельзя использовать при правке через pre_get_posts. Может подскажите что-то дельное, чего я пока не заметил?

    Ответить10 мес назад #
    • Сёмка cайт: site-style.by

      Наилучший и самый оптимальный вариант - это использование хука для pre_get_posts.
      Например:

      function search_filter($query) {
        if ( !is_admin() && $query->is_main_query() ) {
      	if ($query->is_search) {
      	  $query->set('posts_per_page', '-1');
      	}
        }
      }
      
      add_action('pre_get_posts','search_filter');
      Ответить10 мес назад #
      • Спасибо за совет, но у меня этот вариант указан в п2. Не хочется, чтобы в результатах выводилось по несколько десятков постов, не говоря уже о большем кол-ве.

        Нашёл казалось бы верный вариант с хуком post_limits, но после его применения пагинация, создаваемая через paginate_links всё равно выводится, хотя она же вроде бы как использует тот же экземпляр $wp_query. Тут у меня, видимо, пробел в знаниях, буду искать причину такого поведения.

        Ответить10 мес назад #
        • Сёмка cайт: site-style.by

          Вместо -1 напишите цифру и у вас будет выводиться количество постов, которые надо вывести

          -1
          Ответить10 мес назад #
    • Kama7365

      Тебе надо указать параметр no_found_rows = 1 и posts_per_page = N. Тогда пагинации не должно быть...

      Ответить10 мес назад #
      • Спасибо за совет, но мне эти оба варианта не подошли, т.к. в результатах поиска использую $wp_query->found_posts для вывода кол-ва найденных, а:
        при no_found_rows = 1 результатов 0
        при posts_per_page = 5 результатов сколько нашлось, пагинация осталась

        Получилось выкрутиться через хук found_posts, в этом случае и кол-во результатов всегда правильное и пагинацию отдельно не нужно скрывать усл. тегами. Вот код, может кому пригодится в будущем:

        function search_found_posts( $found_posts, $query ) {
        
        	if ( is_search() AND ! is_admin() ) {
        
        		if ( $query->get( 'posts_per_page' ) < $query->found_posts ) {
        
        			$found_posts = $query->get( 'posts_per_page' );
        		}
        	}
        
        	return $found_posts;
        }
        add_filter( 'found_posts', 'search_found_posts', 10, 2 );
        Ответить10 мес назад #
        • Kama7365

          Не понял, а зачем ты юзаешь $wp_query->found_posts, если тебе нужна в результатах показать сколько показано на странице можно же просто посчитать элементы массива: count( $wp_query->posts ). Просто с no_found_rows не будет таких вот костылей и запрос будет полегче...

          Ответить10 мес назад #
  • kolshix512 cайт: paxtoy.com

    Изаю дополнительный поиск только по рубрикам - как в админке в разделе к примеру ТЕГИ, там поиск ищет не посты, а имена рубрик

    Сначала искал быстрое решение, в итоге пришлось этим пользоваться (JS не захотел)

    // Очистка из поисковой фразы пробелов - Зачем? При поиске по имени рубрики - нет результатов если в конце поисковой фразы пробел
    function search_filter($query) {
      if ( ! is_admin() && $query->is_main_query() ) {
    	if ($query->is_search) {
    		$search_text = trim(get_search_query());
    		$query->set('s', $search_text );
    	}
      }
    }
    
    add_action( 'pre_get_posts', 'search_filter' );
    Ответить9 мес назад #
  • Богдан

    Здравствуйте, используя этот хук, я изменяю параметры поиска по сайту это тело функции

    if ( ! is_admin() && $query->is_main_query() && $query->is_search ) {
    		$query->set('meta_query', array(array(
    			'key' => 'original_name',
    			'value' => $query->get('s'),
    			'compare' =>  'LIKE'
    		)));
    		$query->set('post_type', 'post' );

    второй сет не срабатывает и в результаты поиска попадают пейджы. Где я ошибаюсь и как ограничить?

    1
    Ответить2 мес назад #
    • Kama7365

      Ошибки не вижу в запросе, возможно есть на сайте еще какой-то хук, который добавляет page. По другому не знаю как это объяснить. Если page добавляется, значит кто-то его добавляет, надо найти кто. Попробуй приоритет хука поставить на 9999. Попробуй в сам запрос заглянуть (для этого цепляйся на более поздние хуки и смотри что там с запросом происходит и в какой момент появляется page).

      Ответить2 мес назад #
Здравствуйте, !     Войти . Зарегистрироваться