| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717 |
- <?php
- require_once __DIR__ . '/../config.php';
- /**
- * Read JSON file and return decoded data
- */
- function readJsonFile($file) {
- if (!file_exists($file)) {
- return [];
- }
- $content = file_get_contents($file);
- if (empty($content)) {
- return [];
- }
- $data = json_decode($content, true);
- return $data ? $data : [];
- }
- /**
- * Write data to JSON file
- */
- function writeJsonFile($file, $data) {
- $dir = dirname($file);
- if (!is_dir($dir)) {
- mkdir($dir, 0755, true);
- }
- file_put_contents($file, json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
- }
- /**
- * Normalize admin username input.
- */
- function normalizeAdminUsername($username) {
- return trim((string)$username);
- }
- /**
- * Normalize admin description input.
- */
- function normalizeAdminDescription($description) {
- return trim((string)$description);
- }
- /**
- * Normalize admin email input.
- */
- function normalizeAdminEmail($email) {
- return strtolower(trim((string)$email));
- }
- /**
- * Validate admin username format.
- */
- function isValidAdminUsername($username) {
- $username = normalizeAdminUsername($username);
- return preg_match('/^[A-Za-z0-9][A-Za-z0-9._-]{2,49}$/', $username) === 1;
- }
- /**
- * Validate admin description.
- */
- function isValidAdminDescription($description) {
- $description = normalizeAdminDescription($description);
- if ($description === '') {
- return false;
- }
- $length = function_exists('mb_strlen') ? mb_strlen($description) : strlen($description);
- return $length <= 120;
- }
- /**
- * Validate admin email.
- */
- function isValidAdminEmail($email) {
- $email = normalizeAdminEmail($email);
- return $email !== '' && filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
- }
- /**
- * Get default description for an admin account.
- */
- function getDefaultAdminDescription($username) {
- return 'Admin';
- }
- /**
- * Get fallback admin email address from config.
- */
- function getDefaultAdminEmail() {
- if (!defined('ADMIN_EMAIL') || !is_string(ADMIN_EMAIL)) {
- return '';
- }
- $fallbackEmail = normalizeAdminEmail(ADMIN_EMAIL);
- if (!isValidAdminEmail($fallbackEmail)) {
- return '';
- }
- return $fallbackEmail;
- }
- /**
- * Get full admin account records from JSON store.
- */
- function getAdminAccounts() {
- $data = readJsonFile(ADMINS_FILE);
- $accounts = [];
- if (isset($data['admins']) && is_array($data['admins'])) {
- foreach ($data['admins'] as $username => $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 default category records.
- */
- function getDefaultCategories() {
- return [
- ['id' => 'apparel', 'label' => 'Bekleidung'],
- ['id' => 'merch', 'label' => 'Merchandise']
- ];
- }
- /**
- * Normalize category id input to a stable slug.
- */
- function normalizeCategoryId($id) {
- $id = trim((string) $id);
- if ($id === '') {
- return '';
- }
- if (function_exists('iconv')) {
- $converted = @iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $id);
- if (is_string($converted) && $converted !== '') {
- $id = $converted;
- }
- }
- $id = strtolower($id);
- $id = preg_replace('/[^a-z0-9]+/', '-', $id);
- $id = trim((string) $id, '-');
- return $id;
- }
- /**
- * Normalize category label input.
- */
- function normalizeCategoryLabel($label) {
- return trim((string) $label);
- }
- /**
- * Validate category label.
- */
- function isValidCategoryLabel($label) {
- $label = normalizeCategoryLabel($label);
- if ($label === '') {
- return false;
- }
- $length = function_exists('mb_strlen') ? mb_strlen($label) : strlen($label);
- return $length <= 80;
- }
- /**
- * Normalize category records from storage.
- */
- function normalizeCategories($categories) {
- $normalized = [];
- if (!is_array($categories)) {
- $categories = [];
- }
- foreach ($categories as $category) {
- if (!is_array($category)) {
- continue;
- }
- $id = normalizeCategoryId($category['id'] ?? '');
- $label = normalizeCategoryLabel($category['label'] ?? '');
- if ($id === '' || !isValidCategoryLabel($label)) {
- continue;
- }
- $normalized[$id] = [
- 'id' => $id,
- 'label' => $label
- ];
- }
- if (empty($normalized)) {
- foreach (getDefaultCategories() as $category) {
- $normalized[$category['id']] = $category;
- }
- }
- uasort($normalized, function ($left, $right) {
- return strcasecmp($left['label'], $right['label']);
- });
- return array_values($normalized);
- }
- /**
- * Get all categories.
- */
- function getCategories() {
- $data = readJsonFile(CATEGORIES_FILE);
- $categories = isset($data['categories']) ? $data['categories'] : [];
- return normalizeCategories($categories);
- }
- /**
- * Get category by id.
- */
- function getCategoryById($categoryId) {
- $categoryId = normalizeCategoryId($categoryId);
- foreach (getCategories() as $category) {
- if ($category['id'] === $categoryId) {
- return $category;
- }
- }
- return null;
- }
- /**
- * Get category label by id with fallback to raw id.
- */
- function getCategoryLabel($categoryId) {
- $category = getCategoryById($categoryId);
- if ($category !== null) {
- return $category['label'];
- }
- $categoryId = trim((string) $categoryId);
- return $categoryId !== '' ? $categoryId : 'Unbekannt';
- }
- /**
- * Get category labels by ids.
- */
- function getCategoryLabels($categoryIds) {
- $labels = [];
- foreach (normalizeProductCategoryIds($categoryIds) as $categoryId) {
- $labels[] = getCategoryLabel($categoryId);
- }
- return $labels;
- }
- /**
- * Save categories.
- */
- function saveCategories($categories) {
- writeJsonFile(CATEGORIES_FILE, ['categories' => normalizeCategories($categories)]);
- }
- /**
- * Generate a unique category id from a label.
- */
- function generateCategoryIdFromLabel($label, $existingCategories = []) {
- $baseId = normalizeCategoryId($label);
- if ($baseId === '') {
- $baseId = 'category';
- }
- $usedIds = [];
- foreach (normalizeCategories($existingCategories) as $category) {
- $usedIds[$category['id']] = true;
- }
- $candidate = $baseId;
- $counter = 2;
- while (isset($usedIds[$candidate])) {
- $candidate = $baseId . '-' . $counter;
- $counter++;
- }
- return $candidate;
- }
- /**
- * Check whether any product uses a category id.
- */
- function isCategoryInUse($categoryId) {
- $categoryId = normalizeCategoryId($categoryId);
- foreach (getProducts() as $product) {
- if (productHasCategory($product, $categoryId)) {
- return true;
- }
- }
- return false;
- }
- /**
- * Normalize product category ids from legacy or current storage.
- */
- function normalizeProductCategoryIds($categoryValue) {
- if (is_array($categoryValue)) {
- $rawCategoryIds = $categoryValue;
- } elseif ($categoryValue === null || $categoryValue === '') {
- $rawCategoryIds = [];
- } else {
- $rawCategoryIds = [$categoryValue];
- }
- $normalized = [];
- foreach ($rawCategoryIds as $categoryId) {
- $categoryId = normalizeCategoryId($categoryId);
- if ($categoryId === '') {
- continue;
- }
- $normalized[$categoryId] = $categoryId;
- }
- return array_values($normalized);
- }
- /**
- * Get normalized category ids for a product.
- */
- function getProductCategoryIds($product) {
- if (isset($product['categories'])) {
- return normalizeProductCategoryIds($product['categories']);
- }
- return normalizeProductCategoryIds($product['category'] ?? []);
- }
- /**
- * Determine whether a product is assigned to a category.
- */
- function productHasCategory($product, $categoryId) {
- $categoryId = normalizeCategoryId($categoryId);
- if ($categoryId === '') {
- return false;
- }
- return in_array($categoryId, getProductCategoryIds($product), true);
- }
- /**
- * Parse product sizes into a normalized array.
- */
- function getProductSizes($product) {
- if (isset($product['sizes']) && is_array($product['sizes'])) {
- $sizes = $product['sizes'];
- } elseif (isset($product['sizes']) && is_string($product['sizes'])) {
- $sizes = explode(',', $product['sizes']);
- } else {
- $sizes = [];
- }
- $normalized = [];
- foreach ($sizes as $size) {
- $size = trim((string) $size);
- if ($size === '') {
- continue;
- }
- $normalized[$size] = $size;
- }
- return array_values($normalized);
- }
- /**
- * Determine whether a product uses size-based stock.
- */
- function productUsesSizeStock($product) {
- return !empty(getProductSizes($product));
- }
- /**
- * Normalize a single product record for backwards compatibility.
- */
- function normalizeProductRecord($product, $defaultCategoryId = '') {
- if (!is_array($product)) {
- return null;
- }
- $product['id'] = isset($product['id']) ? (int) $product['id'] : 0;
- $product['name'] = isset($product['name']) ? trim((string) $product['name']) : '';
- $product['description'] = isset($product['description']) ? trim((string) $product['description']) : '';
- $product['price'] = isset($product['price']) ? (float) $product['price'] : 0.0;
- $product['image'] = isset($product['image']) ? trim((string) $product['image']) : '';
- $categoryIds = getProductCategoryIds($product);
- if (empty($categoryIds) && $defaultCategoryId !== '') {
- $categoryIds = [$defaultCategoryId];
- }
- $product['categories'] = $categoryIds;
- unset($product['category']);
- $sizes = getProductSizes($product);
- $stockBySize = isset($product['stock_by_size']) && is_array($product['stock_by_size']) ? $product['stock_by_size'] : [];
- if (empty($sizes) && array_key_exists('stock', $product)) {
- $sizes = ['Standard'];
- $stockBySize = ['Standard' => (int) $product['stock']];
- }
- if (!empty($sizes)) {
- $normalizedStockBySize = [];
- foreach ($sizes as $size) {
- $normalizedStockBySize[$size] = isset($stockBySize[$size]) ? max(0, (int) $stockBySize[$size]) : 0;
- }
- $product['sizes'] = implode(',', $sizes);
- $product['stock_by_size'] = $normalizedStockBySize;
- unset($product['stock']);
- } else {
- $product['stock'] = isset($product['stock']) ? max(0, (int) $product['stock']) : 0;
- unset($product['stock_by_size']);
- unset($product['sizes']);
- }
- return $product;
- }
- /**
- * Get all products
- */
- function getProducts() {
- $data = readJsonFile(PRODUCTS_FILE);
- $rawProducts = isset($data['products']) && is_array($data['products']) ? $data['products'] : [];
- $categories = getCategories();
- $defaultCategoryId = !empty($categories) ? $categories[0]['id'] : 'apparel';
- $products = [];
- foreach ($rawProducts as $product) {
- $normalizedProduct = normalizeProductRecord($product, $defaultCategoryId);
- if ($normalizedProduct === null) {
- continue;
- }
- $products[] = $normalizedProduct;
- }
- return $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) {
- $categories = getCategories();
- $defaultCategoryId = !empty($categories) ? $categories[0]['id'] : 'apparel';
- $normalizedProducts = [];
- foreach ($products as $product) {
- $normalizedProduct = normalizeProductRecord($product, $defaultCategoryId);
- if ($normalizedProduct === null) {
- continue;
- }
- $normalizedProducts[] = $normalizedProduct;
- }
- $data = ['products' => $normalizedProducts];
- 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', '<strong>$1</strong>', $escaped);
- $escaped = preg_replace('/(?<!\*)\*(?!\s)(.+?)(?<!\s)\*(?!\*)/s', '<em>$1</em>', $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[] = '<p>' . implode("<br>\n", $renderedLines) . '</p>';
- $paragraphLines = [];
- };
- $closeList = function () use (&$listType, &$htmlParts): void {
- if ($listType === '') {
- return;
- }
- $htmlParts[] = '</' . $listType . '>';
- $listType = '';
- };
- foreach ($lines as $line) {
- $line = rtrim($line);
- $trimmed = trim($line);
- if ($trimmed === '') {
- $flushParagraph();
- $closeList();
- continue;
- }
- if (preg_match('/^(#{1,3})\s+(.+)$/', $trimmed, $matches) === 1) {
- $flushParagraph();
- $closeList();
- $level = strlen($matches[1]);
- $htmlParts[] = '<h' . $level . '>' . renderFaqInlineMarkdown($matches[2]) . '</h' . $level . '>';
- continue;
- }
- if (preg_match('/^\s*[-*]\s+(.+)$/', $line, $matches) === 1) {
- $flushParagraph();
- if ($listType !== 'ul') {
- $closeList();
- $listType = 'ul';
- $htmlParts[] = '<ul>';
- }
- $htmlParts[] = '<li>' . renderFaqInlineMarkdown($matches[1]) . '</li>';
- continue;
- }
- if (preg_match('/^\s*\d+\.\s+(.+)$/', $line, $matches) === 1) {
- $flushParagraph();
- if ($listType !== 'ol') {
- $closeList();
- $listType = 'ol';
- $htmlParts[] = '<ol>';
- }
- $htmlParts[] = '<li>' . renderFaqInlineMarkdown($matches[1]) . '</li>';
- continue;
- }
- $closeList();
- $paragraphLines[] = $trimmed;
- }
- $flushParagraph();
- $closeList();
- if (empty($htmlParts)) {
- return '<p>Keine FAQ-Inhalte vorhanden.</p>';
- }
- 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.
- */
- function checkStock($productId, $quantity, $size = null) {
- $product = getProductById($productId);
- if (!$product) {
- return false;
- }
- if (productUsesSizeStock($product) && $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;
- }
- $stock = isset($product['stock']) ? (int) $product['stock'] : 0;
- return $stock >= $quantity;
- }
- /**
- * Get stock for a product.
- */
- function getStock($product, $size = null) {
- if (productUsesSizeStock($product) && $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.
- */
- function getTotalStock($product) {
- if (productUsesSizeStock($product) && 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) {
- if (productUsesSizeStock($product) && $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 {
- 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) {
- if (productUsesSizeStock($product) && $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 {
- 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 = '<ul style="list-style: none; margin: 0; padding: 0;">';
- foreach ($reservation['items'] as $item) {
- $product = getProductById($item['product_id']);
- if ($product) {
- $sizeInfo = '';
- if (isset($item['size']) && !empty($item['size'])) {
- $sizeInfo = ' - Größe: ' . htmlspecialchars($item['size']);
- }
- $itemsHtml .= '<li style="margin: 0 0 0.6rem 0; padding: 0.75rem 0.9rem; background: #28292a; border: 1px solid #4a5263; border-radius: 6px;"><strong style="color: #cac300;">' . htmlspecialchars($product['name']) . '</strong>' . $sizeInfo . ' - Menge: <strong>' . (int) $item['quantity'] . '</strong></li>';
- }
- }
- $itemsHtml .= '</ul>';
-
- // Customer email
- $customerSubject = 'Ihre Reservierung bei ' . SITE_NAME;
- $customerMessage = '
- <html>
- <head>
- <meta charset="UTF-8">
- </head>
- <body style="font-family: Arial, sans-serif; line-height: 1.6; color: #f5f7fb; background: #28292a; padding: 1.5rem;">
- <div style="max-width: 640px; margin: 0 auto; background: #2f3541; padding: 1.5rem 2rem; border-radius: 10px; border: 1px solid #3b4252;">
- <h2 style="color: #cac300; margin-top: 0;">Reservierung bestätigt</h2>
- <p>Sehr geehrte/r ' . htmlspecialchars($reservation['customer_name']) . ',</p>
- <p>vielen Dank für Ihre Reservierung bei ' . SITE_NAME . '.</p>
-
- <div style="background: #28292a; border: 2px solid #cac300; padding: 1.5rem; margin: 1.5rem 0; border-radius: 8px; text-align: center;">
- <h3 style="margin-top: 0; color: #f5f7fb;">Ihre Bestellnummer:</h3>
- <p style="margin: 0; color: #cac300; font-family: monospace;">' . htmlspecialchars($reservation['id']) . '</p>
- </div>
-
- <h3>Reservierungsdetails:</h3>
- <p><strong>Bestellnummer:</strong> ' . htmlspecialchars($reservation['id']) . '</p>
- <p><strong>Erstellt am:</strong> ' . formatDate($reservation['created']) . '</p>
- <p><strong>Gültig bis:</strong> ' . formatDate($reservation['expires']) . '</p>
-
- <h3>Reservierte Artikel:</h3>
- <div style="background: #303745; border: 1px solid #4a5263; border-left: 4px solid #cac300; border-radius: 8px; padding: 1rem;">' . $itemsHtml . '</div>
-
- <p><strong>Wichtig:</strong> Bitte nennen Sie diese Bestellnummer bei der Abholung. Die Reservierung ist bis zum ' . formatDate($reservation['expires']) . ' gültig.</p>
-
- <p>Mit freundlichen Grüßen<br>' . SITE_NAME . '</p>
- </div>
- </body>
- </html>';
-
- sendEmail($reservation['customer_email'], $customerSubject, $customerMessage);
-
- // Admin email
- $adminSubject = 'Neue Reservierung: ' . $reservation['id'];
- $adminMessage = '
- <html>
- <head>
- <meta charset="UTF-8">
- </head>
- <body style="font-family: Arial, sans-serif; line-height: 1.6; color: #f5f7fb; background: #28292a; padding: 1.5rem;">
- <div style="max-width: 640px; margin: 0 auto; background: #2f3541; padding: 1.5rem 2rem; border-radius: 10px; border: 1px solid #3b4252;">
- <h2 style="color: #cac300; margin-top: 0;">Neue Reservierung</h2>
- <p>Eine neue Reservierung wurde erstellt:</p>
-
- <div style="background: #28292a; border: 2px solid #cac300; padding: 1.5rem; margin: 1.5rem 0; border-radius: 8px;">
- <h3 style="margin-top: 0;">Bestellnummer:</h3>
- <p style="margin: 0; color: #cac300; font-family: monospace;">' . htmlspecialchars($reservation['id']) . '</p>
- </div>
-
- <h3>Kundendaten:</h3>
- <p><strong>Name:</strong> ' . htmlspecialchars($reservation['customer_name']) . '</p>
- <p><strong>E-Mail:</strong> ' . htmlspecialchars($reservation['customer_email']) . '</p>
-
- <h3>Reservierungsdetails:</h3>
- <p><strong>Bestellnummer:</strong> ' . htmlspecialchars($reservation['id']) . '</p>
- <p><strong>Erstellt am:</strong> ' . formatDate($reservation['created']) . '</p>
- <p><strong>Gültig bis:</strong> ' . formatDate($reservation['expires']) . '</p>
-
- <h3>Reservierte Artikel:</h3>
- <div style="background: #303745; border: 1px solid #4a5263; border-left: 4px solid #cac300; border-radius: 8px; padding: 1rem;">' . $itemsHtml . '</div>
- </div>
- </body>
- </html>';
-
- sendAdminNotificationEmails($adminSubject, $adminMessage);
- }
- /**
- * Send backorder confirmation emails
- */
- function sendBackorderEmails($reservation) {
- // Build items list
- $itemsHtml = '<ul style="list-style: none; margin: 0; padding: 0;">';
- foreach ($reservation['items'] as $item) {
- $product = getProductById($item['product_id']);
- if ($product) {
- $sizeInfo = '';
- if (isset($item['size']) && !empty($item['size'])) {
- $sizeInfo = ' - Größe: ' . htmlspecialchars($item['size']);
- }
- $itemsHtml .= '<li style="margin: 0 0 0.6rem 0; padding: 0.75rem 0.9rem; background: #28292a; border: 1px solid #4a5263; border-radius: 6px;"><strong style="color: #cac300;">' . htmlspecialchars($product['name']) . '</strong>' . $sizeInfo . ' - Menge: <strong>' . (int) $item['quantity'] . '</strong></li>';
- }
- }
- $itemsHtml .= '</ul>';
-
- // Customer email
- $customerSubject = 'Vorbestellung bei ' . SITE_NAME;
- $customerMessage = '
- <html>
- <head>
- <meta charset="UTF-8">
- </head>
- <body style="font-family: Arial, sans-serif; line-height: 1.6; color: #f5f7fb; background: #28292a; padding: 1.5rem;">
- <div style="max-width: 640px; margin: 0 auto; background: #2f3541; padding: 1.5rem 2rem; border-radius: 10px; border: 1px solid #3b4252;">
- <h2 style="color: #cac300; margin-top: 0;">Vorbestellung bestätigt</h2>
- <p>Sehr geehrte/r ' . htmlspecialchars($reservation['customer_name']) . ',</p>
- <p>vielen Dank für Ihre Vorbestellung bei ' . SITE_NAME . '.</p>
-
- <div style="background: #28292a; border: 2px solid #cac300; padding: 1.5rem; margin: 1.5rem 0; border-radius: 8px; text-align: center;">
- <h3 style="margin-top: 0; color: #f5f7fb;">Ihre Bestellnummer:</h3>
- <p style="margin: 0; color: #cac300; font-family: monospace;">' . htmlspecialchars($reservation['id']) . '</p>
- </div>
-
- <h3>Vorbestellungsdetails:</h3>
- <p><strong>Bestellnummer:</strong> ' . htmlspecialchars($reservation['id']) . '</p>
- <p><strong>Erstellt am:</strong> ' . formatDate($reservation['created']) . '</p>
-
- <h3>Vorbestellte Artikel:</h3>
- <div style="background: #303745; border: 1px solid #4a5263; border-left: 4px solid #cac300; border-radius: 8px; padding: 1rem;">' . $itemsHtml . '</div>
-
- <div style="background: #28292a; border: 2px solid #cf2e2e; padding: 1.5rem; margin: 1.5rem 0; border-radius: 8px;">
- <strong>Hinweis:</strong> Die Lieferzeiten sind nicht bekannt, da die Bestellung in Chargen erfolgt.
- </div>
-
- <p>Wir informieren Sie, sobald die komplette Vorbestellung zur Abholung bereit ist.</p>
-
- <p>Mit freundlichen Grüßen<br>' . SITE_NAME . '</p>
- </div>
- </body>
- </html>';
-
- sendEmail($reservation['customer_email'], $customerSubject, $customerMessage);
-
- // Admin email
- $adminSubject = 'Neue Vorbestellung: ' . $reservation['id'];
- $adminMessage = '
- <html>
- <head>
- <meta charset="UTF-8">
- </head>
- <body style="font-family: Arial, sans-serif; line-height: 1.6; color: #f5f7fb; background: #28292a; padding: 1.5rem;">
- <div style="max-width: 640px; margin: 0 auto; background: #2f3541; padding: 1.5rem 2rem; border-radius: 10px; border: 1px solid #3b4252;">
- <h2 style="color: #cac300; margin-top: 0;">Neue Vorbestellung</h2>
- <p>Eine neue Vorbestellung wurde erstellt:</p>
-
- <div style="background: #28292a; border: 2px solid #cac300; padding: 1.5rem; margin: 1.5rem 0; border-radius: 8px;">
- <h3 style="margin-top: 0;">Bestellnummer:</h3>
- <p style="margin: 0; color: #cac300; font-family: monospace;">' . htmlspecialchars($reservation['id']) . '</p>
- </div>
-
- <h3>Kundendaten:</h3>
- <p><strong>Name:</strong> ' . htmlspecialchars($reservation['customer_name']) . '</p>
- <p><strong>E-Mail:</strong> ' . htmlspecialchars($reservation['customer_email']) . '</p>
-
- <h3>Vorbestellungsdetails:</h3>
- <p><strong>Bestellnummer:</strong> ' . htmlspecialchars($reservation['id']) . '</p>
- <p><strong>Erstellt am:</strong> ' . formatDate($reservation['created']) . '</p>
-
- <h3>Vorbestellte Artikel:</h3>
- <div style="background: #303745; border: 1px solid #4a5263; border-left: 4px solid #cac300; border-radius: 8px; padding: 1rem;">' . $itemsHtml . '</div>
- </div>
- </body>
- </html>';
-
- sendAdminNotificationEmails($adminSubject, $adminMessage);
- }
- /**
- * Send backorder availability email
- */
- function sendBackorderAvailableEmail($reservation) {
- $itemsHtml = '<ul>';
- foreach ($reservation['items'] as $item) {
- $product = getProductById($item['product_id']);
- if ($product) {
- $sizeInfo = '';
- if (isset($item['size']) && !empty($item['size'])) {
- $sizeInfo = ' - Größe: ' . htmlspecialchars($item['size']);
- }
- $itemsHtml .= '<li>' . htmlspecialchars($product['name']) . $sizeInfo . ' - Menge: ' . $item['quantity'] . '</li>';
- }
- }
- $itemsHtml .= '</ul>';
-
- $subject = 'Ihre Vorbestellung ist zur Abholung bereit';
- $message = '
- <html>
- <head>
- <meta charset="UTF-8">
- </head>
- <body style="font-family: Arial, sans-serif; line-height: 1.6; color: #f5f7fb; background: #28292a; padding: 1.5rem;">
- <div style="max-width: 640px; margin: 0 auto; background: #2f3541; padding: 1.5rem 2rem; border-radius: 10px; border: 1px solid #3b4252;">
- <h2 style="color: #cac300; margin-top: 0;">Vorbestellung zur Abholung bereit</h2>
- <p>Sehr geehrte/r ' . htmlspecialchars($reservation['customer_name']) . ',</p>
- <p>Ihre komplette Vorbestellung ist jetzt zur Abholung bereit.</p>
-
- <div style="background: #28292a; border: 2px solid #cac300; padding: 1.5rem; margin: 1.5rem 0; border-radius: 8px; text-align: center;">
- <h3 style="margin-top: 0; color: #f5f7fb;">Ihre Bestellnummer:</h3>
- <h2 style="font-size: 2rem; letter-spacing: 0.2rem; color: #cac300; font-family: monospace;">' . htmlspecialchars($reservation['id']) . '</h2>
- </div>
-
- <h3>Bereitliegende Artikel:</h3>
- ' . $itemsHtml . '
-
- <p>Bitte nennen Sie die Bestellnummer bei der Abholung.</p>
-
- <p>Mit freundlichen Grüßen<br>' . SITE_NAME . '</p>
- </div>
- </body>
- </html>';
-
- sendEmail($reservation['customer_email'], $subject, $message);
- }
|