238 lines
11 KiB
PHP
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);
|