Перейти к содержимому

Публикация и обновление

Публикация отправляет собранную страницу во внешнюю систему — витрину. Основной провайдер — 4CMS (fourcms), он создаёт там статью из HTML, CSS и JS вашей страницы. Есть также провайдер ftp, выкладывающий самодостаточный HTML-файл.

Что именно создаётся на витрине, зависит от вида документа: страница становится статьёй, а шаблон и макет — отдельной сущностью «шаблон документа».

  1. Страница привязана к сайту. Без сайта публикация вернёт 400 «Page has no associated site».
  2. У сайта настроен провайдер. Если провайдер не привязан, ответ 400 прямо подскажет, что делать: PUT /api/v1/sites/{id}/provider/fourcms. Для 4CMS в настройках обязателен api_token, для FTP — host, username, password.
  3. Известны ID рубрик 4CMS, к которым будет привязана статья. PageCraft их не придумывает; в интерфейсе рубрики показываются деревом (GET /api/sites/{id}/rubrics), в API их берут из 4partners.io. Шаблону рубрики не нужны — их связь ведёт 4CMS, и диалог публикации их у шаблона не спрашивает: для документа, чей kind не article, в нём остаётся только имя.

Это два разных эндпоинта, и каждый работает только в своём состоянии:

Состояние страницы Что вызывать
external_id пустой (никогда не публиковалась) POST /api/v1/pages/{id}/publish
external_id заполнен POST /api/v1/pages/{id}/publish-update

Перепутали — придёт 400 с прямым указанием: «Page already published. Use publish-update instead.» или «Page not published yet. Use publish first.»

Смысл разделения простой: первая публикация создаёт статью на витрине и запоминает её идентификатор, повторная — обновляет ту же самую статью по этому идентификатору. Поэтому повторная публикация не плодит дубли.

GET /api/v1/pages/42/publication
{
"is_published": true,
"external_id": 12345,
"external_slug": "about-the-company",
"published_at": "2026-06-08T12:30:00+00:00",
"updated_at": "2026-06-08T12:31:10+00:00"
}

Эндпоинт отвечает из локальной базы и не ходит к провайдеру — он быстрый и безопасен для опроса перед каждой публикацией. Тот же объект возвращают publish и publish-update.

POST /api/v1/pages/42/publish
{
"rubric_ids": [42, 51],
"meta_title": "О компании | Мой сайт",
"meta_description": "Команда, ценности, история.",
"slug": "about"
}
  • rubric_ids обязателен для страницы и должен содержать хотя бы один положительный ID. Без него провайдер отвечает «rubric_ids is required».
  • Остальные поля необязательны: name, meta_title, meta_description, meta_keywords, meta_robots, slug, preview_text. Непереданное поле берётся со страницы — из её названия и SEO-полей. Пустые значения провайдеру не отправляются вовсе.
  • Статья создаётся сразу активной.

После успеха PageCraft сохраняет external_id и external_slug, полученные от витрины, и проставляет published_at.

У документа с kindarticle тело публикации не нужно вовсе:

POST /api/v1/pages/42/publish
{}

rubric_ids, slug и meta_* шаблону не передаются: адрес, SEO и привязку шаблона к рубрикам ведёт 4CMS. На витрине создаётся шаблон документа, его идентификатор сохраняется в external_id; external_slug у шаблона всегда null — своего адреса у него нет. Повторная публикация — тот же publish-update, тоже без тела.

Провайдер ftp шаблоны публиковать не умеет: попытка вернёт 400 «провайдер не умеет публиковать шаблоны».

POST /api/v1/pages/42/publish-update

Тело опционально. Если не передать ничего, на витрине обновятся только HTML, CSS и JS блоков, а название, рубрики и SEO-поля останутся прежними. Переданные поля заменят соответствующие значения в 4CMS; rubric_ids здесь не обязателен — без него привязка к рубрикам не меняется.

Так выглядит обычный цикл работы: правите блоки на странице → publish-update → проверяете результат на витрине.

  • Без макета — HTML блоков, CSS блоков и JS блоков тремя отдельными полями.
  • С макетом — один готовый HTML-документ: содержимое подставлено в шаблон макета, CSS и JS уже внутри.
  • Блоки со слотами данных (bindingSlots) уезжают шаблоном внутри <template>, а их значения — отдельным полем context, которое отрисовывает рантайм витрины. Провайдер FTP такого рантайма не имеет и рендерит эти блоки как обычные.

Подробности сборки — в разделе Сборка страницы из блоков.

GET /api/pages/{id}/live-url

Адрес не хранится готовым, а собирается заново: провайдер спрашивает у витрины текущий slug статьи и складывает его с доменом сайта. Заодно PageCraft синхронизирует external_slug, если адрес поменяли на стороне 4CMS.

Ответы: 400 — страница не опубликована; 502 — провайдер не смог разрешить URL (нет токена, статья не найдена, не определился домен).

Код Когда
400 не то состояние публикации, нет сайта, не привязан или неизвестен провайдер, невалидное тело, провайдер не поддерживает шаблоны
401 нет API-ключа или сессии
404 страница не найдена или принадлежит другому пользователю
502 ошибка внешнего провайдера; тело содержит его сообщение, например 4CMS API error: invalid token

502 — единственный код, за которым стоит чужая система: повтор запроса имеет смысл, изменение данных страницы обычно нет.

  • backend/src/Service/Page/PublishPageService.php, PublishUpdatePageService.php, PageLiveUrlService.php
  • backend/src/Publishing/Provider/FourCmsProvider.php, FtpProvider.php
  • backend/src/Publishing/BlockAssembler.php, LayoutRenderer.php
  • backend/src/Controller/V1/Page/PublishPagePublicAction.php, PublishUpdatePagePublicAction.php, GetPagePublicationStatusAction.php
  • backend/src/Controller/Page/PageLiveUrlAction.php