From 3f6866e0750ee06e0b5320d9dda4467ed5b0bbe1 Mon Sep 17 00:00:00 2001 From: Clemens Creutzburg Date: Thu, 20 Aug 2026 23:16:30 +0200 Subject: [PATCH] Menue folgt auch den Schaltern des Mandanten Bisher blendete das Menue nur aus, was der Betreiber gesperrt hatte. Hat der Mandant selbst eine Funktion abgeschaltet - etwa "PayPal anbieten" in den Mandant-Einstellungen - blieb der Menuepunkt stehen und fuehrte auf eine Seite ohne Zweck. app_feature_available() prueft nun beide Ebenen, app_feature_tenant_switch() haelt die Zuordnung Funktion -> Mandanten-Schalter. Navigation und Anleitung nutzen die neue Pruefung; paypal-zuordnung.php sperrt sich beim direkten Aufruf ebenfalls und verweist dabei auf die Mandant-Einstellungen statt auf den Betreiber. Co-Authored-By: Claude Opus 5 --- anleitung.php | 6 ++-- app/features.php | 78 ++++++++++++++++++++++++++++++++++++++++++++ docs/backoffice.md | 11 +++++++ footer.php | 8 +++-- paypal-zuordnung.php | 19 +++++++---- 5 files changed, 110 insertions(+), 12 deletions(-) diff --git a/anleitung.php b/anleitung.php index 90ac5a9..83ea6ed 100644 --- a/anleitung.php +++ b/anleitung.php @@ -17,11 +17,11 @@ $istAdmin = $saasUser !== null && saas_user_has_role(['owner', 'admin'], $saasUs // Einstellungen fuer die Hinweise zu den aktivierten Zahlungswegen. $settings = $saasUser !== null ? saas_fetch_tenant_settings($pdo, (int)$saasUser['tenant_id']) : null; -// Gleiche Logik wie im Menue: was der Betreiber fuer diesen Mandanten -// gesperrt hat, wird hier auch nicht erklaert. +// Gleiche Logik wie im Menue: was der Betreiber gesperrt oder der Mandant +// selbst abgeschaltet hat, wird hier auch nicht erklaert. $anleitungTenantId = $saasUser !== null ? (int)$saasUser['tenant_id'] : 0; $kann = static function (string $feature) use ($pdo, $anleitungTenantId): bool { - return $anleitungTenantId <= 0 || app_feature_enabled($pdo, $anleitungTenantId, $feature); + return $anleitungTenantId <= 0 || app_feature_available($pdo, $anleitungTenantId, $feature); }; include "header.php"; diff --git a/app/features.php b/app/features.php index 5f75c8e..4c72ad6 100644 --- a/app/features.php +++ b/app/features.php @@ -165,3 +165,81 @@ function app_feature_notice_html(string $featureKey): string . ' ist für euren Zugang nicht freigeschaltet.
' . 'Wende dich an den Betreiber der Kaffeeliste, wenn ihr diese Funktion nutzen möchtet.

'; } + +/** + * Zuordnung Betreiber-Funktion -> Schalter in den Mandant-Einstellungen. + * Manche Funktionen kann der Kunde zusaetzlich selbst abschalten; wer PayPal + * gar nicht als Zahlungsweg anbietet, braucht auch keine PayPal-Zuordnung. + * Funktionen ohne Eintrag kennen nur den Betreiber-Schalter. + */ +function app_feature_tenant_switch(string $featureKey): ?string +{ + $switches = [ + 'paypal_inbox' => 'paypal_enabled', + 'self_entry' => 'self_entry_enabled', + ]; + + return $switches[$featureKey] ?? null; +} + +/** + * Ist eine Funktion fuer den Mandanten tatsaechlich nutzbar? Prueft beide + * Ebenen: erst die Freischaltung durch den Betreiber, dann - falls vorhanden - + * den eigenen Schalter des Kunden in den Mandant-Einstellungen. + */ +function app_feature_available(PDO $pdo, int $tenantId, string $featureKey): bool +{ + if (!app_feature_enabled($pdo, $tenantId, $featureKey)) { + return false; + } + + $switch = app_feature_tenant_switch($featureKey); + if ($switch === null || $tenantId <= 0) { + return true; + } + + return app_tenant_switch_enabled($pdo, $tenantId, $switch); +} + +/** + * Einzelnen Schalter aus den Mandant-Einstellungen lesen. Wie bei den + * Betreiber-Funktionen wird das Ergebnis pro Request gehalten, damit die + * Navigation die Einstellungen nicht mehrfach laedt. + */ +function app_tenant_switch_enabled(PDO $pdo, int $tenantId, string $settingKey): bool +{ + static $cache = []; + + if (!array_key_exists($tenantId, $cache)) { + try { + require_once __DIR__ . '/saas-auth.php'; + $cache[$tenantId] = saas_fetch_tenant_settings($pdo, $tenantId) ?? []; + } catch (Throwable $e) { + // Lassen sich die Einstellungen nicht lesen, entscheidet allein die + // Betreiber-Freischaltung - kein stiller Funktionsverlust. + $cache[$tenantId] = []; + } + } + + if (!array_key_exists($settingKey, $cache[$tenantId])) { + return true; + } + + return (int)$cache[$tenantId][$settingKey] === 1; +} + +/** + * Gegenstueck zu app_feature_notice_html() fuer den Fall, dass nicht der + * Betreiber, sondern der Mandant selbst die Funktion abgeschaltet hat. Hier + * hilft kein Kontakt zum Betreiber, sondern der Weg in die eigenen + * Einstellungen. + */ +function app_feature_tenant_notice_html(string $featureKey): string +{ + $label = app_feature_catalog()[$featureKey]['label'] ?? $featureKey; + + return '

' . htmlspecialchars($label, ENT_QUOTES, 'UTF-8') + . ' ist in euren Mandant-Einstellungen deaktiviert.
' + . 'Ein Besitzer oder Administrator kann die Funktion unter ' + . 'Mandant-Einstellungen wieder einschalten.

'; +} diff --git a/docs/backoffice.md b/docs/backoffice.md index 975cee8..050f01e 100644 --- a/docs/backoffice.md +++ b/docs/backoffice.md @@ -94,6 +94,17 @@ Design-Entscheidungen: einem Hinweis statt mit einem Fehler. Die POST-Verarbeitung der betroffenen Seiten hängt an derselben Prüfung, eine gesperrte Funktion lässt sich also auch nicht per Formular-POST auslösen. +- **Abgeschaltet vom Kunden heißt ebenfalls unsichtbar.** Für Funktionen + mit eigenem Schalter in den Mandant-Einstellungen zieht `footer.php` + (und die Anleitung) `app_feature_available()` heran: erst die + Freischaltung durch den Betreiber, dann der Schalter des Kunden. Die + Zuordnung steht in `app_feature_tenant_switch()` – aktuell + `paypal_inbox` → `paypal_enabled` und `self_entry` → + `self_entry_enabled`. Wer PayPal nicht als Zahlungsweg anbietet, sieht + den Menüpunkt „PayPal-Zahlungen" also gar nicht erst; beim direkten + Aufruf erscheint statt des Betreiber-Hinweises + `app_feature_tenant_notice_html()` mit dem Weg zurück in die + Mandant-Einstellungen. - Jede Änderung landet als `backoffice.features_updated` im Audit-Log des betroffenen Mandanten – wie jeder andere Back-Office-Zugriff auch. diff --git a/footer.php b/footer.php index 305f208..72f6f89 100644 --- a/footer.php +++ b/footer.php @@ -31,8 +31,10 @@ if ($saasNavUser !== null && function_exists('saas_list_user_memberships')) { } } -// Vom Betreiber gesperrte Funktionen erscheinen gar nicht erst im Menue - -// ein Menuepunkt, der nur zu einer Absage fuehrt, ist schlechter als keiner. +// Gesperrte Funktionen erscheinen gar nicht erst im Menue - ein Menuepunkt, +// der nur zu einer Absage fuehrt, ist schlechter als keiner. Geprueft werden +// beide Ebenen: die Freischaltung durch den Betreiber und der eigene Schalter +// des Mandanten in den Mandant-Einstellungen (z. B. "PayPal anbieten"). require_once __DIR__ . '/app/features.php'; $saasNavTenantId = $saasNavUser !== null ? (int)$saasNavUser['tenant_id'] : 0; $saasNavFeature = static function (string $key) use ($saasNavTenantId): bool { @@ -40,7 +42,7 @@ $saasNavFeature = static function (string $key) use ($saasNavTenantId): bool { return true; } - return app_feature_enabled(app_db_pdo(), $saasNavTenantId, $key); + return app_feature_available(app_db_pdo(), $saasNavTenantId, $key); }; // Das Back-Office war bisher nur ueber die direkte URL erreichbar. Es ist die diff --git a/paypal-zuordnung.php b/paypal-zuordnung.php index a0d1314..8cac45e 100644 --- a/paypal-zuordnung.php +++ b/paypal-zuordnung.php @@ -16,11 +16,18 @@ if ($saasUser !== null && saas_user_has_role(['owner', 'admin', 'treasurer'], $s $hasAccess = true; } -// Vom Betreiber gesperrte Funktion: $hasAccess faellt zurueck, damit auch die -// POST-Verarbeitung weiter unten nicht mehr laeuft. -$gesperrteFunktion = null; +// Gesperrte Funktion: $hasAccess faellt zurueck, damit auch die +// POST-Verarbeitung weiter unten nicht mehr laeuft. Der Menuepunkt ist in +// diesem Fall ausgeblendet - die Seite bleibt aber ueber die direkte URL +// erreichbar und muss deshalb selbst pruefen. +$sperrHinweis = null; if ($hasAccess && !app_feature_enabled($pdo, $tenantId, 'paypal_inbox')) { - $gesperrteFunktion = 'paypal_inbox'; + // Der Betreiber hat die Funktion fuer diesen Mandanten nicht freigeschaltet. + $sperrHinweis = app_feature_notice_html('paypal_inbox'); + $hasAccess = false; +} elseif ($hasAccess && !app_feature_available($pdo, $tenantId, 'paypal_inbox')) { + // Der Mandant selbst bietet PayPal nicht als Zahlungsweg an. + $sperrHinweis = app_feature_tenant_notice_html('paypal_inbox'); $hasAccess = false; } @@ -130,8 +137,8 @@ include "nav.php";