Использование REGEXP в meta_query WP_Query для ключа
Я знаю, что могу использовать 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 запрос?

Вот одна экспериментальная идея:
Допустим, у нас есть:
запись 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-символов, таких как \(
и \\
.

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

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

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

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

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

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

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

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

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

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

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

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

Ваш ответ идеально работает на первом уровне массива, например:
$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;
});
}); Просто на случай, если кому-то понадобится аналогичный ответ,
СПАСИБО ЕЩЕ РАЗ
