$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) . '

'; $paragraphLines = []; }; $closeList = function () use (&$listType, &$htmlParts): void { if ($listType === '') { return; } $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[] = '