| Current Path : /home/seto/888mirror.pm/includes/ |
| Current File : /home/seto/888mirror.pm/includes/db.php |
<?php
require_once __DIR__ . '/../config.php';
function db(): PDO {
static $pdo = null;
if ($pdo === null) {
$pdo = new PDO('sqlite:' . DB_PATH);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
$pdo->exec('PRAGMA journal_mode=WAL');
$pdo->exec('PRAGMA foreign_keys=ON');
migrate($pdo);
}
return $pdo;
}
function migrate(PDO $pdo): void {
$pdo->exec("
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
login TEXT NOT NULL UNIQUE,
password_hash TEXT NOT NULL,
role TEXT NOT NULL DEFAULT 'admin'
);
CREATE TABLE IF NOT EXISTS languages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
code TEXT NOT NULL UNIQUE,
name TEXT NOT NULL,
is_active INTEGER DEFAULT 1
);
CREATE TABLE IF NOT EXISTS brands (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
slug TEXT NOT NULL UNIQUE,
logo TEXT,
tracking_url TEXT NOT NULL,
weight INTEGER DEFAULT 0,
is_featured INTEGER DEFAULT 0,
badge TEXT,
year_founded INTEGER,
status TEXT DEFAULT 'active',
created_at TEXT DEFAULT (datetime('now'))
);
CREATE TABLE IF NOT EXISTS brand_countries (
brand_id INTEGER NOT NULL REFERENCES brands(id) ON DELETE CASCADE,
country_code TEXT NOT NULL,
PRIMARY KEY (brand_id, country_code)
);
CREATE TABLE IF NOT EXISTS brand_translations (
id INTEGER PRIMARY KEY AUTOINCREMENT,
brand_id INTEGER NOT NULL REFERENCES brands(id) ON DELETE CASCADE,
language_code TEXT NOT NULL,
bonus TEXT,
free_spins TEXT,
games_count TEXT,
advantages TEXT,
license TEXT,
payment_methods TEXT,
payment_methods_count TEXT,
min_withdraw TEXT,
max_withdraw TEXT,
currencies TEXT,
generated_at TEXT,
UNIQUE (brand_id, language_code)
);
CREATE TABLE IF NOT EXISTS stats (
id INTEGER PRIMARY KEY AUTOINCREMENT,
brand_id INTEGER NOT NULL REFERENCES brands(id) ON DELETE CASCADE,
country TEXT DEFAULT '-',
language TEXT DEFAULT '-',
event_type TEXT NOT NULL,
created_at TEXT DEFAULT (datetime('now'))
);
CREATE TABLE IF NOT EXISTS settings (
key TEXT PRIMARY KEY,
value TEXT
);
CREATE TABLE IF NOT EXISTS login_attempts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
ip TEXT NOT NULL,
created_at TEXT DEFAULT (datetime('now'))
);
CREATE INDEX IF NOT EXISTS idx_stats_brand ON stats(brand_id);
CREATE INDEX IF NOT EXISTS idx_stats_date ON stats(created_at);
CREATE INDEX IF NOT EXISTS idx_stats_country ON stats(country);
CREATE INDEX IF NOT EXISTS idx_brand_status ON brands(status);
CREATE INDEX IF NOT EXISTS idx_brand_slug ON brands(slug);
");
// Таблица лендингов
$pdo->exec("
CREATE TABLE IF NOT EXISTS landings (
id INTEGER PRIMARY KEY AUTOINCREMENT,
slug TEXT NOT NULL UNIQUE,
name TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'draft',
template TEXT NOT NULL DEFAULT 'dark-premium',
geo TEXT DEFAULT '',
brand_limit INTEGER DEFAULT 0,
h1 TEXT DEFAULT '',
subtitle TEXT DEFAULT '',
cta_text TEXT DEFAULT 'Get Bonus →',
body_text TEXT DEFAULT '',
footer_text TEXT DEFAULT '',
meta_title TEXT DEFAULT '',
meta_description TEXT DEFAULT '',
created_at TEXT DEFAULT (datetime('now')),
updated_at TEXT DEFAULT (datetime('now'))
)
");
// Таблица переводов лендингов
$pdo->exec("
CREATE TABLE IF NOT EXISTS landing_translations (
id INTEGER PRIMARY KEY AUTOINCREMENT,
landing_id INTEGER NOT NULL REFERENCES landings(id) ON DELETE CASCADE,
language_code TEXT NOT NULL,
h1 TEXT DEFAULT '',
subtitle TEXT DEFAULT '',
cta_text TEXT DEFAULT '',
body_text TEXT DEFAULT '',
meta_title TEXT DEFAULT '',
meta_description TEXT DEFAULT '',
footer_text TEXT DEFAULT '',
generated_at TEXT,
UNIQUE (landing_id, language_code)
)
");
// landing_id в stats
try { $pdo->exec("ALTER TABLE stats ADD COLUMN landing_id INTEGER DEFAULT NULL"); } catch (Exception $e) {}
// subid для трекинга постбеков
try { $pdo->exec("ALTER TABLE stats ADD COLUMN subid TEXT DEFAULT NULL"); } catch (Exception $e) {}
// visit_id — уникальный ID загрузки страницы (один на все бренды одного page load)
try { $pdo->exec("ALTER TABLE stats ADD COLUMN visit_id TEXT DEFAULT NULL"); } catch (Exception $e) {}
// referrer — домен источника перехода
try { $pdo->exec("ALTER TABLE stats ADD COLUMN referrer TEXT DEFAULT NULL"); } catch (Exception $e) {}
// ip — для дедупликации кликов и показов
try { $pdo->exec("ALTER TABLE stats ADD COLUMN ip TEXT DEFAULT NULL"); } catch (Exception $e) {}
// постбек включён для бренда
try { $pdo->exec("ALTER TABLE brands ADD COLUMN postback_enabled INTEGER DEFAULT 0"); } catch (Exception $e) {}
// Таблица конверсий (постбеки)
$pdo->exec("
CREATE TABLE IF NOT EXISTS conversions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
subid TEXT NOT NULL DEFAULT '',
brand_id INTEGER,
event_type TEXT NOT NULL DEFAULT 'fd',
payout REAL DEFAULT 0,
ms_id TEXT DEFAULT '',
customer_id TEXT DEFAULT '',
dynamic TEXT DEFAULT '',
dynamic2 TEXT DEFAULT '',
tid TEXT DEFAULT '',
country TEXT DEFAULT '',
dep_sum TEXT DEFAULT '',
raw_params TEXT DEFAULT '',
created_at TEXT DEFAULT (datetime('now'))
);
CREATE INDEX IF NOT EXISTS idx_conv_subid ON conversions(subid);
CREATE INDEX IF NOT EXISTS idx_conv_brand ON conversions(brand_id);
CREATE INDEX IF NOT EXISTS idx_conv_date ON conversions(created_at);
");
// Таблица бот-запросов (устаревшая, оставлена для совместимости)
$pdo->exec("
CREATE TABLE IF NOT EXISTS bot_impressions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
ip TEXT DEFAULT '',
user_agent TEXT DEFAULT '',
path TEXT DEFAULT '',
country TEXT DEFAULT '-',
created_at TEXT DEFAULT (datetime('now'))
);
CREATE INDEX IF NOT EXISTS idx_bot_date ON bot_impressions(created_at);
");
// Единый лог доступов (реальные пользователи + боты)
$pdo->exec("
CREATE TABLE IF NOT EXISTS access_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
type TEXT NOT NULL DEFAULT 'real',
ip TEXT DEFAULT '',
country TEXT DEFAULT '-',
language TEXT DEFAULT '-',
user_agent TEXT DEFAULT '',
path TEXT DEFAULT '/',
created_at TEXT DEFAULT (datetime('now'))
);
CREATE INDEX IF NOT EXISTS idx_alog_date ON access_log(created_at);
CREATE INDEX IF NOT EXISTS idx_alog_type ON access_log(type);
CREATE INDEX IF NOT EXISTS idx_alog_country ON access_log(country);
");
// Таблица партнёрок (ПП)
$pdo->exec("
CREATE TABLE IF NOT EXISTS partners (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
type TEXT NOT NULL DEFAULT 'direct',
contact_name TEXT,
contact_telegram TEXT,
affiliate_url TEXT,
notes TEXT,
created_at TEXT DEFAULT (datetime('now'))
)
");
// Связь бренда с ПП
try { $pdo->exec("ALTER TABLE brands ADD COLUMN partner_id INTEGER DEFAULT NULL"); } catch (Exception $e) {}
// Сид: создать ПП из брендов с контактами (если ПП ещё нет)
$pp_count = (int)$pdo->query("SELECT COUNT(*) FROM partners")->fetchColumn();
if ($pp_count === 0) {
$seed_brands = $pdo->query("
SELECT id, contact_name, contact_telegram, affiliate_url
FROM brands
WHERE contact_name IS NOT NULL AND contact_name != ''
OR contact_telegram IS NOT NULL AND contact_telegram != ''
OR affiliate_url IS NOT NULL AND affiliate_url != ''
")->fetchAll(PDO::FETCH_ASSOC);
$n = 1;
foreach ($seed_brands as $sb) {
$pdo->prepare("INSERT INTO partners (name, type, contact_name, contact_telegram, affiliate_url) VALUES (?,?,?,?,?)")
->execute(['ПП ' . $n, 'direct', $sb['contact_name'], $sb['contact_telegram'], $sb['affiliate_url']]);
$pp_id = $pdo->lastInsertId();
$pdo->prepare("UPDATE brands SET partner_id = ? WHERE id = ?")->execute([$pp_id, $sb['id']]);
$n++;
}
}
// Партнёрские поля бренда
try { $pdo->exec("ALTER TABLE brands ADD COLUMN contact_name TEXT DEFAULT NULL"); } catch (Exception $e) {}
try { $pdo->exec("ALTER TABLE brands ADD COLUMN contact_telegram TEXT DEFAULT NULL"); } catch (Exception $e) {}
try { $pdo->exec("ALTER TABLE brands ADD COLUMN affiliate_url TEXT DEFAULT NULL"); } catch (Exception $e) {}
try { $pdo->exec("ALTER TABLE brands ADD COLUMN payment_models TEXT DEFAULT NULL"); } catch (Exception $e) {}
try { $pdo->exec("ALTER TABLE brands ADD COLUMN rate_cpa REAL DEFAULT NULL"); } catch (Exception $e) {}
try { $pdo->exec("ALTER TABLE brands ADD COLUMN rate_revshare REAL DEFAULT NULL"); } catch (Exception $e) {}
try { $pdo->exec("ALTER TABLE brands ADD COLUMN logo_bg TEXT DEFAULT NULL"); } catch (Exception $e) {}
// Добавить колонку group_name если ещё нет
try { $pdo->exec("ALTER TABLE languages ADD COLUMN group_name TEXT DEFAULT 'Core Markets'"); } catch (Exception $e) {}
// Сид: дефолтные языки
$count = $pdo->query("SELECT COUNT(*) FROM languages")->fetchColumn();
if ($count == 0) {
$langs = [
['en', 'English'], ['ru', 'Русский'], ['fr', 'Français'],
['de', 'Deutsch'], ['es', 'Español'], ['it', 'Italiano'],
['pt', 'Português'], ['pl', 'Polski'], ['tr', 'Türkçe'],
['uk', 'Українська'], ['kk', 'Қазақша'], ['ro', 'Română'],
['hu', 'Magyar'], ['nl', 'Nederlands'], ['cs', 'Čeština'],
];
$stmt = $pdo->prepare("INSERT INTO languages (code, name) VALUES (?, ?)");
foreach ($langs as [$code, $name]) {
$stmt->execute([$code, $name]);
}
}
// Установить group_name для существующих языков (идемпотентно)
$pdo->exec("UPDATE languages SET group_name='Core Markets' WHERE code IN ('en','es','fr','pt','ru','de','tr','it','pl','uk','nl','ro','hu','cs')");
$pdo->exec("UPDATE languages SET group_name='Caucasus & CIS' WHERE code='kk'");
// Добавить новые языки (INSERT OR IGNORE = идемпотентно)
$new_langs = [
// Nordics (активны)
['sv','Svenska', 'Nordics', 1],
['no','Norsk', 'Nordics', 1],
['da','Dansk', 'Nordics', 1],
['fi','Suomi', 'Nordics', 1],
['is','Íslenska', 'Nordics', 1],
// Balkans & SEE (неактивны)
['el','Ελληνικά', 'Balkans & SEE', 0],
['bg','Български', 'Balkans & SEE', 0],
['sr','Srpski', 'Balkans & SEE', 0],
['hr','Hrvatski', 'Balkans & SEE', 0],
['sq','Shqip', 'Balkans & SEE', 0],
['sl','Slovenščina', 'Balkans & SEE', 0],
['mk','Македонски', 'Balkans & SEE', 0],
['bs','Bosanski', 'Balkans & SEE', 0],
['me','Crnogorski', 'Balkans & SEE', 0],
// Baltics (активны)
['lt','Lietuvių', 'Baltics', 1],
['lv','Latviešu', 'Baltics', 1],
['et','Eesti', 'Baltics', 1],
// Caucasus & CIS (неактивны)
['az','Azərbaycan dili', 'Caucasus & CIS', 0],
['hy','Հայերեն', 'Caucasus & CIS', 0],
['ka','ქართული', 'Caucasus & CIS', 0],
// Asia Growth (неактивны)
['zh','中文', 'Asia Growth', 0],
['id','Bahasa Indonesia', 'Asia Growth', 0],
];
$stmt_nl = $pdo->prepare("INSERT OR IGNORE INTO languages (code, name, group_name, is_active) VALUES (?,?,?,?)");
foreach ($new_langs as $l) {
$stmt_nl->execute($l);
}
// Сид: дефолтные настройки
$pdo->exec("
INSERT OR IGNORE INTO settings (key, value) VALUES ('openai_key', '');
INSERT OR IGNORE INTO settings (key, value) VALUES ('openai_model', 'gpt-4o');
INSERT OR IGNORE INTO settings (key, value) VALUES ('site_name', '888mirror.pm');
");
// Миграция: добавить role если отсутствует
try { $pdo->exec("ALTER TABLE users ADD COLUMN role TEXT NOT NULL DEFAULT 'admin'"); } catch (\Exception $e) {}
// Сид: дефолтный admin (логин: admin, пароль: admin123 — сменить сразу!)
$exists = $pdo->query("SELECT COUNT(*) FROM users")->fetchColumn();
if ($exists == 0) {
$hash = password_hash('admin123', PASSWORD_DEFAULT);
$pdo->prepare("INSERT INTO users (login, password_hash, role) VALUES ('admin', ?, 'admin')")->execute([$hash]);
}
// Сид: пользователь yauheni (роль: limited, пароль: changeme — сменить!)
$yExists = $pdo->prepare("SELECT COUNT(*) FROM users WHERE login = 'yauheni'");
$yExists->execute();
if ((int)$yExists->fetchColumn() === 0) {
$hash = password_hash('changeme', PASSWORD_DEFAULT);
$pdo->prepare("INSERT INTO users (login, password_hash, role) VALUES ('yauheni', ?, 'limited')")->execute([$hash]);
}
// Widget stats (отдельная таблица для аналитики виджета)
$pdo->exec("
CREATE TABLE IF NOT EXISTS widget_stats (
id INTEGER PRIMARY KEY AUTOINCREMENT,
brand_id INTEGER NOT NULL,
country TEXT NOT NULL DEFAULT '-',
language TEXT NOT NULL DEFAULT '-',
event_type TEXT NOT NULL,
visit_id TEXT,
ref_host TEXT DEFAULT '',
widget_layout TEXT DEFAULT '',
created_at TEXT DEFAULT (datetime('now'))
)
");
// Widget settings
$pdo->exec("
CREATE TABLE IF NOT EXISTS widget_settings (
key TEXT PRIMARY KEY,
value TEXT NOT NULL DEFAULT ''
)
");
$pdo->exec("INSERT OR IGNORE INTO widget_settings (key, value) VALUES ('default_layout', 'table')");
$pdo->exec("INSERT OR IGNORE INTO widget_settings (key, value) VALUES ('brand_limit', '0')");
// Redirect system
$pdo->exec("
CREATE TABLE IF NOT EXISTS redirect_clicks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
brand_id INTEGER NOT NULL REFERENCES brands(id) ON DELETE CASCADE,
country TEXT NOT NULL DEFAULT '-',
source TEXT NOT NULL DEFAULT '',
subid TEXT NOT NULL DEFAULT '',
ip TEXT NOT NULL DEFAULT '',
created_at TEXT DEFAULT (datetime('now'))
);
CREATE TABLE IF NOT EXISTS redirect_settings (
country_code TEXT PRIMARY KEY,
mode TEXT NOT NULL DEFAULT 'random'
);
");
$pdo->exec("INSERT OR IGNORE INTO settings (key, value) VALUES ('redirect_mode_default', 'random')");
}
function setting(string $key): string {
$row = db()->prepare("SELECT value FROM settings WHERE key = ?");
$row->execute([$key]);
return (string)($row->fetchColumn() ?? '');
}
function setting_set(string $key, string $value): void {
db()->prepare("INSERT OR REPLACE INTO settings (key, value) VALUES (?, ?)")->execute([$key, $value]);
}