Контент: типы записей, таксономии, поля, выборки, формы
Свой тип записей
Отзывы, услуги, вакансии, товары — всё это типы записей, как «Записи» и «Страницы». Регистрируют их в событии init:
add_action('init', function () {
register_post_type('service', [
'labels' => ['name' => 'Услуги', 'singular_name' => 'Услуга', 'add_new' => 'Добавить услугу', 'count' => 'услуга|услуги|услуг'],
'supports' => ['title', 'editor', 'thumbnail', 'excerpt', 'revisions'],
'has_archive' => true, // список всех услуг по адресу /uslugi
'rewrite_slug' => 'uslugi', // адреса: /uslugi и /uslugi/{ярлык}
'menu_icon' => 'grid',
'menu_position' => 26,
'admin_slug' => 'services', // раздел админки: /admin/services
]);
});
Сразу появляются раздел в меню админки, список, редактор и адреса на сайте.
| Параметр | По умолчанию | Что значит |
|---|---|---|
labels |
из label / имени |
подписи: name, singular_name, menu_name, all_items, add_new, add_new_item, edit_item, not_found, count (`одна |
public |
true |
есть ли у записей страницы на сайте (false — только в админке, например отзывы для блока) |
hierarchical |
false |
могут ли записи быть вложенными, как страницы |
supports |
title, editor, author, revisions |
что есть в редакторе: title, editor, author, thumbnail, excerpt, revisions, page-attributes (родитель и порядок), custom-fields |
capability_type |
post |
права: post → edit_posts, publish_posts…; своё значение service → edit_services, publish_services… (их нужно выдать ролям) |
show_in_menu, menu_position, menu_icon |
true, 25, pin |
пункт меню админки; значки — pin, page, image, star, grid, list, users, mail, home, plug… |
admin_slug |
имя типа | адрес раздела в админке |
rewrite_slug |
имя типа | начало адреса на сайте (null — без префикса) |
has_archive |
false |
страница-список всех записей типа |
Шаблоны на сайте — single-service.blade.php, archive-service.blade.php (см. Темы).
Таксономии
register_taxonomy('service_category', 'service', [
'labels' => ['name' => 'Виды услуг', 'singular_name' => 'Вид услуги'],
'hierarchical' => true, // как рубрики (с вложенностью); false — как метки
'rewrite_slug' => 'vid-uslugi',
]);
Параметры: labels, hierarchical, public, show_in_menu, rewrite_slug, capabilities (manage_terms — кто
управляет терминами, по умолчанию manage_categories; assign_terms — кто ставит их записям, по умолчанию edit_posts).
Встроенные таксономии: category (рубрики) и post_tag (метки).
Записи в коде
Все записи всех типов — модель Cms\Models\Post (таблица posts), это обычная модель Eloquent:
use Cms\Models\Post;
$services = Post::ofType('service')->published()->orderBy('menu_order')->get();
$post = get_post(15);
$post->displayTitle(); // заголовок (или «(без названия)»)
$post->permalink(); // адрес на сайте
$post->contentHtml(); // текст для вывода (блоки → HTML, фильтр the_content)
$post->excerptText(30); // отрывок
$post->termsOf('category'); // рубрики записи
$post->thumbnailUrl('medium'); // изображение записи
Статусы: publish, future (запланирована), draft, pending, private. Удаление в корзину — «мягкое» (delete()),
насовсем — forceDelete().
Права на конкретную запись: current_user_can('edit_post', $post), 'delete_post', 'read_post' — с учётом автора, статуса и роли.
Выборки для шаблонов
get_posts(['type' => 'post', 'number' => 3, 'category' => 'novosti']); // свежие новости
get_posts(['type' => 'service', 'number' => 20, 'orderby' => 'menu_order', 'order' => 'asc']);
get_terms('category', ['hide_empty' => true, 'orderby' => 'count']);
get_posts() — только опубликованные: type (post), number (5, до 100), category, tag (ярлык или id, с вложенными
рубриками), exclude (id), orderby (date, title, menu_order, random), order (desc/asc).
get_terms() — hide_empty (true), number (0 — все), orderby (name/count), parent; у каждого термина есть posts_count.
Метаполя
Любые дополнительные данные записи (цена, оценка, адрес) — метаполя. Значение может быть строкой, числом или массивом.
update_post_meta($post, '_price', 1500);
$price = get_post_meta($post, '_price', 0);
delete_post_meta($post, '_price');
Ключ с подчёркиванием в начале (_price) — служебный: его не видно в блоке «Произвольные поля», его меняет только ваш код.
Поля в редакторе (метабоксы)
Свой блок полей в редакторе записи добавляют в событии add_meta_boxes — оно срабатывает для каждой открытой записи:
add_action('add_meta_boxes', function ($postType, $post) {
if ($postType->name === 'service') {
add_meta_box('service-price', 'Цена', 'myplugin::price-box', 'service', 'side', 10);
}
}, 10, 2);
add_meta_box($id, $title, $callback, $screen, $context, $priority):
$callback— имя шаблона (получит$postи$postType) или функцияfn ($post, $postType) => …, возвращающая шаблон или HTML;$screen— тип записей или список типов ('*'— все);$context—normal(под текстом) илиside(правая колонка);$priority— порядок (меньше — выше).
Сохранение — в событии save_post_{тип} (или save_post для всех типов). Права на запись CMS уже проверила:
add_action('save_post_service', function ($post, $request) {
if ($request->has('price')) {
$data = $request->validate(['price' => ['nullable', 'integer', 'min:0']]);
update_post_meta($post, '_price', (int) ($data['price'] ?? 0));
}
}, 10, 2);
Редактор сохраняет запись без перезагрузки страницы: после сохранения содержимое каждого блока полей заменяется свежим HTML с сайта
(блок, в котором есть <script>, остаётся как был). Поэтому обработчики событий вешайте на document (делегирование) или заново
после события cms:boxes-updated на форме #post-form. Автосохранение (раз в минуту) сохраняет только заголовок, текст и отрывок
и save_post не вызывает; запись может впервые появиться как автосохранённый черновик, и тогда её первое обычное сохранение придёт с $update = true.
Проверяйте, что поле пришло ($request->has): если блок полей убрали, событие всё равно срабатывает. Стандартный блок
можно убрать — в том же событии, но позже ядра: add_action('add_meta_boxes', fn () => remove_meta_box('postexcerpt'), 20);
(блоки ядра: postexcerpt, postimagediv, pageparentdiv, seodiv, postcustom, authordiv, revisionsdiv).
Полный пример — плагин «Отзывы».
Изображение записи и медиатека
has_post_thumbnail($post);
get_post_thumbnail_url($post, 'medium');
set_post_thumbnail($post, $mediaId);
media_url($mediaId, 'large'); // адрес файла медиатеки (размеры: thumbnail, medium, large, full)
add_image_size('card', 600, 400, true, 'Карточка'); // свой размер (для файлов, загруженных после этого)
Какие файлы можно загружать — фильтр upload_types, размеры уменьшенных копий — фильтр image_sizes.
Файлы — модель Cms\Models\Media, события add_attachment, edit_attachment, delete_attachment.
Формы
Формы собирают в разделе «Формы», на страницы ставят блоком «Форма», в шаблон темы — {{ cms_form(1) }}.
Для плагинов:
// Своя обработка заявки: в CRM, в Telegram…
add_action('form_submitted', function ($form, $entry) {
$phone = $entry->value('phone'); // значение поля по имени (или подписи)
}, 10, 2);
// Поменять поля при выводе формы: например, подсказку в поле телефона
add_filter('form_fields', fn (array $fields, $form) => array_map(
fn (array $field) => $field['type'] === 'tel' ? [...$field, 'placeholder' => '+7 (___) ___-__-__'] : $field,
$fields,
), 10, 2);
// Кому отправлять письма о заявках
add_filter('form_recipients', fn (array $emails, $form) => [...$emails, 'sales@example.ru'], 10, 2);
Фильтр form_fields меняет только вывод формы (подписи, подсказки, порядок); какие поля проверяются и сохраняются,
задаётся в настройках формы. Типы полей: text, email, tel, textarea, select, radio, checkbox, number, date.
Модели — Cms\Models\Form и Cms\Models\FormEntry (поля заявки — в $entry->data вместе с подписями).
Письма уходят так, как настроено в «Настройки → Почта».