Skip to content

ТЗ — Пиксели/tag manager (cms/pixels)

Слой: 🟡 функц-модуль · Зрелость доноров: ★★ · Донор: — Статус: ТЗ к разработке

Назначение и возможности

Вставка рекламных пикселей (VK Pixel, Top.Mail.Ru) и Google Tag Manager из настроек, привязка событий конверсий ядра/коммерции к пикселям и загрузка скриптов только после согласия пользователя на обработку данных.

  • Вставка кодов VK Pixel, Top.Mail.Ru, GTM по ID из настроек, без правки шаблонов
  • Привязка стандартных событий конверсий (покупка, лид) к событиям ядра/коммерции
  • Consent-зависимая загрузка: скрипты не грузятся до согласия пользователя (cms/cookie-consent)
  • Настраиваемое сопоставление событие ядра → событие пикселя (без хардкода в коде)
  • CSP-совместимая вставка: источники (домены VK Pixel/Top.Mail.Ru/GTM) регистрируются через канонический фильтр FilterBus security.csp, инлайн-код — с nonce ядра, см. /cms-v2/security
  • Запрет передачи персональных данных в параметрах событий
  • Дедупликация события покупки по event_id между client-side и server-side отправкой
  • Фиксированный порядок загрузки пикселей и исключение вставки в admin-зоне

Зависимости и выключение

requires: ядро, cms/cookie-consent · suggests: cms/analytics (общий реестр целей)

Поведение при выключении: пиксели не вставляются на страницы, события конверсий не отправляются во внешние системы — остальной сайт и оформление заказов работают без изменений, деградация без поломки. Внешних платных API нет — пиксели работают через клиентский JS провайдера, модуль не тарифицируется по числу событий.

Модель данных

ТаблицаКлючевые поляПримечание
cms_pixels_providersid, code (vk|topmail|gtm), container_id, is_active, load_orderподключённые пиксели/контейнеры, порядок загрузки
cms_pixels_event_mapid, provider_id, core_event, pixel_event_nameсопоставление событие ядра → событие пикселя

FK provider_idconstrained() + index(); code — PHP Enum; уникальный индекс (provider_id, core_event) в cms_pixels_event_map защищает от дублей сопоставления.

ПДн-паспорт: модуль ПДн не хранит — container_id/event_map не персональные данные. context событий (payload в пиксель) фильтруется на входе от email/телефона/ФИО (см. «Безопасность»). Декларация: «ПДн не храню».

Входные и выходные данные

Входы:

ИсточникПоляЧем валидируется
Форма провайдера (Filament)code, container_id, is_active, load_orderFormRequest, code — whitelist поддерживаемых провайдеров
Форма сопоставления событий (Filament)provider_id, core_event, pixel_event_nameFormRequest, core_event — whitelist известных событий ядра/коммерции/cms/analytics
Событие ядра/коммерции (OrderCompleted, LeadCreated)стандартный payload событияконтракт события; слушатель тонкий — только маппинг на пиксель-событие
Согласие пользователя (чтение)статус согласияcms/cookie-consent (requires), не собственная валидация

Выходы:

ПотребительДанныеФормат
Посетитель (браузер)скрипты пикселей + вызовы событийinline/async-скрипт с nonce, в фиксированном load_order
Filament adminсписок провайдеров и сопоставленийkeyset-JSON через API
Внешние системы пикселей (VK/Top.Mail.Ru/GTM)событие конверсииклиентский вызов API провайдера с event_id для дедупликации
Подписчики событийPixelEventFiredpayload по таблице ниже

Настройки (группа pixels)

КлючТипДефолтaffectsPageCacheОписание
pixels.enabledbooltrueдаВключение вставки пикселей
pixels.vk_pixel_idstringnullдаID VK Pixel
pixels.topmail_idstringnullдаID счётчика Top.Mail.Ru
pixels.gtm_container_idstringnullдаID контейнера Google Tag Manager
pixels.require_consentbooltrueнетТребовать согласие перед загрузкой скриптов
pixels.dedupe_window_minutesint30нетОкно дедупликации event_id для события покупки
pixels.admin_zone_kill_switchbooltrueнетKill-switch: запрет вставки пикселей в admin-зоне (защита от учёта служебных визитов как трафика)

Лимиты и квоты: у внешних провайдеров пикселей нет платных лимитов количества событий (в отличие от API счётчиков cms/analytics) — квот в этом модуле нет; единственный защитный барьер — dedupe_window_minutes против задвоения конверсий в отчётах партнёра.

API

МетодПутьДоступНазначение
GET/api/v1/admin/pixels/providersadmin (pixels.view)Список подключённых пикселей
POST/api/v1/admin/pixels/event-mapadmin (pixels.manage)Правка сопоставления событий

Компоненты

Виджеты: вставка скриптов пикселей с consent-гейтом. Filament: список провайдеров, редактор сопоставления событий. Чего нет: отдельного дашборда метрик (см. cms/analytics).

Демо-контент: PixelsDemoSeeder создаёт демо-провайдер vk (тестовый container_id) и одно сопоставление OrderCompleted → purchase — галерея /_gallery показывает вставку скрипта с consent-гейтом без реального рекламного кабинета.

Фронтенд-бюджет: скрипты пикселей грузятся async/defer в объявленном load_order, ни один не блокирует первую отрисовку; отсутствие пикселя (нет container_id) не оставляет пустых <script>-тегов на странице.

Эксплуатация (ранбук): метрики pixels_events_fired_total{provider}, pixels_events_deduped_total (сколько задвоений отсеяно); алерт — резкий обвал events_fired_total при живом трафике (подозрение на сбой скрипта/CSP-блокировку).

СимптомЧто проверить / команда
Пиксель не грузится у посетителейrequire_consent + статус согласия в cms/cookie-consent, container_id не пустой
Событие покупки задвоено в кабинете партнёрапроверить dedupe_window_minutes, event_id генерируется детерминированно от заказа
Пиксель виден в admin-зонеadmin_zone_kill_switch=true, проверить middleware исключения admin-роутов
Сопоставление события не срабатываетсверить core_event в cms_pixels_event_map со списком реально издаваемых событий ядра/коммерции

Бэкап/рестор: cms_pixels_providers/event_map — конфигурационные таблицы, входят в обычный бэкап целиком; денормализованных агрегатов нет — рестор не требует пересчёта.

События и обмен

СобытиеКогдаPayload
PixelEventFiredсобытие ядра/коммерции отправлено в пиксельprovider_code, pixel_event_name, event_id, context (json, без ПДн)

Слушает: события заказа/лида ядра и коммерции для отправки конверсий; при наличии cms/analytics использует его реестр целей вместо собственного дублирования (suggests).

Таблица взаимодействий:

Сущность/модульКаналНаправлениеЧто происходит
Ядро/коммерция (OrderCompleted, LeadCreated)событие (1)ядро → pixelsтриггер маппинга на событие пикселя по cms_pixels_event_map
cms/cookie-consentсервис-вызов (4, requires)pixels → cookie-consentпроверка статуса согласия перед инициализацией загрузчика скриптов
cms/analytics (suggests)сервис-вызов (4)pixels → analyticsчтение общего реестра целей вместо дублирующего справочника
Внешние пиксели (VK/Top.Mail.Ru/GTM)внешний канал (вне 5)pixels(клиент) → провайдерклиентский JS отправляет событие с event_id напрямую в API провайдера
Подписчики PixelEventFiredсобытие (1)pixels → любойнаблюдаемость/отчётность модулей, если появятся

Фоновая работа

Фоновой работы нет — вставка скриптов и отправка событий конверсии происходит синхронно на рендере страницы (клиентский JS) и в обработчике события ядра.

Производительность и кеш

Ожидаемые объёмы: единицы провайдеров на сайт, десятки сопоставлений событий — таблицы малы, полностью читаются из кеша группы настроек (0 запросов на горячем пути рендера). Горячий путь — вставка скриптов на публичной странице: 0 запросов к БД, load_order и ID читаются из кеша настроек.

Критичные индексы: уникальный (provider_id, core_event) от дублей сопоставления. Собственных тегов кеша нет — участвует только в page-cache ядра. ID пикселей и enabled помечены affectsPageCache: изменение сбрасывает кеш страниц со вставкой скриптов.

Ожидаемая нагрузка на приём событий: клиентская отправка идёт напрямую в API провайдера пикселя, минуя сервер CMS — модуль не является промежуточным звеном для самого события, поэтому не создаёт дополнительного горячего пути на сервере сверх обычного рендера страницы. Единственная серверная работа — обработчик события ядра, формирующий PixelEventFired (лёгкий, без внешних вызовов; клиентская отправка события происходит уже отрисованным на странице JS-стеком).

Безопасность

Скрипты пикселей грузятся только после согласия из cms/cookie-consent (require_consent) — без него модуль не инициализирует загрузчик; источники доменов провайдеров (VK Pixel/Top.Mail.Ru/GTM) регистрируются модулем через канонический фильтр FilterBus security.csp (ревизия №2 ядра, п. 5) — это единственный способ разрешить домен, незарегистрированный <script> блокируется CSP-middleware ядра в режиме enforce; инлайн-код инициализации — с nonce ядра, не собственным. Персональные данные (email, телефон, ФИО) в параметрах событий запрещены и не передаются в payload.

Права: pixels.view, pixels.manage.

Провайдер отключён (is_active=false) — его скрипт не вставляется и его сопоставления событий не срабатывают, но сами строки cms_pixels_event_map не удаляются: включение провайдера обратно восстанавливает прежние сопоставления без повторной настройки.

Матрица ролей:

ДействиеАдминистраторМенеджерРедакторStudio
Просмотр провайдеров/сопоставлений (view)
Правка сопоставления событий (manage)
Подключение/отключение провайдера, container_id
admin_zone_kill_switch

UX-требования

Админ: пустое состояние списка провайдеров — «Пиксели не подключены» со ссылкой «Добавить провайдер»; массовое включение/отключение провайдеров списком; человеческая ошибка при попытке сопоставить несуществующее core_event («Событие X не издаётся ни одним установленным модулем»); подтверждение перед отключением admin_zone_kill_switch (риск учёта служебного трафика).

Посетитель: скрипты пикселей не создают видимой задержки (async-загрузка, фиксированный load_order не создаёт гонки инициализации); отклонённое согласие не блокирует остальной функционал страницы.

Крайние случаи и типовые баги

  1. Порядок загрузки пикселей — GTM должен инициализироваться раньше пикселей, которые он может проксировать, либо независимо, если настроен напрямую → load_order явно задаётся в Filament, не выводится из порядка вставки в БД по умолчанию.
  2. Дубль события покупки между client-side (браузер) и server-side (webhook/событие ядра) отправкой → дедупликация по event_id, детерминированно вычисляемому от id заказа (не случайному), с окном dedupe_window_minutes; повторная отправка в пределах окна — no-op с логом, не повторная конверсия в кабинете партнёра.
  3. Пиксель грузится в admin-зоне — без исключения администратор считается «посетителем» в статистике партнёра, искажая метрики → admin_zone_kill_switch исключает /admin/*-роуты из вставки скриптов на уровне middleware, не на уровне разметки шаблона (нельзя забыть в новом admin-виде).
  4. Отсутствие cms/cookie-consent (жёсткая зависимость requires, не suggests) → модуль не активируется вовсе без согласия (fail-safe: без реализации consent провайдера — деградация до полного отсутствия пикселей, не «грузить без гейта» как в cms/analytics, потому что cms/cookie-consent здесь requires, а не suggests).
  5. CSP блокирует скрипт пикселя (неверный nonce/домен не в whitelist CSP) → пиксель тихо не загружается в браузере посетителя, ошибка видна в консоли разработчика, не ломает остальной рендер страницы; health-чек модуля не может проверить клиентскую загрузку напрямую — только валидность конфигурации ID/nonce на сервере.
  6. Suggests-модуль выключен (cms/analytics отсутствует) → сопоставление событий работает по собственному core_event-whitelist без общего реестра целей — функционал не деградирует, просто нет переиспользования справочника.
  7. Модуль выключен посреди отправки события (например PixelEventFired создан, но модуль выключен до клиентской отправки) → клиентский JS для выключенного модуля не инициализирован вовсе (скрипт не вставлен), событие теряется на клиенте безопасно — не создаёт ошибок оформления заказа.
  8. Пустой/огромный context в событии → whitelist полей ограничивает состав, превышение — событие всё равно отправляется в пиксель без лишних полей (усечение по whitelist, не 422 — конверсия важнее строгости на этом канале).
  9. Измерение locale/city: событие конверсии одинаково для всех измерений — маппинг core_event → pixel_event_name не зависит от city_id/locale; при необходимости разных пикселей на разные регионы — отдельные провайдеры с разным container_id, не встроенная логика измерений в этом модуле.
  10. Противоречивые настройки: require_consent=false при включённом cms/cookie-consent → пиксели грузятся без ожидания согласия, хотя consent-модуль установлен. ⚠️ Противоречие: обходит смысл наличия consent-модуля и создаёт юридический риск. Разрешение: Filament показывает явное предупреждение при сохранении require_consent=false с установленным cms/cookie-consent, требует отдельного подтверждения.
  11. Конкурентное редактирование сопоставления событий двумя админами → lock_version (добавляется к cms_pixels_event_map), конфликт — 409, не «последний победил».
  12. Гонка инициализации: два пикселя с одинаковым load_order → детерминированный вторичный порядок по id (не случайный порядок из БД), чтобы поведение было воспроизводимо между запросами при одинаковом кеше.

Донорский код

Донор: — (новая разработка)

Тесты и приёмка

  • [ ] Контрактный тест: событие OrderCompleted транслируется в сопоставленное событие пикселя
  • [ ] Скрипты пикселей не грузятся до согласия пользователя (consent-гейт, requires)
  • [ ] Вставка не ломает page-cache (ID из настроек, источники доменов зарегистрированы через фильтр security.csp, инлайн — nonce ядра)
  • [ ] При выключении модуля или отсутствии cms/cookie-consent пиксели не вставляются, ошибок нет
  • [ ] Дедупликация event_id предотвращает задвоение конверсии покупки в окне dedupe_window_minutes
  • [ ] admin_zone_kill_switch исключает вставку скриптов в /admin/*
  • [ ] Права pixels.view/pixels.manage разграничивают чтение и правку сопоставлений
  • [ ] Персональные данные (email/телефон/ФИО) отсутствуют в payload событий
  • [ ] Конкурентная правка одного сопоставления двумя админами отдаёт 409
  • [ ] Контрактный набор cms-testing зелёный, пакет тестируется в testbench-изоляции
  • [ ] Feature-тест на каждый роут API; тестовая БД только pixels_test, migrate:fresh запрещён

Внутренняя база знаний студии