api#326 Числовые UTM и новый шум каждого перехода
This commit is contained in:
1 parent
f30742c2fb
commit
81e189ca67
16 files changed
+303
-160
No files matched your search
+4
-14
@@ -3,9 +3,9 @@ require_once __DIR__ . '/browser-upload.php';
|
||||
require __DIR__ . '/upload-error.php';
|
||||
echo '<script>', require __DIR__ . '/navigation-guard.php', '</script>';
|
||||
echo '<script>', require __DIR__ . '/upload-recovery.php', '</script>';
|
||||
echo '<script>', require __DIR__ . '/utm-handoff.php', '</script>';
|
||||
|
||||
$errorMessage = Yii::t('app', 'Возникла ошибка');
|
||||
$utmJson = json_encode($UTM ?? [], JSON_UNESCAPED_UNICODE);
|
||||
|
||||
$createApiUrl = ($apiHost ?? '') . '/api/images/public/create';
|
||||
$uploadPolicyApiUrl = ($apiHost ?? '') . '/api/images/public/upload-policy';
|
||||
@@ -70,19 +70,9 @@ $basePickerAccept = 'image/jpeg,image/png,image/gif,image/webp,image/bmp,image/a
|
||||
}
|
||||
};
|
||||
|
||||
const utmData = <?= $utmJson ?>;
|
||||
const redirectUrlBase = <?= json_encode($redirectUrlBase, JSON_UNESCAPED_UNICODE) ?>;
|
||||
const redirectUrlUtm = <?= json_encode($redirectUrlUtm, JSON_UNESCAPED_UNICODE) ?>;
|
||||
// Сохранённая до перехода на cons загрузка не должна вернуть legacy chb.
|
||||
const normalizeConsentRedirect = query => {
|
||||
const params = new URLSearchParams(query || '');
|
||||
params.delete('chb');
|
||||
const value = params.get('cons');
|
||||
if (!/^[1-9][0-9]{0,9}$/.test(value || '') || Number(value) > 2147483647) {
|
||||
params.set('cons', String(utmData.cons || '2'));
|
||||
}
|
||||
return params.toString();
|
||||
};
|
||||
const utmDigits = <?= json_encode($redirect_digits ?? [], JSON_UNESCAPED_UNICODE) ?>;
|
||||
const createApiUrl = <?= json_encode($createApiUrl, JSON_UNESCAPED_UNICODE) ?>;
|
||||
const uploadPolicyApiUrl = <?= json_encode($uploadPolicyApiUrl, JSON_UNESCAPED_UNICODE) ?>;
|
||||
|
||||
@@ -118,7 +108,7 @@ $basePickerAccept = 'image/jpeg,image/png,image/gif,image/webp,image/bmp,image/a
|
||||
let leavingUploadPage = false;
|
||||
let uploadAttempt = 0;
|
||||
const recovery = window.ZoomzillaUploadRecovery.create({scope: 'preland', target: createApiUrl});
|
||||
const metadata = () => ({actionUrl: createApiUrl, redirectBase: redirectUrlBase, redirectUtm: redirectUrlUtm});
|
||||
const metadata = () => ({actionUrl: createApiUrl, redirectBase: redirectUrlBase, redirectUtm: redirectUrlUtm, utmDigits: {...utmDigits}});
|
||||
const clearRecovery = () => recovery.clear().catch(() => {});
|
||||
let uploadDots = 1;
|
||||
let uploadDotsTimer = null;
|
||||
@@ -213,7 +203,7 @@ $basePickerAccept = 'image/jpeg,image/png,image/gif,image/webp,image/bmp,image/a
|
||||
|
||||
function redirectToTour(record) {
|
||||
let targetUrl = record.meta.redirectBase + encodeURIComponent(record.key);
|
||||
const redirectQuery = normalizeConsentRedirect(record.meta.redirectUtm);
|
||||
const redirectQuery = window.ZoomzillaUtmHandoff.query(record.meta, utmDigits);
|
||||
if (redirectQuery) targetUrl += '&' + redirectQuery;
|
||||
const shownTheme = window.ZoomzillaTheme.current();
|
||||
targetUrl += '&shown_theme=' + encodeURIComponent(shownTheme);
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
/** api#326: внешняя граница UTM; semantic-контекст шаблонов остаётся прежним. */
|
||||
declare(strict_types=1);
|
||||
|
||||
function prelandUtmEnums(): array
|
||||
{
|
||||
return ['theme'=>['1'=>'dark','2'=>'light'], 'reg'=>['1'=>'before','2'=>'after'], 'chob'=>['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','bchoice'=>'chob'] 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','chob'=>'2','tp'=>'1','cons'=>'2','focus'=>'1'];
|
||||
$enums=prelandUtmEnums(); $digits=[]; $context=$query;
|
||||
foreach (['registration','bchoice','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','chob','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') {
|
||||
$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','chob'=>'bchoice'][$key] ?? $key;
|
||||
$context[$internal]=$enums[$key][$digit] ?? $digit;
|
||||
}
|
||||
return ['digits'=>$digits,'context'=>$context];
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
/** api#326: новый шум при каждой навигации; recovery хранит выбранные цифры. */
|
||||
return <<<'JS'
|
||||
(function () {
|
||||
const keys = ['indpic','theme','degr','reg','chob','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 legacy = {reg:'registration',chob:'bchoice'};
|
||||
const valid = (key, value) => /^[1-9]$/.test(value || '') && (!enums[key] || Object.values(enums[key]).includes(value));
|
||||
function restore(meta, defaults) {
|
||||
const query = new URLSearchParams(meta.redirectUtm || '');
|
||||
const digits = {};
|
||||
for (const key of keys) {
|
||||
let value = meta.utmDigits?.[key];
|
||||
if (!valid(key, value)) {
|
||||
// Только сохранённый record: старые enum/открытые цифры не являются raw URL alias.
|
||||
const stored = query.get(legacy[key] || key) ?? query.get(key);
|
||||
value = enums[key]?.[stored] || stored;
|
||||
if (!valid(key, value)) value = Array.from(stored || '')[4];
|
||||
}
|
||||
digits[key] = valid(key, value) ? value : defaults[key];
|
||||
}
|
||||
return digits;
|
||||
}
|
||||
const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
function mask(digit) {
|
||||
const bytes = new Uint8Array(10);
|
||||
crypto.getRandomValues(bytes);
|
||||
const chars = Array.from(bytes, value => alphabet[value % alphabet.length]);
|
||||
chars[4] = digit;
|
||||
return chars.join('');
|
||||
}
|
||||
function query(meta, defaults) {
|
||||
const params = new URLSearchParams(meta.redirectUtm || '');
|
||||
const digits = restore(meta, defaults);
|
||||
for (const key of ['registration','bchoice','chb','key','shown_theme',...keys]) params.delete(key);
|
||||
for (const key of keys) params.set(key, mask(digits[key]));
|
||||
return params.toString();
|
||||
}
|
||||
window.ZoomzillaUtmHandoff = {restore, query};
|
||||
})();
|
||||
JS;
|
||||
Reference in new issue
Block a user