statistiken.php – Admin-Bereich
Diese Seite ist nur für Admins zugänglich und macht drei Dinge gleichzeitig: sie zeigt Statistiken, sie erlaubt Produkte anzulegen, und sie erlaubt bestehende Produkte freizugeben, zu sperren oder zu löschen. Der Reihe nach.
Der komplette Code
<?php
require_once('db.php');
session_start();
// admin-check
if (!isset($_SESSION['user_id']) || ($_SESSION['rolle'] ?? '') !== 'admin') {
header('Location: index.php');
exit;
}
$fehler = '';
$erfolg = '';
// ===== POST-HANDLER =====
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// FREIGABE WECHSELN
if (isset($_POST['toggle_id'])) {
$id = (int)$_POST['toggle_id'];
$stmt = $conn->prepare("UPDATE products SET freigeben = 1 - freigeben WHERE id = ?");
$stmt->execute([$id]);
}
// LÖSCHEN
elseif (isset($_POST['delete_id'])) {
$id = (int)$_POST['delete_id'];
try {
$stmt = $conn->prepare("DELETE FROM products WHERE id = ?");
$stmt->execute([$id]);
} catch (PDOException $e) {
// fk-constraint: produkt schon in order_items
$fehler = 'Produkt kann nicht gelöscht werden (bereits bestellt). Sperre es stattdessen.';
}
}
// HINZUFÜGEN
elseif (isset($_POST['add_name'])) {
$name = trim($_POST['add_name'] ?? '');
$artikelcode = trim($_POST['add_artikelcode'] ?? '');
$price = (float)str_replace(',', '.', $_POST['add_price'] ?? '0');
$category_id = (int)($_POST['add_category'] ?? 0);
$description = trim($_POST['add_description'] ?? '');
$picture = trim($_POST['add_picture'] ?? '');
// validierung
if ($name === '' || $artikelcode === '') {
$fehler = 'Name und Artikelcode sind pflicht.';
} elseif ($price <= 0) {
$fehler = 'Preis muss größer als 0 sein.';
} elseif ($category_id <= 0) {
$fehler = 'Kategorie auswählen.';
} else {
// artikelcode-unique-check (zertifizierung-anforderung!)
$stmt = $conn->prepare("SELECT id FROM products WHERE artikelcode = ?");
$stmt->execute([$artikelcode]);
if ($stmt->fetch()) {
$fehler = 'Artikelcode existiert bereits.';
} else {
$stmt = $conn->prepare(
"INSERT INTO products
(name, artikelcode, price, category_id, description, picture, freigeben)
VALUES (?, ?, ?, ?, ?, ?, 0)"
);
$stmt->execute([$name, $artikelcode, $price, $category_id, $description, $picture]);
$erfolg = 'Produkt angelegt (noch gesperrt, muss freigegeben werden).';
}
}
}
}
// ===== DATEN LADEN =====
$anzahl_user = $conn->query("SELECT COUNT(*) FROM users")->fetchColumn();
$anzahl_produkte = $conn->query("SELECT COUNT(*) FROM products")->fetchColumn();
$anzahl_bestellungen = $conn->query("SELECT COUNT(*) FROM orders")->fetchColumn();
$umsatz = $conn->query("SELECT COALESCE(SUM(menge * preis), 0) FROM order_items")->fetchColumn();
$kategorien = $conn->query("SELECT id, name FROM categories ORDER BY name")->fetchAll(PDO::FETCH_ASSOC);
$alle_produkte = $conn->query(
"SELECT p.id, p.name, p.artikelcode, p.price, p.freigeben, c.name AS kategorie_name
FROM products p
LEFT JOIN categories c ON c.id = p.category_id
ORDER BY p.id DESC"
)->fetchAll(PDO::FETCH_ASSOC);
?>
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Statistiken & Admin</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<?php include('navbar.php'); ?>
<h1>Statistiken</h1>
<p>Registrierte User: <strong><?= (int)$anzahl_user ?></strong></p>
<p>Produkte: <strong><?= (int)$anzahl_produkte ?></strong></p>
<p>Bestellungen: <strong><?= (int)$anzahl_bestellungen ?></strong></p>
<p>Umsatz: <strong><?= number_format($umsatz, 2, ',', '.') ?> €</strong></p>
<hr>
<h1>Produktverwaltung</h1>
<?php if ($fehler !== ''): ?>
<p style="color:red;"><?= htmlspecialchars($fehler, ENT_QUOTES, 'UTF-8') ?></p>
<?php endif; ?>
<?php if ($erfolg !== ''): ?>
<p style="color:green;"><?= htmlspecialchars($erfolg, ENT_QUOTES, 'UTF-8') ?></p>
<?php endif; ?>
<h2>Neues Produkt anlegen</h2>
<form action="statistiken.php" method="POST">
<label>Name:<br><input type="text" name="add_name" required></label><br><br>
<label>Artikelcode:<br><input type="text" name="add_artikelcode" required></label><br><br>
<label>Preis (€):<br><input type="number" name="add_price" step="0.01" min="0.01" required></label><br><br>
<label>Kategorie:<br>
<select name="add_category" required>
<option value="">-- bitte wählen --</option>
<?php foreach ($kategorien as $kat): ?>
<option value="<?= (int)$kat['id'] ?>">
<?= htmlspecialchars($kat['name'], ENT_QUOTES, 'UTF-8') ?>
</option>
<?php endforeach; ?>
</select>
</label><br><br>
<label>Beschreibung:<br><textarea name="add_description" rows="3" cols="40"></textarea></label><br><br>
<label>Bildpfad (z.B. bilder/xyz.jpg):<br><input type="text" name="add_picture"></label><br><br>
<button type="submit">Produkt anlegen</button>
</form>
<h2>Alle Produkte</h2>
<table border="1" cellpadding="6">
<tr>
<th>ID</th><th>Artikelcode</th><th>Name</th><th>Kategorie</th>
<th>Preis</th><th>Status</th><th>Aktionen</th>
</tr>
<?php foreach ($alle_produkte as $p): ?>
<tr>
<td><?= (int)$p['id'] ?></td>
<td><?= htmlspecialchars($p['artikelcode'], ENT_QUOTES, 'UTF-8') ?></td>
<td><?= htmlspecialchars($p['name'], ENT_QUOTES, 'UTF-8') ?></td>
<td><?= htmlspecialchars($p['kategorie_name'] ?? '-', ENT_QUOTES, 'UTF-8') ?></td>
<td><?= number_format($p['price'], 2, ',', '.') ?> €</td>
<td>
<?= $p['freigeben'] ? '<span style="color:green;">freigegeben</span>'
: '<span style="color:red;">gesperrt</span>' ?>
</td>
<td>
<form action="statistiken.php" method="POST" style="display:inline;">
<input type="hidden" name="toggle_id" value="<?= (int)$p['id'] ?>">
<button type="submit">
<?= $p['freigeben'] ? 'Sperren' : 'Freigeben' ?>
</button>
</form>
<form action="statistiken.php" method="POST" style="display:inline;"
onsubmit="return confirm('Wirklich löschen?');">
<input type="hidden" name="delete_id" value="<?= (int)$p['id'] ?>">
<button type="submit">Löschen</button>
</form>
</td>
</tr>
<?php endforeach; ?>
</table>
</body>
</html>
Wichtige Konzepte in diesem File
1. Admin-Check gleich am Anfang
if (!isset($_SESSION['user_id']) || ($_SESSION['rolle'] ?? '') !== 'admin') {
header('Location: index.php');
exit;
}
Wer nicht eingeloggt ist ODER nicht die Rolle "admin" hat → sofort raus, noch bevor irgendwas anderes passiert. Kein Rendering, kein DB-Zugriff für Unbefugte. Das ist die eigentliche Sicherheit – das Verstecken in der navbar ist nur Deko.
2. Ein File, drei Aktionen – unterschieden durch hidden inputs
Es gibt keinen ?action=delete-Parameter. Stattdessen unterscheiden die Formulare durch welches hidden field mitgeschickt wird:
toggle_id→ Freigabe wechselndelete_id→ löschenadd_name→ neues Produkt anlegen
Das elseif-Konstrukt stellt sicher, dass immer nur eine Aktion greift.
3. Toggle-Trick: freigeben = 1 - freigeben
UPDATE products SET freigeben = 1 - freigeben WHERE id = ?
Aus 1 wird 0 (1 - 1 = 0), aus 0 wird 1 (1 - 0 = 1). Ein einziges Statement für beide Richtungen, kein IF drumherum. Klein aber elegant.
4. Artikelcode-Unique-Check
Doppelt abgesichert: die DB hat UNIQUE auf der Spalte, und PHP prüft davor mit SELECT. Die DB würde einen Fehler werfen – aber der Check davor liefert eine schöne Fehlermeldung statt Exception. Das ist die ausdrückliche Anforderung aus der zertifizierung-Angabe: "es darf nicht möglich sein, dass mehrere Produkte denselben Artikelcode haben".
5. Löschen wirft FK-Fehler wenn Produkt schon bestellt
order_items steckt, kann nicht gelöscht werden – der Foreign Key verhindert es. Genau deshalb steht der DELETE in einem try/catch. Fällt die Query auf die Nase, fängt der Catch das ab und schlägt vor: Produkt sperren statt löschen (freigeben = 0 setzen). Das ist auch in echten Shops so – historische Bestellungen sollen ihre Produkte behalten, sonst wird die Rechnung unlesbar.6. Zwei separate forms statt einem mit mehreren buttons
Jede Aktion (Toggle, Löschen) ist ein eigenes <form>, damit das onsubmit="return confirm(...)" beim Löschen nur beim Löschen greift und nicht auch beim Toggeln nachfragt.
7. Neue Produkte starten immer als "gesperrt"
Der INSERT setzt hart freigeben = 0. Der Admin muss aktiv freigeben – so kann kein halbfertiger Datensatz versehentlich im Shop landen.