291 lines
11 KiB
PHP
291 lines
11 KiB
PHP
<?php
|
|
/**
|
|
* @file compcards.php
|
|
* @brief Datei compcards.php
|
|
*
|
|
* @details Diese Datei ist für die Anzeige der Produktkarten auf der Übersichts- oder Suchseite zuständig.
|
|
* Sie verarbeitet Suchanfragen sowie Kategorie- und Attributfilter und generiert die entsprechende HTML-Ausgabe.
|
|
* Es werden keine Änderungen am bestehenden Code vorgenommen, lediglich diese ausführlichen Kommentare hinzugefügt.
|
|
*/
|
|
|
|
// login.php
|
|
|
|
require_once __DIR__ . '/lib/bootstrap.php';
|
|
|
|
/**
|
|
* Fehleranzeige für Entwicklungszwecke aktivieren.
|
|
* Hiermit werden alle Fehler, Warnungen und Hinweise direkt ausgegeben.
|
|
*/
|
|
ini_set('display_errors', 1);
|
|
ini_set('display_startup_errors', 1);
|
|
error_reporting(E_ALL);
|
|
|
|
/**
|
|
* 1) DB-Verbindung (einmalig aufbauen)
|
|
* Die Funktion db_connect() muss in den eingebundenen Bibliotheken (z. B. bootstrap.php / db.php) definiert sein.
|
|
* @var mysqli $conn Die aktive Datenbankverbindung.
|
|
*/
|
|
$conn = db_connect();
|
|
?>
|
|
|
|
<?php
|
|
/**
|
|
* ─────────────────────────────────────────────
|
|
* Reine PHP-Suche (GET ?search=...)
|
|
* ─────────────────────────────────────────────
|
|
* @details Wenn ein Suchbegriff über den URL-Parameter 'search' übergeben wurde,
|
|
* zeigen wir ausschließlich die Suchergebnisse an und überspringen die Anzeige
|
|
* der standardmäßigen Kategorie-Sektionen.
|
|
*/
|
|
|
|
/**
|
|
* @var string $searchTerm Holt den Suchbegriff aus dem GET-Parameter, trimmt überflüssige Leerzeichen.
|
|
*/
|
|
$searchTerm = isset($_GET['search']) ? trim((string)$_GET['search']) : '';
|
|
|
|
/**
|
|
* @var int $searchLen Berechnet die Länge des Suchbegriffs (multibyte-safe, falls mb_strlen existiert).
|
|
*/
|
|
$searchLen = function_exists('mb_strlen') ? mb_strlen($searchTerm, 'UTF-8') : strlen($searchTerm);
|
|
|
|
if ($searchTerm !== '') {
|
|
/**
|
|
* @details Escaping des Suchbegriffs für die LIKE-Klausel, um SQL-Injections durch Wildcards zu verhindern.
|
|
* @var string $like Der für SQL präparierte Such-String mit umschließenden %-Zeichen.
|
|
*/
|
|
$like = addcslashes($searchTerm, "%_\\");
|
|
$like = '%' . $like . '%';
|
|
|
|
/**
|
|
* @details Führt eine Suche auf die Tabelle 'products' aus.
|
|
* Es wird sowohl in der Spalte 'model' als auch in 'description' gesucht.
|
|
* @var mysqli_stmt|false $stmtSearch Das Prepared Statement für die Suche.
|
|
*/
|
|
$stmtSearch = $conn->prepare("
|
|
SELECT productID, model, description, imagePath
|
|
FROM products
|
|
WHERE model LIKE ? OR description LIKE ?
|
|
ORDER BY model ASC
|
|
LIMIT 60
|
|
");
|
|
|
|
if ($stmtSearch) {
|
|
// Parameterbindung: Zwei Strings ('ss') für die doppelten LIKE-Bedingungen.
|
|
$stmtSearch->bind_param('ss', $like, $like);
|
|
$stmtSearch->execute();
|
|
|
|
/**
|
|
* @var mysqli_result $resultSearch Das ResultSet der ausgeführten Suchanfrage.
|
|
*/
|
|
$resultSearch = $stmtSearch->get_result();
|
|
?>
|
|
|
|
<section class="product-section">
|
|
<h2>Suchergebnisse für „<?= htmlspecialchars($searchTerm) ?>“</h2>
|
|
|
|
<?php if ($resultSearch->num_rows <= 0): ?>
|
|
<!-- Wenn keine Ergebnisse gefunden wurden -->
|
|
<p class="search-empty">Keine Produkte gefunden.</p>
|
|
<?php else: ?>
|
|
<!-- Grid für die Anzeige der Suchergebnisse -->
|
|
<div class="product-grid">
|
|
<?php while ($product = $resultSearch->fetch_assoc()): ?>
|
|
<?php
|
|
/**
|
|
* @var int $productId Casting der Produkt-ID auf Integer für den Link.
|
|
*/
|
|
$productId = (int)$product['productID'];
|
|
?>
|
|
<a class="product-card" href="productpage.php?id=<?= $productId ?>">
|
|
<!-- Anzeige des Produktbildes mit Fallback auf ein Platzhalter-Bild -->
|
|
<img
|
|
src="<?= !empty($product['imagePath']) ? htmlspecialchars($product['imagePath']) : 'assets/images/placeholder.png' ?>"
|
|
alt="<?= htmlspecialchars($product['model'] ?? '') ?>">
|
|
|
|
<div class="product-card__content">
|
|
<!-- Der Modellname des Produkts -->
|
|
<h3><?= htmlspecialchars($product['model'] ?? '') ?></h3>
|
|
<!-- Die Beschreibung des Produkts -->
|
|
<p><?= htmlspecialchars($product['description'] ?? '') ?></p>
|
|
</div>
|
|
</a>
|
|
<?php endwhile; ?>
|
|
</div>
|
|
<?php endif; ?>
|
|
</section>
|
|
|
|
<?php
|
|
// Schließen des Prepared Statements zur Freigabe von Ressourcen.
|
|
$stmtSearch->close();
|
|
}
|
|
|
|
/**
|
|
* Wichtig: Im Suchmodus beenden wir die Einbindung dieser Datei hier (return),
|
|
* damit keine weiteren Kategorie-Blöcke gerendert werden.
|
|
*/
|
|
return;
|
|
}
|
|
?>
|
|
|
|
<?php
|
|
/**
|
|
* @details Ermitteln der aktiven Kategorie. Falls keine angegeben wurde, ist 'all' der Standard.
|
|
* @var string $activeCategory Die aktive Kategorie-ID in Textform.
|
|
*/
|
|
$activeCategory = isset($_GET['category']) ? $_GET['category'] : 'all';
|
|
?>
|
|
|
|
<?php
|
|
/**
|
|
* @details Mapping von Kategorie-Keys zu Datenbank-IDs und lesbaren Labels.
|
|
* @var array $categories Ein assoziatives Array der verfügbaren Hauptkategorien.
|
|
*/
|
|
$categories = [
|
|
'iphone' => ['id' => 20, 'label' => 'iPhone'],
|
|
'ipad' => ['id' => 21, 'label' => 'iPad'],
|
|
'macbook' => ['id' => 22, 'label' => 'MacBook'],
|
|
'airpods' => ['id' => 23, 'label' => 'AirPods'],
|
|
'watch' => ['id' => 25, 'label' => 'Watch'],
|
|
'accessories' => ['id' => 24, 'label' => 'Accessories'],
|
|
];
|
|
?>
|
|
|
|
<?php
|
|
/**
|
|
* Iteration über alle definierten Kategorien, um jede als einzelnen Sektor anzuzeigen.
|
|
*/
|
|
foreach ($categories as $key => $cat):
|
|
?>
|
|
|
|
<?php
|
|
/**
|
|
* @details Nur dann Daten laden und Sektion anzeigen, wenn entweder alle Kategorien
|
|
* gewünscht sind oder der key mit der aktuell gewählten Kategorie übereinstimmt.
|
|
*/
|
|
if ($activeCategory === 'all' || $activeCategory === $key):
|
|
?>
|
|
|
|
<?php
|
|
/**
|
|
* @var string $baseQuery Der grundlegende SELECT-Teil der dynamischen Produktsuche.
|
|
*/
|
|
$baseQuery = "SELECT DISTINCT p.productID, p.model, p.description, p.imagePath FROM products p ";
|
|
|
|
/**
|
|
* @var array $whereClauses Array zum Sammeln aller WHERE-Bedingungen.
|
|
*/
|
|
$whereClauses = ["p.categoryID = ?"];
|
|
|
|
/**
|
|
* @var array $params Array zur Aufnahme der Bind-Parameter für das Prepared Statement.
|
|
*/
|
|
$params = [$cat['id']];
|
|
|
|
/**
|
|
* @var string $types Enthält den Typ-String für bind_param (z. B. 'i', 's', 'd').
|
|
*/
|
|
$types = "i";
|
|
|
|
/**
|
|
* @details Auslesen von dynamischen Attribut-Filtern aus der URL ($_GET).
|
|
* @var int $attrIndex Zähler zur Erzeugung eindeutiger Aliase für JOINs.
|
|
*/
|
|
$attrIndex = 0;
|
|
foreach ($_GET as $k => $v) {
|
|
// Nur Parameter berücksichtigen, die auf 'attr_' beginnen und einen Wert haben.
|
|
if ($v !== '' && strpos($k, 'attr_') === 0) {
|
|
$attrId = (int)substr($k, 5);
|
|
$attrAlias = "pa" . $attrIndex;
|
|
|
|
// Dynamischer JOIN der productAttributes-Tabelle für jedes gefilterte Attribut.
|
|
$baseQuery .= " JOIN productAttributes $attrAlias ON p.productID = $attrAlias.productID ";
|
|
|
|
/**
|
|
* @details Sucht im Attribut entweder nach String, Number oder Boolean ('Ja'/'Nein').
|
|
*/
|
|
$whereClauses[] = "($attrAlias.attributeID = ? AND ($attrAlias.valueString = ? OR $attrAlias.valueNumber = ? OR ($attrAlias.valueBool = 1 AND ? = 'Ja') OR ($attrAlias.valueBool = 0 AND ? = 'Nein')))";
|
|
|
|
// Parameter für bind_param befüllen
|
|
$params[] = $attrId;
|
|
$params[] = $v;
|
|
$params[] = is_numeric($v) ? (float)$v : 0;
|
|
$params[] = $v;
|
|
$params[] = $v;
|
|
|
|
// Typen ergänzen: Integer, String, Double, String, String an SQL übergeben
|
|
$types .= "isdss";
|
|
|
|
$attrIndex++;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @var string $sql Zusammensetzen der kompletten SQL-Abfrage aus Base, JOINs und WHEREs.
|
|
*/
|
|
$sql = $baseQuery . " WHERE " . implode(" AND ", $whereClauses);
|
|
|
|
/**
|
|
* @var mysqli_stmt $stmt Das Prepared Statement für die Kategorieabfrage.
|
|
*/
|
|
$stmt = $conn->prepare($sql);
|
|
// Bind Parameter per Spread-Operator aus dem Params-Array.
|
|
$stmt->bind_param($types, ...$params);
|
|
$stmt->execute();
|
|
|
|
/**
|
|
* @var mysqli_result $result Das Ergebnis-Set mit den gefundenen Produkten dieser Kategorie.
|
|
*/
|
|
$result = $stmt->get_result();
|
|
?>
|
|
|
|
<?php
|
|
/**
|
|
* Rendern der Produktsektion nur, falls auch Produkte in dieser Kategorie gefunden wurden.
|
|
*/
|
|
if ($result->num_rows > 0):
|
|
?>
|
|
<section class="product-section">
|
|
<!-- Ausgabe des Kategorie-Labels als Überschrift -->
|
|
<h2><?= htmlspecialchars($cat['label']) ?></h2>
|
|
|
|
<!-- Horizontaler Scroll-Bereich für die Produktkarten einer Kategorie -->
|
|
<div class="product-scroll">
|
|
<?php
|
|
/**
|
|
* Fetch-Schleife über jedes Produkt im Result-Set.
|
|
*/
|
|
while ($product = $result->fetch_assoc()):
|
|
?>
|
|
<?php
|
|
/**
|
|
* @var int $productId Casting der Produkt-ID
|
|
*/
|
|
$productId = (int)$product['productID'];
|
|
?>
|
|
<a class="product-card" href="productpage.php?id=<?= $productId ?>">
|
|
<!-- Produktbild und Fallback auf Platzhalter -->
|
|
<img
|
|
src="<?= isset($product['imagePath']) ? $product['imagePath'] : 'assets/images/placeholder.png' ?>"
|
|
alt="<?= htmlspecialchars($product['model']) ?>">
|
|
|
|
<div class="product-card__content">
|
|
<!-- Anzeige des Produktnamens -->
|
|
<h3><?= htmlspecialchars($product['model']) ?></h3>
|
|
<!-- Anzeige der Produktbeschreibung -->
|
|
<p><?= htmlspecialchars($product['description']) ?></p>
|
|
</div>
|
|
</a>
|
|
<?php endwhile; ?>
|
|
</div>
|
|
</section>
|
|
<?php endif; ?>
|
|
|
|
<?php
|
|
// Schließen des Prepared Statements der Kategorie, um Ressourcen freizugeben.
|
|
$stmt->close();
|
|
?>
|
|
|
|
<?php endif; ?>
|
|
|
|
<?php endforeach; ?>
|