Usar REGEXP en la clave meta_query de WP_Query

8 jul 2015, 00:51:48
Vistas: 14.4K
Votos: 11

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?

0
Todas las respuestas a la pregunta 2
14
10

Aquí tienes una idea experimental:

Supongamos que tenemos:

post A con el campo personalizado location1 como UK - London

post B con el campo personalizado location2 como France - Paris

post C con el campo personalizado location3 como 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 \\.

8 jul 2015 13:17:40
Comentarios

Me encantan estos parámetros personalizados adicionales y divertidos que estás añadiendo. ;-)

Pieter Goosen Pieter Goosen
8 jul 2015 13:21:35

Quizás como los ayudantes de Santa - usualmente hacen que todo funcione mágicamente al final ;-) @PieterGoosen

birgire birgire
8 jul 2015 13:35:13

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 Philipp Kühn
8 jul 2015 17:56:47

parece que la función replace al final de tu plugin no está funcionando correctamente.

Philipp Kühn Philipp Kühn
8 jul 2015 18:09:32

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 birgire
8 jul 2015 19:33:56

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 Philipp Kühn
8 jul 2015 19:56:39

si imprimo $sql['where'] puedo ver que cada clave se reemplaza con la de REGEXP. @birgire

Philipp Kühn Philipp Kühn
8 jul 2015 20:13:45

Esto fue otro problema con $count ;-) Por favor revisa la respuesta actualizada, donde uso una clave de array en su lugar. @PhilippKühn

birgire birgire
8 jul 2015 20:57:06

¡genial! ¡funciona perfectamente!

Philipp Kühn Philipp Kühn
8 jul 2015 22:04:22

oh no... descubrí que esto no soporta estas nuevas consultas meta anidadas introducidas en wp 4.1 :( una historia interminable... @birgire

Philipp Kühn Philipp Kühn
9 jul 2015 23:53:01

aja, buen punto, tal vez solo lo subiré a GitHub en las próximas semanas e intentaré extenderlo allí ;-) @PhilippKühn

birgire birgire
10 jul 2015 00:24:55

¡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 Philipp Kühn
10 jul 2015 10:58:10

@birgire ¿alguna vez pensaste en subir esto a GitHub, colega?

Jacques Koekemoer Jacques Koekemoer
30 may 2023 16:38:28

Parece que me olvidé de esto :-) Probablemente no en un futuro cercano, pero nunca se sabe...

birgire birgire
31 may 2023 20:29:04
Mostrar los 9 comentarios restantes
1

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

19 ene 2016 01:37:10
Comentarios

Gracias por compartir, pero se agradecería si los comentarios en el código estuvieran en inglés, gracias.

birgire birgire
19 ene 2016 16:03:00