# League CommonMark: настоящая PHP-мастерская

Восемь редактируемых сценариев, `league/commonmark 2.10.7`, PHP >= 8.2 + mbstring, Bootstrap 5.3.8. PHP действительно выполняет преобразование. JavaScript управляет редактором и изолированным предпросмотром; другого Markdown-парсера здесь нет.

## Быстрый запуск Windows / PowerShell

Установите поддерживаемый PHP 8.2+ и Composer. В используемом `php.ini` включите `extension=mbstring`; каталог `extension_dir` должен соответствовать установленному PHP. Для Composer нужны доступ в интернет и рабочие CA-сертификаты. Не отключайте проверку TLS.

```powershell
php --version
php --ini
php -m | Select-String mbstring
composer --version
composer install --no-interaction --prefer-dist
composer check-platform-reqs
composer test
composer http-smoke
composer build-public
php -S 127.0.0.1:8501 -t public
```

Откройте http://127.0.0.1:8501/ . Остановка собственного локального сервера — Ctrl+C. Встроенный сервер PHP предназначен для локальной разработки.

Проверка API в другом окне PowerShell, без неоднозначной кодировки curl:

```powershell
$payload = @{ markdown = "# Привет`n`n**Готово**"; profile = 'core'; htmlPolicy = 'escape' } | ConvertTo-Json
Invoke-RestMethod -Uri http://127.0.0.1:8501/convert.php -Method Post -ContentType 'application/json; charset=utf-8' -Body ([Text.Encoding]::UTF8.GetBytes($payload))
php bin/batch.php fixtures var/export-windows docs
```

CLI откажется перезаписывать существующие HTML. Для повторного запуска выберите новый выходной каталог. Отсутствие Markdown-файлов означает пустую обработку; CLI возвращает 0, проверьте ожидаемое число выходных файлов своей CI-командой.

## Linux / Ubuntu или Debian

```bash
sudo apt update
sudo apt install php-cli php-mbstring php-xml php-curl php-zip unzip composer
php -v
php -m | grep mbstring
composer install --no-interaction --prefer-dist
composer check-platform-reqs
composer test
composer http-smoke
composer build-public
php -S 127.0.0.1:8501 -t public
```

Состав и версия PHP в репозитории зависят от выпуска дистрибутива; до установки проверьте, что будет PHP 8.2+ и доступна поддержка безопасности. Не обновляйте системный PHP действующего сервера вслепую. CLI и PHP-FPM могут читать разные ini: mbstring нужен в обоих runtime.

```bash
curl --fail-with-body http://127.0.0.1:8501/convert.php \
  -H 'Content-Type: application/json' \
  --data '{"markdown":"# Привет\n\n**Готово**","profile":"core","htmlPolicy":"escape"}'
php bin/batch.php fixtures var/export-linux docs
```

HTTP-smoke `composer http-smoke` запускает собственный PHP-сервер на случайном свободном loopback-порту, проверяет 22 HTTP-контракта и останавливает только свой процесс. Дополнительный Python или Node для него не нужен; PHP CLI должен разрешать proc_open и HTTP streams (allow_url_fopen). Это проверка локального runtime, не конфигурации вашего рабочего Nginx/FPM.

## Восемь сценариев

1. Статья: заголовки, выделение, списки и цитаты.
2. GFM: таблица, task list, зачёркивание; сравнение с Core.
3. Документация: fenced code и экранирование, без выполнения кода.
4. Оглавление: `[TOC]`, якоря и одинаковые заголовки.
5. Комментарии: raw HTML `escape`/`strip`, опасные URI отключены.
6. Ссылки и изображения: external-link `rel`, alt, локальная иллюстрация и политика preview.
7. Своё расширение: `{{note:текст}}`, до 160 символов, одна строка, экранированный renderer.
8. Пакет: от 1 до 5 документов одним JSON-запросом; в редакторе разделитель отдельной строкой `---DOC---`.

Входные файлы `fixtures/01.md`–`08.md` — авторские примеры. Файл 08 содержит текст для пакетного редактора: CLI рассматривает его как единый Markdown. Для реальной пакетной миграции CLI каждый документ должен лежать в отдельном `.md`-файле.

## API и ограничения

`POST public/convert.php`, Content-Type application/json; одиночный запрос:

```json
{"markdown":"# Заголовок", "profile":"core", "htmlPolicy":"escape"}
```

Пакет: `{"documents":["# Один","# Два"],"profile":"docs"}`. Профили только `core`, `gfm`, `docs`, `comments`, `links`, `notes`; политики только `escape` и `strip`. Не передавайте оба вида входа одновременно. Неизвестные поля запрещены.

Ответ содержит `ok`, `mode`, `results[]`; каждый результат содержит точный HTML, число байтов входа/выхода, профиль, политику, версию и измеренную длительность преобразования в PHP. Время не включает сеть и всю инициализацию endpoint.

Ограничения: тело JSON 64 КиБ, Markdown 8192 байта UTF-8 суммарно, максимум 5 документов, nesting 20, emphasis delimiters 200 на строку, бюджет TOC 100 пунктов суммарно по копиям, HTML 128 КиБ суммарно, процесс PHP пытается установить память 64 МиБ и время 3 секунды. Эти локальные лимиты не заменяют FPM/proxy limits и защиту от большого числа параллельных запросов.

Статусы: 400 некорректный JSON/UTF-8, 405 метод, 413 размер, 415 Content-Type, 422 структура/профиль, 500 runtime. `Sec-Fetch-Site: cross-site` отклоняется; CORS не включается. Endpoint ничего не записывает, не открывает URL или указанный пользователем путь, не принимает код расширений. Не отправляйте реальные секреты в публичную демонстрацию. Тело запроса не логируется нашим кодом; политика хостинга может отличаться.

## Preview и экспорт

Ответ PHP показывается как текст через `textContent`. Для просмотра создаётся отдельная копия в inert `<template>`: удаляются переходы наружу, все картинки заменяются текстовыми подписями, кроме точного `assets/diagram.svg`, который заменяется встроенным авторским SVG. Затем документ попадает в `iframe sandbox=""` с CSP `default-src 'none'`, `img-src data:` и локальным inline-стилем. Скриптов, доступа к родителю и сетевых ресурсов нет. Якоря внутри документа доступны.

«Скачать HTML предпросмотра» сохраняет эту же ограниченную автономную копию с CSS и CSP. «Скачать JSON» и «Копировать HTML» содержат точный результат PHP; ограничения preview к нему не применены. При использовании в продукте отдельно определите политику внешних изображений/ссылок, контекст вывода и доверенные расширения. CommonMark — не универсальный HTML-санитайзер. Отмена прекращает ожидание в браузере, но не гарантирует остановку уже выполняющегося PHP-запроса. Изменение текста/профиля очищает результат, поздний ответ не подменяет новое состояние.

## Linux/VPS: Nginx + PHP-FPM

Размещайте проект, например, в `/srv/markdown-lab`, а web root строго в `/srv/markdown-lab/public`. `vendor`, `src`, `fixtures`, `tests`, Composer-файлы и архивы разработки остаются выше web root. Зависимости устанавливает пользователь проекта, не root и не www-data. FPM получает чтение кода, но не право редактировать PHP. Для этого endpoint не требуется writable-каталог.

Установите PHP-FPM + mbstring для выбранной версии. Проверьте реальный сокет через `ls /run/php/`; ниже `php8.3-fpm.sock` — пример, его надо заменить. Пример виртуального хоста до включения TLS:

```nginx
# В контексте http, не внутри server:
limit_req_zone $binary_remote_addr zone=markdown_lab:10m rate=2r/s;

server {
    listen 80;
    server_name markdown.example.com;
    root /srv/markdown-lab/public;
    index index.html;
    client_max_body_size 64k;

    location = /convert.php {
        limit_req zone=markdown_lab burst=5 nodelay;
        limit_req_status 429;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root/convert.php;
        fastcgi_pass unix:/run/php/php8.3-fpm.sock;
        fastcgi_read_timeout 5s;
    }
    location ~ \.php$ { return 404; }
    location / { try_files $uri $uri/ =404; }
    location ~ /\. { deny all; }
}
```

```bash
sudo nginx -t
sudo systemctl reload nginx
sudo systemctl status php8.3-fpm --no-pager
```

Не заменяйте конфигурацию чужих сайтов. Настройте HTTPS для своего домена принятым на сервере способом (Caddy или действующий ACME-клиент), затем добавьте redirect HTTP→HTTPS. Nginx-фрагмент показывает маршрутизацию и лимиты, а не выдаёт готовый сертификат. За CDN/прокси real IP должен приходить только от доверенного прокси; иначе rate limit будет считать всех одним клиентом или доверять поддельным заголовкам. Для FPM задайте ограниченный пул и `request_terminate_timeout`, согласуйте с лимитами VPS. Runtime, source ZIP и публичные HTML — разные артефакты; не раздавайте всю папку проекта.

```bash
composer install --no-dev --prefer-dist --optimize-autoloader --no-interaction
composer check-platform-reqs
composer audit
composer test
```

Нулевой аудит зависимостей действует только на момент проверки; периодически повторяйте его. При обновлении меняйте lock в проверяемой ветке, повторяйте фикстуры и только затем развёртывайте. На сайте ae-nekrasov.ru runtime размещается внутри отдельного каталога плагина; в такой упаковке оператор должен запретить прямой HTTP-доступ к `src/` и `vendor/`, поскольку web root WordPress расположен выше.

## Состав и лицензии

`src/Converter.php` — общая политика и конвертация, `src/NoteExtension.php` — расширение; `public/convert.php` — HTTP; `bin/batch.php` — CLI; `tests/run.php` — проверки результата и ошибок. `composer build-public` обновляет копии исходников `.txt` для чтения в браузере. `vendor/` воспроизводится командой Composer по lock. Лицензия авторского кода MIT, полный текст MIT и лицензии зависимостей находятся в `LICENSE`, `licenses/`, `THIRD-PARTY-LICENSES.txt`. Bootstrap — MIT; зависимости League и Nette включают их собственные условия.

Официальная документация: https://commonmark.thephpleague.com/2.x/ . Статья: https://ae-nekrasov.ru/ . Встроенные исходники и JSON фикстур не содержат клиентских или производственных данных.
