WordPress. Пользовательские таксономии

04.09.2019

Теги: CMSWeb-разработкаWordPressВиджетИерархияНавигацияПлагинТаксономия

Функционал произвольных записей и таксономий был внедрен разработчиками довольно давно, еще с версии 2.3, которая вышла в далеком 2007 году. Именно тогда WordPress из чисто блогового движка превратился в многофункциональныю CMS, способную решать самые разные задачи.

Произвольные таксономии — это зарегистрированные в системе категории или метки, которые никак не связаны со стандартными рубриками или метками WordPress. При регистрации любой таксономии требуется указать тип записи, для которой эта таксономия будет создаваться. Как нетрудно догадаться, это должен быть пользовательский тип записи, иначе зачем было огород городить?

Когда создается новый тип записи, эти записи надо как-то упорядочить, т.е. добавить для этого типа записи одну или несколько таксономий. Давайте создадим новый тип записи product (товар) и создадим две таксономии: иерархическую (по типу стандартных рубрик) и плоскую (по типу стандартных меток).

Плагин «Каталог товаров»

Итак, создаем директорию tokmakov-catalog и внутри нее — файл tokmakov-catalog.php:

<?php
/*
Plugin Name: Каталог товаров
Plugin URI: https://tokmakov.msk.ru
Description: Позволяет создать на сайте простой каталог товаров с категориями и метками.
Version: 1.0
Author: Евгений Токмаков
Author URI: https://tokmakov.msk.ru
*/

register_activation_hook(__FILE__, function() {
    // проверяем права пользователя на установку плагинов
    if (!current_user_can('activate_plugins')) {
        return;
    }
});

register_deactivation_hook(__FILE__, function() {
    // проверяем права пользователя на деактивацию плагинов
    if (!current_user_can('deactivate_plugins')) {
        return;
    }
});

Регистрируем пользовательский тип записи product:

/*
 * Регистрируем пользовательский тип записи product
 */
add_action('init', function () {
    $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,
        'public' => true,
        'publicly_queryable' => true,
        'show_ui' => true,
        'show_in_menu' => true,
        'query_var' => true,
        'rewrite' => true,
        'capability_type' => 'post',
        'has_archive' => true,
        'hierarchical' => false,
        'menu_position' => null,
        'supports' => [
            'title', 'editor', 'author', 'thumbnail', 'excerpt', 'custom-fields'
        ],
        'taxonomies' => ['group', 'label'],
    ];
    register_post_type('product', $args);
});

Регистрируем две таксономии — иерархическую и плоскую:

/*
 * Регистрируем две таксономии: иерархическую и плоскую
 */
add_action('init', function () {
    // иерархическая таксономия (категории)
    $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);

    // плоская таксономия (метки)
    $labels_tag = array(
        'name'          => 'Метки',
        'singular_name' => 'Метка',
        'menu_name'     => 'Метки' ,
        'search_items'  => 'Поиск по меткам',
        'all_items'     => 'Все метки',
        'edit_item'     => 'Редактировать метку',
        'view_item'     => 'Посмотреть страницу метки',
        'update_item'   => 'Сохранить метку',
        'add_new_item'  => 'Добавить новую метку',
        'new_item_name' => 'Название новой метки',
        'not_found'     => 'Меток не найдено',
        'back_to_items' => 'Назад на страницу меток',
    );
    $args_tag = array(
        'labels'            => $labels_tag,
        'show_admin_column' => true,
        'hierarchical'      => false,
    );
    register_taxonomy('label', ['product'], $args_tag);
});

Идем в панель управления и добавляем категории и метки:

Обувь
    Женская обувь
        Зимняя женская обувь
        Летняя женская обувь
    Мужская обувь
        Зимняя мужская обувь
        Летняя мужская обувь
Одежда
    Женская одежда
        Зимняя женская одежда
        Летняя женская одежда
    Мужская одежда
        Зимняя мужская одежда
        Летняя мужская одежда
Обувь     Мужская    Зимняя
Одежда    Женская    Летняя

После чего можно добавлять первый товар — и он появится на сайте:

Шаблоны для показа товаров

Сейчас для показа страницы товара используется шаблон single.php, который в моей теме выглядит так:

<?php
/**
 * Шаблон для показа отдельной записи произвольного типа,
 * кроме страниц (тип page). Файл single.php
 */

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

<div id="content">
    <div class="container">
        <div class="row">
            <main class="col-md-9">
                <?php get_template_part('parts/single'); ?>
            </main>
            <aside class="col-md-3">
                <?php get_sidebar(); ?>
            </aside>
        </div>
    </div>
</div>

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

Но этот шаблон сам не выводит контент записи, а подключает для этого другой шаблон — single.php в директории parts:

<?php
/**
 * Шаблон для показа отдельной записи произвольного типа,
 * кроме page. Файл parts/single.php
 */
?>

<?php if (have_posts()): ?>
    <div id="clean-single">
        <?php the_post(); ?>
        <h1><?php the_title(); ?></h1>
        <?= get_the_post_thumbnail(null, 'full'); ?>
        <?php the_content(); ?>
        <div class="well well-sm clearfix">
            <span class="pull-left">Категории: <?php the_category(', '); ?></span>
            <span class="pull-right"><?php the_tags(); ?></span>
        </div>
        <ul class="pager">
            <li class="previous">
                <?php
                previous_post_link(
                    '%link',
                    __('Предыдущая', 'clean')
                );
                ?>
            </li>
            <li class="next">
                <?php
                next_post_link(
                    '%link',
                    __('Следующая', 'clean')
                );
                ?>
            </li>
        </ul>
    </div>
    <?php comments_template(); ?>
<?php endif; ?>

Это нас не устраивает, поэтому создадим свой шаблон single-product.php, который будет подключать single-product.php в директории parts:

<?php
/**
 * Шаблон для показа отдельной записи типа product, файл single-product.php
 */

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

<div id="content">
    <div class="container">
        <div class="row">
            <main class="col-md-9">
                <?php get_template_part('parts/single-product'); ?>
            </main>
            <aside class="col-md-3">
                <?php get_sidebar(); ?>
            </aside>
        </div>
    </div>
</div>

<?php
/*
 * Подключаем подвал сайта
 */
get_footer();
?>
<?php
/**
 * Шаблон для показа отдельной записи типа product, файл parts/single-product.php
 */
?>

<?php if (have_posts()): ?>
    <div id="clean-single">
        <?php the_post(); ?>
        <h1><?php the_title(); ?></h1>
        <p><?= get_the_post_thumbnail(null, 'full'); ?></p>
        <?php the_content(); ?>
        <div class="well well-sm clearfix">
            <span class="pull-left">
                Категории:
                <?=
                get_the_term_list(
                    get_the_ID(),
                    'group',
                    '',
                    ', ',
                    ''
                );
                ?>
            </span>
            <span class="pull-right">
                Метки:
                <?=
                get_the_term_list(
                    get_the_ID(),
                    'label',
                    '',
                    ', ',
                    ''
                );
                ?>
            </span>
        </div>
    </div>
<?php endif; ?>

Обратите внимание, что вместо функций the_category() и the_tags(), которые работают с нативными таксномиями WordPress, мы используем функцию get_the_term_list(), которая позволяет получить список элементов произвольной таксономии, связанных с записью.

Аналогично можно создать шаблоны для показа списка товаров выбранной категории и списка товаров с какой-то меткой. Например, для показа товаров категории создать шаблон taxonomy-group.php, а для показа товаров с меткой — шаблон taxonomy-label.php.

Создаем меню каталога

Теперь надо подумать, как пользователь будет ориентироваться в нашем каталоге товаров. Для этогог создадим виджет, который будет показывать все категории в иерархическом виде:

Создаем файл класса виджета Product_Categories_Widget.php в директории плагина:

<?php
/**
 * Класс виджета, который позволяет вывести все категории товаров
 * в виде многоуровневого списка
 */
class Product_Categories_Widget extends WP_Widget {

    /**
     * Cоздание виджета
     */
    function __construct() {
        parent::__construct(
            'product_categories_widget',
            'Категории товаров', // заголовок виджета
            ['description' => 'Категории каталога товаров в виде дерева'] // описание
        );
    }

    /**
     * Метод выводит категории товаров в общедоступной части сайта
     */
    public function widget($args, $instance) {

        // к заголовку применяем фильтр
        $title = apply_filters('widget_title', $instance['title']);

        echo $args['before_widget'];

        // выводим заголовок виджета
        if ( ! empty($title)) {
            echo $args['before_title'] . $title . $args['after_title'];
        }

        // выводим категории каталога
        $this->tree(0);

        echo $args['after_widget'];
    }

    private function tree($parent) {
        $terms = get_terms([
            'taxonomy' => 'group',
            'hide_empty' => false,
            'parent' => $parent
        ]);

        if (!empty($terms)) {
            ?>
            <ul<?= $parent ? '' : ' id="product-categories-widget"'; ?>>
                <?php foreach ($terms as $term): ?>
                    <?php $link = get_term_link($term->term_id, 'group'); ?>
                    <li><a href="<?= $link; ?>"><?= $term->name; ?></a>
                        <?php $this->tree($term->term_id); ?>
                    </li>
                <?php endforeach; ?>
            </ul>
            <?php
        }
    }

    /*
     * Форма настроек виджета в панели управления
     */
    public function form($instance) {
        $title = '';
        if (isset($instance['title'])) {
            $title = $instance['title'];
        }
        ?>
        <p>
            <label for="<?= $this->get_field_id('title'); ?>">Заголовок</label>
            <input type="text" class="widefat"
                   id="<?= $this->get_field_id('title'); ?>"
                   name="<?= $this->get_field_name('title'); ?>'"
                   value="<?= esc_attr($title); ?>" />
        </p>
        <?php
    }

    /*
     * Сохранение настроек виджета в панели управления
     */
    public function update($new_instance, $old_instance) {
        $instance = array();
        $instance['title'] =
            ! empty($new_instance['title']) ? strip_tags($new_instance['title']) : '';
        return $instance;
    }
}

Далее в коде плагина подключаем файл класса и регистрируем виджет:

require 'Product_Categories_Widget.php';

/*
 * Регистрируем виджет «Дерево категорий каталога товаров»
 */
add_action(
    'widgets_init',
    function () {
        register_widget('Product_Categories_Widget');
    }
);

После того, как в панели управления мы разместим наш виджет в сайдбаре, он сформирует такой html-код:

<h3 class="widget-title">Категории каталога</h3>
<ul id="product-categories-widget">
    <li>
        <a href="...">Обувь</a>
        <ul>
            <li>
                <a href="...">Женская обувь</a>
                <ul>
                    <li><a href="...">Зимняя женская обувь</a></li>
                    <li><a href="...">Летняя женская обувь</a></li>
                </ul>
            </li>
            <li>
                <a href="...">Мужская обувь</a>
                <ul>
                    <li><a href="...">Зимняя мужская обувь</a></li>
                    <li><a href="...">Летняя мужская обувь</a></li>
                </ul>
            </li>
        </ul>
    </li>
    <li>
        <a href="...">Одежда</a>
        <ul>
            <li>
                <a href="...">Женская одежда</a>
                <ul>
                    <li><a href="...">Зимняя женская одежда</a></li>
                    <li><a href="...">Летняя женская одежда</a></li>
                </ul>
            </li>
            <li>
                <a href="...">Мужская одежда</a>
                <ul>
                    <li><a href="...">Зимняя мужская одежда</a></li>
                    <li><a href="...">Летняя мужская одежда</a></li>
                </ul>
            </li>
        </ul>
    </li>
</ul>

Показываем товары на главной

Пользовательские типы записей не отображаются на главной странице по умолчанию, для этого нужно изменить главный запрос:

/*
 * Показываем товары на главной странице вместе с постами блога
 */
add_action('pre_get_posts', function ($query) {
    if (is_home() && $query->is_main_query()) {
        $query->set('post_type', ['post', 'product']);
    }
    return $query;
});

Вместо заключения

Теперь мы знаем, как зарегистрировать пользовательский тип записи, добавить для него иерархическую и плоскую таксономии, создать шаблоны для показа отдельной записи и списка записей, вывести в публичной части сайта список элементов таксономии.

Поиск: CMS • Web-разработка • WordPress • Плагин • Виджет • Таксономия • register_taxonomy • register_post_type • get_the_term_list

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