add Multible Images to News

This commit is contained in:
Hein Erik
2026-04-28 10:10:13 +02:00
parent f4d9aa11bb
commit 5bfad3966c

303
admin.php
View File

@@ -1,8 +1,7 @@
<?php <?php
// admin.php - Multi-User Login, Google Authenticator 2FA, Cropper, News Verwaltung & AES-256 VERSCHLÜSSELUNG // admin.php - Multi-User Login, Google Authenticator 2FA, 4-Bilder Upload & News Verwaltung
session_start(); session_start();
// Fehleranzeige (für Live-Betrieb auf 0 setzen - oder anlassen, wenn du Schmerzen magst)
ini_set('display_errors', 1); ini_set('display_errors', 1);
ini_set('display_startup_errors', 1); ini_set('display_startup_errors', 1);
error_reporting(E_ALL); error_reporting(E_ALL);
@@ -13,7 +12,6 @@ error_reporting(E_ALL);
class MiniTOTP { class MiniTOTP {
public static function verify($secret, $code) { public static function verify($secret, $code) {
$time = floor(time() / 30); $time = floor(time() / 30);
// Prüft aktuelles, vorheriges und nächstes Zeitfenster (Toleranz)
for ($i = -1; $i <= 1; $i++) { for ($i = -1; $i <= 1; $i++) {
if (self::getCode($secret, $time + $i) === $code) return true; if (self::getCode($secret, $time + $i) === $code) return true;
} }
@@ -61,13 +59,18 @@ try {
$db = new PDO('sqlite:news_database.sqlite'); $db = new PDO('sqlite:news_database.sqlite');
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// News Tabelle // News Tabelle initialisieren
$db->exec("CREATE TABLE IF NOT EXISTS news ( $db->exec("CREATE TABLE IF NOT EXISTS news (
id INTEGER PRIMARY KEY AUTOINCREMENT, date TEXT, department TEXT, title TEXT, content TEXT, full_text TEXT, image TEXT, link TEXT id INTEGER PRIMARY KEY AUTOINCREMENT, date TEXT, department TEXT, title TEXT, content TEXT, full_text TEXT, image TEXT, link TEXT
)"); )");
try { $db->exec("ALTER TABLE news ADD COLUMN full_text TEXT"); } catch(PDOException $e) {}
// Benutzer Tabelle // DB-Schema Updates (Falls noch alte Version)
try { $db->exec("ALTER TABLE news ADD COLUMN full_text TEXT"); } catch(PDOException $e) {}
try { $db->exec("ALTER TABLE news ADD COLUMN image2 TEXT"); } catch(PDOException $e) {}
try { $db->exec("ALTER TABLE news ADD COLUMN image3 TEXT"); } catch(PDOException $e) {}
try { $db->exec("ALTER TABLE news ADD COLUMN image4 TEXT"); } catch(PDOException $e) {}
try { $db->exec("ALTER TABLE news ADD COLUMN title_index INTEGER DEFAULT 1"); } catch(PDOException $e) {}
$db->exec("CREATE TABLE IF NOT EXISTS users ( $db->exec("CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT UNIQUE, password_hash TEXT, totp_secret TEXT id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT UNIQUE, password_hash TEXT, totp_secret TEXT
)"); )");
@@ -78,17 +81,14 @@ try {
$userCount = $db->query("SELECT COUNT(*) FROM users")->fetchColumn(); $userCount = $db->query("SELECT COUNT(*) FROM users")->fetchColumn();
// ============================================================================ // ============================================================================
// 3. ERSTEINRICHTUNG (Wenn keine Nutzer existieren) // 3. ERSTEINRICHTUNG
// ============================================================================ // ============================================================================
if ($userCount == 0) { if ($userCount == 0) {
$setup_msg = "";
$new_secret = MiniTOTP::generateSecret(); $new_secret = MiniTOTP::generateSecret();
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['do_setup'])) { if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['do_setup'])) {
$user = trim($_POST['username']); $user = trim($_POST['username']);
$pass = $_POST['password']; $pass = $_POST['password'];
$secret = $_POST['totp_secret']; $secret = $_POST['totp_secret'];
if (!empty($user) && !empty($pass)) { if (!empty($user) && !empty($pass)) {
$stmt = $db->prepare("INSERT INTO users (username, password_hash, totp_secret) VALUES (?, ?, ?)"); $stmt = $db->prepare("INSERT INTO users (username, password_hash, totp_secret) VALUES (?, ?, ?)");
$stmt->execute([$user, password_hash($pass, PASSWORD_DEFAULT), $secret]); $stmt->execute([$user, password_hash($pass, PASSWORD_DEFAULT), $secret]);
@@ -99,15 +99,10 @@ if ($userCount == 0) {
?> ?>
<!DOCTYPE html><html lang="de"><head><meta charset="UTF-8"><title>Setup | SG 07</title> <!DOCTYPE html><html lang="de"><head><meta charset="UTF-8"><title>Setup | SG 07</title>
<style>body{font-family:sans-serif;background:#f4f4f4;display:flex;justify-content:center;align-items:center;height:100vh;} .box{background:#fff;padding:30px;border-radius:8px;box-shadow:0 4px 15px rgba(0,0,0,0.1);max-width:400px;text-align:center;} input,button{width:100%;padding:10px;margin:10px 0;box-sizing:border-box;} button{background:#d32f2f;color:white;border:none;font-weight:bold;cursor:pointer;} .qr{margin:20px 0;}</style></head> <style>body{font-family:sans-serif;background:#f4f4f4;display:flex;justify-content:center;align-items:center;height:100vh;} .box{background:#fff;padding:30px;border-radius:8px;box-shadow:0 4px 15px rgba(0,0,0,0.1);max-width:400px;text-align:center;} input,button{width:100%;padding:10px;margin:10px 0;box-sizing:border-box;} button{background:#d32f2f;color:white;border:none;font-weight:bold;cursor:pointer;} .qr{margin:20px 0;}</style></head>
<body><div class="box"><h1 style="color:#d32f2f;">Ersteinrichtung</h1><p>Erstelle den ersten Admin-Zugang.</p> <body><div class="box"><h1 style="color:#d32f2f;">Ersteinrichtung</h1>
<img src="<?= MiniTOTP::getQRCodeUrl('Admin', $new_secret) ?>" class="qr" alt="QR Code"> <img src="<?= MiniTOTP::getQRCodeUrl('Admin', $new_secret) ?>" class="qr" alt="QR Code">
<p style="font-size:0.9rem;color:#666;">1. Scanne diesen QR-Code mit dem <b>Google Authenticator</b> oder <b>Authy</b>.</p> <p style="font-size:0.9rem;color:#666;">1. Scanne diesen QR-Code mit dem <b>Google Authenticator</b> oder <b>Authy</b>.</p>
<form method="POST"> <form method="POST"><input type="text" name="username" placeholder="Benutzername" required><input type="password" name="password" placeholder="Sicheres Passwort" required><input type="hidden" name="totp_secret" value="<?= $new_secret ?>"><button type="submit" name="do_setup">Admin anlegen & weiter</button></form></div></body></html>
<input type="text" name="username" placeholder="Benutzername" required>
<input type="password" name="password" placeholder="Sicheres Passwort" required>
<input type="hidden" name="totp_secret" value="<?= $new_secret ?>">
<button type="submit" name="do_setup">Admin anlegen & weiter</button>
</form></div></body></html>
<?php exit; <?php exit;
} }
@@ -143,12 +138,7 @@ if (!isset($_SESSION['logged_in']) || $_SESSION['logged_in'] !== true) {
<style>body{font-family:'Segoe UI',sans-serif;background:#f4f4f4;display:flex;justify-content:center;align-items:center;height:100vh;} .login-box{background:#fff;padding:40px;border-radius:8px;box-shadow:0 4px 20px rgba(0,0,0,0.1);max-width:350px;width:100%;text-align:center;border-top:5px solid #d32f2f;} input{width:100%;padding:12px;margin-bottom:15px;border:1px solid #ccc;border-radius:4px;box-sizing:border-box;} button{width:100%;padding:14px;background:#d32f2f;color:white;border:none;font-weight:bold;border-radius:4px;cursor:pointer;} button:hover{background:#9a0007;} .err{color:red;margin-bottom:15px;}</style></head> <style>body{font-family:'Segoe UI',sans-serif;background:#f4f4f4;display:flex;justify-content:center;align-items:center;height:100vh;} .login-box{background:#fff;padding:40px;border-radius:8px;box-shadow:0 4px 20px rgba(0,0,0,0.1);max-width:350px;width:100%;text-align:center;border-top:5px solid #d32f2f;} input{width:100%;padding:12px;margin-bottom:15px;border:1px solid #ccc;border-radius:4px;box-sizing:border-box;} button{width:100%;padding:14px;background:#d32f2f;color:white;border:none;font-weight:bold;border-radius:4px;cursor:pointer;} button:hover{background:#9a0007;} .err{color:red;margin-bottom:15px;}</style></head>
<body><div class="login-box"><div style="font-size:3rem;">🔒</div><h1 style="color:#d32f2f;margin-bottom:20px;">Admin Login</h1> <body><div class="login-box"><div style="font-size:3rem;">🔒</div><h1 style="color:#d32f2f;margin-bottom:20px;">Admin Login</h1>
<?php if($login_error) echo "<div class='err'>$login_error</div>"; ?> <?php if($login_error) echo "<div class='err'>$login_error</div>"; ?>
<form method="POST"> <form method="POST"><input type="text" name="username" placeholder="Benutzername" required autofocus><input type="password" name="password" placeholder="Passwort" required><input type="text" name="totp_code" placeholder="6-stelliger Authenticator Code" pattern="\d{6}" maxlength="6" required autocomplete="off"><button type="submit" name="do_login">Sicher einloggen</button></form></div></body></html>
<input type="text" name="username" placeholder="Benutzername" required autofocus>
<input type="password" name="password" placeholder="Passwort" required>
<input type="text" name="totp_code" placeholder="6-stelliger Authenticator Code" pattern="\d{6}" maxlength="6" required autocomplete="off">
<button type="submit" name="do_login">Sicher einloggen</button>
</form></div></body></html>
<?php exit; <?php exit;
} }
@@ -157,65 +147,73 @@ if (!isset($_SESSION['logged_in']) || $_SESSION['logged_in'] !== true) {
// ============================================================================ // ============================================================================
$message = ""; $message = "";
// --- BILD-UPLOAD FUNKTION (mit Base64 Crop-Unterstützung) --- // Bild-Upload Helper für 4 Slots
function handleImageUpload() { function handleSingleImage($index) {
if (!empty($_POST['cropped_image_data'])) { $final_path = '';
$data = $_POST['cropped_image_data'];
list($type, $data) = explode(';', $data);
list(, $data) = explode(',', $data);
$data = base64_decode($data);
// Altes Bild behalten, falls im Edit-Modus und nichts Neues hochgeladen wird
if (isset($_POST['existing_image_'.$index]) && !empty($_POST['existing_image_'.$index])) {
$final_path = $_POST['existing_image_'.$index];
}
// 1. Priorität: Neuer Datei-Upload
if (isset($_FILES['image_file_'.$index]) && $_FILES['image_file_'.$index]['error'] === UPLOAD_ERR_OK) {
$year = date('Y'); $year = date('Y');
$upload_dir = __DIR__ . '/img/news/' . $year; $upload_dir = __DIR__ . '/img/news/' . $year;
if (!is_dir($upload_dir)) { if (!is_dir($upload_dir)) mkdir($upload_dir, 0777, true);
mkdir($upload_dir, 0777, true); // Erstellt den Ordner, wenn er fehlt
$ext = strtolower(pathinfo($_FILES['image_file_'.$index]['name'], PATHINFO_EXTENSION));
$allowed = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
if (in_array($ext, $allowed)) {
$filename = date('Ymd') . '_' . rand(10000, 99999) . '_img' . $index . '.' . $ext;
if (move_uploaded_file($_FILES['image_file_'.$index]['tmp_name'], $upload_dir . '/' . $filename)) {
$final_path = 'img/news/' . $year . '/' . $filename;
}
}
}
// 2. Priorität: Bild-URL
elseif (isset($_POST['image_url_'.$index]) && !empty($_POST['image_url_'.$index])) {
$final_path = str_replace(['http://new.sg07-stleon.de/', 'https://new.sg07-stleon.de/', 'new.sg07-stleon.de/', 'new.sg07-stleon.de\\'], '', trim($_POST['image_url_'.$index]));
}
// 3. Priorität: Platzhalter
elseif (isset($_POST['placeholder_image_'.$index]) && !empty($_POST['placeholder_image_'.$index])) {
$final_path = $_POST['placeholder_image_'.$index];
} }
$filename = date('Ymd') . '_' . rand(10000, 99999) . '.jpg'; // Leeres Bild zurücksetzen, wenn der Nutzer explizit "löschen" wollte (hier vereinfacht: wenn URL und Platzhalter leer sind und kein Upload stattfand)
$target_file = $upload_dir . '/' . $filename; if (empty($_FILES['image_file_'.$index]['name']) && empty($_POST['image_url_'.$index]) && empty($_POST['placeholder_image_'.$index]) && !isset($_POST['keep_existing_'.$index])) {
// Logik zum Behalten wird via Checkboxen geregelt, siehe Formular
}
if (file_put_contents($target_file, $data)) { return $final_path;
return 'img/news/' . $year . '/' . $filename;
}
}
return null;
} }
// --- AKTION: NEWS AKTUALISIEREN (EDIT) --- // --- AKTION: NEWS AKTUALISIEREN (EDIT) ---
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['update_news'])) { if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['update_news'])) {
$final_image_path = $_POST['existing_image']; $img1 = handleSingleImage(1);
$img2 = handleSingleImage(2);
$img3 = handleSingleImage(3);
$img4 = handleSingleImage(4);
$title_idx = (int)$_POST['title_index'];
$uploaded_img = handleImageUpload(); $stmt = $db->prepare("UPDATE news SET date=?, department=?, title=?, content=?, full_text=?, image=?, image2=?, image3=?, image4=?, title_index=?, link=? WHERE id=?");
if ($uploaded_img) { if ($stmt->execute([$_POST['date'], $_POST['department'], $_POST['title'], $_POST['content'], $_POST['full_text'], $img1, $img2, $img3, $img4, $title_idx, $_POST['link'], $_POST['news_id']])) {
$final_image_path = $uploaded_img; $message = "<div class='success'>✓ News (inkl. Galerie) erfolgreich aktualisiert!</div>";
} elseif (isset($_POST['image_url']) && !empty($_POST['image_url'])) {
$final_image_path = str_replace(['http://new.sg07-stleon.de/', 'https://new.sg07-stleon.de/', 'new.sg07-stleon.de/', 'new.sg07-stleon.de\\'], '', trim($_POST['image_url']));
} elseif (isset($_POST['placeholder_image']) && !empty($_POST['placeholder_image'])) {
$final_image_path = $_POST['placeholder_image'];
}
$stmt = $db->prepare("UPDATE news SET date=?, department=?, title=?, content=?, full_text=?, image=?, link=? WHERE id=?");
if ($stmt->execute([$_POST['date'], $_POST['department'], $_POST['title'], $_POST['content'], $_POST['full_text'], $final_image_path, $_POST['link'], $_POST['news_id']])) {
$message = "<div class='success'>✓ News erfolgreich aktualisiert!</div>";
} }
} }
// --- AKTION: NEWS SPEICHERN (NEU) --- // --- AKTION: NEWS SPEICHERN (NEU) ---
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['save_news'])) { if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['save_news'])) {
$final_image_path = ''; $img1 = handleSingleImage(1);
$img2 = handleSingleImage(2);
$img3 = handleSingleImage(3);
$img4 = handleSingleImage(4);
$title_idx = (int)$_POST['title_index'];
$uploaded_img = handleImageUpload(); $stmt = $db->prepare("INSERT INTO news (date, department, title, content, full_text, image, image2, image3, image4, title_index, link) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
if ($uploaded_img) { if ($stmt->execute([$_POST['date'], $_POST['department'], $_POST['title'], $_POST['content'], $_POST['full_text'], $img1, $img2, $img3, $img4, $title_idx, $_POST['link']])) {
$final_image_path = $uploaded_img; $message = "<div class='success'>✓ News (inkl. Galerie) erfolgreich veröffentlicht!</div>";
} elseif (isset($_POST['image_url']) && !empty($_POST['image_url'])) {
$final_image_path = str_replace(['http://new.sg07-stleon.de/', 'https://new.sg07-stleon.de/', 'new.sg07-stleon.de/', 'new.sg07-stleon.de\\'], '', trim($_POST['image_url']));
} elseif (isset($_POST['placeholder_image']) && !empty($_POST['placeholder_image'])) {
$final_image_path = $_POST['placeholder_image'];
}
$stmt = $db->prepare("INSERT INTO news (date, department, title, content, full_text, image, link) VALUES (?, ?, ?, ?, ?, ?, ?)");
if ($stmt->execute([$_POST['date'], $_POST['department'], $_POST['title'], $_POST['content'], $_POST['full_text'], $final_image_path, $_POST['link']])) {
$message = "<div class='success'>✓ News erfolgreich veröffentlicht!</div>";
} }
} }
@@ -227,23 +225,6 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['delete_news'])) {
} }
} }
// --- AKTION: NEUEN BENUTZER ANLEGEN ---
$new_user_qr = "";
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['add_user'])) {
$new_user = trim($_POST['new_username']);
$new_pass = $_POST['new_password'];
$new_secret = MiniTOTP::generateSecret();
try {
$stmt = $db->prepare("INSERT INTO users (username, password_hash, totp_secret) VALUES (?, ?, ?)");
$stmt->execute([$new_user, password_hash($new_pass, PASSWORD_DEFAULT), $new_secret]);
$url = MiniTOTP::getQRCodeUrl($new_user, $new_secret);
$new_user_qr = "<div class='qr-box'><h3>Benutzer '$new_user' angelegt!</h3><p>Bitte lasse den neuen Benutzer <b>jetzt sofort</b> diesen QR Code scannen.</p><img src='$url' alt='QR Code'><p>Geheimer Schlüssel (falls QR nicht geht): <b>$new_secret</b></p></div>";
} catch(PDOException $e) {
$message = "<div class='error'>Fehler: Benutzername existiert evtl. schon.</div>";
}
}
// Edit-Modus prüfen // Edit-Modus prüfen
$is_edit = false; $is_edit = false;
$edit_news = null; $edit_news = null;
@@ -251,53 +232,80 @@ if (isset($_GET['edit_id'])) {
$stmt = $db->prepare("SELECT * FROM news WHERE id = ?"); $stmt = $db->prepare("SELECT * FROM news WHERE id = ?");
$stmt->execute([$_GET['edit_id']]); $stmt->execute([$_GET['edit_id']]);
$edit_news = $stmt->fetch(PDO::FETCH_ASSOC); $edit_news = $stmt->fetch(PDO::FETCH_ASSOC);
if ($edit_news) { if ($edit_news) $is_edit = true;
$is_edit = true;
}
} }
// Alle bestehenden News abrufen für die Übersichtstabelle
$existing_news = $db->query("SELECT id, date, department, title FROM news ORDER BY id DESC")->fetchAll(PDO::FETCH_ASSOC); $existing_news = $db->query("SELECT id, date, department, title FROM news ORDER BY id DESC")->fetchAll(PDO::FETCH_ASSOC);
// HTML Block für einen der 4 Bilder-Uploads
function renderImageSlot($index, $is_edit, $edit_news) {
$img_key = $index === 1 ? 'image' : 'image'.$index;
$current_val = $is_edit ? ($edit_news[$img_key] ?? '') : '';
$title_idx = $is_edit ? (int)($edit_news['title_index'] ?? 1) : 1;
$is_title = ($title_idx === $index);
echo "<div class='img-slot'>";
echo "<h4>Bild Slot $index</h4>";
// Radio für Titelbild
echo "<label style='color: #d32f2f;'><input type='radio' name='title_index' value='$index' " . ($is_title ? 'checked' : '') . "> <b>Als Titelbild (Cover) nutzen</b></label><hr style='border:0; height:1px; background:#ddd; margin:10px 0;'>";
if($is_edit && $current_val) {
echo "<input type='hidden' name='existing_image_$index' value='".htmlspecialchars($current_val)."'>";
echo "<label><input type='checkbox' name='keep_existing_$index' checked> Altes Bild behalten: <br><small>".htmlspecialchars(substr($current_val, 0, 30))."...</small></label><br>";
}
echo "<label>1. Datei hochladen</label>";
echo "<input type='file' name='image_file_$index' accept='image/*' style='background: white; margin-bottom: 5px;'>";
echo "<label>2. ODER Link (URL)</label>";
echo "<input type='text' name='image_url_$index' placeholder='https://...'>";
echo "<label>3. ODER Platzhalter</label>";
echo "<select name='placeholder_image_$index' style='width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px;'>";
echo "<option value=''>-- Kein Bild in diesem Slot --</option>";
echo "<option value=\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Crect width='100' height='100' fill='%23f4f4f4'/%3E%3Ccircle cx='50' cy='50' r='35' fill='%23d32f2f'/%3E%3Ctext x='50' y='57' font-size='20' fill='white' text-anchor='middle' font-weight='bold'%3ESG 07%3C/text%3E%3C/svg%3E\">Neutral (SG 07 Logo)</option>";
echo "<option value=\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Crect width='100' height='100' fill='%23e3f2fd'/%3E%3Ccircle cx='50' cy='50' r='35' fill='%232196f3'/%3E%3Ctext x='50' y='60' font-size='30' fill='white' text-anchor='middle' font-weight='bold' font-family='serif'%3Ei%3C/text%3E%3C/svg%3E\">Info (Blaues i)</option>";
echo "</select>";
echo "</div>";
}
?> ?>
<!DOCTYPE html> <!DOCTYPE html>
<html lang="de"> <html lang="de">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>Admin Dashboard | SG 07 St. Leon</title> <title>Admin Dashboard | SG 07 St. Leon</title>
<!-- Cropper.js für Bildzuschnitt -->
<link href="https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.5.13/cropper.min.css" rel="stylesheet">
<script src="https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.5.13/cropper.min.js"></script>
<style> <style>
body { font-family: 'Segoe UI', sans-serif; background: #f4f4f4; padding: 40px 20px; margin: 0; } body { font-family: 'Segoe UI', sans-serif; background: #f4f4f4; padding: 40px 20px; margin: 0; }
.container { max-width: 1000px; background: white; padding: 40px; border-radius: 8px; box-shadow: 0 4px 15px rgba(0,0,0,0.05); margin: 0 auto; } .container { max-width: 1200px; background: white; padding: 40px; border-radius: 8px; box-shadow: 0 4px 15px rgba(0,0,0,0.05); margin: 0 auto; }
.header-bar { display: flex; justify-content: space-between; align-items: center; border-bottom: 2px solid #f4f4f4; padding-bottom: 20px; margin-bottom: 30px; } .header-bar { display: flex; justify-content: space-between; align-items: center; border-bottom: 2px solid #f4f4f4; padding-bottom: 20px; margin-bottom: 30px; }
.header-bar h1 { color: #d32f2f; margin: 0; } .header-bar h1 { color: #d32f2f; margin: 0; }
.logout-btn { background: #555; color: white; padding: 8px 15px; border-radius: 4px; text-decoration: none; font-weight: bold;} .logout-btn { background: #555; color: white; padding: 8px 15px; border-radius: 4px; text-decoration: none; font-weight: bold;}
.grid-2 { display: grid; grid-template-columns: 2fr 1fr; gap: 40px; } .grid-2 { display: grid; grid-template-columns: 3fr 1fr; gap: 40px; }
label { font-weight: 600; display: block; margin-top: 15px; margin-bottom: 5px; color: #333;} label { font-weight: 600; display: block; margin-top: 15px; margin-bottom: 5px; color: #333; font-size: 0.9rem;}
input[type="text"], input[type="password"], textarea { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box; font-family: inherit; } input[type="text"], input[type="password"], textarea { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box; font-family: inherit; }
button { margin-top: 20px; padding: 15px; background: #d32f2f; color: white; border: none; font-weight: bold; border-radius: 4px; cursor: pointer; width: 100%; transition: background 0.2s; } button { margin-top: 20px; padding: 15px; background: #d32f2f; color: white; border: none; font-weight: bold; border-radius: 4px; cursor: pointer; width: 100%; transition: background 0.2s; }
button:hover { background: #9a0007; } button:hover { background: #9a0007; }
.success { background: #4CAF50; color: white; padding: 15px; border-radius: 4px; margin-bottom: 20px; font-weight: bold;} .success { background: #4CAF50; color: white; padding: 15px; border-radius: 4px; margin-bottom: 20px; font-weight: bold;}
.error { background: #d32f2f; color: white; padding: 15px; border-radius: 4px; margin-bottom: 20px; font-weight: bold;}
.sidebar-box { background: #fafafa; padding: 20px; border-radius: 8px; border: 1px solid #ddd; } .img-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px; background: #fafafa; padding: 20px; border: 1px solid #ddd; border-radius: 8px; margin-top: 10px;}
.qr-box { background: #fff3cd; color: #856404; padding: 20px; border-radius: 8px; text-align: center; margin-bottom: 20px; border: 1px solid #ffeeba;} .img-slot { background: white; padding: 15px; border: 1px solid #ccc; border-radius: 4px; }
.img-slot h4 { margin-top: 0; margin-bottom: 10px; color: #555; border-bottom: 2px solid #eee; padding-bottom: 5px;}
.news-table { width: 100%; border-collapse: collapse; margin-top: 20px; font-size: 0.95rem; } .news-table { width: 100%; border-collapse: collapse; margin-top: 20px; font-size: 0.95rem; }
.news-table th { background: #f4f4f4; padding: 12px; text-align: left; border-bottom: 2px solid #ddd; color: #333; } .news-table th { background: #f4f4f4; padding: 12px; text-align: left; border-bottom: 2px solid #ddd; color: #333; }
.news-table td { padding: 12px; border-bottom: 1px solid #eee; vertical-align: middle; } .news-table td { padding: 12px; border-bottom: 1px solid #eee; vertical-align: middle; }
.news-table tr:hover { background: #fafafa; }
.edit-btn { background: #2196F3; color: white; padding: 6px 12px; border: none; border-radius: 4px; cursor: pointer; text-decoration: none; font-size: 0.9rem; } .edit-btn { background: #2196F3; color: white; padding: 6px 12px; border: none; border-radius: 4px; cursor: pointer; text-decoration: none; font-size: 0.9rem; }
.social-btn { background: #E1306C; color: white; padding: 6px 12px; border: none; border-radius: 4px; cursor: pointer; font-size: 0.9rem; margin-right: 5px; } .social-btn { background: #E1306C; color: white; padding: 6px 12px; border: none; border-radius: 4px; cursor: pointer; font-size: 0.9rem; margin-right: 5px; }
.delete-btn { background: #d32f2f; color: white; padding: 6px 12px; border: none; border-radius: 4px; cursor: pointer; margin: 0; width: auto; font-size: 0.9rem; } .delete-btn { background: #d32f2f; color: white; padding: 6px 12px; border: none; border-radius: 4px; cursor: pointer; margin: 0; width: auto; font-size: 0.9rem; }
@media(max-width: 768px) { .grid-2 { grid-template-columns: 1fr; } } @media(max-width: 900px) { .grid-2 { grid-template-columns: 1fr; } }
</style> </style>
</head> </head>
<body> <body>
<div class="container"> <div class="container">
<div class="header-bar"> <div class="header-bar">
<h1>SG 07 Dashboard</h1> <h1>SG 07 Dashboard <span style="font-size: 0.5em; background: #2196F3; color: white; padding: 3px 8px; border-radius: 10px; vertical-align: middle;">MULTIMEDIA EDITION</span></h1>
<div> <div>
<span style="margin-right:15px; color:#666;">Hallo, <b><?= htmlspecialchars($_SESSION['username']) ?></b></span> <span style="margin-right:15px; color:#666;">Hallo, <b><?= htmlspecialchars($_SESSION['username']) ?></b></span>
<a href="?action=logout" class="logout-btn">Abmelden</a> <a href="?action=logout" class="logout-btn">Abmelden</a>
@@ -305,7 +313,6 @@ $existing_news = $db->query("SELECT id, date, department, title FROM news ORDER
</div> </div>
<?= $message ?> <?= $message ?>
<?= $new_user_qr ?>
<div class="grid-2"> <div class="grid-2">
<!-- News Eintragen / Ändern --> <!-- News Eintragen / Ändern -->
@@ -314,7 +321,6 @@ $existing_news = $db->query("SELECT id, date, department, title FROM news ORDER
<form method="POST" id="newsForm" enctype="multipart/form-data"> <form method="POST" id="newsForm" enctype="multipart/form-data">
<?php if($is_edit): ?> <?php if($is_edit): ?>
<input type="hidden" name="news_id" value="<?= $edit_news['id'] ?>"> <input type="hidden" name="news_id" value="<?= $edit_news['id'] ?>">
<input type="hidden" name="existing_image" value="<?= htmlspecialchars($edit_news['image']) ?>">
<?php endif; ?> <?php endif; ?>
<label>Datum</label> <label>Datum</label>
@@ -328,31 +334,19 @@ $existing_news = $db->query("SELECT id, date, department, title FROM news ORDER
<label>Kompletter Artikel (Popup)</label> <label>Kompletter Artikel (Popup)</label>
<textarea name="full_text" rows="8" required><?= $is_edit ? htmlspecialchars($edit_news['full_text']) : '' ?></textarea> <textarea name="full_text" rows="8" required><?= $is_edit ? htmlspecialchars($edit_news['full_text']) : '' ?></textarea>
<label>Bild hochladen & zuschneiden</label> <h3 style="margin-top: 30px; margin-bottom: 5px; color: #333;">Bilder-Galerie (Maximal 4)</h3>
<input type="file" id="image_file_input" accept="image/*" style="background: white; margin-bottom: 10px;"> <p style="font-size: 0.85rem; color: #666; margin-top: 0;">Du musst nicht alle Slots füllen. Vergiss nicht das <b>Titelbild</b> auszuwählen!</p>
<!-- Cropper Container --> <!-- Die 4 Bilder Slots -->
<div id="cropper_container" style="display:none; margin-bottom: 15px; max-height: 400px; overflow: hidden; border: 1px dashed #ccc;"> <div class="img-grid">
<img id="image_to_crop" src="" style="max-width: 100%; display: block;"> <?php
for($i=1; $i<=4; $i++) {
renderImageSlot($i, $is_edit, $edit_news);
}
?>
</div> </div>
<input type="hidden" name="cropped_image_data" id="cropped_image_data">
<?php if($is_edit && $edit_news['image']): ?> <label style="margin-top: 20px;">Weiterführender Link (Optional)</label>
<p style="font-size: 0.85rem; color: #666; margin-top: -10px; margin-bottom: 10px;">Aktuelles Bild: <b><?= htmlspecialchars($edit_news['image']) ?></b></p>
<?php endif; ?>
<label>ODER Bild-Link (URL)</label>
<input type="text" name="image_url" placeholder="https://..." value="<?= $is_edit ? htmlspecialchars($edit_news['image']) : '' ?>">
<label>ODER Platzhalter-Bild verwenden</label>
<select name="placeholder_image" style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-family: inherit; margin-bottom: 15px; background: white;">
<option value="">-- Kein Platzhalter (Leeres Bild) --</option>
<option value="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Crect width='100' height='100' fill='%23f4f4f4'/%3E%3Ccircle cx='50' cy='50' r='35' fill='%23d32f2f'/%3E%3Ctext x='50' y='57' font-size='20' fill='white' text-anchor='middle' font-weight='bold'%3ESG 07%3C/text%3E%3C/svg%3E">Neutral (SG 07 Logo)</option>
<option value="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Crect width='100' height='100' fill='%23e3f2fd'/%3E%3Ccircle cx='50' cy='50' r='35' fill='%232196f3'/%3E%3Ctext x='50' y='60' font-size='30' fill='white' text-anchor='middle' font-weight='bold' font-family='serif'%3Ei%3C/text%3E%3C/svg%3E">Info (Blaues i)</option>
<option value="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Crect width='100' height='100' fill='%23fff3e0'/%3E%3Cpath d='M50 20 L85 80 L15 80 Z' fill='%23ff9800'/%3E%3Ctext x='50' y='70' font-size='30' fill='white' text-anchor='middle' font-weight='bold'%3E!%3C/text%3E%3C/svg%3E">Wichtig (Warnung)</option>
</select>
<label>Weiterführender Link (Optional)</label>
<input type="text" name="link" value="<?= $is_edit ? htmlspecialchars($edit_news['link']) : '' ?>"> <input type="text" name="link" value="<?= $is_edit ? htmlspecialchars($edit_news['link']) : '' ?>">
<?php if($is_edit): ?> <?php if($is_edit): ?>
@@ -366,23 +360,20 @@ $existing_news = $db->query("SELECT id, date, department, title FROM news ORDER
</form> </form>
</div> </div>
<!-- Benutzerverwaltung --> <!-- Randbereich Info -->
<div> <div>
<div class="sidebar-box"> <div style="background: #fafafa; padding: 20px; border-radius: 8px; border: 1px solid #ddd;">
<h2 style="color: #333; margin-top: 0;">Neuen Admin anlegen</h2> <h2 style="color: #333; margin-top: 0;">Tipps zur Galerie</h2>
<p style="font-size: 0.9rem; color: #666;">Lege einen Kollegen an. Ein QR-Code für dessen 2FA-App wird direkt danach <b>einmalig</b> angezeigt.</p> <p style="font-size: 0.9rem; color: #666;">
<form method="POST"> Das <b>Titelbild</b> wird immer auf der Startseite in der kleinen Karte angezeigt.<br><br>
<label>Benutzername</label> Wenn jemand auf "Weiterlesen" klickt, werden <b>alle</b> Bilder, die du hier hinterlegst, im Artikel angezeigt.<br><br>
<input type="text" name="new_username" required> <b>Tipp:</b> Bilder werden nun automatisch vom System im perfekten Format zugeschnitten (CSS object-fit). Du sparst dir das nervige manuelle Zurechtschneiden!
<label>Passwort</label> </p>
<input type="password" name="new_password" required>
<button type="submit" name="add_user" style="background: #333; padding: 10px;">Admin hinzufügen</button>
</form>
</div> </div>
</div> </div>
</div> </div>
<!-- News Liste zum Löschen / Posten / Ändern --> <!-- News Liste -->
<div style="margin-top: 50px; border-top: 2px solid #eee; padding-top: 30px;"> <div style="margin-top: 50px; border-top: 2px solid #eee; padding-top: 30px;">
<h2 style="color: #333; margin-bottom: 10px;">Bestehende News verwalten</h2> <h2 style="color: #333; margin-bottom: 10px;">Bestehende News verwalten</h2>
@@ -401,67 +392,29 @@ $existing_news = $db->query("SELECT id, date, department, title FROM news ORDER
<td style="text-align: right; display: flex; justify-content: flex-end; gap: 5px;"> <td style="text-align: right; display: flex; justify-content: flex-end; gap: 5px;">
<button type="button" class="social-btn" onclick="copySocialMediaText('<?= htmlspecialchars(addslashes($news_item['title'])) ?>', '<?= htmlspecialchars(addslashes($news_item['department'])) ?>')">📲 Posten</button> <button type="button" class="social-btn" onclick="copySocialMediaText('<?= htmlspecialchars(addslashes($news_item['title'])) ?>', '<?= htmlspecialchars(addslashes($news_item['department'])) ?>')">📲 Posten</button>
<a href="?edit_id=<?= $news_item['id'] ?>" class="edit-btn">Ändern</a> <a href="?edit_id=<?= $news_item['id'] ?>" class="edit-btn">Ändern</a>
<form method="POST" style="margin: 0;" onsubmit="return confirm('Möchtest du diese News wirklich unwiderruflich löschen?');"> <form method="POST" style="margin: 0;" onsubmit="return confirm('Möchtest du diese News wirklich löschen?');">
<input type="hidden" name="delete_id" value="<?= $news_item['id'] ?>"> <input type="hidden" name="delete_id" value="<?= $news_item['id'] ?>">
<button type="submit" name="delete_news" class="delete-btn">Löschen</button> <button type="submit" name="delete_news" class="delete-btn">Löschen</button>
</form> </form>
</td> </td>
</tr> </tr>
<?php endforeach; ?> <?php endforeach; ?>
<?php if(empty($existing_news)): ?>
<tr>
<td colspan="4" style="text-align: center; padding: 20px; color: #888;">Noch keine News vorhanden.</td>
</tr>
<?php endif; ?>
</table> </table>
</div> </div>
</div> </div>
<!-- Javascript für Crop und Social Media --> <!-- Javascript -->
<script> <script>
// CROPPER LOGIK
let cropper;
document.getElementById('image_file_input').addEventListener('change', function(e) {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = function(event) {
document.getElementById('image_to_crop').src = event.target.result;
document.getElementById('cropper_container').style.display = 'block';
if (cropper) cropper.destroy();
cropper = new Cropper(document.getElementById('image_to_crop'), {
aspectRatio: 4 / 3,
viewMode: 1,
autoCropArea: 1
});
};
reader.readAsDataURL(file);
}
});
document.getElementById('newsForm').addEventListener('submit', function(e) {
if (cropper) {
const canvas = cropper.getCroppedCanvas({ width: 800, height: 600 });
if(canvas) {
document.getElementById('cropped_image_data').value = canvas.toDataURL('image/jpeg', 0.85);
}
}
});
// SOCIAL MEDIA EXPORT LOGIK
function copySocialMediaText(title, department) { function copySocialMediaText(title, department) {
const cleanDept = department.replace(/\s+/g, ''); const cleanDept = department.replace(/\s+/g, '');
const text = `🚨 NEUE NEWS VON DER SG 07 🚨\n\n📢 ${title}\n\nLies jetzt den ganzen Bericht auf unserer Website!\n👉 sg07-stleon.de\n\n#SG07StLeon #Vereinsliebe #${cleanDept} #StLeonRot`; const text = `🚨 NEUE NEWS VON DER SG 07 🚨\n\n📢 ${title}\n\nLies jetzt den ganzen Bericht auf unserer Website!\n👉 sg07-stleon.de\n\n#SG07StLeon #Vereinsliebe #${cleanDept} #StLeonRot`;
const tempInput = document.createElement("textarea"); const tempInput = document.createElement("textarea");
tempInput.value = text; tempInput.value = text;
document.body.appendChild(tempInput); document.body.appendChild(tempInput);
tempInput.select(); tempInput.select();
document.execCommand("copy"); document.execCommand("copy");
document.body.removeChild(tempInput); document.body.removeChild(tempInput);
alert("✅ Der Text für Instagram & Facebook wurde kopiert!");
alert("✅ Der Text für Instagram & Facebook wurde in deine Zwischenablage kopiert!\n\nDu kannst jetzt einfach rüber zu Insta gehen, dein Bild einfügen und beim Text auf 'Einsetzen' drücken.");
} }
</script> </script>
</body> </body>