소스 검색

adding url support for faq
making frontpage text configurable

Medowar 3 주 전
부모
커밋
19b12fb381
5개의 변경된 파일114개의 추가작업 그리고 39개의 파일을 삭제
  1. 17 4
      admin/faq.php
  2. 1 1
      data/faq.json
  3. 2 1
      data/settings.json
  4. 92 32
      includes/functions.php
  5. 2 1
      index.php

+ 17 - 4
admin/faq.php

@@ -11,6 +11,7 @@ if (!isset($_SESSION['admin_logged_in']) || !$_SESSION['admin_logged_in']) {
 $pageTitle = "FAQ bearbeiten";
 $message = "";
 $messageType = "";
+$settings = getSystemSettings();
 
 if ($_SERVER['REQUEST_METHOD'] === "POST" && isset($_POST['save_faq'])) {
     // Validate CSRF token
@@ -19,18 +20,24 @@ if ($_SERVER['REQUEST_METHOD'] === "POST" && isset($_POST['save_faq'])) {
         $messageType = "error";
     } else {
         $content = isset($_POST['content']) ? (string) $_POST['content'] : "";
-        if (saveFaqContent($content)) {
+        $settings["startpage_intro_text"] = isset($_POST['startpage_intro_text'])
+            ? (string) $_POST['startpage_intro_text']
+            : "";
+
+        if (saveFaqContent($content) && saveSystemSettings($settings)) {
             logAccess("Admin updated FAQ content");
-            $message = "FAQ-Inhalt wurde gespeichert.";
+            $message = "FAQ-Inhalt und Startseitentext wurden gespeichert.";
             $messageType = "success";
         } else {
-            $message = "FAQ-Inhalt konnte nicht gespeichert werden.";
+            $message = "FAQ-Inhalt und/oder Startseitentext konnten nicht gespeichert werden.";
             $messageType = "error";
         }
     }
 }
 
 $faqContent = getFaqContent();
+$settings = getSystemSettings();
+$startpageIntroText = (string) ($settings["startpage_intro_text"] ?? "");
 
 $bodyClass = "admin-page";
 include __DIR__ . "/../includes/header.php";
@@ -51,7 +58,7 @@ include __DIR__ . "/../includes/header.php";
 
 <div class="panel panel-lg">
     <p class="mb-2">
-        Unterstützte Markdown-Syntax: <code>#</code>, <code>##</code>, <code>###</code>, <code>**fett**</code>, <code>*kursiv*</code>, Listen mit <code>-</code> oder <code>1.</code>
+        Unterstützte Markdown-Syntax: <code>#</code>, <code>##</code>, <code>###</code>, <code>**fett**</code>, <code>*kursiv*</code>, Listen mit <code>-</code> oder <code>1.</code>, Links mit <code>[Text](https://example.com)</code>
     </p>
 
     <form method="POST">
@@ -62,6 +69,12 @@ include __DIR__ . "/../includes/header.php";
                 $faqContent,
             ); ?></textarea>
         </div>
+        <div class="form-group">
+            <label for="startpage_intro_text">Startseitentext</label>
+            <textarea id="startpage_intro_text" name="startpage_intro_text" rows="6"><?php echo htmlspecialchars(
+                $startpageIntroText,
+            ); ?></textarea>
+        </div>
         <button type="submit" name="save_faq" class="btn">Speichern</button>
     </form>
 </div>

+ 1 - 1
data/faq.json

@@ -1,3 +1,3 @@
 {
-    "content": "TODO\r\n\r\n## Ansprechpartner\r\n\r\nTechnische Probleme: Josef Straßl\r\n"
+    "content": "TODO erweitern\r\n\r\n# Uniformen\r\n\r\n## Kleiderordnung\r\nDie Kleiderordnung des LFV Bayern findest du hier: [Kleiderordnung LFV Bayern e.V.](https://www.lfv-bayern.de/media/filer_public/7b/16/7b164f73-f12f-4f2b-b0a6-36876b14fa9b/210401_auftreten-oeffentlichkeit_2021_akt.pdf)\r\n\r\n## Hose & Schuhe\r\n\r\nHose und Schuhe für die Uniformen sind nicht im Formular verfügbar und muss selbst beschafft werden. Details dazu in der Kleiderordnung des LFV.\r\n\r\n## Bestickung \r\n\r\nDie Bestickung mit Ärmelabzeichen und Rangabzeichen wird durch blablabla\r\nWenn passt, wieder zurück, damit es über die Schneiderei angenäht werden kann, inkl. Funktionsabzeichen\r\n\r\n\r\n# Häufig gestellte Fragen\r\n\r\n**Was passiert, wenn etwas nicht lagernd ist?**\r\nWenn ein Teil deiner Bestellung nicht lagernd ist, dann bekommst du zuerst die Teile, die im Lager vorhanden sind. Die nicht vorhandenen Artikel werden beim Hersteller bestellt und bei Eintreffen an dich ausgeliefert.\r\n\r\n**Wie lange dauert die Bestellung, wenn ein Produkt nicht lagernd ist?**\r\nDazu können wir keine generelle Aussage treffen, in der Regel aber 4-12 Wochen. Um den Status deiner Bestellung zu erfragen, wende dich an xxx@freising.de. Gib dabei bitte das Produkt an und wann du die Bestellung aufgegeben hast.\r\n\r\n**Kann ich nur ein Ärmelabzeichen haben? Ich sammel diese?**\r\nNein.\r\n\r\n\r\n## Ansprechpartner Formular\r\n\r\nFeuerwehrtechnik: Josef Kammerloher\r\nIT: Josef Straßl\r\n"
 }

+ 2 - 1
data/settings.json

@@ -3,6 +3,7 @@
         "order_recipient_email": "bestellungen@mailpit.medowar.de",
         "order_confirmation_required": false,
         "order_confirmation_expiry_days": 7,
-        "attach_order_pdf_to_admin_email": true
+        "attach_order_pdf_to_admin_email": true,
+        "startpage_intro_text": "Dieses System dient der internen Bestellung persönlicher Schutzausrüstung der Stadt Freising.\r\nDie Bearbeitung erfolgt durch die Gerätewarte der Feuerwehr Freising."
     }
 }

+ 92 - 32
includes/functions.php

@@ -69,18 +69,18 @@ function escape($value)
  */
 function generateCsrfToken()
 {
-    if (empty($_SESSION['csrf_token'])) {
-        $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
+    if (empty($_SESSION["csrf_token"])) {
+        $_SESSION["csrf_token"] = bin2hex(random_bytes(32));
     }
-    return $_SESSION['csrf_token'];
+    return $_SESSION["csrf_token"];
 }
 
 function validateCsrfToken($token)
 {
-    if (empty($_SESSION['csrf_token'])) {
+    if (empty($_SESSION["csrf_token"])) {
         return false;
     }
-    return hash_equals($_SESSION['csrf_token'], $token);
+    return hash_equals($_SESSION["csrf_token"], $token);
 }
 
 function csrfField()
@@ -130,7 +130,7 @@ function setFlashMessage($key, $type, $message)
         return;
     }
 
-    $_SESSION['flash_messages'][$key] = [
+    $_SESSION["flash_messages"][$key] = [
         "type" => $type,
         "message" => $message,
     ];
@@ -143,7 +143,7 @@ function consumeFlashMessage($key)
         return null;
     }
 
-    $messages = $_SESSION['flash_messages'] ?? [];
+    $messages = $_SESSION["flash_messages"] ?? [];
     if (
         !is_array($messages) ||
         !isset($messages[$key]) ||
@@ -153,7 +153,7 @@ function consumeFlashMessage($key)
     }
 
     $message = $messages[$key];
-    unset($_SESSION['flash_messages'][$key]);
+    unset($_SESSION["flash_messages"][$key]);
 
     $type = trim((string) ($message["type"] ?? ""));
     $text = trim((string) ($message["message"] ?? ""));
@@ -658,7 +658,9 @@ function saveProducts($products)
         }
     }
 
-    return writeJsonFile(PRODUCTS_FILE, ["products" => array_values($normalized)]);
+    return writeJsonFile(PRODUCTS_FILE, [
+        "products" => array_values($normalized),
+    ]);
 }
 
 function getFaqFilePath(): string
@@ -702,6 +704,24 @@ function saveFaqContent(string $markdown): bool
 function renderFaqInlineMarkdown(string $text): string
 {
     $escaped = escape($text);
+    $escaped = preg_replace_callback(
+        '/\[([^\]]+)\]\(([^)\s]+)\)/',
+        function ($matches) {
+            $label = $matches[1];
+            $url = trim(html_entity_decode($matches[2], ENT_QUOTES, "UTF-8"));
+            if ($url === "") {
+                return $matches[0];
+            }
+
+            $scheme = strtolower((string) parse_url($url, PHP_URL_SCHEME));
+            if (!in_array($scheme, ["http", "https", "mailto"], true)) {
+                return $matches[0];
+            }
+
+            return '<a href="' . escape($url) . '">' . $label . "</a>";
+        },
+        $escaped,
+    );
     $escaped = preg_replace(
         "/\*\*(.+?)\*\*/s",
         '<strong>$1</strong>',
@@ -944,6 +964,19 @@ function generateOrganizationIdFromLabel($label, $existingOrganizations = [])
 
 function getDefaultSystemSettings()
 {
+    $startpageIntroText = "";
+    if (defined("DISCLAIMER_LINES") && is_array(DISCLAIMER_LINES)) {
+        $lines = array_filter(
+            array_map(function ($line) {
+                return trim((string) $line);
+            }, DISCLAIMER_LINES),
+            function ($line) {
+                return $line !== "";
+            },
+        );
+        $startpageIntroText = implode("\n", $lines);
+    }
+
     return [
         "order_recipient_email" => defined("ORDER_RECIPIENT_EMAIL")
             ? ORDER_RECIPIENT_EMAIL
@@ -961,6 +994,7 @@ function getDefaultSystemSettings()
         )
             ? (bool) ATTACH_ORDER_PDF_TO_ADMIN_EMAIL
             : true,
+        "startpage_intro_text" => $startpageIntroText,
     ];
 }
 
@@ -986,6 +1020,10 @@ function normalizeSystemSettings($settings)
         $expiryDays = 7;
     }
 
+    $startpageIntroText = trim(
+        (string) ($settings["startpage_intro_text"] ?? $defaults["startpage_intro_text"]),
+    );
+
     return [
         "order_recipient_email" => $recipientEmail,
         "order_confirmation_required" => !empty(
@@ -995,6 +1033,7 @@ function normalizeSystemSettings($settings)
         "attach_order_pdf_to_admin_email" => !empty(
             $settings["attach_order_pdf_to_admin_email"]
         ),
+        "startpage_intro_text" => $startpageIntroText,
     ];
 }
 
@@ -1035,6 +1074,29 @@ function shouldAttachOrderPdfToAdminEmail()
     return !empty($settings["attach_order_pdf_to_admin_email"]);
 }
 
+function getStartpageIntroLines()
+{
+    $settings = getSystemSettings();
+    $text = trim((string) ($settings["startpage_intro_text"] ?? ""));
+    if ($text === "") {
+        return [];
+    }
+
+    $lines = preg_split('/\R+/', $text) ?: [];
+    $lines = array_values(
+        array_filter(
+            array_map(function ($line) {
+                return trim((string) $line);
+            }, $lines),
+            function ($line) {
+                return $line !== "";
+            },
+        ),
+    );
+
+    return $lines;
+}
+
 function normalizeOrderItem($item)
 {
     if (!is_array($item)) {
@@ -1350,7 +1412,7 @@ function buildAbsoluteUrl($path)
     }
 
     $scheme = isHttpsRequest() ? "https" : "http";
-    $host = $_SERVER['HTTP_HOST'] ?? "";
+    $host = $_SERVER["HTTP_HOST"] ?? "";
     if ($host === "") {
         return $path;
     }
@@ -1361,20 +1423,20 @@ function buildAbsoluteUrl($path)
 function isHttpsRequest(): bool
 {
     if (
-        !empty($_SERVER['HTTPS']) &&
-        strtolower((string) $_SERVER['HTTPS']) !== "off"
+        !empty($_SERVER["HTTPS"]) &&
+        strtolower((string) $_SERVER["HTTPS"]) !== "off"
     ) {
         return true;
     }
     if (
-        !empty($_SERVER['HTTP_X_FORWARDED_PROTO']) &&
-        strtolower((string) $_SERVER['HTTP_X_FORWARDED_PROTO']) === "https"
+        !empty($_SERVER["HTTP_X_FORWARDED_PROTO"]) &&
+        strtolower((string) $_SERVER["HTTP_X_FORWARDED_PROTO"]) === "https"
     ) {
         return true;
     }
     if (
-        !empty($_SERVER['SERVER_PORT']) &&
-        (int) $_SERVER['SERVER_PORT'] === 443
+        !empty($_SERVER["SERVER_PORT"]) &&
+        (int) $_SERVER["SERVER_PORT"] === 443
     ) {
         return true;
     }
@@ -1708,7 +1770,7 @@ function formatDate($dateString)
 
 function getCart()
 {
-    $cart = $_SESSION['cart'] ?? [];
+    $cart = $_SESSION["cart"] ?? [];
     if (!is_array($cart)) {
         $cart = [];
     }
@@ -1742,8 +1804,8 @@ function getCart()
         ];
     }
 
-    $_SESSION['cart'] = array_values($normalized);
-    return $_SESSION['cart'];
+    $_SESSION["cart"] = array_values($normalized);
+    return $_SESSION["cart"];
 }
 
 function addCartItem($productId, $size = "")
@@ -1786,7 +1848,7 @@ function addCartItem($productId, $size = "")
         }
 
         $cart[$index]["size"] = $size;
-        $_SESSION['cart'] = array_values($cart);
+        $_SESSION["cart"] = array_values($cart);
 
         return [
             "success" => true,
@@ -1801,7 +1863,7 @@ function addCartItem($productId, $size = "")
         "size" => $size,
     ];
 
-    $_SESSION['cart'] = array_values($cart);
+    $_SESSION["cart"] = array_values($cart);
     return [
         "success" => true,
         "status" => "added",
@@ -1814,13 +1876,13 @@ function removeCartItemByIndex($index)
     $cart = getCart();
     if (isset($cart[$index])) {
         unset($cart[$index]);
-        $_SESSION['cart'] = array_values($cart);
+        $_SESSION["cart"] = array_values($cart);
     }
 }
 
 function clearCart()
 {
-    $_SESSION['cart'] = [];
+    $_SESSION["cart"] = [];
 }
 
 function getCartItemsDetailed()
@@ -2028,9 +2090,7 @@ function sendConfirmedOrderAdminNotification($order)
 
     $subject = SITE_SERVICE_NAME . ": Neue Bestellung - " . $order["id"];
     $intro =
-        "<p>Eine neue Bestellung im " .
-        escape(SITE_SERVICE_NAME) .
-        " der Stadt Freising wurde freigegeben und muss bearbeitet werden.</p>";
+        "<p>Eine neue PSA-Bestellung wurde freigegeben und muss bearbeitet werden.</p>";
     $message = buildOrderSummaryHtml($order, "Neue PSA-Bestellung", $intro);
 
     $attachments = [];
@@ -2341,9 +2401,9 @@ function logError($message, $context = [], $level = "ERROR")
         "level" => $level,
         "message" => $message,
         "context" => $context,
-        "ip" => $_SERVER['REMOTE_ADDR'] ?? "unknown",
-        "user_agent" => $_SERVER['HTTP_USER_AGENT'] ?? "unknown",
-        "request_uri" => $_SERVER['REQUEST_URI'] ?? "unknown",
+        "ip" => $_SERVER["REMOTE_ADDR"] ?? "unknown",
+        "user_agent" => $_SERVER["HTTP_USER_AGENT"] ?? "unknown",
+        "request_uri" => $_SERVER["REQUEST_URI"] ?? "unknown",
         "session_id" => session_id()
             ? substr(session_id(), 0, 8) . "..."
             : "none",
@@ -2368,9 +2428,9 @@ function logAccess($message, $context = [])
         "timestamp" => date("Y-m-d H:i:s.u"),
         "message" => $message,
         "context" => $context,
-        "ip" => $_SERVER['REMOTE_ADDR'] ?? "unknown",
-        "request_method" => $_SERVER['REQUEST_METHOD'] ?? "unknown",
-        "request_uri" => $_SERVER['REQUEST_URI'] ?? "unknown",
+        "ip" => $_SERVER["REMOTE_ADDR"] ?? "unknown",
+        "request_method" => $_SERVER["REQUEST_METHOD"] ?? "unknown",
+        "request_uri" => $_SERVER["REQUEST_URI"] ?? "unknown",
     ];
 
     $logLine = json_encode($entry, JSON_UNESCAPED_UNICODE) . PHP_EOL;

+ 2 - 1
index.php

@@ -5,6 +5,7 @@ require_once __DIR__ . "/includes/functions.php";
 $pageTitle = "Startseite";
 $products = getProducts();
 $categories = getCategories();
+$startpageIntroLines = getStartpageIntroLines();
 
 $category = isset($_GET['category'])
     ? normalizeCategoryId($_GET['category'])
@@ -25,7 +26,7 @@ include __DIR__ . "/includes/header.php";
 <h2><?php echo escape(SITE_SERVICE_HEADER); ?></h2>
 
 <div class="disclaimer-box">
-    <?php foreach (DISCLAIMER_LINES as $line): ?>
+    <?php foreach ($startpageIntroLines as $line): ?>
         <p><?php echo escape($line); ?></p>
     <?php endforeach; ?>
 </div>