Преглед на файлове

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";
 $pageTitle = "FAQ bearbeiten";
 $message = "";
 $message = "";
 $messageType = "";
 $messageType = "";
+$settings = getSystemSettings();
 
 
 if ($_SERVER['REQUEST_METHOD'] === "POST" && isset($_POST['save_faq'])) {
 if ($_SERVER['REQUEST_METHOD'] === "POST" && isset($_POST['save_faq'])) {
     // Validate CSRF token
     // Validate CSRF token
@@ -19,18 +20,24 @@ if ($_SERVER['REQUEST_METHOD'] === "POST" && isset($_POST['save_faq'])) {
         $messageType = "error";
         $messageType = "error";
     } else {
     } else {
         $content = isset($_POST['content']) ? (string) $_POST['content'] : "";
         $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");
             logAccess("Admin updated FAQ content");
-            $message = "FAQ-Inhalt wurde gespeichert.";
+            $message = "FAQ-Inhalt und Startseitentext wurden gespeichert.";
             $messageType = "success";
             $messageType = "success";
         } else {
         } else {
-            $message = "FAQ-Inhalt konnte nicht gespeichert werden.";
+            $message = "FAQ-Inhalt und/oder Startseitentext konnten nicht gespeichert werden.";
             $messageType = "error";
             $messageType = "error";
         }
         }
     }
     }
 }
 }
 
 
 $faqContent = getFaqContent();
 $faqContent = getFaqContent();
+$settings = getSystemSettings();
+$startpageIntroText = (string) ($settings["startpage_intro_text"] ?? "");
 
 
 $bodyClass = "admin-page";
 $bodyClass = "admin-page";
 include __DIR__ . "/../includes/header.php";
 include __DIR__ . "/../includes/header.php";
@@ -51,7 +58,7 @@ include __DIR__ . "/../includes/header.php";
 
 
 <div class="panel panel-lg">
 <div class="panel panel-lg">
     <p class="mb-2">
     <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>
     </p>
 
 
     <form method="POST">
     <form method="POST">
@@ -62,6 +69,12 @@ include __DIR__ . "/../includes/header.php";
                 $faqContent,
                 $faqContent,
             ); ?></textarea>
             ); ?></textarea>
         </div>
         </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>
         <button type="submit" name="save_faq" class="btn">Speichern</button>
     </form>
     </form>
 </div>
 </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_recipient_email": "bestellungen@mailpit.medowar.de",
         "order_confirmation_required": false,
         "order_confirmation_required": false,
         "order_confirmation_expiry_days": 7,
         "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()
 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)
 function validateCsrfToken($token)
 {
 {
-    if (empty($_SESSION['csrf_token'])) {
+    if (empty($_SESSION["csrf_token"])) {
         return false;
         return false;
     }
     }
-    return hash_equals($_SESSION['csrf_token'], $token);
+    return hash_equals($_SESSION["csrf_token"], $token);
 }
 }
 
 
 function csrfField()
 function csrfField()
@@ -130,7 +130,7 @@ function setFlashMessage($key, $type, $message)
         return;
         return;
     }
     }
 
 
-    $_SESSION['flash_messages'][$key] = [
+    $_SESSION["flash_messages"][$key] = [
         "type" => $type,
         "type" => $type,
         "message" => $message,
         "message" => $message,
     ];
     ];
@@ -143,7 +143,7 @@ function consumeFlashMessage($key)
         return null;
         return null;
     }
     }
 
 
-    $messages = $_SESSION['flash_messages'] ?? [];
+    $messages = $_SESSION["flash_messages"] ?? [];
     if (
     if (
         !is_array($messages) ||
         !is_array($messages) ||
         !isset($messages[$key]) ||
         !isset($messages[$key]) ||
@@ -153,7 +153,7 @@ function consumeFlashMessage($key)
     }
     }
 
 
     $message = $messages[$key];
     $message = $messages[$key];
-    unset($_SESSION['flash_messages'][$key]);
+    unset($_SESSION["flash_messages"][$key]);
 
 
     $type = trim((string) ($message["type"] ?? ""));
     $type = trim((string) ($message["type"] ?? ""));
     $text = trim((string) ($message["message"] ?? ""));
     $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
 function getFaqFilePath(): string
@@ -702,6 +704,24 @@ function saveFaqContent(string $markdown): bool
 function renderFaqInlineMarkdown(string $text): string
 function renderFaqInlineMarkdown(string $text): string
 {
 {
     $escaped = escape($text);
     $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(
     $escaped = preg_replace(
         "/\*\*(.+?)\*\*/s",
         "/\*\*(.+?)\*\*/s",
         '<strong>$1</strong>',
         '<strong>$1</strong>',
@@ -944,6 +964,19 @@ function generateOrganizationIdFromLabel($label, $existingOrganizations = [])
 
 
 function getDefaultSystemSettings()
 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 [
     return [
         "order_recipient_email" => defined("ORDER_RECIPIENT_EMAIL")
         "order_recipient_email" => defined("ORDER_RECIPIENT_EMAIL")
             ? ORDER_RECIPIENT_EMAIL
             ? ORDER_RECIPIENT_EMAIL
@@ -961,6 +994,7 @@ function getDefaultSystemSettings()
         )
         )
             ? (bool) ATTACH_ORDER_PDF_TO_ADMIN_EMAIL
             ? (bool) ATTACH_ORDER_PDF_TO_ADMIN_EMAIL
             : true,
             : true,
+        "startpage_intro_text" => $startpageIntroText,
     ];
     ];
 }
 }
 
 
@@ -986,6 +1020,10 @@ function normalizeSystemSettings($settings)
         $expiryDays = 7;
         $expiryDays = 7;
     }
     }
 
 
+    $startpageIntroText = trim(
+        (string) ($settings["startpage_intro_text"] ?? $defaults["startpage_intro_text"]),
+    );
+
     return [
     return [
         "order_recipient_email" => $recipientEmail,
         "order_recipient_email" => $recipientEmail,
         "order_confirmation_required" => !empty(
         "order_confirmation_required" => !empty(
@@ -995,6 +1033,7 @@ function normalizeSystemSettings($settings)
         "attach_order_pdf_to_admin_email" => !empty(
         "attach_order_pdf_to_admin_email" => !empty(
             $settings["attach_order_pdf_to_admin_email"]
             $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"]);
     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)
 function normalizeOrderItem($item)
 {
 {
     if (!is_array($item)) {
     if (!is_array($item)) {
@@ -1350,7 +1412,7 @@ function buildAbsoluteUrl($path)
     }
     }
 
 
     $scheme = isHttpsRequest() ? "https" : "http";
     $scheme = isHttpsRequest() ? "https" : "http";
-    $host = $_SERVER['HTTP_HOST'] ?? "";
+    $host = $_SERVER["HTTP_HOST"] ?? "";
     if ($host === "") {
     if ($host === "") {
         return $path;
         return $path;
     }
     }
@@ -1361,20 +1423,20 @@ function buildAbsoluteUrl($path)
 function isHttpsRequest(): bool
 function isHttpsRequest(): bool
 {
 {
     if (
     if (
-        !empty($_SERVER['HTTPS']) &&
-        strtolower((string) $_SERVER['HTTPS']) !== "off"
+        !empty($_SERVER["HTTPS"]) &&
+        strtolower((string) $_SERVER["HTTPS"]) !== "off"
     ) {
     ) {
         return true;
         return true;
     }
     }
     if (
     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;
         return true;
     }
     }
     if (
     if (
-        !empty($_SERVER['SERVER_PORT']) &&
-        (int) $_SERVER['SERVER_PORT'] === 443
+        !empty($_SERVER["SERVER_PORT"]) &&
+        (int) $_SERVER["SERVER_PORT"] === 443
     ) {
     ) {
         return true;
         return true;
     }
     }
@@ -1708,7 +1770,7 @@ function formatDate($dateString)
 
 
 function getCart()
 function getCart()
 {
 {
-    $cart = $_SESSION['cart'] ?? [];
+    $cart = $_SESSION["cart"] ?? [];
     if (!is_array($cart)) {
     if (!is_array($cart)) {
         $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 = "")
 function addCartItem($productId, $size = "")
@@ -1786,7 +1848,7 @@ function addCartItem($productId, $size = "")
         }
         }
 
 
         $cart[$index]["size"] = $size;
         $cart[$index]["size"] = $size;
-        $_SESSION['cart'] = array_values($cart);
+        $_SESSION["cart"] = array_values($cart);
 
 
         return [
         return [
             "success" => true,
             "success" => true,
@@ -1801,7 +1863,7 @@ function addCartItem($productId, $size = "")
         "size" => $size,
         "size" => $size,
     ];
     ];
 
 
-    $_SESSION['cart'] = array_values($cart);
+    $_SESSION["cart"] = array_values($cart);
     return [
     return [
         "success" => true,
         "success" => true,
         "status" => "added",
         "status" => "added",
@@ -1814,13 +1876,13 @@ function removeCartItemByIndex($index)
     $cart = getCart();
     $cart = getCart();
     if (isset($cart[$index])) {
     if (isset($cart[$index])) {
         unset($cart[$index]);
         unset($cart[$index]);
-        $_SESSION['cart'] = array_values($cart);
+        $_SESSION["cart"] = array_values($cart);
     }
     }
 }
 }
 
 
 function clearCart()
 function clearCart()
 {
 {
-    $_SESSION['cart'] = [];
+    $_SESSION["cart"] = [];
 }
 }
 
 
 function getCartItemsDetailed()
 function getCartItemsDetailed()
@@ -2028,9 +2090,7 @@ function sendConfirmedOrderAdminNotification($order)
 
 
     $subject = SITE_SERVICE_NAME . ": Neue Bestellung - " . $order["id"];
     $subject = SITE_SERVICE_NAME . ": Neue Bestellung - " . $order["id"];
     $intro =
     $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);
     $message = buildOrderSummaryHtml($order, "Neue PSA-Bestellung", $intro);
 
 
     $attachments = [];
     $attachments = [];
@@ -2341,9 +2401,9 @@ function logError($message, $context = [], $level = "ERROR")
         "level" => $level,
         "level" => $level,
         "message" => $message,
         "message" => $message,
         "context" => $context,
         "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()
         "session_id" => session_id()
             ? substr(session_id(), 0, 8) . "..."
             ? substr(session_id(), 0, 8) . "..."
             : "none",
             : "none",
@@ -2368,9 +2428,9 @@ function logAccess($message, $context = [])
         "timestamp" => date("Y-m-d H:i:s.u"),
         "timestamp" => date("Y-m-d H:i:s.u"),
         "message" => $message,
         "message" => $message,
         "context" => $context,
         "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;
     $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";
 $pageTitle = "Startseite";
 $products = getProducts();
 $products = getProducts();
 $categories = getCategories();
 $categories = getCategories();
+$startpageIntroLines = getStartpageIntroLines();
 
 
 $category = isset($_GET['category'])
 $category = isset($_GET['category'])
     ? normalizeCategoryId($_GET['category'])
     ? normalizeCategoryId($_GET['category'])
@@ -25,7 +26,7 @@ include __DIR__ . "/includes/header.php";
 <h2><?php echo escape(SITE_SERVICE_HEADER); ?></h2>
 <h2><?php echo escape(SITE_SERVICE_HEADER); ?></h2>
 
 
 <div class="disclaimer-box">
 <div class="disclaimer-box">
-    <?php foreach (DISCLAIMER_LINES as $line): ?>
+    <?php foreach ($startpageIntroLines as $line): ?>
         <p><?php echo escape($line); ?></p>
         <p><?php echo escape($line); ?></p>
     <?php endforeach; ?>
     <?php endforeach; ?>
 </div>
 </div>