list all languages if (isset($_GET['languages'])) { [$pdo, $tmpDb, $lockFp] = qdb_open(false); $rows = $pdo->query("SELECT languageCode, name FROM language ORDER BY languageCode")->fetchAll(PDO::FETCH_ASSOC); qdb_discard($tmpDb, $lockFp); echo json_encode(["success" => true, "languages" => $rows]); break; } // GET ?questionnaireID=X -> batch: all keys + all translations for a questionnaire $qnID = $_GET['questionnaireID'] ?? ''; if ($qnID) { [$pdo, $tmpDb, $lockFp] = qdb_open(false); $languages = $pdo->query("SELECT languageCode, name FROM language ORDER BY languageCode")->fetchAll(PDO::FETCH_ASSOC); // Question keys + translations $qStmt = $pdo->prepare(" SELECT q.questionID, q.defaultText, q.configJson FROM question q WHERE q.questionnaireID = :id ORDER BY q.orderIndex "); $qStmt->execute([':id' => $qnID]); $dbQuestions = $qStmt->fetchAll(PDO::FETCH_ASSOC); $entries = []; $stringKeys = []; foreach ($dbQuestions as $dbQ) { $entries[] = [ 'key' => $dbQ['defaultText'], 'type' => 'question', 'entityId' => $dbQ['questionID'], ]; // Answer option keys $aoStmt = $pdo->prepare(" SELECT answerOptionID, defaultText FROM answer_option WHERE questionID = :qid ORDER BY orderIndex "); $aoStmt->execute([':qid' => $dbQ['questionID']]); foreach ($aoStmt->fetchAll(PDO::FETCH_ASSOC) as $ao) { $entries[] = [ 'key' => $ao['defaultText'], 'type' => 'answer_option', 'entityId' => $ao['answerOptionID'], ]; } // String keys from configJson $config = json_decode($dbQ['configJson'] ?: '{}', true) ?: []; foreach (['textKey','textKey1','textKey2','hint','hint1','hint2'] as $field) { if (!empty($config[$field])) $stringKeys[$config[$field]] = true; } if (!empty($config['symptoms']) && is_array($config['symptoms'])) { foreach ($config['symptoms'] as $s) $stringKeys[$s] = true; } } foreach (array_keys($stringKeys) as $sk) { $entries[] = [ 'key' => $sk, 'type' => 'string', 'entityId' => $sk, ]; } // Fetch all existing translations for these entities $translations = []; // question translations $qIds = array_filter(array_map(fn($e) => $e['type'] === 'question' ? $e['entityId'] : null, $entries)); if ($qIds) { $ph = implode(',', array_fill(0, count($qIds), '?')); $stmt = $pdo->prepare("SELECT questionID AS entityId, languageCode, text FROM question_translation WHERE questionID IN ($ph)"); $stmt->execute(array_values($qIds)); foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $r) { $translations[$r['entityId']][$r['languageCode']] = $r['text']; } } // answer_option translations $aoIds = array_filter(array_map(fn($e) => $e['type'] === 'answer_option' ? $e['entityId'] : null, $entries)); if ($aoIds) { $ph = implode(',', array_fill(0, count($aoIds), '?')); $stmt = $pdo->prepare("SELECT answerOptionID AS entityId, languageCode, text FROM answer_option_translation WHERE answerOptionID IN ($ph)"); $stmt->execute(array_values($aoIds)); foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $r) { $translations[$r['entityId']][$r['languageCode']] = $r['text']; } } // string translations $sKeys = array_filter(array_map(fn($e) => $e['type'] === 'string' ? $e['entityId'] : null, $entries)); if ($sKeys) { $ph = implode(',', array_fill(0, count($sKeys), '?')); $stmt = $pdo->prepare("SELECT stringKey AS entityId, languageCode, text FROM string_translation WHERE stringKey IN ($ph)"); $stmt->execute(array_values($sKeys)); foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $r) { $translations[$r['entityId']][$r['languageCode']] = $r['text']; } } // Attach translations to entries foreach ($entries as &$e) { $e['translations'] = $translations[$e['entityId']] ?? (object)[]; } unset($e); qdb_discard($tmpDb, $lockFp); echo json_encode([ "success" => true, "languages" => $languages, "entries" => $entries, ]); break; } // Original single-entity fetch $type = $_GET['type'] ?? ''; $id = $_GET['id'] ?? ''; if (!in_array($type, $validTypes, true) || (!$id && $type !== 'string')) { http_response_code(400); echo json_encode(["error" => "type (question|answer_option|string) and id required"]); exit; } [$pdo, $tmpDb, $lockFp] = qdb_open(false); if ($type === 'question') { $stmt = $pdo->prepare("SELECT languageCode, text FROM question_translation WHERE questionID = :id"); $stmt->execute([':id' => $id]); } elseif ($type === 'answer_option') { $stmt = $pdo->prepare("SELECT languageCode, text FROM answer_option_translation WHERE answerOptionID = :id"); $stmt->execute([':id' => $id]); } else { if ($id) { $stmt = $pdo->prepare("SELECT stringKey, languageCode, text FROM string_translation WHERE stringKey = :id"); $stmt->execute([':id' => $id]); } else { $stmt = $pdo->query("SELECT stringKey, languageCode, text FROM string_translation ORDER BY stringKey, languageCode"); } } $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); qdb_discard($tmpDb, $lockFp); echo json_encode(["success" => true, "translations" => $rows]); break; case 'PUT': require_role(['admin', 'supervisor'], $tokenRec); $body = json_decode(file_get_contents('php://input'), true); $type = $body['type'] ?? ''; // Language management if ($type === 'language') { $lang = trim($body['languageCode'] ?? ''); $name = trim($body['name'] ?? ''); if (!$lang) { http_response_code(400); echo json_encode(["error" => "languageCode required"]); exit; } [$pdo, $tmpDb, $lockFp] = qdb_open(true); try { $pdo->prepare("INSERT INTO language (languageCode, name) VALUES (:lc, :n) ON CONFLICT(languageCode) DO UPDATE SET name = excluded.name") ->execute([':lc' => $lang, ':n' => $name]); qdb_save($tmpDb, $lockFp); echo json_encode(["success" => true]); } catch (Throwable $e) { qdb_discard($tmpDb, $lockFp); http_response_code(500); error_log($e->getMessage()); echo json_encode(["error" => "Server error"]); } break; } $id = $body['id'] ?? ''; $lang = trim($body['languageCode'] ?? ''); $text = $body['text'] ?? ''; if (!in_array($type, $validTypes, true) || !$id || !$lang) { http_response_code(400); echo json_encode(["error" => "type, id, and languageCode required"]); exit; } [$pdo, $tmpDb, $lockFp] = qdb_open(true); try { if ($type === 'question') { $pdo->prepare("INSERT INTO question_translation (questionID, languageCode, text) VALUES (:id, :lang, :t) ON CONFLICT(questionID, languageCode) DO UPDATE SET text = excluded.text") ->execute([':id' => $id, ':lang' => $lang, ':t' => $text]); } elseif ($type === 'answer_option') { $pdo->prepare("INSERT INTO answer_option_translation (answerOptionID, languageCode, text) VALUES (:id, :lang, :t) ON CONFLICT(answerOptionID, languageCode) DO UPDATE SET text = excluded.text") ->execute([':id' => $id, ':lang' => $lang, ':t' => $text]); } else { $pdo->prepare("INSERT INTO string_translation (stringKey, languageCode, text) VALUES (:id, :lang, :t) ON CONFLICT(stringKey, languageCode) DO UPDATE SET text = excluded.text") ->execute([':id' => $id, ':lang' => $lang, ':t' => $text]); } qdb_save($tmpDb, $lockFp); echo json_encode(["success" => true]); } catch (Throwable $e) { qdb_discard($tmpDb, $lockFp); http_response_code(500); error_log($e->getMessage()); echo json_encode(["error" => "Server error"]); } break; case 'DELETE': require_role(['admin', 'supervisor'], $tokenRec); $body = json_decode(file_get_contents('php://input'), true); $type = $body['type'] ?? ''; // Language deletion if ($type === 'language') { $lang = trim($body['languageCode'] ?? ''); if (!$lang) { http_response_code(400); echo json_encode(["error" => "languageCode required"]); exit; } [$pdo, $tmpDb, $lockFp] = qdb_open(true); try { $pdo->prepare("DELETE FROM language WHERE languageCode = :lc")->execute([':lc' => $lang]); $pdo->prepare("DELETE FROM question_translation WHERE languageCode = :lc")->execute([':lc' => $lang]); $pdo->prepare("DELETE FROM answer_option_translation WHERE languageCode = :lc")->execute([':lc' => $lang]); $pdo->prepare("DELETE FROM string_translation WHERE languageCode = :lc")->execute([':lc' => $lang]); qdb_save($tmpDb, $lockFp); echo json_encode(["success" => true]); } catch (Throwable $e) { qdb_discard($tmpDb, $lockFp); http_response_code(500); error_log($e->getMessage()); echo json_encode(["error" => "Server error"]); } break; } $id = $body['id'] ?? ''; $lang = trim($body['languageCode'] ?? ''); if (!in_array($type, $validTypes, true) || !$id || !$lang) { http_response_code(400); echo json_encode(["error" => "type, id, and languageCode required"]); exit; } [$pdo, $tmpDb, $lockFp] = qdb_open(true); try { if ($type === 'question') { $pdo->prepare("DELETE FROM question_translation WHERE questionID = :id AND languageCode = :lang") ->execute([':id' => $id, ':lang' => $lang]); } elseif ($type === 'answer_option') { $pdo->prepare("DELETE FROM answer_option_translation WHERE answerOptionID = :id AND languageCode = :lang") ->execute([':id' => $id, ':lang' => $lang]); } else { $pdo->prepare("DELETE FROM string_translation WHERE stringKey = :id AND languageCode = :lang") ->execute([':id' => $id, ':lang' => $lang]); } qdb_save($tmpDb, $lockFp); echo json_encode(["success" => true]); } catch (Throwable $e) { qdb_discard($tmpDb, $lockFp); http_response_code(500); error_log($e->getMessage()); echo json_encode(["error" => "Server error"]); } break; default: http_response_code(405); echo json_encode(["error" => "Method not allowed"]); }