$type, 'message' => $message, ]; } function consumeFlashMessage($key) { $key = trim((string) $key); if ($key === '') { return null; } $messages = $_SESSION['flash_messages'] ?? []; if (!is_array($messages) || !isset($messages[$key]) || !is_array($messages[$key])) { return null; } $message = $messages[$key]; unset($_SESSION['flash_messages'][$key]); $type = trim((string) ($message['type'] ?? '')); $text = trim((string) ($message['message'] ?? '')); if ($type === '' || $text === '') { return null; } return [ 'type' => $type, 'message' => $text, ]; } function normalizeAdminUsername($username) { return trim((string) $username); } function normalizeAdminDescription($description) { return trim((string) $description); } function normalizeAdminEmail($email) { return strtolower(trim((string) $email)); } function isValidAdminUsername($username) { $username = normalizeAdminUsername($username); return preg_match('/^[A-Za-z0-9][A-Za-z0-9._-]{2,49}$/', $username) === 1; } function isValidAdminDescription($description) { $description = normalizeAdminDescription($description); if ($description === '') { return false; } $length = function_exists('mb_strlen') ? mb_strlen($description) : strlen($description); return $length <= 120; } function isValidAdminEmail($email) { $email = normalizeAdminEmail($email); return $email !== '' && filter_var($email, FILTER_VALIDATE_EMAIL) !== false; } function getDefaultAdminDescription($username) { return 'Admin'; } function getDefaultAdminEmail() { $email = defined('ADMIN_EMAIL') ? normalizeAdminEmail(ADMIN_EMAIL) : ''; return isValidAdminEmail($email) ? $email : ''; } function getAdminAccounts() { $data = readJsonFile(ADMINS_FILE); $records = isset($data['admins']) && is_array($data['admins']) ? $data['admins'] : []; $accounts = []; foreach ($records as $username => $record) { $username = normalizeAdminUsername($username); if ($username === '') { continue; } if (is_string($record)) { $record = [ 'password_hash' => $record, 'description' => getDefaultAdminDescription($username), 'email' => getDefaultAdminEmail(), ]; } if (!is_array($record)) { continue; } $hash = isset($record['password_hash']) ? (string) $record['password_hash'] : ''; if ($hash === '') { continue; } $description = normalizeAdminDescription($record['description'] ?? getDefaultAdminDescription($username)); if (!isValidAdminDescription($description)) { $description = getDefaultAdminDescription($username); } $email = normalizeAdminEmail($record['email'] ?? getDefaultAdminEmail()); if (!isValidAdminEmail($email)) { $email = getDefaultAdminEmail(); } $accounts[$username] = [ 'password_hash' => $hash, 'description' => $description, 'email' => $email, ]; } ksort($accounts); return $accounts; } function getAdminUsers() { $users = []; foreach (getAdminAccounts() as $username => $record) { $users[$username] = $record['password_hash']; } return $users; } function saveAdminAccounts($accounts) { $result = []; foreach ($accounts as $username => $record) { $username = normalizeAdminUsername($username); if ($username === '' || !is_array($record)) { continue; } $hash = isset($record['password_hash']) ? (string) $record['password_hash'] : ''; if ($hash === '') { continue; } $description = normalizeAdminDescription($record['description'] ?? getDefaultAdminDescription($username)); if (!isValidAdminDescription($description)) { $description = getDefaultAdminDescription($username); } $email = normalizeAdminEmail($record['email'] ?? getDefaultAdminEmail()); if (!isValidAdminEmail($email)) { $email = getDefaultAdminEmail(); } $result[$username] = [ 'password_hash' => $hash, 'description' => $description, 'email' => $email, ]; } ksort($result); writeJsonFile(ADMINS_FILE, ['admins' => $result]); } function getDefaultCategories() { return [ ['id' => 'apparel', 'label' => 'Bekleidung'], ]; } 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); return trim((string) $id, '-'); } function normalizeCategoryLabel($label) { return trim((string) $label); } function isValidCategoryLabel($label) { $label = normalizeCategoryLabel($label); if ($label === '') { return false; } $length = function_exists('mb_strlen') ? mb_strlen($label) : strlen($label); return $length <= 80; } 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); } function getCategories() { $data = readJsonFile(CATEGORIES_FILE); return normalizeCategories($data['categories'] ?? []); } function saveCategories($categories) { writeJsonFile(CATEGORIES_FILE, ['categories' => normalizeCategories($categories)]); } function getCategoryById($categoryId) { $categoryId = normalizeCategoryId($categoryId); foreach (getCategories() as $category) { if ($category['id'] === $categoryId) { return $category; } } return null; } function getCategoryLabel($categoryId) { $category = getCategoryById($categoryId); if ($category !== null) { return $category['label']; } return trim((string) $categoryId); } function getCategoryLabels($categoryIds) { $labels = []; foreach (normalizeProductCategoryIds($categoryIds) as $categoryId) { $labels[] = getCategoryLabel($categoryId); } return $labels; } function generateCategoryIdFromLabel($label, $existingCategories = []) { $baseId = normalizeCategoryId($label); if ($baseId === '') { $baseId = 'category'; } $used = []; foreach (normalizeCategories($existingCategories) as $category) { $used[$category['id']] = true; } $candidate = $baseId; $counter = 2; while (isset($used[$candidate])) { $candidate = $baseId . '-' . $counter; $counter++; } return $candidate; } function isCategoryInUse($categoryId) { foreach (getProducts() as $product) { if (productHasCategory($product, $categoryId)) { return true; } } return false; } function normalizeProductCategoryIds($categoryValue) { if (is_array($categoryValue)) { $rawIds = $categoryValue; } elseif ($categoryValue === null || $categoryValue === '') { $rawIds = []; } else { $rawIds = [$categoryValue]; } $normalized = []; foreach ($rawIds as $categoryId) { $categoryId = normalizeCategoryId($categoryId); if ($categoryId !== '') { $normalized[$categoryId] = $categoryId; } } return array_values($normalized); } function getProductCategoryIds($product) { if (isset($product['categories'])) { return normalizeProductCategoryIds($product['categories']); } return normalizeProductCategoryIds($product['category'] ?? []); } function productHasCategory($product, $categoryId) { $categoryId = normalizeCategoryId($categoryId); if ($categoryId === '') { return false; } return in_array($categoryId, getProductCategoryIds($product), true); } 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 !== '') { $normalized[$size] = $size; } } return array_values($normalized); } function productUsesSizeStock($product) { return !empty(getProductSizes($product)); } function normalizeAvailabilityLabels($sizes, $labels) { $result = []; if (!is_array($labels)) { $labels = []; } foreach ($sizes as $size) { $text = trim((string) ($labels[$size] ?? '')); $result[$size] = $text; } return $result; } function getAvailabilityLabel($product, $size) { $labels = isset($product['availability_labels']) && is_array($product['availability_labels']) ? $product['availability_labels'] : []; return trim((string) ($labels[$size] ?? '')); } function normalizeProductRecord($product, $defaultCategoryId = '') { if (!is_array($product)) { return null; } $productId = isset($product['id']) ? (int) $product['id'] : 0; $name = trim((string) ($product['name'] ?? '')); if ($productId <= 0 || $name === '') { return null; } $sizes = getProductSizes($product); if (empty($sizes)) { $sizes = ['Standard']; } $categories = getProductCategoryIds($product); if (empty($categories) && $defaultCategoryId !== '') { $categories = [$defaultCategoryId]; } $availabilityLabels = normalizeAvailabilityLabels( $sizes, isset($product['availability_labels']) && is_array($product['availability_labels']) ? $product['availability_labels'] : [] ); return [ 'id' => $productId, 'name' => $name, 'description' => trim((string) ($product['description'] ?? '')), 'image' => trim((string) ($product['image'] ?? '')), 'categories' => $categories, 'sizes' => implode(',', $sizes), 'availability_labels' => $availabilityLabels, ]; } 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) { $normalized = normalizeProductRecord($product, $defaultCategoryId); if ($normalized !== null) { $products[] = $normalized; } } usort($products, function ($left, $right) { return strcasecmp($left['name'], $right['name']); }); return $products; } function getProductById($id) { $id = (int) $id; foreach (getProducts() as $product) { if ((int) $product['id'] === $id) { return $product; } } return null; } function saveProducts($products) { $categories = getCategories(); $defaultCategoryId = !empty($categories) ? $categories[0]['id'] : 'apparel'; $normalized = []; foreach ($products as $product) { $record = normalizeProductRecord($product, $defaultCategoryId); if ($record !== null) { $normalized[] = $record; } } writeJsonFile(PRODUCTS_FILE, ['products' => array_values($normalized)]); } function getFaqFilePath(): string { $dataDir = defined('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; } function getFaqContent(): string { $defaultContent = "# FAQ\n\nHier kann der FAQ-Inhalt im Admin-Bereich bearbeitet werden."; $data = readJsonFile(getFaqFilePath()); if (!isset($data['content']) || !is_string($data['content'])) { return $defaultContent; } return $data['content']; } function saveFaqContent(string $markdown): void { writeJsonFile(getFaqFilePath(), ['content' => (string) $markdown]); } function renderFaqInlineMarkdown(string $text): string { $escaped = escape($text); $escaped = preg_replace('/\*\*(.+?)\*\*/s', '$1', $escaped); $escaped = preg_replace('/(?$1', $escaped); return $escaped; } 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; } $rendered = []; foreach ($paragraphLines as $line) { $rendered[] = renderFaqInlineMarkdown($line); } $htmlParts[] = '

' . implode("
\n", $rendered) . '

'; $paragraphLines = []; }; $closeList = function () use (&$listType, &$htmlParts): void { if ($listType !== '') { $htmlParts[] = ''; $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[] = '' . renderFaqInlineMarkdown($matches[2]) . ''; continue; } if (preg_match('/^\s*[-*]\s+(.+)$/', $line, $matches) === 1) { $flushParagraph(); if ($listType !== 'ul') { $closeList(); $listType = 'ul'; $htmlParts[] = '