WordPress как на ладони
Прибыльная монетизация. Выгодно. Безопасно. Стабильно.

Свои фильтры в таблицах записей, комментариев, пользователей, таксономий

Статья о том, как добавлять дополнительные фильтры для списков записей (таблица записей в админ-панели), для списка комментариев (таблица комментов) или для списка пользователей (таблица пользователей). В статье теория и примеры.

Вспомните таблицу постов в админке - над таблицей есть два выпадающих списка: по дате и по рубрикам. Речь идет о таких фильтрах. Ниже показано как добавить свои фильтры (выпадающие списки) и затем обработать запрос, урезав список записей до значений фильтра.

Возможности описанных ниже фильтров можно расширить с помощью дополнительных сортируемых колонок в таблице. Фильтрами можно «урезать» список записей, а дальше сортировать этот список по колонкам.

Все хуки для вставки HTML фильтров

Фильтруем записи в админке. Фильтры в таблицу списка записей можно добавить для любого типа записи, комментариев или пользователей. Все это делается через нужный хук. Логика везде одна: добавляем HTML код выпадающего списка - поле формы <select>, а затем обрабатываем запрос.

Теперь, посмотрим на все возможные хуки.

Записи (включая вложения)

фильтры в списке записей

restrict_manage_posts:

// Добавляет html только вверх таблицы записей в блок стандартных фильтров.
do_action( 'restrict_manage_posts', $post_type, $which );

manage_posts_extra_tablenav:

// Добавляет html вверх или вниз таблицы записей, после кнопки сабмита стандартных фильтров.
do_action( 'manage_posts_extra_tablenav', $which );

Комментарии

filtry-kommentariev

restrict_manage_comments

// Добавляет html в верх таблицы комментариев в блоке стандартных фильтров.
do_action( 'restrict_manage_comments' );

manage_comments_nav

// Добавляет HTML вверх и вниз таблицы комментариев, после кнопки сабмита фильтров.
do_action( 'manage_comments_nav', $comment_status );

Пользователи

filtry-polzovatelj

restrict_manage_users

// Добавляет 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' );
	//}
}

Получим рабочий фильтр:

Добавление фильтров в таблицу списка записей wordpress

Фильтр в таблице комментариев

Условия: для комментарие устанавливается статус, который записывается в метаполе комментария. Нам нужно в таблице комментариев сделать возможность фильтровать список по статусу. У нас есть 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;
}
15 комментариев
Полезные 1 Все
    Войти