Glossar: Komplettpaket (28 Eintraege, 19 SVGs, 5 DALL-E-Bilder, iPad, Experimente)
- Mindmap mit Force-directed Layout (pages/mindmap.php) + Cross-Links - 28 Eintraege im Fachartikel-Stil mit 113 Quellen + Inline-Referenzen [^n] - Leichte-Sprache-Version fuer alle 28 Eintraege (short_easy, text_easy, examples.text_easy) - 19 Infografiken (12 neu): co2-Keeling, klimawandel-Temp, kohlenstoff-Kreislauf, fotosynthese, energiemix-AT, methan-GWP, photovoltaik, wasserkraft, kipppunkt, biodiversitaet-LPI, permafrost, treibhausgas-GWP + bestehende (treibhauseffekt, ppm, windrad, albedo, fossile, kwh, co2-fussabdruck) - 5 DALL-E-Reprasentationsbilder im Querformat (nachhaltigkeit, klimaneutralitaet, erneuerbare-energie, emissionen, pariser-abkommen) - Alphabetische Sprungnavigation A-Z mit Buchstabenbannern - Lightbox-Vollbild auf allen Infografiken mit expliziten SVG-Dimensionen (Safari/iPad-Fix) - Easy-Sprache-Toggle pro Card mit localStorage-Persistenz - 4 interaktive Experimente: kWh-Rennen, ppm-Finder, albedo-Slider, CO2-Fussabdruck-Rechner - iPad-Optimierung: Touch-Ziele >=38px, Hover in @media (hover), touch-action - Modul-Metadaten aus DB-Tabelle module_info (keine Hardcodes) - Cache-Buster auf JS-Asset - Schema-Erweiterung: glossar_sources, examples.text_easy Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,200 @@
|
||||
<?php
|
||||
/**
|
||||
* Glossar API
|
||||
* GET /api/glossar → alle Einträge (nur Kernfelder)
|
||||
* GET /api/glossar?key=co2 → einzelner Eintrag (volles Detail für Tooltip/Seite)
|
||||
* GET /api/glossar?module=klima → alle Einträge eines Moduls
|
||||
* GET /api/glossar?search=treibhaus → Volltextsuche
|
||||
*
|
||||
* POST /api/glossar (nur Admin) → neuer Eintrag (TODO)
|
||||
* PUT /api/glossar?id=42 (Admin) → bearbeiten (TODO)
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../config/app.php';
|
||||
require_once __DIR__ . '/../config/db.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
$db = getDB();
|
||||
|
||||
// === Leichte Sprache ===
|
||||
// Priorität: ?easy=1|0 (explizit) > Schüler-Session mit students.easy_language=1 > normal
|
||||
if (session_status() === PHP_SESSION_NONE) session_start();
|
||||
$easy = false;
|
||||
if (isset($_GET['easy'])) {
|
||||
$easy = $_GET['easy'] === '1';
|
||||
} elseif (!empty($_SESSION['student_id'])) {
|
||||
if (!isset($_SESSION['_easy_cache']) || ($_SESSION['_easy_cache']['id'] ?? null) !== $_SESSION['student_id']) {
|
||||
$stmt = $db->prepare('SELECT easy_language FROM students WHERE id = ?');
|
||||
$stmt->execute([$_SESSION['student_id']]);
|
||||
$r = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
$_SESSION['_easy_cache'] = ['id' => $_SESSION['student_id'], 'val' => !empty($r['easy_language'])];
|
||||
}
|
||||
$easy = $_SESSION['_easy_cache']['val'];
|
||||
}
|
||||
// Cache pro Variante
|
||||
header($easy ? 'Cache-Control: private, max-age=60' : 'Cache-Control: public, max-age=600');
|
||||
|
||||
/** Liefert Easy-Variante, sonst Fallback auf normal */
|
||||
function pickText(array $row, string $normalKey, string $easyKey, bool $easy) {
|
||||
if ($easy && !empty($row[$easyKey])) return $row[$easyKey];
|
||||
return $row[$normalKey] ?? null;
|
||||
}
|
||||
|
||||
try {
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
|
||||
if ($method === 'GET') {
|
||||
// Graph-Endpoint: alle Nodes + Edges für die Mindmap
|
||||
if (!empty($_GET['graph'])) {
|
||||
// Nodes inkl. Module-Zuordnung
|
||||
$nodes = $db->query('
|
||||
SELECT g.id, g.key_slug, g.title, g.short, g.category, g.image_path,
|
||||
GROUP_CONCAT(m.module_id) AS modules
|
||||
FROM glossar g
|
||||
LEFT JOIN glossar_modules m ON m.glossar_id = g.id
|
||||
GROUP BY g.id
|
||||
ORDER BY g.title
|
||||
')->fetchAll(PDO::FETCH_ASSOC);
|
||||
foreach ($nodes as &$n) {
|
||||
$n['modules'] = $n['modules'] ? explode(',', $n['modules']) : [];
|
||||
$n['id'] = (int) $n['id'];
|
||||
}
|
||||
unset($n);
|
||||
|
||||
// Edges: related-Tabelle — jede Kante einmal (kleinere id → grössere id)
|
||||
$edges = $db->query('
|
||||
SELECT LEAST(glossar_id, related_id) AS a, GREATEST(glossar_id, related_id) AS b
|
||||
FROM glossar_related
|
||||
GROUP BY a, b
|
||||
')->fetchAll(PDO::FETCH_ASSOC);
|
||||
foreach ($edges as &$e) {
|
||||
$e['a'] = (int) $e['a'];
|
||||
$e['b'] = (int) $e['b'];
|
||||
}
|
||||
unset($e);
|
||||
|
||||
echo json_encode(['nodes' => $nodes, 'edges' => $edges], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Einzelner Eintrag per key_slug
|
||||
if (!empty($_GET['key'])) {
|
||||
$key = $_GET['key'];
|
||||
$entry = $db->prepare('SELECT * FROM glossar WHERE key_slug = ?');
|
||||
$entry->execute([$key]);
|
||||
$row = $entry->fetch(PDO::FETCH_ASSOC);
|
||||
if (!$row) {
|
||||
http_response_code(404);
|
||||
echo json_encode(['error' => 'not found']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Leichte Sprache: falls aktiv, short/text durch easy-Varianten ersetzen (Fallback auf normal)
|
||||
$row['short'] = pickText($row, 'short', 'short_easy', $easy);
|
||||
$row['text'] = pickText($row, 'text', 'text_easy', $easy);
|
||||
$row['_easy'] = $easy; // Meta-Info fürs Frontend
|
||||
// Roh-Easy-Felder entfernen, damit JSON schlank bleibt
|
||||
unset($row['short_easy'], $row['text_easy']);
|
||||
|
||||
// Module
|
||||
$mods = $db->prepare('SELECT module_id FROM glossar_modules WHERE glossar_id = ?');
|
||||
$mods->execute([$row['id']]);
|
||||
$row['modules'] = $mods->fetchAll(PDO::FETCH_COLUMN);
|
||||
|
||||
// Beispiele (inkl. optionaler Quellen-Referenz + Easy-Variante)
|
||||
$ex = $db->prepare('SELECT text, text_easy, source_ref FROM glossar_examples WHERE glossar_id = ? ORDER BY sort_order');
|
||||
$ex->execute([$row['id']]);
|
||||
$examples = $ex->fetchAll(PDO::FETCH_ASSOC);
|
||||
foreach ($examples as &$e) {
|
||||
$e['text'] = pickText($e, 'text', 'text_easy', $easy);
|
||||
unset($e['text_easy']);
|
||||
}
|
||||
unset($e);
|
||||
$row['examples'] = $examples;
|
||||
|
||||
// Quellen
|
||||
$sr = $db->prepare('SELECT ref_num, label, url, access_date FROM glossar_sources WHERE glossar_id = ? ORDER BY ref_num');
|
||||
$sr->execute([$row['id']]);
|
||||
$row['sources'] = $sr->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// Links
|
||||
$lk = $db->prepare('SELECT label, url FROM glossar_links WHERE glossar_id = ? ORDER BY sort_order');
|
||||
$lk->execute([$row['id']]);
|
||||
$row['links'] = $lk->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// Related (mit title + key)
|
||||
$rel = $db->prepare('
|
||||
SELECT g.key_slug, g.title
|
||||
FROM glossar_related r
|
||||
JOIN glossar g ON g.id = r.related_id
|
||||
WHERE r.glossar_id = ?
|
||||
');
|
||||
$rel->execute([$row['id']]);
|
||||
$row['related'] = $rel->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
echo json_encode($row, JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Alle Einträge eines Moduls
|
||||
if (!empty($_GET['module'])) {
|
||||
$mod = $_GET['module'];
|
||||
$stmt = $db->prepare('
|
||||
SELECT g.id, g.key_slug, g.title, g.short, g.short_easy, g.text, g.text_easy,
|
||||
g.unit, g.category, g.image_path, g.animation_path
|
||||
FROM glossar g
|
||||
JOIN glossar_modules m ON m.glossar_id = g.id
|
||||
WHERE m.module_id = ?
|
||||
ORDER BY g.title
|
||||
');
|
||||
$stmt->execute([$mod]);
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
foreach ($rows as &$r) {
|
||||
$r['short'] = pickText($r, 'short', 'short_easy', $easy);
|
||||
$r['text'] = pickText($r, 'text', 'text_easy', $easy);
|
||||
unset($r['short_easy'], $r['text_easy']);
|
||||
}
|
||||
unset($r);
|
||||
echo json_encode($rows, JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Volltextsuche
|
||||
if (!empty($_GET['search'])) {
|
||||
$q = '%' . $_GET['search'] . '%';
|
||||
$stmt = $db->prepare('
|
||||
SELECT id, key_slug, title, short, category
|
||||
FROM glossar
|
||||
WHERE title LIKE ? OR short LIKE ? OR text LIKE ?
|
||||
ORDER BY title
|
||||
LIMIT 50
|
||||
');
|
||||
$stmt->execute([$q, $q, $q]);
|
||||
echo json_encode($stmt->fetchAll(PDO::FETCH_ASSOC), JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Alle Einträge (Übersicht) mit Modul-Zuordnungen
|
||||
$stmt = $db->query('
|
||||
SELECT g.id, g.key_slug, g.title, g.short, g.category, g.image_path,
|
||||
GROUP_CONCAT(m.module_id) AS modules
|
||||
FROM glossar g
|
||||
LEFT JOIN glossar_modules m ON m.glossar_id = g.id
|
||||
GROUP BY g.id
|
||||
ORDER BY g.title
|
||||
');
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
foreach ($rows as &$r) {
|
||||
$r['modules'] = $r['modules'] ? explode(',', $r['modules']) : [];
|
||||
}
|
||||
echo json_encode($rows, JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
http_response_code(405);
|
||||
echo json_encode(['error' => 'method not allowed']);
|
||||
} catch (Throwable $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => $e->getMessage()]);
|
||||
}
|
||||
Reference in New Issue
Block a user