Files
preland/bin/ensure-utm-config.php
T
Panfilov Konstantin 84f70b1c71
deploy-preland / deploy zoomstage.online (push) Successful in 54s
fix(admin): align UTM notes and block obsolete chb publication
2026-10-25 23:16:35 +03:00

103 lines
6.5 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/** api#326: заменяет только согласованные JSON-токены, сохраняя прочие байты. */
declare(strict_types=1);
/** Карта диапазонов исходного JSON; числовые токены никогда не сериализуются. */
function utmConfigNode(string $raw, int &$offset): array
{
$length=strlen($raw);
while ($offset<$length && ctype_space($raw[$offset])) $offset++;
$start=$offset; $char=$raw[$offset] ?? '';
if ($char==='"') {
$offset++;
while ($offset<$length) {
if ($raw[$offset]==='\\') { $offset+=2; continue; }
if ($raw[$offset++]==='"') break;
}
return ['start'=>$start,'end'=>$offset,'type'=>'string','value'=>json_decode(substr($raw,$start,$offset-$start),true,512,JSON_THROW_ON_ERROR)];
}
if ($char==='{' || $char==='[') {
$object=$char==='{'; $close=$object ? '}' : ']'; $offset++; $properties=[];
while (true) {
while ($offset<$length && ctype_space($raw[$offset])) $offset++;
if (($raw[$offset] ?? '')===$close) { $offset++; break; }
if ($object) {
$key=utmConfigNode($raw,$offset);
while ($offset<$length && ctype_space($raw[$offset])) $offset++;
$offset++; // Двоеточие уже проверено полным JSON validation.
$value=utmConfigNode($raw,$offset);
$properties[]=['key'=>$key,'value'=>$value];
} else { utmConfigNode($raw,$offset); }
while ($offset<$length && ctype_space($raw[$offset])) $offset++;
if (($raw[$offset] ?? '')===',') { $offset++; continue; }
$offset++; break;
}
return ['start'=>$start,'end'=>$offset,'type'=>$object ? 'object' : 'array','properties'=>$properties];
}
while ($offset<$length && !str_contains(",]} \t\r\n",$raw[$offset])) $offset++;
return ['start'=>$start,'end'=>$offset,'type'=>'scalar'];
}
/** Выбирает единственное свойство; неоднозначные управляющие поля не мигрирует. */
function utmConfigProperty(array $node, string $key): ?array
{
$found=null;
foreach ($node['properties'] ?? [] as $property) {
if ($property['key']['value']!==$key) continue;
if ($found!==null) throw new RuntimeException('Повторное управляющее поле: '.$key);
$found=$property;
}
return $found;
}
if ($argc!==2) { fwrite(STDERR,"Использование: php bin/ensure-utm-config.php config.json\n"); exit(2); }
try {
$path=$argv[1]; $raw=file_get_contents($path);
// Валидация всей грамматики без последующей сериализации decoded данных.
json_decode($raw,false,512,JSON_THROW_ON_ERROR|JSON_BIGINT_AS_STRING);
$offset=0; $root=utmConfigNode($raw,$offset);
// Проверяем source keys, а не decoded object: escaped/повторные имена
// тоже не должны скрыть chb перед публикацией enum replacement.
foreach($root['properties']??[] as $property){
if($property['key']['value']!=='utm'||($property['value']['type']??null)!=='object')continue;
foreach($property['value']['properties'] as $setting)if($setting['key']['value']==='chb')throw new RuntimeException('Устаревший ключ utm.chb нельзя публиковать. Явно удалите его перед миграцией.');
}
$utm=utmConfigProperty($root,'utm')['value'] ?? null;
if (($root['type'] ?? null)!=='object' || ($utm['type'] ?? null)!=='object') throw new RuntimeException('Отсутствует объект utm');
$replacements=[];
$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)];
};
foreach (['theme'=>['dark'=>'1','light'=>'2'],'reg'=>['before'=>'1','after'=>'2'],'banch'=>['b'=>'1','a'=>'2'],'focus'=>['face'=>'1','center'=>'2']] as $key=>$map) {
$property=utmConfigProperty($utm,$key);
$aliases=['reg'=>['registration'],'banch'=>['chob','bchoice']][$key] ?? [];
foreach ($aliases as $old) {
$legacy=utmConfigProperty($utm,$old);
if ($property && $legacy) throw new RuntimeException('Конфликт старого и нового UTM: '.$key);
if ($legacy) { $replace($legacy['key'],$key); $property=$legacy; }
}
if (!$property) continue;
$setting=$property['value'];
$weights=utmConfigProperty($setting,'weights')['value'] ?? null;
$default=utmConfigProperty($setting,'default')['value'] ?? null;
if (($setting['type'] ?? null)!=='object' || ($weights['type'] ?? null)!=='object' || !$default) throw new RuntimeException('Некорректный UTM: '.$key);
$seen=[];
foreach ($weights['properties'] as $variant) {
$name=$variant['key']['value']; $digit=$map[$name] ?? $name;
if (!in_array($digit,['1','2'],true) || isset($seen[$digit])) throw new RuntimeException('Неоднозначный вариант UTM: '.$key);
$seen[$digit]=true;
if ($digit!==$name) $replace($variant['key'],$digit);
}
$value=json_decode(substr($raw,$default['start'],$default['end']-$default['start']),false,512,JSON_THROW_ON_ERROR);
if (!(is_string($value)||is_int($value))) throw new RuntimeException('Некорректный default UTM: '.$key);
$digit=$map[$value] ?? $value;
if (!in_array((string)$digit,['1','2'],true)) throw new RuntimeException('Некорректный default UTM: '.$key);
if ($digit!==$value) $replace($default,$digit);
}
if (!$replacements) { echo "Числовые UTM уже настроены; config не изменён.\n"; exit(0); }
usort($replacements,static fn(array $a,array $b): int=>$b['start']<=>$a['start']);
foreach ($replacements as $replacement) $raw=substr_replace($raw,$replacement['text'],$replacement['start'],$replacement['end']-$replacement['start']);
if (file_put_contents($path,$raw,LOCK_EX)===false) throw new RuntimeException('Не удалось записать config');
echo "Мигрированы только согласованные имена и enum UTM; остальные байты сохранены.\n"; exit(10);
} catch(Throwable $error) { fwrite(STDERR,$error->getMessage()."\n"); exit(2); }