228 lines
9.0 KiB
PHP
228 lines
9.0 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
/**
|
|
* เปิด/ปิด 10 โหมด (ห้อง) ของ Quiz Battle + กำหนดวัน-เวลาที่เล่นได้ถึง [2026-07-22]
|
|
*
|
|
* อ่าน/เขียน Game/data/quiz-battle-modes.json
|
|
* เวลาอ้างอิง = **เวลาไทย (Asia/Bangkok, UTC+7)** เก็บเป็นสตริง 'YYYY-MM-DD HH:MM' ตรงๆ
|
|
* (ไม่เก็บ UTC เพื่อให้ค่าที่แอดมินกรอกกับค่าที่เก็บตรงกันเป๊ะ อ่านไฟล์แล้วเข้าใจทันที)
|
|
*
|
|
* openUntil = '' → ไม่มีวันหมดเขต (เปิดตลอด)
|
|
*/
|
|
|
|
require __DIR__ . '/_common.php';
|
|
|
|
require_login();
|
|
require_tab('quiz-battle');
|
|
|
|
define('QB_MODES_FILE', dirname(__DIR__, 2) . '/Game/data/quiz-battle-modes.json');
|
|
define('QB_ROOM_COUNT', 10);
|
|
define('QB_TZ', 'Asia/Bangkok');
|
|
define('QB_MAX_PLAYERS_CAP', 50); /* เพดานแข็ง — จำนวนคนต่อห้องปรับได้ไม่เกินนี้ */
|
|
|
|
/** จำกัดจำนวนคนต่อห้อง 1..50 (ค่าว่าง/เพี้ยน → 50) */
|
|
function qb_clamp_max($raw): int
|
|
{
|
|
$n = (int)$raw;
|
|
if ($n < 1) {
|
|
return QB_MAX_PLAYERS_CAP;
|
|
}
|
|
return min(QB_MAX_PLAYERS_CAP, $n);
|
|
}
|
|
|
|
/** ชื่อหัวข้อของแต่ละห้อง (ให้หน้า admin โชว์ — ตรงกับรูป Room-NN.png) */
|
|
function qb_default_titles(): array
|
|
{
|
|
return [
|
|
1 => 'กฎหมายใกล้ตัว',
|
|
2 => 'กฎหมายสิทธิพื้นฐาน',
|
|
3 => 'กฎหมายจราจร',
|
|
4 => 'คดีเกี่ยวกับทรัพย์',
|
|
5 => 'คดีหมิ่นประมาท',
|
|
6 => 'คดีทางเพศ',
|
|
7 => 'คดีอาชญากรรม',
|
|
8 => 'อาชญากรรมออนไลน์',
|
|
9 => 'กระบวนการยุติธรรม',
|
|
10 => 'งานบริการกระทรวงยุติธรรม',
|
|
];
|
|
}
|
|
|
|
function qb_default_modes(): array
|
|
{
|
|
$titles = qb_default_titles();
|
|
$modes = [];
|
|
for ($i = 1; $i <= QB_ROOM_COUNT; $i++) {
|
|
$modes[(string)$i] = [
|
|
'title' => $titles[$i] ?? ('ห้อง ' . $i),
|
|
'enabled' => true,
|
|
'openUntil' => '',
|
|
'maxPlayers' => QB_MAX_PLAYERS_CAP,
|
|
];
|
|
}
|
|
return $modes;
|
|
}
|
|
|
|
/** ชื่อหัวข้อห้อง — แก้ได้ [2026-07-23] · ตัดช่องว่าง/อักขระควบคุม จำกัด 60 ตัว · ว่าง → คืน default */
|
|
function qb_clean_title($raw, string $default): string
|
|
{
|
|
if (!is_string($raw)) {
|
|
return $default;
|
|
}
|
|
$s = preg_replace('/[\x00-\x1F\x7F]+/u', ' ', $raw); // ตัดอักขระควบคุม/ขึ้นบรรทัด
|
|
$s = trim(preg_replace('/\s+/u', ' ', $s ?? ''));
|
|
if ($s === '') {
|
|
return $default;
|
|
}
|
|
if (function_exists('mb_substr')) {
|
|
$s = mb_substr($s, 0, 60, 'UTF-8');
|
|
} else {
|
|
$s = substr($s, 0, 180);
|
|
}
|
|
return $s;
|
|
}
|
|
|
|
/** 'YYYY-MM-DD HH:MM' เท่านั้น (เวลาไทย) — ค่าอื่นถือว่าไม่ได้ตั้ง */
|
|
function qb_clean_until($raw): string
|
|
{
|
|
if (!is_string($raw)) {
|
|
return '';
|
|
}
|
|
$s = trim($raw);
|
|
if ($s === '') {
|
|
return '';
|
|
}
|
|
$s = str_replace('T', ' ', $s);
|
|
if (strlen($s) === 16 && preg_match('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}$/', $s) === 1) {
|
|
[$d, $t] = explode(' ', $s, 2);
|
|
[$y, $m, $dd] = array_map('intval', explode('-', $d));
|
|
if (checkdate($m, $dd, $y)) {
|
|
[$hh, $mm] = array_map('intval', explode(':', $t));
|
|
if ($hh >= 0 && $hh <= 23 && $mm >= 0 && $mm <= 59) {
|
|
return $s;
|
|
}
|
|
}
|
|
}
|
|
return '';
|
|
}
|
|
|
|
function qb_read_modes(): array
|
|
{
|
|
$out = ['version' => 1, 'updatedAt' => '', 'modes' => qb_default_modes()];
|
|
$raw = @file_get_contents(QB_MODES_FILE);
|
|
if ($raw === false || $raw === '') {
|
|
return $out;
|
|
}
|
|
$j = json_decode($raw, true);
|
|
if (!is_array($j) || !isset($j['modes']) || !is_array($j['modes'])) {
|
|
return $out;
|
|
}
|
|
$titles = qb_default_titles();
|
|
foreach ($out['modes'] as $k => $def) {
|
|
$src = $j['modes'][$k] ?? null;
|
|
if (!is_array($src)) {
|
|
continue;
|
|
}
|
|
$out['modes'][$k] = [
|
|
/* [2026-07-23] หัวข้อแก้ได้แล้ว — ใช้ค่าที่เก็บไว้ ถ้าว่าง fallback เป็น default ของห้องนั้น */
|
|
'title' => qb_clean_title($src['title'] ?? '', $titles[(int)$k] ?? $def['title']),
|
|
'enabled' => !isset($src['enabled']) || (bool)$src['enabled'],
|
|
'openUntil' => qb_clean_until($src['openUntil'] ?? ''),
|
|
'maxPlayers' => qb_clamp_max($src['maxPlayers'] ?? QB_MAX_PLAYERS_CAP),
|
|
];
|
|
}
|
|
$out['updatedAt'] = is_string($j['updatedAt'] ?? null) ? $j['updatedAt'] : '';
|
|
return $out;
|
|
}
|
|
|
|
function qb_write_modes(array $data): bool
|
|
{
|
|
$dir = dirname(QB_MODES_FILE);
|
|
if (!is_dir($dir) && !@mkdir($dir, 0775, true) && !is_dir($dir)) {
|
|
return false;
|
|
}
|
|
$tmp = QB_MODES_FILE . '.tmp';
|
|
$json = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
|
if ($json === false || @file_put_contents($tmp, $json, LOCK_EX) === false) {
|
|
return false;
|
|
}
|
|
/* เขียน tmp แล้ว rename — กันไฟล์พังครึ่งทางถ้า Node อ่านพอดีจังหวะ */
|
|
if (!@rename($tmp, QB_MODES_FILE)) {
|
|
@unlink($tmp);
|
|
return false;
|
|
}
|
|
@chmod(QB_MODES_FILE, 0664);
|
|
return true;
|
|
}
|
|
|
|
/** สถานะที่คำนวณแล้ว ณ เวลาไทยตอนนี้ — ให้หน้า admin โชว์ว่าห้องไหน "ปิดไปแล้ว" */
|
|
function qb_state(array $mode): array
|
|
{
|
|
$tz = new DateTimeZone(QB_TZ);
|
|
$now = new DateTimeImmutable('now', $tz);
|
|
$until = $mode['openUntil'] ?? '';
|
|
$expired = false;
|
|
if ($until !== '') {
|
|
$end = DateTimeImmutable::createFromFormat('Y-m-d H:i', $until, $tz);
|
|
$expired = ($end !== false) && ($now > $end);
|
|
}
|
|
return [
|
|
'open' => (bool)($mode['enabled'] ?? true) && !$expired,
|
|
'expired' => $expired,
|
|
];
|
|
}
|
|
|
|
$method = $_SERVER['REQUEST_METHOD'];
|
|
|
|
if ($method === 'GET') {
|
|
$data = qb_read_modes();
|
|
$tz = new DateTimeZone(QB_TZ);
|
|
$now = new DateTimeImmutable('now', $tz);
|
|
foreach ($data['modes'] as $k => $m) {
|
|
$data['modes'][$k] = array_merge($m, qb_state($m));
|
|
}
|
|
$data['nowThai'] = $now->format('Y-m-d H:i');
|
|
$data['tz'] = QB_TZ;
|
|
json_response(['ok' => true] + $data);
|
|
}
|
|
|
|
if ($method === 'PUT' || $method === 'POST') {
|
|
$body = require_json_body();
|
|
$src = $body['modes'] ?? null;
|
|
if (!is_array($src)) {
|
|
json_response(['ok' => false, 'error' => 'ต้องส่ง modes มาด้วย'], 400);
|
|
}
|
|
$cur = qb_read_modes();
|
|
$next = $cur['modes'];
|
|
foreach ($next as $k => $def) {
|
|
$m = $src[$k] ?? null;
|
|
if (!is_array($m)) {
|
|
continue;
|
|
}
|
|
$next[$k] = [
|
|
/* [2026-07-23] หัวข้อแก้ได้ — sanitize + ถ้าไม่ส่ง/ว่าง คงค่าเดิม (ไม่ reset เป็น default) */
|
|
'title' => qb_clean_title($m['title'] ?? ($def['title'] ?? ''), $def['title'] ?? ('ห้อง ' . $k)),
|
|
'enabled' => !isset($m['enabled']) || (bool)$m['enabled'],
|
|
'openUntil' => qb_clean_until($m['openUntil'] ?? ''),
|
|
/* [2026-07-23] จำนวนคนต่อห้อง — ถ้าไม่ส่งมา คงค่าเดิมไว้ (ไม่ reset เป็น 50) */
|
|
'maxPlayers' => qb_clamp_max($m['maxPlayers'] ?? ($def['maxPlayers'] ?? QB_MAX_PLAYERS_CAP)),
|
|
];
|
|
}
|
|
$me = current_admin();
|
|
$payload = [
|
|
'version' => 1,
|
|
'updatedAt' => (new DateTimeImmutable('now', new DateTimeZone(QB_TZ)))->format('Y-m-d H:i'),
|
|
'updatedBy' => $me['username'] ?? '',
|
|
'tz' => QB_TZ,
|
|
'modes' => $next,
|
|
];
|
|
if (!qb_write_modes($payload)) {
|
|
json_response(['ok' => false, 'error' => 'บันทึกไม่สำเร็จ (สิทธิ์เขียนไฟล์?)'], 500);
|
|
}
|
|
foreach ($payload['modes'] as $k => $m) {
|
|
$payload['modes'][$k] = array_merge($m, qb_state($m));
|
|
}
|
|
json_response(['ok' => true, 'message' => 'บันทึกแล้ว'] + $payload);
|
|
}
|
|
|
|
json_response(['ok' => false, 'error' => 'Use GET / PUT'], 405);
|