Свои фильтры в таблицах записей, комментариев, пользователей, таксономий
Статья о том, как добавлять дополнительные фильтры для списков записей (таблица записей в админ-панели), для списка комментариев (таблица комментов) или для списка пользователей (таблица пользователей). В статье теория и примеры.
Вспомните таблицу постов в админке - над таблицей есть два выпадающих списка: по дате и по рубрикам. Речь идет о таких фильтрах. Ниже показано как добавить свои фильтры (выпадающие списки) и затем обработать запрос, урезав список записей до значений фильтра.
Возможности описанных ниже фильтров можно расширить с помощью дополнительных сортируемых колонок в таблице. Фильтрами можно «урезать» список записей, а дальше сортировать этот список по колонкам.
Все хуки для вставки HTML фильтров
Фильтруем записи в админке. Фильтры в таблицу списка записей можно добавить для любого типа записи, комментариев или пользователей. Все это делается через нужный хук. Логика везде одна: добавляем HTML код выпадающего списка - поле формы <select>, а затем обрабатываем запрос.
Теперь, посмотрим на все возможные хуки.
Записи (включая вложения)
// Добавляет html только вверх таблицы записей в блок стандартных фильтров. do_action( 'restrict_manage_posts', $post_type, $which );
// Добавляет html вверх или вниз таблицы записей, после кнопки сабмита стандартных фильтров. do_action( 'manage_posts_extra_tablenav', $which );
Комментарии
// Добавляет html в верх таблицы комментариев в блоке стандартных фильтров. do_action( 'restrict_manage_comments' );
// Добавляет HTML вверх и вниз таблицы комментариев, после кнопки сабмита фильтров. do_action( 'manage_comments_nav', $comment_status );
Пользователи
// Добавляет html вверх и вниз таблицы пользователей, в блок стандартных фильтров. do_action( 'restrict_manage_users', $which );
Таксономии
Для таксономий таких фильтров нет. Как добавить туда фильтры смотрите в примере ниже.
Параметр $which
Во всех фильтрах может быть top или bottom, что соответственно означает выводить указанный код вверху или внизу таблицы.
Примеры
Фильтр в таблице записей
Допустим, есть тип записи event и таксономия season. Нам нужно добавить фильтр, чтобы в таблице записей можно было оставлять только записи из указанного элемента таксономии season.
Для этого используем два фильтра: restrict_manage_posts и pre_get_posts:
// фильтр - добавим выпадающий список
add_action( 'restrict_manage_posts', 'add_event_table_filters');
// Фильтрация: обработка запроса
add_action( 'pre_get_posts', 'add_event_table_filters_handler' );
function add_event_table_filters( $post_type ){
echo '
<select name="sel_season">
<option value="-1">- все сезоны -</option>
<option value="203" '. selected(203, @ $_GET['sel_season'], 0) .'>2016-2017</option>
<option value="83"'. selected(83, @ $_GET['sel_season'], 0) .'>2015-2016</option>
<option value="154"'. selected(154, @ $_GET['sel_season'], 0) .'>2014-2015</option>
</select>';
// для динамического построения селекта, можно использовать wp_dropdown_categories()
}
function add_event_table_filters_handler( $query ){
$cs = function_exists('get_current_screen') ? get_current_screen() : null;
// убедимся что мы на нужной странице админки
if( ! is_admin() || empty($cs->post_type) || $cs->post_type != 'event' || $cs->id != 'edit-event' )
return;
// сезон
if( @ $_GET['sel_season'] != -1 ){
$selected_id = @ $_GET['sel_season'] ?: 20;
$query->set( 'tax_query', array([ 'taxonomy'=>'season', 'terms'=>$selected_id ]) );
}
//if( empty($_GET['orderby']) && @ $_GET['sel_season'] != -1 ){
// $query->set( 'orderby', 'menu_order date' );
//}
}
Получим рабочий фильтр:
Фильтр в таблице комментариев
Условия: для комментарие устанавливается статус, который записывается в метаполе комментария. Нам нужно в таблице комментариев сделать возможность фильтровать список по статусу. У нас есть 3 статуса: question (Вопрос), thanks (Спасибо), useful (Полезный).
Задача: вывести эти статусы в «селекте» в фильтрах таблицы комментариев и обработать запрос - если выбран какой-то статус, то изменить запрос, оставив в таблице только комментарии с указанным статусом.
Для этого используем два фильтра: restrict_manage_comments и parse_comment_query:
<?php
// Добавляем HTML фильтра
add_action( 'restrict_manage_comments', 'add_comment_filter_select' );
// изменение запроса
add_action( 'parse_comment_query', 'change_comment_request2' );
function add_comment_filter_select(){
$cond = @ $_GET['condition'];
?>
<select name="condition" class="comm_condition" onchange="window.add_param_to_URL(this);">
<option value="" <?php selected('', $cond) ?> >- Все статусы -</option>
<option value="question" <?php selected('need_answer', $cond) ?> >Вопрос</option>
<option value="thanks" <?php selected('thanks', $cond) ?> >Благодарность</option>
<option value="useful" <?php selected('useful', $cond) ?> >Полезный</option>
</select>
<script>
// добавляет параметр запроса в URL и редиректит при выборе селекта
window.add_param_to_URL = function(el){
var href = window.location.href, sep = /[?]/.test(href) ? "&" : "?", name = el.name.replace(/[^a-z_-]/i,'');
window.location = (new RegExp(name+'=?')).test(href) ? href.replace( (new RegExp('([?&]'+name+'=?)[^&]*')), (el.value ? "$1"+ el.value : '') ) : (href + sep + name + "="+ el.value);
}
</script>
<?php
}
function change_comment_request2( $query ){
// не наш запрос
if( empty( $_GET['condition'] ) || ! is_admin() )
return;
// убедимся что мы на странице таблицы комментов
$cs = get_current_screen();
if( $cs->base != 'edit-comments' || $cs->id != 'edit-comments' )
return;
// выходим, если это запрос на получение номера страницы get_page_of_comment()
$qv = $query->query_vars;
if( $qv['fields'] == 'ids' && $qv['count'] )
return;
// изменяем запрос
$query->query_vars['meta_query'] = array(
array(
'key' => 'condition',
'value' => sanitize_key($_GET['condition'])
),
);
}
В результате получим такой рабочий фильтр:
Javascript и атрибут onchange добавлен для удобности. Чтобы при выборе значение фильтр применялся сразу, а не после нажатия на кнопку фильтр.
Другой хук изменения запроса комментариев
Также есть вариант с использование хука comments_clauses для изменения запроса.
Он позволяет изменить сам SQL запрос. Этот вариант сложнее, но дает больше возможностей, иногда это может пригодиться:
// Изменение запроса
add_filter( 'comments_clauses', 'change_comment_request', 10, 2 );
function change_comment_request( $clauses, $query ){
// не наш запрос
if( empty($_GET['condition']) || ! is_admin() )
return $clauses;
// убедимся что мы на странице таблицы комментов
$cs = get_current_screen();
if( $cs->base != 'edit-comments' || $cs->id != 'edit-comments' )
return $clauses;
// выходим, если это запрос на получение номера страницы get_page_of_comment()
$qv = $query->query_vars;
if( $qv['fields'] == 'ids' && $qv['count'] )
return $clauses;
// изменяем запрос
global $wpdb;
$clauses['join'] = " LEFT JOIN $wpdb->commentmeta cm ON (cm.comment_id = $wpdb->comments.comment_ID)";
$clauses['where'] .= $wpdb->prepare(
" AND cm.meta_key='condition' AND cm.meta_value = %s",
$_GET['condition']
);
return $clauses;
}
Фильтр в таблице пользователей
Предположим, на сайте каждый пользователь должен быть активирован и при регистрации ему в метаполе activation_key записывается ключ активации. В момент активации это поле удаляется.
Задача: вывести в таблице пользователей фильтр, чтобы можно было показать только активированных пользователей или наоборот - только не активированных. Т.е. нам нужно вывести выпадающий список с выбором статуса пользователя (активирован, не активирован). И нужно обработать запрос, оставив в таблице пользователей в соответствии с запросом.
<?php
// Добавляет html вверх и вниз таблицы пользователей, в блок стандартных фильтров.
add_action( 'restrict_manage_users', 'add_users_list_filters' );
// изменение запроса - вариант 1. С версии 4.0
add_action( 'pre_get_users', 'users_filter_handler' );
function add_users_list_filters( $which ){
// только наверху
if( $which != 'top' )
return;
$activated = @ $_GET['u_activated'];
?>
<select name="u_activated" onchange="window.add_param_to_URL(this)">
<option value="">Изменить статус...</option>
<option value="yes" <?php selected('yes', $activated) ?> >Активированные</option>
<option value="no" <?php selected('no', $activated) ?> >Не активированные</option>
</select>
<script>
// добавляет параметр запроса в URL и редиректит при выборе селекта
window.add_param_to_URL = function(el){
var href = window.location.href,
sep = /[?]/.test(href) ? "&" : "?",
name = el.name.replace(/[^a-z_-]/i,'');
window.location = (new RegExp(name+'=?')).test(href)
? href.replace( (new RegExp('([?&]'+name+'=?)[^&]*')), (el.value ? "$1"+ el.value : '') )
: (href + sep + name + "="+ el.value);
}
</script>
<?php
}
function users_filter_handler( $uquery ){
if( ! is_admin() || get_current_screen()->id !== 'users' )
return;
global $wpdb;
if( ! empty($_GET['u_activated']) ){
$compare = $_GET['u_activated'] == 'yes' ? 'NOT EXISTS' : 'EXISTS';
$uquery->set('meta_query', array( array('key'=>'activation_key', 'compare'=>$compare) ) );
if( empty($_GET['orderby']) ){
$uquery->set('order', 'DESC');
$uquery->set('orderby', 'user_registered');
}
}
}
Другой хук изменения запроса пользователей
Также можно использовать хук pre_user_query:
// изменение запроса - вариант 2
add_action( 'pre_user_query', 'users_filter_handler2' );
function users_filter_handler2( $uquery ){
if( ! is_admin() || get_current_screen()->id !== 'users' ) return;
global $wpdb;
$vars = $uquery->query_vars;
if( ! empty($_GET['u_activated']) && $activated = $_GET['u_activated'] ){
$sql_sub = "SELECT user_id FROM $wpdb->usermeta WHERE meta_key = 'activation_key'";
if( $activated == 'yes' )
$uquery->query_where .= " AND ID NOT IN ($sql_sub) ";
else
$uquery->query_where .= " AND ID IN ($sql_sub) ";
$uquery->query_orderby = ' ORDER BY user_registered '. $vars['order'];
}
}
Получим такой рабочий фильтр:
Здесь также как и в прошлом примере. Я добавил яваскрипт, чтобы при изменении значения селекта оно сразу применялось и не надо было делать лишних движений - кликать на кнопку "фильтр".
Фильтр в таблице терминов (таксономий)
На момент версии WP 4.6, для таблицы элементов таксономии подобного фильтра не предусмотрено. Более того, я попытался найти обходные пути, в народе именуемые костылями, так тут тоже не разгуляться...
Ниже пожалуй, единственный способ добавить фильтры в таблицу элементов таксономий:
Это демонстрационный пример, где мы добавим возможность урезать список терминов в таблице до только родительских...
<?php
$taxonomy = 'category'; // такса ради которой все делается
// взываем ob_start()
add_action( "{$taxonomy}_add_form", function($taxonomy){
ob_start();
} );
// вызываем ob_get_clean(), где получаем готовый HTML всей таблицы
// через preg_replace вставляем нужный нам HTML в нужно место
// Выводим что получилось на экран
add_action( "after-{$taxonomy}-table", function($taxonomy){
$html = ob_get_clean();
$__preg_replace_callback = function( $match ){
$val = @ $_GET['parent_only'];
ob_start();
?>
<div class="alignleft actions">
<select name="parent_only" onchange="window.add_param_to_URL(this)">
<option value="">Все уровни...</option>
<option value="yes" <?php selected('yes', $val) ?> >Только родители</option>
</select>
</div>
<script>
// добавляет параметр запроса в URL и редиректит при выборе селекта
window.add_param_to_URL = function(el){
var href = window.location.href, sep = /[?]/.test(href) ? "&" : "?", name = el.name.replace(/[^a-z_-]/i,'');
window.location = (new RegExp(name+'=?')).test(href) ? href.replace( (new RegExp('([?&]'+name+'=?)[^&]*')), (el.value ? "$1"+ el.value : '') ) : (href + sep + name + "="+ el.value);
}
</script>
<?php
return $match[1] . ob_get_clean();
};
echo preg_replace_callback('~(id="doaction[^<]+</div>)~', $__preg_replace_callback, $html );
} );
Чтобы копать в этом направлении, нужно смотреть следующие файлы:
Хук изменения запроса
// Изменим запрос - вариант 1
// $this->query_vars = apply_filters( 'get_terms_args', $query, $taxonomies );
add_filter( 'get_terms_args', 'my_terms_filter_handler' );
function my_terms_filter_handler( $query ){
// проверяем, что мы там где нужно, а то небо упадет...
if( empty($_GET['parent_only']) || ! is_admin() )
return $query;
// убедимся что функция get_terms вызывается именно из класса с таблицей
// она еще вызывается на этой же странице в выпадающем списке при создании термина...
if( ! ( $query['fields'] == 'count' /*подсчета количества*/ || isset($query['page']) /*таблица терминов*/ ) )
return $query;
/*
// убедимся что функция get_terms вызывается именно из класса с таблицей
$backtrace = debug_backtrace(false);
$backtrace = array_pop( $backtrace );
// для самой таблицы терминов и для подсчета количества
if( in_array( @ $backtrace['class'], array('WP_List_Table','WP_Terms_List_Table') ) )
echo $backtrace['class'];
*/
$query['parent'] = 0; // только родители
return $query;
}
Другой хук для изменения SQL запроса: terms_clauses
Этот пример не относится к предыдущему коду. Это код сам по себе, который показывает как добавить параметры запроса по метаполям, чтобы отфильтровать термины.
Предполагается, что могут быть переданы 2 параметра фильтрации subject или grade. Каждый их них отвечает за свое метаполе у терминов. Параметры могут быть переданы вместе или по отдельности...
// изменяем SQL запрос
add_filter( 'terms_clauses', 'subject_section_sortable_orderby', 10, 3 );
function subject_section_sortable_orderby( $pieces, $taxonomies, $args ){
// хорошо проверяем, что мы там где нужно, а то ппц...
if( (! @ $_GET['subject'] && ! @ $_GET['grade']) || ! is_admin() )
return $pieces;
// убедимся что функция get_terms вызывается именно из класса с таблицей
$backtrace = debug_backtrace(false);
$backtrace = array_pop( $backtrace );
if(
( @ $backtrace['class'] != 'WP_List_Table') && // для самой таблицы терминов
( @ $backtrace['class'] != 'WP_Terms_List_Table' ) // для подсчета количества
)
return $pieces;
// дополним запрос
global $wpdb;
if( @ $_GET['subject'] ){
$pieces['join'] .= " LEFT JOIN $wpdb->termmeta AS tm ON t.term_id = tm.term_id ";
$pieces['where'] .= " AND tm.meta_key = 'ss_subject' AND tm.meta_value = ". intval($_GET['subject']) ." ";
}
if( @ $_GET['grade'] ){
$pieces['join'] .= " LEFT JOIN $wpdb->termmeta AS tm2 ON t.term_id = tm2.term_id ";
$pieces['where'] .= " AND tm2.meta_key = 'ss_grade' AND tm2.meta_value = ". intval($_GET['grade']) ." ";
}
return $pieces;
}