initial upload

This commit is contained in:
root
2026-03-24 10:22:01 +00:00
commit e805f225bc
19 changed files with 2160 additions and 0 deletions

97
change_password.php Normal file
View File

@ -0,0 +1,97 @@
<?php
// /var/www/html/qdb/change_password.php
require_once __DIR__ . '/common.php';
header('Content-Type: application/json; charset=UTF-8');
// dieselben Defaults wie in login.php
$USERS_DEFAULT = [
'user01' => 'pw1',
'user02' => 'pw2',
'Tmp_Daniel' => 'dummy4',
'Tmp_Johanna' => 'dummy1',
'Tmp_Anke' => 'dummy2',
'Tmp_Liliana' => 'dummy3',
'Amy_tmp' => 'dummy4',
'Brigitte_tmp' => 'dummy5',
'Extra1_tmp' => 'dummy6',
'Extra2_tmp' => 'dummy7',
'Danny' => 'pw1',
'User1' => 'pw1',
'User2' => 'pw2',
'User3' => 'pw3',
'User4' => 'pw4',
'User5' => 'pw5',
'User6' => 'pw6',
'User7' => 'pw7',
'User8' => 'pw8',
'User9' => 'pw9',
'User10' => 'pw10',
'User11' => 'pw11'
];
$STORE_FILE = __DIR__ . '/users.json';
function load_store($path) {
if (!file_exists($path)) return [];
$txt = file_get_contents($path);
$arr = json_decode($txt, true);
return is_array($arr) ? $arr : [];
}
function save_store($path, $data) {
file_put_contents($path, json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
}
$raw = file_get_contents("php://input");
$in = json_decode($raw, true) ?: [];
$username = trim($in['username'] ?? '');
$oldPassword = (string)($in['old_password'] ?? '');
$newPassword = (string)($in['new_password'] ?? '');
if ($username === '' || $oldPassword === '' || $newPassword === '') {
http_response_code(400);
echo json_encode(["success" => false, "message" => "Felder fehlen"]);
exit;
}
if (strlen($newPassword) < 6) {
http_response_code(400);
echo json_encode(["success" => false, "message" => "Neues Passwort zu kurz (min. 6 Zeichen)."]);
exit;
}
$store = load_store($STORE_FILE);
// Fall A: User hat schon Hash -> altes Passwort gegen Hash prüfen
if (array_key_exists($username, $store) && isset($store[$username]['hash'])) {
$hash = (string)$store[$username]['hash'];
if (!password_verify($oldPassword, $hash)) {
http_response_code(401);
echo json_encode(["success" => false, "message" => "Altes Passwort falsch."]);
exit;
}
} else {
// Fall B: User hat noch keinen Hash -> gegen Default prüfen
if (!array_key_exists($username, $USERS_DEFAULT) || $USERS_DEFAULT[$username] !== $oldPassword) {
http_response_code(401);
echo json_encode(["success" => false, "message" => "Altes Passwort falsch."]);
exit;
}
}
// neuen Hash setzen (bcrypt)
$store[$username] = [
"hash" => password_hash($newPassword, PASSWORD_DEFAULT),
"changedAt" => time()
];
save_store($STORE_FILE, $store);
// Nach erfolgreichem Wechsel direkt einloggen (Token erstellen)
$token = bin2hex(random_bytes(32));
token_add($token, 30 * 24 * 60 * 60);
echo json_encode([
"success" => true,
"message" => "Passwort geändert.",
"token" => $token,
"user" => $username
]);

10
checkDatabaseExists.php Executable file
View File

@ -0,0 +1,10 @@
<?php
header('Content-Type: application/json');
$dbPath = '/var/www/html/uploads/questionnaire_database';
if (file_exists($dbPath)) {
echo json_encode(["exists" => true]);
} else {
echo json_encode(["exists" => false]);
}

91
common.php Normal file
View File

@ -0,0 +1,91 @@
<?php
// /var/www/html/common.php
// Gemeinsame Helfer für Token-Validierung, Key-Derivation (HKDF) und AES (CBC, IV vorangestellt).
// --- MASTER-KEY (Server-seitig zum Speichern der DB) ---
// Per ENV 'QDB_MASTER_KEY' (Base64- oder ASCII-String) oder Fallback auf bisherigen Wert.
function get_master_key_bytes(): string {
$env = getenv('QDB_MASTER_KEY');
if ($env && $env !== '') {
// Versuche Base64 zu dekodieren, sonst als ASCII nehmen
$b = base64_decode($env, true);
if ($b !== false && strlen($b) > 0) {
return str_pad(substr($b, 0, 32), 32, "\0");
}
return str_pad(substr($env, 0, 32), 32, "\0");
}
// Fallback: alter Key aus bestehender Installation (32 ASCII-Bytes)
return "12345678901234567890123456789012";
}
// --- HKDF (SHA-256) über Token -> Session-Key (32 Byte) ---
// Kompatibel zu Android-Implementierung: salt = leer, info = "qdb-aes"
function hkdf_session_key_from_token(string $tokenHex, string $info = 'qdb-aes', int $len = 32): string {
$ikm = @hex2bin(trim($tokenHex));
if ($ikm === false) $ikm = $tokenHex; // Falls bereits binär
if (function_exists('hash_hkdf')) {
return hash_hkdf('sha256', $ikm, $len, $info, '');
}
// Fallback: eigene HKDF-Implementierung
$hashLen = 32;
$salt = str_repeat("\0", $hashLen);
$prk = hash_hmac('sha256', $ikm, $salt, true);
$okm = '';
$t = '';
$n = (int)ceil($len / $hashLen);
for ($i = 1; $i <= $n; $i++) {
$t = hash_hmac('sha256', $t . $info . chr($i), $prk, true);
$okm .= $t;
}
return substr($okm, 0, $len);
}
// --- Token-Storage (mit Ablauf) ---
function tokens_file_path(): string {
return __DIR__ . '/tokens.jsonl';
}
function token_add(string $token, int $ttlSeconds = 86400): void {
$rec = [
'token' => $token,
'created' => time(),
'exp' => time() + $ttlSeconds,
];
file_put_contents(tokens_file_path(), json_encode($rec) . PHP_EOL, FILE_APPEND | LOCK_EX);
// optional weiterhin für Alt-Skripte:
file_put_contents(__DIR__ . '/valid_tokens.txt', $token . PHP_EOL, FILE_APPEND | LOCK_EX);
}
function token_is_valid(string $token): bool {
$f = tokens_file_path();
if (!file_exists($f)) return false;
$h = fopen($f, 'r');
if (!$h) return false;
$now = time();
$valid = false;
while (($line = fgets($h)) !== false) {
$j = json_decode($line, true);
if (!is_array($j)) continue;
if (($j['token'] ?? '') === $token && ($j['exp'] ?? 0) >= $now) {
$valid = true; break;
}
}
fclose($h);
return $valid;
}
// --- 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");
$iv = random_bytes(16);
$cipher = openssl_encrypt($plain, 'AES-256-CBC', $key, OPENSSL_RAW_DATA, $iv);
if ($cipher === false) throw new Exception('openssl_encrypt failed');
return $iv . $cipher;
}
function aes256_cbc_decrypt_bytes(string $data, string $key): string {
if (strlen($data) < 16) throw new Exception('cipher too short');
$key = str_pad(substr($key, 0, 32), 32, "\0");
$iv = substr($data, 0, 16);
$ct = substr($data, 16);
$plain = openssl_decrypt($ct, 'AES-256-CBC', $key, OPENSSL_RAW_DATA, $iv);
if ($plain === false) throw new Exception('openssl_decrypt failed');
return $plain;
}

27
db_download.php Normal file
View File

@ -0,0 +1,27 @@
<?php
// /var/www/html/db_download.php
require_once __DIR__ . '/common.php';
// Token prüfen
$auth = $_SERVER['HTTP_AUTHORIZATION'] ?? ($_SERVER['Authorization'] ?? '');
if (stripos($auth, 'Bearer ') !== 0) { http_response_code(401); echo "Fehlender Bearer-Token"; exit; }
$token = substr($auth, 7);
if (!token_is_valid($token)) { http_response_code(403); echo "Ungültiger oder abgelaufener Token"; exit; }
// DB entschlüsseln
$path = __DIR__ . '/uploads/questionnaire_database';
if (!file_exists($path)) { http_response_code(404); echo "Datenbank nicht gefunden"; exit; }
$enc = file_get_contents($path);
if ($enc === false) { http_response_code(500); echo "Lesefehler"; exit; }
try {
$plain = aes256_cbc_decrypt_bytes($enc, get_master_key_bytes());
} catch (Throwable $e) {
http_response_code(500); echo "Entschlüsselung fehlgeschlagen"; exit;
}
// Ausliefern
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="questionnaire.sqlite"');
header('Content-Length: ' . strlen($plain));
echo $plain;

78
db_view.php Normal file
View File

@ -0,0 +1,78 @@
<?php
// /var/www/html/db_view.php
require_once __DIR__ . '/common.php';
header('Content-Type: text/html; charset=UTF-8');
// ---- 1) Token prüfen (aus Authorization: Bearer <token>) ----
$auth = $_SERVER['HTTP_AUTHORIZATION'] ?? ($_SERVER['Authorization'] ?? '');
if (stripos($auth, 'Bearer ') !== 0) {
http_response_code(401); echo "Fehlender Bearer-Token"; exit;
}
$token = substr($auth, 7);
if (!token_is_valid($token)) {
http_response_code(403); echo "Ungültiger oder abgelaufener Token"; exit;
}
// ---- 2) Master-verschlüsselte DB laden & entschlüsseln ----
$dbPath = __DIR__ . '/uploads/questionnaire_database';
if (!file_exists($dbPath)) { http_response_code(404); echo "Datenbank nicht gefunden"; exit; }
$enc = file_get_contents($dbPath);
if ($enc === false) { http_response_code(500); echo "Lesefehler"; exit; }
try {
$plain = aes256_cbc_decrypt_bytes($enc, get_master_key_bytes());
} catch (Throwable $e) {
http_response_code(500); echo "Entschlüsselung fehlgeschlagen"; exit;
}
// ---- 3) In temporäre SQLite-Datei schreiben ----
$tmp = tempnam(sys_get_temp_dir(), 'qdb_');
file_put_contents($tmp, $plain);
// ---- 4) Tabellen lesen & als HTML ausgeben ----
try {
$pdo = new PDO("sqlite:$tmp");
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// Tabellenliste
$tables = [];
$rs = $pdo->query("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name");
foreach ($rs as $row) $tables[] = $row['name'];
// Ausgabe
echo "<style>h2{margin:24px 0 8px} table{border-collapse:collapse;width:100%} th,td{border:1px solid #ddd;padding:6px 8px;text-align:left} th{background:#f7f7f7}</style>";
echo "<p><b>Tabellen:</b> " . htmlspecialchars(implode(', ', $tables)) . "</p>";
foreach ($tables as $t) {
echo "<h2>" . htmlspecialchars($t) . "</h2>";
// Spaltenüberschriften via PRAGMA
$cols = [];
$cr = $pdo->query("PRAGMA table_info(" . $pdo->quote($t) . ")");
// PRAGMA table_info akzeptiert kein quote(..) direkt, also besser plain escapen:
// Workaround:
$t_esc = preg_replace('/[^A-Za-z0-9_]/', '', $t);
$cr = $pdo->query("PRAGMA table_info($t_esc)");
foreach ($cr as $c) $cols[] = $c['name'];
// Daten holen (ohne Limit; bei sehr großen Tabellen ggf. LIMIT einbauen)
$stmt = $pdo->query("SELECT * FROM $t_esc");
echo "<table><thead><tr>";
foreach ($cols as $c) echo "<th>" . htmlspecialchars($c) . "</th>";
echo "</tr></thead><tbody>";
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
echo "<tr>";
foreach ($cols as $c) {
$val = $row[$c];
if ($val === null) { echo "<td><em>NULL</em></td>"; }
else { echo "<td>" . htmlspecialchars((string)$val) . "</td>"; }
}
echo "</tr>";
}
echo "</tbody></table>";
}
} catch (Throwable $e) {
http_response_code(500); echo "SQLite-Fehler: " . htmlspecialchars($e->getMessage());
} finally {
@unlink($tmp);
}

132
db_view_ordered.php Normal file
View File

@ -0,0 +1,132 @@
<?php
// /var/www/html/db_view_ordered.php
require_once __DIR__ . '/common.php';
header('Content-Type: text/html; charset=UTF-8');
// ---- Auth ----
$auth = $_SERVER['HTTP_AUTHORIZATION'] ?? ($_SERVER['Authorization'] ?? '');
if (stripos($auth, 'Bearer ') !== 0) { http_response_code(401); echo "Missing Bearer token"; exit; }
$token = substr($auth, 7);
if (!token_is_valid($token)) { http_response_code(403); echo "Invalid or expired token"; exit; }
// ---- Paths for order, header labels and value translations ----
$ORDER_JSON = __DIR__ . '/header_order.json';
$LABELS_JSON = __DIR__ . '/questions_en.json'; // Spaltenüberschriften (2. Kopfzeile)
$VALS_JSON = __DIR__ . '/translations_en.json'; // NEU: Werte-Übersetzungen
$order = @json_decode(@file_get_contents($ORDER_JSON), true);
$labels = @json_decode(@file_get_contents($LABELS_JSON), true);
$vals = @json_decode(@file_get_contents($VALS_JSON), true);
if (!is_array($order) || empty($order)) { http_response_code(500); echo "header_order.json missing or empty."; exit; }
if (!is_array($labels)) $labels = [];
if (!is_array($vals)) $vals = [];
function t_val(string $id, string $raw, array $vals): string {
// feste Wörter direkt durchlassen
if ($raw === '' || $raw === 'None' || $raw === 'Done' || $raw === 'Not Done') return $raw;
// Kandidaten: exakt, normalisiert, id+value
$norm = strtolower(preg_replace('/[^a-z0-9]+/i', '_', $raw));
$cands = [$raw];
if ($norm && $norm !== $raw) $cands[] = $norm;
$cands[] = "{$id}_{$raw}";
$cands[] = "{$id}-{$raw}";
$cands[] = "{$id}_{$norm}";
$cands[] = "{$id}-{$norm}";
foreach ($cands as $k) {
if (isset($vals[$k]) && is_string($vals[$k]) && $vals[$k] !== '') return $vals[$k];
}
// Fallback: Versuch mit einfachen globalen Keys (z. B. "yes","no","consent_signed")
if (isset($vals[$norm])) return (string)$vals[$norm];
if (isset($vals[$raw])) return (string)$vals[$raw];
return $raw; // nichts gefunden → Original anzeigen
}
// ---- load & decrypt DB ----
$dbPath = __DIR__ . '/uploads/questionnaire_database';
if (!file_exists($dbPath)) { http_response_code(404); echo "Database not found"; exit; }
$enc = file_get_contents($dbPath);
if ($enc === false) { http_response_code(500); echo "Read error"; exit; }
try { $plain = aes256_cbc_decrypt_bytes($enc, get_master_key_bytes()); }
catch (Throwable $e) { http_response_code(500); echo "Decryption failed"; exit; }
// ---- temp SQLite file ----
$tmp = tempnam(sys_get_temp_dir(), 'qdb_');
file_put_contents($tmp, $plain);
try {
$pdo = new PDO("sqlite:$tmp");
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$clients = $pdo->query("SELECT clientCode FROM clients ORDER BY clientCode")->fetchAll(PDO::FETCH_COLUMN) ?: [];
$questionnaireIds = $pdo->query("SELECT id FROM questionnaires")->fetchAll(PDO::FETCH_COLUMN) ?: [];
$qidSet = array_fill_keys($questionnaireIds, true);
$completed = [];
$stmt = $pdo->query("SELECT clientCode, questionnaireId, isDone FROM completed_questionnaires");
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
$c = $row['clientCode'] ?? 'undefined';
$q = $row['questionnaireId'] ?? 'undefined';
$completed[$c][$q] = ((int)($row['isDone'] ?? 0)) ? true : false;
}
$answers = [];
$stmt = $pdo->query("SELECT clientCode, questionId, answerValue FROM answers");
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
$c = $row['clientCode'] ?? 'undefined';
$q = $row['questionId'] ?? 'undefined';
$answers[$c][$q] = (string)($row['answerValue'] ?? '');
}
// ---- HTML ----
echo '<style>
table{border-collapse:collapse;width:100%;}
th,td{border:1px solid #ddd;padding:6px 8px;text-align:left;vertical-align:top;}
thead th{background:#f7f7f7;position:sticky;top:0;z-index:1;}
thead tr:nth-child(2) th{background:#fafafa;}
.nowrap{white-space:nowrap;}
.chkcol{width:36px;text-align:center;}
</style>';
echo '<table id="qdb-table">';
echo '<thead>';
// Row 1: checkbox + IDs (bleiben als technische Schlüssel im Kopf Werte darunter sind übersetzt)
echo '<tr><th class="chkcol"><input type="checkbox" id="chkAll" title="select all"></th>';
foreach ($order as $id) echo '<th data-id="'.htmlspecialchars($id).'">'.htmlspecialchars($id).'</th>';
echo '</tr>';
// Row 2: blank + English labels
echo '<tr><th class="chkcol"></th>';
foreach ($order as $id) {
$lbl = $labels[$id] ?? '';
echo '<th>'.htmlspecialchars($lbl).'</th>';
}
echo '</tr></thead><tbody>';
foreach ($clients as $client) {
echo '<tr data-client="'.htmlspecialchars($client).'">';
echo '<td class="chkcol"><input type="checkbox" class="rowchk"></td>';
foreach ($order as $id) {
if ($id === 'client_code') {
$val = $client;
} elseif (isset($qidSet[$id]) && strpos($id, '-') === false) {
$val = ($completed[$client][$id] ?? false) ? 'Done' : 'Not Done';
} else {
$raw = $answers[$client][$id] ?? '';
$val = (strlen(trim($raw)) > 0) ? t_val($id, $raw, $vals) : 'None';
}
echo '<td>'.htmlspecialchars($val).'</td>';
}
echo '</tr>';
}
echo '</tbody></table>';
} catch (Throwable $e) {
http_response_code(500);
echo "Error: ".htmlspecialchars($e->getMessage());
} finally {
@unlink($tmp);
}

38
downloadFull.php Executable file
View File

@ -0,0 +1,38 @@
<?php
// /var/www/html/downloadFull.php
require_once __DIR__ . '/common.php';
// Bearer Token
$headers = function_exists('getallheaders') ? getallheaders() : [];
$auth = $headers['Authorization'] ?? $headers['authorization'] ?? '';
if (stripos($auth, 'Bearer ') !== 0) {
http_response_code(403); echo "Kein Token übergeben"; exit;
}
$token = substr($auth, 7);
if (!token_is_valid($token)) {
http_response_code(403); echo "Ungültiger Token"; exit;
}
// Master-verschlüsselte DB laden
$dbPath = __DIR__ . '/uploads/questionnaire_database';
if (!file_exists($dbPath)) { http_response_code(404); echo "Datenbank nicht gefunden"; exit; }
$encMaster = file_get_contents($dbPath);
if ($encMaster === false) { http_response_code(500); echo "Lesefehler"; exit; }
// Mit Master-Key entschlüsseln
$masterKey = get_master_key_bytes();
try {
$plain = aes256_cbc_decrypt_bytes($encMaster, $masterKey);
} catch (Throwable $e) {
http_response_code(500); echo "Entschlüsselung fehlgeschlagen"; exit;
}
// Für diese Session neu verschlüsseln (Key aus Token via HKDF)
$sessionKey = hkdf_session_key_from_token($token);
$out = aes256_cbc_encrypt_bytes($plain, $sessionKey);
// Ausliefern
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="questionnaire_database"');
header('Content-Length: ' . strlen($out));
echo $out;

106
header_order.json Normal file
View File

@ -0,0 +1,106 @@
[
"client_code",
"questionnaire_1_demographic_information",
"questionnaire_1_demographic_information-coach_code",
"questionnaire_1_demographic_information-consent_instruction",
"questionnaire_1_demographic_information-no_consent_entered",
"questionnaire_1_demographic_information-accommodation",
"questionnaire_1_demographic_information-other_accommodation",
"questionnaire_1_demographic_information-client_code_entry_question",
"questionnaire_1_demographic_information-age",
"questionnaire_1_demographic_information-gender",
"questionnaire_1_demographic_information-country_of_origin",
"questionnaire_1_demographic_information-departure_country",
"questionnaire_1_demographic_information-since_in_germany",
"questionnaire_1_demographic_information-living_situation",
"questionnaire_1_demographic_information-number_family_members",
"questionnaire_1_demographic_information-languages_spoken",
"questionnaire_1_demographic_information-german_skills",
"questionnaire_1_demographic_information-school_years_total",
"questionnaire_1_demographic_information-school_years_origin",
"questionnaire_1_demographic_information-school_years_transit",
"questionnaire_1_demographic_information-school_years_germany",
"questionnaire_1_demographic_information-vocational_training",
"questionnaire_1_demographic_information-provisional_accommodation_since",
"questionnaire_2_rhs",
"questionnaire_2_rhs-coach_code",
"questionnaire_2_rhs-glass_explanation",
"questionnaire_2_rhs-q1_symptom",
"questionnaire_2_rhs-q2_symptom",
"questionnaire_2_rhs-q3_symptom",
"questionnaire_2_rhs-q4_symptom",
"questionnaire_2_rhs-q5_symptom",
"questionnaire_2_rhs-q6_symptom",
"questionnaire_2_rhs-q7_symptom",
"questionnaire_2_rhs-q8_symptom",
"questionnaire_2_rhs-q9_symptom",
"questionnaire_2_rhs-q10_reexperience_trauma",
"questionnaire_2_rhs-q11_physical_reaction",
"questionnaire_2_rhs-q12_emotional_numbness",
"questionnaire_2_rhs-q13_easily_startled",
"questionnaire_2_rhs-q14_intro",
"questionnaire_2_rhs-pain_rating_instruction",
"questionnaire_2_rhs-violence_question_1",
"questionnaire_2_rhs-times_happend",
"questionnaire_2_rhs-age_at_incident",
"questionnaire_2_rhs-conflict_since_arrival",
"questionnaire_2_rhs-times_happend2",
"questionnaire_2_rhs-age_at_incident2",
"questionnaire_2_rhs-asylum_procedure_since",
"questionnaire_3_integration_index",
"questionnaire_3_integration_index-coach_code",
"questionnaire_3_integration_index-feeling_connected_to_germany_question",
"questionnaire_3_integration_index-understanding_political_issues",
"questionnaire_3_integration_index-unexpected_expense_question",
"questionnaire_3_integration_index-dining_with_germans_question",
"questionnaire_3_integration_index-reading_german_articles_question",
"questionnaire_3_integration_index-visiting_doctor_question",
"questionnaire_3_integration_index-feeling_as_outsider_question",
"questionnaire_3_integration_index-recent_occupation_question",
"questionnaire_3_integration_index-discussing_politics_question",
"questionnaire_3_integration_index-contact_with_germans_question",
"questionnaire_3_integration_index-speaking_german_opinion_question",
"questionnaire_3_integration_index-job_search_question",
"questionnaire_4_consultation_results",
"questionnaire_4_consultation_results-coach_code",
"questionnaire_4_consultation_results-date_consultation_health_interview_result",
"questionnaire_4_consultation_results-consultation_decision",
"questionnaire_4_consultation_results-consent_conversation_in_6_months",
"questionnaire_4_consultation_results-participation_in_coaching",
"questionnaire_4_consultation_results-consent_coaching_given",
"questionnaire_4_consultation_results-consent_conversation_in_6_months",
"questionnaire_4_consultation_results-decision_after_reflection_period",
"questionnaire_4_consultation_results-professional_referral",
"questionnaire_4_consultation_results-confidentiality_agreement",
"questionnaire_4_consultation_results-health_insurance_card",
"questionnaire_4_consultation_results-consent_conversation_in_6_months",
"questionnaire_5_final_interview",
"questionnaire_5_final_interview-coach_code",
"questionnaire_5_final_interview-consent_followup_6_months",
"questionnaire_5_final_interview-date_final_interview",
"questionnaire_5_final_interview-amount_nat_appointments",
"questionnaire_5_final_interview-amount_session_flowers",
"questionnaire_5_final_interview-amount_session_stones",
"questionnaire_5_final_interview-termination_nat_coaching",
"questionnaire_5_final_interview-client_canceled_NAT",
"questionnaire_6_follow_up_survey",
"questionnaire_6_follow_up_survey-coach_code",
"questionnaire_6_follow_up_survey-follow_up",
"questionnaire_6_follow_up_survey-special_burden_question",
"questionnaire_6_follow_up_survey-glass_explanation",
"questionnaire_6_follow_up_survey-how_strong_past_month",
"questionnaire_6_follow_up_survey-q14_intro",
"questionnaire_6_follow_up_survey-pain_rating_instruction",
"questionnaire_6_follow_up_survey-feeling_connected_to_germany_question",
"questionnaire_6_follow_up_survey-understanding_political_issues",
"questionnaire_6_follow_up_survey-unexpected_expense_question",
"questionnaire_6_follow_up_survey-dining_with_germans_question",
"questionnaire_6_follow_up_survey-reading_german_articles_question",
"questionnaire_6_follow_up_survey-visiting_doctor_question",
"questionnaire_6_follow_up_survey-feeling_as_outsider_question",
"questionnaire_6_follow_up_survey-recent_occupation_question",
"questionnaire_6_follow_up_survey-discussing_politics_question",
"questionnaire_6_follow_up_survey-contact_with_germans_question",
"questionnaire_6_follow_up_survey-speaking_german_opinion_question",
"questionnaire_6_follow_up_survey-job_search_question"
]

330
index.html Normal file
View File

@ -0,0 +1,330 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Questionnaire Database</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style>
body { font-family: system-ui, sans-serif; margin: 20px; }
.card { border: 1px solid #ddd; border-radius: 12px; padding: 16px; margin-bottom: 16px; width: 100%; box-sizing: border-box; }
.hidden { display: none; }
.actions { margin: 12px 0; display:flex; gap:8px; align-items:center; flex-wrap:wrap; }
button, a.button { padding:8px 12px; border:1px solid #ccc; border-radius:8px; background:#fff; cursor:pointer; text-decoration:none; }
button:hover, a.button:hover { background:#f7f7f7; }
.muted { color:#666; }
/* Nur der Tabellenbereich scrollt vertikal */
.table-wrap { overflow-x: auto; border-radius: 8px; }
table { border-collapse: collapse; width: 100%; margin: 8px 0 24px; }
th, td { border: 1px solid #ddd; padding: 6px 8px; text-align: left; }
/* BEIDE Kopfzeilen fixieren */
#qdb-table thead tr:nth-child(1) th {
position: sticky;
top: 0;
z-index: 3;
background: #f7f7f7;
}
#qdb-table thead tr:nth-child(2) th {
position: sticky;
top: var(--head1h, 34px); /* wird per JS exakt gesetzt */
z-index: 2;
background: #f7f7f7;
}
.row-highlight { animation: hi 1.2s ease-in-out 0s 1; }
@keyframes hi { 0% { background:#fff9c4; } 100% { background:transparent; } }
.find-wrap { margin-left:auto; display:flex; gap:6px; align-items:center; }
.find-wrap input[type="text"] { padding:6px 8px; border:1px solid #ccc; border-radius:8px; min-width:160px; }
/* kompaktere „lange“ Spalte (2. Daten-Spalte nach Checkbox) */
#qdb-table th:nth-child(3),
#qdb-table td:nth-child(3) {
max-width: 260px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
</style>
<script src="https://cdn.jsdelivr.net/npm/xlsx@0.18.5/dist/xlsx.full.min.js"></script>
</head>
<body>
<h1>Questionnaire Database</h1>
<div id="loginCard" class="card">
<h2>Login</h2>
<form id="loginForm" autocomplete="on">
<label>Username<br><input id="username" required autocomplete="username" /></label><br><br>
<label>Password<br><input id="password" type="password" required autocomplete="current-password" /></label><br><br>
<button>Sign in</button>
</form>
<p id="loginMsg"></p>
</div>
<div id="appCard" class="card hidden">
<div class="actions">
<span>Signed in as <b id="who"></b>.</span>
<button id="logoutBtn">Logout</button>
<button id="exportSelectedBtn">Download</button>
<span id="selInfo" class="muted">(0 selected)</span>
<div class="find-wrap">
<label for="findClient">Find client:</label>
<input id="findClient" type="text" list="clientList" placeholder="Client code" />
<datalist id="clientList"></datalist>
<button id="findBtn" type="button">Go</button>
</div>
</div>
<div class="table-wrap" id="tableWrap">
<div id="dbContainer"><em>Loading…</em></div>
</div>
</div>
<script>
const $ = sel => document.querySelector(sel);
function setLoggedIn(user, token) {
localStorage.setItem('qdb_user', user);
localStorage.setItem('qdb_token', token);
$('#who').textContent = user;
$('#loginCard').classList.add('hidden');
$('#appCard').classList.remove('hidden');
loadDbView();
}
function setLoggedOut() {
localStorage.removeItem('qdb_user');
localStorage.removeItem('qdb_token');
$('#dbContainer').innerHTML = '';
$('#appCard').classList.add('hidden');
$('#loginCard').classList.remove('hidden');
}
async function login(evt) {
evt.preventDefault();
$('#loginMsg').textContent = 'Signing in…';
try {
const res = await fetch('login.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
username: $('#username').value,
password: $('#password').value
})
});
const data = await res.json();
if (!res.ok || !data.success) {
$('#loginMsg').textContent = data.message || 'Login failed';
return;
}
$('#loginMsg').textContent = '';
setLoggedIn(data.user, data.token);
} catch (e) {
$('#loginMsg').textContent = 'Error: ' + e.message;
}
}
function sizeTableArea() {
const wrap = document.getElementById('tableWrap');
if (!wrap) return;
const rect = wrap.getBoundingClientRect();
const top = rect.top;
const avail = window.innerHeight - top - 16;
wrap.style.maxHeight = (avail > 200 ? avail : 200) + 'px';
wrap.style.overflowY = 'auto';
}
async function loadDbView() {
const token = localStorage.getItem('qdb_token');
if (!token) return setLoggedOut();
$('#dbContainer').innerHTML = '<em>Loading…</em>';
try {
const res = await fetch('db_view_ordered.php', {
headers: { 'Authorization': 'Bearer ' + token }
});
if (res.status === 401 || res.status === 403) {
setLoggedOut();
alert('Session expired or invalid. Please sign in again.');
return;
}
const html = await res.text();
$('#dbContainer').innerHTML = html;
const table = document.querySelector('#qdb-table');
const selInfo = $('#selInfo');
function updateCount(){
const n = table.querySelectorAll('tbody .rowchk:checked').length;
selInfo.textContent = `(${n} selected)`;
}
const chkAll = table.querySelector('#chkAll');
if (chkAll) {
chkAll.addEventListener('change', () => {
table.querySelectorAll('tbody .rowchk').forEach(ch => ch.checked = chkAll.checked);
updateCount();
});
}
table.querySelectorAll('tbody .rowchk').forEach(ch => ch.addEventListener('change', updateCount));
updateCount();
$('#exportSelectedBtn').onclick = () => exportSelectedToXLSX();
buildClientSearchList();
const findInput = $('#findClient');
const findBtn = $('#findBtn');
findBtn.onclick = () => gotoClientRow(findInput.value.trim());
findInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter') { e.preventDefault(); findBtn.click(); }
});
/* Höhe der 1. Kopfzeile messen und als CSS-Variable setzen,
damit die 2. Kopfzeile darunter sticky stehen bleibt */
if (table && table.tHead && table.tHead.rows.length >= 1) {
const h1 = table.tHead.rows[0].getBoundingClientRect().height || 34;
table.style.setProperty('--head1h', `${Math.round(h1)}px`);
}
sizeTableArea();
} catch (e) {
$('#dbContainer').innerHTML = '<b>Error:</b> ' + e.message;
}
}
window.addEventListener('resize', sizeTableArea);
function exportSelectedToXLSX() {
const table = document.querySelector('#qdb-table');
if (!table) return alert('Table not found.');
const headRows = table.tHead.rows;
if (headRows.length < 2) return alert('Header rows missing.');
const ids = [];
for (let i=1; i<headRows[0].cells.length; i++) {
const th = headRows[0].cells[i];
const id = th.getAttribute('data-id') || th.textContent.trim();
ids.push(id);
}
const labels = [];
for (let i=1; i<headRows[1].cells.length; i++) {
labels.push(headRows[1].cells[i].textContent.trim());
}
const rows = Array.from(table.tBodies[0].rows)
.filter(tr => tr.querySelector('.rowchk')?.checked);
if (rows.length === 0) {
alert('Please select at least one row.');
return;
}
const aoa = [];
aoa.push([...ids]);
aoa.push([...labels]);
rows.forEach(tr => {
const tds = tr.cells;
const arr = [];
for (let i=1; i<tds.length; i++) arr.push(tds[i].textContent.trim());
aoa.push(arr);
});
const ws = XLSX.utils.aoa_to_sheet(aoa);
ws['!cols'] = ids.map(()=>({wch:36}));
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, 'Headers');
XLSX.writeFile(wb, 'SelectedClients.xlsx');
}
/* ======= Suche (nur Client-Code) ======= */
function buildClientSearchList() {
const table = document.querySelector('#qdb-table');
if (!table) return;
let clientCol = -1;
const thead = table.tHead;
const rows = thead ? thead.rows : [];
if (rows.length >= 2) {
const cells2 = Array.from(rows[1].cells).map(c => c.textContent.trim().toLowerCase());
clientCol = cells2.findIndex(t => t === 'client code' || t === 'client_code');
}
if (clientCol < 0 && rows.length >= 1) {
const cells1 = Array.from(rows[0].cells).map(c => (c.getAttribute('data-id') || c.textContent).trim().toLowerCase());
clientCol = cells1.findIndex(t => t === 'client code' || t === 'client_code');
}
if (clientCol < 0) return;
const dl = document.querySelector('#clientList');
dl.innerHTML = '';
const codes = new Set();
table.tBodies[0].querySelectorAll('tr').forEach(tr => {
const td = tr.cells[clientCol];
if (!td) return;
const val = td.textContent.trim();
if (val) codes.add(val);
});
Array.from(codes).sort((a,b)=> (''+a).localeCompare(''+b, undefined, {numeric:true}))
.forEach(code => {
const opt = document.createElement('option');
opt.value = code;
dl.appendChild(opt);
});
}
function gotoClientRow(query) {
if (!query) return;
const table = document.querySelector('#qdb-table');
if (!table) return;
let clientCol = -1;
const thead = table.tHead;
const rows = thead ? thead.rows : [];
if (rows.length >= 2) {
const cells2 = Array.from(rows[1].cells).map(c => c.textContent.trim().toLowerCase());
clientCol = cells2.findIndex(t => t === 'client code' || t === 'client_code');
}
if (clientCol < 0 && rows.length >= 1) {
const cells1 = Array.from(rows[0].cells).map(c => (c.getAttribute('data-id') || c.textContent).trim().toLowerCase());
clientCol = cells1.findIndex(t => t === 'client code' || t === 'client_code');
}
if (clientCol < 0) return;
const tryCols = [clientCol, clientCol + 1, Math.max(0, clientCol - 1)];
const bodyRows = Array.from(table.tBodies[0].rows);
let target = null;
outer:
for (const tr of bodyRows) {
for (const col of tryCols) {
const txt = (tr.cells[col]?.textContent || '').trim();
if (!txt) continue;
if (txt === query || txt.toLowerCase().includes(query.toLowerCase())) {
target = tr;
break outer;
}
}
}
if (target) {
target.scrollIntoView({ behavior: 'smooth', block: 'center' });
target.classList.add('row-highlight');
setTimeout(() => target.classList.remove('row-highlight'), 1800);
} else {
alert('No matching client found.');
}
}
/* ======= Ende Suche ======= */
$('#loginForm').addEventListener('submit', login);
$('#logoutBtn').addEventListener('click', setLoggedOut);
const t = localStorage.getItem('qdb_token');
const u = localStorage.getItem('qdb_user');
if (t && u) setLoggedIn(u, t);
</script>
</body>
</html>

91
login.php Normal file
View File

@ -0,0 +1,91 @@
<?php
// /var/www/html/login.php
require_once __DIR__ . '/common.php';
header('Content-Type: application/json; charset=UTF-8');
// Default-Logins (Startzustand)
$USERS_DEFAULT = [
'user01' => 'pw1',
'user02' => 'pw2',
'Tmp_Daniel' => 'dummy4',
'Tmp_Johanna' => 'dummy1',
'Tmp_Anke' => 'dummy2',
'Tmp_Liliana' => 'dummy3',
'Amy_tmp' => 'dummy4',
'Brigitte_tmp' => 'dummy5',
'Extra1_tmp' => 'dummy6',
'Extra2_tmp' => 'dummy7',
'Danny' => 'pw1',
'User1' => 'pw1',
'User2' => 'pw2',
'User3' => 'pw3',
'User4' => 'pw4',
'User5' => 'pw5',
'User6' => 'pw6',
'User7' => 'pw7',
'User8' => 'pw8',
'User9' => 'pw9',
'User10' => 'pw10',
'User11' => 'pw11'
];
$STORE_FILE = __DIR__ . '/users.json'; // persistenter Speicher für gehashte Passwörter
function load_store($path) {
if (!file_exists($path)) return [];
$txt = file_get_contents($path);
$arr = json_decode($txt, true);
return is_array($arr) ? $arr : [];
}
function save_store($path, $data) {
file_put_contents($path, json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
}
$raw = file_get_contents("php://input");
$data = json_decode($raw, true) ?: [];
$username = trim($data['username'] ?? '');
$password = (string)($data['password'] ?? '');
if ($username === '' || $password === '') {
http_response_code(400);
echo json_encode(["success" => false, "message" => "Username/Passwort fehlt"]);
exit;
}
// 1) Prüfe, ob Nutzer bereits ein *eigenes*, gehashtes Passwort hat
$store = load_store($STORE_FILE); // Struktur: ["user01" => ["hash" => "...", "changedAt" => 1234567890]]
if (array_key_exists($username, $store)) {
$hash = (string)($store[$username]['hash'] ?? '');
if ($hash !== '' && password_verify($password, $hash)) {
// ok -> normaler Login
$token = bin2hex(random_bytes(32));
token_add($token, 30 * 24 * 60 * 60);
echo json_encode(["success" => true, "token" => $token, "user" => $username]);
exit;
}
// hat schon Hash, aber PW falsch
http_response_code(401);
echo json_encode(["success" => false, "message" => "Ungültige Zugangsdaten"]);
exit;
}
// 2) Nutzer hat *noch* keinen Hash -> nur Default-Zugang erlaubt
if (!array_key_exists($username, $USERS_DEFAULT)) {
http_response_code(401);
echo json_encode(["success" => false, "message" => "Ungültige Zugangsdaten"]);
exit;
}
if ($USERS_DEFAULT[$username] !== $password) {
http_response_code(401);
echo json_encode(["success" => false, "message" => "Ungültige Zugangsdaten"]);
exit;
}
// 3) Default-Passwort korrekt -> Passwortwechsel erzwingen
echo json_encode([
"success" => true,
"user" => $username,
"must_change_password" => true,
"message" => "Bitte Passwort ändern."
]);

104
questions_en.json Normal file
View File

@ -0,0 +1,104 @@
{
"client_code": "Client code",
"questionnaire_1_demographic_information": "Questionnaire status",
"questionnaire_1_demographic_information-coach_code": "Coach Code",
"questionnaire_1_demographic_information-consent_instruction": "Obtain client consent (separate document)",
"questionnaire_1_demographic_information-no_consent_entered": "You have indicated that the client has not signed the consent form.",
"questionnaire_1_demographic_information-accommodation": "Accommodation",
"questionnaire_1_demographic_information-other_accommodation": "Other accommodation (name)",
"questionnaire_1_demographic_information-client_code_entry_question": "Please enter client code and coach code.",
"questionnaire_1_demographic_information-age": "Age: How old are you?",
"questionnaire_1_demographic_information-gender": "Gender",
"questionnaire_1_demographic_information-country_of_origin": "Country of origin: where are you from?",
"questionnaire_1_demographic_information-departure_country": "When did you leave your country of origin?",
"questionnaire_1_demographic_information-since_in_germany": "Since when have you been in Germany?",
"questionnaire_1_demographic_information-living_situation": "How are you living here?",
"questionnaire_1_demographic_information-number_family_members": "How many family members are you living with?",
"questionnaire_1_demographic_information-languages_spoken": "Which languages do you speak?",
"questionnaire_1_demographic_information-german_skills": "How would you rate your German language skills?",
"questionnaire_1_demographic_information-school_years_total": "How many years did you attend school?",
"questionnaire_1_demographic_information-school_years_origin": "How many years did you attend school? (in your home country)",
"questionnaire_1_demographic_information-school_years_transit": "How many years did you attend school? (while in transit)",
"questionnaire_1_demographic_information-school_years_germany": "How many years did you attend school? (in Germany)",
"questionnaire_1_demographic_information-vocational_training": "Have you completed vocational training?",
"questionnaire_1_demographic_information-provisional_accommodation_since": "Since when have you been in provisional accommodation?",
"questionnaire_2_rhs": "Questionnaire status",
"questionnaire_2_rhs-coach_code": "Coach Code",
"questionnaire_2_rhs-glass_explanation": "The response options are shown in words and as a picture of a glass container: if something does not apply at all, the glass is empty; if it applies somewhat or fairly, the glass fills up; if it applies extremely, the glass is full.",
"questionnaire_2_rhs-q1_symptom": "1. Muscle, bone, or joint pain",
"questionnaire_2_rhs-q2_symptom": "2. Feeling unhappy, sad, or depressed most of the time",
"questionnaire_2_rhs-q3_symptom": "3. Overthinking or worrying too much",
"questionnaire_2_rhs-q4_symptom": "4. Feeling helpless",
"questionnaire_2_rhs-q5_symptom": "5. Being easily startled for no reason",
"questionnaire_2_rhs-q6_symptom": "6. Fatigue, dizziness, or feeling weak",
"questionnaire_2_rhs-q7_symptom": "7. Feeling nervous or insecure",
"questionnaire_2_rhs-q8_symptom": "8. Feeling restless, unable to sit still",
"questionnaire_2_rhs-q9_symptom": "9. Feeling like crying easily",
"questionnaire_2_rhs-q10_reexperience_trauma": "10. ... re-experiencing the trauma; behaving or feeling as if it were happening again?",
"questionnaire_2_rhs-q11_physical_reaction": "11. ... experiencing physical reactions (e.g., sweating, rapid heartbeat) when reminded of the trauma?",
"questionnaire_2_rhs-q12_emotional_numbness": "12. ... feeling emotionally numb (e.g., feeling sad but unable to cry, unable to feel loving emotions)?",
"questionnaire_2_rhs-q13_easily_startled": "13. ... being more easily startled (e.g., when someone approaches from behind)?",
"questionnaire_2_rhs-q14_intro": "14. Thinking about your life in the past four weeks, to what extent do you feel that you:",
"questionnaire_2_rhs-pain_rating_instruction": "Choose the number (010) that best describes how much suffering the respondent experienced in the past week, including today.",
"questionnaire_2_rhs-violence_question_1": "Have you ever been injured by others so seriously that you needed medical attention? (doctor, hospital)",
"questionnaire_2_rhs-times_happend": "Has this ... happened?",
"questionnaire_2_rhs-age_at_incident": "At what age: at ... years?",
"questionnaire_2_rhs-conflict_since_arrival": "Since arriving in Germany, have you ever been involved in violent conflicts (physical fights, physical or psychological conflicts)?",
"questionnaire_2_rhs-times_happend2": "Has this ... happened?",
"questionnaire_2_rhs-age_at_incident2": "At what age: at ... years?",
"questionnaire_2_rhs-asylum_procedure_since": "Since when has your asylum procedure been ongoing?",
"questionnaire_3_integration_index": "Questionnaire status",
"questionnaire_3_integration_index-coach_code": "Coach Code",
"questionnaire_3_integration_index-feeling_connected_to_germany_question": "How connected do you feel to Germany (how well have you settled in Germany)?",
"questionnaire_3_integration_index-understanding_political_issues": "How well do you understand the most important political issues in Germany?",
"questionnaire_3_integration_index-unexpected_expense_question": "Imagine you had to make an unexpected but necessary payment. Which of the following amounts could you pay or afford (if you had free access to money)?",
"questionnaire_3_integration_index-dining_with_germans_question": "How often have you dined (e.g. dinner) with Germans in the past 12 months?",
"questionnaire_3_integration_index-reading_german_articles_question": "How well can you read simple newspaper articles in German about familiar topics (health, home, family, etc.) and understand the main points?",
"questionnaire_3_integration_index-visiting_doctor_question": "How easy or difficult would it be for you to see a doctor in this country? (making an appointment, etc. - not: 'you can't get an appointment', 'I would never go to a doctor here')",
"questionnaire_3_integration_index-feeling_as_outsider_question": "How often do you feel like an outsider in Germany?",
"questionnaire_3_integration_index-recent_occupation_question": "How have you been occupied in the last four weeks? (Please select only one answer)",
"questionnaire_3_integration_index-discussing_politics_question": "Since arriving in Germany, how often have you discussed important political topics in Germany with other people?",
"questionnaire_3_integration_index-contact_with_germans_question": "With how many Germans have you exchanged messages (phone, messenger chat (WhatsApp, Facebook...), or SMS) in the last 4 weeks?",
"questionnaire_3_integration_index-speaking_german_opinion_question": "How well can you participate in a conversation in German on familiar topics (children, family, school, home, etc.) and express your opinion?",
"questionnaire_3_integration_index-job_search_question": "How easy or difficult would it be for you to search for and find a job in this country (even if you may not be allowed to work: what would be your assumption if there were no restrictions)?",
"questionnaire_4_consultation_results": "Questionnaire status",
"questionnaire_4_consultation_results-coach_code": "Coach Code",
"questionnaire_4_consultation_results-date_consultation_health_interview_result": "Date of counseling interview (health interview result green/yellow/red)",
"questionnaire_4_consultation_results-consultation_decision": "Counseling decision (<b><font color='#9E9E9E'>0</font></b>)",
"questionnaire_4_consultation_results-consent_conversation_in_6_months": "Consent for conversation in 6 months:",
"questionnaire_4_consultation_results-participation_in_coaching": "Participation In Coaching",
"questionnaire_4_consultation_results-consent_coaching_given": "Consent for coaching is given",
"questionnaire_4_consultation_results-decision_after_reflection_period": "Decision on .............. (date) after reflection period",
"questionnaire_4_consultation_results-professional_referral": "Professional Referral",
"questionnaire_4_consultation_results-confidentiality_agreement": "Confidentiality Agreement",
"questionnaire_4_consultation_results-health_insurance_card": "Health Insurance Card",
"questionnaire_5_final_interview": "Final Interview",
"questionnaire_5_final_interview-coach_code": "Coach Code",
"questionnaire_5_final_interview-consent_followup_6_months": "Consent for conversation in 6 months",
"questionnaire_5_final_interview-date_final_interview": "Date of final interview",
"questionnaire_5_final_interview-amount_nat_appointments": "Number of NAT appointments (after the counseling interview)",
"questionnaire_5_final_interview-amount_session_flowers": "Number of storytelling sessions with flowers",
"questionnaire_5_final_interview-amount_session_stones": "Number of storytelling sessions with stones",
"questionnaire_5_final_interview-termination_nat_coaching": "Termination of NAT coaching",
"questionnaire_5_final_interview-client_canceled_NAT": "Client discontinued NAT",
"questionnaire_6_follow_up_survey": "Questionnaire status",
"questionnaire_6_follow_up_survey-coach_code": "Coach Code",
"questionnaire_6_follow_up_survey-follow_up": "Follow-up survey",
"questionnaire_6_follow_up_survey-special_burden_question": "Was there any special burden?",
"questionnaire_6_follow_up_survey-glass_explanation": "The response options are shown in words and as a picture of a glass container: if something does not apply at all, the glass is empty; if it applies somewhat or fairly, the glass fills up; if it applies extremely, the glass is full.",
"questionnaire_6_follow_up_survey-how_strong_past_month": "How strongly did you experience the following in the past month...",
"questionnaire_6_follow_up_survey-q14_intro": "14. Thinking about your life in the past four weeks, to what extent do you feel that you:",
"questionnaire_6_follow_up_survey-pain_rating_instruction": "Choose the number (010) that best describes how much suffering the respondent experienced in the past week, including today.",
"questionnaire_6_follow_up_survey-feeling_connected_to_germany_question": "How connected do you feel to Germany (how well have you settled in Germany)?",
"questionnaire_6_follow_up_survey-understanding_political_issues": "How well do you understand the most important political issues in Germany?",
"questionnaire_6_follow_up_survey-unexpected_expense_question": "Imagine you had to make an unexpected but necessary payment. Which of the following amounts could you pay or afford (if you had free access to money)?",
"questionnaire_6_follow_up_survey-dining_with_germans_question": "How often have you dined (e.g. dinner) with Germans in the past 12 months?",
"questionnaire_6_follow_up_survey-reading_german_articles_question": "How well can you read simple newspaper articles in German about familiar topics (health, home, family, etc.) and understand the main points?",
"questionnaire_6_follow_up_survey-visiting_doctor_question": "How easy or difficult would it be for you to see a doctor in this country? (making an appointment, etc. - not: 'you can't get an appointment', 'I would never go to a doctor here')",
"questionnaire_6_follow_up_survey-feeling_as_outsider_question": "How often do you feel like an outsider in Germany?",
"questionnaire_6_follow_up_survey-recent_occupation_question": "How have you been occupied in the last four weeks? (Please select only one answer)",
"questionnaire_6_follow_up_survey-discussing_politics_question": "Since arriving in Germany, how often have you discussed important political topics in Germany with other people?",
"questionnaire_6_follow_up_survey-contact_with_germans_question": "With how many Germans have you exchanged messages (phone, messenger chat (WhatsApp, Facebook...), or SMS) in the last 4 weeks?",
"questionnaire_6_follow_up_survey-speaking_german_opinion_question": "How well can you participate in a conversation in German on familiar topics (children, family, school, home, etc.) and express your opinion?",
"questionnaire_6_follow_up_survey-job_search_question": "How easy or difficult would it be for you to search for and find a job in this country (even if you may not be allowed to work: what would be your assumption if there were no restrictions)?"
}

192
tokens.jsonl Normal file
View File

@ -0,0 +1,192 @@
{"token":"731c101342f056ba57da7f958ba683fb41b0381d42de69090bcabee8c1b3a389","created":1756809382,"exp":1756895782}
{"token":"a1ba2845684ab849d364a84027670c3c6357028b3d1afb5f5b4868e904805235","created":1756809404,"exp":1756895804}
{"token":"6d2a7a1686cddb29441dfbe2e261d853ec55a1d46e1bbb8db3e4f8d0ce953937","created":1756809519,"exp":1756895919}
{"token":"1a0bc695ff5854e16b76dc60d5cbf51529942430b2d5315a36e0266136b9ab43","created":1756809545,"exp":1756895945}
{"token":"375ace02859b27ca837bfa5a130e6237b7c7b39720ff5d880b3519e2c8bbff57","created":1756809577,"exp":1756895977}
{"token":"95c8f8cd5a300c2357fb237c55932dd15079d777c69ac9c523f347c98e6a196c","created":1756809590,"exp":1756895990}
{"token":"66048cfaf8ba428c18125328d3d92ee5ef85fb5da4b57ca10e30ed49e65cb7a3","created":1756990520,"exp":1757076920}
{"token":"74b33d1ec4314def382db73b25a68bb835b847399587b9e1c753b5fc6d7ac526","created":1756990626,"exp":1757077026}
{"token":"cf64baa593827d9d213f65da58f68e4be4fede9d1d089f667247985244218b79","created":1757055382,"exp":1757141782}
{"token":"91161cab2fad81cc2fbd5bec8990bc763169b9151bd5686d5643a7df119ddbeb","created":1757057047,"exp":1757143447}
{"token":"01390c7efae09a04917042c7a33ee669f1f399aea1ad6e38fdf0ea128cb56293","created":1757057152,"exp":1757143552}
{"token":"76da7ea81a475703f54e628d34349cf58825f5c69a83cd7ac28864d46a070f3e","created":1757057205,"exp":1757143605}
{"token":"f39965dc4374d6e6a4ff1d7f3cfcebf8a46591496778de606d5e8be6583fc32b","created":1757057967,"exp":1757144367}
{"token":"91d056c9e6c74d6cfb3a7dfc490e3fb74e0fa1107a644fa9ebc770046ad10108","created":1757058317,"exp":1757144717}
{"token":"a50ca166c451e4eb1a6ecfbd8e8947ab33adc41638cfe490844d4f216df1b2b7","created":1757058371,"exp":1757144771}
{"token":"4fa698c244a21802e1001cd9e2ca95e3836f8a40b87130ba0bbf56e4409a5258","created":1757058387,"exp":1757144787}
{"token":"a0c28eaaf54cc6c85b0aa28abaf3579f7c730127f84782223b549eb69a4fbda2","created":1757315000,"exp":1757401400}
{"token":"e03f93c020e7a1e63ca7bd35e8996effad1f164e771747248086e6e1062361a9","created":1757318539,"exp":1757404939}
{"token":"a2b06b8b6d4c4f13c0e1764b444c9d54b6391a44df11d76e07b388f010cef535","created":1757318594,"exp":1757404994}
{"token":"df8b9dc5ac814565c4fc7543d7dca8385103de68c3463c4a6bf751ec935a930e","created":1757319230,"exp":1757405630}
{"token":"ea183dbe572c64451825d0383554ec325494f79a342de8be082ca356bd634861","created":1757326842,"exp":1757413242}
{"token":"f221237385a68cae4487815580ffde5dba191ab26e530629fbb9d25cfbd78b9b","created":1757328356,"exp":1757414756}
{"token":"1f1fbe3e4e61c93bf0dda1ecd1be15613d33a2065458789465ae6390df55bb6a","created":1758107596,"exp":1758193996}
{"token":"9b36e1cd9064f7a0d0b6932d7e2540c8167b5f0217d25ecb3366890cbe45ad0b","created":1758107713,"exp":1758194113}
{"token":"c5a33030ac64f9695c8afde31dd5c11846e9047a2dea117f49a133f90ce76924","created":1758107738,"exp":1758194138}
{"token":"f2077115a4969f4f17d14b1245d93afee249b9dc224472ca9fb3626a88c767df","created":1758477721,"exp":1758564121}
{"token":"fb6e30161a17fcf4d26fff6b6fdf9390c516cda64dcb3df9d7785f22ff18b1d1","created":1758477745,"exp":1758564145}
{"token":"767d37875a3d3e26eed32a0856ab36e81b500155a2a86b0349b185ec7fb3a090","created":1758477764,"exp":1758564164}
{"token":"606140c6c1c72ee6ed237b9bcd1d06f8e0706d693be2a04eeae4e90830a5bd18","created":1758477789,"exp":1758564189}
{"token":"5dca3d7add783b6448d863809bcc4389578c4b8512fe3f6f24c7cdbef4e1b908","created":1758523992,"exp":1758610392}
{"token":"ecb9246a30d8c24398351fe69b40730d5fa3b1ccc237f822cc6ec568702bdc61","created":1758524058,"exp":1758610458}
{"token":"971f7fc5b21463b1180ce20152e8e9412e6582638f02d0d3f0ab5b87e831c92c","created":1758524093,"exp":1758610493}
{"token":"02ec7c0520bb2e9992012a276fac309770aead2d1e21e201142c88e85dd77938","created":1758524109,"exp":1758610509}
{"token":"b6844c5a99952786846d065777bb33647e24dd59371c6e16b37aa6fde8d08635","created":1758524126,"exp":1758610526}
{"token":"e07239755729bbd43f13112d25a3fd7ca652de6705e24fa49bfe9fbe67a94a43","created":1758524433,"exp":1758610833}
{"token":"0e0e946dbef1255217b9418adf577d79d195db8e2e1060932cc824ba6efaed11","created":1758524480,"exp":1758610880}
{"token":"6d2a941987d0753b8f58d3058039ebc091286030be9f0d6be08a666c6212f325","created":1758631441,"exp":1758717841}
{"token":"a2cc4bedf2d3a070546a795cd2799e0e6ba7a5640c31acc458909d7467fbacfd","created":1758631465,"exp":1758717865}
{"token":"5e5d72eb15330fc4dbfb3acf262d43c730307b04206234f22ddf7efa784857f4","created":1758631622,"exp":1758718022}
{"token":"83e5311d921f2d77a13b3bffc3173e3f1ab402de5a0983da046dac3735d50669","created":1758631641,"exp":1758718041}
{"token":"51835ba874245fb5fb31bf795ab418173a1ac842c50d0555464c61b452d0759b","created":1758631699,"exp":1758718099}
{"token":"f7d1a8f2625a4dc23caf983f78ca6e6cc09fb5a1c9e622ffc47d194bcb1eb2fc","created":1758631715,"exp":1758718115}
{"token":"f262efa480d0d98034e6139072f672a8c287454a415327472b8d8b76fc431bb5","created":1758631821,"exp":1758718221}
{"token":"b3fff94205e321bca2bfa80e5edfafa51a42e1e94f2f5813dc1c226d98e9a21f","created":1758631831,"exp":1758718231}
{"token":"10ba334c91ab6af3f8cb768cd6e139b3fecaf180a7c4a44ac8d6f25712e4a5a9","created":1758632063,"exp":1758718463}
{"token":"53b7527c88487d67a77e110b5274c72d5620088aae58debfdd68fd1b9583438a","created":1758632075,"exp":1758718475}
{"token":"a8e92c5cfcce9f4e4a3402a6b275230d759dd2f0c4de0577d237e5bdebdd7a39","created":1758632214,"exp":1758718614}
{"token":"93c49902b428c4073dd61e93fbd3b3267dc99710e0eace885362c1d36d1bff11","created":1758632231,"exp":1758718631}
{"token":"9bbbfbb833576b33140add4a3a251d5d5d4d130b82c95aa37e8d5f67b6b3fa47","created":1758634360,"exp":1758720760}
{"token":"3f6d20603b463bbff6369b75ed20c482c088d194604438af636a065cc00d38cc","created":1758634417,"exp":1758720817}
{"token":"16f4c983c3b186d282b5b68534e1d581e6aa2922c9d8fcecda9c4de9481d85ee","created":1758634669,"exp":1758721069}
{"token":"ac0e1352a82161ec19577823712b93f62c6eecc645248f8213d80ee5d056aef8","created":1758634694,"exp":1758721094}
{"token":"c44e10696b9de668527ee7e645cb44c7aba983a5890b71dd3fc923ebe93807b2","created":1758634748,"exp":1758721148}
{"token":"7547fd0a6593b28c4f4778ba93a00fa93e905b4d99aef38d4cd3215254659d1e","created":1758634779,"exp":1758721179}
{"token":"03c005ba089d5ca9db37dec87abcc87eda453a11c88cc9e0129c9c4420c66cd7","created":1758634837,"exp":1758721237}
{"token":"17a40e3d0752d7b40b1ecdff2fef13f1a670f027d2152eaf60146ce988cf8418","created":1758634850,"exp":1758721250}
{"token":"404a6ce228679470fd88b9926777238bbc4c3487dda478f5e9ba68cff9be24ba","created":1758637923,"exp":1758724323}
{"token":"9306963145c2beac257849cd41d8a8994899c04eb1661bb891b765d3eac4d202","created":1758638114,"exp":1758724514}
{"token":"02f79fd4f0d88863f6277552aa73a58f44d3706e2bc916f9bfa155b51047c124","created":1758638334,"exp":1758724734}
{"token":"d7722fecb0fcc0f4788fdbfd7c9d72dca05ee9417992426c3a0289dd605ea4cd","created":1758638870,"exp":1758725270}
{"token":"e8f527f42d8a27af0a8dfc1ab94811c9654327542ee6fbf56ed762677d06d24e","created":1758638916,"exp":1758725316}
{"token":"b8b66d92eba410abf7cde0d47528dca87526d0e6ebff12ec6d872ff56a5dfbda","created":1758638951,"exp":1758725351}
{"token":"f7c5cf139714a7d161fef9ff8de76a68131b1bebfc44ccbe2540fe9bd078f0ca","created":1758713063,"exp":1758799463}
{"token":"7b969f437896d654492db1f012bd15454f7d9a54547372a00d500e8575c18ebc","created":1758880572,"exp":1758966972}
{"token":"217f9fc39604e187023233900ea85c3cd6b0500b9a2e31f96b61b26b2b03757e","created":1758880587,"exp":1758966987}
{"token":"54fc17d45268b6653d8354d52a0a538eb62d1dd602e874aed5527f3d70ae9bc4","created":1758880618,"exp":1758967018}
{"token":"2f3f7fd350188548cbbd76959a91120bf67c871d8b0ce0d372a7394a6c958b49","created":1758880849,"exp":1758967249}
{"token":"76758ca57e42ed78504d148e243d9c333a83431b0072f59807e3ba906576c714","created":1758881595,"exp":1758967995}
{"token":"7543060413d38da66b7b0b80a444e8e37d3317c0be32585682365007ff6ce176","created":1758881763,"exp":1758968163}
{"token":"8ceff5d37ee6e6bd58b349fce81fd2d7814280799897e1f3ec94dc2c02b5044b","created":1758882150,"exp":1758968550}
{"token":"f813faa3aced8e2bb4ee7f815a0e5cd7c15cd8a314d178a9bd4adaa1d65ad7a8","created":1758963705,"exp":1759050105}
{"token":"30172769015f9d95aca3cff58dcc8e82dd1c34ff34e5fa421b93f852c2efc995","created":1758963970,"exp":1759050370}
{"token":"286f6448269afb14ad3579b2ec20edf5a5215efc7b8c61a4da42a81f670eea5a","created":1758964604,"exp":1759051004}
{"token":"602ba60c2b42590fd6b81fa0374769c3fa5419e6431ad936556c53f858e0dd3d","created":1758964964,"exp":1759051364}
{"token":"60d72e4dd79c595caa31a363a18fb65d53b408de657be558428486e0f95c1624","created":1759130219,"exp":1759216619}
{"token":"7ecf40a37b1860287ab9749039dd06b1aa41fc968daee96abb86b1f5640e9a3b","created":1759131060,"exp":1759217460}
{"token":"9f7caf0c8b690935664c8458297becfe0f01d4c636cc27739b6a4bb422dce023","created":1759131973,"exp":1759218373}
{"token":"49c9c4105eaae364566eb87e8ade6f76ee501fe304bc34a435ce5f59ba6d0d61","created":1759132236,"exp":1759218636}
{"token":"0bf4c2a9b0635fd0a9e9e6729f958c0c521ba03763740ffe35423d006382f3d2","created":1759132261,"exp":1759218661}
{"token":"4a227bacb48a669ed6f767cd5355bbb2b7ebdb8413439cd880debf8be6f05854","created":1759132276,"exp":1759218676}
{"token":"9f835e179b993f1ce748f3a8deffb5e2b6c420ba0ed256d76b1008bccb09592b","created":1759132305,"exp":1759218705}
{"token":"97726da2f384f90cc9772113d25b05f270d83bd95d8e7174217e0f1b90b6c0d8","created":1759132645,"exp":1759219045}
{"token":"12602dd163016ff3f4cc44e2024af559ab1d3ee4da03b1dd80b9c89bde3b7dac","created":1759132832,"exp":1759219232}
{"token":"555ce8248ee91024d3d78a29a001411643739d68bbd1b845331423cb03a2b686","created":1759132975,"exp":1759219375}
{"token":"23920ccf38cd1d89b8600be7634adf5b568dd65c55683d1aa9ae0065f07d9dd0","created":1759133185,"exp":1759219585}
{"token":"05223b7302ff2ce1920395b840dbaf901b03a19e74e586c89e557c7ecd5c045a","created":1759133317,"exp":1759219717}
{"token":"4bb6e11294e7c1231420ed75a9bb6cdd65c29b1af3a83e33663d41cb64b0db2e","created":1759133349,"exp":1759219749}
{"token":"c23f83b74435194855203c703a4c763b69fe3de6161e64528855887551edd777","created":1759133401,"exp":1759219801}
{"token":"48ca9bc7937f8ec478f97b8d77680a507f671ce02bae4c354ac66a924b4d9a63","created":1759133422,"exp":1759219822}
{"token":"e6952e1cb227a985cd8546ec8008f7d606ab329cf1d19bb148957a2bb794fa8f","created":1759133445,"exp":1759219845}
{"token":"fe1dfd9aa8bdc9717fd2dfbd3867771c979b5e49fab8f45342b8405e5dddfb8d","created":1759133762,"exp":1759220162}
{"token":"d62b8043df3bbdbdb787806c5b492ceaa505402d099ba38c78a46a409d0c33f0","created":1759133809,"exp":1759220209}
{"token":"6c78c2e05d9021ca8a050954104c9e0c31f76e944fdac5d56c7227dbba048f72","created":1759133839,"exp":1759220239}
{"token":"13ccd9f6051c1a11e6950fed92cd0c4e60cf595e47a07cbb25f283ceac7bbf43","created":1759133903,"exp":1759220303}
{"token":"0d9e3dc9e5aef1fcf579e6dc55bb989d84af7e27999295cb60900206a2b043e7","created":1759134295,"exp":1759220695}
{"token":"5248a0c841ae09db2166f1cd3254b1672031f321c71212e775b07794ee2efb99","created":1759136855,"exp":1759223255}
{"token":"dd9941e9b616fe179e33c0a884a3bfc3ada56c2f626afcb9da63e3e585ab9226","created":1759137413,"exp":1759223813}
{"token":"ecced21404122b68765272c3a1c5707be9ae230ab0cf193d661fc4b09bafe78d","created":1759137429,"exp":1759223829}
{"token":"8e013fbd30ee8278356f08e0aa0d3f6e4e709ae5c5246a731a8034070eda7079","created":1759137677,"exp":1759224077}
{"token":"b35841e30ea56d0bbf1274a7606294d1b85d149aebca4389675cb47731a25e1d","created":1759138841,"exp":1759225241}
{"token":"a82c1f1b051f51b33f1f3e676511a07f4914395c1fddce1aeee41aa0c398fa27","created":1759138863,"exp":1759225263}
{"token":"6647b9b2900320da05769955bdc172dabab24c6a24d258659593978d7b8d8be9","created":1759138920,"exp":1759225320}
{"token":"68b87e342ee484d6b9d278913ed8ca7864780e2672c520941eca7adcdabc90d0","created":1759138985,"exp":1759225385}
{"token":"41584f24f999a2b08bca7a26716b7bb82e27df6f3bf22efd925a05c1e49e7f4e","created":1759138997,"exp":1759225397}
{"token":"ee10e4e87b7c80c875e622d53fc1f3658b4d7e6d4cfb86a8b1af8a00537d76af","created":1759139042,"exp":1759225442}
{"token":"4a9fc9ad92631aa019e2f4a6c829bc47277bfad191ef7a0cff744b30949e5002","created":1759139881,"exp":1759226281}
{"token":"a3888918ee647f213e7408e6fff916e99f6fb3e757fe44a02f279b508da2016f","created":1759139945,"exp":1759226345}
{"token":"9b574f805ec4ab45a99aadf98246cc3cac3839cc25cc9f1f40ffc4a314cf5390","created":1759141376,"exp":1759227776}
{"token":"ef0d5340827ffc436c88863199d820cf7d8143f260c4f3f89a9438cbbae9e9ea","created":1759141871,"exp":1759228271}
{"token":"2d51a5ead5266958a15313b8dd92109d90ae0069c476328af7e764e3284e6563","created":1759141889,"exp":1759228289}
{"token":"01ce0e40ecd1253e53eaba374b09e363535fe511cea5c11a9cc7e2d50b5a5d70","created":1759141930,"exp":1759228330}
{"token":"d2a967c7ef1cfd6444f9f98865b71b7a50eee6fb8746b33f753bec23fe8e134c","created":1759141955,"exp":1759228355}
{"token":"31b442f6c1787dfc4090e8687d697bf3b9947acee107595054014892688b69fc","created":1759143187,"exp":1759229587}
{"token":"2cb95a73cd824886d5a8ef8f78e4ea50bec48545399b4ec8a97569e11ad1fae7","created":1759143242,"exp":1759229642}
{"token":"1270a387b5ed061801df25c587a32c16d19e01705bad9b33b5c8d081a8a6e405","created":1759665689,"exp":1759752089}
{"token":"cf99f7b82cf9d5929e171c4a10bfe2f804f8dbcfa8d4615032d116975273a20a","created":1759670447,"exp":1759756847}
{"token":"99a2398703836d3b4ad35122f1eceaaf752404a61df5932684f1bde2983065b0","created":1759671255,"exp":1759757655}
{"token":"492feb4f9c7cbdc9ff81003cf0ea821584ac9018d556ce64303e39f402644151","created":1759672995,"exp":1759759395}
{"token":"4950d4b18494285205bd5a1925d89c74a9a27d8bf61fea546a60c3ff77434c8a","created":1759769234,"exp":1759855634}
{"token":"d15b65be6c96d98d273bf75542da813d8bdf9a3e67b21b5d6e6ffc9578d825f6","created":1759769252,"exp":1759855652}
{"token":"7455244c86cfa13f6a35bb350669fb118ed559eec1b04615af6e61df24b15860","created":1759770329,"exp":1759856729}
{"token":"de71102f6b394bf1c9fefdabb3f81063947971d9c0b3039044f8a5ccb06fe0b9","created":1760019733,"exp":1760106133}
{"token":"33dfc2f32a92c33add844c063b6441435fbd81b099687f562f91bb844cce51fd","created":1760019746,"exp":1760106146}
{"token":"af9b3962b69dd2ed8101a6c82a4d2b3c16e6dcfe9bd2f704dad5ad89e5f4ec78","created":1760020288,"exp":1760106688}
{"token":"60476805d087f95b73bfc85906f3da2d564f0da823be997bff56840b9a3cbf83","created":1760020419,"exp":1760106819}
{"token":"e2865b9b9e93ab3a6824bbfc7b8a538d9504c5e742d8311ac8cff1cd5b1be537","created":1760020537,"exp":1760106937}
{"token":"e9525d496c03eab391ff7a33bebcf894e27739dbe72dbfd78561bd3f0abeeb04","created":1760020598,"exp":1760106998}
{"token":"3dab956d4162eafcafbc909774e5c72b8f705bd0118526ec64fc9aad779ffbd1","created":1760100224,"exp":1760186624}
{"token":"46048543edd7e1164f09e15ad7b270d0cbe902a8e9cf83c74b336126e9812919","created":1760100259,"exp":1760186659}
{"token":"c6769ca917c5b7bc3a8de449d4c1ee432a48af2816106d45151da78b6df5f6fe","created":1760102088,"exp":1760188488}
{"token":"1d661c5092d3f5cb49d9040eca309d899d76aab6853f730e10e11251aeb0e3e6","created":1760102101,"exp":1760188501}
{"token":"18678f9d4b37859266ce5f94974b50b8b79c6b2f8ba95b1127e21e55584242a1","created":1760102109,"exp":1760188509}
{"token":"4d0242faef13d10b3a601a221193e541eafaa4bc3ee6bd803c1ffb7a50839809","created":1760102145,"exp":1760188545}
{"token":"7b70d5aa0f414c658968462359345b2a3a14a9d4a5975b6e11120b474cda67ea","created":1760102318,"exp":1760188718}
{"token":"5cc22039581e43ee6ca998bddef9f38406074365ebab0f19f78a3770d129c80a","created":1760102851,"exp":1760189251}
{"token":"ca738bb4509efb56d9ed890202fe610e086e6c060a937fe24f0950d5e1ad0e91","created":1760102867,"exp":1760189267}
{"token":"63a52bf36dfdbdf183bc3645c0499c7b9d7407ff4e0b68d0ba4a7c4d21b3b2bd","created":1760373199,"exp":1760459599}
{"token":"540025ab075bd0b0d5b5f32136a2d728d33940428f1e03cb1cbefa2a4031cb22","created":1760373303,"exp":1760459703}
{"token":"698c35a7a528eff612691bd1e5b8d87606282e602946f4f3e780ad07882ccf39","created":1760376043,"exp":1760462443}
{"token":"03014ddfae73da00708075e584b7acc7453bd6fb09336ea4d0f311dea048cf74","created":1760423624,"exp":1760510024}
{"token":"59fe7551c92538ba16a8cb037cb4841e804955e541a711b93ce872c0cbfa4822","created":1760428837,"exp":1760515237}
{"token":"4e721a7c891ce14f50525807fefa7f30935fb646abcbe797a57967638d3153b1","created":1760429061,"exp":1760515461}
{"token":"df53f6ee07a698402ae87f2b61d0f1449eb9e8947379f8b5e49416577f44466e","created":1760429937,"exp":1760516337}
{"token":"72d697bda7b5b1b6c433f77d4974d056101cbfe59ed4a3b797b43e501e7315ae","created":1760602832,"exp":1760689232}
{"token":"f0f8bf32768a2741cc503b990d18123c5d4baf492e9a8d842eaaa1010ee5bb88","created":1760608588,"exp":1760694988}
{"token":"b7840ea72c124152e09992cc55bb8ea4239ec90fb5970caa2142344be49cd4c5","created":1760608677,"exp":1760695077}
{"token":"08aa5a790cc9fd8cef3b9a75b5b65286544c008dc2a9ea8f0d43b9e25fbe19a7","created":1760610683,"exp":1760697083}
{"token":"2cbc45e37c995d2673eb5b5d4f5dbc470e69b30b634972bf358c92afe841da06","created":1760610695,"exp":1760697095}
{"token":"bc6cf1156147f85d1b20982dd9f8730bf0fbf50b5740f62cf52bea5d5dd28c82","created":1760611508,"exp":1760697908}
{"token":"f1ff89caf2b5b3a68df5b091a9f158db2174f8e865f7d632cabe156c96847099","created":1760612940,"exp":1760699340}
{"token":"5cd4cc46d876fb7f6ac0435dd35f7b1f10637ef6aea0084cddcf701ee385747d","created":1760612996,"exp":1760699396}
{"token":"7d88c0fa36dc13981eacf5b57f36d666727cc516ef546a84e5708b0c47998175","created":1760613447,"exp":1760699847}
{"token":"627765059218d2e0f829887025a3c2c3319dbbfa052698ef4bbfa9932da34cd7","created":1760971357,"exp":1761057757}
{"token":"be417f82627bee9786ea5f8b99ce64a4dc855114f3e90e81192243bd7f9ee53c","created":1760972135,"exp":1761058535}
{"token":"4c4a6345cdd1a3a5556e44d2d306224eaf00ae580c46c4e0a305f9bf83ae1f1b","created":1761039024,"exp":1761125424}
{"token":"7b8def5d4ba5727c1332601b9652440c4850cfaa87cd93b38b5575b6ee2b93cc","created":1761064340,"exp":1761150740}
{"token":"33b38bbae91754d7459ea880a76f348765bc8c9a348ea32217efc40b487bcceb","created":1763290822,"exp":1763377222}
{"token":"9dde0839b583c875468530ee6fa2a92b85f4689bee30f59dd4e321fc2893cccd","created":1771865346,"exp":1771951746}
{"token":"cd7ef79a5664faf211e7508aa3e1da38a9295d3d7f30852abf87f657c4545ef3","created":1771865413,"exp":1771951813}
{"token":"3f71f2fde72c9fa670e4e17ac67da022952e5526fe3b4ffd70820046bab7dc7d","created":1771865429,"exp":1771951829}
{"token":"4eed29d1c55d2bcfc9fb218177062cd55f502ebca01915f121c4f725ab3886f3","created":1771865524,"exp":1771951924}
{"token":"508fdc8268cdbf8e58acbc6d0a2113f4f266f562ec2e7bbd2b4586226930bc1c","created":1771865748,"exp":1771952148}
{"token":"ea7c5c53ae3add1263435239a377f55a8edb3c3db0bb9d6ca8ca9755f40c952c","created":1771866052,"exp":1771952452}
{"token":"16db690623237f5f1e748c335ff72beb34be1ddf8117937705fc3f956d5854d4","created":1771866144,"exp":1771952544}
{"token":"7c079d53022f27743a8f41c2c16ba241622dde305d07df3da142feb5ce109a7a","created":1771866278,"exp":1771952678}
{"token":"afd3d79a917c910596bffd65ddd3d806f2c8c99f0ab9e398080989e0c352a3a0","created":1771866586,"exp":1771952986}
{"token":"e320bc456c9ab043f6259d687ff8074f0cd10ba63618f89671d584243b7be9e1","created":1771866962,"exp":1771953362}
{"token":"b37345318d18290039246d9473d0dcd64794ee2e9c024ecf6ec2c80a2d81820a","created":1771937505,"exp":1772023905}
{"token":"275398f85a005c7e58bdac6c5b001e544aee3cec9f5d323c4f98fe8578f39ae7","created":1771937569,"exp":1772023969}
{"token":"0476b9a7134403ab3fb28e0da8b00897a18bd1f20c58b3f66e49427f4e4fab17","created":1771939864,"exp":1772026264}
{"token":"6e4372fb37e0abd54d5a559978b1742bfbead965376df39439c3b809857dead9","created":1771940580,"exp":1772026980}
{"token":"dc58bb4fde7a94f169998ce31a1ac52433572b52f696d8af298dfe4191b3864c","created":1771940628,"exp":1772027028}
{"token":"e11133b3ab5c850ecc75a6496a882a7e7b348d4c449eba7d6192f598defdb229","created":1771940848,"exp":1772027248}
{"token":"44e8ac2a418d7b759c061d7f6187a0023736d2a0104b7ea70c1459ec8dab4700","created":1771941296,"exp":1772027696}
{"token":"9d39a349397f05b07b11fb1b07fe04d703030ed322d5f61bdf9b3dd81e244b59","created":1772000804,"exp":1772087204}
{"token":"b3e448c188ec718cf7aec6601fbd800cb087d08475ee95adc753eaab1a6ea800","created":1772001151,"exp":1772087551}
{"token":"9714b678caa1dded7222bb00ac466c8163bf1003dbaaf290d694be764e4a5c46","created":1772001535,"exp":1772087935}
{"token":"48721aaa13f78d571a28275e39fec81586eabda937fee765720ec72e42d82aa1","created":1772002283,"exp":1772088683}
{"token":"36242c20ed90459c72d12d7541311d803da304e3a50848a06ee47b0896543e9c","created":1772002329,"exp":1772088729}
{"token":"af278b2e4b05d94ff422dc4391e4aa1a5c09d43f3b34f1e3e522696ab631e07a","created":1772101322,"exp":1772187722}
{"token":"7abe6b6ffb9f168bcce197ee0e2287d9ee5055551d73aea70e479a8e4ef6ef01","created":1772106567,"exp":1772192967}
{"token":"88052fc72bf9ad9f99b70c4197322aa270dfef966e50a2ad268f62cbbae9e8d9","created":1772110997,"exp":1772197397}
{"token":"af85e2994c1d9e844d9179171e57ff3542de845967f1d1f4f90107029d814f20","created":1772111983,"exp":1772198383}
{"token":"3119ce71143809f78316ce131bc2e93bbf113b7b2a103fa20d584f233efc7a43","created":1772112290,"exp":1772198690}
{"token":"0474561405352eb731e66ed121a827c951673b7ff1b7032ba6195f9d19abde59","created":1772114237,"exp":1772200637}
{"token":"12719b1ce7992358d537eacb1d39397292acbc76fa12bde72c9d4314ae6f80dd","created":1772114779,"exp":1772201179}
{"token":"173d5f63ae00a092ebd135f0c09efe959bd23e426fb13159f8dea14f1dc4b924","created":1772283082,"exp":1772369482}
{"token":"8527a42eedef65315ed02f22f0a489279b5f8fe9f90a3a3e657fe4db3fdc5f48","created":1772452494,"exp":1772538894}
{"token":"8b9a0f52b37fa7dd28fc52ea8f6b6d40d60240dd978a1b78d1c295594836b954","created":1772698644,"exp":1772785044}
{"token":"d0bd7435c514c3f090d06ab92bc208b5d8eabd7cddd333427bd329257521a124","created":1772698738,"exp":1772785138}
{"token":"8f0959185b3c22aa86310192e8e81096c503305e211780a0e9a446711e762639","created":1772699719,"exp":1775291719}
{"token":"9bd42a9a3d9364f3087dd6e15b991daf764b4510cec728369e5b870f08ca91ad","created":1772703629,"exp":1775295629}

348
translations_en.json Normal file
View File

@ -0,0 +1,348 @@
{
"client_code_entry_question": "Please enter client code and coach code.",
"all_fields_between_0_and_15": "Please enter a number between 0 and 15 in all fields!",
"fill_both_fields": "Please fill in both fields!",
"please_answer_all": "Please answer all questions!",
"select_accommodation": "Please select an accommodation!",
"enter_coach_code": "Please enter your coach code to continue!",
"enter_country": "Please enter a country to continue!",
"only_letters_allowed": "Please enter letters only!",
"enter_valid_number": "Please enter a valid number!",
"enter_valid_year": "Please enter a valid year!",
"enter_text_to_continue": "Please enter text to continue!",
"select_at_least_one_language": "Please select at least one language!",
"select_country": "Please select a country",
"select_gender": "Please select a gender!",
"select_one_answer": "Please select one answer!",
"select_one_option": "Please select one option!",
"coach_code": "Coach Code",
"client_code": "Client Code",
"select_month": "Select month!",
"select_year": "Select year!",
"choose_answer": "Choose answer",
"choose_option": "Choose option!",
"consent_not_signed": "Consent not signed",
"consent_signed": "Consent signed",
"error_file": "Error processing file!",
"no_school_attended": "I did not attend school!",
"once": "once",
"year_after_2000": "The year must be after 2000!",
"year_after_departure": "The year must be after leaving the country of origin!",
"year_max": "The year must be less than or equal to $MAX_VALUE_YEAR!",
"data_final_warning": "<b><font color='#FF0000'>Important:</font></b> The data cannot be changed or edited after completion!",
"multiple_times": "multiple times",
"more_than_15_years": "more than 15 years",
"no": "No",
"no_answer": "No answer",
"other_country": "Other country",
"value_must_be_less_equal_max": "The value must be less than or equal to $MAX_VALUE_AGE!",
"value_between_1_and_15": "The value must be between 1 and 15!",
"invalid_month": "Invalid month!",
"invalid_year": "Invalid year!",
"yes": "Yes",
"next": "Next",
"previous": "Back",
"finish": "Complete data entry",
"thank_you_participation": "Thank you for participating in this survey.",
"response_recorded": "Your response has been recorded.",
"january": "January",
"february": "February",
"march": "March",
"april": "April",
"may": "May",
"june": "June",
"july": "July",
"august": "August",
"september": "September",
"october": "October",
"november": "November",
"december": "December",
"consent_instruction": "Obtain client consent (separate document)",
"no_consent_entered": "You have indicated that the client has not signed the consent form.",
"no_consent_note": "This is important information for the evaluation of 'BW schützt!'.",
"coach_code_request": "Please enter your coach code and the name of the accommodation below. Then upload the data as usual.",
"other_accommodation": "Other accommodation (name)",
"age": "Age: How old are you?",
"gender": "Gender",
"gender_male": "Male",
"gender_female": "Female",
"gender_diverse": "Diverse",
"country_of_origin": "Country of origin: where are you from?",
"country_text_entry": "Country of origin (text entry)",
"departure_country": "When did you leave your country of origin?",
"year": "Year",
"since_in_germany": "Since when have you been in Germany?",
"living_situation": "How are you living here?",
"alone": "Alone",
"with_family": "With family members",
"number_family_members": "How many family members are you living with?",
"number_label": "Number of family members",
"languages_spoken": "Which languages do you speak?",
"language_albanian": "Albanian",
"language_pashto": "Pashto",
"language_russian": "Russian",
"language_serbo": "Serbo-Croatian",
"language_somali": "Somali",
"language_tamil": "Tamil",
"language_tigrinya": "Tigrinya",
"language_turkish": "Turkish",
"language_ukrainian": "Ukrainian",
"language_urdu": "Urdu",
"language_other": "Other",
"language_arabic": "Arabic",
"language_dari_farsi": "Dari/Farsi",
"language_chinese": "Chinese",
"language_english": "English",
"language_macedonian": "Macedonian",
"language_kurmanji": "Kurdish-Kurmanji",
"language_hindi": "Hindi",
"language_french": "French",
"german_skills": "How would you rate your German language skills?",
"skill_none": "None",
"skill_basic": "Basic knowledge",
"skill_a1": "A1",
"skill_a2": "A2",
"skill_b1": "B1",
"skill_b2": "B2",
"skill_c1": "C1",
"skill_c2": "C2",
"school_years_origin": "How many years did you attend school? (in your home country)",
"school_years_transit": "How many years did you attend school? (while in transit)",
"school_years_germany": "How many years did you attend school? (in Germany)",
"label_years": "Years",
"vocational_training": "Have you completed vocational training?",
"answer_no": "No",
"answer_started": "Started",
"answer_completed": "Completed",
"provisional_accommodation_since": "Since when have you been in provisional accommodation?",
"asylum_procedure_since": "Since when has your asylum procedure been ongoing?",
"accommodation": "Accommodation",
"steinstrasse": "Steinstraße",
"doerfle": "Dörfle",
"zoll_emmishofer": "Zoll (Emmishofer Straße)",
"other": "Other",
"school_years_total": "How many years did you attend school?",
"glass_explanation": "The response options are shown in words and as a picture of a glass container: if something does not apply at all, the glass is empty; if it applies somewhat or fairly, the glass fills up; if it applies extremely, the glass is full.",
"symptoms": "Symptoms",
"never_glass": "Never",
"little_glass": "A little",
"moderate_glass": "Moderately",
"much_glass": "Much",
"extreme_glass": "Extremely",
"q1_symptom": "1. Muscle, bone, or joint pain",
"q2_symptom": "2. Feeling unhappy, sad, or depressed most of the time",
"q3_symptom": "3. Overthinking or worrying too much",
"q4_symptom": "4. Feeling helpless",
"q5_symptom": "5. Being easily startled for no reason",
"q6_symptom": "6. Fatigue, dizziness, or feeling weak",
"q7_symptom": "7. Feeling nervous or insecure",
"q8_symptom": "8. Feeling restless, unable to sit still",
"q9_symptom": "9. Feeling like crying easily",
"how_strong_past_month": "How strongly did you experience the following in the past month...",
"q10_reexperience_trauma": "10. ... re-experiencing the trauma; behaving or feeling as if it were happening again?",
"q11_physical_reaction": "11. ... experiencing physical reactions (e.g., sweating, rapid heartbeat) when reminded of the trauma?",
"q12_emotional_numbness": "12. ... feeling emotionally numb (e.g., feeling sad but unable to cry, unable to feel loving emotions)?",
"q13_easily_startled": "13. ... being more easily startled (e.g., when someone approaches from behind)?",
"instruction_listen_statements": "I will now read 5 statements to you. Please listen carefully and then tell me which one best applies to you:",
"q14_intro": "14. Thinking about your life in the past four weeks, to what extent do you feel that you:",
"q14_option_1": "Are able to handle everything that comes your way",
"q14_option_2": "Are able to handle most things that come your way",
"q14_option_3": "Are able to handle some things but not others",
"q14_option_4": "Are unable to handle most things",
"q14_option_5": "Are unable to handle anything",
"pain_rating_instruction": "Choose the number (010) that best describes how much suffering the respondent experienced in the past week, including today.",
"violence_intro": "Finally, I have 2 questions about experiences of violence:",
"violence_question_1": "Have you ever been injured by others so seriously that you needed medical attention? (doctor, hospital)",
"times_happend": "Has this ... happened?",
"age_at_incident": "At what age: at ... years?",
"times_happend2": "Has this ... happened?",
"age_at_incident2": "At what age: at ... years?",
"conflict_since_arrival": "Since arriving in Germany, have you ever been involved in violent conflicts (physical fights, physical or psychological conflicts)?",
"finish_data_entry": "When you have finished entering the data, click \"Save\".",
"feeling_connected_to_germany_question": "How connected do you feel to Germany (how well have you settled in Germany)?",
"intro_life_in_germany": "Now we would like to ask you some questions about your life in Germany:",
"not_connected_at_all": "not connected at all",
"somewhat_loose": "somewhat loose",
"moderately_connected": "moderately connected",
"quite_connected": "quite connected",
"very_connected": "very connected",
"understanding_political_issues": "How well do you understand the most important political issues in Germany?",
"very_good": "very good",
"good": "good",
"fairly_good": "fairly good",
"somewhat": "somewhat",
"not_at_all": "not at all",
"unexpected_expense_question": "Imagine you had to make an unexpected but necessary payment. Which of the following amounts could you pay or afford (if you had free access to money)?",
"expense_50": "up to €50",
"expense_100": "€100",
"expense_300": "€300",
"expense_500": "€500",
"expense_800": "€800",
"dining_with_germans_question": "How often have you dined (e.g. dinner) with Germans in the past 12 months?",
"never": "never",
"once_a_year": "once a year",
"once_a_month": "once a month",
"once_a_week": "once a week",
"almost_every_day": "almost every day",
"reading_german_articles_question": "How well can you read simple newspaper articles in German about familiar topics (health, home, family, etc.) and understand the main points?",
"not_good": "not well",
"visiting_doctor_question": "How easy or difficult would it be for you to see a doctor in this country? (making an appointment, etc. - not: 'you can't get an appointment', 'I would never go to a doctor here')",
"very_difficult": "very difficult",
"rather_difficult": "rather difficult",
"neither_nor": "neither difficult nor easy",
"rather_easy": "rather easy",
"very_easy": "very easy",
"feeling_as_outsider_question": "How often do you feel like an outsider in Germany?",
"rarely": "rarely",
"sometimes": "sometimes",
"often": "often",
"always": "always",
"recent_occupation_question": "How have you been occupied in the last four weeks? (Please select only one answer)",
"employed": "in paid employment, even if only temporarily (employed, self-employed, minor jobs)",
"education": "in education (school, vocational training, university)",
"internship": "internship",
"unemployed_searching": "unemployed and actively looking for work",
"unemployed_not_searching": "unemployed and not actively looking for work",
"sick_or_disabled": "ill or unable to work",
"unpaid_housework": "in unpaid housework, childcare / caring for others",
"other_activity": "Other (e.g., language course)",
"discussing_politics_question": "Since arriving in Germany, how often have you discussed important political topics in Germany with other people?",
"contact_with_germans_question": "With how many Germans have you exchanged messages (phone, messenger chat (WhatsApp, Facebook...), or SMS) in the last 4 weeks?",
"zero": "0",
"one_to_three": "1 to 3",
"three_to_six": "3 to 6",
"seven_to_fourteen": "7 to 14",
"fifteen_or_more": "15 or more",
"speaking_german_opinion_question": "How well can you participate in a conversation in German on familiar topics (children, family, school, home, etc.) and express your opinion?",
"moderately_good": "moderately good",
"job_search_question": "How easy or difficult would it be for you to search for and find a job in this country (even if you may not be allowed to work: what would be your assumption if there were no restrictions)?",
"integration_index": "Integration Index: %d",
"consent_followup_6_months": "Consent for conversation in 6 months",
"day": "Day",
"month": "Month",
"select_day": "Please select a day.",
"select_month": "Please select a month.",
"select_year": "Please select a year.",
"date_final_interview": "Date of final interview",
"amount_nat_appointments": "Number of NAT appointments (after the counseling interview)",
"amount_session_flowers": "Number of storytelling sessions with flowers",
"amount_session_stones": "Number of storytelling sessions with stones",
"termination_nat_coaching": "Termination of NAT coaching",
"termination_nat_regular": "Regular (mutual decision to terminate after discussing all important events)",
"termination_nat_referral": "Referral to a specialized professional (doctor, psychologist, ...)",
"termination_nat_dropout": "Client discontinued NAT",
"follow_up": "Follow-up survey",
"follow_up_completed": "was successfully conducted",
"follow_up_failed_contact": "could not be conducted: repeated failed contact attempts",
"follow_up_consent_withdrawn": "could not be conducted: consent withdrawn",
"special_burden_question": "Was there any special burden?",
"resilience_fully_capable": "Able to handle everything that comes your way",
"resilience_mostly_capable": "Able to handle most things that come your way",
"resilience_partially_capable": "Able to handle some things but not others",
"resilience_mostly_not_capable": "Unable to handle most things",
"resilience_not_capable": "Unable to handle anything",
"intro_resilience_questions": "I will now read 5 statements to you. Please listen carefully and then tell me which statement best applies to you:",
"resilience_reflection_prompt": "Thinking about your life in the past four weeks, to what extent do you feel that you:",
"resilience_some_capable_some_not": "Able to handle most things but unable to handle some others",
"follow_up_survey_score": "The RHS total score is: %d",
"select_one_answer_per_row": "Please select one answer per row!",
"no_next_question_defined": "No forwarding page defined",
"date_consultation_health_interview_result": "Date of counseling interview (health interview result green/yellow/red)",
"consultation_decision": "Counseling decision (RHS_POINTS)",
"consent_conversation_in_6_months": "Consent for conversation in 6 months:",
"participation_in_coaching": "Participation in coaching",
"decision_after_reflection_period": "Decision on .............. (date) after reflection period",
"participation_coaching": "Coaching participation",
"consent_coaching_given": "Consent for coaching is given",
"consent_form_must_be_available": "Consent form must be available!",
"professional_referral": "Professional referral",
"confidentiality_agreement": "Confidentiality agreement",
"health_insurance_card": "Health insurance card",
"green": "green",
"yellow": "yellow",
"red": "red",
"time_to_think_about_it": "Reflection period",
"consent_yes": "yes, consent is given",
"consent_no": "no, consent is not given",
"available_yes": "yes, available",
"available_no": "no, not available",
"client_canceled_NAT": "Client discontinued NAT",
"after_meeting": "after session",
"without_giving_reasons": "without giving reasons",
"with_reasons_given": "with reasons given",
"select_at_least_one_answer": "Please select at least one answer.",
"select_at_least_minimum": "Please select at least %d answers.",
"demographic_information": "Demographic information",
"rhs": "RHS",
"integration_index_label": "Integration index",
"consultation_results": "Counseling results",
"final_interview": "Final interview",
"follow_up_survey": "Follow-up survey",
"example_text": "Example text",
"client_code_exists": "This client code already exists.",
"load": "Load",
"date_after": "The date must be after",
"date_before": "The date must be before",
"lay": "it.",
"choose_more_elements": "More elements must be selected.",
"please_client_code": "Please enter client code",
"no_profile": "This client is not yet part of the database",
"questionnaires_finished": "All questionnaires have been completed for this client!",
"edit": "Edit",
"save": "Save",
"upload": "Upload",
"download": "Download",
"database": "Database",
"clients": "Clients",
"client": "Client",
"client_code_label": "Client Code",
"questionnaires": "Questionnaires",
"questionnaire": "Questionnaire",
"questionnaire_id": "Questionnaire ID",
"status": "Status",
"header_label": "Header",
"id": "ID",
"value": "Value",
"no_clients": "No clients available.",
"no_questionnaires": "No questionnaires available.",
"no_questions": "No questions available.",
"question": "Question",
"answer": "Answer",
"download_header": "Download header",
"back": "Back",
"export_success_downloads_headers": "Export successful: Downloads/ClientHeaders.xlsx",
"export_failed": "Export failed.",
"error_generic": "Error",
"not_done": "Not Done",
"none": "None",
"done": "Done",
"locked": "Locked",
"start": "Start",
"points": "Points",
"saved_pdf_csv": "PDF and CSV were saved in the \"Downloads\" folder.",
"no_pdf_viewer": "No PDF viewer installed.",
"save_error": "Error while saving: {message}",
"login_required_title": "Login required",
"username_hint": "Username",
"password_hint": "Password",
"login_btn": "Login",
"exit_btn": "Exit",
"please_username_password": "Please enter username and password.",
"download_failed_no_local_db": "Download failed no local database available",
"download_failed_use_offline": "Download failed working offline with existing database",
"login_failed_with_reason": "Login failed: {reason}",
"no_header_template_found": "No header template found",
"login_required": "Please log in first",
"session_label": "Session",
"session_dash": "Session: —",
"hours_short": "h",
"minutes_short": "min",
"online": "Online",
"offline": "Offline",
"open_client_via_load": "Please open the client via \"Load\".",
"database_clients_title": "Database Clients",
"no_clients_available": "No clients available.",
"headers": "Headers",
"no_questions_available": "No questions available.",
"view_missing": "Missing view: %s"
}

BIN
uploadDelta.log Executable file

Binary file not shown.

466
uploadDeltaTest5.php Normal file
View File

@ -0,0 +1,466 @@
<?php
// /var/www/html/uploadDeltaTest5.php
// Vollständige, eigenständige Version einfach kopieren & einfügen.
// -----------------------------
// Logging & PHP-Settings
// -----------------------------
error_reporting(E_ALL);
ini_set('display_errors', '0');
ini_set('log_errors', '1');
$LOG_FILE = __DIR__ . '/uploadDelta.log';
function log_msg($msg) {
global $LOG_FILE;
error_log(date('[Y-m-d H:i:s] ') . $msg . PHP_EOL, 3, $LOG_FILE);
}
header('Content-Type: application/json; charset=UTF-8');
// -----------------------------
// Krypto-/Token-Helfer
// -----------------------------
function b64_or_ascii_to_32_bytes(string $s): string {
// Versuche Base64, sonst ASCII; pad/truncate auf 32
$b = base64_decode($s, true);
if ($b !== false && $b !== '') {
return str_pad(substr($b, 0, 32), 32, "\0");
}
return str_pad(substr($s, 0, 32), 32, "\0");
}
function get_master_key_bytes(): string {
// MASTER-KEY nur serverseitig (für gespeicherte DB)
// ENV: QDB_MASTER_KEY (Base64 oder ASCII). Fallback ist alter Key.
$env = getenv('QDB_MASTER_KEY');
if ($env && $env !== '') return b64_or_ascii_to_32_bytes($env);
return "12345678901234567890123456789012"; // Fallback (bitte in PROD durch ENV ersetzen!)
}
function hkdf_session_key_from_token(string $tokenHex, string $info = 'qdb-aes', int $len = 32): string {
// HKDF-SHA256: salt = 32x0x00, info = "qdb-aes"
$ikm = @hex2bin(trim($tokenHex));
if ($ikm === false) $ikm = $tokenHex; // falls bereits binär
$hashLen = 32;
$salt = str_repeat("\0", $hashLen);
$prk = hash_hmac('sha256', $ikm, $salt, true);
$okm = '';
$t = '';
$n = (int)ceil($len / $hashLen);
for ($i = 1; $i <= $n; $i++) {
$t = hash_hmac('sha256', $t . $info . chr($i), $prk, true);
$okm .= $t;
}
return substr($okm, 0, $len);
}
function aes256_cbc_encrypt_bytes(string $plain, string $key32): string {
$key = str_pad(substr($key32, 0, 32), 32, "\0");
$iv = random_bytes(16);
$cipher = openssl_encrypt($plain, 'AES-256-CBC', $key, OPENSSL_RAW_DATA, $iv);
if ($cipher === false) throw new Exception('openssl_encrypt failed');
return $iv . $cipher;
}
function aes256_cbc_decrypt_bytes(string $data, string $key32): string {
if (strlen($data) < 16) throw new Exception('cipher too short');
$key = str_pad(substr($key32, 0, 32), 32, "\0");
$iv = substr($data, 0, 16);
$ct = substr($data, 16);
$plain = openssl_decrypt($ct, 'AES-256-CBC', $key, OPENSSL_RAW_DATA, $iv);
if ($plain === false) throw new Exception('openssl_decrypt failed');
return $plain;
}
// Token-Validierung: bevorzugt tokens.jsonl mit exp, Fallback valid_tokens.txt
function token_is_valid(string $token): bool {
$now = time();
$jsonl = __DIR__ . '/tokens.jsonl';
if (file_exists($jsonl)) {
$h = fopen($jsonl, 'r');
if ($h) {
while (($line = fgets($h)) !== false) {
$j = json_decode($line, true);
if (!is_array($j)) continue;
if (($j['token'] ?? '') === $token) {
$exp = $j['exp'] ?? ($now + 1); // falls kein exp, akzeptieren
fclose($h);
return $exp >= $now;
}
}
fclose($h);
}
}
$txt = __DIR__ . '/valid_tokens.txt';
if (file_exists($txt)) {
$arr = file($txt, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
if ($arr && in_array($token, $arr, true)) return true;
}
return false;
}
// -----------------------------
// Hauptlogik
// -----------------------------
try {
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
echo json_encode(["error" => "Nur POST erlaubt"]);
exit;
}
// Token aus multipart oder Authorization: Bearer
$token = $_POST['token'] ?? null;
if (!$token) {
$auth = $_SERVER['HTTP_AUTHORIZATION'] ?? ($_SERVER['Authorization'] ?? '');
if (stripos($auth, 'Bearer ') === 0) {
$token = substr($auth, 7);
}
}
if (!$token || !token_is_valid($token)) {
http_response_code(403);
echo json_encode(["error" => "Ungültiger Token"]);
exit;
}
// Upload entgegennehmen (multipart 'file' oder raw body)
$uploadedFilePath = null;
if (!empty($_FILES['file']) && is_uploaded_file($_FILES['file']['tmp_name'])) {
$uploadedFilePath = $_FILES['file']['tmp_name'];
} else {
$raw = file_get_contents('php://input');
if ($raw !== false && strlen($raw) > 0) {
$tmp = tempnam(sys_get_temp_dir(), 'upl_');
file_put_contents($tmp, $raw);
$uploadedFilePath = $tmp;
}
}
if (!$uploadedFilePath) {
http_response_code(400);
echo json_encode(["error" => "Keine Datei gesendet"]);
exit;
}
// Payload mit SESSION-Key (aus Token via HKDF) entschlüsseln
$encData = file_get_contents($uploadedFilePath);
if ($encData === false) {
http_response_code(500);
echo json_encode(["error" => "Fehler beim Lesen der hochgeladenen Datei"]);
exit;
}
$sessionKey = hkdf_session_key_from_token($token);
$jsonData = null;
try {
$jsonData = aes256_cbc_decrypt_bytes($encData, $sessionKey);
} catch (Throwable $e) {
// Fallback: akzeptiere Plain-JSON (Kompatibilität)
$maybeJson = trim($encData);
if ($maybeJson === '' || json_decode($maybeJson, true) === null) {
log_msg("Entschlüsselung fehlgeschlagen und kein valides JSON. raw len=" . strlen($encData));
http_response_code(400);
echo json_encode(["error" => "Ungültige verschlüsselte Datei oder kein JSON"]);
exit;
}
$jsonData = $maybeJson;
}
$data = json_decode($jsonData, true);
if (!is_array($data)) {
http_response_code(400);
echo json_encode(["error" => "Ungültiges JSON"]);
exit;
}
// Ziel: MASTER-verschlüsselte DB speichern
$dbPath = __DIR__ . '/uploads/questionnaire_database';
if (!is_dir(dirname($dbPath))) {
if (!mkdir(dirname($dbPath), 0755, true) && !is_dir(dirname($dbPath))) {
throw new Exception("Konnte Upload-Ordner nicht erstellen");
}
}
// Lock, damit keine parallelen Writes kollidieren
$lockFile = __DIR__ . '/uploads/.qdb_lock';
$lockFp = fopen($lockFile, 'c');
if ($lockFp === false) throw new Exception("Konnte Lock-Datei nicht öffnen");
if (!flock($lockFp, LOCK_EX)) { fclose($lockFp); throw new Exception("Konnte Lock nicht setzen"); }
$tmpDb = null;
$tmpEncrypted = null;
try {
$tmpDb = tempnam(sys_get_temp_dir(), 'qdb_');
$masterKey = get_master_key_bytes();
// Bestehende MASTER-verschlüsselte DB entschlüsseln oder neue DB mit Schema anlegen
if (file_exists($dbPath) && is_file($dbPath)) {
$storedEnc = @file_get_contents($dbPath);
if ($storedEnc === false) throw new Exception("Konnte gespeicherte DB nicht lesen");
$decrypted = aes256_cbc_decrypt_bytes($storedEnc, $masterKey);
if (file_put_contents($tmpDb, $decrypted) === false) throw new Exception("Konnte temporäre DB nicht schreiben");
} else {
$pdo = new PDO("sqlite:$tmpDb");
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->exec("PRAGMA foreign_keys = ON;");
$pdo->exec("
CREATE TABLE IF NOT EXISTS clients (
clientCode TEXT NOT NULL DEFAULT 'undefined' PRIMARY KEY
);
CREATE TABLE IF NOT EXISTS questionnaires (
id TEXT NOT NULL DEFAULT 'undefined' PRIMARY KEY
);
CREATE TABLE IF NOT EXISTS questions (
questionId TEXT NOT NULL DEFAULT 'undefined' PRIMARY KEY,
questionnaireId TEXT NOT NULL,
question TEXT NOT NULL DEFAULT '',
FOREIGN KEY(questionnaireId) REFERENCES questionnaires(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS answers (
clientCode TEXT NOT NULL,
questionId TEXT NOT NULL,
answerValue TEXT NOT NULL DEFAULT '',
PRIMARY KEY (clientCode, questionId),
FOREIGN KEY(clientCode) REFERENCES clients(clientCode) ON DELETE CASCADE,
FOREIGN KEY(questionId) REFERENCES questions(questionId) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS completed_questionnaires (
clientCode TEXT NOT NULL,
questionnaireId TEXT NOT NULL,
timestamp INTEGER NOT NULL,
isDone INTEGER NOT NULL,
sumPoints INTEGER,
PRIMARY KEY (clientCode, questionnaireId),
FOREIGN KEY(clientCode) REFERENCES clients(clientCode) ON DELETE CASCADE,
FOREIGN KEY(questionnaireId) REFERENCES questionnaires(id) ON DELETE CASCADE
);
");
$pdo = null;
}
// Einspielen der Daten
$db = new PDO("sqlite:$tmpDb");
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$db->exec("PRAGMA foreign_keys = ON;");
$db->beginTransaction();
// --- Sammeln, welche IDs/Paarungen betroffen sind ---
$questionnaireIds = []; // set[string] questionnaireId => true
$clientQuestionPairs = []; // set["client|qid"] => true
if (!empty($data['questionnaires'])) {
foreach ($data['questionnaires'] as $q) {
if (isset($q['id']) && $q['id'] !== null && $q['id'] !== '') {
$questionnaireIds[$q['id']] = true;
}
}
}
if (!empty($data['questions'])) {
foreach ($data['questions'] as $q) {
if (isset($q['questionnaireId']) && $q['questionnaireId'] !== null && $q['questionnaireId'] !== '') {
$questionnaireIds[$q['questionnaireId']] = true;
}
}
}
if (!empty($data['completed_questionnaires'])) {
foreach ($data['completed_questionnaires'] as $c) {
if (isset($c['questionnaireId']) && $c['questionnaireId'] !== null && $c['questionnaireId'] !== '') {
$questionnaireIds[$c['questionnaireId']] = true;
}
if (isset($c['clientCode']) && isset($c['questionnaireId'])) {
$client = ($c['clientCode'] === null || $c['clientCode'] === '') ? 'undefined' : $c['clientCode'];
$qid = ($c['questionnaireId'] === null || $c['questionnaireId'] === '') ? 'undefined' : $c['questionnaireId'];
$clientQuestionPairs["$client|$qid"] = true;
}
}
}
if (!empty($data['answers'])) {
foreach ($data['answers'] as $a) {
if (isset($a['clientCode'])) {
$client = ($a['clientCode'] === null || $a['clientCode'] === '') ? 'undefined' : $a['clientCode'];
$qid = null;
if (isset($a['questionId']) && is_string($a['questionId'])) {
$parts = explode('-', $a['questionId'], 2);
if (count($parts) >= 1) {
$potential = $parts[0];
if ($potential !== '') $qid = $potential;
}
}
if ($qid !== null) {
$questionnaireIds[$qid] = true;
$clientQuestionPairs["$client|$qid"] = true;
}
}
}
}
if (!empty($data['clients']) && !empty($data['questionnaires'])) {
foreach ($data['clients'] as $c) {
if (!isset($c['clientCode'])) continue;
$client = ($c['clientCode'] === null || $c['clientCode'] === '') ? 'undefined' : $c['clientCode'];
foreach ($data['questionnaires'] as $q) {
if (!isset($q['id'])) continue;
$qid = ($q['id'] === null || $q['id'] === '') ? 'undefined' : $q['id'];
$clientQuestionPairs["$client|$qid"] = true;
}
}
}
// --- Löschungen (zuerst abhängige Tabellen) ---
$delAnswersByPairStmt = $db->prepare("
DELETE FROM answers
WHERE clientCode = :clientCode
AND questionId IN (SELECT questionId FROM questions WHERE questionnaireId = :questionnaireId)
");
$delCompletedByPairStmt = $db->prepare("
DELETE FROM completed_questionnaires
WHERE clientCode = :clientCode
AND questionnaireId = :questionnaireId
");
foreach ($clientQuestionPairs as $pair => $_) {
list($clientCode, $qid) = explode('|', $pair, 2);
try {
$delAnswersByPairStmt->execute([
':clientCode' => $clientCode,
':questionnaireId' => $qid
]);
$delCompletedByPairStmt->execute([
':clientCode' => $clientCode,
':questionnaireId' => $qid
]);
log_msg("Deleted answers & completed for client='$clientCode' questionnaire='$qid'");
} catch (Exception $e) {
log_msg("Delete error for pair $pair: " . $e->getMessage());
}
}
if (!empty($questionnaireIds)) {
$delQuestionsStmt = $db->prepare("DELETE FROM questions WHERE questionnaireId = :questionnaireId");
foreach ($questionnaireIds as $qid => $_) {
try {
$delQuestionsStmt->execute([':questionnaireId' => $qid]);
log_msg("Deleted questions for questionnaireId='$qid'");
} catch (Exception $e) {
log_msg("Delete questions error for $qid: " . $e->getMessage());
}
}
}
// --- Inserts/Upserts ---
if (!empty($data['clients'])) {
$stmt = $db->prepare("INSERT OR IGNORE INTO clients (clientCode) VALUES (:clientCode)");
foreach ($data['clients'] as $client) {
if (!isset($client['clientCode'])) continue;
$val = $client['clientCode'] === null || $client['clientCode'] === '' ? 'undefined' : $client['clientCode'];
$stmt->execute([':clientCode' => $val]);
}
}
if (!empty($data['questionnaires'])) {
$stmt = $db->prepare("INSERT OR IGNORE INTO questionnaires (id) VALUES (:id)");
foreach ($data['questionnaires'] as $q) {
if (!isset($q['id'])) continue;
$val = $q['id'] === null || $q['id'] === '' ? 'undefined' : $q['id'];
$stmt->execute([':id' => $val]);
}
}
if (!empty($data['questions'])) {
$stmt = $db->prepare("
INSERT OR IGNORE INTO questions (questionId, questionnaireId, question)
VALUES (:questionId, :questionnaireId, :question)
");
foreach ($data['questions'] as $q) {
if (!isset($q['questionId'])) continue;
$questionId = $q['questionId'] === null || $q['questionId'] === '' ? 'undefined' : $q['questionId'];
$questionnaireId = $q['questionnaireId'] === null || $q['questionnaireId'] === '' ? 'undefined' : $q['questionnaireId'];
$questionText = $q['question'] ?? '';
$stmt->execute([
':questionId' => $questionId,
':questionnaireId' => $questionnaireId,
':question' => $questionText
]);
}
}
if (!empty($data['answers'])) {
$stmt = $db->prepare("
INSERT OR IGNORE INTO answers (clientCode, questionId, answerValue)
VALUES (:clientCode, :questionId, :answerValue)
");
foreach ($data['answers'] as $a) {
if (!isset($a['clientCode']) || !isset($a['questionId'])) continue;
$clientCode = $a['clientCode'] === null || $a['clientCode'] === '' ? 'undefined' : $a['clientCode'];
$questionId = $a['questionId'] === null || $a['questionId'] === '' ? 'undefined' : $a['questionId'];
$answerValue = array_key_exists('answerValue', $a) && $a['answerValue'] !== null ? $a['answerValue'] : '';
$stmt->execute([
':clientCode' => $clientCode,
':questionId' => $questionId,
':answerValue' => $answerValue
]);
}
}
if (!empty($data['completed_questionnaires'])) {
$stmt = $db->prepare("
INSERT OR REPLACE INTO completed_questionnaires (clientCode, questionnaireId, timestamp, isDone, sumPoints)
VALUES (:clientCode, :questionnaireId, :timestamp, :isDone, :sumPoints)
");
foreach ($data['completed_questionnaires'] as $c) {
if (!isset($c['clientCode']) || !isset($c['questionnaireId'])) continue;
$clientCode = $c['clientCode'] === null || $c['clientCode'] === '' ? 'undefined' : $c['clientCode'];
$questionnaireId = $c['questionnaireId'] === null || $c['questionnaireId'] === '' ? 'undefined' : $c['questionnaireId'];
$timestamp = isset($c['timestamp']) ? (int)$c['timestamp'] : time();
$isDone = !empty($c['isDone']) ? 1 : 0;
$sumPoints = array_key_exists('sumPoints', $c) ? ($c['sumPoints'] === null ? null : (int)$c['sumPoints']) : null;
$stmt->execute([
':clientCode' => $clientCode,
':questionnaireId' => $questionnaireId,
':timestamp' => $timestamp,
':isDone' => $isDone,
':sumPoints' => $sumPoints
]);
}
}
$db->commit();
$db = null;
// MASTER-verschlüsselt speichern (atomar via temp + rename)
$plainDb = file_get_contents($tmpDb);
if ($plainDb === false) throw new Exception("Konnte tmp DB nicht lesen");
$enc = aes256_cbc_encrypt_bytes($plainDb, $masterKey);
$tmpEncrypted = tempnam(dirname($dbPath), 'enc_qdb_');
if (file_put_contents($tmpEncrypted, $enc) === false) throw new Exception("Konnte verschlüsselte DB nicht schreiben");
if (!@rename($tmpEncrypted, $dbPath)) {
if (!@copy($tmpEncrypted, $dbPath) || !@unlink($tmpEncrypted)) {
throw new Exception("Konnte verschlüsselte DB nicht speichern (rename/copy fehlgeschlagen)");
}
}
@chmod($dbPath, 0644);
// Cleanup
@unlink($uploadedFilePath);
@unlink($tmpDb);
flock($lockFp, LOCK_UN);
fclose($lockFp);
echo json_encode(["success" => true, "message" => "Delta erfolgreich eingespielt und DB gespeichert"]);
exit;
} catch (Throwable $inner) {
// Cleanup bei Fehler
@flock($lockFp, LOCK_UN);
@fclose($lockFp);
@unlink($tmpDb ?? '');
@unlink($tmpEncrypted ?? '');
@unlink($uploadedFilePath ?? '');
log_msg("Inner exception: " . $inner->getMessage());
http_response_code(500);
echo json_encode(["error" => "Fehler beim Speichern", "message" => $inner->getMessage()]);
exit;
}
} catch (Throwable $e) {
log_msg("Top-level exception: " . $e->getMessage());
http_response_code(500);
echo json_encode(["error" => "Fehler", "message" => $e->getMessage()]);
exit;
}

0
uploads/.qdb_lock Normal file
View File

Binary file not shown.

50
users.json Normal file
View File

@ -0,0 +1,50 @@
{
"user01": {
"hash": "$2y$10$fcB5mBc.RDeCHyY69RScZOpFL/IjY.wFzx1DNzKI7F6kJhd9rwQfu",
"changedAt": 1760610683
},
"user02": {
"hash": "$2y$10$fGDcFLBgL3oypyjkwVna/OhQ5Be6UFH6hNGdupeNfJMASgrFK7h0K",
"changedAt": 1760611508
},
"Tmp_Daniel": {
"hash": "$2y$10$NRdP4/vkDQEM4fD70xGqdO1dgujOBATcA00Zj2aIaH0hoQV5Rp8ri",
"changedAt": 1760612940
},
"Tmp_Liliana": {
"hash": "$2y$10$4Gui323PXRnQ9Ix4WSrefOEr2HQwfbr6TNVaa7qVE2q59ZwBpnisa",
"changedAt": 1760971357
},
"Tmp_Johanna": {
"hash": "$2y$10$B4eNyer1anHYMNjpNliIW.54sK8z1cYyQEHjmHloatUnmeilchjva",
"changedAt": 1760972135
},
"Danny": {
"hash": "$2y$10$JreuWqvh1VO3fH2YhSpqveDpSSgVPUeNW91ySSzxeQ0o/OgNe1gGS",
"changedAt": 1771865346
},
"Extra1_tmp": {
"hash": "$2y$10$zUtdsv1N2Uv/7XZT4wr4lOTnJEWFF74EJp2/kgq6GHpO8dguMQify",
"changedAt": 1771865748
},
"User11": {
"hash": "$2y$10$ql0aI9C.1nzkE33Oxy8T3.dE0jF0y42fe4x8YWyqFm.SwlUBoqv22",
"changedAt": 1772001151
},
"User10": {
"hash": "$2y$10$5HoG6T6UA9CFd2q1VGLiE.3EbwNyyVR8kSuTZWxJ5run8KvHgrhhK",
"changedAt": 1772110997
},
"User2": {
"hash": "$2y$10$/KxLlL9ZdHnGYL1y4IEIfOnWL.bd2HgPVfk.n5V0Sc.3LzDwmeWl6",
"changedAt": 1772111983
},
"User3": {
"hash": "$2y$10$GMtUWbW.bczjc43dpf539OE7rFgwL08QV1qnjFlyDh5mF/Ss4atom",
"changedAt": 1772112290
},
"User7": {
"hash": "$2y$10$AfJp8go8Vt5J0.46SqYmtOPrrp1CqkWw1pmkOGI6xZ5MgqHTmwIga",
"changedAt": 1772283082
}
}

BIN
valid_tokens.txt Normal file

Binary file not shown.