# Plugin SDK — разработка плагинов для Celena CMS

Этот документ — для людей. ИИ-агентам см. [AI_AGENT_GUIDE.md](AI_AGENT_GUIDE.md) (более жёсткий формат).

---

## Что такое плагин

Плагин — это самостоятельный пакет, расширяющий Celena. Он:

- Может добавлять страницы в админку и/или фронт.
- Может регистрировать новые теги шаблонизатора и async-блоки.
- Может подписываться на хуки ядра (`news.published`, `user.registered` и т.п.) и испускать свои.
- Может иметь собственные миграции, модели и сервисы.
- Распространяется через маркетплейс (бесплатный/платный) или вручную (zip).

---

## Структура

```
plugins/<slug>/
├── manifest.json           обязательно
├── plugin.php              обязательно — точка входа
├── README.md               рекомендуется
├── migrations/             необязательно
├── src/                    PHP-классы плагина
│   ├── Controllers/
│   ├── Models/
│   └── Services/
├── templates/              .tpl файлы плагина (для админки и фронта)
├── languages/              ru.json, en.json и т.д.
└── assets/                 CSS/JS, копируются в /public/assets/plugins/<slug>/
```

## manifest.json

```json
{
  "slug": "shop",
  "name": "Интернет-магазин",
  "version": "1.0.0",
  "author": "Celena Team",
  "description": "Полноценный e-commerce: каталог, корзина, заказы, платежи.",
  "requires": {
    "php": ">=8.3",
    "celena": ">=1.0",
    "plugins": []
  },
  "permissions": ["news.read", "users.read", "options.write"],
  "migrations": "migrations/"
}
```

## plugin.php

Точка входа. Подключается при старте `Kernel::boot()` для активных плагинов.

```php
<?php
declare(strict_types=1);

use Celena\Core\Kernel;
use Celena\Core\Http\Router;
use Celena\Core\Plugin\Hook;
use Celena\Core\Template\BlockRegistry;
use Celena\Core\Template\TagRegistry;

$container = Kernel::container();

// 1. Маршруты
$router = $container->make(Router::class);
$router->get('/shop', \Celena\Plugin\Shop\Controllers\StorefrontController::class . '@index');
$router->get('/shop/{slug:[a-z0-9\-]+}', \Celena\Plugin\Shop\Controllers\StorefrontController::class . '@product');
$router->get('/admin/shop', \Celena\Plugin\Shop\Controllers\AdminController::class . '@index');

// 2. Async-блоки
$container->make(BlockRegistry::class)->register('shop_cart', function ($c, array $params): array {
    return [
        'template' => 'plugins/shop/async/cart',
        'vars' => ['items' => /* ... */],
    ];
});

// 3. Теги шаблонизатора
$tags = $container->make(TagRegistry::class);
$tags->registerInline('price', function (array $params, array $vars): string {
    return number_format((float) ($params['amount'] ?? 0), 2, '.', ' ') . ' $';
});

// 4. Хуки
Hook::on('user.registered', function (array $user) {
    // например, создать пустую корзину
});
```

---

## Хуки

Контракт: `<area>.<event>` в нижнем регистре.

### Действия (action)
Эффекты без возврата. Несколько подписчиков, нет порядка важности.

```php
Hook::on('news.published', fn(array $n) => /* ... */);
Hook::action('news.published', $news);
```

### Фильтры (filter)
Преобразование значения. Передают значение по цепочке.

```php
Hook::addFilter('news.title', fn(string $t) => trim($t));
$title = Hook::filter('news.title', $news['title']);
```

### Список встроенных хуков ядра

| Хук | Тип | Аргументы |
|---|---|---|
| `kernel.boot` | action | `Container $c` |
| `request.before` | action | `Request $req` |
| `response.before_send` | filter | `Response $res, Request $req` |
| `user.registered` | action | `array $user` |
| `user.login.success` | action | `array $user` |
| `user.login.failed` | action | `string $email, string $ip` |
| `news.published` | action | `array $news` |
| `news.title` | filter | `string $title, array $news` |
| `template.render.before` | action | `string $template, array $vars` |
| `cache.cleared` | action | `string $type` |

(Список будет дополняться по мере роста ядра.)

---

## Async-блоки и теги

См. [TEMPLATES.md](TEMPLATES.md), разделы «Async-блоки» и «Регистрация своих тегов».

---

## Миграции

```php
<?php
declare(strict_types=1);

use Celena\Core\Database\Blueprint;
use Celena\Core\Database\Connection;
use Celena\Core\Database\Schema;

return function (Schema $schema, Connection $conn): void {
    $schema->create('shop_products', function (Blueprint $t) {
        $t->id();
        $t->string('slug', 191);
        $t->string('title', 255);
        $t->decimal('price', 10, 2);
        $t->integer('stock')->default(0);
        $t->boolean('is_active')->default(true);
        $t->json('attributes')->nullable();
        $t->timestamps();
        $t->unique('slug');
        $t->index('is_active');
    });
};
```

Запускаются автоматически при включении плагина. Уже применённые — пропускаются (по таблице `celena_migrations`).

---

## Ассеты плагина

CSS/JS из `plugins/<slug>/assets/` **автоматически копируются** в `public/assets/plugins/<slug>/` при активации или при обновлении файла (по mtime). В шаблонах темы используй тег `{plugin_assets}` — он подключит CSS всех активных плагинов.

Правило именования: файл, идентичный slug-у плагина (`shop.css`, `shop.js`), подключается автоматически. Прочие файлы — используйте прямую ссылку вида `/public/assets/plugins/shop/icon.svg`.

## Шаблоны плагина

Engine резолвит пути по префиксу `plugins/<slug>/` автоматически:

- `$engine->render('plugins/shop/front/index', ...)` → `plugins/shop/templates/front/index.tpl`

Активная тема может переопределить шаблон плагина, создав файл с тем же путём у себя: `templates/mytheme/plugins/shop/front/index.tpl` — он будет в приоритете.

## Пример: плагин «Интернет-магазин»

В комплекте Celena идёт рабочий плагин [plugins/shop/](../plugins/shop/):

- Миграции `shop_products`, `shop_categories`, `shop_orders`, `shop_order_items`
- Сервис `CartService` (корзина в сессии)
- Контроллеры фронта: `/shop`, `/shop/product/{slug}`, `/cart`, `/checkout`, `/checkout/thanks/{number}`
- Контроллеры админки: `/admin/shop/products`, `/admin/shop/orders`
- Async-блок `shop_cart_widget` в шапке сайта
- Inline-тег `{price amount="123.45"}`
- Хуки: `shop.order.created`, `shop.order.status_changed`

Изучайте его как reference-имплементацию.

---

## Документация плагина

Каждый плагин **обязан** иметь папку `plugins/<slug>/docs/` с минимум одним `README.md`. Админ открывает её прямо из админки — кнопкой `(i)` на странице `Плагины` или пунктом «📖 Документация» в подменю плагина в сайдбаре. Документация выезжает в drawer'е справа на 80% экрана.

### Минимум — один файл

`plugins/<slug>/docs/README.md` — обзор плагина: что делает, какие маршруты добавляет, базовые настройки. Если других файлов нет — этого достаточно.

### Несколько разделов

Положите дополнительные `*.md`-файлы рядом с README:

```
plugins/myplugin/docs/
├── README.md         — Обзор
├── templates.md      — Шаблоны
├── hooks.md          — Хуки и события
└── api.md            — Внешний API
```

По умолчанию они подхватываются в алфавитном порядке (README первым), заголовок берётся из первой `# H1` строки или имени файла.

### Явный порядок и заголовки — через manifest

В `manifest.json` поле `docs`:

```json
"docs": [
  { "file": "README.md",    "title": "Обзор" },
  { "file": "templates.md", "title": "Шаблоны" },
  { "file": "hooks.md",     "title": "Хуки и события" },
  { "file": "api.md",       "title": "REST API" }
]
```

Порядок в списке = порядок табов в drawer'е. Файлы вне `docs/` не отдаются (защита от path traversal).

### Формат

Стандартный Markdown:

- Заголовки `#`/`##`/`###` — уровни.
- Списки `-`, `1.`.
- Таблицы (GFM-стиль).
- Inline-код `` `code` ``, fenced ``` ```php / ```json и т.д.
- Цитаты `>`.

HTML в исходнике **экранируется** автоматически (Markdown-рендер ядра — `Celena\Core\Support\Markdown`). XSS невозможен. Скрипты, `<script>`, `<iframe>` не выполнятся — будут показаны как текст.

### Что писать

Минимальная разбивка для большого плагина:

| Файл | Содержание |
|---|---|
| `README.md` | Что делает плагин, какие пункты меню добавляет, базовые настройки, совместимость с PHP/Celena. |
| `templates.md` | Список всех `.tpl`-файлов плагина с описанием за что отвечает каждый. **Обязательно** показать, как переопределить шаблон в теме. |
| `hooks.md` | Хуки, на которые плагин подписывается + хуки, которые он диспатчит сам (для других плагинов). С примерами кода. |
| `api.md` | Если плагин экспонирует REST API. |
| `migrations.md` | Если структура БД нетривиальная. |

Эталон — документация плагина `shop` в `plugins/shop/docs/`.

---

## Переопределение шаблонов в активной теме

Не правьте файлы внутри `plugins/<slug>/templates/` — они потеряются при обновлении плагина. Вместо этого скопируйте нужный шаблон в активную тему:

```
plugins/shop/templates/front/product.tpl
        ↓ копировать в ↓
templates/<active_theme>/plugins/shop/front/product.tpl
```

`Engine::resolve()` ищет шаблоны плагина в таком порядке:

1. `templates/<active_theme>/plugins/<slug>/<rest>.tpl` — оверрайд активной темы
2. `templates/default/plugins/<slug>/<rest>.tpl` — оверрайд default-темы (fallback)
3. `plugins/<slug>/templates/<rest>.tpl` — оригинальный шаблон плагина

Поэтому достаточно создать файл по пути из пункта 1 — он автоматически перебьёт оригинал. Удалите файл темы — вернётся оригинальный шаблон плагина.

### Пример

Хотите изменить разметку карточки товара:

1. Создать каталог `templates/default/plugins/shop/front/`.
2. Скопировать туда `plugins/shop/templates/front/product.tpl` под тем же именем.
3. Править копию.
4. Шаблон плагина не трогать.

После обновления Shop до 1.1 — ваша версия продолжит работать.

---

## Подписка плагина в маркетплейсе

(Раздел будет описан после релиза маркетплейса. План — в `cuddly-tickling-zebra.md`.)
