Files
justice/www/html/Admin/api/_player_log.php
T
2026-07-31 03:36:58 +00:00

318 lines
10 KiB
PHP

<?php
declare(strict_types=1);
require_once __DIR__ . '/_player_account.php';
define('PLAYER_LOG_FILE', ADMIN_PRIVATE_DIR . '/player-logs.json');
define('PLAYER_LOG_MAX_EVENTS', 2000);
define('PLAYER_LOG_SESSION_GAP_SEC', 1800); // 30 นาที = session ใหม่
function player_log_default(): array
{
return [
'version' => 1,
'stats' => [
'totalEvents' => 0,
'totalVisits' => 0,
'uniquePlayers' => 0,
'visitsToday' => 0,
'todayKey' => '',
],
'players' => [],
'events' => [],
];
}
function player_log_read(): array
{
if (!is_file(PLAYER_LOG_FILE)) {
return player_log_default();
}
$raw = @file_get_contents(PLAYER_LOG_FILE);
$j = json_decode($raw ?: '{}', true);
if (!is_array($j)) {
return player_log_default();
}
return array_replace_recursive(player_log_default(), $j);
}
function player_log_write(array $data): bool
{
if (!is_dir(ADMIN_PRIVATE_DIR)) {
if (!@mkdir(ADMIN_PRIVATE_DIR, 0750, true)) {
return false;
}
}
$tmp = PLAYER_LOG_FILE . '.tmp.' . bin2hex(random_bytes(4));
$json = json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
if ($json === false) {
return false;
}
if (file_put_contents($tmp, $json, LOCK_EX) === false) {
return false;
}
if (!rename($tmp, PLAYER_LOG_FILE)) {
@unlink($tmp);
return false;
}
return true;
}
function player_log_today_key(): string
{
$tz = new DateTimeZone('Asia/Bangkok');
return (new DateTimeImmutable('now', $tz))->format('Y-m-d');
}
function player_log_reset_today_if_needed(array &$log): void
{
$today = player_log_today_key();
if (($log['stats']['todayKey'] ?? '') !== $today) {
$log['stats']['todayKey'] = $today;
$log['stats']['visitsToday'] = 0;
}
}
function player_log_allowed_events(): array
{
return [
'login_guest' => true,
'login_google' => true,
'login_facebook' => true,
'lobby_enter' => true,
'room_enter' => true,
'game_enter' => true,
'quiz_battle_enter' => true,
'logout' => true,
];
}
function player_log_sanitize_name(string $raw): string
{
$s = trim($raw);
$s = preg_replace('/[\x00-\x1f\x7f]/u', '', $s) ?? '';
if (function_exists('mb_substr')) {
return mb_substr($s, 0, 32);
}
return substr($s, 0, 32);
}
function player_log_sanitize_page(string $raw): string
{
$s = trim($raw);
if (function_exists('mb_substr')) {
return mb_substr($s, 0, 180);
}
return substr($s, 0, 180);
}
/** event ที่นับเป็น "การเข้าใช้งาน" */
function player_log_counts_as_visit(string $event): bool
{
return isset([
'login_guest' => true,
'login_google' => true,
'login_facebook' => true,
'lobby_enter' => true,
'room_enter' => true,
'game_enter' => true,
][$event]);
}
function player_log_track(
string $playerKey,
string $event,
string $displayName = '',
string $loginType = 'guest',
string $page = '',
array $meta = []
): array {
$allowed = player_log_allowed_events();
if (!isset($allowed[$event])) {
return ['ok' => false, 'error' => 'unknown event'];
}
if (!valid_player_key($playerKey)) {
return ['ok' => false, 'error' => 'invalid playerKey'];
}
$lt = in_array($loginType, ['guest', 'google', 'facebook', 'email'], true) ? $loginType : 'guest';
$name = player_log_sanitize_name($displayName);
$page = player_log_sanitize_page($page);
$now = gmdate('c');
$nowTs = time();
$log = player_log_read();
player_log_reset_today_if_needed($log);
if (!isset($log['players']) || !is_array($log['players'])) {
$log['players'] = [];
}
if (!isset($log['events']) || !is_array($log['events'])) {
$log['events'] = [];
}
$isNewPlayer = !isset($log['players'][$playerKey]);
if ($isNewPlayer) {
$log['players'][$playerKey] = [
'playerKey' => $playerKey,
'displayName' => $name !== '' ? $name : 'ผู้เล่น',
'loginType' => $lt,
'visitCount' => 0,
'eventCount' => 0,
'firstSeenAt' => $now,
'lastSeenAt' => $now,
'lastVisitAt' => null,
'lastEvent' => $event,
'lastPage' => $page,
];
$log['stats']['uniquePlayers'] = count($log['players']);
}
$p = &$log['players'][$playerKey];
if ($name !== '') {
$p['displayName'] = $name;
}
$p['loginType'] = $lt;
$p['lastSeenAt'] = $now;
$p['lastEvent'] = $event;
if ($page !== '') {
$p['lastPage'] = $page;
}
$p['eventCount'] = max(0, (int) ($p['eventCount'] ?? 0)) + 1;
$countVisit = false;
if (player_log_counts_as_visit($event)) {
$lastVisitAt = isset($p['lastVisitAt']) ? strtotime((string) $p['lastVisitAt']) : false;
if ($lastVisitAt === false || ($nowTs - $lastVisitAt) >= PLAYER_LOG_SESSION_GAP_SEC) {
$countVisit = true;
$p['visitCount'] = max(0, (int) ($p['visitCount'] ?? 0)) + 1;
$p['lastVisitAt'] = $now;
$log['stats']['totalVisits'] = max(0, (int) ($log['stats']['totalVisits'] ?? 0)) + 1;
$log['stats']['visitsToday'] = max(0, (int) ($log['stats']['visitsToday'] ?? 0)) + 1;
}
}
$log['stats']['totalEvents'] = max(0, (int) ($log['stats']['totalEvents'] ?? 0)) + 1;
$ev = [
'id' => bin2hex(random_bytes(8)),
'at' => $now,
'playerKey' => $playerKey,
'displayName' => (string) ($p['displayName'] ?? ''),
'loginType' => $lt,
'event' => $event,
'page' => $page,
'meta' => is_array($meta) ? $meta : [],
'countedVisit' => $countVisit,
];
array_unshift($log['events'], $ev);
if (count($log['events']) > PLAYER_LOG_MAX_EVENTS) {
$log['events'] = array_slice($log['events'], 0, PLAYER_LOG_MAX_EVENTS);
}
if (!player_log_write($log)) {
return ['ok' => false, 'error' => 'บันทึก log ไม่สำเร็จ'];
}
return [
'ok' => true,
'countedVisit' => $countVisit,
'visitCount' => (int) ($p['visitCount'] ?? 0),
];
}
function player_log_public_summary(array $log): array
{
player_log_reset_today_if_needed($log);
return [
'totalEvents' => max(0, (int) ($log['stats']['totalEvents'] ?? 0)),
'totalVisits' => max(0, (int) ($log['stats']['totalVisits'] ?? 0)),
'uniquePlayers' => max(0, (int) ($log['stats']['uniquePlayers'] ?? count($log['players'] ?? []))),
'visitsToday' => max(0, (int) ($log['stats']['visitsToday'] ?? 0)),
'todayKey' => (string) ($log['stats']['todayKey'] ?? player_log_today_key()),
'eventCap' => PLAYER_LOG_MAX_EVENTS,
'storedEvents' => count($log['events'] ?? []),
];
}
function player_log_players_list(array $log, string $search = '', int $limit = 200): array
{
$rows = [];
foreach ($log['players'] ?? [] as $p) {
if (!is_array($p)) {
continue;
}
$key = (string) ($p['playerKey'] ?? '');
if ($key === '') {
continue;
}
if ($search !== '') {
$hay = strtolower($key . ' ' . ($p['displayName'] ?? '') . ' ' . ($p['loginType'] ?? ''));
if (strpos($hay, strtolower($search)) === false) {
continue;
}
}
$rows[] = [
'playerKey' => $key,
'displayName' => (string) ($p['displayName'] ?? 'ผู้เล่น'),
'loginType' => (string) ($p['loginType'] ?? 'guest'),
'visitCount' => max(0, (int) ($p['visitCount'] ?? 0)),
'eventCount' => max(0, (int) ($p['eventCount'] ?? 0)),
'firstSeenAt' => (string) ($p['firstSeenAt'] ?? ''),
'lastSeenAt' => (string) ($p['lastSeenAt'] ?? ''),
'lastVisitAt' => (string) ($p['lastVisitAt'] ?? ''),
'lastEvent' => (string) ($p['lastEvent'] ?? ''),
'lastPage' => (string) ($p['lastPage'] ?? ''),
];
}
usort($rows, static function ($a, $b) {
return strcmp((string) ($b['lastSeenAt'] ?? ''), (string) ($a['lastSeenAt'] ?? ''));
});
if ($limit > 0 && count($rows) > $limit) {
$rows = array_slice($rows, 0, $limit);
}
return $rows;
}
function player_log_events_list(array $log, array $opts = []): array
{
$search = trim((string) ($opts['search'] ?? ''));
$event = trim((string) ($opts['event'] ?? ''));
$playerKey = trim((string) ($opts['playerKey'] ?? ''));
$limit = max(1, min(500, (int) ($opts['limit'] ?? 100)));
$offset = max(0, (int) ($opts['offset'] ?? 0));
$rows = [];
foreach ($log['events'] ?? [] as $ev) {
if (!is_array($ev)) {
continue;
}
if ($event !== '' && ($ev['event'] ?? '') !== $event) {
continue;
}
if ($playerKey !== '' && ($ev['playerKey'] ?? '') !== $playerKey) {
continue;
}
if ($search !== '') {
$hay = strtolower(
($ev['playerKey'] ?? '') . ' ' .
($ev['displayName'] ?? '') . ' ' .
($ev['event'] ?? '') . ' ' .
($ev['page'] ?? '')
);
if (strpos($hay, strtolower($search)) === false) {
continue;
}
}
$rows[] = $ev;
}
$total = count($rows);
if ($offset > 0) {
$rows = array_slice($rows, $offset);
}
if (count($rows) > $limit) {
$rows = array_slice($rows, 0, $limit);
}
return ['rows' => $rows, 'total' => $total];
}