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

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, чтобы исключить из результатов поиска постоянных страницы:

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

add_action( 'pre_get_posts', 'search_filter' );

#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 VER 4.9.2
...
		global $wpdb;

		$this->parse_query();

		/**
		 * Fires after the query variable object is created, but before the actual query is run.
		 *
		 * Note: If using conditional tags, use the method versions within the passed instance
		 * (e.g. $this->is_main_query() instead of is_main_query()). This is because the functions
		 * like is_main_query() test against the global $wp_query instance, not the passed one.
		 *
		 * @since 2.0.0
		 *
		 * @param WP_Query $this The WP_Query instance (passed by reference).
		 */
		do_action_ref_array( 'pre_get_posts', array( &$this ) );

		// Shorthand.
		$q = &$this->query_vars;

		// Fill again in case pre_get_posts unset some vars.
		$q = $this->fill_query_vars($q);

		// Parse meta query
		$this->meta_query = new WP_Meta_Query();
		$this->meta_query->parse_query_vars( $q );

		// Set a flag if a pre_get_posts hook changed the query vars.
		$hash = md5( serialize( $this->query_vars ) );
		if ( $hash != $this->query_vars_hash ) {
			$this->query_vars_changed = true;
...
pre_get_posts 16 комментариев
Вопросы 3 Все
  • Печник Роман cайт: kladka-kamina.ru @

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

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

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

    Ответить1.3 года назад #
    • Kama4711

      Через meta_query норм:

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

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

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

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

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

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

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

      1
      Ответить1.2 года назад #
    • Kama4711

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

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

      Ответить1.2 года назад #
  • Сергей

    Здравствуйте. Мне нужно при выводе архива категории добавить посты из другой категории. Достаточно ли установить category__in или надо ещё что-нибудь сделать (сбросить cat, category_name)?

    Ответить4 месяца назад #
    • Сергей

      Вообще при выводе архива категории изменение cat или category__in приводит к тому, что "записи не найдены". Как через pre_get_posts правильно вывести архив двух категорий?

      Ответить4 месяца назад #
  • Эдуард cайт: i-advocat.ru @

    Здравствуйте, Тимур.
    Мне нужно, чтобы для пользователя с определенной ролью в админке отображались записи пользовательского типа только из определенного соответствующего ему региона.
    Для этого я на этапе регистрации присваиваю пользователю мета-поле "'region', а потом при сохранении каждого поста сохраняю его значение в мета-поле поста из мета-поля пользователя.
    Далее для того, чтобы пользователю в админке отображались записи типа 'practice' только из его региона, я пишу:

    add_action('pre_get_posts', 'livepravo_chamber_practice_table', 99, 1);
    function livepravo_chamber_practice_table($query) {
    if (!$query->is_admin) return; // Если не админнка, нахер с пляжа.
    if (!wp_get_current_user()->has_cap('chamber')) return; // Если не нужная роль- аналогично.
    $query->set('meta_key', 'region');
    $query->set('meta_value', wp_get_current_user()->region);
    } // End function

    Теперь два вопроса:

    1. Как мне сделать, чтобы эти записи и из корзины не выбирались? То есть, чтобы на экране "Все записи типа 'practice'" показывало в корзине количество записей с учетом изложенного условия. Сейчас показывает количество всех записей, без учета региона. Реально, так и пишет: Всего записей 4, включая две опубликованные, два черновика и еще в корзине 28.

    2. Можно ли действие хука pre_get_posts() ограничить только конкретным экраном с таблицей постов, Вместо:
      if (get_current_screen()->id != 'edit-practice') return;
      Объект экрана на этапе выполнения этого хука, как я понял, не доступен (PHP пишет, что функция не определена), Но может есть какой-то обходной вариант?
    Ответить4 месяца назад #
  • Сёмка cайт: site-style.by @

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

    1
    Ответить3 месяца назад #
  • Роман cайт: bro-dev.tk

    Насколько плохо делать вот так?

    add_action('pre_get_posts', 'myPreGetPosts', 10, 1);
    $the_query = new WP_Query($query_string . '&posts_per_page=5&tag_id=4232');
    remove_action('pre_get_posts', 'myPreGetPosts', 10, 1);

    Какие есть альтернативы?

    Ответить29 дней назад #
    • Kama4711

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

      Ответить26 дней назад #

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

Ваш комментарий
Предпросмотр