333 lines
10 KiB
PHP
333 lines
10 KiB
PHP
<?php
|
||
declare(strict_types=1);
|
||
|
||
// Konfiguration einbinden (stellt $servername, $username, $password, $db, $imagedir bereit)
|
||
require_once __DIR__ . '/../config/config.php';
|
||
|
||
/**
|
||
* HTML-Escape Helper (fallback, falls global e() fehlt)
|
||
*/
|
||
if (!function_exists('e')) {
|
||
function e(string $s): string
|
||
{
|
||
return htmlspecialchars($s, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||
}
|
||
}
|
||
/** Attribut-Helper (für data-*, value, title, …) */
|
||
if (!function_exists('attr')) {
|
||
function attr(string $s): string
|
||
{
|
||
return htmlspecialchars($s, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||
}
|
||
}
|
||
|
||
/** Pfad zum Platzhalterbild ermitteln */
|
||
/** Pfad zum Platzhalterbild ermitteln */
|
||
if (!function_exists('lg_default_image')) {
|
||
function lg_default_image(): string
|
||
{
|
||
$dir = $_SERVER['DOCUMENT_ROOT'] . '/' . ($GLOBALS['placeholders_imagedir'] ?? 'img/placeholders');
|
||
|
||
// Alle PNG/JPG Dateien aus dem Ordner holen
|
||
$files = glob($dir . '/*.{png,jpg,jpeg,gif,webp}', GLOB_BRACE);
|
||
|
||
if ($files && count($files) > 0) {
|
||
// Einen zufälligen auswählen
|
||
$randomFile = $files[array_rand($files)];
|
||
|
||
// In relative URL umwandeln
|
||
$relativePath = str_replace($_SERVER['DOCUMENT_ROOT'], '', $randomFile);
|
||
return $relativePath;
|
||
}
|
||
|
||
// Fallback: statisches no-image
|
||
return '/img/no-image-katie-1.png';
|
||
}
|
||
}
|
||
|
||
|
||
/**
|
||
* DB-Connector (keine Fehlerdetails nach außen)
|
||
*/
|
||
function lg_db(): mysqli
|
||
{
|
||
global $servername, $username, $password, $db;
|
||
$conn = new mysqli($servername, $username, $password, $db);
|
||
if ($conn->connect_error) {
|
||
http_response_code(500);
|
||
exit('Interner Fehler (DB)');
|
||
}
|
||
$conn->set_charset('utf8mb4');
|
||
return $conn;
|
||
}
|
||
|
||
/**
|
||
* Einzelne Karten-Kachel rendern
|
||
*/
|
||
function generateListItem(
|
||
int $ListItemID,
|
||
?string $ItemImage,
|
||
string $ItemTitle,
|
||
?string $ItemLink,
|
||
int $ItemPriceCents,
|
||
?string $ItemComment,
|
||
int $ItemReserved,
|
||
?string $ItemDate,
|
||
int $ItemQty,
|
||
): void {
|
||
global $loggedin, $imagedir;
|
||
|
||
// Preis formatieren (de_DE)
|
||
$priceText = '';
|
||
if ($ItemPriceCents > 0) {
|
||
if (class_exists('NumberFormatter')) {
|
||
$fmt = new NumberFormatter('de_DE', NumberFormatter::CURRENCY);
|
||
$priceText = $fmt->formatCurrency($ItemPriceCents / 100, 'EUR');
|
||
} else {
|
||
$priceText = number_format($ItemPriceCents / 100, 2, ',', '.') . ' €';
|
||
}
|
||
}
|
||
|
||
// Kommentar (2 Zeilen max., CSS macht den Clamp)
|
||
$commentPlain = trim((string) $ItemComment);
|
||
$commentHtml = ($commentPlain === '') ? ' ' : e($commentPlain);
|
||
|
||
// Bild (lokal oder Placeholder)
|
||
$srcPath = '';
|
||
if (!empty($ItemImage)) {
|
||
$local = rtrim($imagedir, '/') . '/' . $ItemImage;
|
||
$srcPath = @is_file($local) ? $local : lg_default_image();
|
||
} else {
|
||
$srcPath = lg_default_image();
|
||
}
|
||
$imageTag = '<div class="ratio ratio-16x9"><img src="' . e($srcPath) . '" class="card-img-top object-fit-contain" alt="Bild zum Wunsch"></div>';
|
||
|
||
// Titelzeile: Qty-Pill (nur wenn >1) + einzeiliger Titel
|
||
$qtyPill = ($ItemQty > 1) ? '<span class="qty-pill me-2">×' . (int) $ItemQty . '</span>' : '';
|
||
$titleHtml = '<div class="d-flex align-items-center card-title-row">'
|
||
. $qtyPill
|
||
. '<h5 class="card-title flex-grow-1 mb-0">' . e($ItemTitle) . '</h5>'
|
||
. '</div>';
|
||
|
||
// Status-Badge links
|
||
$statusLeft = '';
|
||
if ($ItemReserved >= $ItemQty) {
|
||
$statusLeft = '<span class="badge bg-danger">alle reserviert</span>';
|
||
} elseif ($ItemReserved > 0) {
|
||
$statusLeft = '<span class="badge bg-warning text-dark">'
|
||
. (int) $ItemReserved . ' / ' . (int) $ItemQty . ' reserviert</span>';
|
||
}
|
||
|
||
// Datum (lokal lesbar)
|
||
$dateHtml = '';
|
||
if (!empty($ItemDate)) {
|
||
$ts = strtotime($ItemDate);
|
||
$dateHtml = $ts ? date('d.m.Y', $ts) : e($ItemDate);
|
||
}
|
||
|
||
// User-Buttons (Reserve / Cancel / Vendor)
|
||
// --- unten: Aktionsleiste (Icons, wachsen bei Hover) ---
|
||
// (Reserve nur wenn möglich; Cancel immer; Vendor nur wenn Link)
|
||
$userBtns = [];
|
||
|
||
if ($ItemReserved < $ItemQty) {
|
||
$userBtns[] = sprintf(
|
||
'<button class="btn btn-primary btn-pill justify-content-end"
|
||
aria-label="Reservieren"
|
||
data-bs-toggle="modal" data-bs-target="#reservationModal"
|
||
data-wishid="%d" data-reserved="0">
|
||
<i class="fa-solid fa-lock"></i>
|
||
<span>Reservieren</span>
|
||
</button>',
|
||
$ListItemID
|
||
);
|
||
}
|
||
|
||
if ($ItemReserved > 0) {
|
||
$userBtns[] = sprintf(
|
||
'<button class="btn btn-outline-primary btn-pill justify-content-center"
|
||
aria-label="Reservierung aufheben"
|
||
data-bs-toggle="modal" data-bs-target="#reservationModal"
|
||
data-wishid="%d" data-reserved="1">
|
||
<i class="fa-solid fa-unlock"></i>
|
||
<span>Reservierung aufheben</span>
|
||
</button>',
|
||
$ListItemID
|
||
);
|
||
}
|
||
|
||
if (!empty($ItemLink)) {
|
||
$safeLink = e($ItemLink);
|
||
$userBtns[] =
|
||
'<a class="btn btn-outline-dark btn-pill justify-content-start"
|
||
aria-label="Zum Anbieter"
|
||
href="' . $safeLink . '" target="_blank" rel="noopener">
|
||
<i class="fa-solid fa-up-right-from-square">
|
||
</i><span>Zum Anbieter</span>
|
||
</a>';
|
||
}
|
||
|
||
$userBtnRow = implode("\n", $userBtns);
|
||
|
||
// ... in deinem Echo-Heredoc:
|
||
// <div class="btn-group btn-group-pills mt-3" role="group" aria-label="Aktionen">
|
||
// {$userBtnRow}
|
||
// </div>
|
||
|
||
|
||
// Admin-Icons (nur eingeloggt)
|
||
$adminFloating = '';
|
||
if (!empty($loggedin)) {
|
||
$adminFloating = '
|
||
<div class="admin-actions btn-group btn-group-sm" role="group" aria-label="Admin-Action">
|
||
<button class="btn btn-outline-secondary btn-icon" title="nach oben"
|
||
data-bs-toggle="modal" data-bs-target="#pushprioModal" data-wishid="' . (int) $ListItemID . '">
|
||
<i class="fa-solid fa-arrow-up"></i>
|
||
</button>
|
||
<button class="btn btn-outline-warning btn-icon" title="edit"
|
||
data-bs-toggle="modal" data-bs-target="#itemModal"
|
||
data-mode="edit"
|
||
data-wishid="' . (int) $ListItemID . '"
|
||
data-title="' . attr($ItemTitle) . '"
|
||
data-description="' . attr($commentPlain) . '"
|
||
data-price="' . attr(($ItemPriceCents > 0) ? number_format($ItemPriceCents / 100, 2, ',', '.') : '') . '"
|
||
data-qty="' . (int) $ItemQty . '"
|
||
data-link="' . attr((string) $ItemLink) . '">
|
||
<i class="fa-solid fa-pen"></i>
|
||
</button>
|
||
<button class="btn btn-outline-danger btn-icon" title="löschen"
|
||
data-bs-toggle="modal" data-bs-target="#deleteModal" data-wishid="' . (int) $ListItemID . '">
|
||
<i class="fa-solid fa-trash"></i>
|
||
</button>
|
||
</div>';
|
||
}
|
||
|
||
// Preis-Badge rechts
|
||
$priceHtml = $priceText !== '' ? '<span class="badge text-bg-light fs-6 fw-semibold">' . e($priceText) . '</span>' : '';
|
||
|
||
echo <<<HTML
|
||
<div class="col">
|
||
<div class="card wl-card shadow-sm h-100 position-relative">
|
||
{$adminFloating}
|
||
{$imageTag}
|
||
<div class="card-body d-flex flex-column">
|
||
{$titleHtml}
|
||
<p class="card-text">{$commentHtml}</p>
|
||
|
||
<div class="d-flex justify-content-between align-items-center mt-1">
|
||
<div>{$statusLeft}</div>
|
||
<div class="status-right text-end">
|
||
<div>{$priceHtml}</div>
|
||
<div class="text-muted small">{$dateHtml}</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="btn-group btn-group-pills mt-3" role="group" aria-label="Aktionen">
|
||
{$userBtnRow}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
HTML;
|
||
}
|
||
|
||
|
||
/**
|
||
* Haupt-Builder für die Listenansicht
|
||
* $ListID: aktive Liste (int, intern)
|
||
* $sortby: einer aus der Whitelist unten
|
||
*/
|
||
function wishlistMainBuilder(int $ListID, string $sortby = 'priority'): void
|
||
{
|
||
global $loggedin;
|
||
|
||
$conn = lg_db();
|
||
|
||
// 1) Listen-Metadaten holen
|
||
$stmt = $conn->prepare('SELECT title, description FROM lists WHERE ID = ?');
|
||
$stmt->bind_param('i', $ListID);
|
||
$stmt->execute();
|
||
$res = $stmt->get_result();
|
||
|
||
$listTitle = 'Unbekannte Liste';
|
||
$listDesc = '';
|
||
if ($res && $row = $res->fetch_assoc()) {
|
||
$listTitle = e((string) $row['title']);
|
||
$listDesc = e((string) $row['description']);
|
||
}
|
||
$stmt->close();
|
||
|
||
// 2) ORDER BY Whitelist
|
||
$orderWhitelist = [
|
||
'priority' => 'priority DESC',
|
||
'price_asc' => 'price ASC',
|
||
'price_desc' => 'price DESC',
|
||
'date_desc' => 'date DESC',
|
||
'date_asc' => 'date ASC',
|
||
'random' => 'RAND()',
|
||
];
|
||
$orderSql = $orderWhitelist[$sortby] ?? $orderWhitelist['priority'];
|
||
|
||
// 3) Wünsche laden
|
||
$sql = "
|
||
SELECT w.ID, w.image, w.title, w.link, w.price, w.description,
|
||
w.date, w.qty,
|
||
(SELECT COUNT(*) FROM wishes_reservations r WHERE r.wish_id = w.ID) AS reserved_count
|
||
FROM wishes w
|
||
WHERE w.wishlist = ?
|
||
ORDER BY {$orderSql}";
|
||
$stmt = $conn->prepare($sql);
|
||
$stmt->bind_param('i', $ListID);
|
||
$stmt->execute();
|
||
$items = $stmt->get_result();
|
||
|
||
// 4) Header-Text (vorher berechnen, kein Ternary im Heredoc)
|
||
$loginMsg = $loggedin
|
||
? 'Eingeloggt: Du kannst Einträge bearbeiten, löschen und priorisieren.'
|
||
: 'Tipp: Du kannst Einträge reservieren. Nur mit deinem Reservierungs-Passwort lässt sich die Reservierung wieder lösen.';
|
||
|
||
echo <<<HTML
|
||
<section class="py-5 text-center container">
|
||
<div class="row py-lg-4">
|
||
<div class="col-lg-8 col-md-10 mx-auto">
|
||
<h1 class="fw-light">{$listTitle}</h1>
|
||
<p class="lead text-muted">{$listDesc}</p>
|
||
<p class="text-muted">{$loginMsg}</p>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<div class="album py-3 bg-light">
|
||
<div class="container">
|
||
<div class="row row-cols-1 row-cols-sm-2 row-cols-md-3 g-3">
|
||
HTML;
|
||
|
||
if ($items !== false && $items->num_rows > 0) {
|
||
while ($row = $items->fetch_assoc()) {
|
||
generateListItem(
|
||
(int) $row['ID'],
|
||
$row['image'] !== null ? (string) $row['image'] : null,
|
||
(string) $row['title'],
|
||
$row['link'] !== null ? (string) $row['link'] : null,
|
||
(int) $row['price'],
|
||
$row['description'] !== null ? (string) $row['description'] : null,
|
||
(int) $row['reserved_count'], // echte Anzahl Reservierungen
|
||
$row['date'] !== null ? (string) $row['date'] : null,
|
||
isset($row['qty']) ? (int) $row['qty'] : 1
|
||
);
|
||
}
|
||
|
||
} else {
|
||
echo '<div class="col"><div class="alert alert-info w-100">Keine Einträge vorhanden.</div></div>';
|
||
}
|
||
|
||
echo <<<HTML
|
||
</div>
|
||
</div>
|
||
</div>
|
||
HTML;
|
||
|
||
$stmt->close();
|
||
$conn->close();
|
||
}
|