Темы
Тема отвечает за внешний вид сайта: шаблоны страниц, стили, области меню. Тема — папка в themes/;
активную выбирают в «Внешний вид → Темы». Стандартная тема — «Старт» (themes/starter).
Своя тема: дочерняя или с нуля
Дочерняя тема — лучший способ сделать сайт клиента: она берёт у родительской всё, чего в ней нет, а вы меняете только нужное. Родительская тема при этом обновляется вместе с CMS.
php artisan cms:make-theme pekarnya --name="Пекарня" # дочерняя тема «Старта»
php artisan cms:make-theme pekarnya --name="Пекарня" --copy # полная копия «Старта», чтобы переделать всё
Готовый пример — дочерняя тема «Пекарня».
Из чего состоит тема
themes/
└── pekarnya/
├── theme.json ← описание (обязательно)
├── functions.php ← хуки темы (подключается, пока тема активна)
├── layout.blade.php ← общий каркас: шапка, подвал
├── index.blade.php ← главный шаблон (обязателен, если у темы нет родительской)
├── single.blade.php, page.blade.php, archive.blade.php, search.blade.php, 404.blade.php …
├── page-templates/ ← шаблоны, которые выбирают в редакторе страницы
├── partials/ ← повторяющиеся куски: карточка записи, боковая колонка
├── blocks/ ← свои шаблоны блоков редактора (необязательно)
└── assets/ ← стили, скрипты, картинки; главный файл стилей — assets/css/style.css
theme.json
{
"name": "Пекарня",
"description": "Тема для сайта пекарни.",
"version": "1.0.0",
"author": "Студия ARGELIT",
"parent": "starter",
"screenshot": "assets/screenshot.png",
"requires_cms": "0.13",
"requires_php": "8.3",
"menus": { "primary": "Главное меню", "footer": "Меню в подвале" },
"templates": {
"landing": { "name": "Лендинг без боковой колонки", "post_types": ["page"] }
}
}
| Поле | Зачем |
|---|---|
name, description, version, author |
показываются в списке тем |
parent |
папка родительской темы — тема становится дочерней |
screenshot |
картинка для списка тем, путь внутри assets/ (лучше 1200×900) |
requires_cms, requires_php |
минимальные версии; на более старой CMS тему нельзя установить и активировать |
menus |
области меню: ключ → название (меню назначают в «Внешний вид → Меню») |
templates |
свои шаблоны страниц: ключ → название и типы записей; файл — page-templates/{ключ}.blade.php |
Какой шаблон будет выбран
CMS берёт первый шаблон из списка, который есть в теме (у дочерней — сначала в ней, потом в родительской):
| Что открыто | Шаблоны по порядку |
|---|---|
| главная, лента записей | front-page → home → index |
| главная — статичная страница | front-page → шаблоны страницы |
| запись | single-{тип}-{ярлык} → single-{тип} → single → singular → index |
| страница | шаблон из редактора → page-{ярлык} → page-{id} → page → singular → index |
| рубрика | category-{ярлык} → category-{id} → category → archive → index |
| метка | tag-{ярлык} → tag-{id} → tag → archive → index |
| своя таксономия | taxonomy-{таксономия}-{ярлык} → taxonomy-{таксономия} → taxonomy → archive → index |
| архив типа записей | archive-{тип} → archive → index |
| поиск | search → index |
| не найдено | 404 → index |
Список можно поменять фильтрами template_hierarchy($candidates, $context) и {тип}_template_hierarchy
(например, single_template_hierarchy), а итоговый шаблон — фильтром template_include($view, $context).
Каркас страницы
Шаблоны обычно наследуют общий каркас layout.blade.php:
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
{{ cms_head() }}
{{ theme_styles() }}
</head>
<body class="{{ body_class() }}">
{{ cms_body_open() }}
<header>
<a href="{{ home_url() }}">{{ site_title() }}</a>
{{ cms_nav_menu('primary', ['class' => 'menu', 'depth' => 2, 'fallback' => 'pages']) }}
</header>
@yield('content')
{{ cms_footer() }}
</body>
</html>
Обязательно:
{{ cms_head() }}в<head>— заголовок вкладки, описание, Open Graph, стили блоков, событиеsite_head(сюда добавляют своё плагины);{{ theme_styles() }}после него —assets/css/style.cssтемы (у дочерней — сначала родительской, потом свой);{{ cms_body_open() }}сразу после<body>и{{ cms_footer() }}перед</body>— панель администратора и события плагинов.
Шаблон записи:
@extends('theme::layout')
@section('content')
<article>
<h1>{{ $post->displayTitle() }}</h1>
<p>{{ cms_date($post->published_at) }}</p>
{{-- Тег <img> с srcset: на телефоне браузер загрузит копию поменьше. Пусто, если изображения нет. --}}
{{ get_post_thumbnail($post, 'large', ['class' => 'post-image']) }}
{!! $post->contentHtml() !!}
</article>
@endsection
Шаблоны темы подключаются через пространство theme:: — @include('theme::partials.post-card', ['post' => $item]).
Переменные в шаблонах
| Переменная | Где | Что это |
|---|---|---|
$context |
везде | что открыто: $context->type (front-page, home, single, page, category, tag, taxonomy, post-type-archive, search, 404), $context->title(), $context->is('category', 'tag'), $context->isSingular(), $context->isArchive(), $context->isFrontPage, $context->isPreview (посетители эту версию не видят), $context->previewChanges (показаны несохранённые правки редактора, ?preview=1), $context->term, $context->search |
$post |
запись, страница | модель Cms\Models\Post: displayTitle(), contentHtml(), excerptText(), permalink(), thumbnail(), thumbnailUrl('medium'), termsOf('category'), author, published_at, getMeta('ключ'), adjacent('previous') |
$posts |
лента, архивы, поиск | записи текущей страницы ленты (постраничный список Laravel): @foreach ($posts as $item); номера страниц — как в «Старте»: @include('theme::partials.pagination', ['paginator' => $posts]) |
Вне шаблона то же самое даёт функция cms_context().
Функции для шаблонов
site_title(), site_description(), home_url('/путь'), body_class(), cms_nav_menu(), has_nav_menu(),
get_posts([...]) (например, «последние новости» в боковой колонке), get_terms('category'), cms_date(), cms_datetime(),
theme_asset('js/theme.js'), media_url($id, 'medium'), cms_form(1) (форма из раздела «Формы»). Все — в справочнике.
Меню
Области меню объявляют в theme.json (menus) или в functions.php: register_nav_menus(['social' => 'Соцсети']).
Вывод — {{ cms_nav_menu('primary', [...]) }}:
| Параметр | По умолчанию | Что значит |
|---|---|---|
class |
menu |
класс списка <ul> |
id |
— | id списка |
depth |
0 | сколько уровней показывать (0 — все) |
fallback |
— | pages — если меню не назначено, показать список страниц |
has_nav_menu('footer') — назначено ли меню в область. Пункты и готовый HTML меняют фильтры nav_menu_items и nav_menu_html.
Свои шаблоны страниц
Опишите шаблон в theme.json (templates) и положите файл page-templates/{ключ}.blade.php. В редакторе страницы
в блоке «Атрибуты» появится выбор «Шаблон». Ключ выбранного шаблона хранится в метаполе _page_template,
и этот шаблон важнее всех остальных из таблицы выше.
Стили, скрипты, картинки
Всё из папки assets/ отдаётся по адресу /theme-assets/{тема}/{путь} (стили, скрипты, картинки, шрифты):
<script src="{{ theme_asset('js/theme.js') }}" defer></script>
<img src="{{ theme_asset('img/logo.svg') }}" alt="{{ site_title() }}">
theme_asset() ищет файл сначала в дочерней теме, потом в родительской, и добавляет к адресу время изменения.
Главный файл стилей assets/css/style.css подключает theme_styles().
Блоки редактора в теме
Тема может нарисовать любой блок по-своему: положите шаблон blocks/{имя}.blade.php (например, blocks/quote.blade.php) —
он заменит стандартный. Какие данные есть у блока — в Блоках. Если тема рисует блоки полностью сама,
базовые стили блоков можно отключить: add_filter('load_block_styles', fn () => false);.
functions.php
Подключается, пока тема активна (у дочерней — после functions.php родительской). Здесь тема подписывается на события
и фильтры, регистрирует области меню, размеры картинок, свои блоки:
<?php
add_image_size('card', 600, 400, true, 'Карточка');
add_filter('excerpt_length', fn () => 25);
add_action('site_head', fn () => print('<link rel="preconnect" href="https://fonts.gstatic.com">'));
Свои функции оборачивайте в if (! function_exists('pekarnya_hours')) { … } — файл может подключаться повторно.
Ошибка в functions.php не роняет сайт: её текст показывается в «Внешний вид → Темы».
События темы
Тема может давать плагинам и дочерним темам свои точки расширения: «Старт» вызывает @action('starter_sidebar', $context)
внизу боковой колонки. Дочерняя тема «Пекарня» выводит там часы работы. Называйте свои события с префиксом темы.
Публикация
Тема распространяется как zip-архив её папки (внутри — папка темы с theme.json) и ставится в
«Внешний вид → Темы → Добавить тему»; с галочкой «Заменить установленную версию» так же обновляется.
Дочернюю тему ставят вместе с родительской. Удалить нельзя активную тему и родительскую для других тем.