Filtrar múltiples campos personalizados con WP REST API 2
Quiero filtrar publicaciones basadas en múltiples campos personalizados de ACF con relación AND. Algo como esto:
$args = array(
'post_type' => 'product', // Tipo de publicación
'meta_query' => array(
'relation' => 'AND', // Relación AND entre los filtros
array(
'key' => 'color', // Campo color
'value' => 'blue', // Valor a buscar
'compare' => '=', // Comparación exacta
),
array(
'key' => 'price', // Campo precio
'value' => array( 20, 100 ), // Rango de valores
'type' => 'numeric', // Tipo numérico
'compare' => 'BETWEEN', // Comparación entre valores
),
),
);
Incluso podría tener más filtros. ¿Cómo puedo convertir estos filtros para usarlos en REST API 2?

Esta solución funciona con get_items()
en /lib/endpoints/class-wp-rest-posts-controller.php
de la API REST WP v2
.
Primero, deberás construir los argumentos GET
como lo harías para una new WP_Query()
. La forma más fácil de hacer esto es con 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 );
Producirá algo como:
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
Si deseas que sea legible, también puedes usar las herramientas de Chrome y decodeURIComponent('tu-consulta-aquí')
para facilitar la lectura cuando la incluyas en tu URL de la API REST JSON:
Nota: Para usar tu tipo de publicación personalizado, colocarías product
antes de ?
/wp-json/wp/v2/<tipo-de-publicacion-personalizado>?filter[meta_query]
Así que ya tienes tu consulta, pero necesitamos instruir a WP sobre cómo manejar algunas cosas:
- Agregar soporte REST para el tipo de publicación personalizado
product
- Permitir los argumentos de consulta
meta_query
- Analizar
meta_query
// 1) Agregar soporte REST para CPT <product>
function wpse_20160526_add_product_rest_support() {
global $wp_post_types;
//¡asegúrate de establecer esto al nombre de tu tipo de publicación!
$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) Agregar soporte para `meta_query` en la solicitud GET
function wpse_20160526_rest_query_vars( $valid_vars ) {
$valid_vars = array_merge( $valid_vars, array( 'meta_query' ) ); // Omite meta_key, meta_value si no los necesitas
return $valid_vars;
}
add_filter( 'rest_query_vars', 'wpse_20160526_rest_query_vars', PHP_INT_MAX, 1 );
// 3) Analizar argumentos personalizados
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 ) {
/*
Array (
[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;
}
// reemplazar con argumentos de consulta saneados
$args['meta_query'] = $meta_query;
}
return $args;
}
add_action( 'rest_product_query', 'wpse_20160526_rest_product_query', 10, 2 );

Aquí está una prueba que realicé en Localhost:
Por razones de seguridad, meta_query no está permitido en la API de WP. Primero, lo que debes hacer es agregar meta_query a las consultas REST permitidas añadiendo esta función en el archivo functions.php
de tu tema de WordPress:
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' );
Después de eso, necesitarás construir la consulta HTML usando esta función en el otro sitio web que obtendrá los datos del sitio 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);
Modifiqué el array de campos para que ahora se parezca a tus argumentos de consulta. La cadena de consulta codificada se verá así:
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
Al usar urldecode()
, que en este caso sería: urldecode('http://yourwordpreswebssite.com/wp-json/wp/v2/posts?' . $field_string);
, obtendrás una URL como esta:
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
Si puedes proporcionarnos la URL de tu sitio web en vivo, podemos probarlo usando Postman directamente en tu sitio, porque para probarlo en localhost o en cualquier sitio WordPress existente sería necesario crear un tipo de publicación personalizado de producto y agregar campos meta, etc. ¡Saludos!

Gracias por tu respuesta, pero he probado con la consulta como en la pregunta en Postman y no funcionó.

@Dan hice algunas mejoras en la solución, los valores del filtro son los mismos que tus argumentos de consulta, incluyendo el tipo de post personalizado que no estaba especificado en la solución anterior.

No tenemos la taxonomía product
. ¡Funciona genial! No había pensado en envolver el meta_query
dentro del filter
:)

@Dan Me alegra escucharlo. Ayer escribí un post sobre esto, podrías considerar compartirlo :) WordPress REST API con campos meta.

Un par de cosas, en algunos servidores AWS, usar [] como array puede matar la solicitud. Deberías usar array() para estar seguro y para aquellos que podrían copiar/pegar. Además, ¿esto soporta el CPT product o solo la taxonomía? Y por último, ¿necesitas sanitizar meta_query? Viendo que fue extraído, ¿existe un riesgo de seguridad al aceptar cualquier cosa que proporcione un usuario?

@Eduart Sé cómo hacer que funcione para mí basándome en tu idea, pero también deberías editar la respuesta.

@jgraup muchas gracias por tu comentario. No sabía ese dato sobre los servidores de AWS. Los tipos de posts personalizados y la taxonomía personalizada deben registrarse con la API REST previamente y sin duda se debe usar validación en la entrada y escape en la salida. La razón por la que no usé sanitize es porque solo lo probé en la salida para campos personalizados que ya había validado previamente en la entrada. Pero tienes razón, debería mejorarse para otros usuarios que solo copien y peguen el código.

- Primero añade el plugin o copia todo el código y pégalo en el functions.php del siguiente enlace https://github.com/WP-API/rest-filter
- Usa esto
?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]='='

Puedes hacerlo sin la API REST Así es como (Es mi filtro de publicaciones)
$paged = (get_query_var('paged')) ? get_query_var('paged') : 1;
$args = array(
'paged' => $paged,
'orderby' => 'date', // la ordenación por fecha estará siempre presente (pero puedes modificarlo/mejorarlo)
'order' => 'DESC',
);
// creamos el array $args['meta_query'] si se especifica al menos un precio o se marca un checkbox
if( isset( $_GET['price_min'] ) || isset( $_GET['price_max'] ) || isset( $_GET['type'] ) )
$args['meta_query'] = array( 'relation'=>'AND' ); // AND significa que todas las condiciones de meta_query deben cumplirse
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'
);
};

En WordPress 4.7 se eliminó el argumento filter
.
Puedes reactivarlo instalando este plugin proporcionado por el equipo de WordPress. Solo después de eso podrás usar alguna de las soluciones propuestas en las otras respuestas.
Hasta ahora no he encontrado una solución para hacer lo mismo sin instalar el plugin.
