Расширение контекста поиска в списке записей админ-панели
Я создал произвольный тип записи и добавил к нему некоторые произвольные поля. Теперь я хотел бы, чтобы поиск, который авторы могут выполнять на экране списка произвольных записей (в админ-панели), также выполнялся по мета-полям, а не только по заголовку и содержимому, как обычно.
Где можно сделать хук и какой код нужно использовать?
Пример изображения
Стефано

Я решил проблему фильтрации запроса, добавив соединение с таблицей postmeta и изменив условие WHERE. Советы по фильтрации условия WHERE (часто требуют поиска и замены с использованием регулярных выражений) можно найти здесь в codex:
add_filter( 'posts_join', 'segnalazioni_search_join' );
function segnalazioni_search_join ( $join ) {
global $pagenow, $wpdb;
// Фильтр применяется только при поиске на странице редактирования произвольного типа записи "segnalazioni".
if ( is_admin() && 'edit.php' === $pagenow && 'segnalazioni' === $_GET['post_type'] && ! empty( $_GET['s'] ) ) {
$join .= 'LEFT JOIN ' . $wpdb->postmeta . ' ON ' . $wpdb->posts . '.ID = ' . $wpdb->postmeta . '.post_id ';
}
return $join;
}
add_filter( 'posts_where', 'segnalazioni_search_where' );
function segnalazioni_search_where( $where ) {
global $pagenow, $wpdb;
// Фильтр применяется только при поиске на странице редактирования произвольного типа записи "segnalazioni".
if ( is_admin() && 'edit.php' === $pagenow && 'segnalazioni' === $_GET['post_type'] && ! empty( $_GET['s'] ) ) {
$where = preg_replace(
"/\(\s*" . $wpdb->posts . ".post_title\s+LIKE\s*(\'[^\']+\')\s*\)/",
"(" . $wpdb->posts . ".post_title LIKE $1) OR (" . $wpdb->postmeta . ".meta_value LIKE $1)", $where );
$where.= " GROUP BY {$wpdb->posts}.id"; // Решает проблему дублирования результатов
}
return $where;
}

Вау! Именно то, что я искал. Однако, кажется, я нашел баг: при поиске по заголовку записи я получаю совпадение, которое затем дублируется в результатах 5 раз!?! http://imgur.com/eE52gIA

Вот еще один скриншот с выводом SQL: http://tinypic.com/view.php?pic=124tqb6&s=5 Не могу понять, почему получается 5 элементов!?!

Задал отдельный вопрос по исправлению бага с дублированием: http://wordpress.stackexchange.com/questions/111185/how-do-i-improve-this-admin-query-snippet-to-avoid-generating-duplicate-results

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

@Stefano, результаты поиска работают. Есть проблема: поле по умолчанию "Заголовок записи", результаты поиска повторяются много раз в админке. Смотрите: https://imgur.com/a/W4wmXhO

Ответ Стефано отличный, но в нем не хватает условия DISTINCT:
function segnalazioni_search_distinct( $where ){
global $pagenow, $wpdb;
if ( is_admin() && $pagenow=='edit.php' && $_GET['post_type']=='segnalazioni' && $_GET['s'] != '') {
return "DISTINCT";
}
return $where;
}
add_filter( 'posts_distinct', 'segnalazioni_search_distinct' );
Добавьте приведенный выше код, обновите его, и он будет работать без дубликатов.

Это сработает:
function custom_search_query( $query ) {
$custom_fields = array(
// здесь укажите все мета-поля, по которым нужно производить поиск
"rg_first_name",
"rg_1job_designation"
);
$searchterm = $query->query_vars['s'];
// необходимо удалить параметр "s" из запроса, чтобы он не мешал поиску записей
$query->query_vars['s'] = "";
if ($searchterm != "") {
$meta_query = array('relation' => 'OR');
foreach($custom_fields as $cf) {
array_push($meta_query, array(
'key' => $cf,
'value' => $searchterm,
'compare' => 'LIKE'
));
}
$query->set("meta_query", $meta_query);
};
}
add_filter( "pre_get_posts", "custom_search_query");

Пожалуйста, правильно отступайте ваш код и включите объяснение, почему и как это будет работать.

Хотя сначала я проголосовал за это, я понял, что, к сожалению, это будет работать при каждом поиске, и это может сломать фронтенд-поиск.

Добавление проверки if ( $query->query['post_type'] != 'your_custom_post_type' ){ return; }
в начало функции предотвратит выполнение этого кода при других поисках. Обратите внимание, что техника в этом ответе больше не ищет post_title, и добавить это обратно нетривиально.

Еще одна проблема - индикатор Результаты поиска по запросу «<ключевое слово>» вызывает get_search_query()
, который в свою очередь вызывает get_query_var( 's' )
. Поскольку "s" установлен в пустую строку, Результаты поиска по запросу «» всегда будет иметь пустое значение между кавычками. Есть ли доработка этого решения, которая обходит эту проблему?

Еще одна проблема - это решение будет работать только для мета-значений сейчас. Поиск по заголовку, например, не будет работать.

Решение 1: Добавьте этот код в файл функций и измените/добавьте названия столбцов, которые вы используете в своем пользовательском типе записи
function extend_admin_search( $query ) {
// используйте ваш тип записи
$post_type = 'document';
// Используйте ваши пользовательские поля/названия столбцов для поиска
$custom_fields = array(
"_file_name",
);
if( ! is_admin() )
return;
if ( $query->query['post_type'] != $post_type )
return;
$search_term = $query->query_vars['s'];
// Установите пустое значение, иначе ничего не найдется
$query->query_vars['s'] = '';
if ( $search_term != '' ) {
$meta_query = array( 'relation' => 'OR' );
foreach( $custom_fields as $custom_field ) {
array_push( $meta_query, array(
'key' => $custom_field,
'value' => $search_term,
'compare' => 'LIKE'
));
}
$query->set( 'meta_query', $meta_query );
};
}
add_action( 'pre_get_posts', 'extend_admin_search' );
Решение 2: (Рекомендуется) Используйте этот код в файле функций без изменений
function cf_search_join( $join ) {
global $wpdb;
if ( is_search() ) {
$join .=' LEFT JOIN '.$wpdb->postmeta. ' ON '. $wpdb->posts . '.ID = ' . $wpdb->postmeta . '.post_id ';
}
return $join;
}
add_filter('posts_join', 'cf_search_join' );
function cf_search_where( $where ) {
global $pagenow, $wpdb;
if ( is_search() ) {
$where = preg_replace(
"/\(\s*".$wpdb->posts.".post_title\s+LIKE\s*(\'[^\']+\')\s*\)/",
"(".$wpdb->posts.".post_title LIKE $1) OR (".$wpdb->postmeta.".meta_value LIKE $1)", $where );
}
return $where;
}
add_filter( 'posts_where', 'cf_search_where' );
function cf_search_distinct( $where ) {
global $wpdb;
if ( is_search() ) {
return "DISTINCT";
}
return $where;
}
add_filter( 'posts_distinct', 'cf_search_distinct' );

С помощью этого кода вы можете осуществлять поиск в списке записей в админ-панели WordPress по значениям произвольных полей (метаданных) вместе с заголовком и другими стандартными полями.
Пожалуйста, добавьте следующий код в файл functions.php:
if (!function_exists('extend_admin_search')) {
add_action('admin_init', 'extend_admin_search');
/**
* Подключаем фильтр поиска записей, если мы находимся на странице админки для нашего типа записи
*/
function extend_admin_search() {
global $typenow;
if ($typenow === 'your_custom_post_type') {
add_filter('posts_search', 'posts_search_custom_post_type', 10, 2);
}
}
/**
* Добавляем условие запроса для произвольных метаполей
* @param string $search текущая строка поиска
* @param WP_Query $query
* @return string
*/
function posts_search_custom_post_type($search, $query) {
global $wpdb;
if ($query->is_main_query() && !empty($query->query['s'])) {
$sql = "
or exists (
select * from {$wpdb->postmeta} where post_id={$wpdb->posts}.ID
and meta_key in ('custom_field1','custom_field2')
and meta_value like %s
)
";
$like = '%' . $wpdb->esc_like($query->query['s']) . '%';
$search = preg_replace("#\({$wpdb->posts}.post_title LIKE [^)]+\)\K#",
$wpdb->prepare($sql, $like), $search);
}
return $search;
}
}

Пожалуйста, не копируйте свой ответ на каждый похожий вопрос. Как только вы наберете достаточную репутацию, вы сможете комментировать любые сообщения и предоставлять ссылку на свой ответ.

Версия кода из нескольких ответов, которая модифицирует параметр meta_query в WP_Query для поиска через pre_get_posts, перестала искать по post_title. Возможность искать либо по заголовку записи (post_title), либо по значениям мета-полей нельзя реализовать напрямую в WP_Query без модификации SQL, как подробно объясняется в этом вопросе: Использование meta_query ('meta_query') с поисковым запросом ('s')
Я объединил несколько техник из представленных здесь решений, чтобы получить рабочую версию, которая избегает использования preg_replaces и слишком значительных модификаций SQL (хотя хотелось бы обойтись без них вообще). Единственным недостатком является то, что после выполнения поиска текст подзаголовка в верхней части страницы отображает "Результаты поиска для ''". Для своего пользовательского типа записи (custom post type) я просто скрыл этот текст с помощью CSS.
/**
* Расширяет поиск для пользовательского типа записи в админ-панели, включая поиск по мета-полям
* @param WP_Query $query
*/
function extend_cpt_admin_search( $query ) {
// Проверяем, что мы в админ-панели и что это наш пользовательский тип записи
if ( !is_admin() || $query->query['post_type'] != 'your_custom_post_type' ){
return;
}
// Укажите все мета-поля, по которым нужно осуществлять поиск
$custom_fields = array(
"your_custom_meta_field",
"your_custom_meta_field2",
"your_custom_meta_field3"
);
// Строка поиска, переданная через поисковую форму
$searchterm = $query->query_vars['s'];
// Устанавливаем пустое значение, иначе результаты не будут возвращены
// Побочный эффект - отображаемый текст поиска будет пустым в верхней части страницы
$query->query_vars['s'] = '';
if ($searchterm != ""){
// Добавляем параметр meta_query к объекту WP_Query
// Справочная информация: https://codex.wordpress.org/Class_Reference/WP_Query#Custom_Field_Parameters
$meta_query = array();
foreach($custom_fields as $cf) {
array_push($meta_query, array(
'key' => $cf,
'value' => $searchterm,
'compare' => 'LIKE'
));
}
// Используем сравнение 'OR' для каждого дополнительного мета-поля
if (count($meta_query) > 1){
$meta_query['relation'] = 'OR';
}
// Устанавливаем параметр meta_query
$query->set('meta_query', $meta_query);
// Чтобы поиск также возвращал результаты по post_title с условием "OR"
$query->set('_meta_or_title', $searchterm);
}
}
add_action('pre_get_posts', 'extend_cpt_admin_search');
