Usar REGEXP en la clave meta_query de WP_Query
Sé que puedo usar REGEXP en WP_Query de esta forma:
$query = new WP_Query(array(
'posts_per_page' => -1,
'post_status' => 'publish',
'meta_query' => array(
array(
'key' => 'custom_fields',
'value' => 'foo[(][0-9][)]', // con expresiones regulares
'compare' => 'REGEXP',
),
),
));
Pero necesito usar expresiones regulares también en la clave. Algo así:
$query = new WP_Query(array(
'posts_per_page' => -1,
'post_status' => 'publish',
'meta_query' => array(
array(
'key' => 'custom_fields[(][0-9][)]', // con expresiones regulares
'value' => 'foo',
'compare' => 'REGEXP',
),
),
));
¿Existe alguna forma de lograr esto quizás con un filtro? ¿O tengo que construirlo todo yo mismo con una consulta SQL personalizada?
Aquí tienes una idea experimental:
Supongamos que tenemos:
post A con el campo personalizado
location1como UK - Londonpost B con el campo personalizado
location2como France - Parispost C con el campo personalizado
location3como USA - New York
Entonces podríamos usar, por ejemplo:
$args = [
'meta_query' => [
'relation' => 'OR',
[
'key' => "^location[0-9]",
'_key_compare' => 'REGEXP',
'value' => 'London',
'compare' => 'LIKE',
],
[
'key' => 'location%',
'_key_compare' => 'LIKE',
'value' => 'Paris',
'compare' => 'LIKE'
],
[
'key' => 'location3',
'value' => 'New York',
'compare' => 'LIKE'
]
]
];
donde admitimos el argumento personalizado _key_compare con el siguiente plugin:
<?php
/**
* Plugin Name: Extended Meta Key Search In WP_Query
* Description: Custom '_key_compare' argument as REGEXP, RLIKE or LIKE
* Plugin URI: http://wordpress.stackexchange.com/a/193841/26350
* Plugin Author: Birgir Erlendsson (birgire)
* Version: 0.0.3
*/
add_action( 'pre_get_posts', function( $q )
{
// Verificar la meta query:
$mq = $q->get( 'meta_query' );
if( empty( $mq ) )
return;
// Inicialización:
$marker = '___tmp_marker___';
$rx = [];
// Recopilar todas las sub meta queries que usan REGEXP, RLIKE o LIKE:
foreach( $mq as $k => $m )
{
if( isset( $m['_key_compare'] )
&& in_array( strtoupper( $m['_key_compare'] ), [ 'REGEXP', 'RLIKE', 'LIKE' ] )
&& isset( $m['key'] )
) {
// Marcar la clave con una cadena única para asegurar los reemplazos posteriores:
$m['key'] .= $marker . $k; // Hacer que el marcador temporal añadido sea único
// Modificar la variable de consulta original correspondiente:
$q->query_vars['meta_query'][$k]['key'] = $m['key'];
// Recopilarlo:
$rx[$k] = $m;
}
}
// Nada que hacer:
if( empty( $rx ) )
return;
// Obtener acceso al SQL generado de la meta query:
add_filter( 'get_meta_sql', function( $sql ) use ( $rx, $marker )
{
// Ejecutar solo una vez:
static $nr = 0;
if( 0 != $nr++ )
return $sql;
// Modificar la parte WHERE donde reemplazamos los marcadores temporales:
foreach( $rx as $k => $r )
{
$sql['where'] = str_replace(
sprintf(
".meta_key = '%s' ",
$r['key']
),
sprintf(
".meta_key %s '%s' ",
$r['_key_compare'],
str_replace(
$marker . $k,
'',
$r['key']
)
),
$sql['where']
);
}
return $sql;
});
});
donde añadimos marcadores únicos en cada meta clave para los reemplazos de cadenas.
Nota que esto no admite el escape de caracteres regex, como \( y \\.
Me encantan estos parámetros personalizados adicionales y divertidos que estás añadiendo. ;-)
Pieter Goosen
Quizás como los ayudantes de Santa - usualmente hacen que todo funcione mágicamente al final ;-) @PieterGoosen
birgire
Wow amigo... esto es increíble. Pero desafortunadamente no está funcionando :( Tengo nombres de claves como custom_field_language(0)language en mi base de datos y mi expresión regular custom_field_languages[(][0-9][)]language no funciona con tu plugin. ¿Alguna idea?
Philipp Kühn
parece que la función replace al final de tu plugin no está funcionando correctamente.
Philipp Kühn
gracias, lo he solucionado. Simplemente usé $count++ en una construcción de cadena cuando olvidé que tenía que usar $count dos veces ;-) No recomendaría usar caracteres especiales en los nombres de las claves, aparte del guion bajo. Estás usando paréntesis en tu clave. Tienen un significado especial con expresiones regulares. Así que tendrías que escaparlos con \( y \) en REGEXP o RLIKE, pero el escape no está soportado en mi plugin. Podrías probar con LIKE en su lugar usando custom_field_language(%)language. @PhilippKühn
birgire
sí. muchas gracias. ¡tu código corregido funciona! pero hay otro problema. si uso otro meta_query siempre obtengo cero resultados con cualquier relación. como este 'meta_query' => array(
'relation' => 'OR',
array(
'key' => 'custom_field_location',
'value' => 'Berlin',
'compare' => '=',
),
array(
'key' => 'custom_field_languages[(][0-9][)]language',
'_key_compare' => 'REGEXP',
'value' => 'ak',
'compare' => 'LIKE',
),
)
Philipp Kühn
si imprimo $sql['where'] puedo ver que cada clave se reemplaza con la de REGEXP. @birgire
Philipp Kühn
Esto fue otro problema con $count ;-) Por favor revisa la respuesta actualizada, donde uso una clave de array en su lugar. @PhilippKühn
birgire
oh no... descubrí que esto no soporta estas nuevas consultas meta anidadas introducidas en wp 4.1 :( una historia interminable... @birgire
Philipp Kühn
aja, buen punto, tal vez solo lo subiré a GitHub en las próximas semanas e intentaré extenderlo allí ;-) @PhilippKühn
birgire
¡Esto sería genial! Hasta entonces he encontrado una solución alternativa un poco chapucera. Pero estaría encantado de usar tu script porque es una forma mucho más limpia. @birgire
Philipp Kühn
@birgire ¿alguna vez pensaste en subir esto a GitHub, colega?
Jacques Koekemoer
Tu respuesta funciona perfectamente en el primer nivel del array, por ejemplo:
$args['meta_query'][] = array(
'key' => 'tour_itinerario_ciudades_repeater_%_tour_ciudades_nombre',
'_key_compare' => 'LIKE',
'value' => 'MEXICO',
'compare' => 'LIKE',
);
Necesito hacer algunas modificaciones para que funcione en el segundo nivel del array:
$args['meta_query'][] = array(
'relation' => 'OR',
array(
'key' => 'tour_itinerario_ciudades_repeater_%_tour_ciudades_nombre',
'_key_compare' => 'LIKE',
'value' => 'CONDESA',
'compare' => 'LIKE',
),
array(
'key' => 'tour_itinerario_ciudades_repeater_%_tour_ciudades_nombre',
'_key_compare' => 'LIKE',
'value' => 'Ciudad 1',
'compare' => 'LIKE',
)
);
Ahora,
add_action('pre_get_posts', function( $q ) {
// Verificar la meta query:
$mq = $q->get('meta_query');
if (empty($mq))
return;
// Inicializar:
$marker = '___tmp_marker___';
$rx = [];
// Recolectar todas las sub meta queries que usan REGEXP, RLIKE o LIKE:
// Solo funciona para el primer nivel del array
foreach ($mq as $k => $m) {
if (isset($m['_key_compare']) && in_array(strtoupper($m['_key_compare']), [ 'REGEXP', 'RLIKE', 'LIKE']) && isset($m['key'])
) {
// Marcar la clave con un string único para asegurar los reemplazos posteriores:
$m['key'] .= $marker . $k; // Hacer único el marcador temporal añadido
// Modificar la variable de consulta original correspondiente:
$q->query_vars['meta_query'][$k]['key'] = $m['key'];
// Recolectarla:
$rx[$k] = $m;
}
}
// código personalizado para hacerlo funcionar con argumentos en arrays multidimensionales
foreach ($mq as $k => $m) {
foreach ($m as $k_i => $m_i) {
if (count($m_i) >= 3) {
if (isset($m_i['_key_compare']) && in_array(strtoupper($m_i['_key_compare']), [ 'REGEXP', 'RLIKE', 'LIKE']) && isset($m_i['key'])
) {
// Marcar la clave con un string único para asegurar los reemplazos posteriores:
$m_i['key'] .= $marker . $k_i; // Hacer único el marcador temporal añadido
// Modificar la variable de consulta original correspondiente:
$q->query_vars['meta_query'][$k][$k_i]['key'] = $m_i['key'];
// Recolectarla:
$rx[$k][$k_i] = $m_i;
}
}
}
}
// Nada que hacer:
if (empty($rx))
return;
// Obtener acceso al SQL generado de la meta query:
add_filter('get_meta_sql', function( $sql ) use ( $rx, $marker ) {
// Ejecutar solo una vez:
static $nr = 0;
if (0 != $nr++)
return $sql;
// Modificar la parte WHERE donde reemplazamos los marcadores temporales:
//PRIMER NIVEL
foreach ($rx as $k => $r) {
$sql['where'] = str_replace(
sprintf(
".meta_key = '%s' ", $r['key']
), sprintf(
".meta_key %s '%s' ", $r['_key_compare'], str_replace(
$marker . $k, '', $r['key']
)
), $sql['where']
);
}
//SEGUNDO NIVEL
foreach ($rx as $k => $r) {
//TODO: probar con varios casos ya que puede tener errores
if (!isset($r['key'])) {//FORZAR LA ENTRADA
foreach ($r as $k_i => $r_i) {
$sql['where'] = str_replace(
sprintf(
".meta_key = '%s' ", $r_i['key']
), sprintf(
".meta_key %s '%s' ", $r_i['_key_compare'], str_replace(
$marker . $k_i, '', $r_i['key']
)
), $sql['where']
);
}
}
}
var_dump($sql);
return $sql;
});
}); Solo por si alguien necesita una respuesta similar,
GRACIAS DE NUEVO