318 lines
10 KiB
PHP
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];
|
|
}
|