Quellcode durchsuchen

Add organization-based CC routing for internal order notifications

Josef Straßl vor 3 Wochen
Ursprung
Commit
0e911f76fc
5 geänderte Dateien mit 173 neuen und 16 gelöschten Zeilen
  1. 11 11
      admin/settings.php
  2. 2 1
      data/settings.json
  3. 25 0
      docs/CONFIG_REFERENCE.md
  4. 18 1
      docs/MAIL_PROCESS.md
  5. 117 3
      includes/functions.php

+ 11 - 11
admin/settings.php

@@ -17,17 +17,17 @@ if ($_SERVER['REQUEST_METHOD'] === "POST" && isset($_POST['save_settings'])) {
         $message = "Ungültiges Token. Bitte versuchen Sie es erneut.";
         $messageType = "error";
     } else {
-        $settings = [
-            "order_recipient_email" => $_POST['order_recipient_email'] ?? "",
-            "order_confirmation_required" => isset(
-                $_POST['order_confirmation_required'],
-            ),
-            "order_confirmation_expiry_days" =>
-                (int) ($_POST['order_confirmation_expiry_days'] ?? 7),
-            "attach_order_pdf_to_admin_email" => isset(
-                $_POST['attach_order_pdf_to_admin_email'],
-            ),
-        ];
+        $settings = getSystemSettings();
+        $settings["order_recipient_email"] = $_POST['order_recipient_email'] ?? "";
+        $settings["order_confirmation_required"] = isset(
+            $_POST['order_confirmation_required'],
+        );
+        $settings["order_confirmation_expiry_days"] = (int) ($_POST[
+            'order_confirmation_expiry_days'
+        ] ?? 7);
+        $settings["attach_order_pdf_to_admin_email"] = isset(
+            $_POST['attach_order_pdf_to_admin_email'],
+        );
 
         if (saveSystemSettings($settings)) {
             logAccess("Admin updated system settings");

+ 2 - 1
data/settings.json

@@ -1,9 +1,10 @@
 {
     "settings": {
         "order_recipient_email": "bestellungen@mailpit.medowar.de",
+        "order_additional_recipients_by_organization": {},
         "order_confirmation_required": false,
         "order_confirmation_expiry_days": 7,
         "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."
     }
-}
+}

+ 25 - 0
docs/CONFIG_REFERENCE.md

@@ -38,3 +38,28 @@
 ## Hinweis
 
 Die Konstanten definieren die Startwerte. Änderbare Betriebsparameter wie Bestätigungspflicht oder interne Empfängeradresse können zusätzlich im Adminbereich unter `Einstellungen` angepasst werden.
+
+## Laufzeit-Einstellungen (`data/settings.json`)
+
+| Schlüssel | Zweck |
+|---|---|
+| `order_recipient_email` | Primäre Empfängeradresse für interne Bestellmails (`To`) |
+| `order_additional_recipients_by_organization` | Zusätzliche Empfänger je `organization_id` (werden als `Cc` versendet, aktuell nur per JSON-Datei gepflegt) |
+| `order_confirmation_required` | Schaltet Besteller-Bestätigung vor interner Weiterleitung ein/aus |
+| `order_confirmation_expiry_days` | Gültigkeit des Bestätigungslinks in Tagen |
+| `attach_order_pdf_to_admin_email` | Hängt PDF an interne Bestellmails an |
+| `startpage_intro_text` | Freitext für den Intro-Block auf der Startseite |
+
+### Beispiel für organisationbasierte Zusatzempfänger
+
+```json
+{
+  "settings": {
+    "order_recipient_email": "bestellungen@example.org",
+    "order_additional_recipients_by_organization": {
+      "jugend": ["jugend@example.org"],
+      "spielmannszug": ["spielmannszug@example.org"]
+    }
+  }
+}
+```

+ 18 - 1
docs/MAIL_PROCESS.md

@@ -20,6 +20,7 @@ Die folgenden Parameter steuern den Mailfluss:
 | Schlüssel | Quelle | Wirkung |
 |---|---|---|
 | `order_recipient_email` | Admin-Einstellungen (`data/settings.json`) | Empfänger für interne Bestellmail |
+| `order_additional_recipients_by_organization` | Admin-Einstellungen (`data/settings.json`) | Zusätzliche interne Empfänger je `organization_id` (als `Cc`, aktuell nur per JSON-Datei gepflegt) |
 | `order_confirmation_required` | Admin-Einstellungen | Erzwingt Bestätigungsmail vor interner Weiterleitung |
 | `order_confirmation_expiry_days` | Admin-Einstellungen | Gültigkeit des Bestätigungslinks |
 | `attach_order_pdf_to_admin_email` | Admin-Einstellungen | Hängt PDF an interne Bestellmail an |
@@ -55,10 +56,25 @@ Die folgenden Parameter steuern den Mailfluss:
 - Trigger:
   - direkt nach `createOrder(...)`, wenn `order_confirmation_required = false`
   - nach erfolgreicher Token-Bestätigung in `confirmOrderByToken(...)`, wenn vorher `pending`
-- Empfänger: `getOrderRecipientEmail()` (normalisiert/validiert)
+- Empfänger:
+  - `To`: `getOrderRecipientEmail()` (normalisiert/validiert)
+  - `Cc`: `order_additional_recipients_by_organization[order.organization_id]` (normalisiert, dedupliziert, ohne `To`-Duplikat)
 - Inhalt: HTML-Bestellzusammenfassung
 - Optional: PDF-Anhang `bestellung-<order-id>.pdf` bei aktivem `attach_order_pdf_to_admin_email`
 
+Beispiel:
+
+```json
+{
+  "settings": {
+    "order_additional_recipients_by_organization": {
+      "jugend": ["jugend@example.org"],
+      "spielmannszug": ["spielmannszug@example.org"]
+    }
+  }
+}
+```
+
 ## Ablauf nach Konfiguration
 
 ### Fall A: Bestätigung erforderlich
@@ -91,6 +107,7 @@ Die folgenden Parameter steuern den Mailfluss:
   - `From: <FROM_NAME> <FROM_EMAIL>`
   - `Reply-To: FROM_EMAIL`
   - `X-Mailer: PHP/<version>`
+  - optional `Cc: <adresse1>, <adresse2>, ...` für organisationsbasierte Zusatzempfänger
 - Bestätigungslink wird über `buildAbsoluteUrl(...)` erzeugt:
   - absolute `SITE_URL` wird direkt verwendet,
   - bei relativer `SITE_URL` wird aus Request-Kontext (`HTTP_HOST`, HTTPS) eine absolute URL gebaut.

+ 117 - 3
includes/functions.php

@@ -464,6 +464,27 @@ function isValidAdminEmail($email)
     return $email !== "" && filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
 }
 
+function normalizeEmailRecipientList($emails)
+{
+    if (!is_array($emails)) {
+        return [];
+    }
+
+    $normalized = [];
+    $seen = [];
+    foreach ($emails as $email) {
+        $email = normalizeAdminEmail($email);
+        if (!isValidAdminEmail($email) || isset($seen[$email])) {
+            continue;
+        }
+
+        $seen[$email] = true;
+        $normalized[] = $email;
+    }
+
+    return $normalized;
+}
+
 function getDefaultAdminDescription($username)
 {
     return "Admin";
@@ -1271,6 +1292,7 @@ function getDefaultSystemSettings()
         "order_recipient_email" => defined("ORDER_RECIPIENT_EMAIL")
             ? ORDER_RECIPIENT_EMAIL
             : getDefaultAdminEmail(),
+        "order_additional_recipients_by_organization" => [],
         "order_confirmation_required" => defined("ORDER_CONFIRMATION_REQUIRED")
             ? (bool) ORDER_CONFIRMATION_REQUIRED
             : false,
@@ -1303,6 +1325,37 @@ function normalizeSystemSettings($settings)
         $recipientEmail = $defaults["order_recipient_email"];
     }
 
+    $additionalRecipientsByOrganization = [];
+    $globalSeenRecipients = [];
+    $configuredRecipientsByOrganization =
+        $settings["order_additional_recipients_by_organization"] ??
+        $defaults["order_additional_recipients_by_organization"];
+    if (is_array($configuredRecipientsByOrganization)) {
+        foreach ($configuredRecipientsByOrganization as $organizationId => $recipients) {
+            $organizationId = normalizeOrganizationId($organizationId);
+            if ($organizationId === "") {
+                continue;
+            }
+
+            $normalizedRecipients = normalizeEmailRecipientList($recipients);
+            $uniqueRecipients = [];
+            foreach ($normalizedRecipients as $recipient) {
+                if (isset($globalSeenRecipients[$recipient])) {
+                    continue;
+                }
+
+                $globalSeenRecipients[$recipient] = true;
+                $uniqueRecipients[] = $recipient;
+            }
+
+            if (empty($uniqueRecipients)) {
+                continue;
+            }
+
+            $additionalRecipientsByOrganization[$organizationId] = $uniqueRecipients;
+        }
+    }
+
     $expiryDays = isset($settings["order_confirmation_expiry_days"])
         ? (int) $settings["order_confirmation_expiry_days"]
         : $defaults["order_confirmation_expiry_days"];
@@ -1316,6 +1369,7 @@ function normalizeSystemSettings($settings)
 
     return [
         "order_recipient_email" => $recipientEmail,
+        "order_additional_recipients_by_organization" => $additionalRecipientsByOrganization,
         "order_confirmation_required" => !empty(
             $settings["order_confirmation_required"]
         ),
@@ -1346,6 +1400,30 @@ function getOrderRecipientEmail()
     return $settings["order_recipient_email"];
 }
 
+function getOrderAdditionalRecipientsByOrganization()
+{
+    $settings = getSystemSettings();
+    $recipientsByOrganization =
+        $settings["order_additional_recipients_by_organization"] ?? [];
+
+    return is_array($recipientsByOrganization) ? $recipientsByOrganization : [];
+}
+
+function getOrderAdditionalRecipientsForOrganization($organizationId)
+{
+    $organizationId = normalizeOrganizationId($organizationId);
+    if ($organizationId === "") {
+        return [];
+    }
+
+    $recipientsByOrganization = getOrderAdditionalRecipientsByOrganization();
+    if (!isset($recipientsByOrganization[$organizationId])) {
+        return [];
+    }
+
+    return normalizeEmailRecipientList($recipientsByOrganization[$organizationId]);
+}
+
 function isOrderConfirmationRequired()
 {
     $settings = getSystemSettings();
@@ -2387,6 +2465,16 @@ function sendConfirmedOrderAdminNotification($order)
     if (!isValidAdminEmail($recipient)) {
         return false;
     }
+    $recipient = normalizeAdminEmail($recipient);
+
+    $ccRecipients = getOrderAdditionalRecipientsForOrganization(
+        $order["organization_id"] ?? "",
+    );
+    $ccRecipients = array_values(
+        array_filter($ccRecipients, function ($ccRecipient) use ($recipient) {
+            return $ccRecipient !== $recipient;
+        }),
+    );
 
     $subject = SITE_SERVICE_NAME . ": Neue Bestellung - " . $order["id"];
     $intro =
@@ -2402,15 +2490,41 @@ function sendConfirmedOrderAdminNotification($order)
         ];
     }
 
-    return sendEmail($recipient, $subject, $message, true, $attachments);
+    return sendEmail(
+        $recipient,
+        $subject,
+        $message,
+        true,
+        $attachments,
+        $ccRecipients,
+    );
 }
 
-function sendEmail($to, $subject, $message, $isHtml = true, $attachments = [])
-{
+function sendEmail(
+    $to,
+    $subject,
+    $message,
+    $isHtml = true,
+    $attachments = [],
+    $cc = [],
+)
+{
+    $to = trim((string) $to);
+    $primaryRecipient = normalizeAdminEmail($to);
+    $ccRecipients = normalizeEmailRecipientList($cc);
+    $ccRecipients = array_values(
+        array_filter($ccRecipients, function ($ccRecipient) use ($primaryRecipient) {
+            return $ccRecipient !== $primaryRecipient;
+        }),
+    );
+
     $headers = [];
     $headers[] = "From: " . FROM_NAME . " <" . FROM_EMAIL . ">";
     $headers[] = "Reply-To: " . FROM_EMAIL;
     $headers[] = "X-Mailer: PHP/" . phpversion();
+    if (!empty($ccRecipients)) {
+        $headers[] = "Cc: " . implode(", ", $ccRecipients);
+    }
 
     if (empty($attachments)) {
         if ($isHtml) {