Свої фільтри в таблицях записів, коментарів, користувачів, таксономій
Стаття про те, як додавати додаткові фільтри для списків записів (таблиця записів в адмін-панелі), списку коментарів (таблиця коментарів) або списку користувачів (таблиця користувачів). У статті теорія та приклади.
Згадайте таблицю постів в адмінці – над таблицею є два списки, що випадають: за датою і за рубриками. Йдеться про такі фільтри. Нижче показано як додати свої фільтри (випадають списки) і потім обробити запит, урізавши список записів до значень фільтра.
Можливості описаних нижче фільтрів можна розширити за допомогою додаткових колонок , що сортуються в таблиці. Фільтрами можна «урізати» список записів, а далі сортувати цей список колонками.
Усі хуки для вставки 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
.
Для цього використовуємо два фільтри: 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 (Корисний).
Завдання: вивести ці статуси у «селекті» у фільтрах таблиці коментарів та обробити запит – якщо вибрано якийсь статус, то змінити запит, залишивши в таблиці лише коментарі із зазначеним статусом.
Для цього використовуємо два фільтри: 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 = 'категорія'; // Такса заради якої все робиться // закликаємо 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
Цей приклад не стосується попереднього коду. Це код сам по собі, який показує як додати параметри запиту метаполями, щоб відфільтрувати терміни.
Передбачається, що можуть бути передані два параметри фільтрації 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; }