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

02.10.2019

Теги: CMSWeb-разработкаWordPressЗапросКаталогТоваровМетаДанныеПлагинФормаШаблонСайта

Хорошо, у нас есть возможность добавления фильтров и мы можем устанавливать значения этих фильтров. Теперь добавим форму фильтра на страницу архива записей типа product. Эта страница доступна по адресу /product/ сразу после регистрации нового типа записи. Если это не так — нужно в панели управления перейти на страницу настроек постоянных ссылок и просто нажать кнопку «Сохранить изменения».

Добавим в код плагина функцию, которая будет выводить форму фильтров:

/*
 * Функция выводит форму с фильтрами для товаров каталога
 */
function tokmakov_catalog_filters() {
    $filters = get_option('tokmakov_catalog_filters');
    if (empty($filters)) {
        return;
    }
    /*
     * $filters = array(
     *     'size' => array(
     *         'text' = 'Размер',
     *         'value' => array(
     *             'small' => 'Маленький',
     *             'medium' => 'Средний',
     *             'large' => 'Большой'
     *         )
     *     ),
     *     'color' => array(
     *         'text' = 'Цвет',
     *         'value' => array(
     *             'red' => 'Красный',
     *             'blue' => 'Синий',
     *             'green' => 'Зеленый'
     *         )
     *     ),
     * );
     */
    $action = get_post_type_archive_link('product');
    ?>
    <form action="<?= $action; ?>" method="get" id="tokmakov-catalog-filters">
        <h3>Фильтры</h3>
        <?php foreach ($filters as $slug => $filter): ?>
            <fieldset>
                <legend><?= $filter['text']; ?></legend>
                <?php foreach ($filter['value'] as $value => $text): ?>
                    <?php
                    $checked = false;
                    if (isset($_GET['filter'][$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
}

Вызов этой функции нужно вставить в шаблон темы. Можно использовать archive.php или создать отдельный шаблон для товаров — archive-product.php. При использовании шаблона archive.php потребуется дополнительная проверка, что мы находимся на странице архива записей типа product:

if (is_post_type_archive('product')) {
    tokmakov_catalog_filters();
}

Так что второй вариант предпочтительнее:

<?php
/**
 * Шаблон для показа списка всех товаров каталога, файл archive-product.php
 */

/*
 * Подключаем шапку сайта
 */
get_header();
?>

<div id="content">
    <div class="container">
        <div class="row">
            <main class="col-md-9">
                <h1>Все товары</h1>
                <p><?php the_archive_description(); ?></p>
                <?php tokmakov_catalog_filters(); ?>
                <?php get_template_part('parts/list', 'product'); ?>
            </main>
            <aside class="col-md-3">
                <?php get_sidebar(); ?>
            </aside>
        </div>
    </div>
</div>

<?php
/*
 * Подключаем подвал сайта
 */
get_footer();
?>

Для оформления формы подключим css-файл frontend-style.css

/*
 * Подключаем файл стилей для публичной части сайта, чтобы оформить форму фильтров
 */
add_action('wp_enqueue_scripts', function () {
    wp_enqueue_style(
        'tokmakov-catalog',
        plugin_dir_url(__FILE__) . 'frontend-style.css'
    );
});
#tokmakov-catalog-filters {
    background: #eee;
    padding: 20px 10px 10px 10px;
    margin: 2em 0 1em 0;
    border: 1px solid #ddd;
    position: relative;
}
    #tokmakov-catalog-filters h3 {
        font-size: 1.2em;
        position: absolute;
        top: -12px;
        margin: 0;
        background: #fff;
        padding: 2px 15px;
        border: 1px solid #ddd;
        border-radius: 15px;
        display: inline-block;
    }
    #tokmakov-catalog-filters fieldset {
        border: 1px solid #ddd;
        padding: 5px 10px 10px 10px;
        margin-bottom: 10px;
        background: #fff;
    }
    #tokmakov-catalog-filters fieldset:last-child {
        margin-bottom: 0;
    }
        #tokmakov-catalog-filters fieldset legend {
            border: 1px solid #ddd;
            padding: 0 10px;
            border-radius: 12px;
            background: #fff;
            width: auto;
            min-width: 70px;
            font-size: 1em;
            margin-bottom: 0;
        }
        #tokmakov-catalog-filters fieldset input[type="checkbox"] {
            position: relative;
            top: 2px;
        }

Массив $_GET, который надо обработать, выглядит так:

Array
(
    [filter] => Array
        (
            [size] => Array
                (
                    [small] => 1
                    [large] => 1
                )
            [color] => Array
                (
                    [white] => 1
                    [black] => 1
                )
        )
    [apply_filter] => Применить
)
/*
 * Изменяем основной запрос к базе данных
 */
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;
    }
    // если данные формы не отправлены — то и фильтровать не надо
    if (empty($_GET['filter'])) {
        return;
    }
    $filters = get_option('tokmakov_catalog_filters');
    if (empty($filters)) {
        return;
    }
    /*
    $meta_query => array(
        'relation' => 'AND',
        array(
            array(
                'key'     => 'size',
                'value'   => array('small', 'large'),
                'compare' => 'IN',
            )
        ),
        array(
            array(
                'key'   => 'color',
                'value'   => array('white', 'black'),
                'compare' => 'IN',
            )
        ),
    )
    */
    $meta_query = [];
    foreach ($filters as $slug => $filter) {
        $temp = [];
        foreach ($filter['value'] as $value => $text) {
            if (isset($_GET['filter'][$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);
    }
});

Основным параметром для работы с мета-данными в WP_Query является meta_query. Он получает записи по ключам и значениям произвольных полей. В нашем случае meta_query может быть:

array(
    'relation' => 'AND',
    array(
        'relation' => 'OR',
        array(
            'key'     => 'size',
            'value'   => 'small',
        ),
        array(
            'key'     => 'size',
            'value'   => 'large',
        ),
    ),
    array(
        'relation' => 'OR',
        array(
            'key'     => 'color',
            'value'   => 'white',
        ),
        array(
            'key'     => 'color',
            'value'   => 'black',
        ),
    ),
)
array(
    'relation' => 'AND',
    array(
        array(
            'key'     => 'size',
            'value'   => array('small', 'large'),
            'compare' => 'IN',
        )
    ),
    array(
        array(
            'key'   => 'color',
            'value'   => array('white', 'black'),
            'compare' => 'IN',
        )
    ),
)

Мы использовали второй вариант и в итоге SQL-запрос будет таким:

SELECT
    SQL_CALC_FOUND_ROWS `wp_posts`.`ID`
FROM
    `wp_posts`
    INNER JOIN `wp_postmeta` ON (`wp_posts`.`ID` = `wp_postmeta`.`post_id`)
    INNER JOIN `wp_postmeta` AS `mt1` ON (`wp_posts`.`ID` = `mt1`.`post_id`)
WHERE
    1 = 1
    AND
    (
        (`wp_postmeta`.`meta_key` = '_tokmakov_catalog_filter_size' AND `wp_postmeta`.`meta_value` IN ('small','large'))
        AND 
        (`mt1`.`meta_key` = '_tokmakov_catalog_filter_color' AND `mt1`.`meta_value` IN ('white','black'))
    )
    AND
        `wp_posts`.`post_type` = 'product'
    AND
        `wp_posts`.`post_status` = 'publish'
GROUP BY
    `wp_posts`.`ID`
ORDER BY
    `wp_posts`.`post_date` DESC
LIMIT
    0, 5

Поиск: CMS • Web-разработка • WordPress • Каталог товаров • Мета данные • Форма • Шаблон сайта • Запрос • SQL • Плагин

Каталог оборудования
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.