Пользовательские поля или произвольные поля (custom fields) — это одна из самых мощных функциональностей, доступных в WordPress.

Они особенно полезны при расширении WordPress за счет использования пользовательских типов постов (custom post types). Я постоянно создаю пользовательские типы постов для таких вещей, как товары, портфолио или галереи при разработке тем WordPress для клиентов.

Пользовательские поля очень удобны, когда нужно добавить детали продукта, такие как номера товаров или цены. К сожалению, поиск в WordPress по пользовательским полям невозможен из коробки. Чтобы исправить это, необходимо изменить поиск WordPress так, чтобы он включал пользовательские поля.

Left Join

Таблица 'postmeta' — это место, где в базе данных хранятся все данные произвольных полей. По умолчанию функция поиска WordPress настроена на поиск только в таблице 'posts'. Чтобы включить данные пользовательских полей в наш поиск, нам сначала нужно выполнить левое объединение (лефт джоин) таблиц 'posts' и 'postmeta' в базе данных.

/**
 * Join posts and postmeta tables
 *
 * http://codex.wordpress.org/Plugin_API/Filter_Reference/posts_join
 */
function cf_search_join( $join ) {
    global $wpdb;

    if ( is_search() ) {    
        $join .=' LEFT JOIN '.$wpdb->postmeta. ' ON '. $wpdb->posts . '.ID = ' . $wpdb->postmeta . '.post_id ';
    }

    return $join;
}
add_filter('posts_join', 'cf_search_join' );

Изменяем запрос (Query)

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

/**
 * Modify the search query with posts_where
 *
 * http://codex.wordpress.org/Plugin_API/Filter_Reference/posts_where
 */
function cf_search_where( $where ) {
    global $pagenow, $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;
}
add_filter( 'posts_where', 'cf_search_where' );

Предотвращение дубликатов

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


/**
 * Prevent duplicates
 *
 * http://codex.wordpress.org/Plugin_API/Filter_Reference/posts_distinct
 */
function cf_search_distinct( $where ) {
    global $wpdb;

    if ( is_search() ) {
        return "DISTINCT";
    }

    return $where;
}
add_filter( 'posts_distinct', 'cf_search_distinct' );

Добавьте следующее в файл functions.php, чтобы добавить функционал поиска в WordPress по кастомным полям. Этот код не только изменит поиск во внешнем интерфейсе, но вы также сможете искать по пользовательским полям в админке.

<?php

/**
 * Join posts and postmeta tables
 *
 * http://codex.wordpress.org/Plugin_API/Filter_Reference/posts_join
 */
function cf_search_join( $join ) {
    global $wpdb;

    if ( is_search() ) {    
        $join .=' LEFT JOIN '.$wpdb->postmeta. ' ON '. $wpdb->posts . '.ID = ' . $wpdb->postmeta . '.post_id ';
    }

    return $join;
}
add_filter('posts_join', 'cf_search_join' );

/**
 * Modify the search query with posts_where
 *
 * http://codex.wordpress.org/Plugin_API/Filter_Reference/posts_where
 */
function cf_search_where( $where ) {
    global $pagenow, $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;
}
add_filter( 'posts_where', 'cf_search_where' );

/**
 * Prevent duplicates
 *
 * http://codex.wordpress.org/Plugin_API/Filter_Reference/posts_distinct
 */
function cf_search_distinct( $where ) {
    global $wpdb;

    if ( is_search() ) {
        return "DISTINCT";
    }

    return $where;
}
add_filter( 'posts_distinct', 'cf_search_distinct' );

Получение всех записей с одинаковым значением пользовательского поля

Например, если нам надо получить все записи с одинаковым почтовым индексом (zipcode) в качестве метазначения, то следующий код можно использовать для наших целей:

$query_args = array(
        'post_type'   => 'service',
        'posts_per_page' => -1,
        'meta_query'  => array(
            array(
                'value'   => $zip,
                'compare' => 'LIKE',
                'key'     => 'zipcode',
            ),
        )
    );
   $query = new WP_Query($query_args);
   <?php if ( $query->have_posts() ) :while ( $query->have_posts() ) : $query->the_post();  ?>
       <h3><?php the_title(); ?></h3>
   <?php endwhile; // end of the loop. ?>
   <?php wp_reset_query(); ?>
   <?php else: ?>
      No results found.
   <?php endif; ?>

zipcode — это цифры, например 12345. Если записи имеют значение 12345 в пользовательском поле, то оно должно отображать все записи, которые имеют значение 12345.

Запрос записей по произвольным полям

Ниже я приведу рекомендации по запросу (Query) по кастомным полям от создателей плагина ACF (Advanced Custom Fields). Ниже расскажу, как получить массив объектов записей из базы данных с помощью собственных функций WP. В WP существует множество способов запросить записи, однако в этой статье будут использованы общие функции get_posts, объект WP_Query и фильтр pre_get_posts.

Общие правила

Если вы уже знакомы с приведенными выше функциями, объектами и фильтрами, то можете пропустить этот блок.

Объект WP_Query используется для запроса записей и возвращает объект, содержащий массив объектов $post и множество полезных методов.

Функция get_posts использует вышеупомянутый объект WP_Query, однако возвращает только массив объектов $post, что делает ее более простым способом поиска и перебора постов.

Фильтр pre_get_post вызывается после создания объекта запроса, но перед выполнением самого запроса.

Пример

Этот пример демонстрирует, как запросить все посты и отобразить их в списке. Обратите внимание, что функции setup_postdata() и wp_reset_postdata() используются для того, чтобы такие функции, как the_permalink() и the_title(), работали как положено.

<?php 

$posts = get_posts(array(
	'posts_per_page'	=> -1,
	'post_type'			=> 'post'
));

if( $posts ): ?>
	
	<ul>
		
	<?php foreach( $posts as $post ): 
		
		setup_postdata( $post );
		
		?>
		<li>
			<a href="<?php the_permalink(); ?>"><?php the_title(); ?></a>
		</li>
	
	<?php endforeach; ?>
	
	</ul>
	
	<?php wp_reset_postdata(); ?>

<?php endif; ?>

Параметры произвольного поля

И функция get_posts, и объект WP_Query принимают аргументы для запроса значений пользовательских полей. Существует как базовый, так и расширенный способ запроса, которые описаны ниже. Вы можете прочитать больше о параметрах в руководстве WP codex.

Основной пример

В этом примере показаны аргументы для поиска всех постов, в которых пользовательское поле 'color' имеет значение 'red'.

$posts = get_posts(array(
	'numberposts'	=> -1,
	'post_type'		=> 'post',
	'meta_key'		=> 'color',
	'meta_value'	=> 'red'
));

Продвинутый пример

В этом примере показаны аргументы для поиска всех записей, в которых пользовательское поле 'color' имеет значение 'red' или 'orange', а другое пользовательское поле 'featured' (флажок или чекбокс) отмечен галочкой.

$posts = get_posts(array(
	'numberposts'	=> -1,
	'post_type'		=> 'post',
	'meta_query'	=> array(
		'relation'		=> 'AND',
		array(
			'key'	 	=> 'color',
			'value'	  	=> array('red', 'orange'),
			'compare' 	=> 'IN',
		),
		array(
			'key'	  	=> 'featured',
			'value'	  	=> '1',
			'compare' 	=> '=',
		),
	),
));

Ниже вы найдете еще ряд примеров.

Обратите внимание, что в этих примерах используется объект WP_Query, а не функция get_posts, однако аргументы и логика остаются теми же.

Одно значение пользовательского поля

В этом примере мы найдем все записи, которые имеют тип поста (post_type) 'event', где пользовательское поле 'location' равно 'Melbourne'. Пользовательское поле 'location' в данном случае может быть текстовым полем, радиокнопкой или полем выбора (то, что сохраняет одно текстовое значение).

<?php 

// args
$args = array(
	'numberposts'	=> -1,
	'post_type'		=> 'event',
	'meta_key'		=> 'location',
	'meta_value'	=> 'Melbourne'
);


// query
$the_query = new WP_Query( $args );

?>
<?php if( $the_query->have_posts() ): ?>
	<ul>
	<?php while( $the_query->have_posts() ) : $the_query->the_post(); ?>
		<li>
			<a href="<?php the_permalink(); ?>">
				<img src="<?php the_field('event_thumbnail'); ?>" />
				<?php the_title(); ?>
			</a>
		</li>
	<?php endwhile; ?>
	</ul>
<?php endif; ?>

<?php wp_reset_query();	 // Restore global post data stomped by the_post(). ?>

Множественные значения произвольных полей (значения на основе текста)

В этом примере мы найдем все записи, которые имеют тип записи (post_type) 'event', где пользовательское поле 'location' равно 'Melbourne', а пользовательское поле 'attendees' больше 100. Пользовательское поле 'attendees' в данном случае может быть числовым полем, текстовым полем, радиокнопкой или полем выбора (то, что сохраняет одно текстовое значение).

<?php 

// args
$args = array(
	'numberposts'	=> -1,
	'post_type'		=> 'event',
	'meta_query'	=> array(
		'relation'		=> 'AND',
		array(
			'key'		=> 'location',
			'value'		=> 'Melbourne',
			'compare'	=> '='
		),
		array(
			'key'		=> 'attendees',
			'value'		=> 100,
			'type'		=> 'NUMERIC',
			'compare'	=> '>'
		)
	)
);


// query
$the_query = new WP_Query( $args );

?>
<?php if( $the_query->have_posts() ): ?>
	<ul>
	<?php while ( $the_query->have_posts() ) : $the_query->the_post(); ?>
		<li>
			<a href="<?php the_permalink(); ?>">
				<img src="<?php the_field('event_thumbnail'); ?>" />
				<?php the_title(); ?>
			</a>
		</li>
	<?php endwhile; ?>
	</ul>
<?php endif; ?>

<?php wp_reset_query();	 // Restore global post data stomped by the_post(). ?>

Множественные значения полей (значения на основе массива)

В этом примере мы найдем все записи, имеющие тип (post_type) 'event', в которых пользовательское поле 'location' равно 'Melbourne' или 'Sydney'. Пользовательское поле 'location' в данном случае может быть полем с несколькими вариантами выбора или полем с флажком (что-то, что сохраняет сериализованное значение массива).

<?php 

// args
$args = array(
	'numberposts'	=> -1,
	'post_type'		=> 'event',
	'meta_query'	=> array(
		'relation'		=> 'OR',
		array(
			'key'		=> 'location',
			'value'		=> 'Melbourne',
			'compare'	=> 'LIKE'
		),
		array(
			'key'		=> 'location',
			'value'		=> 'Sydney',
			'compare'	=> 'LIKE'
		)
	)
);


// query
$the_query = new WP_Query( $args );

?>
<?php if( $the_query->have_posts() ): ?>
	<ul>
	<?php while ( $the_query->have_posts() ) : $the_query->the_post(); ?>
		<li>
			<a href="<?php the_permalink(); ?>">
				<img src="<?php the_field('event_thumbnail'); ?>" />
				<?php the_title(); ?>
			</a>
		</li>
	<?php endwhile; ?>
	</ul>
<?php endif; ?>

<?php wp_reset_query();	 // Restore global post data stomped by the_post(). ?>

Значения дополнительных настраиваемых подполей

В этом примере мы найдем все events, у которых есть city или Melbourne или Sydney. Каждый city добавляется как новая строка в повторяющееся поле под названием location.

Чтобы успешно запросить значения подполей, нужно помнить, что номер строки неизвестен (может быть 1, 2 или даже 3 строки данных повторяющегося поля). Поэтому нам нужно использовать предложение LIKE в нашем SQL-запросе, чтобы разрешить WILDCARD в поиске мета ключа meta_key. Для этого мы создаем пользовательский фильтр, чтобы заменить стандартное ‘=’ на ‘LIKE’.

UPD: После изменения поведения esc_sql() в WordPress 4.8.3, теперь не так просто использовать символ % в качестве заполнителя для следующего поиска и замены, вместо него мы рекомендуем использовать символ $, как показано ниже.

Примечание: этот метод требует подключения к фильтру posts_where, который не гарантированно будет выполняться при всех запросах к записям. Чтобы решить эту проблему, установите значение suppress_filters равным false в массиве аргументов, передаваемых в get_posts() или WP_Query.

<?php 

// filter
function my_posts_where( $where ) {
	
	$where = str_replace("meta_key = 'locations_$", "meta_key LIKE 'locations_%", $where);

	return $where;
}

add_filter('posts_where', 'my_posts_where');


// vars
$city = 'Melbourne';


// args
$args = array(
	'numberposts'	=> -1,
	'post_type'		=> 'event',
	'meta_query'	=> array(
		'relation'		=> 'OR',
		array(
			'key'		=> 'locations_$_city',
			'compare'	=> '=',
			'value'		=> 'Melbourne',
		),
		array(
			'key'		=> 'locations_$_city',
			'compare'	=> '=',
			'value'		=> 'Sydney',
		)
	)
);


// query
$the_query = new WP_Query( $args );

?>
<?php if( $the_query->have_posts() ): ?>
	<ul>
	<?php while ( $the_query->have_posts() ) : $the_query->the_post(); ?>
		<li>
			<a href="<?php the_permalink(); ?>"><?php the_title(); ?></a>
		</li>
	<?php endwhile; ?>
	</ul>
<?php endif; ?>

<?php wp_reset_query();	 // Restore global post data stomped by the_post(). ?>

Динамические параметры $_GET

Этот пример показывает, как использовать параметры $_GET (из URL) для изменения запроса архива типа post. В данном примере предполагается, что существует тип поста 'event' и что его архив существует по адресу URL; www.website.com/events.

Тип поста event содержит поле select под названием 'city' со значениями 'melbourne' и 'sydney'. Если добавить параметр к url, запрос будет изменен, и будут показаны только те посты, которые соответствуют 'city': www.website.com/events?city=melbourne.

// FUNCTIONS.PHP

function my_pre_get_posts( $query ) {
	
	// do not modify queries in the admin
	if( is_admin() ) {
		
		return $query;
		
	}
	
	
	// only modify queries for 'event' post type
	if( isset($query->query_vars['post_type']) && $query->query_vars['post_type'] == 'event' ) {
		
		// allow the url to alter the query
		if( isset($_GET['city']) ) {
			
    		$query->set('meta_key', 'city');
			$query->set('meta_value', $_GET['city']);
			
    	} 
		
	}
	
	
	// return
	return $query;

}

add_action('pre_get_posts', 'my_pre_get_posts');

Оцените, пожалуйста

Нажмите на звезду, чтобы оценить!

Средняя оценка 5 / 5. Количество оценок: 2

Оценок пока нет. Поставьте оценку первым.

ACF
PHP
Wordpress

Статьи # # #