/ — dieser * Ordner ist per .htaccess gesperrt (Originale enthalten EXIF/GPS!). * Atlas holt sie vom Server, strippt EXIF, skaliert und baut sie in die Slots ein. */ require_once __DIR__ . '/../php/config/app.php'; $bp = BASE_PATH; $UPLOAD_BASE = __DIR__ . '/../uploads/buch-fotos'; $MAX_BYTES = 40 * 1024 * 1024; // 40 MB je Datei $MAX_FILES = 12; // je Absenden $ALLOWED_EXT = ['jpg','jpeg','png','heic','heif','webp','tif','tiff']; /* ---------- Slots aus den Kapiteln parsen ---------- */ $ORDER = ['tourismustal','tourismusregion','kofferdetektiv','busfahrt','logistik', 'weltkueche','farmer','klima','fluss','energiemanager','heli', 'sonnensystem','eu-werkstatt']; $KAPITEL = []; foreach ($ORDER as $id) { $file = __DIR__ . "/buch-{$id}.php"; if (!is_file($file)) continue; $src = file_get_contents($file); $titel = preg_match('/

(.*?)<\/h1>/s', $src, $m) ? trim(strip_tags($m[1])) : $id; preg_match_all("/buchBild\(\s*'((?:[^'\\\\]|\\\\.)*)'\s*,\s*'((?:[^'\\\\]|\\\\.)*)'/s", $src, $mm, PREG_SET_ORDER); $slots = []; foreach ($mm as $b) { $slug = stripcslashes($b[1]); $vorschlag = stripcslashes($b[2]); // Leicht- und Normal-Version rufen denselben Slot auf → längste Beschreibung gewinnt if (!isset($slots[$slug]) || mb_strlen($vorschlag) > mb_strlen($slots[$slug])) { $slots[$slug] = $vorschlag; } } if ($slots) $KAPITEL[$id] = ['titel' => $titel, 'slots' => $slots]; } /* ---------- Helfer ---------- */ function slotBildPfad(string $sim, string $slug): ?string { foreach (['jpg','jpeg','webp','png'] as $ext) { $rel = "assets/img/buch/{$sim}/{$slug}.{$ext}"; if (is_file(__DIR__ . '/../' . $rel)) return $rel; } return null; } function pendingUploads(string $base, string $sim, string $slug): array { $dir = "{$base}/{$sim}"; if (!is_dir($dir)) return []; $out = []; foreach (glob($dir . '/' . $slug . '__*') as $f) { if (substr($f, -9) === '.note.txt') continue; $out[] = ['name' => basename($f), 'kb' => (int)round(filesize($f) / 1024)]; } return $out; } function safeName(string $s): string { $s = preg_replace('/[^A-Za-z0-9._-]+/', '-', $s); return trim(mb_substr($s, 0, 80), '-.') ?: 'foto'; } /* ---------- Upload verarbeiten (PRG-Muster) ---------- */ $fehler = []; if ($_SERVER['REQUEST_METHOD'] === 'POST') { $sim = $_POST['sim'] ?? ''; $slug = preg_replace('/[^a-z0-9-]/', '', $_POST['slug'] ?? ''); $note = trim($_POST['note'] ?? ''); $gueltig = ($sim === 'sonstiges' && $slug !== '') || (isset($KAPITEL[$sim]) && isset($KAPITEL[$sim]['slots'][$slug])); if (!$gueltig) { $fehler[] = 'Unbekannter Foto-Slot.'; } else { $dir = "{$UPLOAD_BASE}/{$sim}"; if (!is_dir($dir)) mkdir($dir, 0775, true); // Ordner absichern: Originale (mit GPS!) dürfen nie öffentlich abrufbar sein if (!is_file($UPLOAD_BASE . '/.htaccess')) { file_put_contents($UPLOAD_BASE . '/.htaccess', "Require all denied\n"); } $ts = date('Ymd-His'); $gespeichert = 0; if (!empty($_FILES['fotos']) && is_array($_FILES['fotos']['name'])) { $n = min(count($_FILES['fotos']['name']), $MAX_FILES); for ($i = 0; $i < $n; $i++) { if ($_FILES['fotos']['error'][$i] === UPLOAD_ERR_NO_FILE) continue; if ($_FILES['fotos']['error'][$i] !== UPLOAD_ERR_OK) { $fehler[] = 'Upload-Fehler bei Datei ' . ($i + 1) . ' (Code ' . $_FILES['fotos']['error'][$i] . ').'; continue; } if ($_FILES['fotos']['size'][$i] > $MAX_BYTES) { $fehler[] = htmlspecialchars($_FILES['fotos']['name'][$i]) . ' ist größer als 40 MB.'; continue; } $orig = $_FILES['fotos']['name'][$i]; $ext = strtolower(pathinfo($orig, PATHINFO_EXTENSION)); if (!in_array($ext, $ALLOWED_EXT, true)) { $fehler[] = htmlspecialchars($orig) . ': Dateityp .' . htmlspecialchars($ext) . ' nicht erlaubt.'; continue; } $ziel = "{$dir}/{$slug}__{$ts}__" . ($i + 1) . '__' . safeName($orig); if (move_uploaded_file($_FILES['fotos']['tmp_name'][$i], $ziel)) $gespeichert++; else $fehler[] = htmlspecialchars($orig) . ' konnte nicht gespeichert werden.'; } } if ($gespeichert === 0 && !$fehler) $fehler[] = 'Keine Datei ausgewählt.'; if ($note !== '' && ($gespeichert > 0 || $sim === 'sonstiges')) { file_put_contents("{$dir}/{$slug}__{$ts}.note.txt", $note . "\n"); } if ($gespeichert > 0 && !$fehler) { header("Location: ?ok={$gespeichert}&sim=" . urlencode($sim) . "&slug=" . urlencode($slug) . "#{$sim}-{$slug}"); exit; } } } $ok = isset($_GET['ok']) ? (int)$_GET['ok'] : 0; $okAnker = $ok ? (($_GET['sim'] ?? '') . '-' . ($_GET['slug'] ?? '')) : ''; /* Übersichtszahlen */ $gesamt = 0; $mitFoto = 0; $wartend = 0; foreach ($KAPITEL as $sim => $k) { foreach ($k['slots'] as $slug => $v) { $gesamt++; if (slotBildPfad($sim, $slug)) $mitFoto++; if (pendingUploads($UPLOAD_BASE, $sim, $slug)) $wartend++; } } ?> 📷 Foto-Backend · Schulbuch · GeoGraSim

📷 Foto-Backend zum Schulbuch

Alle Foto-Slots aus den 13 Schulbuch-Kapiteln mit ihren Foto-Wünschen. Zu jedem Slot kannst du direkt Fotos hochladen (mehrere auf einmal, auch HEIC vom iPhone/iPad) und eine Notiz dazuschreiben — Atlas holt sie ab, entfernt die GPS-Daten, verkleinert sie und baut sie ein.

📷 Slots eingebaut 📥 mit wartenden Uploads
🔒 Hochgeladene Original-Dateien sind nicht öffentlich abrufbar (Ordner gesperrt) — deine GPS-Daten bleiben privat. Veröffentlicht wird nur die bereinigte Version im Kapitel.
Foto 1 ? 's' : '' ?> hochgeladen — danke! Atlas kümmert sich um Zuschnitt und Einbau.
⚠️
Kapitel: $k): ?> · Sonstiges
$k): ?>

$vorschlag): $bild = slotBildPfad($sim, $slug); $pend = pendingUploads($UPLOAD_BASE, $sim, $slug); $status = $bild ? 'hat' : ($pend ? 'wartet' : 'offen'); ?>

📥 ( KB)

Sonstiges / ohne Zuordnung

Fotos, die zu keinem Slot passen, aber gut sein könnten — mit kurzer Beschreibung, wofür du sie siehst. Atlas ordnet sie zu oder legt neue Slots an.