a425ce31e6
- 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>
201 lines
7.9 KiB
PHP
201 lines
7.9 KiB
PHP
<?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()]);
|
|
}
|