added translation tab
This commit is contained in:
160
common.php
160
common.php
@ -202,6 +202,166 @@ function rbac_client_filter(array $tokenRecord, string $clientAlias = 'client'):
|
||||
}
|
||||
}
|
||||
|
||||
// --- Translations: German (de) is the canonical source language ---
|
||||
define('QDB_SOURCE_LANGUAGE', 'de');
|
||||
|
||||
function qdb_ensure_source_language(PDO $pdo): void {
|
||||
$pdo->prepare("INSERT INTO language (languageCode, name) VALUES (:lc, :n)
|
||||
ON CONFLICT(languageCode) DO NOTHING")
|
||||
->execute([':lc' => QDB_SOURCE_LANGUAGE, ':n' => 'German']);
|
||||
}
|
||||
|
||||
function qdb_upsert_source_translation(PDO $pdo, string $type, string $id, string $text): void {
|
||||
qdb_ensure_source_language($pdo);
|
||||
$lang = QDB_SOURCE_LANGUAGE;
|
||||
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]);
|
||||
} elseif ($type === 'string') {
|
||||
$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]);
|
||||
}
|
||||
}
|
||||
|
||||
function qdb_require_non_empty_german(string $text, string $label = 'German text'): void {
|
||||
if ($text === '') {
|
||||
json_error('MISSING_FIELDS', "$label is required", 400);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build translation entry lists for one questionnaire.
|
||||
* Returns ['stringEntries' => [...], 'contentEntries' => [...]] (questions + options interleaved).
|
||||
*/
|
||||
function qdb_translation_entry_lists(PDO $pdo, string $qnID): array {
|
||||
$qStmt = $pdo->prepare("
|
||||
SELECT q.questionID, q.defaultText, q.configJson, q.orderIndex
|
||||
FROM question q WHERE q.questionnaireID = :id ORDER BY q.orderIndex
|
||||
");
|
||||
$qStmt->execute([':id' => $qnID]);
|
||||
$dbQuestions = $qStmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
$stringKeys = [];
|
||||
$stringEntries = [];
|
||||
$contentEntries = [];
|
||||
|
||||
foreach ($dbQuestions as $dbQ) {
|
||||
$qOrder = (int)($dbQ['orderIndex'] ?? 0);
|
||||
$contentEntries[] = [
|
||||
'key' => $dbQ['defaultText'],
|
||||
'type' => 'question',
|
||||
'entityId' => $dbQ['questionID'],
|
||||
'sortOrder' => $qOrder * 1000,
|
||||
];
|
||||
|
||||
$aoStmt = $pdo->prepare("
|
||||
SELECT answerOptionID, defaultText, orderIndex
|
||||
FROM answer_option WHERE questionID = :qid ORDER BY orderIndex
|
||||
");
|
||||
$aoStmt->execute([':qid' => $dbQ['questionID']]);
|
||||
foreach ($aoStmt->fetchAll(PDO::FETCH_ASSOC) as $ao) {
|
||||
$contentEntries[] = [
|
||||
'key' => $ao['defaultText'],
|
||||
'type' => 'answer_option',
|
||||
'entityId' => $ao['answerOptionID'],
|
||||
'sortOrder' => $qOrder * 1000 + 10 + (int)($ao['orderIndex'] ?? 0),
|
||||
];
|
||||
}
|
||||
|
||||
$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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$stringOrder = 0;
|
||||
$skList = array_keys($stringKeys);
|
||||
sort($skList, SORT_STRING);
|
||||
foreach ($skList as $sk) {
|
||||
$stringEntries[] = [
|
||||
'key' => $sk,
|
||||
'type' => 'string',
|
||||
'entityId' => $sk,
|
||||
'sortOrder' => $stringOrder++,
|
||||
];
|
||||
}
|
||||
|
||||
return ['stringEntries' => $stringEntries, 'contentEntries' => $contentEntries];
|
||||
}
|
||||
|
||||
/** Attach translation texts and germanText to entry rows. */
|
||||
function qdb_attach_translation_texts(array &$entries, array $translations): void {
|
||||
foreach ($entries as &$e) {
|
||||
$tr = $translations[$e['entityId']] ?? [];
|
||||
$e['translations'] = (object)$tr;
|
||||
$de = trim($tr[QDB_SOURCE_LANGUAGE] ?? '');
|
||||
$e['germanText'] = $de !== '' ? $de : $e['key'];
|
||||
}
|
||||
unset($e);
|
||||
}
|
||||
|
||||
/** Load all translation rows for a flat entry list. */
|
||||
function qdb_load_translations_for_entries(PDO $pdo, array $entries): array {
|
||||
$translations = [];
|
||||
|
||||
$qIds = array_values(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($qIds);
|
||||
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $r) {
|
||||
$translations[$r['entityId']][$r['languageCode']] = $r['text'];
|
||||
}
|
||||
}
|
||||
|
||||
$aoIds = array_values(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($aoIds);
|
||||
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $r) {
|
||||
$translations[$r['entityId']][$r['languageCode']] = $r['text'];
|
||||
}
|
||||
}
|
||||
|
||||
$sKeys = array_values(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($sKeys);
|
||||
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $r) {
|
||||
$translations[$r['entityId']][$r['languageCode']] = $r['text'];
|
||||
}
|
||||
}
|
||||
|
||||
return $translations;
|
||||
}
|
||||
|
||||
// --- AES-256-CBC: IV(16) + CIPHERTEXT ---
|
||||
function aes256_cbc_encrypt_bytes(string $plain, string $key): string {
|
||||
$key = str_pad(substr($key, 0, 32), 32, "\0");
|
||||
|
||||
Reference in New Issue
Block a user