api#347: передавать и настраивать UTM part без ссылки в Preland
This commit is contained in:
1 parent
94619432a3
commit
0d350ed14b
23 files changed
+126
-47
No files matched your search
@@ -39,7 +39,7 @@ final class Config
|
||||
}
|
||||
private static function adaptChange(\stdClass $config,array $change): array {
|
||||
$path=$change['path']??[];if(($path[0]??null)!=='utm'||!isset($path[1]))return $change;
|
||||
$aliases=['reg'=>['reg','registration'],'banch'=>['banch','chob','bchoice']];$maps=['theme'=>['1'=>'dark','2'=>'light'],'reg'=>['1'=>'before','2'=>'after'],'banch'=>['1'=>'b','2'=>'a'],'focus'=>['1'=>'face','2'=>'center']];
|
||||
$aliases=['reg'=>['reg','registration'],'banch'=>['banch','chob','bchoice']];$maps=['theme'=>['1'=>'dark','2'=>'light'],'reg'=>['1'=>'before','2'=>'after'],'banch'=>['1'=>'b','2'=>'a'],'focus'=>['1'=>'face','2'=>'center'],'part'=>['1'=>'1','2'=>'2']];
|
||||
$key=$path[1];$canonical=['registration'=>'reg','chob'=>'banch','bchoice'=>'banch'][$key]??$key;$settings=$config->utm??new \stdClass();$existing=[];
|
||||
foreach($aliases[$canonical]??[$canonical] as $alias)if(isset($settings->$alias))$existing[]=$alias;
|
||||
if(count($existing)>1)throw new Problem("Конфликт UTM-ключей {$canonical}. Перечитайте и явно устраните неоднозначность.");
|
||||
@@ -65,11 +65,11 @@ final class Config
|
||||
$utm=$config->utm ?? new \stdClass();
|
||||
if (!$utm instanceof \stdClass) throw new Problem('UTM должен быть объектом.');
|
||||
foreach (['registration'=>'reg','chob'=>'banch','bchoice'=>'banch'] as $old=>$new) if (isset($utm->$old,$utm->$new)) throw new Problem("Конфликт старого {$old} и нового {$new} UTM-ключа.");
|
||||
$maps=['theme'=>['1'=>'dark','2'=>'light'],'reg'=>['1'=>'before','2'=>'after'],'banch'=>['1'=>'b','2'=>'a'],'focus'=>['1'=>'face','2'=>'center']];
|
||||
$maps=['theme'=>['1'=>'dark','2'=>'light'],'reg'=>['1'=>'before','2'=>'after'],'banch'=>['1'=>'b','2'=>'a'],'focus'=>['1'=>'face','2'=>'center'],'part'=>['1'=>'1','2'=>'2']];
|
||||
$warnings=[];
|
||||
foreach (get_object_vars($utm) as $key=>$group) {
|
||||
$canonical=['registration'=>'reg','chob'=>'banch','bchoice'=>'banch'][$key] ?? $key;
|
||||
if (!in_array($canonical,['indpic','theme','degr','reg','banch','tp','cons','focus'],true)) continue;
|
||||
if (!in_array($canonical,['indpic','theme','degr','reg','banch','tp','cons','focus','part'],true)) continue;
|
||||
if (!$group instanceof \stdClass || !isset($group->default) || !(($group->weights ?? null) instanceof \stdClass)) throw new Problem("Некорректная структура UTM {$key}.");
|
||||
$number=static function($value) use($maps,$canonical): string {
|
||||
if (!is_int($value) && !is_string($value)) throw new Problem('Вариант UTM должен быть номером.');
|
||||
|
||||
+2
-2
@@ -5,9 +5,9 @@ namespace PrelandAdmin;
|
||||
/** Применяет независимый именованный UTM-снимок, сохраняя остальные поля получателя. */
|
||||
final class Copy
|
||||
{
|
||||
private const GROUPS = ['indpic','theme','degr','reg','banch','tp','cons','focus'];
|
||||
private const GROUPS = ['indpic','theme','degr','reg','banch','tp','cons','focus','part'];
|
||||
private const ALIASES = ['reg'=>['reg','registration'],'banch'=>['banch','chob','bchoice']];
|
||||
private const MAPS = ['theme'=>['1'=>'dark','2'=>'light'],'reg'=>['1'=>'before','2'=>'after'],'banch'=>['1'=>'b','2'=>'a'],'focus'=>['1'=>'face','2'=>'center']];
|
||||
private const MAPS = ['theme'=>['1'=>'dark','2'=>'light'],'reg'=>['1'=>'before','2'=>'after'],'banch'=>['1'=>'b','2'=>'a'],'focus'=>['1'=>'face','2'=>'center'],'part'=>['1'=>'1','2'=>'2']];
|
||||
|
||||
public function __construct(private Store $store) {}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace PrelandAdmin;
|
||||
/** Независимый выбор меток: читает реестр целей, пишет только приватное состояние ссылки. */
|
||||
final class LinkBuilder
|
||||
{
|
||||
private const KEYS = ['indpic', 'theme', 'degr', 'reg', 'banch', 'tp', 'cons', 'focus'];
|
||||
private const KEYS = ['indpic', 'theme', 'degr', 'reg', 'banch', 'tp', 'cons', 'focus', 'part'];
|
||||
|
||||
public function __construct(private readonly Store $store) {}
|
||||
|
||||
@@ -22,10 +22,15 @@ final class LinkBuilder
|
||||
|| !is_string($saved['url'] ?? null) || !is_string($saved['saved_at'] ?? null)) {
|
||||
throw new Problem('Повреждено сохранённое состояние конструктора ссылки.', 503);
|
||||
}
|
||||
$selection = $this->selection($saved['selection']);
|
||||
// Старые восемь полей дополняем part=1, сохраняя исходный шум остальных меток.
|
||||
$legacy = count($saved['selection']) === 8 && !array_key_exists('part', $saved['selection']);
|
||||
$selection = $this->selection($legacy ? $saved['selection'] + ['part'=>'1'] : $saved['selection']);
|
||||
$query = parse_url($saved['url'], PHP_URL_QUERY);
|
||||
if (!is_string($query)) throw new Problem('Повреждена сохранённая ссылка.', 503);
|
||||
parse_str($query, $parameters);
|
||||
if ($legacy && array_keys($parameters) === array_slice(self::KEYS, 0, 8)) {
|
||||
$parameters['part'] = bin2hex(random_bytes(2)) . '1' . bin2hex(random_bytes(6));
|
||||
}
|
||||
if (array_keys($parameters) !== self::KEYS) throw new Problem('Повреждена сохранённая ссылка.', 503);
|
||||
foreach ($selection as $key=>$value) {
|
||||
$digit = $value === 'rnd' ? '0' : $value;
|
||||
@@ -35,7 +40,8 @@ final class LinkBuilder
|
||||
}
|
||||
// Переименование цели меняет только origin, сохранённый шум не генерируется заново.
|
||||
$url = $origin . '/?' . http_build_query($parameters, '', '&', PHP_QUERY_RFC3986);
|
||||
if ($saved['url'] !== $url) {
|
||||
if ($saved['url'] !== $url || $legacy) {
|
||||
$saved['selection'] = $selection;
|
||||
$saved['url'] = $url;
|
||||
$this->store->write($name, $saved);
|
||||
}
|
||||
@@ -66,12 +72,13 @@ final class LinkBuilder
|
||||
private function selection(array $input): array
|
||||
{
|
||||
if (count($input) !== count(self::KEYS) || array_diff(array_keys($input), self::KEYS)) {
|
||||
throw new Problem('Передайте ровно восемь параметров конструктора ссылки.');
|
||||
throw new Problem('Передайте ровно девять параметров конструктора ссылки.');
|
||||
}
|
||||
$selection = [];
|
||||
foreach (self::KEYS as $key) {
|
||||
$value = $input[$key] ?? null;
|
||||
if (!is_string($value) || ($value !== 'rnd' && !preg_match('/^[1-9]$/D', $value))) {
|
||||
if (!is_string($value) || ($value !== 'rnd' && !preg_match('/^[1-9]$/D', $value))
|
||||
|| ($key === 'part' && !in_array($value, ['1', '2', 'rnd'], true))) {
|
||||
throw new Problem("Недопустимое значение параметра {$key} конструктора ссылки.");
|
||||
}
|
||||
$selection[$key] = $value;
|
||||
|
||||
+4
-2
@@ -4,7 +4,7 @@ namespace PrelandAdmin;
|
||||
|
||||
final class Notes
|
||||
{
|
||||
private const MARKERS=['indpic','theme','degr','reg','banch','tp','cons','focus'];
|
||||
private const MARKERS=['indpic','theme','degr','reg','banch','tp','cons','focus','part'];
|
||||
public function __construct(private readonly Store $store) {}
|
||||
|
||||
public function all(): array {
|
||||
@@ -29,7 +29,9 @@ final class Notes
|
||||
$notes=[];foreach(self::MARKERS as $marker)$notes[$marker]=['text'=>'','version'=>hash('sha256','utm-notes:'.$marker.':empty'),'saved_at'=>null];return $notes;
|
||||
}
|
||||
$notes=$this->store->read('utm-notes.json',null);
|
||||
if(!is_array($notes)||count($notes)!==8||array_diff(self::MARKERS,array_keys($notes)))throw new Problem('Повреждено приватное состояние заметок.',503);
|
||||
// Старое хранилище без part читаем с пустой заметкой; прежние версии сохраняем.
|
||||
if(is_array($notes)&&count($notes)===8&&!array_key_exists('part',$notes))$notes['part']=['text'=>'','version'=>hash('sha256','utm-notes:part:empty'),'saved_at'=>null];
|
||||
if(!is_array($notes)||count($notes)!==count(self::MARKERS)||array_diff(self::MARKERS,array_keys($notes)))throw new Problem('Повреждено приватное состояние заметок.',503);
|
||||
$ordered=[];
|
||||
foreach(self::MARKERS as $marker){
|
||||
$note=$notes[$marker];
|
||||
|
||||
Reference in new issue
Block a user