exec("CREATE TABLE IF NOT EXISTS analytics_events ( id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT, ts INT UNSIGNED NOT NULL, day DATE NOT NULL, type VARCHAR(16) NOT NULL, sid CHAR(32) NOT NULL DEFAULT '', path VARCHAR(160) NOT NULL DEFAULT '', ref_host VARCHAR(120) NOT NULL DEFAULT '', src VARCHAR(40) NOT NULL DEFAULT '', device VARCHAR(8) NOT NULL DEFAULT '', bot TINYINT NOT NULL DEFAULT 0, from_code VARCHAR(10) NOT NULL DEFAULT '', to_code VARCHAR(10) NOT NULL DEFAULT '', train_no VARCHAR(10) NOT NULL DEFAULT '', jdate DATE NULL, n INT NOT NULL DEFAULT 0, note VARCHAR(60) NOT NULL DEFAULT '', ip_hash CHAR(16) NOT NULL DEFAULT '' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"); /* ---------------- filters ---------------- */ $today = new DateTime('now', new DateTimeZone('Asia/Kolkata')); $preset = $_GET['preset'] ?? '30'; $to = $_GET['to'] ?? $today->format('Y-m-d'); $from = $_GET['from'] ?? (clone $today)->modify('-29 day')->format('Y-m-d'); if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $from)) $from = (clone $today)->modify('-29 day')->format('Y-m-d'); if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $to)) $to = $today->format('Y-m-d'); if (isset($_GET['preset']) && !isset($_GET['from'])) { $to = $today->format('Y-m-d'); if ($preset === 'today') $from = $to; elseif ($preset === '7') $from = (clone $today)->modify('-6 day')->format('Y-m-d'); elseif ($preset === '30') $from = (clone $today)->modify('-29 day')->format('Y-m-d'); elseif ($preset === 'all') { $from = '2026-01-01'; } } $incbots = !empty($_GET['incbots']) ? 1 : 0; $dev = in_array($_GET['dev'] ?? '', ['mobile', 'desktop', 'bot'], true) ? $_GET['dev'] : ''; $srcF = isset($_GET['src']) ? substr(preg_replace('/[^0-9A-Za-z_\-]/', '', $_GET['src']), 0, 40) : ''; $w = ["day BETWEEN ? AND ?"]; $p = [$from, $to]; if (!$incbots) { $w[] = "bot = 0"; } if ($dev !== '') { $w[] = "device = ?"; $p[] = $dev; } if ($srcF !== '') { $w[] = "src = ?"; $p[] = $srcF; } $W = implode(' AND ', $w); function q(PDO $pdo, string $sql, array $p): array { $st = $pdo->prepare($sql); $st->execute($p); return $st->fetchAll(PDO::FETCH_ASSOC); } function names(PDO $pdo, string $table, string $keyCol, string $nameCol, array $keys): array { $keys = array_values(array_unique(array_filter($keys))); if (!$keys) return []; $in = implode(',', array_fill(0, count($keys), '?')); $st = $pdo->prepare("SELECT $keyCol k, $nameCol nm FROM $table WHERE $keyCol IN ($in)"); $st->execute($keys); $m = []; foreach ($st as $r) $m[$r['k']] = $r['nm']; return $m; } /* ---------------- metrics ---------------- */ $sum = q($pdo, "SELECT COALESCE(SUM(type='pageview'),0) pv, COUNT(DISTINCT CASE WHEN type='pageview' THEN sid END) uv, COALESCE(SUM(type='search'),0) s, COALESCE(SUM(type='availability'),0) a, COALESCE(SUM(type='pnr'),0) pnr FROM analytics_events WHERE $W", $p)[0] ?? ['pv'=>0,'uv'=>0,'s'=>0,'a'=>0,'pnr'=>0]; $daily = q($pdo, "SELECT day, COALESCE(SUM(type='pageview'),0) pv, COUNT(DISTINCT CASE WHEN type='pageview' THEN sid END) uv, COALESCE(SUM(type='search'),0) s, COALESCE(SUM(type='availability'),0) a, COALESCE(SUM(type='pnr'),0) pnr FROM analytics_events WHERE $W GROUP BY day ORDER BY day", $p); $routes = q($pdo, "SELECT from_code, to_code, COUNT(*) c FROM analytics_events WHERE $W AND type='search' AND from_code<>'' AND to_code<>'' GROUP BY from_code, to_code ORDER BY c DESC LIMIT 25", $p); $origins = q($pdo, "SELECT from_code k, COUNT(*) c FROM analytics_events WHERE $W AND type='search' AND from_code<>'' GROUP BY from_code ORDER BY c DESC LIMIT 12", $p); $dests = q($pdo, "SELECT to_code k, COUNT(*) c FROM analytics_events WHERE $W AND type='search' AND to_code<>'' GROUP BY to_code ORDER BY c DESC LIMIT 12", $p); $trains = q($pdo, "SELECT train_no k, COUNT(*) c FROM analytics_events WHERE $W AND type='availability' AND train_no<>'' GROUP BY train_no ORDER BY c DESC LIMIT 15", $p); $sources = q($pdo, "SELECT CASE WHEN src='' THEN '(direct / app)' ELSE src END src, COUNT(DISTINCT sid) uv, COUNT(*) hits FROM analytics_events WHERE $W AND type='pageview' GROUP BY src ORDER BY uv DESC LIMIT 15", $p); $referrers = q($pdo, "SELECT ref_host, COUNT(DISTINCT sid) uv FROM analytics_events WHERE $W AND type='pageview' AND ref_host<>'' GROUP BY ref_host ORDER BY uv DESC LIMIT 12", $p); $devices = q($pdo, "SELECT device, COUNT(DISTINCT sid) uv, COUNT(*) hits FROM analytics_events WHERE $W AND type='pageview' GROUP BY device ORDER BY uv DESC", $p); $stCodes = []; foreach ($routes as $r) { $stCodes[] = $r['from_code']; $stCodes[] = $r['to_code']; } foreach ($origins as $r) $stCodes[] = $r['k']; foreach ($dests as $r) $stCodes[] = $r['k']; $stName = names($pdo, 'stations', 'code', 'name', $stCodes); $trName = names($pdo, 'trains', 'train_no', 'train_name', array_column($trains, 'k')); function sn(array $m, string $c): string { return ($m[$c] ?? '') !== '' ? $m[$c] . " ($c)" : $c; } $maxPv = 1; foreach ($daily as $d) $maxPv = max($maxPv, (int) $d['pv']); $pvN = (int) $sum['pv']; $uvN = (int) $sum['uv']; $sN = (int) $sum['s']; $aN = (int) $sum['a']; $pnrN = (int) $sum['pnr']; $spv = $uvN ? round($sN / $uvN, 2) : 0; $aps = $sN ? round($aN / $sN * 100) : 0; function h($v): string { return htmlspecialchars((string) $v, ENT_QUOTES, 'UTF-8'); } function qs(array $over): string { $base = ['from' => $_GET['from'] ?? null, 'to' => $_GET['to'] ?? null, 'preset' => $_GET['preset'] ?? null, 'incbots' => $_GET['incbots'] ?? null, 'dev' => $_GET['dev'] ?? null, 'src' => $_GET['src'] ?? null]; $m = array_merge($base, $over); $m = array_filter($m, fn($v) => $v !== null && $v !== ''); return '?' . http_build_query($m); } $page_title = 'Analytics'; require APP_DIR . '/header.php'; ?>

Analytics

Visitors
unique sessions
Pageviews
per visitor
Searches
per visitor
Availability checks
per 100 searches
PNR checks
this period

Daily trend — pageviews

No data in this range yet.

Day-by-day

DateVisitorsPageviewsSearchesAvail.PNR
No data.

Top routes searched

FromToSearches
No searches yet.

Top origin stations

StationSearches

Top destination stations

StationSearches

Most-checked trains (availability)

TrainChecks
No availability checks yet.

Traffic sources

SourceVisitorsViews

Tip: add ?src=wa to the URL in your WhatsApp message to see that campaign here.

Referrers & devices

ReferrerVisitors
— (direct/app)
DeviceVisitorsViews