$record) { $normalizedUsername = normalizeAdminUsername($username); if ($normalizedUsername === '') { continue; } $hash = ''; $description = ''; $email = getDefaultAdminEmail(); if (is_string($record)) { $hash = $record; $description = getDefaultAdminDescription($normalizedUsername); } elseif (is_array($record)) { $hash = isset($record['password_hash']) && is_string($record['password_hash']) ? $record['password_hash'] : ''; $description = isset($record['description']) ? normalizeAdminDescription($record['description']) : getDefaultAdminDescription($normalizedUsername); $email = isset($record['email']) ? normalizeAdminEmail($record['email']) : getDefaultAdminEmail(); } if ($hash === '') { continue; } if (!isValidAdminDescription($description)) { $description = getDefaultAdminDescription($normalizedUsername); } if (!isValidAdminEmail($email)) { $email = getDefaultAdminEmail(); } $accounts[$normalizedUsername] = [ 'password_hash' => $hash, 'description' => $description, 'email' => $email ]; } } return $accounts; } /** * Get admin users for authentication (username => password hash). */ function getAdminUsers() { $accounts = getAdminAccounts(); $admins = []; foreach ($accounts as $username => $account) { if (!isset($account['password_hash']) || !is_string($account['password_hash']) || $account['password_hash'] === '') { continue; } $admins[$username] = $account['password_hash']; } return $admins; } /** * Save full admin accounts to JSON store. */ function saveAdminAccounts($accounts) { $sanitizedAccounts = []; foreach ($accounts as $username => $account) { $normalizedUsername = normalizeAdminUsername($username); if ($normalizedUsername === '' || !is_array($account)) { continue; } $hash = isset($account['password_hash']) && is_string($account['password_hash']) ? $account['password_hash'] : ''; if ($hash === '') { continue; } $description = isset($account['description']) ? normalizeAdminDescription($account['description']) : getDefaultAdminDescription($normalizedUsername); if (!isValidAdminDescription($description)) { $description = getDefaultAdminDescription($normalizedUsername); } $email = isset($account['email']) ? normalizeAdminEmail($account['email']) : getDefaultAdminEmail(); if (!isValidAdminEmail($email)) { $email = getDefaultAdminEmail(); } $sanitizedAccounts[$normalizedUsername] = [ 'password_hash' => $hash, 'description' => $description, 'email' => $email ]; } ksort($sanitizedAccounts); writeJsonFile(ADMINS_FILE, ['admins' => $sanitizedAccounts]); } /** * Save admin users to JSON store (username => password hash). */ function saveAdminUsers($admins) { $existingAccounts = getAdminAccounts(); $normalizedAccounts = []; foreach ($admins as $username => $hash) { $normalizedUsername = normalizeAdminUsername($username); if ($normalizedUsername === '' || !is_string($hash) || $hash === '') { continue; } $description = isset($existingAccounts[$normalizedUsername]['description']) ? normalizeAdminDescription($existingAccounts[$normalizedUsername]['description']) : getDefaultAdminDescription($normalizedUsername); if (!isValidAdminDescription($description)) { $description = getDefaultAdminDescription($normalizedUsername); } $email = isset($existingAccounts[$normalizedUsername]['email']) ? normalizeAdminEmail($existingAccounts[$normalizedUsername]['email']) : getDefaultAdminEmail(); if (!isValidAdminEmail($email)) { $email = getDefaultAdminEmail(); } $normalizedAccounts[$normalizedUsername] = [ 'password_hash' => $hash, 'description' => $description, 'email' => $email ]; } saveAdminAccounts($normalizedAccounts); } /** * Get all products */ function getProducts() { $data = readJsonFile(PRODUCTS_FILE); return isset($data['products']) ? $data['products'] : []; } /** * Get product by ID */ function getProductById($id) { $products = getProducts(); foreach ($products as $product) { if ($product['id'] == $id) { return $product; } } return null; } /** * Save products */ function saveProducts($products) { $data = ['products' => $products]; writeJsonFile(PRODUCTS_FILE, $data); } /** * Resolve FAQ file path and force storage inside DATA_DIR. */ function getFaqFilePath(): string { $dataDir = defined('DATA_DIR') && is_string(DATA_DIR) ? DATA_DIR : (dirname(__DIR__) . '/data/'); $defaultPath = rtrim($dataDir, '/\\') . '/faq.json'; if (!defined('FAQ_FILE') || !is_string(FAQ_FILE) || FAQ_FILE === '') { return $defaultPath; } $configuredPath = FAQ_FILE; $normalizedDataDir = str_replace('\\', '/', rtrim($dataDir, '/\\')) . '/'; $normalizedConfigured = str_replace('\\', '/', $configuredPath); if (strpos($normalizedConfigured, $normalizedDataDir) !== 0) { return $defaultPath; } return $configuredPath; } /** * Get FAQ markdown content from JSON store. */ function getFaqContent(): string { $defaultContent = "# FAQ\n\nHier kann der FAQ-Inhalt im Admin-Bereich bearbeitet werden."; $data = readJsonFile(getFaqFilePath()); if (!is_array($data)) { return $defaultContent; } if (!isset($data['content']) || !is_string($data['content'])) { return $defaultContent; } return $data['content']; } /** * Save FAQ markdown content to JSON store. */ function saveFaqContent(string $markdown): void { writeJsonFile(getFaqFilePath(), ['content' => (string) $markdown]); } /** * Render inline markdown safely (bold + italic). */ function renderFaqInlineMarkdown(string $text): string { $escaped = htmlspecialchars($text, ENT_QUOTES, 'UTF-8'); // Apply markdown after escaping so raw HTML is never executed. $escaped = preg_replace('/\*\*(.+?)\*\*/s', '$1', $escaped); $escaped = preg_replace('/(?$1', $escaped); return $escaped; } /** * Render a minimal safe markdown subset for FAQ content. */ function renderFaqMarkdown(string $markdown): string { $normalized = str_replace(["\r\n", "\r"], "\n", $markdown); $lines = explode("\n", $normalized); $htmlParts = []; $paragraphLines = []; $listType = ''; $flushParagraph = function () use (&$paragraphLines, &$htmlParts): void { if (empty($paragraphLines)) { return; } $renderedLines = []; foreach ($paragraphLines as $line) { $renderedLines[] = renderFaqInlineMarkdown($line); } $htmlParts[] = '
' . implode("
\n", $renderedLines) . '
Keine FAQ-Inhalte vorhanden.
'; } return implode("\n", $htmlParts); } /** * Get all reservations */ function getReservations() { $data = readJsonFile(RESERVATIONS_FILE); return isset($data['reservations']) ? $data['reservations'] : []; } /** * Get reservation by order number */ function getReservationByOrderNumber($orderNumber) { $reservations = getReservations(); foreach ($reservations as $reservation) { if (isset($reservation['id']) && $reservation['id'] === $orderNumber && !isReservationHidden($reservation)) { return $reservation; } } return null; } /** * Get remembered order IDs for the current browser profile. */ function getRememberedOrderIds(): array { return readSignedOrderHistoryCookie(); } /** * Remember a newly created order ID in browser history cookie. */ function rememberOrderId(string $orderId): void { if (!isValidOrderHistoryOrderId($orderId)) { return; } $existingIds = getRememberedOrderIds(); $updatedIds = [$orderId]; foreach ($existingIds as $existingId) { if ($existingId !== $orderId) { $updatedIds[] = $existingId; } } $maxIds = getOrderHistoryMaxIds(); if (count($updatedIds) > $maxIds) { $updatedIds = array_slice($updatedIds, 0, $maxIds); } writeSignedOrderHistoryCookie($updatedIds); } /** * Read and validate signed browser order history cookie. */ function readSignedOrderHistoryCookie(): array { $cookieName = getOrderHistoryCookieName(); if (!isset($_COOKIE[$cookieName]) || !is_string($_COOKIE[$cookieName])) { return []; } $secret = getOrderHistorySecret(); if ($secret === '') { return []; } $cookieValue = $_COOKIE[$cookieName]; $parts = explode('.', $cookieValue, 2); if (count($parts) !== 2) { return []; } $encodedPayload = $parts[0]; $signature = $parts[1]; if ($encodedPayload === '' || $signature === '') { return []; } $expectedSignature = hash_hmac('sha256', $encodedPayload, $secret); if (!hash_equals($expectedSignature, $signature)) { return []; } $payloadJson = base64UrlDecode($encodedPayload); if ($payloadJson === null) { return []; } $payload = json_decode($payloadJson, true); if (!is_array($payload)) { return []; } $version = isset($payload['v']) ? (int)$payload['v'] : 0; if ($version !== 1) { return []; } $ids = isset($payload['ids']) && is_array($payload['ids']) ? $payload['ids'] : []; return sanitizeOrderHistoryIds($ids); } /** * Write signed browser order history cookie. */ function writeSignedOrderHistoryCookie(array $ids): void { if (headers_sent()) { return; } $secret = getOrderHistorySecret(); if ($secret === '') { return; } $sanitizedIds = sanitizeOrderHistoryIds($ids); $payload = [ 'v' => 1, 'ids' => $sanitizedIds, 'iat' => time() ]; $payloadJson = json_encode($payload, JSON_UNESCAPED_UNICODE); if ($payloadJson === false) { return; } $encodedPayload = base64UrlEncode($payloadJson); $signature = hash_hmac('sha256', $encodedPayload, $secret); $cookieValue = $encodedPayload . '.' . $signature; $expires = time() + (getOrderHistoryTtlDays() * 86400); $success = setcookie(getOrderHistoryCookieName(), $cookieValue, [ 'expires' => $expires, 'path' => getOrderHistoryCookiePath(), 'secure' => isHttpsRequest(), 'httponly' => true, 'samesite' => 'Lax' ]); if ($success) { $_COOKIE[getOrderHistoryCookieName()] = $cookieValue; } } /** * Build a safe, deduplicated order history ID list. */ function sanitizeOrderHistoryIds(array $ids): array { $result = []; $seen = []; $maxIds = getOrderHistoryMaxIds(); foreach ($ids as $id) { if (!is_string($id) || !isValidOrderHistoryOrderId($id)) { continue; } if (isset($seen[$id])) { continue; } $seen[$id] = true; $result[] = $id; if (count($result) >= $maxIds) { break; } } return $result; } /** * Check if order ID matches configured pattern. */ function isValidOrderHistoryOrderId($orderId): bool { if (!is_string($orderId) || $orderId === '') { return false; } $prefix = defined('ORDER_PREFIX') ? ORDER_PREFIX : 'ORD'; $pattern = '/^' . preg_quote($prefix, '/') . '-\d{4}-\d+$/'; return preg_match($pattern, $orderId) === 1; } /** * Base64url encode helper. */ function base64UrlEncode(string $data): string { return rtrim(strtr(base64_encode($data), '+/', '-_'), '='); } /** * Base64url decode helper. */ function base64UrlDecode(string $data): ?string { $remainder = strlen($data) % 4; if ($remainder > 0) { $data .= str_repeat('=', 4 - $remainder); } $decoded = base64_decode(strtr($data, '-_', '+/'), true); if ($decoded === false) { return null; } return $decoded; } /** * Get cookie name for order history. */ function getOrderHistoryCookieName(): string { return defined('ORDER_HISTORY_COOKIE_NAME') ? ORDER_HISTORY_COOKIE_NAME : 'order_history'; } /** * Get secret for order history cookie signing. */ function getOrderHistorySecret(): string { return defined('ORDER_HISTORY_COOKIE_SECRET') ? (string) ORDER_HISTORY_COOKIE_SECRET : ''; } /** * Get maximum number of remembered order IDs. */ function getOrderHistoryMaxIds(): int { $maxIds = defined('ORDER_HISTORY_MAX_IDS') ? (int) ORDER_HISTORY_MAX_IDS : 10; return $maxIds > 0 ? $maxIds : 10; } /** * Get order history cookie retention days. */ function getOrderHistoryTtlDays(): int { $ttlDays = defined('ORDER_HISTORY_COOKIE_TTL_DAYS') ? (int) ORDER_HISTORY_COOKIE_TTL_DAYS : 365; return $ttlDays > 0 ? $ttlDays : 365; } /** * Get cookie path from SITE_URL. */ function getOrderHistoryCookiePath(): string { $siteUrl = defined('SITE_URL') ? trim((string) SITE_URL) : ''; if (strpos($siteUrl, '://') !== false) { $parsedPath = parse_url($siteUrl, PHP_URL_PATH); $siteUrl = is_string($parsedPath) ? $parsedPath : ''; } if ($siteUrl === '' || $siteUrl === '/') { return '/'; } if ($siteUrl[0] !== '/') { $siteUrl = '/' . $siteUrl; } return rtrim($siteUrl, '/'); } /** * Detect whether current request uses HTTPS. */ function isHttpsRequest(): bool { if (!empty($_SERVER['HTTPS']) && strtolower((string) $_SERVER['HTTPS']) !== 'off') { return true; } if (isset($_SERVER['SERVER_PORT']) && (int) $_SERVER['SERVER_PORT'] === 443) { return true; } if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && strtolower((string) $_SERVER['HTTP_X_FORWARDED_PROTO']) === 'https') { return true; } return false; } /** * Check if reservation is hidden (spam/deleted) */ function isReservationHidden($reservation) { return isset($reservation['is_hidden']) && $reservation['is_hidden'] === true; } /** * Save reservations */ function saveReservations($reservations) { $data = ['reservations' => $reservations]; writeJsonFile(RESERVATIONS_FILE, $data); } /** * Generate order number * Pattern: PREFIX-YEAR-SEQ */ function generateReservationId() { $reservations = getReservations(); $year = date('Y'); $prefix = defined('ORDER_PREFIX') ? ORDER_PREFIX : 'ORD'; $max = 0; $pattern = '/^' . preg_quote($prefix, '/') . '-\\d{4}-(\\d+)$/'; foreach ($reservations as $reservation) { if (!isset($reservation['id'])) { continue; } if (preg_match($pattern, $reservation['id'], $matches)) { $num = (int)$matches[1]; if ($num > $max) { $max = $num; } } } $next = $max + 1; return sprintf('%s-%s-%03d', $prefix, $year, $next); } /** * Check if product has enough stock * For apparel: checks stock for specific size * For merch: checks general stock */ function checkStock($productId, $quantity, $size = null) { $product = getProductById($productId); if (!$product) { return false; } // For apparel with sizes, check stock per size if ($product['category'] === 'apparel' && !empty($product['sizes']) && $size !== null) { if (!isset($product['stock_by_size']) || !is_array($product['stock_by_size'])) { return false; } $sizeStock = isset($product['stock_by_size'][$size]) ? (int)$product['stock_by_size'][$size] : 0; return $sizeStock >= $quantity; } // For merch or apparel without size-specific stock, use general stock $stock = isset($product['stock']) ? (int)$product['stock'] : 0; return $stock >= $quantity; } /** * Get stock for a product (per size for apparel, general for merch) */ function getStock($product, $size = null) { if ($product['category'] === 'apparel' && !empty($product['sizes']) && $size !== null) { if (isset($product['stock_by_size']) && is_array($product['stock_by_size'])) { return isset($product['stock_by_size'][$size]) ? (int)$product['stock_by_size'][$size] : 0; } } return isset($product['stock']) ? (int)$product['stock'] : 0; } /** * Get total stock for a product (sum of all sizes for apparel) */ function getTotalStock($product) { if ($product['category'] === 'apparel' && isset($product['stock_by_size']) && is_array($product['stock_by_size'])) { return array_sum($product['stock_by_size']); } return isset($product['stock']) ? (int)$product['stock'] : 0; } /** * Allocate stock for reservation */ function allocateStock($productId, $quantity, $size = null) { $products = getProducts(); foreach ($products as &$product) { if ($product['id'] == $productId) { // For apparel with sizes, allocate per size if ($product['category'] === 'apparel' && !empty($product['sizes']) && $size !== null) { if (!isset($product['stock_by_size']) || !is_array($product['stock_by_size'])) { $product['stock_by_size'] = []; } if (!isset($product['stock_by_size'][$size])) { $product['stock_by_size'][$size] = 0; } $product['stock_by_size'][$size] -= $quantity; if ($product['stock_by_size'][$size] < 0) { $product['stock_by_size'][$size] = 0; } } else { // For merch or general stock if (!isset($product['stock'])) { $product['stock'] = 0; } $product['stock'] -= $quantity; if ($product['stock'] < 0) { $product['stock'] = 0; } } break; } } saveProducts($products); } /** * Release stock from reservation */ function releaseStock($productId, $quantity, $size = null) { $products = getProducts(); foreach ($products as &$product) { if ($product['id'] == $productId) { // For apparel with sizes, release per size if ($product['category'] === 'apparel' && !empty($product['sizes']) && $size !== null) { if (!isset($product['stock_by_size']) || !is_array($product['stock_by_size'])) { $product['stock_by_size'] = []; } if (!isset($product['stock_by_size'][$size])) { $product['stock_by_size'][$size] = 0; } $product['stock_by_size'][$size] += $quantity; } else { // For merch or general stock if (!isset($product['stock'])) { $product['stock'] = 0; } $product['stock'] += $quantity; } break; } } saveProducts($products); } /** * Create new reservation */ function createReservation($customerName, $customerEmail, $items) { $reservations = getReservations(); // Validate stock for all items foreach ($items as $item) { $size = isset($item['size']) ? $item['size'] : null; if (!checkStock($item['product_id'], $item['quantity'], $size)) { $product = getProductById($item['product_id']); $productName = $product ? $product['name'] : 'Produkt'; $sizeInfo = $size ? " (Größe: $size)" : ''; return ['success' => false, 'message' => "Nicht genügend Lagerbestand für: $productName$sizeInfo"]; } } // Allocate stock foreach ($items as $item) { $size = isset($item['size']) ? $item['size'] : null; allocateStock($item['product_id'], $item['quantity'], $size); } // Create reservation $now = new DateTime(); $expires = clone $now; $expires->modify('+' . RESERVATION_EXPIRY_DAYS . ' days'); $reservation = [ 'id' => generateReservationId(), 'customer_name' => $customerName, 'customer_email' => $customerEmail, 'items' => $items, 'created' => $now->format('Y-m-d H:i:s'), 'expires' => $expires->format('Y-m-d H:i:s'), 'status' => 'open', 'picked_up' => false, 'type' => 'regular', 'is_hidden' => false ]; $reservations[] = $reservation; saveReservations($reservations); // Send confirmation emails sendReservationEmails($reservation); return ['success' => true, 'reservation' => $reservation]; } /** * Create new backorder reservation */ function createBackorderReservation($customerName, $customerEmail, $items) { $reservations = getReservations(); $now = new DateTime(); $reservation = [ 'id' => generateReservationId(), 'customer_name' => $customerName, 'customer_email' => $customerEmail, 'items' => $items, 'created' => $now->format('Y-m-d H:i:s'), 'expires' => '', 'status' => 'open', 'picked_up' => false, 'type' => 'backorder', 'backorder_status' => 'pending', 'is_hidden' => false ]; $reservations[] = $reservation; saveReservations($reservations); // Send confirmation emails sendBackorderEmails($reservation); return ['success' => true, 'reservation' => $reservation]; } /** * Mark reservation as picked up */ function markReservationPickedUp($reservationId) { $reservations = getReservations(); foreach ($reservations as &$reservation) { if ($reservation['id'] === $reservationId) { if (isReservationHidden($reservation)) { break; } $reservation['picked_up'] = true; $reservation['status'] = 'picked_up'; break; } } saveReservations($reservations); } /** * Mark reservation/backorder as spam/deleted and hide it from non-admin views. * For open regular reservations we release stock, because the order is discarded. */ function markReservationHidden($reservationId) { $reservations = getReservations(); foreach ($reservations as &$reservation) { if ($reservation['id'] !== $reservationId) { continue; } if (isReservationHidden($reservation)) { return ['success' => false, 'message' => 'Bestellung ist bereits als Spam/Gelöscht markiert.']; } $isBackorder = isset($reservation['type']) && $reservation['type'] === 'backorder'; if (!$isBackorder && isset($reservation['status']) && $reservation['status'] === 'open' && empty($reservation['picked_up'])) { foreach ($reservation['items'] as $item) { $size = isset($item['size']) ? $item['size'] : null; releaseStock($item['product_id'], $item['quantity'], $size); } $reservation['status'] = 'deleted'; } $reservation['is_hidden'] = true; $reservation['hidden_at'] = date('Y-m-d H:i:s'); $reservation['hidden_reason'] = 'spam_deleted'; saveReservations($reservations); return ['success' => true]; } return ['success' => false, 'message' => 'Bestellung nicht gefunden.']; } /** * Check and expire old reservations */ function expireOldReservations() { $reservations = getReservations(); $now = new DateTime(); $changed = false; foreach ($reservations as &$reservation) { if (isReservationHidden($reservation)) { continue; } if ($reservation['status'] === 'open' && !$reservation['picked_up']) { if (isset($reservation['type']) && $reservation['type'] === 'backorder') { continue; } if (empty($reservation['expires'])) { continue; } $expires = new DateTime($reservation['expires']); if ($now > $expires) { $reservation['status'] = 'expired'; // Release stock foreach ($reservation['items'] as $item) { $size = isset($item['size']) ? $item['size'] : null; releaseStock($item['product_id'], $item['quantity'], $size); } $changed = true; } } } if ($changed) { saveReservations($reservations); } } /** * Check if all items are in stock */ function canFulfillReservationItems($items) { foreach ($items as $item) { $size = isset($item['size']) ? $item['size'] : null; if (!checkStock($item['product_id'], $item['quantity'], $size)) { return false; } } return true; } /** * Mark backorder as available */ function markBackorderAvailable($reservationId) { $reservations = getReservations(); foreach ($reservations as &$reservation) { if ($reservation['id'] === $reservationId) { if (isReservationHidden($reservation)) { return ['success' => false, 'message' => 'Diese Vorbestellung ist als Spam/Gelöscht markiert.']; } if (!isset($reservation['type']) || $reservation['type'] !== 'backorder') { return ['success' => false, 'message' => 'Diese Vorbestellung wurde bereits in eine Bestellung umgewandelt.']; } if (isset($reservation['backorder_status']) && $reservation['backorder_status'] === 'notified') { return ['success' => false, 'message' => 'Diese Vorbestellung wurde bereits informiert.']; } if (!canFulfillReservationItems($reservation['items'])) { return ['success' => false, 'message' => 'Nicht alle Artikel sind verfügbar.']; } foreach ($reservation['items'] as $item) { $size = isset($item['size']) ? $item['size'] : null; allocateStock($item['product_id'], $item['quantity'], $size); } $now = new DateTime(); $expires = clone $now; $expires->modify('+' . RESERVATION_EXPIRY_DAYS . ' days'); $reservation['type'] = 'regular'; $reservation['status'] = 'open'; $reservation['picked_up'] = false; $reservation['expires'] = $expires->format('Y-m-d H:i:s'); if (isset($reservation['backorder_status'])) { unset($reservation['backorder_status']); } saveReservations($reservations); sendBackorderAvailableEmail($reservation); return ['success' => true, 'reservation' => $reservation]; } } return ['success' => false, 'message' => 'Vorbestellung nicht gefunden.']; } /** * Sanitize input */ function sanitize($input) { return htmlspecialchars(strip_tags(trim($input)), ENT_QUOTES, 'UTF-8'); } /** * Format price */ function formatPrice($price) { return number_format($price, 2, ',', '.') . ' €'; } /** * Format date */ function formatDate($dateString) { $date = new DateTime($dateString); return $date->format('d.m.Y H:i'); } /** * Send email */ function sendEmail($to, $subject, $message, $isHtml = true) { $headers = []; $headers[] = 'From: ' . FROM_NAME . ' <' . FROM_EMAIL . '>'; $headers[] = 'Reply-To: ' . FROM_EMAIL; $headers[] = 'X-Mailer: PHP/' . phpversion(); if ($isHtml) { $headers[] = 'MIME-Version: 1.0'; $headers[] = 'Content-type: text/html; charset=UTF-8'; } return mail($to, $subject, $message, implode("\r\n", $headers)); } /** * Get all admin notification recipients from admin accounts. * Falls back to ADMIN_EMAIL if no account email is configured. */ function getAdminNotificationEmails() { $accounts = getAdminAccounts(); $emails = []; foreach ($accounts as $account) { if (!isset($account['email'])) { continue; } $email = normalizeAdminEmail($account['email']); if (!isValidAdminEmail($email)) { continue; } $emails[] = $email; } if (empty($emails)) { $fallbackEmail = getDefaultAdminEmail(); if ($fallbackEmail !== '') { $emails[] = $fallbackEmail; } } return array_values(array_unique($emails)); } /** * Send admin notifications to all configured recipients. */ function sendAdminNotificationEmails($subject, $message, $isHtml = true) { $emails = getAdminNotificationEmails(); if (empty($emails)) { return false; } $sent = false; foreach ($emails as $email) { $result = sendEmail($email, $subject, $message, $isHtml); if ($result) { $sent = true; } } return $sent; } /** * Send reservation confirmation emails */ function sendReservationEmails($reservation) { $products = getProducts(); // Build items list $itemsHtml = 'Sehr geehrte/r ' . htmlspecialchars($reservation['customer_name']) . ',
vielen Dank für Ihre Reservierung bei ' . SITE_NAME . '.
' . htmlspecialchars($reservation['id']) . '
Bestellnummer: ' . htmlspecialchars($reservation['id']) . '
Erstellt am: ' . formatDate($reservation['created']) . '
Gültig bis: ' . formatDate($reservation['expires']) . '
Wichtig: Bitte nennen Sie diese Bestellnummer bei der Abholung. Die Reservierung ist bis zum ' . formatDate($reservation['expires']) . ' gültig.
Mit freundlichen Grüßen
' . SITE_NAME . '
Eine neue Reservierung wurde erstellt:
' . htmlspecialchars($reservation['id']) . '
Name: ' . htmlspecialchars($reservation['customer_name']) . '
E-Mail: ' . htmlspecialchars($reservation['customer_email']) . '
Bestellnummer: ' . htmlspecialchars($reservation['id']) . '
Erstellt am: ' . formatDate($reservation['created']) . '
Gültig bis: ' . formatDate($reservation['expires']) . '
Sehr geehrte/r ' . htmlspecialchars($reservation['customer_name']) . ',
vielen Dank für Ihre Vorbestellung bei ' . SITE_NAME . '.
' . htmlspecialchars($reservation['id']) . '
Bestellnummer: ' . htmlspecialchars($reservation['id']) . '
Erstellt am: ' . formatDate($reservation['created']) . '
Wir informieren Sie, sobald die komplette Vorbestellung zur Abholung bereit ist.
Mit freundlichen Grüßen
' . SITE_NAME . '
Eine neue Vorbestellung wurde erstellt:
' . htmlspecialchars($reservation['id']) . '
Name: ' . htmlspecialchars($reservation['customer_name']) . '
E-Mail: ' . htmlspecialchars($reservation['customer_email']) . '
Bestellnummer: ' . htmlspecialchars($reservation['id']) . '
Erstellt am: ' . formatDate($reservation['created']) . '
Sehr geehrte/r ' . htmlspecialchars($reservation['customer_name']) . ',
Ihre komplette Vorbestellung ist jetzt zur Abholung bereit.
Bitte nennen Sie die Bestellnummer bei der Abholung.
Mit freundlichen Grüßen
' . SITE_NAME . '