fix: TTS test ask user if heard + PHP 8 end() ref bug + DB migration guard for fresh volumes

- app.js: TTS kiosk timeout 4s → 10s; fires interactive 'Hai sentito?' YES/NO
  prompt instead of showing error (TTS can take 6-8s; UtteranceProgressListener
  may not fire on all firmware); YES → success, NO → troubleshooting steps
- translations: add heard_question/heard_yes/heard_no/test_ok_kiosk/test_fail_steps
  to all 5 languages (it/en/de/fr/es) under settings.tts
- api/index.php: fix end() PHP 8.0+ reference error in _offFetchProduct()
  (categories_hierarchy stored in temp var before calling end())  (fixes #130)
- api/database.php: migrateDB() now checks sqlite_master for 'products' table;
  if missing, calls initializeDB() and returns — no ALTER on nonexistent table
  (fixes #133, covers #131)
- api/index.php: health_check db_row_count query guarded against missing
  inventory table (fixes #131)
This commit is contained in:
dadaloop82
2026-05-26 14:59:17 +00:00
parent c16067d9e5
commit 50660f634f
8 changed files with 70 additions and 13 deletions
+10
View File
@@ -126,6 +126,16 @@ function initializeDB(PDO $db): void {
} }
function migrateDB(PDO $db): void { function migrateDB(PDO $db): void {
// Guard: if core tables don't exist yet (e.g. DB file present but empty / partial init),
// run initializeDB first so all tables are created, then return — no ALTER TABLE needed.
$productsExists = $db->query(
"SELECT name FROM sqlite_master WHERE type='table' AND name='products'"
)->fetchColumn();
if (!$productsExists) {
initializeDB($db);
return;
}
// Add package_unit column if missing // Add package_unit column if missing
$cols = $db->query("PRAGMA table_info(products)")->fetchAll(); $cols = $db->query("PRAGMA table_info(products)")->fetchAll();
$colNames = array_column($cols, 'name'); $colNames = array_column($cols, 'name');
+9 -4
View File
@@ -477,9 +477,13 @@ if (($_GET['action'] ?? '') === 'health_check') {
'hint' => $wal !== 'wal' ? 'Journal mode not optimal — will be corrected automatically on next startup' : null]; 'hint' => $wal !== 'wal' ? 'Journal mode not optimal — will be corrected automatically on next startup' : null];
// Size & rows // Size & rows
$checks['db_size'] = ['ok' => true, 'value' => round(filesize($dbPath)/1024).' KB', 'optional' => true]; $checks['db_size'] = ['ok' => true, 'value' => round(filesize($dbPath)/1024).' KB', 'optional' => true];
$cnt = $pdo->query("SELECT COUNT(*) FROM inventory WHERE quantity > 0")->fetchColumn(); if (empty($missing) || !in_array('inventory', $missing)) {
$checks['db_row_count'] = ['ok' => true, 'value' => $cnt.' prodotti in inventario', 'optional' => true]; $cnt = $pdo->query("SELECT COUNT(*) FROM inventory WHERE quantity > 0")->fetchColumn();
$checks['db_row_count'] = ['ok' => true, 'value' => $cnt.' prodotti in inventario', 'optional' => true];
} else {
$checks['db_row_count'] = ['ok' => true, 'value' => '0 prodotti in inventario', 'optional' => true];
}
} else { } else {
foreach (['db_tables', 'db_integrity'] as $k) foreach (['db_tables', 'db_integrity'] as $k)
$checks[$k] = ['ok' => false, 'hint' => 'Cannot verify — DB connection failed']; $checks[$k] = ['ok' => false, 'hint' => 'Cannot verify — DB connection failed'];
@@ -2185,7 +2189,8 @@ function _offFetchProduct(string $barcode): ?array {
} }
$ingredients = $p['ingredients_text_it'] ?? $p['ingredients_text'] ?? ''; $ingredients = $p['ingredients_text_it'] ?? $p['ingredients_text'] ?? '';
$category = $p['categories_tags'][0] ?? end($p['categories_hierarchy'] ?? []) ?? $p['categories'] ?? ''; $catHierarchy = $p['categories_hierarchy'] ?? [];
$category = $p['categories_tags'][0] ?? (empty($catHierarchy) ? null : end($catHierarchy)) ?? $p['categories'] ?? '';
$allergens = ''; $allergens = '';
if (!empty($p['allergens_tags'])) { if (!empty($p['allergens_tags'])) {
$allergens = implode(', ', array_map(fn($a) => str_replace('en:', '', $a), $p['allergens_tags'])); $allergens = implode(', ', array_map(fn($a) => str_replace('en:', '', $a), $p['allergens_tags']));
+21 -4
View File
@@ -14186,7 +14186,7 @@ async function testTTS() {
window._kioskTtsDone = (uid) => { window._kioskTtsDone = (uid) => {
clearTimeout(_ttsTestTimer); clearTimeout(_ttsTestTimer);
window._kioskTtsDone = null; window._kioskTtsError = null; window._kioskTtsDone = null; window._kioskTtsError = null;
if (statusEl) { statusEl.className = 'settings-status success'; statusEl.textContent = '✅ Voce riprodotta correttamente.'; } if (statusEl) { statusEl.className = 'settings-status success'; statusEl.textContent = '✅ ' + t('settings.tts.test_ok_kiosk'); }
}; };
window._kioskTtsError = (uid, code) => { window._kioskTtsError = (uid, code) => {
clearTimeout(_ttsTestTimer); clearTimeout(_ttsTestTimer);
@@ -14194,11 +14194,28 @@ async function testTTS() {
const msg = code == -1 ? 'sintesi non riuscita' : code == -2 ? 'lingua non supportata' : code == -3 ? 'servizio non disponibile' : ('codice ' + code); const msg = code == -1 ? 'sintesi non riuscita' : code == -2 ? 'lingua non supportata' : code == -3 ? 'servizio non disponibile' : ('codice ' + code);
if (statusEl) { statusEl.className = 'settings-status error'; statusEl.textContent = '❌ Errore TTS Android (' + msg + ') — installa o aggiorna Google Text-to-Speech dal Play Store.'; } if (statusEl) { statusEl.className = 'settings-status error'; statusEl.textContent = '❌ Errore TTS Android (' + msg + ') — installa o aggiorna Google Text-to-Speech dal Play Store.'; }
}; };
// Timeout: if Android doesn't callback within 4s, warn about media volume // Timeout: if Android doesn't callback within 10s, ask user if they heard the voice
// (speech can take 6-8 s; UtteranceProgressListener may not fire on all firmware)
_ttsTestTimer = setTimeout(() => { _ttsTestTimer = setTimeout(() => {
window._kioskTtsDone = null; window._kioskTtsError = null; window._kioskTtsDone = null; window._kioskTtsError = null;
if (statusEl) { statusEl.className = 'settings-status error'; statusEl.textContent = '⚠️ Nessun feedback ricevuto. Controlla: 1) volume media del dispositivo non sia 0; 2) Google Text-to-Speech installato e aggiornato; 3) pacchetto vocale italiano scaricato.'; } if (!statusEl) return;
}, 4000); statusEl.className = 'settings-status';
statusEl.style.display = 'block';
statusEl.innerHTML =
'<strong>🔊 ' + t('settings.tts.heard_question') + '</strong><br>' +
'<div style="display:flex;gap:8px;margin-top:8px">' +
'<button onclick="window._ttsTestYes()" style="flex:1;padding:8px;background:#15803d;color:#fff;border:none;border-radius:6px;font-size:0.95rem;cursor:pointer">✅ ' + t('settings.tts.heard_yes') + '</button>' +
'<button onclick="window._ttsTestNo()" style="flex:1;padding:8px;background:#dc2626;color:#fff;border:none;border-radius:6px;font-size:0.95rem;cursor:pointer">❌ ' + t('settings.tts.heard_no') + '</button>' +
'</div>';
window._ttsTestYes = () => {
window._ttsTestYes = null; window._ttsTestNo = null;
if (statusEl) { statusEl.className = 'settings-status success'; statusEl.innerHTML = '✅ ' + t('settings.tts.test_ok'); }
};
window._ttsTestNo = () => {
window._ttsTestYes = null; window._ttsTestNo = null;
if (statusEl) { statusEl.className = 'settings-status error'; statusEl.innerHTML = '❌ ' + t('settings.tts.test_fail_steps'); }
};
}, 10000);
_speakBrowser('Test vocale EverShelf. La sintesi vocale funziona correttamente.'); _speakBrowser('Test vocale EverShelf. La sintesi vocale funziona correttamente.');
return; return;
} }
+6 -1
View File
@@ -701,7 +701,12 @@
"voices_hint": "Verfügbare Stimmen hängen vom Betriebssystem und Browser ab. Auf macOS/iOS ist die Stimme Paola (Italienisch) verfügbar. Drücken Sie ↺ wenn die Liste nicht lädt.", "voices_hint": "Verfügbare Stimmen hängen vom Betriebssystem und Browser ab. Auf macOS/iOS ist die Stimme Paola (Italienisch) verfügbar. Drücken Sie ↺ wenn die Liste nicht lädt.",
"url_missing": "⚠️ Endpunkt-URL fehlt.", "url_missing": "⚠️ Endpunkt-URL fehlt.",
"test_sending": "⏳ Wird gesendet…", "test_sending": "⏳ Wird gesendet…",
"test_ok": "✅ Antwort {code} — prüfe ob der Lautsprecher gesprochen hat." "test_ok": "✅ Antwort {code} — prüfe ob der Lautsprecher gesprochen hat.",
"heard_question": "Hast du die Stimme gehört?",
"heard_yes": "Ja, ich habe es gehört",
"heard_no": "Nein, ich habe nichts gehört",
"test_ok_kiosk": "TTS funktioniert.",
"test_fail_steps": "Prüfe: 1) Medienvolume ist nicht 0; 2) Google Text-to-Speech installiert und aktualisiert; 3) Deutsches Sprachpaket in den Android TTS-Einstellungen heruntergeladen."
}, },
"language": { "language": {
"title": "🌐 Sprache", "title": "🌐 Sprache",
+6 -1
View File
@@ -701,7 +701,12 @@
"voices_hint": "Available voices depend on the OS and browser. On macOS/iOS the Paola (Italian) voice is available. Press ↺ if the list does not load.", "voices_hint": "Available voices depend on the OS and browser. On macOS/iOS the Paola (Italian) voice is available. Press ↺ if the list does not load.",
"url_missing": "⚠️ Endpoint URL missing.", "url_missing": "⚠️ Endpoint URL missing.",
"test_sending": "⏳ Sending…", "test_sending": "⏳ Sending…",
"test_ok": "✅ Response {code} — check that the speaker has spoken." "test_ok": "✅ Response {code} — check that the speaker has spoken.",
"heard_question": "Did you hear the voice?",
"heard_yes": "Yes, I heard it",
"heard_no": "No, I didn't hear it",
"test_ok_kiosk": "TTS is working.",
"test_fail_steps": "Check: 1) media volume is not 0; 2) Google Text-to-Speech is installed and updated; 3) Italian voice package is downloaded in Android TTS settings."
}, },
"language": { "language": {
"title": "🌐 Language", "title": "🌐 Language",
+6 -1
View File
@@ -694,7 +694,12 @@
"voices_hint": "Las voces disponibles dependen del SO y el navegador. Pulsa ↺ si la lista no carga.", "voices_hint": "Las voces disponibles dependen del SO y el navegador. Pulsa ↺ si la lista no carga.",
"url_missing": "⚠️ URL del endpoint faltante.", "url_missing": "⚠️ URL del endpoint faltante.",
"test_sending": "⏳ Enviando…", "test_sending": "⏳ Enviando…",
"test_ok": "✅ Respuesta {code} — comprueba que el altavoz haya hablado." "test_ok": "✅ Respuesta {code} — comprueba que el altavoz haya hablado.",
"heard_question": "¿Has escuchado la voz?",
"heard_yes": "Sí, la escuché",
"heard_no": "No, no escuché nada",
"test_ok_kiosk": "TTS funcionando.",
"test_fail_steps": "Comprueba: 1) el volumen del multimedia no es 0; 2) Google Text-to-Speech está instalado y actualizado; 3) el paquete de voz español está descargado en la configuración TTS de Android."
}, },
"language": { "language": {
"title": "🌐 Idioma", "title": "🌐 Idioma",
+6 -1
View File
@@ -694,7 +694,12 @@
"voices_hint": "Les voix disponibles dépendent du système d'exploitation et du navigateur. Appuyez sur ↺ si la liste ne se charge pas.", "voices_hint": "Les voix disponibles dépendent du système d'exploitation et du navigateur. Appuyez sur ↺ si la liste ne se charge pas.",
"url_missing": "⚠️ URL de l'endpoint manquante.", "url_missing": "⚠️ URL de l'endpoint manquante.",
"test_sending": "⏳ Envoi…", "test_sending": "⏳ Envoi…",
"test_ok": "✅ Réponse {code} — vérifiez que le haut-parleur a parlé." "test_ok": "✅ Réponse {code} — vérifiez que le haut-parleur a parlé.",
"heard_question": "Avez-vous entendu la voix ?",
"heard_yes": "Oui, je l'ai entendu",
"heard_no": "Non, je n'ai rien entendu",
"test_ok_kiosk": "TTS fonctionne.",
"test_fail_steps": "Vérifiez : 1) le volume média n'est pas 0 ; 2) Google Text-to-Speech est installé et mis à jour ; 3) le pack vocal français est téléchargé dans les paramètres TTS Android."
}, },
"language": { "language": {
"title": "🌐 Langue", "title": "🌐 Langue",
+6 -1
View File
@@ -701,7 +701,12 @@
"voices_hint": "Le voci disponibili dipendono dal sistema operativo e dal browser. Su macOS/iOS è disponibile la voce Paola (italiano). Premi ↺ se la lista non si carica.", "voices_hint": "Le voci disponibili dipendono dal sistema operativo e dal browser. Su macOS/iOS è disponibile la voce Paola (italiano). Premi ↺ se la lista non si carica.",
"url_missing": "⚠️ URL endpoint mancante.", "url_missing": "⚠️ URL endpoint mancante.",
"test_sending": "⏳ Invio in corso…", "test_sending": "⏳ Invio in corso…",
"test_ok": "✅ Risposta {code} — controlla che l'altoparlante abbia parlato." "test_ok": "✅ Risposta {code} — controlla che l'altoparlante abbia parlato.",
"heard_question": "Hai sentito la voce?",
"heard_yes": "Sì, ho sentito",
"heard_no": "No, non ho sentito",
"test_ok_kiosk": "TTS funzionante.",
"test_fail_steps": "Controlla: 1) volume media del dispositivo non sia 0; 2) Google Text-to-Speech installato e aggiornato; 3) pacchetto vocale italiano scaricato nelle impostazioni TTS Android."
}, },
"language": { "language": {
"title": "🌐 Lingua / Language", "title": "🌐 Lingua / Language",