Темы

Тема отвечает за внешний вид сайта: шаблоны страниц, стили, области меню. Тема — папка в 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) и ставится в «Внешний вид → Темы → Добавить тему»; с галочкой «Заменить установленную версию» так же обновляется. Дочернюю тему ставят вместе с родительской. Удалить нельзя активную тему и родительскую для других тем.