Страницы
Страница — публично доступный URL с привязкой к шаблону и набором блоков. У каждой страницы есть тип (Page Type), который определяет дополнительные атрибуты.
Структура модели Page
| Колонка | Назначение |
|---|---|
url | Полный публичный URL (уникальный): /contacts, /blog/article-slug |
alias | Алиас страницы — короткий идентификатор внутри родителя |
parent_id | FK на родительскую страницу (для иерархии) |
type_id | FK на page_types — тип страницы |
template_page_id | FK на template_pages — Blade-шаблон рендера |
title | Заголовок страницы |
bread_title | Заголовок в хлебных крошках (если отличается от title) |
seo_data | JSON: title, description, keywords, canonical, og_image |
social_data | JSON: переопределение OG/Twitter-карт для соцсетей |
template_data | JSON: значения полей шаблона (см. Шаблоны) |
status | 0 = черновик, 1 = опубликовано |
publish_at | Дата отложенной публикации (опционально) |
unpublish_at | Дата автоматической депубликации (опционально) |
display_tree | Boolean — показывать страницу в дереве/меню |
city_scope | global / city_source / materialized (см. ниже) |
city_id | FK на cities — для материализованных вариантов |
views | Счётчик просмотров |
img | FK на files — основное изображение |
screen | FK на 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_id | FK на template_pages — какой шаблон используют страницы этого типа |
settings | JSON произвольных настроек |
Атрибуты типа страницы (PageTypeAttribute)
У типа есть собственная иерархия атрибутов в таблице page_type_attributes:
| Колонка | Назначение |
|---|---|
key | Идентификатор атрибута (snake_case) |
name | Отображаемое название |
type | Тип значения (аналогично типам полей блоков) |
options | JSON-конфиг типа |
required | Обязательное поле |
filterable | Можно фильтровать список страниц по этому атрибуту |
sortable | Можно сортировать список страниц |
order | Порядок |
Две системы полей
В Templite две независимые системы полей:
block_fields— для блоков и шаблонов (через polymorphicfieldable_type). См. Поля.page_type_attributes+page_attribute_values— для атрибутов типов страниц.
Не путайте их. Атрибуты типа страницы — это «параметры карточки» (например, author, published_at, category), которые применяются к всем страницам этого типа. Поля блоков — это контент конкретного блока на конкретной странице.
Значения атрибутов
Конкретные значения хранятся в page_attribute_values:
| Колонка | Назначение |
|---|---|
page_id | FK на страницу |
attribute_id | FK на page_type_attributes |
value | Значение (приводится к типу атрибута) |
Доступ из шаблона:
@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-роутер.
Связь с шаблоном
У страницы две связи с шаблонами:
- Через
type_id→page_types.template_page_id— основной путь. Тип определяет шаблон для всех страниц этого типа. - Через прямое
template_page_idна странице — переопределение шаблона для конкретной страницы.
При рендере PageRenderer использует $page->templatePage (которая разрешает связь). См. Шаблоны.
Значения полей шаблона хранятся в pages.template_data (JSON) на каждой странице индивидуально.
Иерархия и связи
Дерево страниц
parent_id→ родитель.Page::root()— корневые.$page->children— потомки.
Связанные страницы (related)
Через таблицу 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_id → target_page_id).
Метод-помощник:
$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 через
uniqueindex в миграции. - Слаги алиаса — латиница нижнего регистра через дефис. Алиас + 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_datavsattribute_values— разные сущности.template_data— значения полей шаблона (block_fieldsчерез polymorphic),attribute_values— значения атрибутов типа страницы (page_type_attributes).- Материализованные city-варианты — независимые записи. Изменения в исходнике (
city_source) НЕ переносятся автоматически в материализованные копии.
Связанные разделы
- Шаблоны — Blade-каркас рендера
- Блоки — содержимое страницы
- Поля — типы значений полей блоков и шаблонов
- Локализация и города — переводы и мультигородность
- Файлы и медиа — изображения страницы