Всем привет!
Хочу рассказать об универсальном способе фильтровать любые записи в WordPress. Таким способом можно фильтровать как стандартные типы записей, так и кастомные, созданные вами или плагинами. Также этот способ подойдет для каталога в WooCommerce, но там можно все сделать немного проще.
Если интересно — пишите комментарии, может быть я напишу статью об этом и выпущу видео 🙂
С чем будем работать
У меня есть сайт на WordPress, с активной дочерней темой от TwentyTwenty. В этой теме я сгенерировал 20 записей типа post при помощи WP-CLI. У записей есть 4 рубрики: WordPress; CMS; ModX; CMS Winter. Есть также теги: темы; плагины; доработки; начинающим.
В случайном порядке я добавил эти записи к рубрикам и тегам. Также для примера добавлю мета-поле «Сложность»: высокая; средняя; низкая.




На примере рубрик и меток мы рассмотрим фильтрацию по таксономиям и терминам, а на примере мета-поля — фильтрацию по мета-полям. Аналогично можно фильтровать записи по мета-полям, созданным в ACF или Carbon Fields.
Шаг 1. Создание интерфейса
Для начала создадим интерфейс — то, с чем будет работать пользователь. Разместим его вверху страницы блога. Для этого скопируем файл index.php
из родительской темы и добавим вверх экшен feodoraxis_posts_filter
.
Если не знакомы с темой экшенов, хуков и фильтров в WordPress — то можете ознакомиться со статьёй «Хуки и фильтры WordPress для начинающих«.
Код скопированного index.php
будет выглядеть так:
<?php
get_header();
?>
<!-- Добавление экшена -->
<div class="section-inner">
<?php do_action( "feodoraxis_posts_filter" ); ?>
</div>
<!-- Добавление экшена (конец) -->
<main id="site-content">
<?php
$archive_title = '';
$archive_subtitle = '';
if ( is_search() ) {
/**
* @global WP_Query $wp_query WordPress Query object.
*/
global $wp_query;
$archive_title = sprintf(
'%1$s %2$s',
'<span class="color-accent">' . __( 'Search:', 'twentytwenty' ) . '</span>',
'“' . get_search_query() . '”'
);
if ( $wp_query->found_posts ) {
$archive_subtitle = sprintf(
/* translators: %s: Number of search results. */
_n(
'We found %s result for your search.',
'We found %s results for your search.',
$wp_query->found_posts,
'twentytwenty'
),
number_format_i18n( $wp_query->found_posts )
);
} else {
$archive_subtitle = __( 'We could not find any results for your search. You can give it another try through the search form below.', 'twentytwenty' );
}
} elseif ( is_archive() && ! have_posts() ) {
$archive_title = __( 'Nothing Found', 'twentytwenty' );
} elseif ( ! is_home() ) {
$archive_title = get_the_archive_title();
$archive_subtitle = get_the_archive_description();
}
if ( $archive_title || $archive_subtitle ) {
?>
<header class="archive-header has-text-align-center header-footer-group">
<div class="archive-header-inner section-inner medium">
<?php if ( $archive_title ) { ?>
<h1 class="archive-title"><?php echo wp_kses_post( $archive_title ); ?></h1>
<?php } ?>
<?php if ( $archive_subtitle ) { ?>
<div class="archive-subtitle section-inner thin max-percentage intro-text"><?php echo wp_kses_post( wpautop( $archive_subtitle ) ); ?></div>
<?php } ?>
</div><!-- .archive-header-inner -->
</header><!-- .archive-header -->
<?php
}
if ( have_posts() ) {
$i = 0;
while ( have_posts() ) {
++$i;
if ( $i > 1 ) {
echo '<hr class="post-separator styled-separator is-style-wide section-inner" aria-hidden="true" />';
}
the_post();
get_template_part( 'template-parts/content', get_post_type() );
}
} elseif ( is_search() ) {
?>
<div class="no-search-results-form section-inner thin">
<?php
get_search_form(
array(
'aria_label' => __( 'search again', 'twentytwenty' ),
)
);
?>
</div><!-- .no-search-results -->
<?php
}
?>
<?php get_template_part( 'template-parts/pagination' ); ?>
</main><!-- #site-content -->
<?php get_template_part( 'template-parts/footer-menus-widgets' ); ?>
<?php
get_footer();
Теперь отобразим список полей для фильтрации. Для этого добавим хук к нашему экшену. Чтобы сделать это, в functions.php
дочерней темы добавим следующий код:
<?php
function feodoraxis_posts_filter():void {
global $wpdb;
//Получим доступные для фильтрации категории
$categories = get_terms( [
'taxonomy' => 'category'
] );
//Получим доступные для фильтрации теги
$tags = get_terms( [
'taxonomy' => 'post_tag'
] );
//Получим доступные для фильтрации уровни сложности
$sql = "SELECT `meta_value` FROM `{$wpdb->postmeta}` WHERE `meta_key` = 'Сложность' GROUP BY `meta_value`";
$result = $wpdb->get_results( $sql, ARRAY_A );
$difficulty_levels = array_column( $result, "meta_value" );
get_template_part( 'template-parts/posts', 'filter', [
'categories' => $categories ?? [],
'tags' => $tags ?? [],
'difficulty_levels' => $difficulty_levels ?? []
] );
}
add_action( "feodoraxis_posts_filter", "feodoraxis_posts_filter" );
Обратите внимание, что здесь мы пока получаем только возможные доступные значения, по которым пользователь может фильтровать записи в блоге. Также по мета-полю, с помощью sql-запроса мы получаем список всех доступных вариантов сложности, чтобы не хардкодить их а дать возможность получить записи по реальным значениям.
Также обратите внимание, что в конце мы, с помощью функции get_template_part()
, подключаем файл posts-filter.php
в папке template-parts
, и передаем в этот файл все полученные значения.
Создадим сам файл и сделаем в нём форму:
<?php
if ( ! defined( "ABSPATH" ) ) {
exit;
}
/**
* @var array{categories:WP_Term[], tags:WP_Term[], difficulty_levels:string[]} $args
*/
?>
<form action="<?php echo $_SERVER['REQUEST_URI']; ?>" method="post" data-action="<?php echo admin_url( "admin-ajax.php" ); ?>" id="feodoraxis_posts_filter_form">
<ul class="post-meta">
<?php if ( is_array( $args['categories'] ) && ! empty( $args['categories'] ) ) : ?>
<li>
<p>Категории:</p>
<div class="feodoraxis-flex">
<?php foreach ( $args['categories'] as $category ) : ?>
<div class="feodoraxis-flex">
<input type="radio" name="category" value="<?php echo $category->term_id; ?>" id="category_<?php echo $category->term_id; ?>" />
<label for="category_<?php echo $category->term_id; ?>"><?php echo $category->name; ?></label>
</div>
<?php endforeach; ?>
</div>
</li>
<?php endif; ?>
<?php if ( is_array( $args['tags'] ) && ! empty( $args['tags'] ) ) : ?>
<li>
<p>Теги:</p>
<?php foreach ( $args['tags'] as $tag ) : ?>
<input type="checkbox" name="tag[]" value="<?php echo $tag->term_id; ?>" id="tag_<?php echo $tag->term_id; ?>" />
<label for="tag_<?php echo $tag->term_id; ?>"><?php echo $tag->name; ?></label>
<?php endforeach; ?>
</li>
<?php endif; ?>
<?php if ( is_array( $args['difficulty_levels'] ) && ! empty( $args['difficulty_levels'] ) ) : ?>
<li>
<p>Сложность:</p>
<select name="difficulty_level" id="">
<option value="">-- Выбрать --</option>
<?php foreach ( $args['difficulty_levels'] as $difficulty_level ) : ?>
<option value="<?php echo $difficulty_level; ?>"><?php echo $difficulty_level; ?></option>
<?php endforeach; ?>
</select>
</li>
<?php endif; ?>
<li>
<button class="wp-block-search__button wp-element-button">Отправить</button>
</li>
</ul>
</form>
Для блоков с классом .feodoraxis-flex я добавил такие стили:
.feodoraxis-flex {
display: flex;
align-items: center;
}
.feodoraxis-flex label {
margin: 0;
}
Вот что должно получиться:

Мы создали интерфейс. Отлично!
Переходим к следующему шагу.
Шаг 2. Отправка Ajax-запроса
Если вы плохо знакомы с темой Ajax в WordPress, то можете ознакомиться со статьёй Ajax в WordPress
Для начала создадим файл main.js
в корне дочерней темы и подключим его. Для этого добавим такой код в файл functions.php
:
<?php
function feodoraxis_enqueue_script():void {
// Обратите внимание, что мы сначала выходим из родительской темы и потом заходим в дочернюю, т.к. функция get_template_directory_uri() по-умолчанию ведет в родительскую тему
wp_enqueue_script( 'feodoraxis-main', get_template_directory_uri() . '/../twentytwenty-child/main.js', [], '2.0' );
}
add_action( 'wp_enqueue_scripts', 'feodoraxis_enqueue_script' );
Чтобы отправить ajax-запрос, добавим такой JavaScript-код:
document.addEventListener("DOMContentLoaded", () => {
const feodoraxisPostsFilterForm = document.querySelector("#feodoraxis_posts_filter_form");
if ( ! feodoraxisPostsFilterForm ) {
return;
}
//Добавим событие отправки формы
feodoraxisPostsFilterForm.addEventListener("submit", (e) => {
//Отменим стандартное поведение формы при отправке
e.preventDefault();
//Получим ссылку, по которой нужно сделать запрос
const adminAjax = feodoraxisPostsFilterForm.dataset.action;
//Получим данные из формы
const formData = new FormData(feodoraxisPostsFilterForm);
//Добавим в данные информацию об ajax-экшене, который должен исполниться при запросе
formData.append('action', 'feodoraxis_posts_filter_request' );
//Создадим запрос
const request = new XMLHttpRequest();
// Откроем запрос
request.open("POST", adminAjax);
//Укажем заголовки запроса
request.setRequestHeader("Accept", "application/json");
//Отправим данные из формы
request.send(formData);
//Когда запрос выполнится - обновим список записей
request.onload = () => {
const resultJson = request.responseText;
const result = JSON.parse(resultJson);
document.querySelector("#site-content").innerHTML = result.data.posts;
};
return false;
})
});
Шаг 3. Обработка ajax-запроса
Теперь в functions.php
добавим код для обработки этого запроса:
function feodoraxis_posts_filter_request():void {
if ( $_SERVER['REQUEST_METHOD'] != "POST" || ! wp_doing_ajax() ) {
wp_send_json_error();
}
$args = [
'post_type' => 'post'
];
if ( isset( $_POST['category'] ) && ! empty( $_POST['category'] ) ) {
$args['tax_query'][] = [
'taxonomy' => 'category',
'field' => 'id',
'terms' => intval( $_POST['category'] ),
];
}
if ( isset( $_POST['tag'] ) && ! empty( $_POST['tag'] ) ) {
$tags = array_map( function( $tag ) {
return intval( $tag );
}, $_POST['tag'] );
$args['tax_query'][] = [
'taxonomy' => 'post_tag',
'field' => 'id',
'terms' => $tags,
];
}
if ( isset( $_POST['difficulty_level'] ) && ! empty( $_POST['difficulty_level'] ) ) {
$difficulty_level = sanitize_text_field( $_POST['difficulty_level'] );
$args['meta_query'][] = [
'key' => 'Сложность',
'value' => $difficulty_level
];
}
$query = new WP_Query( $args );
ob_start();
if ( $query->have_posts() ) {
$i = 0;
while ( $query->have_posts() ) {
++$i;
if ( $i > 1 ) {
echo '<hr class="post-separator styled-separator is-style-wide section-inner" aria-hidden="true" />';
}
$query->the_post();
get_template_part( 'template-parts/content', 'post' );
}
} else {
echo '<p>К сожалению, по вашему запросу ничего не найдено</p>';
}
$content = ob_get_contents();
ob_clean();
wp_reset_postdata();
wp_send_json_success( [
'posts' => $content,
] );
}
add_action( 'wp_ajax_' . 'feodoraxis_posts_filter_request', 'feodoraxis_posts_filter_request' );
add_action( 'wp_ajax_nopriv_' . 'feodoraxis_posts_filter_request', 'feodoraxis_posts_filter_request' );
Отлично! Если вы все сделали правильно, то после этого у вас должен заработать фильтр!
Завершение
Надеюсь, что статья была вам полезна. Обязательно пишите комментарии и задавайте вопросы. Буду рад на них ответить 🙂
Желаю вам успехов!