468 lines
26 KiB
PHP
468 lines
26 KiB
PHP
<?php
|
|
// admin.php - Multi-User Login, Google Authenticator 2FA, Cropper, News Verwaltung & AES-256 VERSCHLÜSSELUNG
|
|
session_start();
|
|
|
|
// Fehleranzeige (für Live-Betrieb auf 0 setzen - oder anlassen, wenn du Schmerzen magst)
|
|
ini_set('display_errors', 1);
|
|
ini_set('display_startup_errors', 1);
|
|
error_reporting(E_ALL);
|
|
|
|
// ============================================================================
|
|
// 1. MINI-KLASSE FÜR GOOGLE AUTHENTICATOR (TOTP)
|
|
// ============================================================================
|
|
class MiniTOTP {
|
|
public static function verify($secret, $code) {
|
|
$time = floor(time() / 30);
|
|
// Prüft aktuelles, vorheriges und nächstes Zeitfenster (Toleranz)
|
|
for ($i = -1; $i <= 1; $i++) {
|
|
if (self::getCode($secret, $time + $i) === $code) return true;
|
|
}
|
|
return false;
|
|
}
|
|
public static function getCode($secret, $time) {
|
|
$secret = self::base32Decode($secret);
|
|
$time = pack('N', 0) . pack('N', $time);
|
|
$hash = hash_hmac('sha1', $time, $secret, true);
|
|
$offset = ord(substr($hash, -1)) & 0x0F;
|
|
$code = (unpack('N', substr($hash, $offset, 4))[1] & 0x7FFFFFFF) % 1000000;
|
|
return str_pad($code, 6, '0', STR_PAD_LEFT);
|
|
}
|
|
public static function base32Decode($b32) {
|
|
$b32 = strtoupper($b32);
|
|
$map = ['A'=>0,'B'=>1,'C'=>2,'D'=>3,'E'=>4,'F'=>5,'G'=>6,'H'=>7,'I'=>8,'J'=>9,'K'=>10,'L'=>11,'M'=>12,'N'=>13,'O'=>14,'P'=>15,'Q'=>16,'R'=>17,'S'=>18,'T'=>19,'U'=>20,'V'=>21,'W'=>22,'X'=>23,'Y'=>24,'Z'=>25,'2'=>26,'3'=>27,'4'=>28,'5'=>29,'6'=>30,'7'=>31];
|
|
$decoded = ''; $buffer = 0; $bits = 0;
|
|
for ($i = 0; $i < strlen($b32); $i++) {
|
|
$char = $b32[$i];
|
|
if (!isset($map[$char])) continue;
|
|
$buffer = ($buffer << 5) | $map[$char];
|
|
$bits += 5;
|
|
if ($bits >= 8) { $bits -= 8; $decoded .= chr(($buffer >> $bits) & 0xFF); }
|
|
}
|
|
return $decoded;
|
|
}
|
|
public static function generateSecret($length = 16) {
|
|
$chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
|
|
$secret = '';
|
|
for ($i = 0; $i < $length; $i++) $secret .= $chars[random_int(0, 31)];
|
|
return $secret;
|
|
}
|
|
public static function getQRCodeUrl($username, $secret) {
|
|
$issuer = urlencode("SG 07 St.Leon");
|
|
$account = urlencode($username);
|
|
$otpauth = "otpauth://totp/{$issuer}:{$account}?secret={$secret}&issuer={$issuer}";
|
|
return "https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=" . urlencode($otpauth);
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// 2. DATENBANK SETUP (News & Benutzer)
|
|
// ============================================================================
|
|
try {
|
|
$db = new PDO('sqlite:news_database.sqlite');
|
|
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
|
|
|
// News Tabelle
|
|
$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
|
|
)");
|
|
try { $db->exec("ALTER TABLE news ADD COLUMN full_text TEXT"); } catch(PDOException $e) {}
|
|
|
|
// Benutzer Tabelle
|
|
$db->exec("CREATE TABLE IF NOT EXISTS users (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT UNIQUE, password_hash TEXT, totp_secret TEXT
|
|
)");
|
|
} catch(PDOException $e) {
|
|
die("Datenbankfehler: " . $e->getMessage());
|
|
}
|
|
|
|
$userCount = $db->query("SELECT COUNT(*) FROM users")->fetchColumn();
|
|
|
|
// ============================================================================
|
|
// 3. ERSTEINRICHTUNG (Wenn keine Nutzer existieren)
|
|
// ============================================================================
|
|
if ($userCount == 0) {
|
|
$setup_msg = "";
|
|
$new_secret = MiniTOTP::generateSecret();
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['do_setup'])) {
|
|
$user = trim($_POST['username']);
|
|
$pass = $_POST['password'];
|
|
$secret = $_POST['totp_secret'];
|
|
|
|
if (!empty($user) && !empty($pass)) {
|
|
$stmt = $db->prepare("INSERT INTO users (username, password_hash, totp_secret) VALUES (?, ?, ?)");
|
|
$stmt->execute([$user, password_hash($pass, PASSWORD_DEFAULT), $secret]);
|
|
header("Location: admin.php");
|
|
exit;
|
|
}
|
|
}
|
|
?>
|
|
<!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>
|
|
<body><div class="box"><h1 style="color:#d32f2f;">Ersteinrichtung</h1><p>Erstelle den ersten Admin-Zugang.</p>
|
|
<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>
|
|
<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>
|
|
<?php exit;
|
|
}
|
|
|
|
// ============================================================================
|
|
// 4. LOGIN LOGIK
|
|
// ============================================================================
|
|
if (isset($_GET['action']) && $_GET['action'] == 'logout') {
|
|
session_destroy(); header("Location: admin.php"); exit;
|
|
}
|
|
|
|
$login_error = '';
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['do_login'])) {
|
|
$stmt = $db->prepare("SELECT * FROM users WHERE username = ?");
|
|
$stmt->execute([trim($_POST['username'])]);
|
|
$user = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
|
|
if ($user && password_verify($_POST['password'], $user['password_hash'])) {
|
|
if (MiniTOTP::verify($user['totp_secret'], $_POST['totp_code'])) {
|
|
$_SESSION['logged_in'] = true;
|
|
$_SESSION['username'] = $user['username'];
|
|
header("Location: admin.php"); exit;
|
|
} else {
|
|
$login_error = "Der 2FA Code ist falsch oder abgelaufen.";
|
|
}
|
|
} else {
|
|
$login_error = "Benutzername oder Passwort falsch.";
|
|
}
|
|
}
|
|
|
|
if (!isset($_SESSION['logged_in']) || $_SESSION['logged_in'] !== true) {
|
|
?>
|
|
<!DOCTYPE html><html lang="de"><head><meta charset="UTF-8"><title>Login | SG 07</title>
|
|
<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>
|
|
<?php if($login_error) echo "<div class='err'>$login_error</div>"; ?>
|
|
<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>
|
|
<?php exit;
|
|
}
|
|
|
|
// ============================================================================
|
|
// 5. ADMIN BEREICH (Eingeloggt)
|
|
// ============================================================================
|
|
$message = "";
|
|
|
|
// --- BILD-UPLOAD FUNKTION (mit Base64 Crop-Unterstützung) ---
|
|
function handleImageUpload() {
|
|
if (!empty($_POST['cropped_image_data'])) {
|
|
$data = $_POST['cropped_image_data'];
|
|
list($type, $data) = explode(';', $data);
|
|
list(, $data) = explode(',', $data);
|
|
$data = base64_decode($data);
|
|
|
|
$year = date('Y');
|
|
$upload_dir = __DIR__ . '/img/news/' . $year;
|
|
if (!is_dir($upload_dir)) {
|
|
mkdir($upload_dir, 0777, true); // Erstellt den Ordner, wenn er fehlt
|
|
}
|
|
|
|
$filename = date('Ymd') . '_' . rand(10000, 99999) . '.jpg';
|
|
$target_file = $upload_dir . '/' . $filename;
|
|
|
|
if (file_put_contents($target_file, $data)) {
|
|
return 'img/news/' . $year . '/' . $filename;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// --- AKTION: NEWS AKTUALISIEREN (EDIT) ---
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['update_news'])) {
|
|
$final_image_path = $_POST['existing_image'];
|
|
|
|
$uploaded_img = handleImageUpload();
|
|
if ($uploaded_img) {
|
|
$final_image_path = $uploaded_img;
|
|
} 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) ---
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['save_news'])) {
|
|
$final_image_path = '';
|
|
|
|
$uploaded_img = handleImageUpload();
|
|
if ($uploaded_img) {
|
|
$final_image_path = $uploaded_img;
|
|
} 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>";
|
|
}
|
|
}
|
|
|
|
// --- AKTION: NEWS LÖSCHEN ---
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['delete_news'])) {
|
|
$stmt = $db->prepare("DELETE FROM news WHERE id = ?");
|
|
if ($stmt->execute([$_POST['delete_id']])) {
|
|
$message = "<div class='success'>✓ Die News wurde unwiderruflich gelöscht!</div>";
|
|
}
|
|
}
|
|
|
|
// --- 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
|
|
$is_edit = false;
|
|
$edit_news = null;
|
|
if (isset($_GET['edit_id'])) {
|
|
$stmt = $db->prepare("SELECT * FROM news WHERE id = ?");
|
|
$stmt->execute([$_GET['edit_id']]);
|
|
$edit_news = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
if ($edit_news) {
|
|
$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);
|
|
?>
|
|
<!DOCTYPE html>
|
|
<html lang="de">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<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>
|
|
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; }
|
|
.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; }
|
|
.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; }
|
|
label { font-weight: 600; display: block; margin-top: 15px; margin-bottom: 5px; color: #333;}
|
|
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:hover { background: #9a0007; }
|
|
.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; }
|
|
.qr-box { background: #fff3cd; color: #856404; padding: 20px; border-radius: 8px; text-align: center; margin-bottom: 20px; border: 1px solid #ffeeba;}
|
|
|
|
.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 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; }
|
|
.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; }
|
|
|
|
@media(max-width: 768px) { .grid-2 { grid-template-columns: 1fr; } }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<div class="header-bar">
|
|
<h1>SG 07 Dashboard</h1>
|
|
<div>
|
|
<span style="margin-right:15px; color:#666;">Hallo, <b><?= htmlspecialchars($_SESSION['username']) ?></b></span>
|
|
<a href="?action=logout" class="logout-btn">Abmelden</a>
|
|
</div>
|
|
</div>
|
|
|
|
<?= $message ?>
|
|
<?= $new_user_qr ?>
|
|
|
|
<div class="grid-2">
|
|
<!-- News Eintragen / Ändern -->
|
|
<div>
|
|
<h2 style="color: #333; margin-bottom: 10px;"><?= $is_edit ? 'News bearbeiten' : 'News veröffentlichen' ?></h2>
|
|
<form method="POST" id="newsForm" enctype="multipart/form-data">
|
|
<?php if($is_edit): ?>
|
|
<input type="hidden" name="news_id" value="<?= $edit_news['id'] ?>">
|
|
<input type="hidden" name="existing_image" value="<?= htmlspecialchars($edit_news['image']) ?>">
|
|
<?php endif; ?>
|
|
|
|
<label>Datum</label>
|
|
<input type="text" name="date" value="<?= $is_edit ? htmlspecialchars($edit_news['date']) : date('d.m.Y') ?>" required>
|
|
<label>Abteilung</label>
|
|
<input type="text" name="department" placeholder="z.B. Turnen" value="<?= $is_edit ? htmlspecialchars($edit_news['department']) : '' ?>" required>
|
|
<label>Überschrift</label>
|
|
<input type="text" name="title" value="<?= $is_edit ? htmlspecialchars($edit_news['title']) : '' ?>" required>
|
|
<label>Kurze Einleitung (Vorschau)</label>
|
|
<textarea name="content" rows="3" required><?= $is_edit ? htmlspecialchars($edit_news['content']) : '' ?></textarea>
|
|
<label>Kompletter Artikel (Popup)</label>
|
|
<textarea name="full_text" rows="8" required><?= $is_edit ? htmlspecialchars($edit_news['full_text']) : '' ?></textarea>
|
|
|
|
<label>Bild hochladen & zuschneiden</label>
|
|
<input type="file" id="image_file_input" accept="image/*" style="background: white; margin-bottom: 10px;">
|
|
|
|
<!-- Cropper Container -->
|
|
<div id="cropper_container" style="display:none; margin-bottom: 15px; max-height: 400px; overflow: hidden; border: 1px dashed #ccc;">
|
|
<img id="image_to_crop" src="" style="max-width: 100%; display: block;">
|
|
</div>
|
|
<input type="hidden" name="cropped_image_data" id="cropped_image_data">
|
|
|
|
<?php if($is_edit && $edit_news['image']): ?>
|
|
<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']) : '' ?>">
|
|
|
|
<?php if($is_edit): ?>
|
|
<div style="display: flex; gap: 10px;">
|
|
<button type="submit" name="update_news" style="background: #2196F3;">Änderungen speichern</button>
|
|
<a href="admin.php" style="display: block; text-align: center; margin-top: 20px; padding: 15px; background: #555; color: white; border-radius: 4px; text-decoration: none; font-weight: bold; width: 100%; box-sizing: border-box;">Abbrechen</a>
|
|
</div>
|
|
<?php else: ?>
|
|
<button type="submit" name="save_news">News online stellen</button>
|
|
<?php endif; ?>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- Benutzerverwaltung -->
|
|
<div>
|
|
<div class="sidebar-box">
|
|
<h2 style="color: #333; margin-top: 0;">Neuen Admin anlegen</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>
|
|
<form method="POST">
|
|
<label>Benutzername</label>
|
|
<input type="text" name="new_username" required>
|
|
<label>Passwort</label>
|
|
<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>
|
|
|
|
<!-- News Liste zum Löschen / Posten / Ändern -->
|
|
<div style="margin-top: 50px; border-top: 2px solid #eee; padding-top: 30px;">
|
|
<h2 style="color: #333; margin-bottom: 10px;">Bestehende News verwalten</h2>
|
|
|
|
<table class="news-table">
|
|
<tr>
|
|
<th>Datum</th>
|
|
<th>Abteilung</th>
|
|
<th>Titel</th>
|
|
<th style="width: 250px; text-align: right;">Aktion</th>
|
|
</tr>
|
|
<?php foreach($existing_news as $news_item): ?>
|
|
<tr>
|
|
<td><?= htmlspecialchars($news_item['date']) ?></td>
|
|
<td><span style="background: #eee; padding: 2px 8px; border-radius: 4px; font-size: 0.85rem;"><?= htmlspecialchars($news_item['department']) ?></span></td>
|
|
<td><strong><?= htmlspecialchars($news_item['title']) ?></strong></td>
|
|
<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>
|
|
<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?');">
|
|
<input type="hidden" name="delete_id" value="<?= $news_item['id'] ?>">
|
|
<button type="submit" name="delete_news" class="delete-btn">Löschen</button>
|
|
</form>
|
|
</td>
|
|
</tr>
|
|
<?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>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Javascript für Crop und Social Media -->
|
|
<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) {
|
|
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 tempInput = document.createElement("textarea");
|
|
tempInput.value = text;
|
|
document.body.appendChild(tempInput);
|
|
tempInput.select();
|
|
document.execCommand("copy");
|
|
document.body.removeChild(tempInput);
|
|
|
|
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>
|
|
</body>
|
|
</html>
|