aboutsummaryrefslogtreecommitdiffstats
path: root/api/traffic-stats.php
blob: f8fd70abddbbc57f305a6b7c545dc449bd21916e (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
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']);
}