Фильтрация по нескольким пользовательским полям в WP REST API 2
Я хочу фильтровать записи по нескольким пользовательским полям ACF с условием AND. Примерно так:
$args = array(
'post_type' => 'product',
'meta_query' => array(
'relation' => 'AND',
array(
'key' => 'color',
'value' => 'blue',
'compare' => '=',
),
array(
'key' => 'price',
'value' => array( 20, 100 ),
'type' => 'numeric',
'compare' => 'BETWEEN',
),
),
);
Фильтров может быть даже больше. Как преобразовать это в фильтры для REST API 2?

Это решение работает с get_items()
в файле /lib/endpoints/class-wp-rest-posts-controller.php
v2 WP REST API
.
Сначала вам нужно сформировать аргументы GET
, как вы бы сделали для new WP_Query()
. Проще всего это сделать с помощью http_build_query()
.
$args = array (
'filter' => array (
'meta_query' => array (
'relation' => 'AND',
array (
'key' => 'color',
'value' => 'blue',
'compare' => '=',
),
array (
'key' => 'test',
'value' => 'testing',
'compare' => '=',
),
),
),
);
$field_string = http_build_query( $args );
Результат будет примерно таким:
filter%5Bmeta_query%5D%5Brelation%5D=AND&filter%5Bmeta_query%5D%5B0%5D%5Bkey%5D=color&filter%5Bmeta_query%5D%5B0%5D%5Bvalue%5D=blue&filter%5Bmeta_query%5D%5B0%5D%5Bcompare%5D=%3D&filter%5Bmeta_query%5D%5B1%5D%5Bkey%5D=test&filter%5Bmeta_query%5D%5B1%5D%5Bvalue%5D=testing&filter%5Bmeta_query%5D%5B1%5D%5Bcompare%5D=%3D
Если вам нужен читаемый вариант, вы можете использовать инструменты Chrome и функцию decodeURIComponent('ваш-запрос-здесь')
, чтобы сделать его более удобным для чтения при добавлении в URL JSON REST API:
Примечание: Для использования вашего пользовательского типа записи укажите product
перед ?
/wp-json/wp/v2/<пользовательский-тип-записи>?filter[meta_query]
Итак, у вас есть запрос, но нам нужно указать WordPress, как обрабатывать несколько моментов:
- Добавление поддержки REST для пользовательского типа записи
product
- Разрешение аргументов запроса
meta_query
- Обработка
meta_query
// 1) Добавление поддержки CPT <product>
function wpse_20160526_add_product_rest_support() {
global $wp_post_types;
//Убедитесь, что указано имя вашего типа записи!
$post_type_name = 'product';
if( isset( $wp_post_types[ $post_type_name ] ) ) {
$wp_post_types[$post_type_name]->show_in_rest = true;
$wp_post_types[$post_type_name]->rest_base = $post_type_name;
$wp_post_types[$post_type_name]->rest_controller_class = 'WP_REST_Posts_Controller';
}
}
add_action( 'init', 'wpse_20160526_add_product_rest_support', 25 );
// 2) Добавление поддержки `meta_query` в GET-запросе
function wpse_20160526_rest_query_vars( $valid_vars ) {
$valid_vars = array_merge( $valid_vars, array( 'meta_query' ) ); // Пропустите meta_key, meta_value, если они вам не нужны
return $valid_vars;
}
add_filter( 'rest_query_vars', 'wpse_20160526_rest_query_vars', PHP_INT_MAX, 1 );
// 3) Обработка пользовательских аргументов
function wpse_20160526_rest_product_query( $args, $request ) {
if ( isset( $args[ 'meta_query' ] ) ) {
$relation = 'AND';
if( isset($args['meta_query']['relation']) && in_array($args['meta_query']['relation'], array('AND', 'OR'))) {
$relation = sanitize_text_field( $args['meta_query']['relation'] );
}
$meta_query = array(
'relation' => $relation
);
foreach ( $args['meta_query'] as $inx => $query_req ) {
/*
Массив (
[key] => test
[value] => testing
[compare] => =
)
*/
$query = array();
if( is_numeric($inx)) {
if( isset($query_req['key'])) {
$query['key'] = sanitize_text_field($query_req['key']);
}
if( isset($query_req['value'])) {
$query['value'] = sanitize_text_field($query_req['value']);
}
if( isset($query_req['type'])) {
$query['type'] = sanitize_text_field($query_req['type']);
}
if( isset($query_req['compare']) && in_array($query_req['compare'], array('=', '!=', '>','>=','<','<=','LIKE','NOT LIKE','IN','NOT IN','BETWEEN','NOT BETWEEN', 'NOT EXISTS')) ) {
$query['compare'] = sanitize_text_field($query_req['compare']);
}
}
if( ! empty($query) ) $meta_query[] = $query;
}
// Заменяем на обработанные аргументы запроса
$args['meta_query'] = $meta_query;
}
return $args;
}
add_action( 'rest_product_query', 'wpse_20160526_rest_product_query', 10, 2 );

Вот тест, который я провел на локальном сервере:
По соображениям безопасности meta_query не разрешен в WP API. Первое, что нужно сделать — добавить meta_query в разрешенные rest_query, добавив эту функцию в файл темы WordPress functions.php
:
function api_allow_meta_query( $valid_vars ) {
$valid_vars = array_merge( $valid_vars, array( 'meta_query') );
return $valid_vars;
}
add_filter( 'rest_query_vars', 'api_allow_meta_query' );
После этого вам нужно построить HTML-запрос, используя эту функцию на другом сайте, который будет получать данные с WordPress-сайта:
$curl = curl_init();
$fields = array (
'filter[meta_query]' => array (
'relation' => 'AND',
array (
'key' => 'color',
'value' => 'blue',
'compare' => '='
),
array (
'key' => 'price',
'value' => array ( 20, 100 ),
'type' => 'numeric',
'compare' => 'BETWEEN'
),
),
);
$field_string = http_build_query($fields);
curl_setopt_array($curl, array (
CURLOPT_RETURNTRANSFER => 1,
CURLOPT_URL => 'http://yourwordpreswebssite.com/wp-json/wp/v2/posts?' . $field_string
)
);
$result = curl_exec($curl);
echo htmlentities($result);
Я изменил массив полей, чтобы он выглядел как ваши аргументы запроса. Закодированная строка запроса будет выглядеть так:
http://yourwordpreswebssite.com/wp-json/wp/v2/posts?filter%5Btaxonomy%5D=product&filter%5Bmeta_query%5D%5Brelation%5D=AND&filter%5Bmeta_query%5D%5B0%5D%5Bkey%5D=color&filter%5Bmeta_query%5D%5B0%5D%5Bvalue%5D=blue&filter%5Bmeta_query%5D%5B0%5D%5Bcompare%5D=%3D&filter%5Bmeta_query%5D%5B1%5D%5Bkey%5D=price&filter%5Bmeta_query%5D%5B1%5D%5Bvalue%5D%5B0%5D=20&filter%5Bmeta_query%5D%5B1%5D%5Bvalue%5D%5B1%5D=100&filter%5Bmeta_query%5D%5B1%5D%5Btype%5D=numeric&filter%5Bmeta_query%5D%5B1%5D%5Bcompare%5D=BETWEEN
Использование urldecode()
, в данном случае: urldecode('http://yourwordpreswebssite.com/wp-json/wp/v2/posts?' . $field_string);
, даст URL вида:
http://yourwordpreswebssite.com/wp-json/wp/v2/posts?filter[taxonomy]=product&filter[meta_query][relation]=AND&filter[meta_query][0][key]=color&filter[meta_query][0][value]=blue&filter[meta_query][0][compare]==&filter[meta_query][1][key]=price&filter[meta_query][1][value][0]=20&filter[meta_query][1][value][1]=100&filter[meta_query][1][type]=numeric&filter[meta_query][1][compare]=BETWEEN
Если вы можете предоставить URL вашего рабочего сайта, мы сможем протестировать его напрямую с помощью Postman, поскольку для тестирования на локальном сервере или любом существующем WordPress-сайте потребуется создание пользовательского типа записей для товаров и добавление метаполей и т.д. Удачи!

Спасибо за ответ, но я проверил запрос, как в вопросе, в Postman, и он не сработал.

@Dan я внес некоторые улучшения в решение, значения фильтров совпадают с вашими аргументами запроса, включая пользовательский тип записи, который не был указан в предыдущем решении.

У нас нет таксономии product
. Работает отлично! Не подумал обернуть meta_query
внутри filter
:)

@Dan Рад это слышать. Вчера я написал пост на эту тему, возможно, тебе будет интересно поделиться им :) WordPress REST API с метаполями.

Несколько замечаний: на некоторых серверах AWS использование [] для массивов может привести к ошибке запроса. Лучше использовать array() для надежности и для тех, кто может копировать/вставлять код. Также, поддерживает ли это CPT product или только таксономию? И последнее: нужно ли санитизировать meta_query? Учитывая, что данные берутся напрямую, есть ли риск безопасности при принятии любых пользовательских данных?

@Eduart Я знаю, как заставить это работать для себя на основе твоей идеи, но тебе тоже стоит отредактировать ответ.

@jgraup большое спасибо за ваш комментарий. Я не знал этого факта про сервера AWS. Пользовательские типы записей и пользовательские таксономии должны быть зарегистрированы с REST API заранее, и конечно же должна использоваться валидация на ввод и экранирование на вывод. Причина, по которой я не использовал sanitize, в том, что я тестировал это только на выводе для пользовательских полей, которые я предварительно валидировал на вводе. Но вы правы, это стоит улучшить для других пользователей, которые просто скопируют и вставят код.

- Сначала добавьте плагин или скопируйте весь код и вставьте его в functions.php по ссылке https://github.com/WP-API/rest-filter
- Используйте следующий запрос:
?filter[meta_query][relation]=AND
&filter[meta_query][0][key]=REAL_HOMES_property_price
&filter[meta_query][0][value][0]=10
&filter[meta_query][0][value][1]=10000001
&filter[meta_query][0][compare]=''
&filter[meta_query][1][key]=REAL_HOMES_property_price
&filter[meta_query][1][value]=10
&filter[meta_query][1][compare]='='

Вы можете сделать это без Rest API Вот так (Это мой фильтр записей)
$paged = (get_query_var('paged')) ? get_query_var('paged') : 1;
$args = array(
'paged' => $paged,
'orderby' => 'date', // сортировка по дате у нас будет в любом случае (но вы можете изменить/доработать это)
'order' => 'DESC',
);
// создаём массив $args['meta_query'] если указана хотя бы одна цена или отмечен чекбокс
if( isset( $_GET['price_min'] ) || isset( $_GET['price_max'] ) || isset( $_GET['type'] ) )
$args['meta_query'] = array( 'relation'=>'AND' ); // AND значит все условия meta_query должны выполняться
if( $type ){
$args['meta_query'][] = array(
'key' => 'type',
'value' => $type,
);
};
if( $plan ){
$args['meta_query'][] = array(
'key' => 'plan',
'value' => $plan,
);
};
if( $room_num ){
$args['meta_query'][] = array(
'key' => 'room_num',
'value' => $room_num,
);
};
if( $etage ){
$args['meta_query'][] = array(
'key' => 'etage',
'value' => $etage,
);
};
if( $price_min || $price_max ){
$args['meta_query'][] = array(
'key' => 'price',
'value' => array( $price_min, $price_max ),
'type' => 'numeric',
'compare' => 'BETWEEN'
);
};
if( $area_min || $area_max ){
$args['meta_query'][] = array(
'key' => 'area',
'value' => array( $area_min, $area_max ),
'type' => 'numeric',
'compare' => 'BETWEEN'
);
};

В WordPress 4.7 аргумент filter
был удалён.
Вы можете повторно активировать его, установив этот плагин, предоставленный командой WordPress. Только после этого вы сможете использовать одно из решений, предложенных в других ответах.
Пока что я не нашёл решения, как сделать то же самое без установки плагина.
