aboutsummaryrefslogtreecommitdiffstats
path: root/api/traffic-stats.php
diff options
context:
space:
mode:
authorsillylaird <sillyfanboy@gmail.com>2026-09-03 00:33:59 +0000
committersillylaird <sillyfanboy@gmail.com>2026-09-03 00:33:59 +0000
commit898b52edcb47bcb3e9d6106e74ca73e74ea01e70 (patch)
tree85c6ee5ad58b860144551184d4cf86b560c62b91 /api/traffic-stats.php
downloadwww-main.tar.gz
www-main.zip
import live www.sillylaird.ca webrootHEADmain
Diffstat (limited to 'api/traffic-stats.php')
-rw-r--r--api/traffic-stats.php95
1 files changed, 95 insertions, 0 deletions
diff --git a/api/traffic-stats.php b/api/traffic-stats.php
new file mode 100644
index 0000000..f8fd70a
--- /dev/null
+++ b/api/traffic-stats.php
@@ -0,0 +1,95 @@
+<?php
+/**
+ * Admin-only live traffic stats (JSON).
+ * Used by /admin.php?section=traffic to poll and redraw charts in realtime.
+ *
+ * Requires the shared admin session. Never stores or returns IPs.
+ */
+
+declare(strict_types=1);
+
+require_once __DIR__ . '/../partials/session.php';
+
+header('Content-Type: application/json; charset=utf-8');
+header('Cache-Control: no-store, no-cache, must-revalidate');
+header('X-Content-Type-Options: nosniff');
+header('Referrer-Policy: no-referrer');
+header('X-Robots-Tag: noindex');
+
+if (empty($_SESSION['admin'])) {
+ http_response_code(401);
+ echo json_encode(['ok' => false, 'err' => 'auth required']);
+ exit;
+}
+
+if (($_SERVER['REQUEST_METHOD'] ?? 'GET') !== 'GET') {
+ http_response_code(405);
+ echo json_encode(['ok' => false, 'err' => 'GET only']);
+ exit;
+}
+
+require_once __DIR__ . '/../partials/traffic.php';
+
+try {
+ $db = traffic_db();
+ $latestHitId = (int)$db->query('SELECT COALESCE(MAX(id), 0) FROM hits')->fetchColumn();
+ $hitCount = (int)$db->query('SELECT COUNT(*) FROM hits')->fetchColumn();
+
+ $summary = traffic_summary();
+ $analytics = traffic_analytics();
+ $top = traffic_top_pages(80);
+ $recent = traffic_recent(150);
+
+ // Normalize recent rows for the client (bool is_bot, formatted fields optional).
+ $recentOut = [];
+ foreach ($recent as $r) {
+ $isBot = !empty($r['is_bot']) || traffic_visitor_is_bot((string)($r['visitor'] ?? ''));
+ $recentOut[] = [
+ 'id' => (int)($r['id'] ?? 0),
+ 'ts' => (int)($r['ts'] ?? 0),
+ 'host' => (string)($r['host'] ?? ''),
+ 'path' => (string)($r['path'] ?? ''),
+ 'visitor' => (string)($r['visitor'] ?? ''),
+ 'lang' => (string)($r['lang'] ?? ''),
+ 'ref_host' => (string)($r['ref_host'] ?? ''),
+ 'local_count' => (int)($r['local_count'] ?? 0),
+ 'is_new' => !empty($r['is_new']) ? 1 : 0,
+ 'is_bot' => $isBot ? 1 : 0,
+ ];
+ }
+
+ $topOut = [];
+ foreach ($top as $p) {
+ $topOut[] = [
+ 'page_key' => (string)($p['page_key'] ?? ''),
+ 'host' => (string)($p['host'] ?? ''),
+ 'path' => (string)($p['path'] ?? ''),
+ 'hits' => (int)($p['hits'] ?? 0),
+ 'unique_sessions' => (int)($p['unique_sessions'] ?? 0),
+ 'bot_hits' => (int)($p['bot_hits'] ?? 0),
+ 'last_local_count' => (int)($p['last_local_count'] ?? 0),
+ 'last_visitor' => (string)($p['last_visitor'] ?? ''),
+ 'last_hit' => (int)($p['last_hit'] ?? 0),
+ ];
+ }
+
+ // Fingerprint so the client can skip redraw when nothing changed.
+ $rev = $latestHitId . ':' . $hitCount
+ . ':' . (int)($summary['total_hits'] ?? 0)
+ . ':' . (int)($summary['bot_hits'] ?? 0)
+ . ':' . (int)($summary['today'] ?? 0)
+ . ':' . (int)($summary['bot_today'] ?? 0);
+
+ echo json_encode([
+ 'ok' => true,
+ 'rev' => $rev,
+ 'server_time' => time(),
+ 'summary' => $summary,
+ 'analytics' => $analytics,
+ 'top' => $topOut,
+ 'recent' => $recentOut,
+ ], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
+} catch (Throwable $e) {
+ http_response_code(500);
+ echo json_encode(['ok' => false, 'err' => 'store failed']);
+}