'application/json; charset=UTF-8', 'Content-Disposition' => 'attachment; filename="' . $filename . '"', ] ); } if (isset($_GET['languages'])) { try { $opened = qdb_open_read_or_fail(); [$pdo, $tmpDb, $lockFp] = $opened; $rows = $pdo->query("SELECT languageCode, name FROM language ORDER BY languageCode")->fetchAll(PDO::FETCH_ASSOC); qdb_discard($tmpDb, $lockFp); json_success(['languages' => $rows]); } catch (Throwable $e) { qdb_handler_fail($e, 'Load languages', null, $tmpDb ?? null, $lockFp ?? null); } } if (!empty($_GET['all'])) { try { $opened = qdb_open_read_or_fail(); [$pdo, $tmpDb, $lockFp] = $opened; $languages = $pdo->query("SELECT languageCode, name FROM language ORDER BY languageCode")->fetchAll(PDO::FETCH_ASSOC); if (!array_filter($languages, fn($l) => $l['languageCode'] === QDB_SOURCE_LANGUAGE)) { array_unshift($languages, ['languageCode' => QDB_SOURCE_LANGUAGE, 'name' => 'German']); } $qnRows = $pdo->query(" SELECT questionnaireID, name, orderIndex FROM questionnaire ORDER BY orderIndex ")->fetchAll(PDO::FETCH_ASSOC); $appEntries = qdb_app_ui_string_entries_for_website($pdo); $appTranslations = qdb_load_translations_for_entries($pdo, $appEntries); qdb_attach_translation_texts($appEntries, $appTranslations); $questionnaires = []; $allEntries = $appEntries; foreach ($qnRows as $qn) { $lists = qdb_translation_entry_lists($pdo, $qn['questionnaireID']); $entries = array_merge($lists['stringEntries'], $lists['contentEntries']); if (!$entries) { continue; } $translations = qdb_load_translations_for_entries($pdo, $entries); qdb_attach_translation_texts($entries, $translations); $questionnaires[] = [ 'questionnaireID' => $qn['questionnaireID'], 'name' => $qn['name'], 'orderIndex' => (int)$qn['orderIndex'], 'entries' => $entries, ]; foreach ($entries as $e) { $allEntries[] = $e; } } qdb_discard($tmpDb, $lockFp); json_success([ 'languages' => $languages, 'appStrings' => $appEntries, 'questionnaires' => $questionnaires, 'entries' => $allEntries, 'sourceLanguage' => QDB_SOURCE_LANGUAGE, ]); } catch (Throwable $e) { qdb_handler_fail($e, 'Load translations', null, $tmpDb ?? null, $lockFp ?? null); } } $qnID = trim((string)($_GET['questionnaireID'] ?? '')); if ($qnID !== '') { try { $opened = qdb_open_read_or_fail(); [$pdo, $tmpDb, $lockFp] = $opened; $languages = $pdo->query("SELECT languageCode, name FROM language ORDER BY languageCode")->fetchAll(PDO::FETCH_ASSOC); if (!array_filter($languages, fn($l) => $l['languageCode'] === QDB_SOURCE_LANGUAGE)) { array_unshift($languages, ['languageCode' => QDB_SOURCE_LANGUAGE, 'name' => 'German']); } $qnRow = $pdo->prepare('SELECT name FROM questionnaire WHERE questionnaireID = :id'); $qnRow->execute([':id' => $qnID]); $qnName = $qnRow->fetchColumn(); if ($qnName === false) { qdb_discard($tmpDb, $lockFp); json_error('NOT_FOUND', 'Questionnaire not found', 404); } $lists = qdb_translation_entry_lists($pdo, $qnID); $entries = array_merge($lists['stringEntries'], $lists['contentEntries']); $translations = qdb_load_translations_for_entries($pdo, $entries); qdb_attach_translation_texts($entries, $translations); qdb_discard($tmpDb, $lockFp); json_success([ 'languages' => $languages, 'entries' => $entries, 'questionnaire' => ['questionnaireID' => $qnID, 'name' => (string)$qnName], 'sourceLanguage' => QDB_SOURCE_LANGUAGE, ]); } catch (Throwable $e) { qdb_handler_fail($e, 'Load translations', null, $tmpDb ?? null, $lockFp ?? null); } } $type = $_GET['type'] ?? ''; $id = $_GET['id'] ?? ''; if (!in_array($type, $validTypes, true) || (!$id && $type !== 'string')) { json_error('BAD_REQUEST', 'type (question|answer_option|string) and id required', 400); } try { $opened = qdb_open_read_or_fail(); [$pdo, $tmpDb, $lockFp] = $opened; 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"); } } if ($type === 'question' || $type === 'answer_option') { if (!qdb_translation_entity_exists($pdo, $type, (string)$id)) { qdb_discard($tmpDb, $lockFp); json_error('NOT_FOUND', 'Translation entity not found', 404); } } $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); qdb_discard($tmpDb, $lockFp); json_success(['translations' => $rows]); } catch (Throwable $e) { qdb_handler_fail($e, 'Load translations', null, $tmpDb ?? null, $lockFp ?? null); } break; case 'POST': require_role(['admin', 'supervisor'], $tokenRec); $body = read_json_body(); if (($body['action'] ?? '') === 'importBundle') { $bundle = $body['bundle'] ?? null; if (!is_array($bundle)) { json_error('MISSING_FIELDS', 'bundle object is required', 400); } [$pdo, $tmpDb, $lockFp] = qdb_open_write_or_fail(); try { $result = qdb_import_translations_bundle($pdo, $bundle); qdb_save($tmpDb, $lockFp); json_success($result); } catch (Throwable $e) { qdb_handler_fail($e, 'Import translations', null, $tmpDb, $lockFp); } break; } if (($body['action'] ?? '') === 'deleteAll') { require_role(['admin'], $tokenRec); $phrase = trim((string)($body['confirmPhrase'] ?? '')); if ($phrase !== 'DELETE ALL TRANSLATIONS') { json_error('CONFIRMATION_REQUIRED', 'Type DELETE ALL TRANSLATIONS to confirm', 400); } [$pdo, $tmpDb, $lockFp] = qdb_open_write_or_fail(); try { $result = qdb_delete_all_translations($pdo, true); qdb_save($tmpDb, $lockFp); json_success($result); } catch (Throwable $e) { qdb_handler_fail($e, 'Delete all translations', null, $tmpDb, $lockFp); } break; } json_error('BAD_REQUEST', 'Unknown action', 400); break; case 'PUT': require_role(['admin', 'supervisor'], $tokenRec); $body = read_json_body(); $type = $body['type'] ?? ''; if ($type === 'language') { $lang = trim($body['languageCode'] ?? ''); $name = trim($body['name'] ?? ''); if (!$lang) { json_error('MISSING_FIELDS', 'languageCode required', 400); } [$pdo, $tmpDb, $lockFp] = qdb_open_write_or_fail(); try { if ($lang === QDB_SOURCE_LANGUAGE) { qdb_ensure_source_language($pdo); } else { $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); json_success([]); } catch (Throwable $e) { qdb_handler_fail($e, 'Save language', null, $tmpDb, $lockFp); } break; } $id = $body['id'] ?? ''; $lang = trim($body['languageCode'] ?? ''); $text = $body['text'] ?? ''; if (!in_array($type, $validTypes, true) || !$id || !$lang) { json_error('MISSING_FIELDS', 'type, id, and languageCode required', 400); } try { $opened = qdb_open_write_or_fail(); [$pdo, $tmpDb, $lockFp] = $opened; if (!qdb_translation_entity_exists($pdo, $type, (string)$id)) { qdb_discard($tmpDb, $lockFp); json_error('NOT_FOUND', 'Translation entity not found', 404); } qdb_put_translation($pdo, $type, $id, $lang, $text); qdb_save($tmpDb, $lockFp); json_success([]); } catch (Throwable $e) { qdb_handler_fail($e, 'Save translation', null, $tmpDb ?? null, $lockFp ?? null); } break; case 'DELETE': require_role(['admin', 'supervisor'], $tokenRec); $body = read_json_body(); $type = $body['type'] ?? ''; if ($type === 'language') { $lang = trim($body['languageCode'] ?? ''); if (!$lang) { json_error('MISSING_FIELDS', 'languageCode required', 400); } if ($lang === QDB_SOURCE_LANGUAGE) { json_error('BAD_REQUEST', 'German (de) cannot be removed', 400); } [$pdo, $tmpDb, $lockFp] = qdb_open_write_or_fail(); 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); json_success([]); } catch (Throwable $e) { qdb_handler_fail($e, 'Delete language', null, $tmpDb, $lockFp); } break; } $id = $body['id'] ?? ''; $lang = trim($body['languageCode'] ?? ''); if (!in_array($type, $validTypes, true) || !$id || !$lang) { json_error('MISSING_FIELDS', 'type, id, and languageCode required', 400); } try { $opened = qdb_open_write_or_fail(); [$pdo, $tmpDb, $lockFp] = $opened; if ($type === 'question' || $type === 'answer_option') { if (!qdb_translation_entity_exists($pdo, $type, (string)$id)) { qdb_discard($tmpDb, $lockFp); json_error('NOT_FOUND', 'Translation entity not found', 404); } } 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); json_success([]); } catch (Throwable $e) { qdb_handler_fail($e, 'Delete translation', null, $tmpDb ?? null, $lockFp ?? null); } break; default: json_error('METHOD_NOT_ALLOWED', 'Method not allowed', 405); }