164 lines
5.2 KiB
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);
|
|
}
|