Собственный тип материала
В начале серии мы отделили курс от рубрики: курс имеет собственную карточку и учебный результат, а рубрика собирает записи по теме. Теперь реализуем эту разницу в WordPress. Плагин pw-workshop-tools зарегистрирует тип материала pw_course. Результат урока — четыре самостоятельные карточки курса с обычным редактором и своим публичным префиксом, которые не исчезают при переключении темы.
Собственный тип материала не означает новую физическую таблицу для каждого вида текста. WordPress хранит поддерживаемые сущности через свои механизмы, а регистрация сообщает, какие возможности и представления нужны объекту. Это описано в руководстве регистрации типов. Название pw_course является техническим ключом, а «Курсы» — подписью интерфейса. После появления данных ключ нельзя менять случайно вместе с названием раздела.
Регистрация на init
Продолжаем основной плагин после урока 16, без необязательной Policy. Добавьте в pw-workshop-tools.php следующие полные функции и регистрации после существующего кода. Они не заменяют shortcode. Конечный полный файл находится в снимке урока 17.
function pw_workshop_register_course_type(): void {
register_post_type('pw_course', [
'labels' => [
'name' => 'Курсы',
'singular_name' => 'Курс',
'add_new_item' => 'Добавить курс',
'edit_item' => 'Изменить курс',
],
'public' => true,
'show_in_rest' => true,
'rest_base' => 'courses',
'has_archive' => 'courses',
'rewrite' => ['slug' => 'courses', 'with_front' => false],
'supports' => ['title', 'editor', 'excerpt', 'author', 'custom-fields'],
]);
}
function pw_workshop_activate(): void {
pw_workshop_register_course_type();
flush_rewrite_rules();
}
function pw_workshop_deactivate(): void {
unregister_post_type('pw_course');
flush_rewrite_rules();
}
add_action('init', 'pw_workshop_register_course_type');
register_activation_hook(__FILE__, 'pw_workshop_activate');
register_deactivation_hook(__FILE__, 'pw_workshop_deactivate');
public делает тип предназначенным для публичного использования, но конкретная карточка всё равно зависит от статуса. Черновик курса не становится доступным анонимно только из-за регистрации типа. show_in_rest подключает REST-представление и помогает использованию блочного редактора. supports перечисляет нужные возможности объекта. Здесь нет отдельной иерархии курсов и таксономии предметов: не подразумеваем их по русскому названию раздела.
По умолчанию пример использует стандартные возможности записей. Борис с ролью Editor работает с карточками, а участник Анна не получает автономную публикацию. Отдельные роли преподавателей и ограничение курсов по командам не реализованы. Для них понадобится самостоятельная схема capabilities и назначения. Изменение capability_type без такого процесса может оставить тип недоступным нужным людям или, наоборот, расширить права. В учебном шаге сохраняем понятную связь с уже рассмотренными ролями.
Четыре карточки и их адреса
После регистрации создайте четыре курса: «JavaScript» со слагом javascript и двадцатью будущими уроками, «HTML и CSS» — html-css, шестнадцать, «Публикация из Markdown» — markdown, двенадцать, «Производительность» — performance, двенадцать. Количество пока записываем в карточке редакционных данных; структурированное поле добавит следующий урок. Не вставляйте это число одновременно в несколько абзацев, которые потом придётся согласовывать вручную.
Для тела курса можно использовать обычный паттерн введения, заменяя его подсказки конкретным содержанием. У JavaScript исходное знание — базовая HTML-разметка, результат — события и запросы к API. У HTML и CSS — создание семантической страницы и её оформления. Markdown объясняет хранение и публикацию текста, а производительность — диагностику ресурсов. Это одна предметная модель с разными аудиториями, а не четыре копии одинакового описания.
После публикации на закрытом стенде ожидаются /courses/javascript/, /courses/html-css/, /courses/markdown/ и /courses/performance/. Архив типа должен находиться на /courses/. Поэтому ранее в модели не создавалась обычная страница с тем же слагом courses. Если она уже есть в вашей установке, сначала разрешите конфликт маршрутов, не удаляя содержание автоматически. Одно и то же внешнее место не должно независимо принадлежать архиву и служебной странице.
Правила адресов и активация
Новый activation hook не выполняется сам оттого, что вы изменили файл уже активного плагина. На самостоятельном стенде можно деактивировать и вновь активировать учебный Tools, предварительно сохранив состояние, либо использовать документированный способ обновления правил постоянных ссылок. Регистрация типа должна происходить до построения его правил. Это объясняет отдельный вызов функции внутри activation: обычный момент init и момент активации расширения нельзя считать одним и тем же событием.
flush_rewrite_rules является дорогой операцией обновления правил. Она здесь связана с активацией и деактивацией, а не помещена в обычный init. Иначе каждый запрос мог бы заново перестраивать маршрутизацию. Такая ошибка часто выглядит безобидно на маленьком стенде, но создаёт лишнюю стоимость на работающем сайте. Плагин должен регистрировать свой тип на каждом запросе, а обновлять сохраняемые правила лишь по согласованному изменению конфигурации.
Технический ключ типа и публичный префикс REST также различаются. pw_course идентифицирует сущность внутри WordPress, rest_base: courses задаёт часть адреса API. Префикс rewrite относится к HTML-страницам. Можно выбрать одинаковое смысловое слово для двух представлений, но это не один обработчик. В следующей главе REST будет возвращать структурированные данные, тогда как одиночная карточка показывает блоковый текст через тему. Неправильный HTML-шаблон не исправляется переименованием REST base.
Проверьте курс со статусом черновика и один опубликованный курс раздельно. Авторизованный редактор может видеть предварительный просмотр первого, анонимный посетитель — только допустимый публичный объект. Если в панели появились все четыре карточки, это ещё не означает четыре общедоступных адреса. У карты маршрутов должен быть статус материала и режим запроса. Такая проверка сохраняет связь с редакционным процессом первых уроков и не позволяет случайно выпустить незавершённое описание через новую сущность.
Шаблон карточки и граница курса
Одиночная карточка pw_course может использовать общий single темы по иерархии, пока нет более специфичного файла. Значит, она получит наш хедер, H1, содержимое и футер. Архив использует подходящую выборку основного запроса. Для будущего дизайна можно создать single-pw_course.html и archive-pw_course.html, но в этом шаге они не нужны. Подключение типа не требует немедленно копировать всю оболочку в отдельные шаблоны.
Количество уроков само по себе не создаёт учебную последовательность, связь с записями или переход «следующая глава». Эти возможности остаются отдельным расширением. Наша цель — выразить сущность курса, которую потом сможет читать API, а не построить полноценную образовательную платформу одним вызовом. Также отсутствуют регистрация студентов, оплаты, прогресс и закрытый доступ. Видимое слово «Курсы» в панели не подтверждает наличие этих функций.
Ожидаемое состояние — зарегистрированный тип, четыре карточки и пять публичных маршрутов на отдельном стенде. Активация, обновление правил и HTTP-запросы здесь не выполнялись. Если адрес карточки возвращает 404, различайте статус материала, регистрацию типа, конфликт пути и правила веб-сервера. Не исправляйте ошибку созданием дубликата обычной страницы. Следующая глава добавит одно структурированное число к каждой карточке и покажет, где проверяются его значение и право изменения.