Файл: pages/account/save.php
Строк: 156
<?php
// Physical module map v50.6: public URL stays clean while code lives in /pages.
if (!defined('GAME_ROOT')) define('GAME_ROOT', dirname(__DIR__, 2));
chdir(GAME_ROOT);
if (PHP_SAPI !== 'cli') { $_SERVER['PHP_SELF']='/save.php'; $_SERVER['SCRIPT_NAME']='/save.php'; }
/* Сохранение героя / подтверждение данных: by Метриум (Стэлп) */
foreach(['common.php','functions.php','user.php'] as $i) require_once './system/'.$i;
$unic_code=(string)($_GET['unic_code']??'');
if(!$unic_code || !security_flow_valid($unic_code,'save')){header('Location:/user/');exit;}
if(!$user){header('Location:/');exit;} if((int)$user['save']===1){header('Location:/');exit;}
$title='Сохранение'; include './system/h.php'; echo '<div class="main">';
$errors=[]; $step=(string)($_POST['step']??'form');
if($_SERVER['REQUEST_METHOD']==='POST') security_require_csrf();
if($step==='prepare'){
$login=trim((string)($_POST['login']??''));$password=(string)($_POST['password']??'');
if(!preg_match('/^[a-z0-9а-яё _-]{2,20}$/iu',$login))$errors[]='Имя персонажа введено неверно';
if(strlen($password)<4 || strlen($password)>72)$errors[]='Пароль должен содержать от 4 до 72 символов';
if((int)(db_result(db_query("SELECT COUNT(*) FROM `users` WHERE `login`='".db_real_escape_string($login)."' AND `id`<>".(int)$user['id']),0)?:0)>0)$errors[]='Выбранное имя персонажа уже занято';
if(!$errors){$_SESSION['_save_confirm']=['login'=>$login,'hash'=>security_password_hash($password),'sex'=>(int)($_POST['sex']??0),'r'=>(int)($_POST['r']??0),'at'=>time()];$step='confirm';}
}
if($step==='change'){unset($_SESSION['_save_confirm']);$step='form';}
if($step==='confirm' && isset($_SESSION['_save_confirm']) && ($_POST['confirm']??'')==='1'){
$p=$_SESSION['_save_confirm'];
if(time()-(int)$p['at']>1800){
unset($_SESSION['_save_confirm']);
$errors[]='Сессия подтверждения истекла';
$step='form';
} else {
$uid=(int)$user['id'];
try {
// Сохраняем только гарантированно существующие поля users. Без транзакции вокруг legacy-таблиц:
// это исключает конфликт уже открытой транзакции на production. by Метриум (Стэлп)
$stmt=db_prepare('UPDATE `users` SET `login`=:login,`r`=:race,`sex`=:sex,`password`=:pass,`save`='1',`g`=`g`+100 WHERE `id`=:uid AND `save`='0' LIMIT 1',[
'login'=>(string)$p['login'],
'race'=>max(0,min(2,(int)$p['r'])),
'sex'=>max(0,min(1,(int)$p['sex'])),
'pass'=>(string)$p['hash'],
'uid'=>$uid,
]);
$check=db_fetch_one('SELECT `id`,`save`,`login`,`password` FROM `users` WHERE `id`=:uid LIMIT 1',['uid'=>$uid]);
if(!$check || (int)($check['save']??0)!==1){
throw new RuntimeException('save flag was not persisted');
}
// Необязательные поля старых/новых схем не имеют права ломать сохранение героя.
try {
$cols=[]; $qr=db_query('SHOW COLUMNS FROM `users`');
while($cr=db_fetch_assoc($qr)){ $f=(string)($cr['Field']??''); if($f!=='')$cols[$f]=true; }
$extra=[];
if(isset($cols['new'])) $extra[]='`new`=1';
if(isset($cols['hunt_battles'])) $extra[]='`hunt_battles`=15';
if(isset($cols['hunt_battles_max'])) $extra[]='`hunt_battles_max`=15';
if($extra) db_query('UPDATE `users` SET '.implode(',',$extra).' WHERE `id`='.$uid.' LIMIT 1');
} catch(Throwable $optionalError){ error_log('[SAVE_OPTIONAL] '.$optionalError->getMessage()); }
try { game_bonus_offer_ensure($uid); } catch(Throwable $bonusError) { error_log('[SAVE_BONUS] '.$bonusError->getMessage()); }
security_auth_login($uid);
$_COOKIE['id']=(string)$uid;
if(function_exists('tutorial_clear_progress')) tutorial_clear_progress();
unset($_SESSION['_save_confirm'],$_SESSION['_flow']['save'],$_SESSION['_tutorial_progress']);
$_SESSION['just_saved']=1;
$_SESSION['ok']='Персонаж сохранён. Полный доступ к игре открыт.';
header('Location:/bonus-kit/?saved=1'); exit;
} catch(Throwable $e) {
error_log('[SAVE_CHARACTER] uid='.$uid.' '.get_class($e).': '.$e->getMessage());
$errors[]='Не удалось завершить сохранение персонажа. Попробуйте ещё раз.';
$step='form';
}
}
}
foreach($errors as $e) echo '<div class="block center red">'.htmlspecialchars($e,ENT_QUOTES,'UTF-8').'</div>';
if($step==='confirm' && isset($_SESSION['_save_confirm'])){$p=$_SESSION['_save_confirm'];?>
<div class="save-ref"><div class="save-ref__title">Проверьте данные</div><div class="save-ref__line"></div><b>Имя персонажа:</b> <?=htmlspecialchars($p['login'],ENT_QUOTES,'UTF-8')?><br><b>Ваш пароль:</b> ••••••••<form method="post" action="/save?unic_code=<?=urlencode($unic_code)?>"><?=security_csrf_field()?><input type="hidden" name="step" value="confirm"><input type="hidden" name="confirm" value="1"><button class="old-btn" type="submit">Всё верно</button></form><form method="post" action="/save?unic_code=<?=urlencode($unic_code)?>"><?=security_csrf_field()?><input type="hidden" name="step" value="change"><button class="save-link" type="submit">Изменить данные</button></form></div>
<?php } else { ?>
<div class="title">Сохранение</div><div class="save-ref"><div class="save-ref__bonus">За регистрацию вы получите <img src="/images/icon/gold.png" alt=""> <b>100 золота!</b><br>Откроете акцию на <span class="blue">Редкий комплект</span><br><span class="green">+1600 к параметрам!</span></div><form method="post" action="/save?unic_code=<?=urlencode($unic_code)?>"><?=security_csrf_field()?><input type="hidden" name="step" value="prepare"><b>Имя персонажа:</b><br><input class="text" name="login" maxlength="20" required><br><b>Введите пароль:</b><br><input class="text" type="password" name="password" maxlength="72" required><input type="hidden" name="sex" value="<?= (int)($user['sex']??0) ?>"><input type="hidden" name="r" value="<?= (int)($user['r']??0) ?>"><br><button class="old-btn" type="submit">Сохранить</button></form></div>
<?php } echo '</div>'; include './system/f.php'; ?>