From 45e20ed0cd3c75004767eaae86bf0fa7d07321ca Mon Sep 17 00:00:00 2001 From: Panfilov Konstantin Date: Sat, 17 Oct 2026 07:39:37 +0300 Subject: [PATCH] fix: align stage preland picker and demo layout --- README.md | 26 +++++++- components/examples.php | 5 +- components/form.php | 2 +- ...6-10-17-stage-picker-utm-demo-structure.md | 53 ++++++++++++++++ images/{demo/1 => demo-2}/1/result.avif | Bin images/{demo/1 => demo-2}/1/result.jpg | Bin images/{demo/1 => demo-2}/1/result.webp | Bin images/{demo/2 => demo-2}/1/result_mob.avif | Bin images/{demo/1 => demo-2}/1/source.avif | Bin images/{demo/1 => demo-2}/1/source.jpg | Bin images/{demo/1 => demo-2}/1/source.webp | Bin images/{demo/2 => demo-2}/1/source_mob.avif | Bin images/{demo/1 => demo-2}/1/thumb.avif | Bin images/{demo/1 => demo-2}/1/thumb.jpg | Bin images/{demo/1 => demo-2}/1/thumb.webp | Bin images/{demo/2 => demo-2}/1/thumb_mob.avif | Bin images/{demo/1/4 => demo-2/2}/result.avif | Bin images/{demo/1/4 => demo-2/2}/result.jpg | Bin images/{demo/1/4 => demo-2/2}/result.webp | Bin images/{demo/1/4 => demo-2/2}/result_mob.avif | Bin images/{demo/1/4 => demo-2/2}/source.avif | Bin images/{demo/1/4 => demo-2/2}/source.jpg | Bin images/{demo/1/4 => demo-2/2}/source.webp | Bin images/{demo/1/4 => demo-2/2}/source_mob.avif | Bin images/{demo/1/4 => demo-2/2}/thumb.avif | Bin images/{demo/1/4 => demo-2/2}/thumb.jpg | Bin images/{demo/1/4 => demo-2/2}/thumb.webp | Bin images/{demo/2 => demo-2}/2/thumb_mob.avif | Bin images/{demo/1/2 => demo-2/3}/result.avif | Bin images/{demo/1/2 => demo-2/3}/result.jpg | Bin images/{demo/1/2 => demo-2/3}/result.webp | Bin images/{demo/1/2 => demo-2/3}/result_mob.avif | Bin images/{demo/1/2 => demo-2/3}/source.avif | Bin images/{demo/1/2 => demo-2/3}/source.jpg | Bin images/{demo/1/2 => demo-2/3}/source.webp | Bin images/{demo/1/2 => demo-2/3}/source_mob.avif | Bin images/{demo/1/2 => demo-2/3}/thumb.avif | Bin images/{demo/1/2 => demo-2/3}/thumb.jpg | Bin images/{demo/1/2 => demo-2/3}/thumb.webp | Bin images/{demo/2 => demo-2}/3/thumb_mob.avif | Bin images/{demo/1/5 => demo-2/4}/result.avif | Bin images/{demo/1/5 => demo-2/4}/result.jpg | Bin images/{demo/1/5 => demo-2/4}/result.webp | Bin images/{demo/1/5 => demo-2/4}/result_mob.avif | Bin images/{demo/1/5 => demo-2/4}/source.avif | Bin images/{demo/1/5 => demo-2/4}/source.jpg | Bin images/{demo/1/5 => demo-2/4}/source.webp | Bin images/{demo/1/5 => demo-2/4}/source_mob.avif | Bin images/{demo/1/5 => demo-2/4}/thumb.avif | Bin images/{demo/1/5 => demo-2/4}/thumb.jpg | Bin images/{demo/1/5 => demo-2/4}/thumb.webp | Bin images/{demo/2 => demo-2}/4/thumb_mob.avif | Bin images/{demo/1/3 => demo-2/5}/result.avif | Bin images/{demo/1/3 => demo-2/5}/result.jpg | Bin images/{demo/1/3 => demo-2/5}/result.webp | Bin images/{demo/1/3 => demo-2/5}/result_mob.avif | Bin images/{demo/1/3 => demo-2/5}/source.avif | Bin images/{demo/1/3 => demo-2/5}/source.jpg | Bin images/{demo/1/3 => demo-2/5}/source.webp | Bin images/{demo/1/3 => demo-2/5}/source_mob.avif | Bin images/{demo/1/3 => demo-2/5}/thumb.avif | Bin images/{demo/1/3 => demo-2/5}/thumb.jpg | Bin images/{demo/1/3 => demo-2/5}/thumb.webp | Bin images/{demo/2 => demo-2}/5/thumb_mob.avif | Bin images/demo/{2 => }/1/result.avif | Bin images/demo/{2 => }/1/result.jpg | Bin images/demo/{2 => }/1/result.webp | Bin images/demo/1/{1 => }/result_mob.avif | Bin images/demo/{2 => }/1/source.avif | Bin images/demo/{2 => }/1/source.jpg | Bin images/demo/{2 => }/1/source.webp | Bin images/demo/1/{1 => }/source_mob.avif | Bin images/demo/{2 => }/1/thumb.avif | Bin images/demo/{2 => }/1/thumb.jpg | Bin images/demo/{2 => }/1/thumb.webp | Bin images/demo/1/{1 => }/thumb_mob.avif | Bin images/demo/2/{3 => }/result.avif | Bin images/demo/2/{3 => }/result.jpg | Bin images/demo/2/{3 => }/result.webp | Bin images/demo/2/{3 => }/result_mob.avif | Bin images/demo/2/{3 => }/source.avif | Bin images/demo/2/{3 => }/source.jpg | Bin images/demo/2/{3 => }/source.webp | Bin images/demo/2/{3 => }/source_mob.avif | Bin images/demo/2/{3 => }/thumb.avif | Bin images/demo/2/{3 => }/thumb.jpg | Bin images/demo/2/{3 => }/thumb.webp | Bin images/demo/{1 => }/2/thumb_mob.avif | Bin images/demo/{2/5 => 3}/result.avif | Bin images/demo/{2/5 => 3}/result.jpg | Bin images/demo/{2/5 => 3}/result.webp | Bin images/demo/{2/5 => 3}/result_mob.avif | Bin images/demo/{2/5 => 3}/source.avif | Bin images/demo/{2/5 => 3}/source.jpg | Bin images/demo/{2/5 => 3}/source.webp | Bin images/demo/{2/5 => 3}/source_mob.avif | Bin images/demo/{2/5 => 3}/thumb.avif | Bin images/demo/{2/5 => 3}/thumb.jpg | Bin images/demo/{2/5 => 3}/thumb.webp | Bin images/demo/{1 => }/3/thumb_mob.avif | Bin images/demo/{2/2 => 4}/result.avif | Bin images/demo/{2/2 => 4}/result.jpg | Bin images/demo/{2/2 => 4}/result.webp | Bin images/demo/{2/2 => 4}/result_mob.avif | Bin images/demo/{2/2 => 4}/source.avif | Bin images/demo/{2/2 => 4}/source.jpg | Bin images/demo/{2/2 => 4}/source.webp | Bin images/demo/{2/2 => 4}/source_mob.avif | Bin images/demo/{2/2 => 4}/thumb.avif | Bin images/demo/{2/2 => 4}/thumb.jpg | Bin images/demo/{2/2 => 4}/thumb.webp | Bin images/demo/{1 => }/4/thumb_mob.avif | Bin images/demo/{2/4 => 5}/result.avif | Bin images/demo/{2/4 => 5}/result.jpg | Bin images/demo/{2/4 => 5}/result.webp | Bin images/demo/{2/4 => 5}/result_mob.avif | Bin images/demo/{2/4 => 5}/source.avif | Bin images/demo/{2/4 => 5}/source.jpg | Bin images/demo/{2/4 => 5}/source.webp | Bin images/demo/{2/4 => 5}/source_mob.avif | Bin images/demo/{2/4 => 5}/thumb.avif | Bin images/demo/{2/4 => 5}/thumb.jpg | Bin images/demo/{2/4 => 5}/thumb.webp | Bin images/demo/{1 => }/5/thumb_mob.avif | Bin template.php | 13 ++-- tests/check-demo-layout.php | 50 +++++++++++++++ tests/check-upload-admission.php | 2 +- tests/check-utm-redirect-contract.php | 58 ++++++++++++++++++ 128 files changed, 196 insertions(+), 13 deletions(-) create mode 100644 docs/superpowers/plans/2026-10-17-stage-picker-utm-demo-structure.md rename images/{demo/1 => demo-2}/1/result.avif (100%) rename images/{demo/1 => demo-2}/1/result.jpg (100%) rename images/{demo/1 => demo-2}/1/result.webp (100%) rename images/{demo/2 => demo-2}/1/result_mob.avif (100%) rename images/{demo/1 => demo-2}/1/source.avif (100%) rename images/{demo/1 => demo-2}/1/source.jpg (100%) rename images/{demo/1 => demo-2}/1/source.webp (100%) rename images/{demo/2 => demo-2}/1/source_mob.avif (100%) rename images/{demo/1 => demo-2}/1/thumb.avif (100%) rename images/{demo/1 => demo-2}/1/thumb.jpg (100%) rename images/{demo/1 => demo-2}/1/thumb.webp (100%) rename images/{demo/2 => demo-2}/1/thumb_mob.avif (100%) rename images/{demo/1/4 => demo-2/2}/result.avif (100%) rename images/{demo/1/4 => demo-2/2}/result.jpg (100%) rename images/{demo/1/4 => demo-2/2}/result.webp (100%) rename images/{demo/1/4 => demo-2/2}/result_mob.avif (100%) rename images/{demo/1/4 => demo-2/2}/source.avif (100%) rename images/{demo/1/4 => demo-2/2}/source.jpg (100%) rename images/{demo/1/4 => demo-2/2}/source.webp (100%) rename images/{demo/1/4 => demo-2/2}/source_mob.avif (100%) rename images/{demo/1/4 => demo-2/2}/thumb.avif (100%) rename images/{demo/1/4 => demo-2/2}/thumb.jpg (100%) rename images/{demo/1/4 => demo-2/2}/thumb.webp (100%) rename images/{demo/2 => demo-2}/2/thumb_mob.avif (100%) rename images/{demo/1/2 => demo-2/3}/result.avif (100%) rename images/{demo/1/2 => demo-2/3}/result.jpg (100%) rename images/{demo/1/2 => demo-2/3}/result.webp (100%) rename images/{demo/1/2 => demo-2/3}/result_mob.avif (100%) rename images/{demo/1/2 => demo-2/3}/source.avif (100%) rename images/{demo/1/2 => demo-2/3}/source.jpg (100%) rename images/{demo/1/2 => demo-2/3}/source.webp (100%) rename images/{demo/1/2 => demo-2/3}/source_mob.avif (100%) rename images/{demo/1/2 => demo-2/3}/thumb.avif (100%) rename images/{demo/1/2 => demo-2/3}/thumb.jpg (100%) rename images/{demo/1/2 => demo-2/3}/thumb.webp (100%) rename images/{demo/2 => demo-2}/3/thumb_mob.avif (100%) rename images/{demo/1/5 => demo-2/4}/result.avif (100%) rename images/{demo/1/5 => demo-2/4}/result.jpg (100%) rename images/{demo/1/5 => demo-2/4}/result.webp (100%) rename images/{demo/1/5 => demo-2/4}/result_mob.avif (100%) rename images/{demo/1/5 => demo-2/4}/source.avif (100%) rename images/{demo/1/5 => demo-2/4}/source.jpg (100%) rename images/{demo/1/5 => demo-2/4}/source.webp (100%) rename images/{demo/1/5 => demo-2/4}/source_mob.avif (100%) rename images/{demo/1/5 => demo-2/4}/thumb.avif (100%) rename images/{demo/1/5 => demo-2/4}/thumb.jpg (100%) rename images/{demo/1/5 => demo-2/4}/thumb.webp (100%) rename images/{demo/2 => demo-2}/4/thumb_mob.avif (100%) rename images/{demo/1/3 => demo-2/5}/result.avif (100%) rename images/{demo/1/3 => demo-2/5}/result.jpg (100%) rename images/{demo/1/3 => demo-2/5}/result.webp (100%) rename images/{demo/1/3 => demo-2/5}/result_mob.avif (100%) rename images/{demo/1/3 => demo-2/5}/source.avif (100%) rename images/{demo/1/3 => demo-2/5}/source.jpg (100%) rename images/{demo/1/3 => demo-2/5}/source.webp (100%) rename images/{demo/1/3 => demo-2/5}/source_mob.avif (100%) rename images/{demo/1/3 => demo-2/5}/thumb.avif (100%) rename images/{demo/1/3 => demo-2/5}/thumb.jpg (100%) rename images/{demo/1/3 => demo-2/5}/thumb.webp (100%) rename images/{demo/2 => demo-2}/5/thumb_mob.avif (100%) rename images/demo/{2 => }/1/result.avif (100%) rename images/demo/{2 => }/1/result.jpg (100%) rename images/demo/{2 => }/1/result.webp (100%) rename images/demo/1/{1 => }/result_mob.avif (100%) rename images/demo/{2 => }/1/source.avif (100%) rename images/demo/{2 => }/1/source.jpg (100%) rename images/demo/{2 => }/1/source.webp (100%) rename images/demo/1/{1 => }/source_mob.avif (100%) rename images/demo/{2 => }/1/thumb.avif (100%) rename images/demo/{2 => }/1/thumb.jpg (100%) rename images/demo/{2 => }/1/thumb.webp (100%) rename images/demo/1/{1 => }/thumb_mob.avif (100%) rename images/demo/2/{3 => }/result.avif (100%) rename images/demo/2/{3 => }/result.jpg (100%) rename images/demo/2/{3 => }/result.webp (100%) rename images/demo/2/{3 => }/result_mob.avif (100%) rename images/demo/2/{3 => }/source.avif (100%) rename images/demo/2/{3 => }/source.jpg (100%) rename images/demo/2/{3 => }/source.webp (100%) rename images/demo/2/{3 => }/source_mob.avif (100%) rename images/demo/2/{3 => }/thumb.avif (100%) rename images/demo/2/{3 => }/thumb.jpg (100%) rename images/demo/2/{3 => }/thumb.webp (100%) rename images/demo/{1 => }/2/thumb_mob.avif (100%) rename images/demo/{2/5 => 3}/result.avif (100%) rename images/demo/{2/5 => 3}/result.jpg (100%) rename images/demo/{2/5 => 3}/result.webp (100%) rename images/demo/{2/5 => 3}/result_mob.avif (100%) rename images/demo/{2/5 => 3}/source.avif (100%) rename images/demo/{2/5 => 3}/source.jpg (100%) rename images/demo/{2/5 => 3}/source.webp (100%) rename images/demo/{2/5 => 3}/source_mob.avif (100%) rename images/demo/{2/5 => 3}/thumb.avif (100%) rename images/demo/{2/5 => 3}/thumb.jpg (100%) rename images/demo/{2/5 => 3}/thumb.webp (100%) rename images/demo/{1 => }/3/thumb_mob.avif (100%) rename images/demo/{2/2 => 4}/result.avif (100%) rename images/demo/{2/2 => 4}/result.jpg (100%) rename images/demo/{2/2 => 4}/result.webp (100%) rename images/demo/{2/2 => 4}/result_mob.avif (100%) rename images/demo/{2/2 => 4}/source.avif (100%) rename images/demo/{2/2 => 4}/source.jpg (100%) rename images/demo/{2/2 => 4}/source.webp (100%) rename images/demo/{2/2 => 4}/source_mob.avif (100%) rename images/demo/{2/2 => 4}/thumb.avif (100%) rename images/demo/{2/2 => 4}/thumb.jpg (100%) rename images/demo/{2/2 => 4}/thumb.webp (100%) rename images/demo/{1 => }/4/thumb_mob.avif (100%) rename images/demo/{2/4 => 5}/result.avif (100%) rename images/demo/{2/4 => 5}/result.jpg (100%) rename images/demo/{2/4 => 5}/result.webp (100%) rename images/demo/{2/4 => 5}/result_mob.avif (100%) rename images/demo/{2/4 => 5}/source.avif (100%) rename images/demo/{2/4 => 5}/source.jpg (100%) rename images/demo/{2/4 => 5}/source.webp (100%) rename images/demo/{2/4 => 5}/source_mob.avif (100%) rename images/demo/{2/4 => 5}/thumb.avif (100%) rename images/demo/{2/4 => 5}/thumb.jpg (100%) rename images/demo/{2/4 => 5}/thumb.webp (100%) rename images/demo/{1 => }/5/thumb_mob.avif (100%) create mode 100644 tests/check-demo-layout.php create mode 100644 tests/check-utm-redirect-contract.php diff --git a/README.md b/README.md index bd6c7d9..e26cdf1 100644 --- a/README.md +++ b/README.md @@ -18,12 +18,15 @@ - `rnd` — случайное значение по весам (`weights`), так делится трафик между вариантами; - неизвестное значение отбрасывается, отсутствующее — заменяется на `default`. -Ненастроенные параметры (`utm_source`, `keyword`, `subid` и любые другие) проходят как есть. Все метки уходят дальше -в адрес сайта: сайт сохраняет их на воронку и шлёт в Keitaro. +Ненастроенные параметры (`utm_source`, `keyword`, `subid` и любые другие) проходят как есть. Перед переходом +Preland всегда формирует полный resolved-набор своих семи меток: явное допустимое значение остаётся, `rnd` +разрешается по весам, отсутствующее получает `default`. Вместе с внешними рекламными параметрами этот набор +уходит в `claim`, а затем сайт переносит его в `upscale`; ни один из семи параметров не должен пропадать только +потому, что был назначен default. | Метка | Что задаёт | Где | |---|---|---| -| `indpic` | картинка-пример на преленде | `images/demo//` | +| `indpic` | картинка-пример на преленде | `images/demo/` для `1`, `images/demo-N/` для `N > 1` | | `theme` | `light` / `dark` | преленд и сайт | | `degr` | пресет ухудшения превью «До» | сайт (#42) | | `registration` | `before` — PIN до оплаты, `after` — после | сайт, страница результата | @@ -76,6 +79,23 @@ | `title` | нет | заголовок страницы («Улучшение изображения») | | `default_language` | нет | сейчас не действует: язык всегда `ru` | +### Демо-наборы + +Preland хранит наборы ровно как Index, поэтому их можно переносить между проектами без переименования и +перекладывания файлов: + +```text +images/demo/1/{source,result,thumb}.{jpg,webp,avif} +images/demo/1/{source,result,thumb}_mob.avif +... +images/demo/5/... +images/demo-2/1/... +... +images/demo-N/5/... +``` + +`indpic=1` выбирает `images/demo/`, а `indpic=N` (`N > 1`) — `images/demo-N/`. + ## Деплой на произвольный домен **Требования.** PHP 8.0+ (боевой хостинг — 8.2), веб-сервер с `index.php` в роли индексного файла и **HTTPS**: diff --git a/components/examples.php b/components/examples.php index b808a59..68dfa39 100644 --- a/components/examples.php +++ b/components/examples.php @@ -1,7 +1,8 @@ diff --git a/docs/superpowers/plans/2026-10-17-stage-picker-utm-demo-structure.md b/docs/superpowers/plans/2026-10-17-stage-picker-utm-demo-structure.md new file mode 100644 index 0000000..d3fbb71 --- /dev/null +++ b/docs/superpowers/plans/2026-10-17-stage-picker-utm-demo-structure.md @@ -0,0 +1,53 @@ +# Preland stage: picker, UTM и структура примеров — план реализации + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** На `zoomstage.online` синхронизировать список файлов с Index, гарантировать полный resolved-набор UTM при claim-переходе и привести размещение demo-ассетов к Index. + +**Architecture:** Preland остаётся единственным источником resolved UTM: `index.php` нормализует собственные семь меток, сохраняет внешние параметры и передаёт их в claim. Форма имеет полноценный синхронный `accept`, а API policy только подтверждает/актуализирует его. Демонстрационные файлы получают тот же layout, что и Index: `images/demo//` и `images/demo-N//`. + +**Tech Stack:** PHP 8.2, inline JavaScript, Git, Gitea Actions staging deployment, Docker PHP checks. + +**Spec:** пользовательское поручение от 2026-10-17 в текущем чате. + +## Global Constraints + +- Публиковать только ветку `staging`; `main` и production-домены не менять. +- В переходе сохранять семь параметров `indpic`, `theme`, `degr`, `registration`, `bchoice`, `chb`, `tp` и все внешние scalar query-параметры, кроме `key`. +- Поддерживаемые picker-форматы определяются тем же API contract, что и Index; drop остаётся server-side content gate. +- Ассеты не конвертировать и не переименовывать; переместить без потери файлов. + +## Review Focus + +- Первый клик по picker до завершения `fetch(upload-policy)` всё равно предлагает PDF и raster-набор. +- Type-in без UTM передаёт все семь default-значений в адрес claim. +- `rnd` разрешается до редиректа и не утекает как `rnd`. +- Внешняя `utm_source` и произвольный `subid` сохраняются в claim URL. +- `indpic=2` читает ассеты из `images/demo-2/`, а `indpic=1` — из `images/demo/`. + +### Task 1: Контракт picker и UTM + +**Files:** +- Modify: `components/form.php`, `tests/check-upload-admission.php` +- Create: `tests/check-utm-redirect-contract.php` + +- [ ] Написать проверки синхронного PDF/raster `accept` и полного type-in resolved URL; увидеть RED. +- [ ] Добавить PDF в fallback `accept` и при необходимости явный единый builder query; увидеть GREEN. + +### Task 2: Структура demo-ассетов + +**Files:** +- Move: `images/demo/1/*` → `images/demo/*`, `images/demo/2/*` → `images/demo-2/*` +- Modify: `template.php`, `components/examples.php` +- Create: `tests/check-demo-layout.php` + +- [ ] Написать проверку Index-совместимого layout и путей рендеринга; увидеть RED. +- [ ] Переместить файлы и заменить построение путей; увидеть GREEN. + +### Task 3: Документация, stage-release и регрессия + +**Files:** +- Modify: `README.md`, `../known-base/Проекты/Преленд — anton-preland.md`, `../known-base/Инфраструктура/Версионируемый конфиг ZoomPixel Preland.md` + +- [ ] Зафиксировать resolved UTM contract и asset layout. +- [ ] Прогнать все Preland PHP checks в Docker, push только `staging`, проверить опубликованный stage HTML и assets. diff --git a/images/demo/1/1/result.avif b/images/demo-2/1/result.avif similarity index 100% rename from images/demo/1/1/result.avif rename to images/demo-2/1/result.avif diff --git a/images/demo/1/1/result.jpg b/images/demo-2/1/result.jpg similarity index 100% rename from images/demo/1/1/result.jpg rename to images/demo-2/1/result.jpg diff --git a/images/demo/1/1/result.webp b/images/demo-2/1/result.webp similarity index 100% rename from images/demo/1/1/result.webp rename to images/demo-2/1/result.webp diff --git a/images/demo/2/1/result_mob.avif b/images/demo-2/1/result_mob.avif similarity index 100% rename from images/demo/2/1/result_mob.avif rename to images/demo-2/1/result_mob.avif diff --git a/images/demo/1/1/source.avif b/images/demo-2/1/source.avif similarity index 100% rename from images/demo/1/1/source.avif rename to images/demo-2/1/source.avif diff --git a/images/demo/1/1/source.jpg b/images/demo-2/1/source.jpg similarity index 100% rename from images/demo/1/1/source.jpg rename to images/demo-2/1/source.jpg diff --git a/images/demo/1/1/source.webp b/images/demo-2/1/source.webp similarity index 100% rename from images/demo/1/1/source.webp rename to images/demo-2/1/source.webp diff --git a/images/demo/2/1/source_mob.avif b/images/demo-2/1/source_mob.avif similarity index 100% rename from images/demo/2/1/source_mob.avif rename to images/demo-2/1/source_mob.avif diff --git a/images/demo/1/1/thumb.avif b/images/demo-2/1/thumb.avif similarity index 100% rename from images/demo/1/1/thumb.avif rename to images/demo-2/1/thumb.avif diff --git a/images/demo/1/1/thumb.jpg b/images/demo-2/1/thumb.jpg similarity index 100% rename from images/demo/1/1/thumb.jpg rename to images/demo-2/1/thumb.jpg diff --git a/images/demo/1/1/thumb.webp b/images/demo-2/1/thumb.webp similarity index 100% rename from images/demo/1/1/thumb.webp rename to images/demo-2/1/thumb.webp diff --git a/images/demo/2/1/thumb_mob.avif b/images/demo-2/1/thumb_mob.avif similarity index 100% rename from images/demo/2/1/thumb_mob.avif rename to images/demo-2/1/thumb_mob.avif diff --git a/images/demo/1/4/result.avif b/images/demo-2/2/result.avif similarity index 100% rename from images/demo/1/4/result.avif rename to images/demo-2/2/result.avif diff --git a/images/demo/1/4/result.jpg b/images/demo-2/2/result.jpg similarity index 100% rename from images/demo/1/4/result.jpg rename to images/demo-2/2/result.jpg diff --git a/images/demo/1/4/result.webp b/images/demo-2/2/result.webp similarity index 100% rename from images/demo/1/4/result.webp rename to images/demo-2/2/result.webp diff --git a/images/demo/1/4/result_mob.avif b/images/demo-2/2/result_mob.avif similarity index 100% rename from images/demo/1/4/result_mob.avif rename to images/demo-2/2/result_mob.avif diff --git a/images/demo/1/4/source.avif b/images/demo-2/2/source.avif similarity index 100% rename from images/demo/1/4/source.avif rename to images/demo-2/2/source.avif diff --git a/images/demo/1/4/source.jpg b/images/demo-2/2/source.jpg similarity index 100% rename from images/demo/1/4/source.jpg rename to images/demo-2/2/source.jpg diff --git a/images/demo/1/4/source.webp b/images/demo-2/2/source.webp similarity index 100% rename from images/demo/1/4/source.webp rename to images/demo-2/2/source.webp diff --git a/images/demo/1/4/source_mob.avif b/images/demo-2/2/source_mob.avif similarity index 100% rename from images/demo/1/4/source_mob.avif rename to images/demo-2/2/source_mob.avif diff --git a/images/demo/1/4/thumb.avif b/images/demo-2/2/thumb.avif similarity index 100% rename from images/demo/1/4/thumb.avif rename to images/demo-2/2/thumb.avif diff --git a/images/demo/1/4/thumb.jpg b/images/demo-2/2/thumb.jpg similarity index 100% rename from images/demo/1/4/thumb.jpg rename to images/demo-2/2/thumb.jpg diff --git a/images/demo/1/4/thumb.webp b/images/demo-2/2/thumb.webp similarity index 100% rename from images/demo/1/4/thumb.webp rename to images/demo-2/2/thumb.webp diff --git a/images/demo/2/2/thumb_mob.avif b/images/demo-2/2/thumb_mob.avif similarity index 100% rename from images/demo/2/2/thumb_mob.avif rename to images/demo-2/2/thumb_mob.avif diff --git a/images/demo/1/2/result.avif b/images/demo-2/3/result.avif similarity index 100% rename from images/demo/1/2/result.avif rename to images/demo-2/3/result.avif diff --git a/images/demo/1/2/result.jpg b/images/demo-2/3/result.jpg similarity index 100% rename from images/demo/1/2/result.jpg rename to images/demo-2/3/result.jpg diff --git a/images/demo/1/2/result.webp b/images/demo-2/3/result.webp similarity index 100% rename from images/demo/1/2/result.webp rename to images/demo-2/3/result.webp diff --git a/images/demo/1/2/result_mob.avif b/images/demo-2/3/result_mob.avif similarity index 100% rename from images/demo/1/2/result_mob.avif rename to images/demo-2/3/result_mob.avif diff --git a/images/demo/1/2/source.avif b/images/demo-2/3/source.avif similarity index 100% rename from images/demo/1/2/source.avif rename to images/demo-2/3/source.avif diff --git a/images/demo/1/2/source.jpg b/images/demo-2/3/source.jpg similarity index 100% rename from images/demo/1/2/source.jpg rename to images/demo-2/3/source.jpg diff --git a/images/demo/1/2/source.webp b/images/demo-2/3/source.webp similarity index 100% rename from images/demo/1/2/source.webp rename to images/demo-2/3/source.webp diff --git a/images/demo/1/2/source_mob.avif b/images/demo-2/3/source_mob.avif similarity index 100% rename from images/demo/1/2/source_mob.avif rename to images/demo-2/3/source_mob.avif diff --git a/images/demo/1/2/thumb.avif b/images/demo-2/3/thumb.avif similarity index 100% rename from images/demo/1/2/thumb.avif rename to images/demo-2/3/thumb.avif diff --git a/images/demo/1/2/thumb.jpg b/images/demo-2/3/thumb.jpg similarity index 100% rename from images/demo/1/2/thumb.jpg rename to images/demo-2/3/thumb.jpg diff --git a/images/demo/1/2/thumb.webp b/images/demo-2/3/thumb.webp similarity index 100% rename from images/demo/1/2/thumb.webp rename to images/demo-2/3/thumb.webp diff --git a/images/demo/2/3/thumb_mob.avif b/images/demo-2/3/thumb_mob.avif similarity index 100% rename from images/demo/2/3/thumb_mob.avif rename to images/demo-2/3/thumb_mob.avif diff --git a/images/demo/1/5/result.avif b/images/demo-2/4/result.avif similarity index 100% rename from images/demo/1/5/result.avif rename to images/demo-2/4/result.avif diff --git a/images/demo/1/5/result.jpg b/images/demo-2/4/result.jpg similarity index 100% rename from images/demo/1/5/result.jpg rename to images/demo-2/4/result.jpg diff --git a/images/demo/1/5/result.webp b/images/demo-2/4/result.webp similarity index 100% rename from images/demo/1/5/result.webp rename to images/demo-2/4/result.webp diff --git a/images/demo/1/5/result_mob.avif b/images/demo-2/4/result_mob.avif similarity index 100% rename from images/demo/1/5/result_mob.avif rename to images/demo-2/4/result_mob.avif diff --git a/images/demo/1/5/source.avif b/images/demo-2/4/source.avif similarity index 100% rename from images/demo/1/5/source.avif rename to images/demo-2/4/source.avif diff --git a/images/demo/1/5/source.jpg b/images/demo-2/4/source.jpg similarity index 100% rename from images/demo/1/5/source.jpg rename to images/demo-2/4/source.jpg diff --git a/images/demo/1/5/source.webp b/images/demo-2/4/source.webp similarity index 100% rename from images/demo/1/5/source.webp rename to images/demo-2/4/source.webp diff --git a/images/demo/1/5/source_mob.avif b/images/demo-2/4/source_mob.avif similarity index 100% rename from images/demo/1/5/source_mob.avif rename to images/demo-2/4/source_mob.avif diff --git a/images/demo/1/5/thumb.avif b/images/demo-2/4/thumb.avif similarity index 100% rename from images/demo/1/5/thumb.avif rename to images/demo-2/4/thumb.avif diff --git a/images/demo/1/5/thumb.jpg b/images/demo-2/4/thumb.jpg similarity index 100% rename from images/demo/1/5/thumb.jpg rename to images/demo-2/4/thumb.jpg diff --git a/images/demo/1/5/thumb.webp b/images/demo-2/4/thumb.webp similarity index 100% rename from images/demo/1/5/thumb.webp rename to images/demo-2/4/thumb.webp diff --git a/images/demo/2/4/thumb_mob.avif b/images/demo-2/4/thumb_mob.avif similarity index 100% rename from images/demo/2/4/thumb_mob.avif rename to images/demo-2/4/thumb_mob.avif diff --git a/images/demo/1/3/result.avif b/images/demo-2/5/result.avif similarity index 100% rename from images/demo/1/3/result.avif rename to images/demo-2/5/result.avif diff --git a/images/demo/1/3/result.jpg b/images/demo-2/5/result.jpg similarity index 100% rename from images/demo/1/3/result.jpg rename to images/demo-2/5/result.jpg diff --git a/images/demo/1/3/result.webp b/images/demo-2/5/result.webp similarity index 100% rename from images/demo/1/3/result.webp rename to images/demo-2/5/result.webp diff --git a/images/demo/1/3/result_mob.avif b/images/demo-2/5/result_mob.avif similarity index 100% rename from images/demo/1/3/result_mob.avif rename to images/demo-2/5/result_mob.avif diff --git a/images/demo/1/3/source.avif b/images/demo-2/5/source.avif similarity index 100% rename from images/demo/1/3/source.avif rename to images/demo-2/5/source.avif diff --git a/images/demo/1/3/source.jpg b/images/demo-2/5/source.jpg similarity index 100% rename from images/demo/1/3/source.jpg rename to images/demo-2/5/source.jpg diff --git a/images/demo/1/3/source.webp b/images/demo-2/5/source.webp similarity index 100% rename from images/demo/1/3/source.webp rename to images/demo-2/5/source.webp diff --git a/images/demo/1/3/source_mob.avif b/images/demo-2/5/source_mob.avif similarity index 100% rename from images/demo/1/3/source_mob.avif rename to images/demo-2/5/source_mob.avif diff --git a/images/demo/1/3/thumb.avif b/images/demo-2/5/thumb.avif similarity index 100% rename from images/demo/1/3/thumb.avif rename to images/demo-2/5/thumb.avif diff --git a/images/demo/1/3/thumb.jpg b/images/demo-2/5/thumb.jpg similarity index 100% rename from images/demo/1/3/thumb.jpg rename to images/demo-2/5/thumb.jpg diff --git a/images/demo/1/3/thumb.webp b/images/demo-2/5/thumb.webp similarity index 100% rename from images/demo/1/3/thumb.webp rename to images/demo-2/5/thumb.webp diff --git a/images/demo/2/5/thumb_mob.avif b/images/demo-2/5/thumb_mob.avif similarity index 100% rename from images/demo/2/5/thumb_mob.avif rename to images/demo-2/5/thumb_mob.avif diff --git a/images/demo/2/1/result.avif b/images/demo/1/result.avif similarity index 100% rename from images/demo/2/1/result.avif rename to images/demo/1/result.avif diff --git a/images/demo/2/1/result.jpg b/images/demo/1/result.jpg similarity index 100% rename from images/demo/2/1/result.jpg rename to images/demo/1/result.jpg diff --git a/images/demo/2/1/result.webp b/images/demo/1/result.webp similarity index 100% rename from images/demo/2/1/result.webp rename to images/demo/1/result.webp diff --git a/images/demo/1/1/result_mob.avif b/images/demo/1/result_mob.avif similarity index 100% rename from images/demo/1/1/result_mob.avif rename to images/demo/1/result_mob.avif diff --git a/images/demo/2/1/source.avif b/images/demo/1/source.avif similarity index 100% rename from images/demo/2/1/source.avif rename to images/demo/1/source.avif diff --git a/images/demo/2/1/source.jpg b/images/demo/1/source.jpg similarity index 100% rename from images/demo/2/1/source.jpg rename to images/demo/1/source.jpg diff --git a/images/demo/2/1/source.webp b/images/demo/1/source.webp similarity index 100% rename from images/demo/2/1/source.webp rename to images/demo/1/source.webp diff --git a/images/demo/1/1/source_mob.avif b/images/demo/1/source_mob.avif similarity index 100% rename from images/demo/1/1/source_mob.avif rename to images/demo/1/source_mob.avif diff --git a/images/demo/2/1/thumb.avif b/images/demo/1/thumb.avif similarity index 100% rename from images/demo/2/1/thumb.avif rename to images/demo/1/thumb.avif diff --git a/images/demo/2/1/thumb.jpg b/images/demo/1/thumb.jpg similarity index 100% rename from images/demo/2/1/thumb.jpg rename to images/demo/1/thumb.jpg diff --git a/images/demo/2/1/thumb.webp b/images/demo/1/thumb.webp similarity index 100% rename from images/demo/2/1/thumb.webp rename to images/demo/1/thumb.webp diff --git a/images/demo/1/1/thumb_mob.avif b/images/demo/1/thumb_mob.avif similarity index 100% rename from images/demo/1/1/thumb_mob.avif rename to images/demo/1/thumb_mob.avif diff --git a/images/demo/2/3/result.avif b/images/demo/2/result.avif similarity index 100% rename from images/demo/2/3/result.avif rename to images/demo/2/result.avif diff --git a/images/demo/2/3/result.jpg b/images/demo/2/result.jpg similarity index 100% rename from images/demo/2/3/result.jpg rename to images/demo/2/result.jpg diff --git a/images/demo/2/3/result.webp b/images/demo/2/result.webp similarity index 100% rename from images/demo/2/3/result.webp rename to images/demo/2/result.webp diff --git a/images/demo/2/3/result_mob.avif b/images/demo/2/result_mob.avif similarity index 100% rename from images/demo/2/3/result_mob.avif rename to images/demo/2/result_mob.avif diff --git a/images/demo/2/3/source.avif b/images/demo/2/source.avif similarity index 100% rename from images/demo/2/3/source.avif rename to images/demo/2/source.avif diff --git a/images/demo/2/3/source.jpg b/images/demo/2/source.jpg similarity index 100% rename from images/demo/2/3/source.jpg rename to images/demo/2/source.jpg diff --git a/images/demo/2/3/source.webp b/images/demo/2/source.webp similarity index 100% rename from images/demo/2/3/source.webp rename to images/demo/2/source.webp diff --git a/images/demo/2/3/source_mob.avif b/images/demo/2/source_mob.avif similarity index 100% rename from images/demo/2/3/source_mob.avif rename to images/demo/2/source_mob.avif diff --git a/images/demo/2/3/thumb.avif b/images/demo/2/thumb.avif similarity index 100% rename from images/demo/2/3/thumb.avif rename to images/demo/2/thumb.avif diff --git a/images/demo/2/3/thumb.jpg b/images/demo/2/thumb.jpg similarity index 100% rename from images/demo/2/3/thumb.jpg rename to images/demo/2/thumb.jpg diff --git a/images/demo/2/3/thumb.webp b/images/demo/2/thumb.webp similarity index 100% rename from images/demo/2/3/thumb.webp rename to images/demo/2/thumb.webp diff --git a/images/demo/1/2/thumb_mob.avif b/images/demo/2/thumb_mob.avif similarity index 100% rename from images/demo/1/2/thumb_mob.avif rename to images/demo/2/thumb_mob.avif diff --git a/images/demo/2/5/result.avif b/images/demo/3/result.avif similarity index 100% rename from images/demo/2/5/result.avif rename to images/demo/3/result.avif diff --git a/images/demo/2/5/result.jpg b/images/demo/3/result.jpg similarity index 100% rename from images/demo/2/5/result.jpg rename to images/demo/3/result.jpg diff --git a/images/demo/2/5/result.webp b/images/demo/3/result.webp similarity index 100% rename from images/demo/2/5/result.webp rename to images/demo/3/result.webp diff --git a/images/demo/2/5/result_mob.avif b/images/demo/3/result_mob.avif similarity index 100% rename from images/demo/2/5/result_mob.avif rename to images/demo/3/result_mob.avif diff --git a/images/demo/2/5/source.avif b/images/demo/3/source.avif similarity index 100% rename from images/demo/2/5/source.avif rename to images/demo/3/source.avif diff --git a/images/demo/2/5/source.jpg b/images/demo/3/source.jpg similarity index 100% rename from images/demo/2/5/source.jpg rename to images/demo/3/source.jpg diff --git a/images/demo/2/5/source.webp b/images/demo/3/source.webp similarity index 100% rename from images/demo/2/5/source.webp rename to images/demo/3/source.webp diff --git a/images/demo/2/5/source_mob.avif b/images/demo/3/source_mob.avif similarity index 100% rename from images/demo/2/5/source_mob.avif rename to images/demo/3/source_mob.avif diff --git a/images/demo/2/5/thumb.avif b/images/demo/3/thumb.avif similarity index 100% rename from images/demo/2/5/thumb.avif rename to images/demo/3/thumb.avif diff --git a/images/demo/2/5/thumb.jpg b/images/demo/3/thumb.jpg similarity index 100% rename from images/demo/2/5/thumb.jpg rename to images/demo/3/thumb.jpg diff --git a/images/demo/2/5/thumb.webp b/images/demo/3/thumb.webp similarity index 100% rename from images/demo/2/5/thumb.webp rename to images/demo/3/thumb.webp diff --git a/images/demo/1/3/thumb_mob.avif b/images/demo/3/thumb_mob.avif similarity index 100% rename from images/demo/1/3/thumb_mob.avif rename to images/demo/3/thumb_mob.avif diff --git a/images/demo/2/2/result.avif b/images/demo/4/result.avif similarity index 100% rename from images/demo/2/2/result.avif rename to images/demo/4/result.avif diff --git a/images/demo/2/2/result.jpg b/images/demo/4/result.jpg similarity index 100% rename from images/demo/2/2/result.jpg rename to images/demo/4/result.jpg diff --git a/images/demo/2/2/result.webp b/images/demo/4/result.webp similarity index 100% rename from images/demo/2/2/result.webp rename to images/demo/4/result.webp diff --git a/images/demo/2/2/result_mob.avif b/images/demo/4/result_mob.avif similarity index 100% rename from images/demo/2/2/result_mob.avif rename to images/demo/4/result_mob.avif diff --git a/images/demo/2/2/source.avif b/images/demo/4/source.avif similarity index 100% rename from images/demo/2/2/source.avif rename to images/demo/4/source.avif diff --git a/images/demo/2/2/source.jpg b/images/demo/4/source.jpg similarity index 100% rename from images/demo/2/2/source.jpg rename to images/demo/4/source.jpg diff --git a/images/demo/2/2/source.webp b/images/demo/4/source.webp similarity index 100% rename from images/demo/2/2/source.webp rename to images/demo/4/source.webp diff --git a/images/demo/2/2/source_mob.avif b/images/demo/4/source_mob.avif similarity index 100% rename from images/demo/2/2/source_mob.avif rename to images/demo/4/source_mob.avif diff --git a/images/demo/2/2/thumb.avif b/images/demo/4/thumb.avif similarity index 100% rename from images/demo/2/2/thumb.avif rename to images/demo/4/thumb.avif diff --git a/images/demo/2/2/thumb.jpg b/images/demo/4/thumb.jpg similarity index 100% rename from images/demo/2/2/thumb.jpg rename to images/demo/4/thumb.jpg diff --git a/images/demo/2/2/thumb.webp b/images/demo/4/thumb.webp similarity index 100% rename from images/demo/2/2/thumb.webp rename to images/demo/4/thumb.webp diff --git a/images/demo/1/4/thumb_mob.avif b/images/demo/4/thumb_mob.avif similarity index 100% rename from images/demo/1/4/thumb_mob.avif rename to images/demo/4/thumb_mob.avif diff --git a/images/demo/2/4/result.avif b/images/demo/5/result.avif similarity index 100% rename from images/demo/2/4/result.avif rename to images/demo/5/result.avif diff --git a/images/demo/2/4/result.jpg b/images/demo/5/result.jpg similarity index 100% rename from images/demo/2/4/result.jpg rename to images/demo/5/result.jpg diff --git a/images/demo/2/4/result.webp b/images/demo/5/result.webp similarity index 100% rename from images/demo/2/4/result.webp rename to images/demo/5/result.webp diff --git a/images/demo/2/4/result_mob.avif b/images/demo/5/result_mob.avif similarity index 100% rename from images/demo/2/4/result_mob.avif rename to images/demo/5/result_mob.avif diff --git a/images/demo/2/4/source.avif b/images/demo/5/source.avif similarity index 100% rename from images/demo/2/4/source.avif rename to images/demo/5/source.avif diff --git a/images/demo/2/4/source.jpg b/images/demo/5/source.jpg similarity index 100% rename from images/demo/2/4/source.jpg rename to images/demo/5/source.jpg diff --git a/images/demo/2/4/source.webp b/images/demo/5/source.webp similarity index 100% rename from images/demo/2/4/source.webp rename to images/demo/5/source.webp diff --git a/images/demo/2/4/source_mob.avif b/images/demo/5/source_mob.avif similarity index 100% rename from images/demo/2/4/source_mob.avif rename to images/demo/5/source_mob.avif diff --git a/images/demo/2/4/thumb.avif b/images/demo/5/thumb.avif similarity index 100% rename from images/demo/2/4/thumb.avif rename to images/demo/5/thumb.avif diff --git a/images/demo/2/4/thumb.jpg b/images/demo/5/thumb.jpg similarity index 100% rename from images/demo/2/4/thumb.jpg rename to images/demo/5/thumb.jpg diff --git a/images/demo/2/4/thumb.webp b/images/demo/5/thumb.webp similarity index 100% rename from images/demo/2/4/thumb.webp rename to images/demo/5/thumb.webp diff --git a/images/demo/1/5/thumb_mob.avif b/images/demo/5/thumb_mob.avif similarity index 100% rename from images/demo/1/5/thumb_mob.avif rename to images/demo/5/thumb_mob.avif diff --git a/template.php b/template.php index c4f35f2..b2e79dd 100644 --- a/template.php +++ b/template.php @@ -20,7 +20,8 @@ @@ -164,4 +165,4 @@ document.addEventListener('DOMContentLoaded', () => { }); - \ No newline at end of file + diff --git a/tests/check-demo-layout.php b/tests/check-demo-layout.php new file mode 100644 index 0000000..5e2d6a9 --- /dev/null +++ b/tests/check-demo-layout.php @@ -0,0 +1,50 @@ +/ и images/demo-N/<пример>/. + */ + +declare(strict_types=1); + +$root = dirname(__DIR__); +$failures = []; + +foreach ([ + 'images/demo/1/source.avif', + 'images/demo/1/result.avif', + 'images/demo/1/thumb.avif', + 'images/demo/5/source_mob.avif', + 'images/demo-2/1/source.avif', + 'images/demo-2/5/result_mob.avif', +] as $relativePath) { + if (!is_file($root . '/' . $relativePath)) { + $failures[] = "Отсутствует Index-совместимый asset {$relativePath}."; + } +} + +foreach ([ + 'images/demo/1/1', + 'images/demo/2/1', +] as $legacyPath) { + if (is_dir($root . '/' . $legacyPath)) { + $failures[] = "Не должна оставаться промежуточная группировка {$legacyPath}."; + } +} + +$template = (string) file_get_contents($root . '/template.php'); +$examples = (string) file_get_contents($root . '/components/examples.php'); +$pathBuilder = "\$demoDirName = 'demo' . (\$indpic === '1' ? '' : '-' . \$indpic);"; +if (!str_contains($template, $pathBuilder) + || !str_contains($examples, $pathBuilder) + || !str_contains($template, '"/images/{$demoDirName}/1"') + || !str_contains($examples, '"/images/{$demoDirName}"')) { + $failures[] = 'Template и examples должны строить путь из Index-совместимого имени demo/demo-N.'; +} + +if ($failures !== []) { + fwrite(STDERR, "FAILED\n- " . implode("\n- ", $failures) . "\n"); + exit(1); +} + +fwrite(STDOUT, "OK: Preland demo layout matches Index\n"); diff --git a/tests/check-upload-admission.php b/tests/check-upload-admission.php index 19c14cf..23cd9b8 100644 --- a/tests/check-upload-admission.php +++ b/tests/check-upload-admission.php @@ -6,7 +6,7 @@ $form = file_get_contents(dirname(__DIR__) . '/components/form.php'); $failures = []; foreach ([ - "\$basePickerAccept = 'image/jpeg,image/png,image/gif,image/webp,image/bmp,image/avif,image/heic,image/heif,image/jxl,image/tiff," => 'Preland должен ограничивать штатный picker поддерживаемыми raster-контейнерами.', + "\$basePickerAccept = 'image/jpeg,image/png,image/gif,image/webp,image/bmp,image/avif,image/heic,image/heif,image/jxl,image/tiff,application/pdf,.pdf," => 'Preland должен сразу предлагать raster-контейнеры и PDF, не ожидая upload-policy.', 'accept=" 'Preland должен передать допустимые типы в штатный file input.', 'picker_accept' => 'Preland должен получать актуальный accept из API policy для PDF.', 'max_file_size_bytes' => 'Preland должен получить лимит размера для preflight без передачи гигабайтного файла.', diff --git a/tests/check-utm-redirect-contract.php b/tests/check-utm-redirect-contract.php new file mode 100644 index 0000000..37aa2a7 --- /dev/null +++ b/tests/check-utm-redirect-contract.php @@ -0,0 +1,58 @@ + 'stage-ad', + 'subid' => 'click-42', + 'key' => 'must-not-replace-client-key', +]; +$_SERVER['HTTP_HOST'] = 'zoomstage.online'; + +ob_start(); +require dirname(__DIR__) . '/index.php'; +$html = (string) ob_get_clean(); + +if (!preg_match('/const redirectUrlUtm = (.+);/', $html, $match)) { + fwrite(STDERR, "FAIL: Preland не отдал redirectUrlUtm.\n"); + exit(1); +} + +$query = json_decode($match[1], true); +if (!is_string($query)) { + fwrite(STDERR, "FAIL: redirectUrlUtm должен быть JSON-строкой.\n"); + exit(1); +} + +parse_str($query, $params); +$expected = [ + 'indpic' => '1', + 'theme' => 'dark', + 'degr' => '1', + 'registration' => 'after', + 'bchoice' => 'a', + 'chb' => 'b', + 'tp' => '1', + 'utm_source' => 'stage-ad', + 'subid' => 'click-42', +]; + +ksort($params); +ksort($expected); + +if ($params !== $expected) { + fwrite(STDERR, "FAIL: claim URL обязан нести все resolved UTM и внешние параметры.\n"); + exit(1); +} + +if (isset($params['key'])) { + fwrite(STDERR, "FAIL: внешний key не должен подменять одноразовый ключ загрузки.\n"); + exit(1); +} + +fwrite(STDOUT, "OK: preland redirects resolved UTM and external parameters\n");