Вход Регистрация
Файл: new_top/modules/office/index.php
Строк: 860
<?php
/**
 * User profile / office — multifunctional
 */

declare(strict_types=1);

$action $_GET['action'] ?? 'dashboard';
$userId = (int)($_SESSION['user_id'] ?? 0);
$level  = (int)($_SESSION['user_level'] ?? 0);
$cfgSite Database::fetch('SELECT * FROM config WHERE id = 1') ?: [];

// ---- Auth: login / register ----
if ($userId 1) {
    
$mode = ($_GET['mode'] ?? '') === 'reg' 'reg' 'login';
    
$error '';
    if (
$_SERVER['REQUEST_METHOD'] === 'POST') {
        
csrf_verify();
        
$form $_POST['form'] ?? 'login';
        if (
$form === 'login') {
            if (!
login_throttle_check('office_login')) {
                
$error 'Слишком много попыток. Подождите 15 минут.';
            } else {
                
$login trim($_POST['login'] ?? '');
                
$pass  $_POST['password'] ?? '';
                
$user Database::fetch('SELECT * FROM user WHERE login = ? AND ban = 0', [$login]);
                if (
$user && password_verify($pass$user['password'])) {
                    
login_throttle_reset('office_login');
                    
session_regenerate_id(true);
                    
$_SESSION['user_id'] = (int)$user['id'];
                    
$_SESSION['user_level'] = (int)$user['level'];
                    
$_SESSION['user_login'] = $user['login'];
                    
Database::query('UPDATE user SET last_time = ?, ip = ? WHERE id = ?', [time(), client_ip(), $user['id']]);
                    
redirect('/?module=office');
                }
                
login_throttle_hit('office_login');
                
$error 'Неверный логин или пароль';
            }
        } elseif (
$form === 'reg') {
            if (empty(
$cfgSite['reg'])) {
                
$error 'Регистрация закрыта';
            } else {
                
$login trim($_POST['login'] ?? '');
                
$email trim($_POST['email'] ?? '');
                
$pass  $_POST['password'] ?? '';
                
$pass2 $_POST['password2'] ?? '';
                if (
mb_strlen($login) < || mb_strlen($login) > 32$error 'Логин 3–32 символа';
                elseif (!
preg_match('/^[a-zA-Z0-9_]+$/'$login)) $error 'Логин: латиница, цифры, _';
                elseif (
mb_strlen($pass) < 6$error 'Пароль не менее 6 символов';
                elseif (
$pass !== $pass2$error 'Пароли не совпадают';
                elseif (
Database::fetchColumn('SELECT id FROM user WHERE login = ?', [$login])) $error 'Логин занят';
                else {
                    
$id Database::insert('user', [
                        
'login' => $login,
                        
'password' => password_hash($passPASSWORD_DEFAULT),
                        
'email' => $email,
                        
'level' => 1,
                        
'reg_time' => time(),
                        
'last_time' => time(),
                        
'ip' => client_ip(),
                    ]);
                    
$_SESSION['user_id'] = $id;
                    
$_SESSION['user_level'] = 1;
                    
$_SESSION['user_login'] = $login;
                    
redirect('/?module=office');
                }
            }
            
$mode 'reg';
        }
    }
    
?>
    <!DOCTYPE html>
    <html lang="ru"><head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
    <?= pwa_meta() ?>
    <title><?= $mode === 'reg' 'Регистрация' 'Вход' ?></title>
    <link rel="stylesheet" href="/assets/css/style.css">
    </head><body>
    <div class="auth-wrap">
        <div class="auth-box">
            <div class="logo-lg">T</div>
            <h1><?= $mode === 'reg' 'Регистрация' 'Личный кабинет' ?></h1>
            <?php if ($error): ?><div class="alert alert-error"><?= e($error?></div><?php endif; ?>
            <?php if ($mode === 'login'): ?>
            <form method="post">
                <?= csrf_field() ?>
                <input type="hidden" name="form" value="login">
                <label>Логин</label>
                <input type="text" name="login" required autofocus autocomplete="username">
                <label>Пароль</label>
                <input type="password" name="password" required autocomplete="current-password">
                <button type="submit" class="btn btn-primary btn-block">Войти</button>
            </form>
            <?php if (!empty($cfgSite['reg'])): ?>
            <p class="text-center" style="margin-top:16px;font-size:.9rem"><a href="/?module=office&mode=reg">Создать аккаунт</a></p>
            <?php endif; ?>
            <?php else: ?>
            <form method="post">
                <?= csrf_field() ?>
                <input type="hidden" name="form" value="reg">
                <label>Логин</label>
                <input type="text" name="login" required autofocus pattern="[a-zA-Z0-9_]{3,32}">
                <label>Email</label>
                <input type="email" name="email">
                <label>Пароль</label>
                <input type="password" name="password" required minlength="6">
                <label>Повтор пароля</label>
                <input type="password" name="password2" required minlength="6">
                <button type="submit" class="btn btn-primary btn-block">Зарегистрироваться</button>
            </form>
            <p class="text-center" style="margin-top:16px;font-size:.9rem"><a href="/?module=office">Уже есть аккаунт</a></p>
            <?php endif; ?>
            <p class="text-center text-muted" style="margin-top:14px;font-size:.85rem"><a href="/">← На главную</a></p>
        </div>
    </div>
    <?= pwa_register() ?>
</body></html>
    <?php
    
exit;
}

if (
$action === 'logout') {
    
session_destroy();
    
redirect('/');
}

$base site_url();
$flash $_SESSION['flash'] ?? '';
unset(
$_SESSION['flash']);
$error '';

// ---- POST ----
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    
csrf_verify();
    
$do $_POST['do'] ?? '';
    try {
        if (
$do === 'add') {
            
$url sanitize_platform_url((string)($_POST['url'] ?? ''));
            
$msg trim($_POST['message'] ?? '');
            
$cat = (int)($_POST['cat_id'] ?? 13);
            
$needMod = !empty($cfgSite['pf_mod']) ? 0;
            if (
$url === null) {
                
$error 'Некорректный URL (только домен, например example.com)';
            } elseif (
Database::fetchColumn('SELECT id FROM platforms WHERE url = ?', [$url])) {
                
$error 'Такой адрес уже есть';
            } else {
                
$styleBig max(1min(11, (int)($_POST['style_big'] ?? 1)));
                
$styleSmall max(1min(11, (int)($_POST['style_small'] ?? 1)));
                
$id Database::insert('platforms', [
                    
'user_id' => $userId,
                    
'url' => $url,
                    
'message' => mb_substr($msg0120),
                    
'cat_id' => $cat,
                    
'mode' => $needMod,
                    
'time_add' => time(),
                    
'style_big' => $styleBig,
                    
'style_small' => $styleSmall,
                ]);
                
$_SESSION['flash'] = $needMod 'Площадка на модерации' 'Площадка добавлена';
                
redirect('/?module=office&action=code&id=' $id);
            }
        }
        if (
$do === 'edit') {
            
$id = (int)($_POST['id'] ?? 0);
            
$pf Database::fetch('SELECT * FROM platforms WHERE id = ? AND user_id = ?', [$id$userId]);
            if (!
$pf) {
                
$error 'Не найдено';
            } else {
                
$url sanitize_platform_url((string)($_POST['url'] ?? ''));
                
$msg mb_substr(trim($_POST['message'] ?? ''), 0120);
                
$cat = (int)($_POST['cat_id'] ?? 13);
                
$exists $url Database::fetchColumn('SELECT id FROM platforms WHERE url = ? AND id != ?', [$url$id]) : true;
                if (
$url === null || $exists) {
                    
$error 'URL занят или некорректен';
                } else {
                    
$styleBig max(1min(11, (int)($_POST['style_big'] ?? 1)));
                    
$styleSmall max(1min(11, (int)($_POST['style_small'] ?? 1)));
                    
Database::query('UPDATE platforms SET url = ?, message = ?, cat_id = ?, style_big = ?, style_small = ? WHERE id = ? AND user_id = ?',
                        [
$url$msg$cat$styleBig$styleSmall$id$userId]);
                    
$_SESSION['flash'] = 'Сохранено';
                    
redirect('/?module=office&action=platforms');
                }
            }
        }
        if (
$do === 'delete') {
            
$id = (int)($_POST['id'] ?? 0);
            
Database::query('DELETE FROM platforms WHERE id = ? AND user_id = ?', [$id$userId]);
            
$_SESSION['flash'] = 'Удалено';
            
redirect('/?module=office&action=platforms');
        }
        if (
$do === 'buy_gold_points') {
            
$pid = (int)($_POST['platform_id'] ?? 0);
            
$days max(1min(90, (int)($_POST['days'] ?? 7)));
            
$res Points::buyGold($userId$pid$days);
            if (
$res['ok']) {
                
$_SESSION['flash'] = 'GOLD до ' date('d.m.Y H:i', (int)$res['until']) . ' (−' . (int)$res['cost'] . ' баллов)';
            } else {
                
$error $res['error'] ?? 'Ошибка';
            }
            if (empty(
$error)) {
                
redirect('/?module=office&action=points');
            }
        }
        if (
$do === 'pay_ad_points') {
            
$adId = (int)($_POST['ad_id'] ?? 0);
            
$res Points::payAd($userId$adId);
            if (
$res['ok']) {
                
$_SESSION['flash'] = 'Реклама оплачена баллами (−' . (int)$res['cost'] . ') и активирована';
                
redirect('/?module=office&action=ads');
            }
            
$error $res['error'] ?? 'Ошибка';
        }
        if (
$do === 'buy_ad') {
            
$pos trim($_POST['position'] ?? '');
            
$priceRow Ads::price($pos);
            if (!
$priceRow) {
                
$error 'Место недоступно';
            } else {
                
$url sanitize_platform_url((string)($_POST['url'] ?? ''));
                
$name mb_substr(trim($_POST['name'] ?? ''), 064);
                
$text mb_substr(trim($_POST['text'] ?? ''), 0200);
                
$days max(1min(90, (int)($_POST['days'] ?? 7)));
                if (
$url === null || $name === '') {
                    
$error 'Укажите корректный URL и название';
                } else {
                    
$price round((float)$priceRow['price_day'] * $days2);
                    
Database::insert('ads', [
                        
'position' => $pos,
                        
'user_id' => $userId,
                        
'status' => 0,
                        
'url' => $url,
                        
'name' => $name,
                        
'text' => $text,
                        
'days' => $days,
                        
'price' => $price,
                        
'time_add' => time(),
                        
'time_end' => 0,
                        
'time_pay' => 0,
                    ]);
                    
$_SESSION['flash'] = 'Заявка отправлена. После оплаты администратор активирует рекламу. Сумма: ' $price ' ₽';
                    
redirect('/?module=office&action=ads');
                }
            }
        }
        if (
$do === 'save_styles') {
            
$id = (int)($_POST['id'] ?? 0);
            
$styleBig max(1min(11, (int)($_POST['style_big'] ?? 1)));
            
$styleSmall max(1min(11, (int)($_POST['style_small'] ?? 1)));
            
Database::query(
                
'UPDATE platforms SET style_big = ?, style_small = ? WHERE id = ? AND user_id = ?',
                [
$styleBig$styleSmall$id$userId]
            );
            
$_SESSION['flash'] = 'Стиль счётчика сохранён';
            
redirect('/?module=office&action=code&id=' $id);
        }
        if (
$do === 'toggle_hide') {
            
$id = (int)($_POST['id'] ?? 0);
            
$pf Database::fetch('SELECT hide FROM platforms WHERE id = ? AND user_id = ?', [$id$userId]);
            if (
$pf) {
                
$new = (int)$pf['hide'] ? 1;
                
Database::query('UPDATE platforms SET hide = ? WHERE id = ? AND user_id = ?', [$new$id$userId]);
                
$_SESSION['flash'] = $new 'Статистика скрыта' 'Статистика открыта';
            }
            
redirect('/?module=office&action=platforms');
        }
        if (
$do === 'profile') {
            
$email trim($_POST['email'] ?? '');
            
$pass $_POST['password'] ?? '';
            
$pass2 $_POST['password2'] ?? '';
            if (
$email !== '' && !filter_var($emailFILTER_VALIDATE_EMAIL)) {
                
$error 'Некорректный email';
            } elseif (
$pass !== '' && mb_strlen($pass) < 6) {
                
$error 'Пароль не менее 6 символов';
            } elseif (
$pass !== '' && $pass !== $pass2) {
                
$error 'Пароли не совпадают';
            } else {
                
Database::query('UPDATE user SET email = ? WHERE id = ?', [$email$userId]);
                if (
$pass !== '') {
                    
Database::query('UPDATE user SET password = ? WHERE id = ?', [password_hash($passPASSWORD_DEFAULT), $userId]);
                }
                
$_SESSION['flash'] = 'Профиль обновлён';
                
redirect('/?module=office&action=profile');
            }
        }
    } catch (
Throwable $e) {
        
$error $e->getMessage();
    }
}

function 
officeHeader(string $titlestring $active): void
{
    
$tabs = [
        
'dashboard' => 'Обзор',
        
'platforms' => 'Площадки',
        
'add'       => 'Добавить',
        
'ads'       => 'Реклама',
        
'points'    => 'Баллы',
        
'profile'   => 'Профиль',
    ];
    echo 
'<!DOCTYPE html><html lang="ru"><head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
    <?= pwa_meta() ?>
    <title>' 
e($title) . '</title>
    <link rel="stylesheet" href="/assets/css/style.css">
    <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
    </head><body><div class="app">
    <header class="topbar">
        <div class="topbar-brand"><div class="logo">T</div><span>' 
e($_SESSION['user_login'] ?? '') . '</span></div>
        <nav class="topbar-nav">
            <a href="/">Топ</a>
            <a href="/?module=office&action=logout">Выход</a>
        </nav>
    </header>
    <nav class="tabs">'
;
    foreach (
$tabs as $k => $label) {
        
$cls $k === $active 'active' '';
        echo 
'<a class="' $cls '" href="/?module=office&action=' $k '">' e($label) . '</a>';
    }
    echo 
'</nav>';
}

function 
officeFooter(): void
{
    echo 
pwa_register() . '</div></body></html>';
}

// ================= CODE =================
if ($action === 'code') {
    
$id = (int)($_GET['id'] ?? 0);
    
$pf Database::fetch('SELECT * FROM platforms WHERE id = ? AND user_id = ?', [$id$userId]);
    if (!
$pf && $level 9) {
        
redirect('/?module=office&action=platforms');
    }
    if (!
$pf$pf Database::fetch('SELECT * FROM platforms WHERE id = ?', [$id]);
    
$codes Counter::embedCodes($id$base);
    
officeHeader('Код счётчика''platforms');
    
?>
    <h1 class="page-title"><?= e($pf['url']) ?></h1>
    <p class="page-sub">HTML-код счётчика</p>
    <?php $styles Counter::availableStyles(); ?>
    <div class="card">
        <h2>Внешний вид счётчика</h2>
        <form method="post">
                <?= csrf_field() ?>
            <input type="hidden" name="do" value="save_styles">
            <input type="hidden" name="id" value="<?= (int)$id ?>">
            <label>Большой стиль</label>
            <select name="style_big">
                <?php foreach ($styles as $num => $name): ?>
                    <option value="<?= (int)$num ?><?= (int)($pf['style_big'] ?? 1) === (int)$num 'selected' '' ?>><?= (int)$num ?><?= e($name?></option>
                <?php endforeach; ?>
            </select>
            <label>Малый стиль</label>
            <select name="style_small">
                <?php foreach ($styles as $num => $name): ?>
                    <option value="<?= (int)$num ?><?= (int)($pf['style_small'] ?? 1) === (int)$num 'selected' '' ?>><?= (int)$num ?><?= e($name?></option>
                <?php endforeach; ?>
            </select>
            <button type="submit" class="btn btn-primary">Сохранить стиль</button>
        </form>
        <div class="preview-bar" style="margin-top:14px;display:flex;gap:12px;align-items:center;flex-wrap:wrap">
            <img src="/cn/big/<?= (int)$id ?>?t=<?= time() ?>" alt="big" width="88" height="31" style="border-radius:4px;background:#111">
            <img src="/cn/small/<?= (int)$id ?>?t=<?= time() ?>" alt="small" width="60" height="20" style="border-radius:4px;background:#111">
        </div>
    </div>
    <div class="card">
        <label>Большой (88×31) — HTML</label>
        <textarea readonly onclick="this.select()"><?= e($codes['big']) ?></textarea>
    </div>
    <div class="card">
        <label>Малый (60×20) — HTML</label>
        <textarea readonly onclick="this.select()"><?= e($codes['small']) ?></textarea>
    </div>
    <div class="actions">
        <a class="btn btn-ghost" href="/?module=office&action=platforms">← Площадки</a>
        <a class="btn btn-primary" href="/?module=office&action=pstats&id=<?= $id ?>">Статистика</a>
    </div>
    <?php
    officeFooter
();
    exit;
}

// ================= PLATFORM STATS =================
if ($action === 'pstats') {
    
$id = (int)($_GET['id'] ?? 0);
    
$pf Database::fetch('SELECT * FROM platforms WHERE id = ? AND user_id = ?', [$id$userId]);
    if (!
$pf) {
        
$_SESSION['flash'] = 'Площадка не найдена';
        
redirect('/?module=office&action=platforms');
    }
    
$days = (int)($_GET['days'] ?? 14);
    if (!
in_array($days, [71430], true)) {
        
$days 14;
    }
    
$hosts Stats::dailyHosts($id$days);
    
$hits Stats::dailyHits($id$days);
    
$ins Stats::dailyIn($id$days);
    
$labels Stats::shortLabels(array_keys($hosts));
    
$hourly Stats::hourlyHits($id);
    
$hourLabels array_map(static function ($h) { return sprintf('%02d'$h); }, range(023));
    
$weekday Stats::weekdayHits($id28);
    
$weekLabels = ['Пн','Вт','Ср','Чт','Пт','Сб','Вс'];
    
$weekData = [];
    for (
$i 1$i <= 7$i++) {
        
$weekData[] = (int)($weekday[$i] ?? 0);
    }
    
$online Stats::onlineCount($id);
    
$rank Stats::rankPosition($id);
    
$delta Stats::rankDelta($id);
    
$ctr Stats::ctr($id);
    
$growth Stats::hostGrowth($id);
    
$peak Stats::peakHour($id);
    
$hits24 Stats::hitsLastHours(24$id);
    
$hosts24 Stats::hostsLastHours(24$id);
    
$totals Stats::periodTotals($id$days);

    
officeHeader('Статистика''platforms');
    if (
$flash) echo '<div class="alert alert-ok">' e($flash) . '</div>';
    
?>
    <h1 class="page-title"><?= e($pf['url']) ?></h1>
    <p class="page-sub">
        Место <?= $rank '#'.$rank '—' ?>
        <?php if ($delta): ?>
            · <?= (int)$delta['delta'] >= '<span style="color:var(--accent)">↑'.(int)$delta['delta'].'</span>' '<span style="color:var(--danger)">↓'.abs((int)$delta['delta']).'</span>' ?>
        <?php endif; ?>
        · CTR <?= e((string)$ctr?>% · пик <?= sprintf('%02d'$peak['hour']) ?>:00
    </p>
    <div class="tabs">
        <a class="<?= $days===7?'active':'' ?>" href="/?module=office&action=pstats&id=<?= $id ?>&days=7">7 дн.</a>
        <a class="<?= $days===14?'active':'' ?>" href="/?module=office&action=pstats&id=<?= $id ?>&days=14">14 дн.</a>
        <a class="<?= $days===30?'active':'' ?>" href="/?module=office&action=pstats&id=<?= $id ?>&days=30">30 дн.</a>
        <a href="/?module=stats&id=<?= $id ?>">Публичная</a>
        <a href="/?module=office&action=code&id=<?= $id ?>">Код</a>
    </div>
    <div class="metrics">
        <div class="metric"><div class="m-label">Хосты</div><div class="m-val accent"><?= (int)$pf['hs'?></div></div>
        <div class="metric"><div class="m-label">Хиты</div><div class="m-val"><?= (int)$pf['ht'?></div></div>
        <div class="metric"><div class="m-label">In</div><div class="m-val gold"><?= (int)$pf['in_count'?></div></div>
        <div class="metric"><div class="m-label">Онлайн</div><div class="m-val"><?= (int)$online ?></div></div>
        <div class="metric"><div class="m-label">24ч хиты</div><div class="m-val"><?= (int)$hits24 ?></div></div>
        <div class="metric"><div class="m-label">24ч хосты</div><div class="m-val"><?= (int)$hosts24 ?></div></div>
        <div class="metric"><div class="m-label">Рост</div><div class="m-val"><?= ($growth['pct']>=0?'+':'').e((string)$growth['pct']) ?>%</div></div>
        <div class="metric"><div class="m-label">За период</div><div class="m-val"><?= (int)($totals['hits'] ?? 0?></div></div>
    </div>
    <div class="card">
        <h2>Динамика за <?= (int)$days ?> дней</h2>
        <div class="chart-box" style="height:240px"><canvas id="c1"></canvas></div>
    </div>
    <div class="card">
        <h2>Сегодня по часам</h2>
        <div class="chart-box" style="height:190px"><canvas id="cHour"></canvas></div>
    </div>
    <div class="card">
        <h2>Дни недели</h2>
        <div class="chart-box" style="height:170px"><canvas id="cWeek"></canvas></div>
    </div>
    <div class="card">
        <h2>Сводка</h2>
        <p class="text-muted" style="font-size:.9rem;line-height:1.85">
            Всего хостов: <strong style="color:var(--text)"><?= (int)$pf['hs_all'?></strong><br>
            Всего хитов: <strong style="color:var(--text)"><?= (int)$pf['ht_all'?></strong><br>
            Mobile / PC: <strong style="color:var(--text)"><?= (int)$pf['mb'?></strong> / <strong style="color:var(--text)"><?= (int)$pf['pc'?></strong><br>
            Добавлена: <?= $pf['time_add'] ? date('d.m.Y H:i', (int)$pf['time_add']) : '—' ?>
        </p>
    </div>
    <script>
    const grid='rgba(255,255,255,0.06)'; Chart.defaults.color='#8b9cb8'; Chart.defaults.borderColor=grid;
    const opt={responsive:true,maintainAspectRatio:false,plugins:{legend:{position:'top',labels:{boxWidth:12}}},scales:{x:{grid:{color:grid}},y:{beginAtZero:true,grid:{color:grid}}}};
    new Chart(document.getElementById('c1'),{type:'line',data:{labels:<?= json_encode($labelsJSON_UNESCAPED_UNICODE?>,datasets:[
        {label:'Хосты',data:<?= json_encode(array_values($hosts)) ?>,borderColor:'#2ee6b8',backgroundColor:'rgba(46,230,184,0.12)',fill:true,tension:0.35,pointRadius:2},
        {label:'Хиты',data:<?= json_encode(array_values($hits)) ?>,borderColor:'#6b9fff',backgroundColor:'rgba(107,159,255,0.1)',fill:true,tension:0.35,pointRadius:2},
        {label:'In',data:<?= json_encode(array_values($ins)) ?>,borderColor:'#ffc94a',backgroundColor:'rgba(255,201,74,0.1)',fill:true,tension:0.35,pointRadius:2}
    ]},options:opt});
    new Chart(document.getElementById('cHour'),{type:'bar',data:{labels:<?= json_encode($hourLabels?>,datasets:[{label:'Хиты',data:<?= json_encode(array_values($hourly)) ?>,backgroundColor:'rgba(107,159,255,0.7)',borderRadius:4}]},options:{...opt,plugins:{legend:{display:false}}}});
    new Chart(document.getElementById('cWeek'),{type:'bar',data:{labels:<?= json_encode($weekLabelsJSON_UNESCAPED_UNICODE?>,datasets:[{label:'Хиты',data:<?= json_encode($weekData?>,backgroundColor:'rgba(46,230,184,0.65)',borderRadius:6}]},options:{...opt,plugins:{legend:{display:false}}}});
    </script>
    <?php
    officeFooter
();
    exit;
}


if (
$action === 'dashboard') {
    
$my Database::fetchAll('SELECT * FROM platforms WHERE user_id = ? ORDER BY hs DESC', [$userId]);
    
$sumHs 0$sumHt 0;
    foreach (
$my as $p) { $sumHs += (int)$p['hs']; $sumHt += (int)$p['ht']; }
    
$userDaily = ['hosts'=>[],'hits'=>[],'in'=>[]];
    try { 
$userDaily Stats::userDaily($userId14); } catch (Throwable $e) {}
    
officeHeader('Кабинет''dashboard');
    if (
$flash) echo '<div class="alert alert-ok">' e($flash) . '</div>';
    if (
$error) echo '<div class="alert alert-error">' e($error) . '</div>';
    
?>
    <h1 class="page-title">Обзор</h1>
    <p class="page-sub">Ваша статистика</p>
    <div class="metrics">
        <div class="metric"><div class="m-label">Площадок</div><div class="m-val"><?= count($my?></div></div>
        <div class="metric"><div class="m-label">Хосты</div><div class="m-val accent"><?= number_format($sumHs0''' '?></div></div>
        <div class="metric"><div class="m-label">Хиты</div><div class="m-val"><?= number_format($sumHt0''' '?></div></div>
        <div class="metric"><div class="m-label">Уровень</div><div class="m-val gold"><?= $level >= 'Admin' 'User' ?></div></div>
    </div>
    <div class="card">
        <h2>Сводка за 14 дней</h2>
        <div class="chart-box" style="height:220px"><canvas id="dashChart"></canvas></div>
    </div>
    <script>
    (function(){
      const el=document.getElementById('dashChart'); if(!el||!window.Chart) return;
      const grid='rgba(255,255,255,0.06)';
      new Chart(el,{type:'line',data:{labels:<?= json_encode(Stats::shortLabels(array_keys($userDaily['hosts']??[])), JSON_UNESCAPED_UNICODE?>,datasets:[
        {label:'Хосты',data:<?= json_encode(array_values($userDaily['hosts']??[])) ?>,borderColor:'#2ee6b8',backgroundColor:'rgba(46,230,184,0.12)',fill:true,tension:0.35,pointRadius:2},
        {label:'Хиты',data:<?= json_encode(array_values($userDaily['hits']??[])) ?>,borderColor:'#6b9fff',backgroundColor:'rgba(107,159,255,0.1)',fill:true,tension:0.35,pointRadius:2},
        {label:'In',data:<?= json_encode(array_values($userDaily['in']??[])) ?>,borderColor:'#ffc94a',tension:0.35,pointRadius:2}
      ]},options:{responsive:true,maintainAspectRatio:false,plugins:{legend:{position:'top',labels:{boxWidth:12}}},scales:{x:{grid:{color:grid}},y:{beginAtZero:true,grid:{color:grid}}}}});
    })();
    </script>
    <?php if ($my): ?>
    <div class="card"><h2>Ваши площадки</h2>
        <?php foreach (array_slice($my05) as $p): ?>
            <div style="display:flex;justify-content:space-between;padding:10px 0;border-bottom:1px solid var(--border);gap:8px;flex-wrap:wrap">
                <div>
                    <strong><?= e($p['url']) ?></strong>
                    <?php if ((int)$p['mode']): ?><span class="badge badge-mod">MOD</span><?php endif; ?>
                    <?php if ((int)$p['ban']): ?><span class="badge badge-ban">BAN</span><?php endif; ?>
                </div>
                <div class="text-muted" style="font-size:.85rem"><?= (int)$p['hs'?> / <?= (int)$p['ht'?></div>
            </div>
        <?php endforeach; ?>
        <a href="/?module=office&action=platforms" class="btn btn-ghost btn-sm" style="margin-top:12px">Все площадки</a>
    </div>
    <?php else: ?>
    <div class="empty">
        <div class="empty-icon">➕</div>
        <div>Добавьте первую площадку</div>
        <a href="/?module=office&action=add" class="btn btn-primary" style="margin-top:14px">Добавить</a>
    </div>
    <?php endif;
    
officeFooter();
    exit;
}

// ================= PLATFORMS LIST =================
if ($action === 'platforms') {
    
$list Database::fetchAll(
        
'SELECT p.*, c.name AS cat_name FROM platforms p LEFT JOIN cat c ON c.id = p.cat_id WHERE p.user_id = ? ORDER BY p.id DESC',
        [
$userId]
    );
    
officeHeader('Площадки''platforms');
    if (
$flash) echo '<div class="alert alert-ok">' e($flash) . '</div>';
    
?>
    <h1 class="page-title">Мои площадки</h1>
    <p class="page-sub"><a href="/?module=office&action=add">+ Добавить новую</a></p>
    <?php if (!$list): ?>
        <div class="empty"><div>Пока пусто</div></div>
    <?php else: foreach ($list as $p): ?>
        <article class="rank-card" style="grid-template-columns:1fr;gap:10px">
            <div>
                <div class="rank-url"><?= e($p['url']) ?>
                    <?php if ((int)$p['gold'] > time()): ?><span class="badge-gold">★ Gold</span><?php endif; ?>
                    <?php if ((int)$p['mode']): ?><span class="badge badge-mod">MOD</span><?php endif; ?>
                    <?php if ((int)$p['ban']): ?><span class="badge badge-ban">BAN</span><?php endif; ?>
                </div>
                <div class="rank-meta">
                    <span class="chip"><?= e($p['cat_name'] ?? '—'?></span>
                    <span class="chip">hs <?= (int)$p['hs'?></span>
                    <span class="chip">ht <?= (int)$p['ht'?></span>
                </div>
            </div>
            <div class="act-btns">
                <a href="/?module=office&action=pstats&id=<?= (int)$p['id'?>">Статистика</a>
                <a href="/?module=office&action=code&id=<?= (int)$p['id'?>">Код</a>
                <a href="/?module=office&action=edit&id=<?= (int)$p['id'?>">Изменить</a>
                <form method="post" style="display:inline"><?= csrf_field() ?>
                    <input type="hidden" name="do" value="toggle_hide">
                    <input type="hidden" name="id" value="<?= (int)$p['id'?>">
                    <button type="submit"><?= (int)$p['hide'] ? 'Показать стат' 'Скрыть стат' ?></button>
                </form>
                <form method="post" style="display:inline" onsubmit="return confirm('Удалить площадку?')"><?= csrf_field() ?>
                    <input type="hidden" name="do" value="delete">
                    <input type="hidden" name="id" value="<?= (int)$p['id'?>">
                    <button class="danger" type="submit">Удалить</button>
                </form>
            </div>
        </article>
    <?php endforeach; endif;
    
officeFooter();
    exit;
}

// ================= ADD =================
if ($action === 'add') {
    
$cats Database::fetchAll('SELECT * FROM cat ORDER BY sort, id');
    
officeHeader('Добавить''add');
    if (
$error) echo '<div class="alert alert-error">' e($error) . '</div>';
    
?>
    <h1 class="page-title">Новая площадка</h1>
    <div class="card">
        <form method="post">
                <?= csrf_field() ?>
            <input type="hidden" name="do" value="add">
            <label>Адрес (без http://)</label>
            <input type="text" name="url" placeholder="example.com" required maxlength="60" inputmode="url">
            <label>Описание</label>
            <input type="text" name="message" maxlength="120" placeholder="Кратко о сайте">
            <label>Категория</label>
            <select name="cat_id">
                <?php foreach ($cats as $c): ?>
                    <option value="<?= (int)$c['id'?>"><?= e($c['name']) ?></option>
                <?php endforeach; ?>
            </select>
            <?php $styles Counter::availableStyles(); ?>
            <label>Стиль большого счётчика</label>
            <select name="style_big">
                <?php foreach ($styles as $num => $name): ?>
                    <option value="<?= (int)$num ?>"><?= (int)$num ?><?= e($name?></option>
                <?php endforeach; ?>
            </select>
            <label>Стиль малого счётчика</label>
            <select name="style_small">
                <?php foreach ($styles as $num => $name): ?>
                    <option value="<?= (int)$num ?>"><?= (int)$num ?><?= e($name?></option>
                <?php endforeach; ?>
            </select>
            <?php if (!empty($cfgSite['pf_mod'])): ?>
                <p class="text-muted" style="font-size:.85rem;margin-bottom:12px">Новые площадки проходят модерацию</p>
            <?php endif; ?>
            <button type="submit" class="btn btn-primary btn-block">Добавить</button>
        </form>
    </div>
    <?php
    officeFooter
();
    exit;
}

// ================= EDIT =================
if ($action === 'edit') {
    
$id = (int)($_GET['id'] ?? 0);
    
$pf Database::fetch('SELECT * FROM platforms WHERE id = ? AND user_id = ?', [$id$userId]);
    if (!
$pfredirect('/?module=office&action=platforms');
    
$cats Database::fetchAll('SELECT * FROM cat ORDER BY sort, id');
    
officeHeader('Редактирование''platforms');
    if (
$error) echo '<div class="alert alert-error">' e($error) . '</div>';
    
?>
    <h1 class="page-title">Изменить площадку</h1>
    <div class="card">
        <form method="post">
                <?= csrf_field() ?>
            <input type="hidden" name="do" value="edit">
            <input type="hidden" name="id" value="<?= $id ?>">
            <label>URL</label>
            <input type="text" name="url" value="<?= e($pf['url']) ?>" required>
            <label>Описание</label>
            <input type="text" name="message" value="<?= e($pf['message']) ?>" maxlength="120">
            <label>Категория</label>
            <select name="cat_id">
                <?php foreach ($cats as $c): ?>
                    <option value="<?= (int)$c['id'?><?= (int)$c['id']===(int)$pf['cat_id']?'selected':'' ?>><?= e($c['name']) ?></option>
                <?php endforeach; ?>
            </select>
            <?php $styles Counter::availableStyles(); ?>
            <label>Стиль большого счётчика</label>
            <select name="style_big">
                <?php foreach ($styles as $num => $name): ?>
                    <option value="<?= (int)$num ?><?= (int)($pf['style_big'] ?? 1)===(int)$num?'selected':'' ?>><?= (int)$num ?><?= e($name?></option>
                <?php endforeach; ?>
            </select>
            <label>Стиль малого счётчика</label>
            <select name="style_small">
                <?php foreach ($styles as $num => $name): ?>
                    <option value="<?= (int)$num ?><?= (int)($pf['style_small'] ?? 1)===(int)$num?'selected':'' ?>><?= (int)$num ?><?= e($name?></option>
                <?php endforeach; ?>
            </select>
            <button type="submit" class="btn btn-primary btn-block">Сохранить</button>
        </form>
    </div>
    <?php
    officeFooter
();
    exit;
}

// ================= PROFILE =================
if ($action === 'profile') {
    
$user Database::fetch('SELECT * FROM user WHERE id = ?', [$userId]);
    
officeHeader('Профиль''profile');
    if (
$flash) echo '<div class="alert alert-ok">' e($flash) . '</div>';
    if (
$error) echo '<div class="alert alert-error">' e($error) . '</div>';
    
?>
    <h1 class="page-title">Профиль</h1>
    <p class="page-sub">Логин: <strong><?= e($user['login'] ?? ''?></strong> · ID <?= $userId ?></p>
    <div class="metric-row">
        <div class="metric"><div class="m-label">Регистрация</div><div class="m-val blue" style="font-size:1rem"><?= !empty($user['reg_time']) ? date('d.m.Y', (int)$user['reg_time']) : '—' ?></div></div>
        <div class="metric"><div class="m-label">Последний вход</div><div class="m-val blue" style="font-size:1rem"><?= !empty($user['last_time']) ? date('d.m.Y H:i', (int)$user['last_time']) : '—' ?></div></div>
    </div>
    <div class="card">
        <h2>Настройки</h2>
        <form method="post">
                <?= csrf_field() ?>
            <input type="hidden" name="do" value="profile">
            <label>Email</label>
            <input type="email" name="email" value="<?= e($user['email'] ?? ''?>">
            <label>Новый пароль (оставьте пустым, если не меняете)</label>
            <input type="password" name="password" minlength="6" autocomplete="new-password">
            <label>Повтор пароля</label>
            <input type="password" name="password2" minlength="6" autocomplete="new-password">
            <button type="submit" class="btn btn-primary btn-block">Сохранить</button>
        </form>
    </div>
    <?php
    officeFooter
();
    exit;
}

if (
$action === 'points') {
    
$bal Points::balance($userId);
    
$hist Points::history($userId40);
    
$myPlats Database::fetchAll('SELECT id, url, gold FROM platforms WHERE user_id = ? ORDER BY id DESC', [$userId]);
    
$inReward = (int) Points::cfg('in_reward''1');
    
$goldCost = (int) Points::cfg('gold_day_cost''50');
    
officeHeader('Баллы''points');
    if (
$flash) echo '<div class="alert alert-ok">' e($flash) . '</div>';
    if (
$error) echo '<div class="alert alert-error">' e($error) . '</div>';
    
?>
    <h1 class="page-title">Баллы</h1>
    <p class="page-sub">Баланс: <strong style="color:var(--accent)"><?= (int)$bal ?></strong>
        · за уникальный переход +<?= (int)$inReward ?> · GOLD <?= (int)$goldCost ?> баллов/день</p>

    <div class="card">
        <h2>Купить GOLD за баллы</h2>
        <?php if (!$myPlats): ?>
            <p class="text-muted">Нет площадок</p>
        <?php else: ?>
        <form method="post">
            <?= csrf_field() ?>
            <input type="hidden" name="do" value="buy_gold_points">
            <label>Площадка</label>
            <select name="platform_id" required>
                <?php foreach ($myPlats as $mp): ?>
                    <option value="<?= (int)$mp['id'?>"><?= e($mp['url']) ?><?= (int)$mp['gold'] > time() ? ' (GOLD до '.date('d.m', (int)$mp['gold']).')' '' ?></option>
                <?php endforeach; ?>
            </select>
            <label>Дней (1–90)</label>
            <input type="number" name="days" value="7" min="1" max="90" required>
            <button type="submit" class="btn btn-primary btn-block">Купить GOLD</button>
        </form>
        <?php endif; ?>
    </div>

    <h2 class="page-title" style="font-size:1.05rem">История</h2>
    <?php if (!$hist): ?>
        <div class="empty"><div>Пока пусто</div></div>
    <?php else: foreach ($hist as $h): ?>
        <div style="padding:8px 0;border-bottom:1px solid var(--border);font-size:.85rem;display:flex;justify-content:space-between;gap:8px">
            <div>
                <?= e(Points::reasonLabel($h['reason'])) ?>
                <span class="text-muted"><?= date('d.m.Y H:i', (int)$h['time']) ?></span>
            </div>
            <div style="color:<?= (int)$h['delta'] >= 'var(--ok)' 'var(--danger)' ?>;font-weight:700">
                <?= (int)$h['delta'] >= '+' '' ?><?= (int)$h['delta'?>
                <span class="text-muted" style="font-weight:500">(<?= (int)$h['balance'?>)</span>
            </div>
        </div>
    <?php endforeach; endif;
    
officeFooter();
    exit;
}

if (
$action === 'ads') {
    
$prices Ads::prices();
    
$mine = [];
    try {
        
$mine Database::fetchAll(
            
'SELECT * FROM ads WHERE user_id = ? ORDER BY id DESC LIMIT 50',
            [
$userId]
        );
    } catch (
Throwable $e) {
        
$error 'Реклама ещё не настроена (нужна миграция migrate_ads.sql)';
    }
    
$positions Ads::positions();
    
officeHeader('Реклама''ads');
    if (
$flash) echo '<div class="alert alert-ok">' e($flash) . '</div>';
    if (
$error) echo '<div class="alert alert-error">' e($error) . '</div>';
    
?>
    <h1 class="page-title">Реклама</h1>
    <p class="page-sub">Покупка мест на сайте. Оплата вручную — после заявки свяжитесь с администрацией.</p>

    <div class="card">
        <h2>Заказать размещение</h2>
        <?php if (!$prices): ?>
            <p class="text-muted">Нет доступных мест</p>
        <?php else: ?>
        <form method="post">
            <?= csrf_field() ?>
            <input type="hidden" name="do" value="buy_ad">
            <label>Место</label>
            <select name="position" id="adPos" required>
                <?php foreach ($prices as $pr): ?>
                    <option value="<?= e($pr['position']) ?>" data-price="<?= e((string)$pr['price_day']) ?>">
                        <?= e($pr['title'] ?: ($positions[$pr['position']] ?? $pr['position'])) ?>
                        — <?= number_format((float)$pr['price_day'], 2'.'' '?> ₽/день
                    </option>
                <?php endforeach; ?>
            </select>
            <label>Дней (1–90)</label>
            <input type="number" name="days" id="adDays" value="7" min="1" max="90" required>
            <p class="text-muted" style="font-size:.85rem">Итого примерно: <strong id="adTotal">—</strong> ₽</p>
            <label>URL (без http://)</label>
            <input type="text" name="url" required maxlength="60" placeholder="mysite.com">
            <label>Название</label>
            <input type="text" name="name" required maxlength="64">
            <label>Краткий текст</label>
            <input type="text" name="text" maxlength="200" placeholder="Описание">
            <button type="submit" class="btn btn-primary btn-block">Отправить заявку</button>
        </form>
        <script>
        function updAdTotal(){
            var p=document.getElementById('adPos');
            var d=document.getElementById('adDays');
            var opt=p.options[p.selectedIndex];
            var price=parseFloat(opt.getAttribute('data-price')||'0');
            var days=parseInt(d.value||'0',10);
            document.getElementById('adTotal').textContent=(price*days).toFixed(2);
        }
        document.getElementById('adPos').addEventListener('change',updAdTotal);
        document.getElementById('adDays').addEventListener('input',updAdTotal);
        updAdTotal();
        </script>
        <?php endif; ?>
    </div>

    <h2 class="page-title" style="font-size:1.05rem">Мои заявки</h2>
    <?php if (!$mine): ?>
        <div class="empty"><div>Заявок пока нет</div></div>
    <?php else: foreach ($mine as $ad): ?>
        <article class="rank-card" style="grid-template-columns:1fr;gap:6px">
            <div>
                <strong><?= e($ad['name']) ?></strong>
                <span class="badge <?= (int)$ad['status']===1?'badge-ok':((int)$ad['status']===0?'badge-mod':'badge-ban'?>">
                    <?= e(Ads::statusLabel((int)$ad['status'])) ?>
                </span>
                <div class="text-muted" style="font-size:.8rem;margin-top:4px">
                    <?= e($positions[$ad['position']] ?? $ad['position']) ?>
                    · <?= e($ad['url']) ?>
                    · <?= (int)$ad['days'?> дн.
                    · <?= number_format((float)$ad['price'], 2'.'' '?> ₽
                    <?php if ((int)$ad['time_end']): ?> · до <?= date('d.m.Y', (int)$ad['time_end']) ?><?php endif; ?>
                </div>
                <?php if ((int)$ad['status'] === 0): ?>
                <form method="post" style="margin-top:8px">
                    <?= csrf_field() ?>
                    <input type="hidden" name="do" value="pay_ad_points">
                    <input type="hidden" name="ad_id" value="<?= (int)$ad['id'?>">
                    <button type="submit" class="btn btn-accent btn-sm">Оплатить баллами</button>
                </form>
                <?php endif; ?>
            </div>
        </article>
    <?php endforeach; endif;
    
officeFooter();
    exit;
}

redirect('/?module=office&action=dashboard');
Онлайн: 2
Реклама