Xmb forum whos online extended version

xmb forum is a free open source online community (forum) platform.

The existing whos online functionality is somewhat limited.

Enter ipinfo!

With this basic php script, forum admin can:

  • see if your forum is being visited by known good bots
  • see additional data such as ASN to block bad actors
  • view all visitors, just guests, just known good bots, sort by user name or latest visit time

Attribution given to ipinfo

<?php

define('X_SCRIPT', 'whosonlinexmb.php');
require_once 'header.php';
header('Content-Type: text/html; charset=UTF-8');

if (!X_STAFF) {
    echo "<div class='alert'>Access Denied - Staff Only</div>";
    exit;
}

// config options
// ipinfo lite offers additional unlimited data lookups, sign up for free at
// https://ipinfo.io/signup

$ipinfo_token = ''; // Add your IPInfo Lite token here, or leave blank to disable
$cache_ttl = 300; // Cache time in seconds
// $cache_dir = __DIR__ . '/cache';
$cache_dir = __DIR__ . '/../../cache'; // Change this to your cache path
@mkdir($cache_dir, 0755, true);

// Input
$view = $_GET['view'] ?? 'all';
$sort = $_GET['sort'] ?? 'time';
$clear = isset($_GET['clearcache']);

$cache_file = "$cache_dir/whosonlinexmb_{$view}_{$sort}.html";

// Cache
if (!$clear && file_exists($cache_file) && time() - filemtime($cache_file) < $cache_ttl) {
    readfile($cache_file);
    exit;
}

// DB Query
$where = '';
switch ($view) {
    case 'users':
        $where = "WHERE username != 'xguest123'";
        break;
    case 'guests':
        $where = "WHERE username = 'xguest123'";
        break;
    case 'bots':
        $where = "WHERE username = 'xguest123'";
        break;
}
$order_by = $sort === 'username' ? 'username ASC' : '`time` DESC';

$sql = "SELECT username, ip, `time`, location, invisible
        FROM " . X_PREFIX . "whosonline
        $where
        ORDER BY $order_by";

$query = $db->query($sql);

// Known Bot detection
function verify_bot($ip) {
    static $bots = [
        'Googlebot' => '/\.google(bot)?\.com$/',
        'Bingbot' => '/\.search\.msn\.com$/',
        'Applebot' => '/\.applebot\.apple\.com$/',
        'DuckDuckBot' => '/\.duckduckgo\.com$/',
        'YandexBot' => '/\.yandex\.ru$/',
        'Baiduspider' => '/\.baidu\.com$/',
    ];

    $hostname = gethostbyaddr($ip);
    $forward = gethostbyname($hostname);
    foreach ($bots as $name => $pattern) {
        if (preg_match($pattern, $hostname) && $forward === $ip) {
            return "✔ Verified $name (High Trust)";
        }
    }
    return null;
}

function ipinfo_lite($ip, $token) {
    if (!$token) return null;

    $cache_file = __DIR__ . "/cache/ipinfo_" . md5($ip) . ".json";
    if (file_exists($cache_file) && time() - filemtime($cache_file) < 86400) {
        return json_decode(file_get_contents($cache_file), true);
    }

    $url = "https://api.ipinfo.io/lite/{$ip}?token=$token";

    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT => 5,
        CURLOPT_USERAGENT => 'WhosOnlineXMBScript/1.0',
    ]);
    $response = curl_exec($ch);
    $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    if ($http_code !== 200 || !$response) {
        return null;
    }

    file_put_contents($cache_file, $response);
    return json_decode($response, true);
}

// Output
ob_start();
?>
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Who's Online</title>
    <style>
        body { font-family: sans-serif; background: #f4f4f4; padding: 20px; }
        h1 { font-size: 24px; }
        .filters a { margin-right: 10px; }
        table { width: 100%; border-collapse: collapse; background: #fff; margin-top: 10px; }
        th, td { padding: 8px; border: 1px solid #ccc; text-align: left; font-size: 14px; }
        th { background: #eee; }
        .bot { color: #d2691e; }
        .verified { color: green; }
        .ipinfo { color: #555; font-size: 12px; }
        .clearcache { float: right; }
    </style>
</head>
<body>
    <h1>Who's Online</h1>
    <div class="filters">
        <a href="?view=all">All</a> |
        <a href="?view=users">Users</a> |
        <a href="?view=guests">Guests</a> |
        <a href="?view=bots">Bots</a> |
        <a class="clearcache" href="?view=<?=htmlspecialchars($view)?>&sort=<?=htmlspecialchars($sort)?>&clearcache=1">Clear Cache</a>
    </div>
    <div class="filters">
        Sort by: 
        <a href="?view=<?=htmlspecialchars($view)?>&sort=time">Time</a> | 
        <a href="?view=<?=htmlspecialchars($view)?>&sort=username">Username</a>
    </div>
    <?php if ($ipinfo_token): ?>
    <p><em>IP data powered by ipinfo.io</em></p>
    <?php endif; ?>
    <table>
        <tr>
            <th>Username</th>
            <th>IP</th>
            <th>Time</th>
            <th>Location</th>
            <th>Status</th>
        </tr>
<?php
while ($row = $db->fetch_array($query)) {
    $ip = $row['ip'];
    $time = gmdate('Y-m-d H:i:s', $row['time'] + ($timeoffset * 3600));
    //$location = htmlspecialchars(shortenString(url_to_text($row['location'])['text'], 80));
    $location = htmlspecialchars(substr($row['location'], 0, 80));
    $is_guest = $row['username'] === 'xguest123';

    $username = $is_guest ? 'Guest' : htmlspecialchars($row['username']);
    $status = '';
    $bot_check = verify_bot($ip);
    if ($bot_check) {
        $status = '<span class="bot verified">' . $bot_check . '</span>';
    }

    if (!$bot_check && $is_guest && $view === 'bots') {
        continue; // Filtering bots only, skip normal guest
    }

    if ($ipinfo_token) {
        $info = ipinfo_lite($ip, $ipinfo_token);
        if ($info) {
            $asn = $info['asn'] ?? '';
            $asname = $info['as_name'] ?? '';
            $asdom = $info['as_domain'] ?? '';
            $country = $info['country'] ?? '';
            $ip_link = "https://ipinfo.io/$ip";
            $asn_link = $asn ? "https://ipinfo.io/$asn" : '';

            $asn_html = $asn_link
                ? "<a href='$asn_link' target='_blank' rel='noopener noreferrer'>$asn</a>"
                : htmlspecialchars($asn);

            $status .= "<br><span class='ipinfo'>"
                . "IP: <a href='$ip_link' target='_blank' rel='noopener noreferrer'>$ip</a> "
                . "| ASN: $asn_html ($asname, $asdom) - $country"
                . "</span>";

        }
    }

    echo "<tr>
        <td>$username</td>
        <td>$ip</td>
        <td>$time</td>
        <td>$location</td>
        <td>$status</td>
    </tr>";
}
?>
    </table>
</body>
</html>
<?php
$html = ob_get_clean();
file_put_contents($cache_file, $html);
echo $html;
?>

1 Like

Awesome! Thank you very much for sharing it.

Do you have any plans to bring it as a native part of the codebase: GitHub - miqrogroove/xmb: XMB eXtreme Message Board, a forum system written in PHP.