fix(utm): заменить публичную chob на banch (#327)
deploy-preland / deploy zoomstage.online (push) Successful in 42s
deploy-preland / deploy zoomstage.online (push) Successful in 42s
This commit is contained in:
1 parent
08febba368
commit
965eb2e6a5
12 files changed
+56
-31
No files matched your search
@@ -106,7 +106,8 @@ jobs:
|
|||||||
SSHPASS="$SFTP_PASS" sshpass -e sftp -oBatchMode=no -oStrictHostKeyChecking=yes -oUserKnownHostsFile="$known_hosts" -b "$batch" "$SFTP_USER@$SFTP_HOST"
|
SSHPASS="$SFTP_PASS" sshpass -e sftp -oBatchMode=no -oStrictHostKeyChecking=yes -oUserKnownHostsFile="$known_hosts" -b "$batch" "$SFTP_USER@$SFTP_HOST"
|
||||||
echo "залито файлов: $(wc -l < "$files")"
|
echo "залито файлов: $(wc -l < "$files")"
|
||||||
|
|
||||||
# api#326: сначала совместимый PHP-код, затем точечная миграция UTM.
|
# api#326/#327: сначала совместимый PHP-код, затем точечная миграция UTM.
|
||||||
|
# chob→banch меняет только токен имени; веса/default владельца сохраняются.
|
||||||
# Остальные поля владельца и их JSON-типы сохраняются.
|
# Остальные поля владельца и их JSON-типы сохраняются.
|
||||||
- name: Migrate numeric UTM in preserved host config
|
- name: Migrate numeric UTM in preserved host config
|
||||||
env:
|
env:
|
||||||
|
|||||||
@@ -6,13 +6,13 @@
|
|||||||
маленькая заглушка `Yii::t` в `i18n.php`.
|
маленькая заглушка `Yii::t` в `i18n.php`.
|
||||||
|
|
||||||
Активные production: `https://zoompixel.ru`, `https://zoompixel.online`; текущая цель выката — только `https://zoomstage.online`. Пример ссылки входа:
|
Активные production: `https://zoompixel.ru`, `https://zoompixel.online`; текущая цель выката — только `https://zoomstage.online`. Пример ссылки входа:
|
||||||
`https://zoomstage.online/?indpic=e9st1mm12b&theme=e9st1mm12b°r=e9st2mm12b®=e9st2mm12b&chob=e9st2mm12b&tp=e9st1mm12b&utm_source=test&subid=CLICK1`.
|
`https://zoomstage.online/?indpic=e9st1mm12b&theme=e9st1mm12b°r=e9st2mm12b®=e9st2mm12b&banch=e9st2mm12b&tp=e9st1mm12b&utm_source=test&subid=CLICK1`.
|
||||||
|
|
||||||
## Как работает
|
## Как работает
|
||||||
|
|
||||||
### Метки (api#326)
|
### Метки (api#326)
|
||||||
|
|
||||||
Из публичного значения учитывается только пятый символ: цифра1–9 выбирает вариант,0 на Preland выбирает его по прежним `weights`. Отсутствующий, короткий, массивный и невалидный ввод получает `default`. Открытые `1`, `rnd`, `dark`, старые имена `registration/bchoice/chb` больше не выбирают варианты. Точная длина входной строки не ограничена; Unicode-шум не изменяет позицию управляющего символа.
|
Из публичного значения учитывается только пятый символ: цифра1–9 выбирает вариант,0 на Preland выбирает его по прежним `weights`. Отсутствующий, короткий, массивный и невалидный ввод получает `default`. Открытые `1`, `rnd`, `dark`, старые имена `registration/bchoice/chob/chb` больше не выбирают варианты. Точная длина входной строки не ограничена; Unicode-шум не изменяет позицию управляющего символа.
|
||||||
|
|
||||||
| Метка | Числовые значения |
|
| Метка | Числовые значения |
|
||||||
|---|---|
|
|---|---|
|
||||||
@@ -20,13 +20,15 @@
|
|||||||
| `theme` |1 dark,2 light |
|
| `theme` |1 dark,2 light |
|
||||||
| `degr` | номер настроенной группы1–9 |
|
| `degr` | номер настроенной группы1–9 |
|
||||||
| `reg` |1 before (PIN до оплаты),2 after |
|
| `reg` |1 before (PIN до оплаты),2 after |
|
||||||
| `chob` |1 без попапа банка,2 с попапом |
|
| `banch` |1 без попапа банка,2 с попапом |
|
||||||
| `tp` | номер настроенной тарифной группы1–9 |
|
| `tp` | номер настроенной тарифной группы1–9 |
|
||||||
| `cons` | номер группы согласий1–9; существование проверяет API |
|
| `cons` | номер группы согласий1–9; существование проверяет API |
|
||||||
| `focus` |1 face,2 center |
|
| `focus` |1 face,2 center |
|
||||||
|
|
||||||
`config.json` хранит публичные числовые `weights/default`. Внутри PHP сохраняются прежние semantic enum. Runtime-конфиг мигрируется `bin/ensure-utm-config.php`: только согласованные UTM, с сохранением весов, default и JSON-типов соседних полей. Скрипт идемпотентен; старые focus/cons patch-скрипты больше не вызываются доставкой.
|
`config.json` хранит публичные числовые `weights/default`. Внутри PHP сохраняются прежние semantic enum. Runtime-конфиг мигрируется `bin/ensure-utm-config.php`: только согласованные UTM, с сохранением весов, default и JSON-типов соседних полей. Скрипт идемпотентен; старые focus/cons patch-скрипты больше не вызываются доставкой.
|
||||||
|
|
||||||
|
В api#327 публичное `chob` заменено на `banch`; внутренний `bchoice` сохраняется. Мигратор переименовывает текущий host-ключ токенно, сохраняя все остальные байты, включая веса и default. Одновременные `banch/chob/bchoice` блокируют patch без записи. До миграции код читает прежний host-config; сохранённые recovery-записи с `utmDigits.chob` или `redirectUtm` продолжают выбранный вариант и выпускают уже `banch`.
|
||||||
|
|
||||||
Перед каждым переходом Preland создаёт для всех восьми меток новый шум из10 ASCII букв/цифр, с уже выбранной цифрой в пятой позиции. Retry и recovery меняют только шум; вариант повторно не выбирается. Выбранные цифры сохраняются в upload record. Старые записи IndexedDB адаптируются только на пути recovery, без публичных alias старого URL.
|
Перед каждым переходом Preland создаёт для всех восьми меток новый шум из10 ASCII букв/цифр, с уже выбранной цифрой в пятой позиции. Retry и recovery меняют только шум; вариант повторно не выбирается. Выбранные цифры сохраняются в upload record. Старые записи IndexedDB адаптируются только на пути recovery, без публичных alias старого URL.
|
||||||
|
|
||||||
Чужие параметры (`utm_source`, `keyword`, `subid` и любые другие) сохраняют значения после обычного URL-кодирования. Технические `key/id/stage/shown_theme` не маскируются; внешний `key` не может подменить одноразовый ключ загрузки. `shown_theme` берётся из фактически показанной темы непосредственно при переходе.
|
Чужие параметры (`utm_source`, `keyword`, `subid` и любые другие) сохраняют значения после обычного URL-кодирования. Технические `key/id/stage/shown_theme` не маскируются; внешний `key` не может подменить одноразовый ключ загрузки. `shown_theme` берётся из фактически показанной темы непосредственно при переходе.
|
||||||
|
|||||||
@@ -62,12 +62,14 @@ try {
|
|||||||
$replace=static function(array $node,string $value) use (&$replacements): void {
|
$replace=static function(array $node,string $value) use (&$replacements): void {
|
||||||
$replacements[]=['start'=>$node['start'],'end'=>$node['end'],'text'=>json_encode($value,JSON_THROW_ON_ERROR)];
|
$replacements[]=['start'=>$node['start'],'end'=>$node['end'],'text'=>json_encode($value,JSON_THROW_ON_ERROR)];
|
||||||
};
|
};
|
||||||
foreach (['theme'=>['dark'=>'1','light'=>'2'],'reg'=>['before'=>'1','after'=>'2'],'chob'=>['b'=>'1','a'=>'2'],'focus'=>['face'=>'1','center'=>'2']] as $key=>$map) {
|
foreach (['theme'=>['dark'=>'1','light'=>'2'],'reg'=>['before'=>'1','after'=>'2'],'banch'=>['b'=>'1','a'=>'2'],'focus'=>['face'=>'1','center'=>'2']] as $key=>$map) {
|
||||||
$old=['reg'=>'registration','chob'=>'bchoice'][$key] ?? null;
|
|
||||||
$property=utmConfigProperty($utm,$key);
|
$property=utmConfigProperty($utm,$key);
|
||||||
$legacy=$old===null ? null : utmConfigProperty($utm,$old);
|
$aliases=['reg'=>['registration'],'banch'=>['chob','bchoice']][$key] ?? [];
|
||||||
|
foreach ($aliases as $old) {
|
||||||
|
$legacy=utmConfigProperty($utm,$old);
|
||||||
if ($property && $legacy) throw new RuntimeException('Конфликт старого и нового UTM: '.$key);
|
if ($property && $legacy) throw new RuntimeException('Конфликт старого и нового UTM: '.$key);
|
||||||
if ($legacy) { $replace($legacy['key'],$key); $property=$legacy; }
|
if ($legacy) { $replace($legacy['key'],$key); $property=$legacy; }
|
||||||
|
}
|
||||||
if (!$property) continue;
|
if (!$property) continue;
|
||||||
$setting=$property['value'];
|
$setting=$property['value'];
|
||||||
$weights=utmConfigProperty($setting,'weights')['value'] ?? null;
|
$weights=utmConfigProperty($setting,'weights')['value'] ?? null;
|
||||||
|
|||||||
@@ -4,14 +4,14 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
function prelandUtmEnums(): array
|
function prelandUtmEnums(): array
|
||||||
{
|
{
|
||||||
return ['theme'=>['1'=>'dark','2'=>'light'], 'reg'=>['1'=>'before','2'=>'after'], 'chob'=>['1'=>'b','2'=>'a'], 'focus'=>['1'=>'face','2'=>'center']];
|
return ['theme'=>['1'=>'dark','2'=>'light'], 'reg'=>['1'=>'before','2'=>'after'], 'banch'=>['1'=>'b','2'=>'a'], 'focus'=>['1'=>'face','2'=>'center']];
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Совместимость host-config при доставке кода до точечной миграции. */
|
/** Совместимость host-config при доставке кода до точечной миграции. */
|
||||||
function prelandNumericUtmConfig(array $config): array
|
function prelandNumericUtmConfig(array $config): array
|
||||||
{
|
{
|
||||||
$settings = is_array($config['utm'] ?? null) ? $config['utm'] : [];
|
$settings = is_array($config['utm'] ?? null) ? $config['utm'] : [];
|
||||||
foreach (['registration'=>'reg','bchoice'=>'chob'] as $old=>$new) {
|
foreach (['registration'=>'reg','chob'=>'banch','bchoice'=>'banch'] as $old=>$new) {
|
||||||
if (!isset($settings[$new]) && isset($settings[$old])) $settings[$new] = $settings[$old];
|
if (!isset($settings[$new]) && isset($settings[$old])) $settings[$new] = $settings[$old];
|
||||||
unset($settings[$old]);
|
unset($settings[$old]);
|
||||||
}
|
}
|
||||||
@@ -36,15 +36,15 @@ function prelandNumericUtmConfig(array $config): array
|
|||||||
function prelandResolveUtm(array $query, array $config): array
|
function prelandResolveUtm(array $query, array $config): array
|
||||||
{
|
{
|
||||||
$config=prelandNumericUtmConfig($config);
|
$config=prelandNumericUtmConfig($config);
|
||||||
$defaults=['indpic'=>'1','theme'=>'2','degr'=>'1','reg'=>'2','chob'=>'2','tp'=>'1','cons'=>'2','focus'=>'1'];
|
$defaults=['indpic'=>'1','theme'=>'2','degr'=>'1','reg'=>'2','banch'=>'2','tp'=>'1','cons'=>'2','focus'=>'1'];
|
||||||
$enums=prelandUtmEnums(); $digits=[]; $context=$query;
|
$enums=prelandUtmEnums(); $digits=[]; $context=$query;
|
||||||
foreach (['registration','bchoice','chb'] as $old) unset($context[$old]);
|
foreach (['registration','bchoice','chob','chb'] as $old) unset($context[$old]);
|
||||||
foreach ($defaults as $key=>$fallback) {
|
foreach ($defaults as $key=>$fallback) {
|
||||||
unset($context[$key]);
|
unset($context[$key]);
|
||||||
$settings=$config['utm'][$key] ?? [];
|
$settings=$config['utm'][$key] ?? [];
|
||||||
$valid=static fn($digit): bool => preg_match('/^[1-9]$/D',(string)$digit)===1
|
$valid=static fn($digit): bool => preg_match('/^[1-9]$/D',(string)$digit)===1
|
||||||
&& (!isset($enums[$key]) || isset($enums[$key][$digit]))
|
&& (!isset($enums[$key]) || isset($enums[$key][$digit]))
|
||||||
&& (in_array($key,['cons','theme','reg','chob','focus'],true) || isset($settings['weights'][$digit]));
|
&& (in_array($key,['cons','theme','reg','banch','focus'],true) || isset($settings['weights'][$digit]));
|
||||||
$default=(string)($settings['default'] ?? $fallback);
|
$default=(string)($settings['default'] ?? $fallback);
|
||||||
if (!$valid($default)) $default=$fallback;
|
if (!$valid($default)) $default=$fallback;
|
||||||
$value=$query[$key] ?? null;
|
$value=$query[$key] ?? null;
|
||||||
@@ -81,7 +81,7 @@ function prelandResolveUtm(array $query, array $config): array
|
|||||||
}
|
}
|
||||||
if (!$valid($digit ?? '')) $digit=$default;
|
if (!$valid($digit ?? '')) $digit=$default;
|
||||||
$digits[$key]=$digit;
|
$digits[$key]=$digit;
|
||||||
$internal=['reg'=>'registration','chob'=>'bchoice'][$key] ?? $key;
|
$internal=['reg'=>'registration','banch'=>'bchoice'][$key] ?? $key;
|
||||||
$context[$internal]=$enums[$key][$digit] ?? $digit;
|
$context[$internal]=$enums[$key][$digit] ?? $digit;
|
||||||
}
|
}
|
||||||
return ['digits'=>$digits,'context'=>$context];
|
return ['digits'=>$digits,'context'=>$context];
|
||||||
|
|||||||
@@ -2,18 +2,18 @@
|
|||||||
/** api#326: новый шум при каждой навигации; recovery хранит выбранные цифры. */
|
/** api#326: новый шум при каждой навигации; recovery хранит выбранные цифры. */
|
||||||
return <<<'JS'
|
return <<<'JS'
|
||||||
(function () {
|
(function () {
|
||||||
const keys = ['indpic','theme','degr','reg','chob','tp','cons','focus'];
|
const keys = ['indpic','theme','degr','reg','banch','tp','cons','focus'];
|
||||||
const enums = {theme:{dark:'1',light:'2'},reg:{before:'1',after:'2'},chob:{b:'1',a:'2'},focus:{face:'1',center:'2'}};
|
const enums = {theme:{dark:'1',light:'2'},reg:{before:'1',after:'2'},banch:{b:'1',a:'2'},focus:{face:'1',center:'2'}};
|
||||||
const legacy = {reg:'registration',chob:'bchoice'};
|
const legacy = {reg:'registration',banch:'bchoice'};
|
||||||
const valid = (key, value) => /^[1-9]$/.test(value || '') && (!enums[key] || Object.values(enums[key]).includes(value));
|
const valid = (key, value) => /^[1-9]$/.test(value || '') && (!enums[key] || Object.values(enums[key]).includes(value));
|
||||||
function restore(meta, defaults) {
|
function restore(meta, defaults) {
|
||||||
const query = new URLSearchParams(meta.redirectUtm || '');
|
const query = new URLSearchParams(meta.redirectUtm || '');
|
||||||
const digits = {};
|
const digits = {};
|
||||||
for (const key of keys) {
|
for (const key of keys) {
|
||||||
let value = meta.utmDigits?.[key];
|
let value = meta.utmDigits?.[key] ?? (key === 'banch' ? meta.utmDigits?.chob : undefined);
|
||||||
if (!valid(key, value)) {
|
if (!valid(key, value)) {
|
||||||
// Только сохранённый record: старые enum/открытые цифры не являются raw URL alias.
|
// Только сохранённый record: старые enum/открытые цифры не являются raw URL alias.
|
||||||
const stored = query.get(legacy[key] || key) ?? query.get(key);
|
const stored = query.get(legacy[key] || key) ?? query.get(key) ?? (key === 'banch' ? query.get('chob') : null);
|
||||||
value = enums[key]?.[stored] || stored;
|
value = enums[key]?.[stored] || stored;
|
||||||
if (!valid(key, value)) value = Array.from(stored || '')[4];
|
if (!valid(key, value)) value = Array.from(stored || '')[4];
|
||||||
}
|
}
|
||||||
@@ -32,7 +32,7 @@ return <<<'JS'
|
|||||||
function query(meta, defaults) {
|
function query(meta, defaults) {
|
||||||
const params = new URLSearchParams(meta.redirectUtm || '');
|
const params = new URLSearchParams(meta.redirectUtm || '');
|
||||||
const digits = restore(meta, defaults);
|
const digits = restore(meta, defaults);
|
||||||
for (const key of ['registration','bchoice','chb','key','shown_theme',...keys]) params.delete(key);
|
for (const key of ['registration','bchoice','chob','chb','key','shown_theme',...keys]) params.delete(key);
|
||||||
for (const key of keys) params.set(key, mask(digits[key]));
|
for (const key of keys) params.set(key, mask(digits[key]));
|
||||||
return params.toString();
|
return params.toString();
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -36,7 +36,7 @@
|
|||||||
},
|
},
|
||||||
"default": "2"
|
"default": "2"
|
||||||
},
|
},
|
||||||
"chob": {
|
"banch": {
|
||||||
"weights": {
|
"weights": {
|
||||||
"2": 1,
|
"2": 1,
|
||||||
"1": 0
|
"1": 0
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ Yii::init($config['default_language'] ?? 'ru');
|
|||||||
$config = prelandNumericUtmConfig($config);
|
$config = prelandNumericUtmConfig($config);
|
||||||
$resolvedUtm = prelandResolveUtm($_GET, $config);
|
$resolvedUtm = prelandResolveUtm($_GET, $config);
|
||||||
$UTM = $resolvedUtm['context'];
|
$UTM = $resolvedUtm['context'];
|
||||||
$get_params = array_diff_key($_GET, array_fill_keys(['indpic','theme','degr','reg','chob','tp','cons','focus','registration','bchoice','chb','key'], true));
|
$get_params = array_diff_key($_GET, array_fill_keys(['indpic','theme','degr','reg','banch','tp','cons','focus','registration','bchoice','chob','chb','key'], true));
|
||||||
// Числа — внутренний snapshot для recovery. Публичная оболочка создаётся при навигации.
|
// Числа — внутренний snapshot для recovery. Публичная оболочка создаётся при навигации.
|
||||||
$redirect_digits = $resolvedUtm['digits'];
|
$redirect_digits = $resolvedUtm['digits'];
|
||||||
$themeInitial = prelandThemeResolve(($UTM['theme'] ?? 'light') === 'dark' ? 'dark' : 'light');
|
$themeInitial = prelandThemeResolve(($UTM['theme'] ?? 'light') === 'dark' ? 'dark' : 'light');
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ try{
|
|||||||
$actual=json_decode(file_get_contents($file)); $before=json_decode($raw);
|
$actual=json_decode(file_get_contents($file)); $before=json_decode($raw);
|
||||||
foreach(['host','empty','list','map'] as $key)if(serialize($actual->$key)!==serialize($before->$key))throw new RuntimeException('Изменён соседний JSON тип');
|
foreach(['host','empty','list','map'] as $key)if(serialize($actual->$key)!==serialize($before->$key))throw new RuntimeException('Изменён соседний JSON тип');
|
||||||
if(isset($actual->utm->registration,$actual->utm->bchoice))throw new RuntimeException('Остались старые имена');
|
if(isset($actual->utm->registration,$actual->utm->bchoice))throw new RuntimeException('Остались старые имена');
|
||||||
if(json_encode($actual->utm->reg)!=='{"weights":{"1":3,"2":7},"default":"2"}'||json_encode($actual->utm->chob)!=='{"weights":{"2":8,"1":2},"default":"1"}')throw new RuntimeException('Изменён смысл reg/chob');
|
if(json_encode($actual->utm->reg)!=='{"weights":{"1":3,"2":7},"default":"2"}'||json_encode($actual->utm->banch)!=='{"weights":{"2":8,"1":2},"default":"1"}')throw new RuntimeException('Изменён смысл reg/banch');
|
||||||
if($actual->utm->theme->default!=='2'||$actual->utm->focus->default!=='1'||serialize($actual->utm->custom)!==serialize($before->utm->custom))throw new RuntimeException('Неверная миграция enum');
|
if($actual->utm->theme->default!=='2'||$actual->utm->focus->default!=='1'||serialize($actual->utm->custom)!==serialize($before->utm->custom))throw new RuntimeException('Неверная миграция enum');
|
||||||
$first=file_get_contents($file);exec(PHP_BINARY.' '.escapeshellarg($script).' '.escapeshellarg($file),$out,$code);
|
$first=file_get_contents($file);exec(PHP_BINARY.' '.escapeshellarg($script).' '.escapeshellarg($file),$out,$code);
|
||||||
if($code!==0||file_get_contents($file)!==$first)throw new RuntimeException('Patch не идемпотентен');
|
if($code!==0||file_get_contents($file)!==$first)throw new RuntimeException('Patch не идемпотентен');
|
||||||
@@ -31,5 +31,16 @@ try{
|
|||||||
if($patched!==$expectedTokens)throw new RuntimeException('Изменены байты вне согласованных source ranges');
|
if($patched!==$expectedTokens)throw new RuntimeException('Изменены байты вне согласованных source ranges');
|
||||||
exec(PHP_BINARY.' '.escapeshellarg($script).' '.escapeshellarg($file),$out,$code);
|
exec(PHP_BINARY.' '.escapeshellarg($script).' '.escapeshellarg($file),$out,$code);
|
||||||
if($code!==0||file_get_contents($file)!==$patched)throw new RuntimeException('Token patch не идемпотентен');
|
if($code!==0||file_get_contents($file)!==$patched)throw new RuntimeException('Token patch не идемпотентен');
|
||||||
|
$current='{ "other":1.0,"huge":9223372036854775809,"utm": {"chob" : { "weights":{"2":9e+01,"1":-0.0},"default":2 },"nested":{"chob":"keep"}}}';
|
||||||
|
file_put_contents($file,$current);exec(PHP_BINARY.' '.escapeshellarg($script).' '.escapeshellarg($file),$out,$code);
|
||||||
|
$renamed=str_replace('"chob" :','"banch" :',$current);
|
||||||
|
if($code!==10||file_get_contents($file)!==$renamed)throw new RuntimeException('api#327: миграция chob→banch должна менять только токен имени');
|
||||||
|
exec(PHP_BINARY.' '.escapeshellarg($script).' '.escapeshellarg($file),$out,$code);
|
||||||
|
if($code!==0||file_get_contents($file)!==$renamed)throw new RuntimeException('api#327: повторная миграция не должна писать');
|
||||||
|
foreach(['"chob":{"weights":{"1":1},"default":"1"},"banch":{"weights":{"2":1},"default":"2"}', '"bchoice":{"weights":{"a":1},"default":"a"},"chob":{"weights":{"1":1},"default":"1"}'] as $conflicting){
|
||||||
|
$conflict='{"utm":{'.$conflicting.'}}';file_put_contents($file,$conflict);
|
||||||
|
exec(PHP_BINARY.' '.escapeshellarg($script).' '.escapeshellarg($file).' 2>/dev/null',$out,$code);
|
||||||
|
if($code!==2||file_get_contents($file)!==$conflict)throw new RuntimeException('api#327: конфликт aliases должен блокировать patch без записи');
|
||||||
|
}
|
||||||
echo "OK: numeric config migration preserves weights/default/foreign JSON types\n";
|
echo "OK: numeric config migration preserves weights/default/foreign JSON types\n";
|
||||||
}finally{unlink($file);}
|
}finally{unlink($file);}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
/** api#326: публичные маски выбирают внутренние варианты без открытых alias. */
|
/** api#326: публичные маски выбирают внутренние варианты без открытых alias. */
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
$_GET = ['indpic'=>'abcd2', 'theme'=>'abcd2x', 'degr'=>'abcd2', 'reg'=>'abcd1', 'chob'=>'abcd1', 'tp'=>'abcd2', 'cons'=>'abcd3', 'focus'=>'abcd2', 'registration'=>'before', 'bchoice'=>'b', 'chb'=>'a', 'utm_source'=>'abcd9same', 'subid'=>'тест + &'];
|
$_GET = ['indpic'=>'abcd2', 'theme'=>'abcd2x', 'degr'=>'abcd2', 'reg'=>'abcd1', 'banch'=>'abcd1', 'tp'=>'abcd2', 'cons'=>'abcd3', 'focus'=>'abcd2', 'registration'=>'before', 'bchoice'=>'b', 'chb'=>'a', 'utm_source'=>'abcd9same', 'subid'=>'тест + &'];
|
||||||
$_SERVER['HTTP_HOST']='zoomstage.online';
|
$_SERVER['HTTP_HOST']='zoomstage.online';
|
||||||
ob_start(); require dirname(__DIR__).'/index.php'; $html=ob_get_clean();
|
ob_start(); require dirname(__DIR__).'/index.php'; $html=ob_get_clean();
|
||||||
$expected=['indpic'=>'2','theme'=>'light','degr'=>'2','registration'=>'before','bchoice'=>'b','tp'=>'2','cons'=>'3','focus'=>'center'];
|
$expected=['indpic'=>'2','theme'=>'light','degr'=>'2','registration'=>'before','bchoice'=>'b','tp'=>'2','cons'=>'3','focus'=>'center'];
|
||||||
@@ -17,7 +17,7 @@ foreach(['2','rnd','dark','abcdx','abc',[],null,'abcd0'] as $value) {
|
|||||||
if(prelandResolveUtm(["theme"=>"абвг2"],$config)["digits"]["theme"]!=="2")throw new RuntimeException("Unicode fifth symbol");
|
if(prelandResolveUtm(["theme"=>"абвг2"],$config)["digits"]["theme"]!=="2")throw new RuntimeException("Unicode fifth symbol");
|
||||||
for($i=0;$i<50;$i++) if(prelandResolveUtm(['indpic'=>'abcd0'],$config)['digits']['indpic']!=='2')throw new RuntimeException('0 нарушил веса');
|
for($i=0;$i<50;$i++) if(prelandResolveUtm(['indpic'=>'abcd0'],$config)['digits']['indpic']!=='2')throw new RuntimeException('0 нарушил веса');
|
||||||
$settings=json_decode(file_get_contents(dirname(__DIR__).'/config.json'),true);
|
$settings=json_decode(file_get_contents(dirname(__DIR__).'/config.json'),true);
|
||||||
$defaults=['indpic'=>'1','theme'=>'1','degr'=>'1','reg'=>'2','chob'=>'2','tp'=>'2','cons'=>'2','focus'=>'1'];
|
$defaults=['indpic'=>'1','theme'=>'1','degr'=>'1','reg'=>'2','banch'=>'2','tp'=>'2','cons'=>'2','focus'=>'1'];
|
||||||
foreach(array_keys($defaults) as $key) foreach(['1','rnd','abcdx','abc',[],null,"\xffabcd2"] as $invalid) {
|
foreach(array_keys($defaults) as $key) foreach(['1','rnd','abcdx','abc',[],null,"\xffabcd2"] as $invalid) {
|
||||||
$resolved=prelandResolveUtm([$key=>$invalid,'registration'=>'before','bchoice'=>'b','chb'=>'a'],$settings);
|
$resolved=prelandResolveUtm([$key=>$invalid,'registration'=>'before','bchoice'=>'b','chb'=>'a'],$settings);
|
||||||
if($resolved['digits'][$key]!==$defaults[$key])throw new RuntimeException('Открытый/invalid URL изменил '.$key);
|
if($resolved['digits'][$key]!==$defaults[$key])throw new RuntimeException('Открытый/invalid URL изменил '.$key);
|
||||||
@@ -29,3 +29,9 @@ $zero=['utm'=>['theme'=>['weights'=>['2'=>0,'1'=>0],'default'=>'1'],'cons'=>['we
|
|||||||
if(prelandResolveUtm(['theme'=>'abcd0'],$zero)['digits']['theme']!=='2')throw new RuntimeException('Generic zero weights обязаны выбрать первый ключ, как до #326');
|
if(prelandResolveUtm(['theme'=>'abcd0'],$zero)['digits']['theme']!=='2')throw new RuntimeException('Generic zero weights обязаны выбрать первый ключ, как до #326');
|
||||||
if(prelandResolveUtm(['cons'=>'abcd0'],$zero)['digits']['cons']!=='2')throw new RuntimeException('Cons zero weights обязаны сохранить special default');
|
if(prelandResolveUtm(['cons'=>'abcd0'],$zero)['digits']['cons']!=='2')throw new RuntimeException('Cons zero weights обязаны сохранить special default');
|
||||||
echo "OK: numeric public boundary, defaults, weighted0, foreign values\n";
|
echo "OK: numeric public boundary, defaults, weighted0, foreign values\n";
|
||||||
|
foreach (['abcd1','1','b',[]] as $old) {
|
||||||
|
$resolved=prelandResolveUtm(['chob'=>$old,'bchoice'=>'b','chb'=>'a'],$settings);
|
||||||
|
if($resolved['digits']['banch']!=='2'||isset($resolved['context']['chob']))throw new RuntimeException('api#327: внешний chob должен игнорироваться и удаляться');
|
||||||
|
}
|
||||||
|
$oldConfig=['utm'=>['chob'=>['weights'=>['1'=>5,'2'=>0],'default'=>'1']]];
|
||||||
|
if(prelandResolveUtm([],$oldConfig)['digits']['banch']!=='1')throw new RuntimeException('api#327: старый host config обязан работать до миграции');
|
||||||
@@ -11,6 +11,7 @@ $_GET = [
|
|||||||
'utm_source' => 'stage-ad',
|
'utm_source' => 'stage-ad',
|
||||||
'subid' => 'click-42',
|
'subid' => 'click-42',
|
||||||
'key' => 'must-not-replace-client-key',
|
'key' => 'must-not-replace-client-key',
|
||||||
|
'chob' => 'abcd1ignored',
|
||||||
];
|
];
|
||||||
$_SERVER['HTTP_HOST'] = 'zoomstage.online';
|
$_SERVER['HTTP_HOST'] = 'zoomstage.online';
|
||||||
|
|
||||||
@@ -44,7 +45,7 @@ if ($params !== $expected) {
|
|||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($redirect_digits !== ['indpic'=>'1','theme'=>'1','degr'=>'1','reg'=>'2','chob'=>'2','tp'=>'2','cons'=>'2','focus'=>'1']) {
|
if ($redirect_digits !== ['indpic'=>'1','theme'=>'1','degr'=>'1','reg'=>'2','banch'=>'2','tp'=>'2','cons'=>'2','focus'=>'1']) {
|
||||||
throw new RuntimeException('Type-in обязан сохранить полный resolved numeric snapshot для handoff/recovery.');
|
throw new RuntimeException('Type-in обязан сохранить полный resolved numeric snapshot для handoff/recovery.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ if (!is_array($config)) {
|
|||||||
$failures[] = 'Рабочий config.json должен фиксировать явно согласованный focus=face.';
|
$failures[] = 'Рабочий config.json должен фиксировать явно согласованный focus=face.';
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (['indpic', 'theme', 'degr', 'reg', 'chob', 'cons', 'tp'] as $key) {
|
foreach (['indpic', 'theme', 'degr', 'reg', 'banch', 'cons', 'tp'] as $key) {
|
||||||
$setting = $config['utm'][$key] ?? null;
|
$setting = $config['utm'][$key] ?? null;
|
||||||
if (!is_array($setting) || !is_array($setting['weights'] ?? null) || !is_string($setting['default'] ?? null)) {
|
if (!is_array($setting) || !is_array($setting['weights'] ?? null) || !is_string($setting['default'] ?? null)) {
|
||||||
$failures[] = "Рабочий config.json должен хранить валидный stage-снимок UTM {$key}.";
|
$failures[] = "Рабочий config.json должен хранить валидный stage-снимок UTM {$key}.";
|
||||||
|
|||||||
@@ -2,17 +2,19 @@
|
|||||||
const assert = require('node:assert/strict');
|
const assert = require('node:assert/strict');
|
||||||
const fs = require('node:fs');
|
const fs = require('node:fs');
|
||||||
const vm = require('node:vm');
|
const vm = require('node:vm');
|
||||||
const source = fs.readFileSync('/app/components/utm-handoff.php','utf8').split("<<<'JS'\n")[1].split('\nJS;')[0];
|
const source = fs.readFileSync('/app/components/utm-handoff.php','utf8').replace(/\r\n/g,'\n').split("<<<'JS'\n")[1].split('\nJS;')[0];
|
||||||
const context = {window:{},URLSearchParams,Uint8Array,crypto:require('node:crypto').webcrypto};
|
const context = {window:{},URLSearchParams,Uint8Array,crypto:require('node:crypto').webcrypto};
|
||||||
vm.runInNewContext(source,context);
|
vm.runInNewContext(source,context);
|
||||||
const handoff=context.window.ZoomzillaUtmHandoff;
|
const handoff=context.window.ZoomzillaUtmHandoff;
|
||||||
const defaults={indpic:'1',theme:'1',degr:'1',reg:'2',chob:'2',tp:'1',cons:'2',focus:'1'};
|
const defaults={indpic:'1',theme:'1',degr:'1',reg:'2',banch:'2',tp:'1',cons:'2',focus:'1'};
|
||||||
const selected={indpic:'2',theme:'2',degr:'2',reg:'1',chob:'1',tp:'2',cons:'3',focus:'2'};
|
const selected={indpic:'2',theme:'2',degr:'2',reg:'1',banch:'1',tp:'2',cons:'3',focus:'2'};
|
||||||
const foreign='utm_source=abcd9same&subid=%D1%82%D0%B5%D1%81%D1%82+%2B+%26&id=42&stage=keep';
|
const foreign='utm_source=abcd9same&subid=%D1%82%D0%B5%D1%81%D1%82+%2B+%26&id=42&stage=keep';
|
||||||
for(const meta of [
|
for(const meta of [
|
||||||
{redirectUtm:foreign,utmDigits:selected},
|
{redirectUtm:foreign,utmDigits:selected},
|
||||||
|
{redirectUtm:foreign,utmDigits:{...selected,banch:undefined,chob:'1'}},
|
||||||
|
{redirectUtm:foreign+'&indpic=abcd2&theme=абвг2°r=abcd2®=abcd1&chob=abcd1&tp=abcd2&cons=abcd3&focus=abcd2'},
|
||||||
{redirectUtm:foreign+'&indpic=2&theme=light°r=2®istration=before&bchoice=b&tp=2&cons=3&focus=center&chb=a'},
|
{redirectUtm:foreign+'&indpic=2&theme=light°r=2®istration=before&bchoice=b&tp=2&cons=3&focus=center&chb=a'},
|
||||||
{redirectUtm:foreign+'&indpic=abcd2&theme=абвг2°r=abcd2®=abcd1&chob=abcd1&tp=abcd2&cons=abcd3&focus=abcd2'}
|
{redirectUtm:foreign+'&indpic=abcd2&theme=абвг2°r=abcd2®=abcd1&banch=abcd1&tp=abcd2&cons=abcd3&focus=abcd2'}
|
||||||
]){
|
]){
|
||||||
const before=JSON.stringify(meta);
|
const before=JSON.stringify(meta);
|
||||||
const first=new URLSearchParams(handoff.query(meta,defaults));
|
const first=new URLSearchParams(handoff.query(meta,defaults));
|
||||||
@@ -27,7 +29,7 @@ for(const meta of [
|
|||||||
assert.equal(query.get('utm_source'),'abcd9same');
|
assert.equal(query.get('utm_source'),'abcd9same');
|
||||||
assert.equal(query.get('subid'),'тест + &');
|
assert.equal(query.get('subid'),'тест + &');
|
||||||
assert.equal(query.get('id'),'42'); assert.equal(query.get('stage'),'keep');
|
assert.equal(query.get('id'),'42'); assert.equal(query.get('stage'),'keep');
|
||||||
for(const legacy of ['registration','bchoice','chb'])assert.equal(query.has(legacy),false);
|
for(const legacy of ['registration','bchoice','chb','chob'])assert.equal(query.has(legacy),false);
|
||||||
}
|
}
|
||||||
assert.equal(JSON.stringify(meta),before);
|
assert.equal(JSON.stringify(meta),before);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user