Geizkragen/api/search_products.php

164 lines
5.2 KiB
PHP

<?php
/**
* @file search_products.php
* @brief Skript zur asynchronen Produktsuche (Live-Suche).
*
* @details Diese Datei dient als API-Endpunkt für die Echtzeit-Suche. Sie nimmt eine
* Suchanfrage über den GET-Parameter 'q' entgegen, durchsucht die Datenbanktabelle 'products'
* nach übereinstimmenden Modellnamen oder Beschreibungen und gibt die Resultate im JSON-Format zurück.
* Die Ergebnisse werden priorisiert, sodass exakte oder teilweise Übereinstimmungen im Modellnamen
* weiter oben erscheinen.
*
* @author Fabian
* @date 2026-04-04
*/
declare(strict_types=1);
/**
* Einbinden der Bootstrap-Datei.
* Übernimmt die Basiskonfiguration und lädt wichtige Bibliotheken.
*/
require_once __DIR__ . '/../lib/bootstrap.php';
/**
* Setzen des Content-Type-Headers auf JSON, da dieses Skript
* von JavaScript (AJAX/Fetch) aufgerufen wird und JSON erwartet.
*/
header('Content-Type: application/json; charset=utf-8');
/**
* Sicherheits-Header, der verhindert, dass der Browser den MIME-Typ der Antwort
* errät (MIME-Sniffing). Erhöht die Sicherheit der API.
*/
header('X-Content-Type-Options: nosniff');
try {
/**
* @var mysqli|PDO db_connect() Stellt eine Verbindung zur Datenbank her.
*/
$conn = db_connect();
/**
* @var string $q Der Such-String aus dem GET-Parameter 'q'.
* Falls nicht vorhanden, wird ein leerer String verwendet.
*/
$q = isset($_GET['q']) ? (string)$_GET['q'] : '';
$q = trim($q);
/**
* @var int $limit Die maximale Anzahl der zurückzugebenden Suchergebnisse.
* Standard ist 8, falls kein Limit oder ein ungültiges Limit angegeben wird.
*/
$limit = isset($_GET['limit']) ? (int)$_GET['limit'] : 8;
// Sicherstellen, dass mindestens 1 Ergebnis zurückgegeben wird
if ($limit < 1) {
$limit = 1;
}
// Begrenzen der maximalen Ergebnisse auf 15, um Datenbanküberlastung zu vermeiden
if ($limit > 15) {
$limit = 15;
}
/**
* Mindestlänge für die Suche.
* Wenn der Suchbegriff leer ist oder aus weniger als 1 Zeichen besteht,
* wird ein leeres JSON-Array zurückgegeben und die Ausführung beendet.
*/
if (mb_strlen($q, 'UTF-8') < 1) {
echo json_encode(['items' => []], JSON_UNESCAPED_UNICODE);
exit;
}
/**
* @var string $like Der escapte Such-String für das LIKE-Statement in SQL.
* Spezielle Wildcard-Zeichen (%) und (_) werden escapt, um SQL-Fehler
* oder unerwartetes Verhalten bei Benutzereingaben zu verhindern.
*/
$like = addcslashes($q, "%_\\");
$like = '%' . $like . '%';
/**
* @var string $sql Die SQL-Abfrage zur Suche nach Produkten.
* Durchsucht die Spalten `model` und `description`.
* Sortiert Treffer im `model` vor Treffern in der `description`.
*/
$sql = "
SELECT p.productID, p.model, p.description, p.imagePath
FROM products p
WHERE (p.model LIKE ? OR p.description LIKE ?)
ORDER BY
CASE WHEN p.model LIKE ? THEN 0 ELSE 1 END,
p.model ASC
LIMIT ?
";
/**
* @var mysqli_stmt|PDOStatement $stmt Das vorbereitete SQL-Statement.
*/
$stmt = $conn->prepare($sql);
// Überprüfen, ob das Statement erfolgreich vorbereitet wurde
if (!$stmt) {
// HTTP-Statuscode 500 für einen internen Serverfehler setzen
http_response_code(500);
echo json_encode(['error' => 'DB-Query konnte nicht vorbereitet werden.'], JSON_UNESCAPED_UNICODE);
exit;
}
/**
* Binden der Parameter an das vorbereitete Statement.
* 'sssi' bedeutet: String, String, String, Integer.
*/
$stmt->bind_param('sssi', $like, $like, $like, $limit);
// Ausführen der vorbereiteten Abfrage
$stmt->execute();
// Holen des Ergebnisses aus der Datenbank
$res = $stmt->get_result();
/**
* @var array $items Das Array zur Speicherung der aufbereiteten Suchergebnisse.
*/
$items = [];
// Durchlaufen der einzelnen Datensätze / Zeilen
while ($row = $res->fetch_assoc()) {
/**
* @var int $id Die Produkt-ID iterierten Produkts.
*/
$id = (int)($row['productID'] ?? 0);
// Überspringe ungültige oder defekte IDs
if ($id <= 0) {
continue;
}
// Hinzufügen des formatierten Produkts in das Result-Array
$items[] = [
'id' => $id,
'model' => (string)($row['model'] ?? ''),
'description' => (string)($row['description'] ?? ''),
'imagePath' => (string)($row['imagePath'] ?? ''),
'url' => 'productpage.php?id=' . $id,
];
}
/**
* Rückgabe des Arrays als JSON-kodierter String.
* JSON_UNESCAPED_UNICODE verhindert, dass Umlaute escapet werden (z.B. %u00e4).
*/
echo json_encode(['items' => $items], JSON_UNESCAPED_UNICODE);
} catch (Throwable $e) {
/**
* Fehlerbehandlung im Falle einer Exception (z.B. bei einem Datenbankfehler).
* Sendet einen 500er Statuscode und eine generische Fehlermeldung als JSON.
*/
http_response_code(500);
echo json_encode(['error' => 'Serverfehler'], JSON_UNESCAPED_UNICODE);
}