Files
justice/www/html/Admin/api/game-quiz-settings.php
T
2026-05-02 06:51:21 +00:00

238 lines
11 KiB
PHP

<?php
declare(strict_types=1);
/**
* Proxy ค่าคำถาม quiz-settings ระหว่างแผง Admin กับ Node (Game/server.js)
* แก้กรณี nginx ส่ง /Game/ เป็น static แล้ว PUT ไม่ถึง Node — เบราว์เซอร์เรียก PHP แทน
*
* รองรับ Node เวอร์ชันเก่าที่ GET ไม่ส่ง battleQuizMcq / PUT เขียนทับไฟล์แล้วตัดฟิลด์:
* - GET: ดึงจากดิสก์มาแปะทับบางฟิลด์ (battleQuizMcq + carry*) เพราะหลัง PUT เรา merge ลงไฟล์ใต้ www เสมอ — Node อาจอ่านคนละ path กับ PHP
* - PUT: merge body ลง quiz-settings.json บนดิสก์เสมอ (หลัง proxy Node) เพื่อให้ข้อมูลคงอยู่หลังรีเฟรช
*/
require __DIR__ . '/_common.php';
require_login();
if (!function_exists('curl_init')) {
json_response(['ok' => false, 'error' => 'ต้องเปิด PHP extension curl (ติดตั้งแพ็กเกจ php-curl แล้วรีสตาร์ท PHP-FPM)'], 500);
}
$port = preg_replace('/[^0-9]/', '', (string)(getenv('GAME_NODE_PORT') ?: '3001')) ?: '3001';
$host = getenv('GAME_NODE_INTERNAL_HOST') ?: '127.0.0.1';
$target = 'http://' . $host . ':' . $port . '/Game/api/quiz-settings';
function quiz_settings_disk_path(): string
{
return dirname(__DIR__, 2) . '/Game/data/quiz-settings.json';
}
/**
* รวมฟิลด์ที่ส่งมาใน PUT เข้ากับไฟล์บนดิสก์ (ไม่ทับฟิลด์ที่ไม่ได้ส่งมา)
*/
function merge_quiz_settings_disk_from_put(string $rawBody): bool
{
$patch = json_decode($rawBody, true);
if (!is_array($patch)) {
return false;
}
$path = quiz_settings_disk_path();
$base = [];
$hadFileButInvalidJson = false;
if (is_file($path)) {
$prev = @file_get_contents($path);
if ($prev !== false && $prev !== '') {
$d = json_decode($prev, true);
if (is_array($d)) {
$base = $d;
} else {
$hadFileButInvalidJson = true;
}
}
}
if ($hadFileButInvalidJson) {
return false;
}
$mergeKeys = ['readMs', 'answerMs', 'betweenMs', 'carryReadMs', 'carryAnswerMs', 'carrySessionLength', 'carryMapPanelTheme', 'quizMapPanelTheme', 'carryEmbedCountdownTheme', 'carryChoicePlaqueTheme', 'carryChoicePlaqueThemes', 'carryChoicePlaqueMapScale', 'carryWalkSpeedMultForMapId', 'carryWalkSpeedMult', 'quizRoundQuestionCount', 'questions', 'carryQuestions', 'battleQuizMcq'];
foreach ($mergeKeys as $k) {
if (array_key_exists($k, $patch)) {
$base[$k] = $patch[$k];
}
}
$dir = dirname($path);
if (!is_dir($dir)) {
if (!@mkdir($dir, 0755, true)) {
return false;
}
}
$json = json_encode($base, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
if ($json === false) {
return false;
}
return @file_put_contents($path, $json) !== false;
}
/**
* แปะค่าจาก quiz-settings.json ใต้ docroot ทับ JSON จาก Node — ให้แอดมินรีเฟรชแล้วเห็นค่าที่ merge หลัง PUT (กันคนละไฟล์กับ Node)
* Overlay keys from on-disk quiz-settings.json over Node JSON — Admin refresh matches post-PUT merge (fixes Node vs disk path mismatch).
*/
function overlay_quiz_settings_from_disk(string $nodeBody): string
{
$j = json_decode($nodeBody, true);
if (!is_array($j)) {
return $nodeBody;
}
$path = quiz_settings_disk_path();
if (!is_file($path)) {
return $nodeBody;
}
$raw = @file_get_contents($path);
if ($raw === false || $raw === '') {
return $nodeBody;
}
$disk = json_decode($raw, true);
if (!is_array($disk)) {
return $nodeBody;
}
if (array_key_exists('battleQuizMcq', $disk)) {
$j['battleQuizMcq'] = $disk['battleQuizMcq'];
}
if (array_key_exists('carryMapPanelTheme', $disk) && is_array($disk['carryMapPanelTheme'])) {
$j['carryMapPanelTheme'] = $disk['carryMapPanelTheme'];
}
if (array_key_exists('quizMapPanelTheme', $disk) && is_array($disk['quizMapPanelTheme'])) {
$j['quizMapPanelTheme'] = $disk['quizMapPanelTheme'];
}
if (array_key_exists('carryEmbedCountdownTheme', $disk) && is_array($disk['carryEmbedCountdownTheme'])) {
$j['carryEmbedCountdownTheme'] = $disk['carryEmbedCountdownTheme'];
}
/* ไม่ทับ carryChoicePlaqueThemes / carryChoicePlaqueTheme จากดิสก์ — ใช้ค่าจาก Node (อ่านไฟล์เดียวกันกับ disk merge) เพื่อกันทับด้วย JSON เก่าที่ไม่มี plaqueImageUrl ทำให้ช่อง URL ใน Admin ว่างหลังรีเฟรช */
foreach (['carryReadMs', 'carryAnswerMs', 'carrySessionLength', 'carryChoicePlaqueMapScale', 'carryWalkSpeedMultForMapId', 'carryWalkSpeedMult', 'quizRoundQuestionCount'] as $ck) {
if (array_key_exists($ck, $disk)) {
$j[$ck] = $disk[$ck];
}
}
if (array_key_exists('carryQuestions', $disk) && is_array($disk['carryQuestions'])) {
$j['carryQuestions'] = $disk['carryQuestions'];
}
$enc = json_encode($j, JSON_UNESCAPED_UNICODE);
return $enc !== false ? $enc : $nodeBody;
}
function proxy_quiz_curl(string $method, ?string $body = null): void
{
global $target;
$ch = curl_init($target);
if ($ch === false) {
json_response(['ok' => false, 'error' => 'curl init failed'], 500);
}
$headers = ['Accept: application/json'];
if ($body !== null && $method !== 'GET') {
$headers[] = 'Content-Type: application/json';
}
$opts = [
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 15,
];
if ($body !== null && $method !== 'GET') {
$opts[CURLOPT_POSTFIELDS] = $body;
}
curl_setopt_array($ch, $opts);
$out = curl_exec($ch);
$code = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
$err = curl_error($ch);
curl_close($ch);
if ($method === 'PUT' && $body !== null && $body !== '') {
merge_quiz_settings_disk_from_put($body);
}
if ($out === false || $out === '') {
if ($method === 'GET') {
$disk = quiz_settings_disk_path();
if (is_file($disk)) {
$rawFile = @file_get_contents($disk);
if ($rawFile !== false && $rawFile !== '') {
http_response_code(200);
header('Content-Type: application/json; charset=utf-8');
header('X-Content-Type-Options: nosniff');
header('Cache-Control: no-store');
echo $rawFile;
exit;
}
}
}
if ($method === 'PUT' && $body !== null && $body !== '' && is_file(quiz_settings_disk_path())) {
http_response_code(200);
header('Content-Type: application/json; charset=utf-8');
header('X-Content-Type-Options: nosniff');
header('Cache-Control: no-store');
$payload = ['ok' => true, 'diskFallback' => true];
$rawDisk = @file_get_contents(quiz_settings_disk_path());
if ($rawDisk !== false && $rawDisk !== '') {
$dj = json_decode($rawDisk, true);
if (is_array($dj)) {
if (isset($dj['carryMapPanelTheme']) && is_array($dj['carryMapPanelTheme'])) {
$payload['carryMapPanelTheme'] = $dj['carryMapPanelTheme'];
}
if (isset($dj['carryEmbedCountdownTheme']) && is_array($dj['carryEmbedCountdownTheme'])) {
$payload['carryEmbedCountdownTheme'] = $dj['carryEmbedCountdownTheme'];
}
if (isset($dj['carryChoicePlaqueThemes']) && is_array($dj['carryChoicePlaqueThemes'])) {
$payload['carryChoicePlaqueThemes'] = $dj['carryChoicePlaqueThemes'];
}
if (isset($dj['carryChoicePlaqueTheme']) && is_array($dj['carryChoicePlaqueTheme'])) {
$payload['carryChoicePlaqueTheme'] = $dj['carryChoicePlaqueTheme'];
}
if (array_key_exists('carryReadMs', $dj)) {
$payload['carryReadMs'] = $dj['carryReadMs'];
}
if (array_key_exists('carryAnswerMs', $dj)) {
$payload['carryAnswerMs'] = $dj['carryAnswerMs'];
}
if (array_key_exists('carrySessionLength', $dj)) {
$payload['carrySessionLength'] = $dj['carrySessionLength'];
}
if (array_key_exists('carryWalkSpeedMultForMapId', $dj)) {
$payload['carryWalkSpeedMultForMapId'] = $dj['carryWalkSpeedMultForMapId'];
}
if (array_key_exists('carryWalkSpeedMult', $dj)) {
$payload['carryWalkSpeedMult'] = $dj['carryWalkSpeedMult'];
}
}
}
echo json_encode($payload, JSON_UNESCAPED_UNICODE);
exit;
}
json_response([
'ok' => false,
'error' => 'ไม่ต่อถึงเซิร์ฟเวอร์เกม (Node) — ตรวจว่า pm2/systemd รัน Game/server.js อยู่ที่พอร์ต ' . (getenv('GAME_NODE_PORT') ?: '3001') . ' · ' . $err,
], 502);
}
http_response_code($code > 0 ? $code : 500);
header('Content-Type: application/json; charset=utf-8');
header('X-Content-Type-Options: nosniff');
header('Cache-Control: no-store');
if ($method === 'GET' && $code >= 200 && $code < 300) {
echo overlay_quiz_settings_from_disk($out);
} else {
echo $out;
}
exit;
}
$method = $_SERVER['REQUEST_METHOD'] ?? 'GET';
if ($method === 'GET') {
proxy_quiz_curl('GET', null);
}
if ($method === 'PUT') {
$raw = file_get_contents('php://input');
if ($raw === false) {
$raw = '';
}
proxy_quiz_curl('PUT', $raw);
}
json_response(['ok' => false, 'error' => 'Method not allowed'], 405);