changes to translation system
Some checks failed
PHPUnit / test (push) Has been cancelled

This commit is contained in:
tom.hempel
2026-07-09 14:59:56 +02:00
parent 4afab336ee
commit f04388e0ec
24 changed files with 1882 additions and 415 deletions

View File

@ -19,7 +19,10 @@
if ($method === 'GET' && !empty($_GET['translations'])) {
$opened = qdb_open_read_or_fail();
[$pdo, $tmpDb, $lockFp] = $opened;
json_success(['translations' => qdb_build_app_translations_map($pdo)]);
json_success([
'translations' => qdb_build_app_translations_map($pdo),
'availableLanguages' => qdb_available_language_codes($pdo),
]);
qdb_discard($tmpDb, $lockFp);
return;
}
@ -588,10 +591,11 @@ if ($qnID) {
qdb_discard($tmpDb, $lockFp);
json_success([
'meta' => ['id' => $qnID],
'structureRevision' => $structureRevision,
'questions' => $questions,
'translations' => qdb_build_questionnaire_translations_map($pdo, $qnID),
'meta' => ['id' => $qnID],
'structureRevision' => $structureRevision,
'questions' => $questions,
'translations' => qdb_build_questionnaire_translations_map($pdo, $qnID),
'availableLanguages' => qdb_available_language_codes($pdo, $qnID),
]);
}

View File

@ -62,7 +62,45 @@ case 'GET':
break;
case 'POST':
$body = read_json_body();
$body = read_json_body();
if (($body['action'] ?? '') === 'reset') {
$clientCode = trim((string)($body['clientCode'] ?? ''));
if ($clientCode === '') {
json_error('MISSING_FIELDS', 'clientCode is required', 400);
}
try {
[$pdo, $tmpDb, $lockFp] = qdb_open_write_or_fail();
[$clause, $params] = rbac_client_filter($tokenRec, 'cl', 'all');
$chk = $pdo->prepare(
"SELECT clientCode FROM client cl WHERE cl.clientCode = :cc AND ($clause)"
);
$chk->execute(array_merge([':cc' => $clientCode], $params));
if (!$chk->fetch()) {
qdb_discard($tmpDb, $lockFp);
json_error('NOT_FOUND', 'Client not found or not authorized', 404);
}
require_once __DIR__ . '/../lib/submissions.php';
$pdo->beginTransaction();
$cleared = qdb_delete_client_response_data($pdo, [$clientCode]);
$pdo->commit();
qdb_save($tmpDb, $lockFp);
json_success([
'clientCode' => $clientCode,
'reset' => true,
'cleared' => $cleared,
]);
} catch (Throwable $e) {
qdb_handler_fail($e, 'clients', $pdo ?? null, $tmpDb ?? null, $lockFp ?? null);
}
break;
}
$coachID = trim($body['coachID'] ?? '');
$bulk = !empty($body['bulk']);

View File

@ -85,7 +85,7 @@ if ($allVersions) {
$filename = $safeName . '_all_versions_' . date('Y-m-d') . '.csv';
qdb_http_download(
qdb_export_rows_to_csv_string($rows, qdb_export_all_versions_csv_fallback_header($resultColumns)),
qdb_export_rows_to_csv_string($rows, qdb_export_all_versions_csv_fallback_header($pdo, $resultColumns)),
[
'Content-Type' => 'text/csv; charset=UTF-8',
'Content-Disposition' => 'attachment; filename="' . $filename . '"',
@ -100,7 +100,7 @@ $safeName = qdb_export_safe_basename($questionnaire['name']);
$filename = $safeName . '_v' . $questionnaire['version'] . '.csv';
qdb_http_download(
qdb_export_rows_to_csv_string($rows, qdb_export_current_csv_fallback_header($resultColumns)),
qdb_export_rows_to_csv_string($rows, qdb_export_current_csv_fallback_header($pdo, $resultColumns)),
[
'Content-Type' => 'text/csv; charset=UTF-8',
'Content-Disposition' => 'attachment; filename="' . $filename . '"',

View File

@ -1,5 +1,7 @@
<?php
require_once __DIR__ . '/../lib/submissions.php';
$tokenRec = require_valid_token_web();
if ($method !== 'GET') {
@ -112,12 +114,19 @@ if (!empty($questionIDs) && !empty($clients)) {
unset($c);
}
$scoringSummary = qdb_clients_scoring_summary_for_list($pdo, $tokenRec);
foreach ($clients as &$c) {
$c['scoringProfiles'] = qdb_client_scoring_dots_for_client($c['clientCode'], $scoringSummary);
}
unset($c);
qdb_discard($tmpDb, $lockFp);
json_success([
'questionnaire' => $questionnaire,
'questions' => $questions,
'clients' => $clients,
'questionnaire' => $questionnaire,
'questions' => $questions,
'clients' => $clients,
'scoringProfiles' => $scoringSummary['profiles'],
]);
} catch (Throwable $e) {
qdb_handler_fail($e, 'Load results', null, $tmpDb ?? null, $lockFp ?? null);

View File

@ -2,7 +2,7 @@
$tokenRec = require_valid_token_web();
$validTypes = ['question', 'answer_option', 'string', 'app_string', 'language'];
$validTypes = ['question', 'answer_option', 'string', 'app_string', 'language', 'language_enabled', 'questionnaire_language'];
switch ($method) {
@ -32,9 +32,13 @@ case 'GET':
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);
$rows = qdb_load_languages($pdo);
$disables = qdb_questionnaire_language_disables($pdo);
qdb_discard($tmpDb, $lockFp);
json_success(['languages' => $rows]);
json_success([
'languages' => $rows,
'questionnaireLanguageDisables' => $disables,
]);
} catch (Throwable $e) {
qdb_handler_fail($e, 'Load languages', null, $tmpDb ?? null, $lockFp ?? null);
}
@ -45,9 +49,9 @@ case 'GET':
$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);
$languages = qdb_load_languages($pdo);
if (!array_filter($languages, fn($l) => $l['languageCode'] === QDB_SOURCE_LANGUAGE)) {
array_unshift($languages, ['languageCode' => QDB_SOURCE_LANGUAGE, 'name' => 'German']);
array_unshift($languages, ['languageCode' => QDB_SOURCE_LANGUAGE, 'name' => 'German', 'enabled' => 1]);
}
$qnRows = $pdo->query("
@ -82,11 +86,12 @@ case 'GET':
qdb_discard($tmpDb, $lockFp);
json_success([
'languages' => $languages,
'appStrings' => $appEntries,
'questionnaires' => $questionnaires,
'entries' => $allEntries,
'sourceLanguage' => QDB_SOURCE_LANGUAGE,
'languages' => $languages,
'appStrings' => $appEntries,
'questionnaires' => $questionnaires,
'entries' => $allEntries,
'sourceLanguage' => QDB_SOURCE_LANGUAGE,
'questionnaireLanguageDisables' => qdb_questionnaire_language_disables($pdo),
]);
} catch (Throwable $e) {
qdb_handler_fail($e, 'Load translations', null, $tmpDb ?? null, $lockFp ?? null);
@ -99,9 +104,9 @@ case 'GET':
$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);
$languages = qdb_load_languages($pdo);
if (!array_filter($languages, fn($l) => $l['languageCode'] === QDB_SOURCE_LANGUAGE)) {
array_unshift($languages, ['languageCode' => QDB_SOURCE_LANGUAGE, 'name' => 'German']);
array_unshift($languages, ['languageCode' => QDB_SOURCE_LANGUAGE, 'name' => 'German', 'enabled' => 1]);
}
$qnRow = $pdo->prepare('SELECT name FROM questionnaire WHERE questionnaireID = :id');
@ -119,10 +124,11 @@ case 'GET':
qdb_discard($tmpDb, $lockFp);
json_success([
'languages' => $languages,
'entries' => $entries,
'questionnaire' => ['questionnaireID' => $qnID, 'name' => (string)$qnName],
'sourceLanguage' => QDB_SOURCE_LANGUAGE,
'languages' => $languages,
'entries' => $entries,
'questionnaire' => ['questionnaireID' => $qnID, 'name' => (string)$qnName],
'sourceLanguage' => QDB_SOURCE_LANGUAGE,
'questionnaireLanguageDisables' => qdb_questionnaire_language_disables($pdo),
]);
} catch (Throwable $e) {
qdb_handler_fail($e, 'Load translations', null, $tmpDb ?? null, $lockFp ?? null);
@ -185,6 +191,28 @@ case 'POST':
}
break;
}
if (($body['action'] ?? '') === 'deleteTranslationsScope') {
$scope = trim((string)($body['scope'] ?? ''));
$qnID = trim((string)($body['questionnaireID'] ?? ''));
$lang = isset($body['languageCode']) ? trim((string)$body['languageCode']) : '';
[$pdo, $tmpDb, $lockFp] = qdb_open_write_or_fail();
try {
$deleted = qdb_delete_translations_for_scope(
$pdo,
$scope,
$qnID !== '' ? $qnID : null,
$lang !== '' ? $lang : null
);
qdb_save($tmpDb, $lockFp);
json_success(['deleted' => $deleted]);
} catch (InvalidArgumentException $e) {
qdb_discard($tmpDb, $lockFp);
json_error('INVALID_FIELD', $e->getMessage(), 400);
} catch (Throwable $e) {
qdb_handler_fail($e, 'Delete translations', null, $tmpDb, $lockFp);
}
break;
}
json_error('BAD_REQUEST', 'Unknown action', 400);
break;
@ -203,6 +231,10 @@ case 'PUT':
try {
if ($lang === QDB_SOURCE_LANGUAGE) {
qdb_ensure_source_language($pdo);
} elseif (qdb_column_exists($pdo, 'language', 'enabled')) {
$pdo->prepare("INSERT INTO language (languageCode, name, enabled) VALUES (:lc, :n, 1)
ON CONFLICT(languageCode) DO UPDATE SET name = excluded.name")
->execute([':lc' => $lang, ':n' => $name]);
} else {
$pdo->prepare("INSERT INTO language (languageCode, name) VALUES (:lc, :n)
ON CONFLICT(languageCode) DO UPDATE SET name = excluded.name")
@ -216,6 +248,47 @@ case 'PUT':
break;
}
if ($type === 'language_enabled') {
$lang = trim($body['languageCode'] ?? '');
if (!$lang) {
json_error('MISSING_FIELDS', 'languageCode required', 400);
}
$enabled = !empty($body['enabled']);
[$pdo, $tmpDb, $lockFp] = qdb_open_write_or_fail();
try {
qdb_set_language_enabled($pdo, $lang, $enabled);
qdb_save($tmpDb, $lockFp);
json_success([]);
} catch (InvalidArgumentException $e) {
qdb_discard($tmpDb, $lockFp);
json_error('INVALID_FIELD', $e->getMessage(), 400);
} catch (Throwable $e) {
qdb_handler_fail($e, 'Save language setting', null, $tmpDb, $lockFp);
}
break;
}
if ($type === 'questionnaire_language') {
$qnID = trim($body['questionnaireID'] ?? '');
$lang = trim($body['languageCode'] ?? '');
if (!$qnID || !$lang) {
json_error('MISSING_FIELDS', 'questionnaireID and languageCode required', 400);
}
$enabled = !empty($body['enabled']);
[$pdo, $tmpDb, $lockFp] = qdb_open_write_or_fail();
try {
qdb_set_questionnaire_language_disabled($pdo, $qnID, $lang, !$enabled);
qdb_save($tmpDb, $lockFp);
json_success([]);
} catch (InvalidArgumentException $e) {
qdb_discard($tmpDb, $lockFp);
json_error('INVALID_FIELD', $e->getMessage(), 400);
} catch (Throwable $e) {
qdb_handler_fail($e, 'Save questionnaire language setting', null, $tmpDb, $lockFp);
}
break;
}
$id = $body['id'] ?? '';
$lang = trim($body['languageCode'] ?? '');
$text = $body['text'] ?? '';