*/ function app_feature_catalog(): array { return [ 'faq' => [ 'label' => 'FAQ-Seite', 'description' => 'Eigene Fragen und Antworten für die Mitglieder des Mandanten.', ], 'self_entry' => [ 'label' => 'Striche selbst eintragen', 'description' => 'Mitglieder tragen ihre Striche im eigenen Dashboard ein (zusätzlich zur Mandant-Einstellung „Web-Striche erlauben").', ], 'pdf_export' => [ 'label' => 'Kaffeeliste als PDF', 'description' => 'Ausdruck der Strichliste zum Aufhängen.', ], 'paypal_inbox' => [ 'label' => 'PayPal-Zahlungseingang', 'description' => 'Automatisches Verbuchen weitergeleiteter PayPal-Mails samt Zuordnungs-Warteschlange.', ], 'csv_import' => [ 'label' => 'CSV-Import', 'description' => 'Einlesen von Einzahlungen und Mitgliedern aus einer Datei.', ], 'mail_dispatch' => [ 'label' => 'Info-Mails und Erinnerungen', 'description' => 'Sammelmails an Mitglieder und automatische Zahlungserinnerungen.', ], 'year_closing' => [ 'label' => 'Jahresabschluss', 'description' => 'Verteilen eines Gesamtbetrags als Guthaben auf alle Mitglieder.', ], 'data_export' => [ 'label' => 'Datenexport', 'description' => 'Herunterladen aller Mandantendaten durch den Kunden selbst.', ], 'branding' => [ 'label' => 'Eigenes Design', 'description' => 'Eigenes Logo und eigene Akzentfarbe in der App-Oberfläche des Mandanten.', ], ]; } /** * Standard fuer jede Funktion, wenn der Betreiber nichts abweichend gesetzt * hat. Alles ist freigeschaltet - ein neuer Kunde soll den vollen * Funktionsumfang bekommen, ohne dass der Betreiber erst etwas einschalten * muss. */ function app_feature_default(string $featureKey): bool { return array_key_exists($featureKey, app_feature_catalog()); } /** * Aktueller Stand aller bekannten Funktionen fuer einen Mandanten. * * @return array */ function app_features_for_tenant(PDO $pdo, int $tenantId): array { $state = []; foreach (array_keys(app_feature_catalog()) as $key) { $state[$key] = app_feature_default($key); } try { $stmt = $pdo->prepare('SELECT feature_key, enabled FROM tenant_features WHERE tenant_id = ?'); $stmt->execute([$tenantId]); foreach ($stmt->fetchAll() as $row) { $key = (string)$row['feature_key']; if (array_key_exists($key, $state)) { $state[$key] = (int)$row['enabled'] === 1; } } } catch (Throwable $e) { // Fehlt die Tabelle (Migration noch nicht eingespielt), bleibt es bei // den Standardwerten - eine nicht ausgerollte Migration darf keine // Funktion sperren. return $state; } return $state; } /** * Einzelne Funktion pruefen. Ergebnis wird pro Request zwischengespeichert, * damit Seiten mit mehreren Pruefungen (Navigation!) nicht je Aufruf erneut * die Datenbank befragen. */ function app_feature_enabled(PDO $pdo, int $tenantId, string $featureKey): bool { static $cache = []; if ($tenantId <= 0) { return app_feature_default($featureKey); } if (!array_key_exists($tenantId, $cache)) { $cache[$tenantId] = app_features_for_tenant($pdo, $tenantId); } return $cache[$tenantId][$featureKey] ?? app_feature_default($featureKey); } /** * Setzt die Schalter eines Mandanten neu. Erwartet die Formulardaten des * Back-Office-Formulars: jede angehakte Funktion steht als Schluessel in * $input['features']. * * @param array $submittedFeatures * @return list Liste der geaenderten Funktionsschluessel */ function app_features_update(PDO $pdo, int $tenantId, array $submittedFeatures): array { $before = app_features_for_tenant($pdo, $tenantId); $changed = []; $stmt = $pdo->prepare( 'INSERT INTO tenant_features (tenant_id, feature_key, enabled) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE enabled = VALUES(enabled)' ); foreach (array_keys(app_feature_catalog()) as $key) { $enabled = !empty($submittedFeatures[$key]); $stmt->execute([$tenantId, $key, $enabled ? 1 : 0]); if (($before[$key] ?? true) !== $enabled) { $changed[] = $key; } } return $changed; } /** * Seitenwaechter: bricht mit einer verstaendlichen Meldung ab, wenn der * Betreiber die Funktion fuer diesen Mandanten abgeschaltet hat. Die Meldung * wird bewusst in die bereits geoeffnete Seite geschrieben, damit sie im * gewohnten Layout erscheint statt als nackter Text. */ function app_feature_notice_html(string $featureKey): string { $label = app_feature_catalog()[$featureKey]['label'] ?? $featureKey; return '

' . htmlspecialchars($label, ENT_QUOTES, 'UTF-8') . ' ist für euren Zugang nicht freigeschaltet.
' . 'Wende dich an den Betreiber der Kaffeeliste, wenn ihr diese Funktion nutzen möchtet.

'; }