Использование REGEXP в meta_query WP_Query для ключа

8 июл. 2015 г., 00:51:48
Просмотры: 14.4K
Голосов: 11

Я знаю, что могу использовать REGEXP в WP_Query вот так:

$query = new WP_Query(array(
  'posts_per_page'    => -1,
  'post_status'       => 'publish',
  'meta_query'        => array(
    array(
      'key'     => 'custom_fields',
      'value'   => 'foo[(][0-9][)]', // регулярное выражение
      'compare' => 'REGEXP',
    ),
  ),
));

Но мне нужны регулярные выражения и в ключе. Вот так:

$query = new WP_Query(array(
  'posts_per_page'    => -1,
  'post_status'       => 'publish',
  'meta_query'        => array(
    array(
      'key'     => 'custom_fields[(][0-9][)]', // регулярное выражение
      'value'   => 'foo',
      'compare' => 'REGEXP',
    ),
  ),
));

Есть ли способ реализовать это с помощью фильтра? Или мне нужно писать собственный SQL запрос?

0
Все ответы на вопрос 2
14
10

Вот одна экспериментальная идея:

Допустим, у нас есть:

запись A с произвольным полем location1 со значением Великобритания - Лондон

запись B с произвольным полем location2 со значением Франция - Париж

запись C с произвольным полем location3 со значением США - Нью-Йорк

Тогда мы могли бы использовать, например:

$args = [
    'meta_query' => [
        'relation' => 'OR',
        [
            'key'          => "^location[0-9]",
            '_key_compare' => 'REGEXP',
            'value'        => 'Лондон',
            'compare'      => 'LIKE',
        ],
        [
            'key'          => 'location%',
            '_key_compare' => 'LIKE',
            'value'        => 'Париж',
            'compare'      => 'LIKE'
        ],
        [
            'key'          => 'location3',
            'value'        => 'Нью-Йорк',
            'compare'      => 'LIKE'
        ]
    ]
];

где мы поддерживаем пользовательский аргумент _key_compare с помощью следующего плагина:

<?php
/**
 *  Plugin Name:   Extended Meta Key Search In WP_Query
 *  Description:   Пользовательский аргумент '_key_compare' с поддержкой REGEXP, RLIKE или 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 )
{
    // Проверяем мета-запрос:
    $mq = $q->get( 'meta_query' );

    if( empty( $mq ) )
        return;

    // Инициализация:
    $marker = '___tmp_marker___'; 
    $rx     = [];

    // Собираем все подзапросы, использующие REGEXP, RLIKE или LIKE:
    foreach( $mq as $k => $m )                                    
    {
        if(    isset( $m['_key_compare'] )
            && in_array( strtoupper( $m['_key_compare'] ), [ 'REGEXP', 'RLIKE', 'LIKE' ] )
            && isset( $m['key'] )
        ) {
            // Помечаем ключ уникальной строкой для последующей замены:
            $m['key'] .= $marker . $k; // Создаем уникальный временный маркер

            // Модифицируем соответствующую переменную запроса:
            $q->query_vars['meta_query'][$k]['key'] = $m['key'];

            // Сохраняем:
            $rx[$k] = $m;
        }
    }

    // Если нечего делать:
    if( empty( $rx ) )
        return;

    // Получаем доступ к сгенерированному SQL мета-запроса:
    add_filter( 'get_meta_sql', function( $sql ) use ( $rx, $marker )
    {
        // Выполняем только один раз:
        static $nr = 0;         
        if( 0 != $nr++ )
            return $sql;

        // Модифицируем часть WHERE, заменяя временные маркеры:
        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;
    });

});

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

Обратите внимание, что это не поддерживает экранирование regex-символов, таких как \( и \\.

8 июл. 2015 г. 13:17:40
Комментарии

Мне нравятся эти забавные дополнительные пользовательские параметры, которые ты добавляешь. ;-)

Pieter Goosen Pieter Goosen
8 июл. 2015 г. 13:21:35

Возможно, как маленькие помощники Санты - они обычно волшебным образом все делают в конце ;-) @PieterGoosen

birgire birgire
8 июл. 2015 г. 13:35:13

Вау, чувак... это потрясающе. Но, к сожалению, не работает :( У меня в базе есть ключи вида custom_field_language(0)language, и мой regex-ключ custom_field_languages[(][0-9][)]language не работает с твоим плагином. Есть идеи?

Philipp Kühn Philipp Kühn
8 июл. 2015 г. 17:56:47

Похоже, что функция замены в конце вашего плагина работает некорректно.

Philipp Kühn Philipp Kühn
8 июл. 2015 г. 18:09:32

Спасибо, я исправил это. Просто использовал $count++ при построении строки, забыв, что мне нужно было использовать $count дважды ;-) Я не рекомендую использовать специальные символы в именах ключей, кроме нижнего подчеркивания. Вы используете скобки в своем ключе. Они имеют специальное значение в регулярных выражениях. Поэтому вам пришлось бы экранировать их с помощью \( и \) в REGEXP или RLIKE, но экранирование не поддерживается в моем плагине. Вы можете попробовать использовать LIKE с custom_field_language(%)language. @PhilippKühn

birgire birgire
8 июл. 2015 г. 19:33:56

Да. Большое спасибо. Ваш исправленный код работает!!! Но есть другая проблема. Если я использую другой meta_query, я всегда получаю нулевые результаты с любым отношением. Например: '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 июл. 2015 г. 19:56:39

если я выведу $sql['where'], то увижу, что каждый ключ заменён на REGEXP. @birgire

Philipp Kühn Philipp Kühn
8 июл. 2015 г. 20:13:45

Это была ещё одна проблема с $count ;-) Пожалуйста, посмотрите обновлённый ответ, где я использую ключ массива вместо этого. @PhilippKühn

birgire birgire
8 июл. 2015 г. 20:57:06

гениально! работает как часы!

Philipp Kühn Philipp Kühn
8 июл. 2015 г. 22:04:22

о нет... выяснилось, что это не поддерживает новые вложенные meta queries, добавленные в wp 4.1 :( бесконечная история... @birgire

Philipp Kühn Philipp Kühn
9 июл. 2015 г. 23:53:01

ага, хорошее замечание, возможно, я просто выложу это на GitHub в ближайшие недели и попробую расширить функционал там ;-) @PhilippKühn

birgire birgire
10 июл. 2015 г. 00:24:55

Это было бы отлично! А пока я нашел временное костыльное решение. Но я был бы рад использовать ваш скрипт, потому что это гораздо более чистый способ. @birgire

Philipp Kühn Philipp Kühn
10 июл. 2015 г. 10:58:10

@birgire, когда-нибудь думал выложить это на GitHub, приятель?

Jacques Koekemoer Jacques Koekemoer
30 мая 2023 г. 16:38:28

Похоже, я забыл об этом :-) Вряд ли в ближайшее время, но кто знает...

birgire birgire
31 мая 2023 г. 20:29:04
Показать остальные 9 комментариев
1

Ваш ответ идеально работает на первом уровне массива, например:

$args['meta_query'][] = array(

  'key' => 'tour_itinerario_ciudades_repeater_%_tour_ciudades_nombre',
  '_key_compare' => 'LIKE',
  'value' => 'MEXICO',
  'compare' => 'LIKE',
  );

Мне нужно внести некоторые изменения, чтобы это работало на втором уровне массива:

$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',
    )
);

Теперь,

add_action('pre_get_posts', function( $q ) {
// Проверяем meta_query:
$mq = $q->get('meta_query');

if (empty($mq))
    return;

// Инициализация:
$marker = '___tmp_marker___';
$rx = [];

// Собираем все подзапросы, которые используют REGEXP, RLIKE или LIKE:
// Работает только для первого уровня массива
foreach ($mq as $k => $m) {
    if (isset($m['_key_compare']) && in_array(strtoupper($m['_key_compare']), [ 'REGEXP', 'RLIKE', 'LIKE']) && isset($m['key'])
    ) {
        // Помечаем ключ уникальной строкой для безопасной замены:
        $m['key'] .= $marker . $k; // Делаем временный маркер уникальным
        // Модифицируем соответствующую переменную запроса:
        $q->query_vars['meta_query'][$k]['key'] = $m['key'];

        // Сохраняем:
        $rx[$k] = $m;
    }
}

// Пользовательский код для работы с аргументами в многомерном массиве 
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'])
            ) {
                // Помечаем ключ уникальной строкой для безопасной замены:
                $m_i['key'] .= $marker . $k_i; // Делаем временный маркер уникальным
                // Модифицируем соответствующую переменную запроса:
                $q->query_vars['meta_query'][$k][$k_i]['key'] = $m_i['key'];

                // Сохраняем:
                $rx[$k][$k_i] = $m_i;
            }
        }
    }
}


// Нечего делать:
if (empty($rx))
    return;

// Получаем доступ к сгенерированному SQL meta_query:
add_filter('get_meta_sql', function( $sql ) use ( $rx, $marker ) {
    // Выполняем только один раз:
    static $nr = 0;
    if (0 != $nr++)
        return $sql;

    // Модифицируем часть WHERE, заменяя временные маркеры:
    //ПЕРВЫЙ УРОВЕНЬ
    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']
        );
    }
    //ВТОРОЙ УРОВЕНЬ
    foreach ($rx as $k => $r) {
        //TODO: протестировать с разными случаями, так как могут быть баги
        if (!isset($r['key'])) {//ФОРСИРУЕМ ВХОД 
            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;
});

}); Просто на случай, если кому-то понадобится аналогичный ответ,

СПАСИБО ЕЩЕ РАЗ

19 янв. 2016 г. 01:37:10
Комментарии

Спасибо за публикацию, но было бы здорово, если бы комментарии в коде были на английском, спасибо.

birgire birgire
19 янв. 2016 г. 16:03:00