Geizkragen/productpage.php

658 lines
26 KiB
PHP

<?php
/**
* @file productpage.php
* @brief Darstellung der Detailseite eines spezifischen Produkts.
*
* @details Diese Datei generiert die Produktdetailseite. Sie umfasst das Laden der Produktdaten
* anhand der übergebenen ID, Behandeln von POST-Anfragen (Bewertung löschen, Wunschliste bearbeiten,
* Vergleiche verwalten) und die Darstellung der zugehörigen Shops, Attribute sowie Bewertungen.
*
* @author System
* @version 1.0
*/
// productpage.php
require_once __DIR__ . '/lib/bootstrap.php';
/**
* @brief Initiiert die Datenbankverbindung.
* @details Stellt eine globale Verbindung zur Datenbank her, um die nachfolgenden Queries ausführen zu können.
*/
// 1) DB-Verbindung (einmal)
$conn = db_connect();
/**
* @brief Holt die Produkt-ID aus dem GET-Parameter.
* @details Validierung der ID, Rückfall auf 0, wenn kein valider Wert übergeben wurde.
*/
$productId = isset($_GET['id']) ? (int)$_GET['id'] : 0;
if ($productId <= 0) {
include __DIR__ . '/404.php';
exit;
}
/**
* @brief Überprüft, ob das Produkt in der Datenbank existiert.
* @details Falls kein Datensatz für die gegebene productID gefunden wird, wird der Nutzer auf eine 404-Seite umgeleitet.
*/
$checkStmt = $conn->prepare("SELECT productID FROM products WHERE productID = ?");
$checkStmt->bind_param("i", $productId);
$checkStmt->execute();
$checkResult = $checkStmt->get_result();
if ($checkResult->num_rows === 0) {
include __DIR__ . '/404.php';
exit;
}
/**
* @brief Behandelt das Löschen von Bewertungen.
* @details Administrator-Nutzer können Bewertungen über einen POST-Request löschen.
* Überprüft die Nutzerrolle in der Session und führt das DELETE-Statement aus.
*/
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['delete_review']) && isset($_POST['delete_review_id'])) {
if (!empty($_SESSION['user_roles']) && in_array('ADMIN', $_SESSION['user_roles'], true)) {
$deleteId = (int)$_POST['delete_review_id'];
$delStmt = $conn->prepare("DELETE FROM reviews WHERE reviewID = ?");
$delStmt->bind_param("i", $deleteId);
$delStmt->execute();
$delStmt->close();
echo "<script>window.location.href = 'productpage.php?id=" . $productId . "';</script>";
exit;
}
}
?>
<?php include 'header.php'; ?>
<?php
/**
* @brief Holt die detaillierten Produktdaten und -attribute.
* @details Führt einen JOIN über products, categoryAttributes, attributes und productAttributes aus,
* um alle Eigenschaften für die Anzeige abzurufen.
*/
$stmt = $conn->prepare("
SELECT
a.name,
a.unit,
a.dataType,
pa.valueString,
pa.valueNumber,
pa.valueBool,
p.model,
p.description,
p.imagePath,
p.categoryID
FROM products p
INNER JOIN categoryAttributes ca
ON p.categoryID = ca.categoryID
INNER JOIN attributes a
ON ca.attributeID = a.attributeID
LEFT JOIN productAttributes pa
ON pa.productID = p.productID
AND pa.attributeID = a.attributeID
WHERE p.productID = ?
ORDER BY a.attributeID
");
$stmt->bind_param("i", $productId);
$stmt->execute();
$result = $stmt->get_result();
$product = $result->fetch_assoc();
$categoryId = $product['categoryID'];
/**
* @brief Initialer Status für die Wunschliste.
*/
$alreadyInWishlist = false;
/**
* @brief Prüft, ob sich das Produkt bereits auf der Wunschliste des angemeldeten Nutzers befindet.
* @details Falls der Nutzer angemeldet ist (`user_id` ist gesetzt), wird in `userFavorites` gesucht.
*/
if (isset($_SESSION['user_id'])) {
$stmtCheck = mysqli_prepare(
$conn,
"SELECT 1 FROM userFavorites
WHERE userID = ? AND productID = ?
LIMIT 1"
);
if ($stmtCheck) {
mysqli_stmt_bind_param(
$stmtCheck,
"ii",
$_SESSION['user_id'],
$productId
);
mysqli_stmt_execute($stmtCheck);
mysqli_stmt_store_result($stmtCheck);
if (mysqli_stmt_num_rows($stmtCheck) > 0) {
$alreadyInWishlist = true;
}
mysqli_stmt_close($stmtCheck);
}
}
?>
<?php
/**
* @brief Verarbeitet POST-Requests für die Wunschliste.
* @details Benutzer können das Produkt der Wunschliste hinzufügen oder daraus entfernen.
* Benötigt eine aktive Session mit gültiger `user_id`.
*/
// PRÜFEN: POST-Request und Nutzer ist eingeloggt
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_SESSION['user_id'])) {
// FALL 1: Zur Wunschliste hinzufügen
if (isset($_POST['add_wishlist']) && !$alreadyInWishlist) {
$stmtFav = mysqli_prepare($conn, "INSERT INTO userFavorites (productID, userID) VALUES (?, ?)");
if ($stmtFav) {
mysqli_stmt_bind_param($stmtFav, 'ii', $productId, $_SESSION['user_id']);
mysqli_stmt_execute($stmtFav);
mysqli_stmt_close($stmtFav);
// Status aktualisieren, damit gleich der "Entfernen"-Button erscheint
$alreadyInWishlist = true;
}
}
// FALL 2: Von der Wunschliste entfernen (NEU)
elseif (isset($_POST['remove_wishlist']) && $alreadyInWishlist) {
$stmtDel = mysqli_prepare($conn, "DELETE FROM userFavorites WHERE productID = ? AND userID = ?");
if ($stmtDel) {
mysqli_stmt_bind_param($stmtDel, 'ii', $productId, $_SESSION['user_id']);
mysqli_stmt_execute($stmtDel);
mysqli_stmt_close($stmtDel);
// Status aktualisieren, damit gleich wieder der "Hinzufügen"-Button erscheint
$alreadyInWishlist = false;
}
}
}
/**
* @brief Logik für das Produktvergleichs-Feature.
* @details Speichert Produkte nach `categoryID` gruppiert in der Session-Variable `compare`,
* um später mehrere Produkte vergleichen zu können. POST-Requests updaten dieses Array.
*/
// Vergleichslogik
if (!isset($_SESSION['compare'])) {
$_SESSION['compare'] = [];
}
if (!isset($_SESSION['compare'][$categoryId])) {
$_SESSION['compare'][$categoryId] = [];
}
$alreadyInCompare = in_array($productId, $_SESSION['compare'][$categoryId]);
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (isset($_POST['add_compare']) && !$alreadyInCompare) {
$_SESSION['compare'][$categoryId][] = $productId;
$alreadyInCompare = true;
} elseif (isset($_POST['remove_compare']) && $alreadyInCompare) {
$_SESSION['compare'][$categoryId] = array_diff($_SESSION['compare'][$categoryId], [$productId]);
$alreadyInCompare = false;
}
}
?>
<?php
/**
* @brief Berechnet Bewertungs-Statistiken für dieses Produkt.
* @details Holt den Durchschnitt und zählt, wie viele Bewertungen je Sterne-Kategorie existieren,
* um die Bewertungsübersicht (Balkendiagramm) darzustellen.
*/
// SQL korrigiert: SUM statt COUNT für die bedingten Zählungen
$stmtRevOv = mysqli_prepare($conn,
"SELECT ROUND(AVG(rating), 1) as avgRating,
COUNT(*) as reviewCount,
SUM(rating = 5) as fiveStarCount,
SUM(rating = 4) as fourStarCount,
SUM(rating = 3) as threeStarCount,
SUM(rating = 2) as twoStarCount,
SUM(rating = 1) as oneStarCount
FROM reviews WHERE productID = ?");
$stmtRevOv->bind_param("i", $productId);
$stmtRevOv->execute();
$resultRevOv = $stmtRevOv->get_result();
$reviewOverview = $resultRevOv->fetch_assoc();
// Falls NULL zurückkommt (keine Bewertungen), auf 0 setzen
if ($reviewOverview['reviewCount'] === null) {
$reviewOverview['reviewCount'] = 0;
}
?>
<div class="product-wrapper">
<div class="product-left">
<div class="product-image-box">
<img src="<?= isset($product['imagePath']) ? $product['imagePath'] : 'assets/images/placeholder.png' ?>"
alt="<?= htmlspecialchars($product['model'] ?? 'Produktbild') ?>">
</div>
<?php if (isset($_SESSION['user_id'])): ?>
<?php if ($alreadyInWishlist): ?>
<form method="POST">
<input type="hidden" name="product_id" value="<?= (int)$productId ?>">
<input type="hidden" name="remove_wishlist" value="1">
<div class="auth__actions">
<input class="auth__submit" type="submit" value="Aus Wunschliste entfernen" style="background: #ef4444; border-color: #ef4444; color: white;">
</div>
</form>
<?php else: ?>
<form method="POST">
<input type="hidden" name="product_id" value="<?= (int)$productId ?>">
<input type="hidden" name="add_wishlist" value="1">
<div class="auth__actions">
<input class="auth__submit" type="submit" value="Zur Wunschliste hinzufügen">
</div>
</form>
<?php endif; ?>
<?php else: ?>
<div class="auth__actions">
<a href="login.php">
<input class="auth__submit" type="button" value="Zum Hinzufügen einloggen">
</a>
</div>
<?php endif; ?>
<?php if ($alreadyInCompare): ?>
<form method="POST">
<input type="hidden" name="product_id" value="<?= (int)$productId ?>">
<input type="hidden" name="remove_compare" value="1">
<div class="auth__actions" style="margin-top: 10px;">
<input class="auth__submit" type="submit" value="Aus Vergleich entfernen" style="background: #eab308; border-color: #eab308; color: white;">
</div>
</form>
<?php else: ?>
<form method="POST">
<input type="hidden" name="product_id" value="<?= (int)$productId ?>">
<input type="hidden" name="add_compare" value="1">
<div class="auth__actions" style="margin-top: 10px;">
<input class="auth__submit" type="submit" value="Zum Vergleich hinzufügen" style="background: #3b82f6; border-color: #3b82f6; color: white;">
</div>
</form>
<?php endif; ?>
<div class="review-overview-box">
<?php if ($reviewOverview['reviewCount'] > 0): ?>
<div class="overview-header">
<div class="overview-avg">
<?= htmlspecialchars($reviewOverview['avgRating']) ?> <span class="star filled">★</span>
</div>
<div class="overview-count">
basierend auf <?= htmlspecialchars($reviewOverview['reviewCount']) ?> Bewertungen
</div>
</div>
<div class="overview-breakdown">
<?php
// Ein kleines Array für die saubere Ausgabe mit einer Schleife
$starCounts = [
5 => (int)$reviewOverview['fiveStarCount'],
4 => (int)$reviewOverview['fourStarCount'],
3 => (int)$reviewOverview['threeStarCount'],
2 => (int)$reviewOverview['twoStarCount'],
1 => (int)$reviewOverview['oneStarCount']
];
foreach ($starCounts as $stars => $count):
// Prozentwert für den Balken berechnen
$percent = ($reviewOverview['reviewCount'] > 0) ? round(($count / $reviewOverview['reviewCount']) * 100) : 0;
?>
<div class="breakdown-row">
<div class="breakdown-stars"><?= $stars ?> Sterne</div>
<div class="breakdown-bar-bg">
<div class="breakdown-bar-fill" style="width: <?= $percent ?>%;"></div>
</div>
<div class="breakdown-num"><?= $count ?></div>
</div>
<?php endforeach; ?>
</div>
<?php else: ?>
<div class="overview-empty">
<p>Noch keine Bewertungen vorhanden.</p>
</div>
<?php endif; ?>
</div>
</div>
<div class="product-right">
<h1 class="product-title">
<?= htmlspecialchars($product['model'] ?? 'Produkt') ?>
</h1>
<div class="product-specs">
<div class="product-desc">
<?= htmlspecialchars($product['description']) ?>
</div>
<?php
/**
* @brief Gibt die Produktattribute (Specs) dynamisch aus.
* @details Iteriert durch die vorab geladenen Attribute und formatiert die Ausgabe
* entsprechend den Datentypen (String, Number, Boolean).
*/
while ($row = $result->fetch_assoc()) {
echo "<p><strong>{$row['name']}:</strong> ";
if (!empty($row['valueString'])) echo $row['valueString'];
if (!empty($row['valueNumber'])) echo $row['valueNumber'] . " " . $row['unit'];
if (!is_null($row['valueBool'])) echo $row['valueBool'] ? "Ja" : "Nein";
echo "</p>";
}
?>
</div>
</div>
</div>
<?php
/**
* @brief Holt die anbietenden Shops inkl. Preisen und Links zum Produkt.
* @details Abhängig von der bestehenden Datenbankstruktur ('productURL' vs 'offerURL') wird
* die korrekte Spalte per Fallback ermittelt und abgefragt.
*/
// Unterschiedliche DB-Stände: URL-Spalte heißt je nach Schema z.B. productURL oder offerURL.
// Wir ermitteln die existierende Spalte dynamisch, damit die Seite nicht mit "Unknown column" crasht.
$urlColumn = '';
$colCheck = mysqli_query($conn, "SHOW COLUMNS FROM offers LIKE 'productURL'");
if ($colCheck && mysqli_num_rows($colCheck) > 0) {
$urlColumn = 'productURL';
} else {
$colCheck2 = mysqli_query($conn, "SHOW COLUMNS FROM offers LIKE 'offerURL'");
if ($colCheck2 && mysqli_num_rows($colCheck2) > 0) {
$urlColumn = 'offerURL';
}
}
$urlSelect = $urlColumn !== '' ? ("offers." . $urlColumn . " AS offerURL") : "'' AS offerURL";
$stmt = mysqli_prepare($conn,
"SELECT price, shippingCost, inStock, shops.name, $urlSelect, shops.logoPath, shops.shippingTime
FROM offers
INNER JOIN shops ON
offers.shopID = shops.shopID WHERE offers.productID = ? ORDER BY offers.price ASC");
$stmt->bind_param("i", $productId);
$stmt->execute();
$result = $stmt->get_result();
$shopInfo = [];
/**
* @brief Speichert die Shop-Ergebnisseingebungen in einem Array für die spätere Iteration.
*/
while ($row = $result->fetch_assoc()) {
$shopInfo[] = $row;
}
?>
<div class="shop-offers">
<?php if (!empty($shopInfo)): ?>
<?php foreach ($shopInfo as $shop): ?>
<div class="shop-line">
<div class="shop-left">
<div class="shop-logo">
<img src="<?= isset($shop['logoPath']) ? $shop['logoPath'] : 'assets/images/placeholder.png' ?>"
alt ="Kein Logo gefunden" >
</div>
<div class="shop-name">
<a href="<?= htmlspecialchars($shop['offerURL']) ?>" target="_blank">
<?= htmlspecialchars($shop['name']) ?>
</a>
</div>
</div>
<div class="shop-middle">
<div class="shop-shipping">
Versand: <?= htmlspecialchars($shop['shippingCost']) ?> € &nbsp &nbsp &nbsp
Lieferzeit: <?= htmlspecialchars($shop['shippingTime']) ?> Werktage
</div>
<div class="shop-stock <?= $shop['inStock'] ? 'in-stock' : 'out-stock' ?>">
<?= $shop['inStock'] ? "Lagernd" : "Nicht lagernd" ?>
</div>
<div class="shop-price">
Preis: <?= htmlspecialchars($shop['price']) ?> € <br>
</div>
</div>
</div>
<?php endforeach; ?>
<?php else: ?>
<div class="no-shop">
<p>Keine Shops bieten dieses Produkt an.</p>
</div>
<?php endif; ?>
</div>
<?php $stmt->close(); ?>
<?php
/**
* @brief Holt alle Nutzer-Bewertungen zu diesem Produkt.
* @details Liefert Bewertungstexte, Anzahl vergebener Sterne und die nutzerspezifischen Profileigenschaften.
*/
// HIER ANGEPASST: profilePicture und createdAt zum SELECT hinzugefügt
$stmt = mysqli_prepare($conn,
" SELECT reviews.reviewID, rating, comment, users.displayname, users.profilePicture, reviews.createdAt
FROM reviews
INNER JOIN users ON reviews.userID = users.userID
WHERE productID = ? ORDER BY rating DESC");
$stmt->bind_param("i", $productId);
$stmt->execute();
$result = $stmt->get_result();
$reviews = [];
/**
* @brief Speichert alle ermittelten Bewertungen im Array für die Ansicht.
*/
while ($row = $result->fetch_assoc()) {
$reviews[] = $row;
}
?>
<div class="reviews">
<h2 class="reviews-title">Bewertungen</h2>
<div class="reviews-all">
<?php if (!empty($reviews)): ?>
<?php foreach ($reviews as $review): ?>
<div class="review-card">
<div class="review-header">
<div class="review-user-info">
<img class="review-avatar"
src="<?= !empty($review['profilePicture']) ? htmlspecialchars($review['profilePicture']) : 'assets/images/placeholder.png' ?>"
alt="Profilbild von <?= htmlspecialchars($review['displayname']) ?>">
<div>
<div class="review-user">
<?= htmlspecialchars($review['displayname']) ?>
</div>
<?php if (isset($review['createdAt'])): ?>
<div style="font-size: 0.8rem; color: #94a3b8;">
<?= date('d.m.Y', strtotime($review['createdAt'])) ?>
</div>
<?php endif; ?>
</div>
</div>
<div class="review-rating">
<?php for ($i = 1; $i <= 5; $i++): ?>
<span class="star <?= $i <= $review['rating'] ? 'filled' : '' ?>">★</span>
<?php endfor; ?>
</div>
</div>
<div class="review-comment">
<?= nl2br(htmlspecialchars($review['comment'])) ?>
</div>
<?php if (!empty($_SESSION['user_roles']) && in_array('ADMIN', $_SESSION['user_roles'], true)): ?>
<div class="review-admin-actions" style="margin-top: 10px; text-align: right;">
<form method="post" action="productpage.php?id=<?= $productId ?>" onsubmit="return confirm('Bewertung wirklich löschen?');">
<input type="hidden" name="delete_review_id" value="<?= $review['reviewID'] ?>">
<button type="submit" name="delete_review" style="background-color: #ef4444; color: white; border: none; padding: 5px 10px; border-radius: 4px; cursor: pointer; font-size: 0.8rem;">Löschen</button>
</form>
</div>
<?php endif; ?>
</div>
<?php endforeach; ?>
<?php else: ?>
<div class="no-review">
<p>Es gibt noch keine Bewertungen.</p>
</div>
<?php endif; ?>
</div>
<div class="review-add">
<h2 class="reviews-title">Füge deine Bewertung hinzu!</h2>
<?php
/**
* @brief Steuert den Logik-Fluss zum Hinzufügen einer eigenen Bewertung.
* @details Prüft zunächst, ob der momentan eingeloggte Nutzer das Produkt bereits
* bewertet hat. Falls nicht, kann eine neue Bewertung über einen POST-Request verarbeitet werden.
*/
$userHasReviewed = false;
// 1. Prüfen, ob der eingeloggte Nutzer schon bewertet hat
if (isset($_SESSION['user_id'])) {
$stmtCheckRev = mysqli_prepare($conn, "SELECT 1 FROM reviews WHERE userID = ? AND productID = ? LIMIT 1");
mysqli_stmt_bind_param($stmtCheckRev, "ii", $_SESSION['user_id'], $productId);
mysqli_stmt_execute($stmtCheckRev);
mysqli_stmt_store_result($stmtCheckRev);
if (mysqli_stmt_num_rows($stmtCheckRev) > 0) {
$userHasReviewed = true;
}
mysqli_stmt_close($stmtCheckRev);
}
// 2. Bewertung speichern (NUR wenn noch keine existiert!)
if (
$_SERVER['REQUEST_METHOD'] === 'POST' &&
isset($_POST['submit_review']) &&
isset($_SESSION['user_id']) &&
!$userHasReviewed
) {
$rating = (int)$_POST['rating'];
$comment = trim($_POST['comment']);
$userID = $_SESSION['user_id'];
if ($rating >= 1 && $rating <= 5 && !empty($comment)) {
$stmtInsertRev = mysqli_prepare(
$conn,
"INSERT INTO reviews (userID, productID, rating, comment) VALUES (?, ?, ?, ?)"
);
if ($stmtInsertRev) {
mysqli_stmt_bind_param(
$stmtInsertRev,
"iiis",
$userID,
$productId,
$rating,
$comment
);
mysqli_stmt_execute($stmtInsertRev);
mysqli_stmt_close($stmtInsertRev);
// JS Weiterleitung nach dem erfolgreichen Anlegen der Bewertung
echo "<script>window.location.href = 'productpage.php?id=" . $productId . "';</script>";
exit;
}
}
}
?>
<div class="review-card">
<?php if (!isset($_SESSION['user_id'])): ?>
<div class="review-login-prompt">
<p style="color: #cbd5e1; margin-bottom: 1rem;">Du musst eingeloggt sein, um eine Bewertung abzugeben.</p>
<a href="login.php">
<input class="auth__submit" type="button" value="Zum Einloggen">
</a>
</div>
<?php elseif ($userHasReviewed): ?>
<div class="review-login-prompt">
<p class="review-login-msg">Du hast dieses Produkt bereits bewertet. Vielen Dank!</p>
</div>
<?php else: ?>
<!--
* @brief Eingabeformular für Bewertungen.
* @details Bietet ein Feld für den Kommentartext sowie Radio-Buttons für das Sterne-Rating (1-5).
-->
<form class="review-input-form" method="post" autocomplete="off">
<input type="hidden" name="submit_review" value="1">
<div class="rating-input">
<input type="radio" id="star5" name="rating" value="5" required />
<label for="star5" title="5 Sterne">★</label>
<input type="radio" id="star4" name="rating" value="4" />
<label for="star4" title="4 Sterne">★</label>
<input type="radio" id="star3" name="rating" value="3" />
<label for="star3" title="3 Sterne">★</label>
<input type="radio" id="star2" name="rating" value="2" />
<label for="star2" title="2 Sterne">★</label>
<input type="radio" id="star1" name="rating" value="1" />
<label for="star1" title="1 Stern">★</label>
</div>
<textarea class="review-comment-input" name="comment" rows="4"
placeholder="Teile deine Meinung mit anderen!" required></textarea>
<input class="auth__submit" type="submit" value="Senden">
</form>
<?php endif; ?>
</div>
</div>
<?php include 'footer.php'; ?>