besnovatyj / yii2-cms-blocks
Модуль блоков Yii2 CMS: управляемое из админки содержимое мест, объявленных темой
Package info
github.com/besnovatyj/yii2-cms-blocks
Type:yii2-extension
pkg:composer/besnovatyj/yii2-cms-blocks
Requires
- php: >=8.4
- besnovatyj/yii2-cms-backend-widgets: ^1.0
- besnovatyj/yii2-cms-contracts: ^3.2
- besnovatyj/yii2-cms-datetime-widgets: ^1.0
- besnovatyj/yii2-cms-editor: ^1.0
- besnovatyj/yii2-cms-file-manager: ^1.1
- besnovatyj/yii2-cms-forms: ^1.0
- besnovatyj/yii2-cms-helpers: ^1.0
- besnovatyj/yii2-cms-kernel: ^1.2
- yiisoft/yii2: ~2.0.0
- yiisoft/yii2-bootstrap5: ~2.0.0
Requires (Dev)
- roave/security-advisories: dev-latest
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Модуль блоков — управляемое из админки содержимое мест, которые объявляет тема (аналог widget areas в WordPress).
Зачем
Тема не может заводить свои CRUD в админке, а вещей вида «объявление над шапкой», «всплывающее окно», «текст в колонке подвала» у неё много. Тема объявляет места, модуль хранит их начинку.
Как это устроено
| Роль | Кто | Что делает |
|---|---|---|
| Место | тема | объявляет его в areas.php и вызывает BlockArea::widget(['area' => '…']) в разметке |
| Каркас | тема | обёртка, кнопки, id для скриптов, всё, что не должно ломаться правкой текста |
| Блок | этот модуль | начинка места: данные по схеме типа, статус, окно показа, порядок |
| Тип блока | модуль, тема или другой модуль | схема полей и шаблон вывода, см. «Типы блоков» |
| Заготовка | тема → пикер сниппетов | разметка начинки в стиле темы, с которой удобно начать |
- Каталог мест приходит через контракт
Besnovatyj\Contracts\theme\ThemeAreaCatalog, реализация — в пакете тем (yii2-cms-themes). Пакета тем нет: место задаётся в блоке вручную. - Блоки в месте выводятся подряд по
sort_order; выключенные и вне окна показа пропускаются. Блок в месте, которое активная тема не объявляет, хранится, но не выводится (админка это показывает). - Кэш: все включённые блоки — один элемент кэша на все места с тегом
blocks, сбрасывается при любой правке. Окно показа проверяется на каждый запрос, поэтому наступившая дата не ждёт сброса. - Шорткоды: текстовые (
%name%) разворачиваются при выводе в HTML-полях, если подключён модуль шорткодов.
Типы блоков
Блок — это данные по схеме его типа, разметку рисует шаблон типа. Менеджер заполняет форму, а не верстает.
| Источник | Id | Где объявлен |
|---|---|---|
| Встроенные | html, embed |
src/types/BuiltInTypes.php, шаблоны в src/views/blockTypes/ |
| Тема | cards-3 (без префикса) |
themes/{тема}/blockTypes/{type}/schema.php + view.php |
| Модуль | Performance:showcase |
класс модуля реализует Besnovatyj\Contracts\block\BlockTypeProvider |
- Встроенные типы разрешены в любом месте. Остальные место перечисляет в
areas.phpключомtypes. - Тип задаётся при создании и не меняется.
embed— код чужого виджета, выводится как есть. Создать такой блок и править его код можно только с разрешением/Blocks/backend/embed/edit(псевдомаршрут RBAC, контроллёра за ним нет; его дают и шаблоны/Blocks/backend/*,/Blocks/*). В админке код только показывается текстом.- Шаблон модульного или встроенного типа тема перекрывает обычным overlay:
themes/{тема}/modules/{ModuleId}/views/blockTypes/…. Шаблон рендерится черезView::renderFile(). - Тип пропал (сменили тему, удалили модуль) — блок не выводится, данные хранятся, админка помечает.
Схема
// themes/berdrama/blockTypes/cards-3/schema.php return [ 'label' => 'Три карточки в ряд', 'hint' => 'Изображение, подпись и ссылка у каждой карточки.', 'fields' => [ 'heading' => ['kind' => 'text', 'label' => 'Заголовок раздела', 'maxLength' => 120], 'cards' => ['kind' => 'repeater', 'label' => 'Карточки', 'min' => 1, 'max' => 3, 'fields' => [ 'image' => ['kind' => 'image', 'label' => 'Изображение', 'required' => true], 'caption' => ['kind' => 'text', 'label' => 'Подпись'], 'link' => ['kind' => 'link', 'label' => 'Ссылка'], ]], ], ];
Ключи поля: kind (обязателен), label, hint, required, default, maxLength (text, textarea),
options (select, обязателен), min/max (repeater), fields (repeater).
| kind | Значение в $data |
Замечание |
|---|---|---|
text, textarea |
строка | |
richtext |
HTML | Jodit со сниппетами; выводить через $block->html() (текстовые шорткоды) |
image |
['file' => …, 'alt' => …] |
файл — относительно папки блока; выводить через $block->imageUrl() |
link |
['url' => …, 'route' => …, 'params' => […], 'blank' => bool] |
раздел из MenuTargetProvider или свой URL; выводить через $block->linkUrl() и $block->linkAttributes() (флажок «в новой вкладке» → target="_blank" rel="nofollow noopener") |
select |
ключ из options |
options — массив или Closure (ленивый список из БД модуля, см. ниже) |
checkbox |
bool | |
repeater |
список строк | без вложенных repeater; richtext и code внутри строки недоступны |
code |
строка | только для кода виджетов, требует разрешения embed |
Схема может меняться: отсутствующее поле получает значение по умолчанию, лишнее выбрасывается при
следующем сохранении. Шаблон всегда получает полный набор ключей и не проверяет isset.
Свободный URL ссылки принимается только относительный, #, http(s)://, mailto:, tel:.
Ленивый список select ('options' => fn () => $repo->options()) — для вариантов из БД модуля:
витрины, рубрики, галереи. Замыкание вызывают только форма и проверка при сохранении; при выводе
блока значение берётся как сохранено, без запроса. Вариант, пропавший после сохранения (удалили
рубрику), шаблон или поставщик данных обрабатывают сами — обычно ничего не находят и блок не выводят.
Шаблон
<?php use Besnovatyj\Blocks\render\BlockViewContext; use yii\helpers\Html; /** @var array $data данные по схеме */ /** @var BlockViewContext $block id, тип, место и помощники вывода */ ?> <section class="cards-3"> <?php if ($data['heading'] !== ''): ?><h2><?= Html::encode($data['heading']) ?></h2><?php endif; ?> <?php foreach ($data['cards'] as $card): ?> <a href="<?= Html::encode($block->linkUrl($card['link'])) ?>"<?= $block->linkAttributes($card['link']) ?>> <img src="<?= Html::encode($block->imageUrl($card['image'])) ?>" alt="<?= Html::encode($card['image']['alt']) ?>"> <?= Html::encode($card['caption']) ?> </a> <?php endforeach; ?> </section>
Каркас вокруг каждого блока не навязывается: шаблон сам решает, нужна ли ему обёртка. Ошибка шаблона в режиме отладки пробрасывается, на проде блок пропускается с записью в лог.
Типы модулей
Модуль реализует BlockTypeProvider::blockTypes() и возвращает BlockType с локальным id (showcase),
префикс {moduleId}: ставит модуль блоков. Данные, которых нет в полях блока (спектакли, посты),
отдаёт класс BlockType::$dataProvider (BlockDataProvider::provide(array $data): array), созданный
DI-контейнером. Кэширует выборку сам поставщик: готовый HTML блоков модуль блоков не кэширует.
Файлы блока
У каждого блока своя папка @static/origin/Blocks/{id}: создаётся вместе с блоком (у старых блоков —
при первом открытии формы), удаляется вместе с ним. Редактор и выбор изображений в форме блока
заперты в этой папке. Поэтому содержимое заполняется после первого сохранения.
Блоки ≠ шорткоды ≠ сниппеты
- Шорткод — плейсхолдер в контенте, разворачивается в код при рендере (контент → код).
- Место — «дырка» в коде темы, которую заполняет контент из админки (код → контент).
- Сниппет — заготовка разметки, вставляемая в редактор при правке.
Механизмы компонуются: блок набирают со сниппета темы, внутри могут быть текстовые шорткоды.
Вывод в теме
use Besnovatyj\Blocks\widgets\BlockArea; <?php $content = BlockArea::widget(['area' => 'popup-bar']); ?> <?php if ($content !== ''): ?> <div class="…"><?= $content ?></div> <?php endif; ?>
inline => true снимает с начинки обёртку <p>, если блок типа html — один абзац без атрибутов:
для мест, которые встают в строку (копирайт, заголовок).
Объявление мест в теме
themes/{тема}/areas.php:
return [ 'popup-bar' => ['label' => 'Полоса над шапкой', 'hint' => 'Текст объявления и кнопка-ссылка', 'sort' => 10], 'home' => ['label' => 'Главная', 'types' => ['cards-3', 'Performance:showcase'], 'sort' => 5], ];
types — типы сверх встроенных (html, embed), которые место принимает. Не задан — только встроенные.
Установка
modman install Blocks + recompile (+ reload php-fpm на проде).