Skip to content

Страницы

Страница — публично доступный URL с привязкой к шаблону и набором блоков. У каждой страницы есть тип (Page Type), который определяет дополнительные атрибуты.

Структура модели Page

КолонкаНазначение
urlПолный публичный URL (уникальный): /contacts, /blog/article-slug
aliasАлиас страницы — короткий идентификатор внутри родителя
parent_idFK на родительскую страницу (для иерархии)
type_idFK на page_types — тип страницы
template_page_idFK на template_pages — Blade-шаблон рендера
titleЗаголовок страницы
bread_titleЗаголовок в хлебных крошках (если отличается от title)
seo_dataJSON: title, description, keywords, canonical, og_image
social_dataJSON: переопределение OG/Twitter-карт для соцсетей
template_dataJSON: значения полей шаблона (см. Шаблоны)
status0 = черновик, 1 = опубликовано
publish_atДата отложенной публикации (опционально)
unpublish_atДата автоматической депубликации (опционально)
display_treeBoolean — показывать страницу в дереве/меню
city_scopeglobal / city_source / materialized (см. ниже)
city_idFK на cities — для материализованных вариантов
viewsСчётчик просмотров
imgFK на files — основное изображение
screenFK на files — скриншот для админки
orderПорядок (default 100)
handlerКастомный handler (опционально)

Статусы и публикация

Колонка status хранит tinyInteger:

ЗначениеСостояние
0Черновик — недоступна публично, видна в админке
1Опубликована — доступна по URL

Аксессор $page->is_published учитывает все факторы вместе:

status === 1
  AND (publish_at IS NULL OR publish_at <= now())
  AND (unpublish_at IS NULL OR unpublish_at > now())

Это позволяет планировать публикации и автоматическую депубликацию через колонки publish_at / unpublish_at. Команда cms:process-scheduled-pages (запускается scheduler'ом) меняет status страниц с истёкшими / наступившими сроками.

Скоупы Eloquent для запросов:

ScopeЧто фильтрует
Page::published()Опубликованные с учётом дат
Page::draft()Только черновики (status = 0)
Page::byType($typeId)По типу страницы
Page::root()Только корневые (parent_id = NULL)
Page::visible()display_tree = true

Типы страниц (PageType)

Тип страницы — это шаблон с расширенным набором полей: «Статья», «Продукт», «Новость», «Услуга». Один тип может использовать только один Blade-шаблон, но к нему добавляются собственные атрибуты.

Колонки page_types

КолонкаНазначение
slugУникальный идентификатор типа
nameОтображаемое название
iconИконка для UI
template_page_idFK на template_pages — какой шаблон используют страницы этого типа
settingsJSON произвольных настроек

Атрибуты типа страницы (PageTypeAttribute)

У типа есть собственная иерархия атрибутов в таблице page_type_attributes:

КолонкаНазначение
keyИдентификатор атрибута (snake_case)
nameОтображаемое название
typeТип значения (аналогично типам полей блоков)
optionsJSON-конфиг типа
requiredОбязательное поле
filterableМожно фильтровать список страниц по этому атрибуту
sortableМожно сортировать список страниц
orderПорядок

Две системы полей

В Templite две независимые системы полей:

  • block_fields — для блоков и шаблонов (через polymorphic fieldable_type). См. Поля.
  • page_type_attributes + page_attribute_values — для атрибутов типов страниц.

Не путайте их. Атрибуты типа страницы — это «параметры карточки» (например, author, published_at, category), которые применяются к всем страницам этого типа. Поля блоков — это контент конкретного блока на конкретной странице.

Значения атрибутов

Конкретные значения хранятся в page_attribute_values:

КолонкаНазначение
page_idFK на страницу
attribute_idFK на page_type_attributes
valueЗначение (приводится к типу атрибута)

Доступ из шаблона:

blade
@php
    $author = $page->attributeValues
        ->firstWhere('attribute.key', 'author')
        ?->value;
@endphp

Удобнее работать через scope/accessor — это обычная Eloquent-связь attributeValues().


URL и роутинг

Формат

Колонка url хранит полный путь от корня сайта:

  • /contacts — корневая страница
  • /blog — корневая страница blog
  • /blog/article-slug — дочерняя страница

URL формируется при сохранении на основе alias + parent.url. Изменение parent_id обновляет URL и каскадом всех потомков.

Catch-all маршрут

Templite регистрирует catch-all-роут в Laravel, который перехватывает все запросы, не пойманные обычными маршрутами:

Request → web.php → routes/api.php → routes/admin.php → ... → CMS catch-all

Если ни один маршрут Laravel не сработал — CMS ищет страницу по url в БД и рендерит её. Если страница не найдена — возвращает 404.

WARNING

Кастомные роуты Laravel выигрывают у страниц CMS. Если в routes/web.php объявлен Route::get('/contacts', ...) — он перехватит запрос до того, как до него дойдёт CMS-роутер.


Связь с шаблоном

У страницы две связи с шаблонами:

  1. Через type_idpage_types.template_page_id — основной путь. Тип определяет шаблон для всех страниц этого типа.
  2. Через прямое template_page_id на странице — переопределение шаблона для конкретной страницы.

При рендере PageRenderer использует $page->templatePage (которая разрешает связь). См. Шаблоны.

Значения полей шаблона хранятся в pages.template_data (JSON) на каждой странице индивидуально.


Иерархия и связи

Дерево страниц

  • parent_id → родитель.
  • Page::root() — корневые.
  • $page->children — потомки.

Через таблицу page_to_page:

$page->relatedPages

Используется для рекомендаций, перелинковки.

Блоки на странице

Через page_blocks (см. Блоки):

$page->pageBlocks

Каждая запись — конкретный экземпляр блока на странице с собственными значениями полей.

Переводы

page_translations — переводы заголовков и SEO-данных страницы:

$page->translation('en')

См. Локализация и города.

Ассеты

PageAsset — скомпилированные CSS/JS для страницы:

$page->asset?->cssUrl()
$page->asset?->jsUrl()
$page->asset?->cdnCssLinks()
$page->asset?->cdnJsLinks()

Эти ссылки автоматически подставляются в $assets шаблона страницы.


Мультигородность

Колонка city_scope определяет область видимости страницы:

ЗначениеПоведение
globalГлобальная страница — видна всем городам без изменений
city_sourceИсходник для материализации в городские варианты
materializedКонкретный city-вариант, привязан к city_id

Материализация

Из страницы-исходника (city_source) создаются материализованные копии (materialized) — отдельные записи pages с теми же blocks, но привязанные к конкретному городу. Дальше материализованную копию можно править независимо.

Связь между источником и копиями — через таблицу city_pages (source_page_idtarget_page_id).

Метод-помощник:

php
$page->isCitySource();
$page->isGlobalScope();
$page->isMaterialized();

Подробнее — Локализация и города.


SEO

Колонка seo_data (JSON) хранит:

{
  "title": "Заголовок для <title>",
  "description": "Описание для <meta description>",
  "keywords": "ключ1, ключ2",
  "canonical": "https://example.com/canonical-url",
  "og_image": "/uploads/og.jpg"
}

Колонка social_data (JSON) — переопределение полей OG/Twitter-карты, если нужно отличие от seo_data.

В шаблоне эти данные удобнее всего выводить через встроенный компонент <x-cms::meta-tags :page="$page" /> — он генерирует полный набор meta-тегов автоматически.


Импорт и экспорт

Страницы экспортируются в ZIP через раздел «Экспорт/Импорт» вместе с блоками, файлами и переводами. При экспорте:

  • data блоков сканируется на ID файлов (MediaFieldScanner::replaceIdsWithPaths) — пути сохраняются вместо ID.
  • При импорте обратное преобразование (replacePathsWithIds) восстанавливает связи.

Зависимости разрешаются по слагам через ImportContext (parent, page_type, template, blocks, presets, files).


Подводные камни

Подводные камни

  • URL должны быть уникальными глобально, а не в пределах parent. Это enforced через unique index в миграции.
  • Слаги алиаса — латиница нижнего регистра через дефис. Алиас + parent.url = url.
  • Кастомные Laravel-роуты выигрывают у CMS catch-all. Прежде чем пытаться найти страницу с конфликтным URL — проверьте routes/web.php и роуты модулей.
  • Status хранится как tinyInteger (0/1), не строка. Для UI обычно отображается как «Черновик» / «Опубликовано».
  • publish_at без status = 1 не сработает: команда cms:process-scheduled-pages поднимает status в 1 по достижении времени, но если status уже стоит 1 и publish_at в будущем — is_published всё равно вернёт false.
  • template_data vs attribute_values — разные сущности. template_data — значения полей шаблона (block_fields через polymorphic), attribute_values — значения атрибутов типа страницы (page_type_attributes).
  • Материализованные city-варианты — независимые записи. Изменения в исходнике (city_source) НЕ переносятся автоматически в материализованные копии.

Связанные разделы

Распространяется под лицензией MIT.