WP_Query с "post_title LIKE 'something%'"?
Мне нужно выполнить WP_Query
с условием LIKE
для поля post_title
.
Я начал с обычного запроса WP_Query
:
$wp_query = new WP_Query(
array (
'post_type' => 'wp_exposants',
'posts_per_page' => '1',
'post_status' => 'publish',
'orderby' => 'title',
'order' => 'ASC',
'paged' => $paged
)
);
Но то, что я хочу сделать, выглядит так на SQL:
$query = "
SELECT *
FROM $wpdb->posts
WHERE $wpdb->posts.post_title LIKE '$param2%'
AND $wpdb->posts.post_type = 'wp_exposants'
ORDER BY $wpdb->posts.post_title
";
$wpdb->get_results($query);
Вывод показывает ожидаемые результаты, но я использую стандартный цикл <?php while ( $wp_query->have_posts() ) : $wp_query->the_post(); ?>
для отображения результатов.
И это не работает с $wpdb->get_results()
.
Как мне достичь того, что я описал здесь?
Я бы решил это с помощью фильтра для WP_Query
. Фильтр, который обнаруживает дополнительный параметр запроса и использует его в качестве префикса заголовка.
add_filter( 'posts_where', 'wpse18703_posts_where', 10, 2 );
function wpse18703_posts_where( $where, &$wp_query )
{
global $wpdb;
if ( $wpse18703_title = $wp_query->get( 'wpse18703_title' ) ) {
$where .= ' AND ' . $wpdb->posts . '.post_title LIKE \'' . esc_sql( $wpdb->esc_like( $wpse18703_title ) ) . '%\'';
}
return $where;
}
Таким образом, вы по-прежнему можете вызывать WP_Query
, просто передавая заголовок в качестве аргумента wpse18703_title
(или измените имя на что-то более короткое).

@kaiser: Прошло много времени, но, кажется, это было невозможно с prepare()
. $wpdb->prepare('LIKE "%s%%"', 'banana')
вернул бы "LIKE ''banana'%'"
, так что нам придется самостоятельно конструировать запрос и выполнять экранирование.

@JanFabry Рад снова тебя видеть! :) Заходи как-нибудь в чат, а? StopPress будет рад тебя видеть. Насчет этого prepare()
. Да, это непросто, мне самому пришлось несколько раз попробовать, прежде чем разобраться. Вот пример из того, что я только что сделал: $wpdb->prepare( ' AND {$wpdb->posts}.post_title LIKE %s ', esc_sql( '%'.like_escape( trim( $term ) ).'%' ) )
. И я почти уверен, что esc_sql()
здесь излишне и просто параноидально.

Похоже, что нельзя искать строку с '
(апострофом) внутри. Предполагаю, что это из-за экранирования? Пока не нашёл решение

Упрощённый вариант:
function title_filter( $where, &$wp_query )
{
global $wpdb;
// 2. используем пользовательский запрос:
if ( $search_term = $wp_query->get( 'search_prod_title' ) ) {
$where .= ' AND ' . $wpdb->posts . '.post_title LIKE \'%' . esc_sql( like_escape( $search_term ) ) . '%\'';
}
return $where;
}
$args = array(
'post_type' => 'product',
'posts_per_page' => $page_size,
'paged' => $page,
// 1. определяем пользовательскую переменную запроса для передачи поискового запроса:
'search_prod_title' => $search_term,
'post_status' => 'publish',
'orderby' => 'title',
'order' => 'ASC'
);
add_filter( 'posts_where', 'title_filter', 10, 2 );
$wp_query = new WP_Query($args);
remove_filter( 'posts_where', 'title_filter', 10 );
return $wp_query;

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

@fdrv Вы правы, но согласно документации WP, $wpdb->esc_like всё ещё требует esc_sql(). Поэтому я считаю, что правильный код будет esc_sql( $wpdb->esc_like( $search_term ) )

remove_filter
использует только 3 аргумента, последний можно удалить.

смог заставить вышеуказанное работать, но по какой-то причине при выполнении поиска возвращались данные из других post_types, хотя я указал тип записи в массиве $args. В итоге добавил его в запрос заголовка, что сработало для меня: $where .= ' OR ' . $wpdb->posts . '.post_title LIKE \'%' . $wpdb->esc_like( $search_term ) . '%\' AND '. $wpdb->posts . '.post_type = \'huguenot-family\'';

Хотел обновить этот код, над которым вы работали, для WordPress 4.0 и выше, так как esc_sql() устарел в версиях 4.0 и выше.
function title_filter($where, &$wp_query){
global $wpdb;
if($search_term = $wp_query->get( 'search_prod_title' )){
/*используем esc_like() здесь вместо esc_sql()*/
$search_term = $wpdb->esc_like($search_term);
$search_term = ' \'%' . $search_term . '%\'';
$where .= ' AND ' . $wpdb->posts . '.post_title LIKE '.$search_term;
}
return $where;
}
Остальное остается без изменений.
Также хочу отметить, что вы можете использовать переменную s в аргументах WP_Query для передачи поискового запроса, которая, как я полагаю, также будет искать по заголовку записи.
Вот так:
$args = array(
'post_type' => 'post',
's' => $search_term,
'post_status' => 'publish',
'orderby' => 'title',
'order' => 'ASC'
);
$wp_query = new WP_Query($args);

Что именно представляет собой search_prod_title
? Стоит ли изменить это на что-то другое?

С каких пор esc_sql
устарел? Это не так. $wpdb->escape
действительно устарел... https://developer.wordpress.org/reference/functions/esc_sql/

Обратите внимание, что параметр s выполняет поиск по содержимому записи, что может не соответствовать желаемой цели. =)

С некоторыми уязвимыми решениями, опубликованными здесь, я предлагаю немного упрощенную и защищенную версию.
Сначала создаем функцию для фильтра posts_where
, которая позволяет показывать только записи, соответствующие определенным условиям:
function cc_post_title_filter($where, &$wp_query) {
global $wpdb;
if ( $search_term = $wp_query->get( 'cc_search_post_title' ) ) {
$where .= ' AND ' . $wpdb->posts . '.post_title LIKE \'%' . $wpdb->esc_like( $search_term ) . '%\'';
}
return $where;
}
Теперь добавляем cc_search_post_title
в аргументы нашего запроса:
$args = array(
'cc_search_post_title' => $search_term, // поиск только по заголовку записи
'post_status' => 'publish',
);
И наконец, оборачиваем запрос в фильтр:
add_filter( 'posts_where', 'cc_post_title_filter', 10, 2 );
$query = new WP_Query($args);
remove_filter( 'posts_where', 'cc_post_title_filter', 10 );
Использование get_posts()
Некоторые функции, которые получают записи, не применяют фильтры, поэтому функции фильтра posts_where
, которые вы добавляете, не изменят запрос. Если вы планируете использовать get_posts()
для запроса записей, вам нужно установить suppress_filters
в значение false
в массиве аргументов:
$args = array(
'cc_search_post_title' => $search_term,
'suppress_filters' => FALSE,
'post_status' => 'publish',
);
Теперь можно использовать get_posts()
:
add_filter( 'posts_where', 'cc_post_title_filter', 10, 2 );
$posts = get_posts($args);
remove_filter( 'posts_where', 'cc_post_title_filter', 10 );
А что насчет параметра s
?
Параметр s
доступен:
$args = array(
's' => $search_term,
);
Хотя добавление вашего поискового запроса в параметр s
работает и будет искать по заголовку записи, он также будет искать по содержимому записи.
А что насчет параметра title
, добавленного в WP 4.4?
Передача поискового запроса в параметр title
:
$args = array(
'title' => $search_term,
);
Чувствительна к регистру и использует LIKE
, а не %LIKE%
. Это означает, что поиск по запросу hello
не вернет запись с заголовком Hello World
или Hello
.

Отлично. Я искал параметр 'post_title' и, очевидно, ничего не нашел.

Я получаю ошибку с wp query или get posts: "E_WARNING Error in file »class-wp-hook.php« at line 288: Parameter 2 to cc_post_title_filter() expected to be a reference, value given

Развивая предыдущие ответы, чтобы обеспечить гибкость в ситуации, когда нужно найти пост, содержащий слово либо в мета-поле, либо в заголовке поста, я предоставляю такую возможность через аргумент "title_filter_relation". В данной реализации я разрешаю только значения "OR" или "AND" с установленным по умолчанию значением "AND".
function title_filter($where, &$wp_query){
global $wpdb;
if($search_term = $wp_query->get( 'title_filter' )){
$search_term = $wpdb->esc_like($search_term); //вместо esc_sql()
$search_term = ' \'%' . $search_term . '%\'';
$title_filter_relation = (strtoupper($wp_query->get( 'title_filter_relation'))=='OR' ? 'OR' : 'AND');
$where .= ' '.$title_filter_relation.' ' . $wpdb->posts . '.post_title LIKE '.$search_term;
}
return $where;
}
Вот пример использования кода для очень простого типа записи "faq", где вопрос является самим заголовком поста:
add_filter('posts_where','title_filter',10,2);
$s1 = new WP_Query( array(
'post_type' => 'faq',
'posts_per_page' => -1,
'title_filter' => $q,
'title_filter_relation' => 'OR',
'post_status' => 'publish',
'orderby' => 'title',
'order' => 'ASC',
'meta_query' => array(
'relation' => 'OR',
array(
'key' => 'faq_answer',
'value' => $q,
'compare' => 'LIKE'
)
)
));
remove_filter('posts_where','title_filter',10,2);

Хорошее наблюдение, добавление пользовательских "query vars" в аргументы запроса, передаваемые в WP_Query
, чтобы иметь возможность обращаться к ним внутри фильтра posts_where
.

Оптимальный подход для выполнения WP_Query с поиском 'LIKE' по полю post_title — это включение параметра запроса 'search_columns'. Если вы создаёте WP_Query вручную, его следует указать следующим образом:
'search_columns' => ['post_title']
Подробнее можно узнать в исходном коде WordPress на Trac: https://core.trac.wordpress.org/browser/tags/6.3/src/wp-includes/class-wp-query.php#L1426
Однако, если ваша цель — изменить запросы, сгенерированные сторонними плагинами или темами, вы можете добиться этого с помощью хука 'post_search_columns'.
Официальная документация содержит подробную информацию о том, как использовать этот хук: https://developer.wordpress.org/reference/hooks/post_search_columns/
