['1'=>'dark','2'=>'light'], 'reg'=>['1'=>'before','2'=>'after'], 'banch'=>['1'=>'b','2'=>'a'], 'focus'=>['1'=>'face','2'=>'center']]; } /** Совместимость host-config при доставке кода до точечной миграции. */ function prelandNumericUtmConfig(array $config): array { $settings = is_array($config['utm'] ?? null) ? $config['utm'] : []; foreach (['registration'=>'reg','chob'=>'banch','bchoice'=>'banch'] as $old=>$new) { if (!isset($settings[$new]) && isset($settings[$old])) $settings[$new] = $settings[$old]; unset($settings[$old]); } unset($settings['chb']); foreach (prelandUtmEnums() as $key=>$map) { if (!is_array($settings[$key] ?? null)) continue; $weights=[]; foreach ((array)($settings[$key]['weights'] ?? []) as $variant=>$weight) { $digit=array_search((string)$variant,$map,true); $weights[$digit === false ? $variant : $digit]=$weight; } $settings[$key]['weights']=$weights; $default=$settings[$key]['default'] ?? null; $digit=array_search($default,$map,true); if ($digit !== false) $settings[$key]['default']=(string)$digit; } $config['utm']=$settings; return $config; } /** Выбирает random один раз, до рендера/сохранения загрузки. */ function prelandResolveUtm(array $query, array $config): array { $config=prelandNumericUtmConfig($config); $defaults=['indpic'=>'1','theme'=>'2','degr'=>'1','reg'=>'2','banch'=>'2','tp'=>'1','cons'=>'2','focus'=>'1']; $enums=prelandUtmEnums(); $digits=[]; $context=$query; foreach (['registration','bchoice','chob','chb'] as $old) unset($context[$old]); foreach ($defaults as $key=>$fallback) { unset($context[$key]); $settings=$config['utm'][$key] ?? []; $valid=static fn($digit): bool => preg_match('/^[1-9]$/D',(string)$digit)===1 && (!isset($enums[$key]) || isset($enums[$key][$digit])) && (in_array($key,['cons','theme','reg','banch','focus'],true) || isset($settings['weights'][$digit])); $default=(string)($settings['default'] ?? $fallback); if (!$valid($default)) $default=$fallback; $value=$query[$key] ?? null; $digit=is_string($value) && preg_match('/\A.{4}([0-9])/us',$value,$match) ? $match[1] : null; if ($digit==='0') { // Generic resolver исторически выбирает первый ключ при sum<=0; // cons имеет отдельный контракт: только положительные веса и default. if ($key !== 'cons') { $weights=(array)($settings['weights'] ?? []); $total=array_sum($weights); $digit=$weights ? (string)array_key_first($weights) : $default; if ($total>0 && is_finite((float)$total)) { $point=mt_rand()/mt_getrandmax()*$total; $current=0.0; foreach ($weights as $variant=>$weight) { $current+=(float)$weight; $digit=(string)$variant; if ($point<=$current) break; } } } else { $weights=[]; foreach ((array)($settings['weights'] ?? []) as $variant=>$weight) { if ($valid($variant) && is_numeric($weight) && is_finite((float)$weight) && (float)$weight>0) $weights[$variant]=(float)$weight; } $total=array_sum($weights); $digit=$default; if ($total>0 && is_finite($total)) { $point=mt_rand()/(mt_getrandmax()+1.0)*$total; foreach ($weights as $variant=>$weight) { if ($point<$weight) { $digit=(string)$variant; break; } $point-=$weight; } } } } if (!$valid($digit ?? '')) $digit=$default; $digits[$key]=$digit; $internal=['reg'=>'registration','banch'=>'bchoice'][$key] ?? $key; $context[$internal]=$enums[$key][$digit] ?? $digit; } return ['digits'=>$digits,'context'=>$context]; }