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

Добавляем поиск по метаполю в обычный поиск WordPress

Базовый поиск WordPress ищет указанный текст в заголовке, контенте и отрывке (в полях post_title, post_content, post_excerpt). Ниже показано, как к этим базовым полям поиска добавить поиск и по метаполям. Для этого мы будем внедряться в запрос через хуки и изменять его.

Хуки который для этого понадобятся:

Решение задачи на хуках: posts_join, posts_where, posts_distinct

add_filter( 'posts_join', 'cf_search_join' );
add_filter( 'posts_where', 'cf_search_where' );
add_filter( 'posts_distinct', 'cf_search_distinct' );

# Объединяет таблицы записей и таблиц метаданных.
function cf_search_join( $join ){
	global $wpdb;

	if( is_search() )
		$join .= " LEFT JOIN $wpdb->postmeta ON ID = $wpdb->postmeta.post_id ";

	return $join;
}

# Указывает по каким метаполям и какое значение искать в секции WHERE.
function cf_search_where( $where ){
	global $wpdb;

	if ( is_search() ) {
		$where = preg_replace(
			"/\(\s*$wpdb->posts.post_title\s+LIKE\s*(\'[^\']+\')\s*\)/",
			"($wpdb->posts.post_title LIKE $1) OR ($wpdb->postmeta.meta_value LIKE $1)", $where );
	}

	return $where;
}

# Предотвращает появление дубликатов в выборке.
function cf_search_distinct( $where ){
	return is_search() ? 'DISTINCT' : $where;
}

Что делается в коде:

Шаг 1: Добавление таблицы метаданных в запрос (JOIN)

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

Шаг 2: Изменение выборки в запросе (WHERE)

Дополним WHERE часть запроса, чтобы "стандартный поиск" в дополнение искал ещё и по всем метаполям постов.

Шаг 3: Изменение выборки в запросе (DISTINCT)

Из-за LEFT JOIN в 1 шаге могут появится дубликаты. Избавимся от них.

Решение задачи на хуке posts_clauses

add_filter( 'posts_clauses', 'km_metadata_search' );

# Добавляем поиск по метаполям в базовый поиск WordPress
function km_metadata_search( $clauses ){
	global $wpdb;

	if( ! is_search() || ! is_main_query() )
		return $clauses;

	$clauses['join'] .= " LEFT JOIN $wpdb->postmeta kmpm ON (ID = kmpm.post_id)";

	$clauses['where'] = preg_replace(
		"/OR +\( *$wpdb->posts.post_content +LIKE +('[^']+')/",
		"OR (kmpm.meta_value LIKE $1) $0",
		$clauses['where']
	);

	// если нужно искать в конкретном метаполе
	//$clauses['where'] .= $wpdb->prepare(' AND kmpm.meta_key = %s', 'foo' );

	$clauses['distinct'] = 'DISTINCT';

	// дебаг итогового запроса
	0 && add_filter( 'posts_request', function( $sql ){   die( $sql );  } );

	return $clauses;
}

Фильтры работать не будут, если для запроса включен параметр suppress_filters. Для запроса get_posts() фильтр работать не будет - там этот параметр включен по умолчанию!

-

При создании кода, была использована статья: adambalee.com

campusboy 3438youtube.com/c/wpplus
Создатель YouTube канала wp-plus, на котором делюсь своим опытом. Активный пользователь wp-kama.ru. WordPress-разработчик. Разработка сайтов и лендингов. Доработка существующих проектов. Сопровождение ресурсов.
Редакторы: Kama 7604
3 коммента
  • Добрый день. Пробовал и ваш вариант и ( Адама adambalee.com )
    Проблема такая, в результатах поиска страницы, содержащие метаполя находятся, но не отображается содержимое метаполей!
    Есть метаполе "Повторитель" из плагина ACF, и в нем вкладки. Так вот их содержимое не показывается.

    Ответить10.Сен.2019 в 22:11 #
  • Поиск по произвольным полям работает, но у меня отключен поиск по заголовкам, в итоге если использовать оба этих кода, то полностью меняется сам принцип поиска. Вместо поиска по точному сочетанию поисковой фразы, вордпресс выдает все посты где встречается хотя бы одно слово из поисковой фразы.
    Вот собственно сам код поиска по заголовкам

    function __search_by_title_only( $search, &$wp_query )
    {
     global $wpdb;
     if ( empty( $search ) )
     return $search; // skip processing - no search term in query
     $q = $wp_query->query_vars;
     $n = ! empty( $q['exact'] ) ? '' : '%';
     $search =
     $searchand = '';
     foreach ( (array) $q['search_terms'] as $term ) {
     $term = esc_sql( like_escape( $term ) );
     $search .= "{$searchand}($wpdb->posts.post_title LIKE '{$n}{$term}{$n}')";
     $searchand = ' AND ';
     }
     if ( ! empty( $search ) ) {
     $search = " AND ({$search}) ";
     if ( ! is_user_logged_in() )
     $search .= " AND ($wpdb->posts.post_password = '') ";
     }
     return $search;
    }
    add_filter( 'posts_search', '__search_by_title_only', 500, 2 ); 
    Ответить06.Ноя.2019 в 14:49 #
    • Kama7604

      Вместо поиска по точному сочетанию поисковой фразы, вордпресс выдает все посты где встречается хотя бы одно слово из поисковой фразы.

      Это базовое поведение поиска в ВП. Чтобы в ВП искать по точной фразе, нужно враз у взять в кавычки...

      Ответить20.Дек.2019 в 17:32 #