WordPress. Фильтр записей по произвольным полям. Часть 3 из 3

04.10.2019

Теги: CMSRegExpWeb-разработкаWordPressКаталогТоваровМетаДанныеПлагинФормаЧПУ

В принципе, наш фильтр уже работает и на этом можно было бы и закруглиться. Но URL страницы с результатми фильтрации выглядит страшно, потому что GET-параметр filter представляет собой вложенный массив. Давайте улучшим наш плагин и добавим ЧПУ, чтобы из URL сразу было понятно, где мы находимся. В результате URL страницы с результатом фильтрации товаров будет выглядеть так

http://www.server.com/products/filter/size-in-small-large-and-color-in-white-black/

Что мы будем делать:

  1. Добавим в форму скрытое поле action и изменим значение атрибута action (это из кодекса WordPress)
  2. Создадим обработчик данных формы, который будет анализировать массив $_REQUEST и выполнять редирект
  3. Создадим два правила перезаписи URL и добавим к публичным переменным запроса еще одну — filter
  4. Ну и еще несколько изменений, поскольку формат передачи данных о включенных фильтрах изменился
register_activation_hook(__FILE__, function() {
    // проверяем права пользователя на установку плагинов
    if (!current_user_can('activate_plugins')) {
        return;
    }
    // регистрируем тип записи product и таксономию group
    tokmakov_catalog_register();
    // добавляем правила перезаписи URL, связанные с каталогом
    tokmakov_catalog_rewrite();
    flush_rewrite_rules();
});
register_deactivation_hook(__FILE__, function() {
    // проверяем права пользователя на деактивацию плагинов
    if (!current_user_can('deactivate_plugins')) {
        return;
    }
    // отменяем регистрацию типа записи product и таксономии group
    unregister_post_type('product');
    unregister_taxonomy('group');
    // удаляем правила перезаписи URL, связанные с каталогом
    flush_rewrite_rules();
});

При активации плагина мы регистрируем тип записи product, иерархическую таксономию group, добавляем два правила перезаписи URL для фильтра. И сразу обновляем правила перезаписи URL, чтобы WordPress записал новые правила в таблицу БД wp_options.

/*
 * В случае, если внешний код (сам WordPress или какой-то плагин)
 * сбрасывает правила перезаписи URL, нам нужно добавить два наших
 * правила, связанных с фильтром
 */
add_action('generate_rewrite_rules', 'tokmakov_catalog_rewrite');
function tokmakov_catalog_rewrite() {
    /*
     * Два правила перезаписи URL: одно для первой страницы результатов
     * фильтрации товаров, второе — для всех остальных страниц
     */
    add_rewrite_rule(
        'products/filter/([-_a-zA-Z0-9]+)/page/(\d+)/?$',
        'index.php?post_type=product&filter=$matches[1]&paged=$matches[2]',
        'top'
    );
    add_rewrite_rule(
        'products/filter/([-_a-zA-Z0-9]+)/?$',
        'index.php?post_type=product&filter=$matches[1]',
        'top'
    );
}
/*
 * Регистрируем тип записи product (товары каталога) и
 * таксономию group (разделы каталога)
 */
add_action('init', 'tokmakov_catalog_register');
function tokmakov_catalog_register() {
    /*
     * Регистрируем тип записи product (товары каталога)
     */
    $labels = [
        'name' => 'Товары',
        'menu_name' => 'Товары',
        'singular_name' => 'Товар',
        'add_new' => 'Добавить товар',
        'add_new_item' => 'Добавить новый товар',
        'edit_item' => 'Редактировать товар',
        'new_item' => 'Новый товар',
        'all_items' => 'Все товары',
        'view_item' => 'Посмотреть товар',
        'search_items' => 'Найти товары',
        'not_found' =>  'Ничего не найдено',
        'not_found_in_trash' => 'В корзине не найдено'
    ];
    $args = [
        'labels' => $labels,
        'description' => 'Коллекции женской, мужской и детской обуви на все времена года.',
        'public' => true,
        'publicly_queryable' => true,
        'show_ui' => true,
        'show_in_menu' => true,
        'query_var' => true,
        'capability_type' => 'post',
        /*
         * По умолчанию архив доступен по URL http://server.com/post_type/,
         * т.е.в нашем случае это будут http://server.com/product/,
         * http://server.com/product/page/2/. Но у нас архив записей типа
         * product будет доступен по URL http://server.com/products/,
         * http://server.com/products/page/2/
         */
        'has_archive' => 'products',
        'rewrite' => [
            /*
             * Страница товара будет http://server.com/product/some-slug/,
             * эту настройку можно не задавать, потому что по умолчанию
             * используется название типа записи, т.е. product.
             */
            'slug' => 'product',
            // не добавлять в начало URL общий префикс из настроек
            'with_front' => false,
            // не создавать правила перезаписи URL для feed-ленты товаров
            'feeds' => false,
            // создавать правила перезаписи URL для красивой пагинации
            'pages' => true
        ],
        'hierarchical' => false,
        'menu_position' => null,
        'supports' => [
            'title', 'editor', 'author', 'thumbnail', 'excerpt', 'custom-fields'
        ],
        'taxonomies' => ['group'],
    ];
    register_post_type('product', $args);

    /*
     * Регистрируем иерархическую таксономию (разделы каталога)
     */
    $labels = array(
        'name'          => 'Категории',
        'singular_name' => 'Категория',
        'menu_name'     => 'Категории' ,
        'all_items'     => 'Все категории',
        'edit_item'     => 'Редактировать категорию',
        'view_item'     => 'Посмотреть категорию',
        'update_item'   => 'Сохранить категорию',
        'add_new_item'  => 'Добавить новую категорию',
        'parent_item'   => 'Родительская категория',
        'search_items'  => 'Поиск по категориям',
        'back_to_items' => 'Назад на страницу категорий',
        'most_used'     => 'Популярные категории',
    );
    $args = array(
        'labels'            => $labels,
        'show_admin_column' => true,
        'hierarchical'      => true,
    );
    register_taxonomy('group', ['product'], $args);
}
/*
 * Добавляем к публичным переменным запроса еще одну — filter
 */
add_filter('query_vars', function ($vars) {
    $vars[] = 'filter';
    return $vars;
});
/*
 * Функция выводит форму с фильтрами для товаров каталога
 */
function tokmakov_catalog_filters() {
    $filters = get_option('tokmakov_catalog_filters');
    if (empty($filters)) {
        return;
    }
    /*
     * Нам нужно знать, какие флажки уже были отмечены
     */
    $var = get_query_var('filter');
    $apply = [];
    // $var = size-in-small-large-and-color-in-white-black
    if (!empty($var)) {
        // $parts = [size-in-small-large, color-in-white-black]
        $parts = explode('-and-', $var);
        foreach ($parts as $part) {
            // $temp = [size, small-large]
            $temp = explode('-in-', $part);
            // $tmp = [small, large]
            $tmp = explode('-', $temp[1]);
            foreach ($tmp as $item) {
                $apply[$temp[0]][$item] = 1;
            }
        }
    }
    ?>
    <form action="<?= admin_url('admin-post.php'); ?>" method="post" id="tokmakov-catalog-filters">
        <h3>Фильтры</h3>
        <input type="hidden" name="action" value="tokmakov_apply_filters" />
        <?php foreach ($filters as $slug => $filter): ?>
            <fieldset>
                <legend><?= $filter['text']; ?></legend>
                <?php foreach ($filter['value'] as $value => $text): ?>
                    <?php
                    $checked = false;
                    if (isset($apply[$slug][$value])) {
                        $checked = true;
                    }
                    ?>
                    <input type="checkbox" name="filter[<?= $slug; ?>][<?= $value; ?>]"
                           value="1"<?php checked($checked); ?> /> <?= $text ?>
                <?php endforeach; ?>
            </fieldset>
        <?php endforeach; ?>
        <input type="submit" name="apply_filter" value="Применить" />
    </form>
    <?php
    }
/*
 * После отправки данных формы выполняем редирект на красивый URL
 * /products/filter/size-in-small-large-and-color-in-white-black/
 */
add_action('admin_post_nopriv_tokmakov_apply_filters', 'tokmakov_catalog_process_form');
add_action('admin_post_tokmakov_apply_filters', 'tokmakov_catalog_process_form');
function tokmakov_catalog_process_form() {
    $filters = get_option('tokmakov_catalog_filters');
    $part = [];
    foreach ($filters as $slug => $filter) {
        $temp = [];
        foreach ($filter['value'] as $value => $text) {
            if (isset($_REQUEST['filter'][$slug][$value])) {
                $temp[] = $value;
            }
        }
        if (!empty($temp)) {
            $part[] = $slug . '-in-' . implode('-', $temp);
        }
    }
    $redirect = '/products/';
    if (!empty($part)) {
        $redirect .= 'filter/' . implode('-and-', $part) . '/';
    }
    wp_redirect($redirect, 302);
    die();
}

Мы использовали метод POST для отправки данных формы, но можем использовать и GET, потому что в обработчике формы работаем с массивом $_REQUEST, который выглядит так:

Array
(
    [filter] => Array
        (
            [size] => Array
                (
                    [small] => 1
                    [large] => 1
                )
            [color] => Array
                (
                    [white] => 1
                    [black] => 1
                )
        )
    [apply_filter] => Применить
)

Анализируя данные формы, формируем URL, на который выполняется редирект:

/products/filter/size-in-small-large-and-color-in-white-black/
/*
 * Изменяем основной запрос к базе данных
 */
add_action('pre_get_posts', function ($query) {
    // только в публичной части сайта
    if (is_admin()) {
        return;
    }
    // нас интересует только главный запрос
    if (!$query->is_main_query()) {
        return;
    }
    // нам нужна страница архива записей...
    if (!$query->is_post_type_archive) {
        return;
    }
    // ...только одного типа — product
    if ($query->query['post_type'] != 'product') {
        return;
    }
    // если фильтры еще не созданы, здесь делать нечего
    $filters = get_option('tokmakov_catalog_filters');
    if (empty($filters)) {
        return;
    }
    // если переменная не задана, здесь делать нечего
    $var = get_query_var('filter');
    if (empty($var)) {
        return;
    }

    /*
     * Определить, какие флажки были отмечены, мы можем из переменной
     * $var = size-in-small-large-and-color-in-white-black
     */
    $apply = [];
    // $parts = [size-in-small-large, color-in-white-black]
    $parts = explode('-and-', $var);
    foreach ($parts as $part) {
        // $temp = [size, small-large]
        $temp = explode('-in-', $part);
        // $tmp = [small, large]
        $tmp = explode('-', $temp[1]);
        foreach ($tmp as $item) {
            $apply[$temp[0]][$item] = 1;
        }
    }

    $meta_query = [];
    foreach ($filters as $slug => $filter) {
        $temp = [];
        foreach ($filter['value'] as $value => $text) {
            if (isset($apply[$slug][$value])) {
                $temp[] = $value;
            }
        }
        if (!empty($temp)) {
            $meta_query[] = [
                'key' => '_tokmakov_catalog_filter_'.$slug,
                'value' => $temp,
                'compare' => 'IN'
            ];
        }
    }
    if (!empty($meta_query)) {
        $meta_query['relation'] = 'AND';
        $query->set('meta_query', $meta_query);
    }
});

Поскольку мы добавили два правила перезаписи URL, то можем использовать для обращения к странице с включенным фильтром

http://www.server.com/products/filter/size-in-small-large-and-color-in-white-black/

Если бы мы не добавили эти правила, к странице пришлось бы обращаться так:

http://www.server.com/products/?filter=size-in-small-large-and-color-in-white-black

А если бы не было правил перезаписи, которые добавил WordPress — тогда так:

http:///www.server.com/?post_type=product&filter=size-in-small-large-and-color-in-white-black

Для наглядности — несколько снимков экрана с правилами перезаписи URL:

  1. Правила при активации плагина перед выполнением функции flush_rewrite_rules() и после (сравнение)
  2. Правила при активации плагина перед выполнением функции flush_rewrite_rules() и после (продолжение)
  3. Правила при деактивации плагина перед выполнением функции flush_rewrite_rules() и после (сравнение)
  4. Правила при деактивации плагина перед выполнением функции flush_rewrite_rules() и после (продолжение)

Дополнительно

Поиск: CMS • RegExp • Web-разработка • WordPress • Каталог товаров • Форма • ЧПУ • Мета данные • Rewrite • Плагин • Plugin

Каталог оборудования
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Производители
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Функциональные группы
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.