Свои фильтры в таблицах записей, комментариев, пользователей, таксономий
Статья о том, как добавлять дополнительные фильтры для списков записей (таблица записей в админ-панели), для списка комментариев (таблица комментов) или для списка пользователей (таблица пользователей). В статье теория и примеры.
Вспомните таблицу постов в админке - над таблицей есть два выпадающих списка: по дате и по рубрикам. Речь идет о таких фильтрах. Ниже показано как добавить свои фильтры (выпадающие списки) и затем обработать запрос, урезав список записей до значений фильтра.
Возможности описанных ниже фильтров можно расширить с помощью дополнительных сортируемых колонок в таблице. Фильтрами можно «урезать» список записей, а дальше сортировать этот список по колонкам.
Все хуки для вставки 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; }