diff --git a/Internal/de/PROJECT_CONTEXT.md b/Internal/de/PROJECT_CONTEXT.md index 7c6a3b0..88761d2 100644 --- a/Internal/de/PROJECT_CONTEXT.md +++ b/Internal/de/PROJECT_CONTEXT.md @@ -17,6 +17,7 @@ Papa-Kind-Treff ist eine PHP-basierte Plattform für Väter. Kernbereiche sind l ## Konto / Mitgliederbereich - Profil-Menü aktuell: `Profil`, `Kinder`, `Events`, `Orte & Veranstaltungen`, `Community`, `Einstellungen`, `Abmelden` +- Für `SiteOwner` kommt zusätzlich der Bereich `Profil-Levels` dazu. - Kinder können angelegt, bearbeitet und gelöscht werden. - Wenn ein Geburtsdatum gesetzt ist, wird das Alter automatisch berechnet und später bei Bedarf aktualisiert. - Die Konto-E-Mail wird app-seitig verschlüsselt gespeichert und über einen separaten HMAC-Lookup-Hash adressiert. @@ -43,6 +44,9 @@ Papa-Kind-Treff ist eine PHP-basierte Plattform für Väter. Kernbereiche sind l - Bei `Veranstaltung aus Datenbank` muss das Event-Datum innerhalb des hinterlegten Gültigkeitszeitraums dieser Veranstaltungs-Quelle liegen. - Kategorien werden im Mitgliederbereich per Sucheingabe mit bestehenden Vorschlägen und automatischer Neuanlage gepflegt. - Eigene Events unterstützen optionalen Beschreibungstext, Ja/Nein-Angabe `Mit Kindern`, optionale Platzzahl, optionales Bild sowie eine Location aus Datenbank oder benutzerdefinierte Adresse. +- `Profil-Levels` ist ein Owner-Bereich für Community-Level und die Übersicht der System-Level. +- Community-Level sind jetzt über `system_settings.community_levels_json` editierbar und enthalten aktuell die Rechte `Hilfreiche Antworten hervorheben` und `Bewerbung als Forum-Admin`. +- System-Level bleiben getrennt als Rollen `Forum-Admin`, `Site-Admin` und `SiteOwner`. ## Technischer Rahmen - eigener Front-Controller in `public/index.php` diff --git a/Internal/de/PROJECT_STRUCTURE.md b/Internal/de/PROJECT_STRUCTURE.md index 7902057..9ec49cf 100644 --- a/Internal/de/PROJECT_STRUCTURE.md +++ b/Internal/de/PROJECT_STRUCTURE.md @@ -20,6 +20,7 @@ Stand: 2026-08-10 - Event-Quellen aus Datenbank plus Speicherung der Event-Location-Herkunft: `partials/landing/account/dashboard.php`, `src/App/AccountPages.php`, `Internal/db/schema.sql` - Consent- und rechtliche Texte: `public/page/datenschutz.php` - System-Einstellungen: `src/App/SystemSettings.php` +- Profil-Levels für Owner: `partials/landing/account/dashboard.php`, `src/App/AccountPages.php`, `src/App/Community.php`, `src/App/CommunityAccess.php`, `src/App/SystemSettings.php` - neue Listing-/Ort-Basis: `src/App/ListingCatalog.php` - Kalender-Export und abonnierbarer Feed: `src/App/CalendarSync.php` plus `public/page/calendar/export.php` und `public/page/calendar/feed.php` - UI für getrennte Bereiche `Events` und `Orte & Veranstaltungen` im Mitgliederbereich: `partials/landing/account/dashboard.php` diff --git a/Internal/de/README.md b/Internal/de/README.md index 3e75644..466c524 100644 --- a/Internal/de/README.md +++ b/Internal/de/README.md @@ -17,6 +17,7 @@ Papa-Kind-Treff ist eine PHP-basierte Plattform für Väter mit Fokus auf lokale - standortbasierte Sortierung für die neuesten Events - Mitgliederbereich mit linker Bereichsnavigation - Profil-Menü mit `Profil`, `Kinder`, `Events`, `Orte & Veranstaltungen`, `Community`, `Einstellungen` +- Für `SiteOwner` gibt es zusätzlich den Punkt `Profil-Levels` - Konto-E-Mails sowie sensible Profilfelder werden app-seitig verschlüsselt gespeichert - Kinder können angelegt, bearbeitet und gelöscht werden - Profiladresse mit verschlüsselter Straße/Hausnummer, Adresssuche, Browser-Übernahme und Validierung diff --git a/Internal/en/PROJECT_CONTEXT.md b/Internal/en/PROJECT_CONTEXT.md index 2f3713f..d7cea63 100644 --- a/Internal/en/PROJECT_CONTEXT.md +++ b/Internal/en/PROJECT_CONTEXT.md @@ -17,6 +17,7 @@ Papa-Kind-Treff is a PHP-based platform for fathers. Core areas are local events ## Account / Member Area - Profile menu currently contains `Profile`, `Children`, `Events`, `Places & Events`, `Community`, `Settings`, `Logout`. +- `SiteOwner` additionally gets the area `Profile Levels`. - Children can be created, edited, and deleted. - If a birth date is set, age is calculated automatically and updated later when needed. - Account email is encrypted application-side and addressed through a separate HMAC lookup hash. @@ -43,6 +44,9 @@ Papa-Kind-Treff is a PHP-based platform for fathers. Core areas are local events - When `event from database` is selected, the event date must fall inside that source event's valid date window. - Categories in the member area are handled via a search input with existing suggestions and automatic creation when needed. - Own events support an optional description, a clear `with children` yes/no field, optional capacity, an optional image, and either a database-backed location or a custom address. +- `Profile Levels` is an owner-only area for community levels and the overview of system levels. +- Community levels are now editable through `system_settings.community_levels_json` and currently carry the rights `highlight helpful replies` and `apply for forum admin`. +- System levels remain separate as the roles `Forum Admin`, `Site Admin`, and `SiteOwner`. ## Technical Frame - custom front controller in `public/index.php` diff --git a/Internal/en/PROJECT_STRUCTURE.md b/Internal/en/PROJECT_STRUCTURE.md index 8b78bf0..84a23fe 100644 --- a/Internal/en/PROJECT_STRUCTURE.md +++ b/Internal/en/PROJECT_STRUCTURE.md @@ -20,6 +20,7 @@ Updated: 2026-08-10 - database-backed event location sources plus persisted event location origin: `partials/landing/account/dashboard.php`, `src/App/AccountPages.php`, `Internal/db/schema.sql` - consent and legal texts: `public/page/datenschutz.php` - system settings: `src/App/SystemSettings.php` +- owner profile-level management: `partials/landing/account/dashboard.php`, `src/App/AccountPages.php`, `src/App/Community.php`, `src/App/CommunityAccess.php`, `src/App/SystemSettings.php` - new listing/place base: `src/App/ListingCatalog.php` - calendar export and subscribable feed: `src/App/CalendarSync.php` plus `public/page/calendar/export.php` and `public/page/calendar/feed.php` - separated `Events` and `Places & Events` UI in member area: `partials/landing/account/dashboard.php` diff --git a/Internal/en/README.md b/Internal/en/README.md index 1b52071..f9d304f 100644 --- a/Internal/en/README.md +++ b/Internal/en/README.md @@ -17,6 +17,7 @@ Papa-Kind-Treff is a PHP-based platform for fathers focused on local events, app - location-based ordering for the newest events - member area with left-side section navigation - profile menu with `Profile`, `Children`, `Events`, `Places & Events`, `Community`, `Settings` +- `SiteOwner` additionally gets the member-area item `Profile Levels` - account emails and sensitive profile fields are stored encrypted application-side - children can be created, edited, and deleted - profile address supports encrypted street/house number, address search, browser import, and validation diff --git a/README.md b/README.md index 0b07e14..1b67563 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ Papa-Kind-Treff ist eine PHP-basierte Plattform für Väter mit Fokus auf: - `Termine` und `Treffen` ergänzend in SEO- und Erklärungstexten - Hauptnavigation aktuell: `Home`, `Event Suche`, `Community` - Profil-Menü aktuell: `Profil`, `Kinder`, `Events`, `Orte & Veranstaltungen`, `Community`, `Einstellungen`, `Abmelden` +- Für `SiteOwner` gibt es zusätzlich den Mitgliederbereichspunkt `Profil-Levels` - neue interne Grundstruktur: `listing_places`, `listings`, `listing_occurrences`, `listing_prices`, `listing_benefits` - Seiten-Admins haben zusätzlich eine System-Sektion für globale Wartungs- und Diensteschalter - Im Mitgliederbereich ist `Events` jetzt wieder auf echte eigene Events und Event-Teilnahmen beschränkt diff --git a/partials/landing/account/dashboard.php b/partials/landing/account/dashboard.php index a4fcccf..3174203 100755 --- a/partials/landing/account/dashboard.php +++ b/partials/landing/account/dashboard.php @@ -142,6 +142,9 @@ $sectionLinks = [ 'community' => 'Community', 'settings' => 'Einstellungen', ]; +if (!empty($canManageProfileLevels)) { + $sectionLinks['profile-levels'] = 'Profil-Levels'; +} $showEventTypeChoice = !$editing && (($section ?? 'events') === 'places') && $requestedCreateListingKind === null; $modalSectionTarget = ($editingListing || ($section ?? '') === 'places') ? 'places' : 'events'; if (!empty($canManageSystemSettings)) { @@ -321,6 +324,141 @@ if (!empty($canManageSystemSettings)) { + + +
+
+

Profil-Levels

+
+
+
+ Community-Levels +
+ +
+ + · ab Punkten + +
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+
+ Rechte +
+ $rightDefinition): ?> + + +
+
+
+ +
+
+
+ + + +
+
+ +
+
+ +
+ Neues Community-Level +
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+
+ Rechte +
+ $rightDefinition): ?> + + +
+
+
+ +
+
+
+ +
+ System-Level +
+ +
+ + +
+ Aktuell: + +
+ +
    + +
  • + +
+
+ +
+
+
+
+ +
diff --git a/partials/landing/community/thread.php b/partials/landing/community/thread.php index fecb14b..30de1de 100644 --- a/partials/landing/community/thread.php +++ b/partials/landing/community/thread.php @@ -78,7 +78,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && $community && $access) { } $points = $community->computePoints((int)$userId); if (!$access->canHighlightHelpful($points) && !$access->canModerateForum((int)$userId)) { - throw new \RuntimeException('Dafür ist mindestens der Rang Mentor-Vater erforderlich.'); + throw new \RuntimeException('Dein Community-Level darf hilfreiche Antworten aktuell noch nicht hervorheben.'); } $postId = (int)($_POST['post_id'] ?? 0); $highlight = ((string)($_POST['highlight'] ?? '1')) === '1'; diff --git a/partials/structure/nav.php b/partials/structure/nav.php index 143da49..3f9fa94 100755 --- a/partials/structure/nav.php +++ b/partials/structure/nav.php @@ -5,6 +5,7 @@ $displayName = 'Profil'; $profileInitial = 'P'; $showAdminLink = false; $showSystemLink = false; +$showProfileLevelsLink = false; if ($isLoggedIn) { try { @@ -27,6 +28,7 @@ if ($isLoggedIn) { $communityAccess = new \App\CommunityAccess($pdo, $communityConfig); $showAdminLink = $communityAccess->canModerateForum((int)$_SESSION['user_id']); $showSystemLink = $communityAccess->canManageApplications((int)$_SESSION['user_id']); + $showProfileLevelsLink = $communityAccess->canManageRoles((int)$_SESSION['user_id']); } } } catch (\Throwable) { @@ -34,6 +36,7 @@ if ($isLoggedIn) { $profileInitial = 'P'; $showAdminLink = false; $showSystemLink = false; + $showProfileLevelsLink = false; } } ?> @@ -65,6 +68,9 @@ if ($isLoggedIn) { Orte & Veranstaltungen Community Einstellungen + + Profil-Levels + System @@ -92,6 +98,9 @@ if ($isLoggedIn) { Orte & Veranstaltungen Community Einstellungen + + Profil-Levels + System diff --git a/src/App/AccountPages.php b/src/App/AccountPages.php index b6bdf2c..e199bb8 100755 --- a/src/App/AccountPages.php +++ b/src/App/AccountPages.php @@ -179,10 +179,14 @@ final class AccountPages $calendarSync = $pdo ? new CalendarSync($app) : null; $section = (string)($_GET['section'] ?? 'profile'); $canManageSystemSettings = $communityAccess ? $communityAccess->canManageApplications($userId) : false; + $canManageProfileLevels = $communityAccess ? $communityAccess->canManageRoles($userId) : false; $allowedSections = ['profile', 'children', 'events', 'places', 'community', 'settings']; if ($canManageSystemSettings) { $allowedSections[] = 'system'; } + if ($canManageProfileLevels) { + $allowedSections[] = 'profile-levels'; + } if ($systemSettings) { $systemSettings->ensureSchema(); @@ -641,6 +645,68 @@ final class AccountPages } $communityAccess->submitApplication($userId, (string)($_POST['motivation'] ?? '')); $info = 'Deine Bewerbung wurde eingereicht.'; + } elseif ($action === 'community_level_save') { + if (!$canManageProfileLevels || !$community) { + throw new \RuntimeException('Keine Berechtigung für Profil-Levels.'); + } + + $levelId = trim((string)($_POST['level_id'] ?? '')); + $label = trim((string)($_POST['label'] ?? '')); + $minValueRaw = trim((string)($_POST['min_points'] ?? '0')); + $icon = trim((string)($_POST['icon'] ?? '')); + if ($label === '') { + throw new \RuntimeException('Bitte gib einen Namen für das Community-Level an.'); + } + if ($minValueRaw === '' || !is_numeric($minValueRaw)) { + throw new \RuntimeException('Bitte gib eine gültige Mindestpunktzahl an.'); + } + + $rights = []; + foreach (array_keys(Community::membershipRightDefinitions()) as $rightKey) { + $rights[$rightKey] = ((string)($_POST['rights'][$rightKey] ?? '0')) === '1'; + } + + $levels = $community->listMembershipLevels(); + $updated = false; + foreach ($levels as &$level) { + if ((string)($level['id'] ?? '') !== $levelId) { + continue; + } + $level['label'] = $label; + $level['min'] = (float)$minValueRaw; + $level['icon'] = $icon; + $level['rights'] = $rights; + $updated = true; + break; + } + unset($level); + + if (!$updated) { + $levels[] = [ + 'id' => $levelId !== '' ? $levelId : self::slugifyValue($label . '-' . $minValueRaw . '-' . bin2hex(random_bytes(3))), + 'label' => $label, + 'min' => (float)$minValueRaw, + 'icon' => $icon, + 'rights' => $rights, + ]; + } + + $community->saveMembershipLevels($levels, $userId); + $info = $updated ? 'Community-Level gespeichert.' : 'Community-Level angelegt.'; + } elseif ($action === 'community_level_delete') { + if (!$canManageProfileLevels || !$community) { + throw new \RuntimeException('Keine Berechtigung für Profil-Levels.'); + } + $levelId = trim((string)($_POST['level_id'] ?? '')); + $levels = array_values(array_filter( + $community->listMembershipLevels(), + static fn(array $level): bool => (string)($level['id'] ?? '') !== $levelId + )); + if ($levels === []) { + throw new \RuntimeException('Mindestens ein Community-Level muss erhalten bleiben.'); + } + $community->saveMembershipLevels($levels, $userId); + $info = 'Community-Level gelöscht.'; } } catch (\Throwable $e) { $error = $e->getMessage(); @@ -843,7 +909,10 @@ final class AccountPages $communityPoints = $community ? $community->computePoints($userId) : 0.0; $communityLevel = $community ? $community->membershipLevel($communityPoints) : ['label' => '', 'icon' => '']; + $communityLevelDefinitions = $community ? $community->listMembershipLevels() : []; + $communityLevelRightDefinitions = Community::membershipRightDefinitions(); $communityRoles = $communityAccess ? $communityAccess->getUserRoles($userId) : []; + $systemRoleAssignments = $communityAccess && $canManageProfileLevels ? $communityAccess->listRoleAssignments() : []; $communityApplication = $communityAccess ? $communityAccess->getLatestApplication($userId) : null; $communityCanApply = $communityAccess ? $communityAccess->canApplyForForumAdmin($userId, $communityPoints) : false; $communityRestrictions = $communityAccess ? $communityAccess->getRestrictionState($userId) : [ @@ -851,6 +920,35 @@ final class AccountPages 'reply_blocked' => false, 'reason' => null, ]; + $systemLevelDefinitions = [ + [ + 'key' => 'forum_admin', + 'label' => 'Forum-Admin', + 'rights' => [ + 'Themen und Antworten moderieren', + 'Community-Sperren setzen und aufheben', + 'Meldungen bearbeiten', + ], + ], + [ + 'key' => 'site_admin', + 'label' => 'Site-Admin', + 'rights' => [ + 'Beinhaltet alle Rechte von Forum-Admin', + 'Community-Admin-Bewerbungen bearbeiten', + 'System-Einstellungen verwalten', + ], + ], + [ + 'key' => 'owner', + 'label' => 'SiteOwner', + 'rights' => [ + 'Beinhaltet alle Rechte von Site-Admin', + 'System-Rollen vergeben und entziehen', + 'Profil-Levels verwalten', + ], + ], + ]; $systemSettingsValues = $systemSettings ? $systemSettings->getAll() : []; $listingCatalogStatus = $listingCatalog ? $listingCatalog->status() : ['complete' => false, 'missing' => [], 'tables' => []]; if (!in_array($section, $allowedSections, true)) { @@ -889,11 +987,16 @@ final class AccountPages 'eventLocationOptions', 'communityPoints', 'communityLevel', + 'communityLevelDefinitions', + 'communityLevelRightDefinitions', 'communityRoles', + 'systemRoleAssignments', 'communityApplication', 'communityCanApply', 'communityRestrictions', + 'systemLevelDefinitions', 'canManageSystemSettings', + 'canManageProfileLevels', 'systemSettingsValues', 'listingCatalogStatus', 'avatarBuilder', diff --git a/src/App/Community.php b/src/App/Community.php index a20ff9c..962e072 100755 --- a/src/App/Community.php +++ b/src/App/Community.php @@ -8,6 +8,7 @@ use App\Avatar\AvatarManager; final class Community { private ?array $forumStructure = null; + private ?array $membershipLevelCache = null; private array $tableCache = []; private array $columnCache = []; @@ -418,18 +419,122 @@ final class Community public function membershipLevel(float $points): array { - $levels = $this->config['levels'] ?? []; - usort($levels, fn($a,$b) => ($b['min'] ?? 0) <=> ($a['min'] ?? 0)); - foreach ($levels as $lvl) { - if ($points >= (float)($lvl['min'] ?? 0)) { - return [ - 'label' => $lvl['label'] ?? 'New Daddy', - 'icon' => $lvl['icon'] ?? '', - ]; + $level = $this->membershipLevelMeta($points); + return [ + 'label' => (string)($level['label'] ?? 'Neuer Vater'), + 'icon' => (string)($level['icon'] ?? ''), + ]; + } + + public function membershipLevelMeta(float $points): array + { + $levels = $this->listMembershipLevels(); + $sortedLevels = $levels; + usort($sortedLevels, fn(array $a, array $b): int => ((float)($b['min'] ?? 0.0)) <=> ((float)($a['min'] ?? 0.0))); + foreach ($sortedLevels as $level) { + if ($points >= (float)($level['min'] ?? 0.0)) { + return $level; } } - $fallback = $levels ? $levels[count($levels)-1] : ['label' => 'New Daddy','icon' => '']; - return ['label' => $fallback['label'], 'icon' => $fallback['icon'] ?? '']; + + return $sortedLevels !== [] ? $sortedLevels[array_key_last($sortedLevels)] : $this->normalizeMembershipLevel(['label' => 'Neuer Vater', 'min' => 0], 0); + } + + public function listMembershipLevels(): array + { + if ($this->membershipLevelCache !== null) { + return $this->membershipLevelCache; + } + + $levels = $this->config['levels'] ?? []; + try { + $settings = new SystemSettings($this->pdo); + $rawLevels = trim((string)$settings->get('community_levels_json', '')); + if ($rawLevels !== '') { + $decodedLevels = json_decode($rawLevels, true); + if (is_array($decodedLevels)) { + $levels = $decodedLevels; + } + } + } catch (\Throwable) { + } + + $normalizedLevels = []; + foreach ($levels as $index => $level) { + if (!is_array($level)) { + continue; + } + $normalizedLevels[] = $this->normalizeMembershipLevel($level, (int)$index); + } + + if ($normalizedLevels === []) { + $normalizedLevels[] = $this->normalizeMembershipLevel(['label' => 'Neuer Vater', 'min' => 0], 0); + } + + usort($normalizedLevels, function (array $left, array $right): int { + $minCompare = ((float)($left['min'] ?? 0.0)) <=> ((float)($right['min'] ?? 0.0)); + if ($minCompare !== 0) { + return $minCompare; + } + + return strcmp((string)($left['label'] ?? ''), (string)($right['label'] ?? '')); + }); + + return $this->membershipLevelCache = array_values($normalizedLevels); + } + + public function saveMembershipLevels(array $levels, ?int $updatedBy = null): void + { + $normalizedLevels = []; + foreach ($levels as $index => $level) { + if (!is_array($level)) { + continue; + } + $normalizedLevels[] = $this->normalizeMembershipLevel($level, (int)$index); + } + + if ($normalizedLevels === []) { + throw new \RuntimeException('Mindestens ein Community-Level muss vorhanden sein.'); + } + + $seenIds = []; + foreach ($normalizedLevels as $level) { + $levelId = (string)($level['id'] ?? ''); + if ($levelId === '' || isset($seenIds[$levelId])) { + throw new \RuntimeException('Jedes Community-Level braucht eine eindeutige Kennung.'); + } + $seenIds[$levelId] = true; + } + + usort($normalizedLevels, function (array $left, array $right): int { + $minCompare = ((float)($left['min'] ?? 0.0)) <=> ((float)($right['min'] ?? 0.0)); + if ($minCompare !== 0) { + return $minCompare; + } + + return strcmp((string)($left['label'] ?? ''), (string)($right['label'] ?? '')); + }); + + $settings = new SystemSettings($this->pdo); + $settings->ensureSchema(); + $settings->set( + 'community_levels_json', + json_encode($normalizedLevels, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT) ?: '[]', + $updatedBy + ); + $this->membershipLevelCache = array_values($normalizedLevels); + } + + public static function membershipRightDefinitions(): array + { + return [ + 'can_highlight_helpful' => [ + 'label' => 'Hilfreiche Antworten hervorheben', + ], + 'can_apply_for_forum_admin' => [ + 'label' => 'Bewerbung als Forum-Admin', + ], + ]; } private function forumStructure(): array @@ -554,4 +659,63 @@ final class Community return implode(', ', $columns); } + + private function normalizeMembershipLevel(array $level, int $index): array + { + $label = trim((string)($level['label'] ?? '')); + if ($label === '') { + $label = 'Level ' . ($index + 1); + } + + $minValue = is_numeric($level['min'] ?? null) ? (float)$level['min'] : 0.0; + if ($minValue < 0) { + $minValue = 0.0; + } + + $levelId = trim((string)($level['id'] ?? '')); + if ($levelId === '') { + $levelId = $this->slugifyLevelIdentifier($label . '-' . (string)$minValue . '-' . (string)$index); + } + + $rightsInput = is_array($level['rights'] ?? null) ? $level['rights'] : []; + $rights = []; + foreach (array_keys(self::membershipRightDefinitions()) as $rightKey) { + if (array_key_exists($rightKey, $rightsInput)) { + $rights[$rightKey] = $this->toBool($rightsInput[$rightKey]); + continue; + } + + $rights[$rightKey] = match ($rightKey) { + 'can_highlight_helpful' => $minValue >= 500.0, + 'can_apply_for_forum_admin' => $minValue >= 750.0, + default => false, + }; + } + + return [ + 'id' => $levelId, + 'min' => $minValue, + 'label' => $label, + 'icon' => trim((string)($level['icon'] ?? '')), + 'rights' => $rights, + ]; + } + + private function slugifyLevelIdentifier(string $value): string + { + $value = mb_strtolower(trim($value)); + $value = strtr($value, ['ä' => 'ae', 'ö' => 'oe', 'ü' => 'ue', 'ß' => 'ss']); + $value = preg_replace('/[^a-z0-9]+/u', '-', $value) ?: ''; + $value = trim($value, '-'); + return $value !== '' ? $value : 'community-level'; + } + + private function toBool(mixed $value): bool + { + if (is_bool($value)) { + return $value; + } + + return in_array((string)$value, ['1', 'true', 'yes', 'on'], true); + } } diff --git a/src/App/CommunityAccess.php b/src/App/CommunityAccess.php index 81fd1d4..a319d25 100644 --- a/src/App/CommunityAccess.php +++ b/src/App/CommunityAccess.php @@ -119,7 +119,10 @@ final class CommunityAccess public function canApplyForForumAdmin(int $userId, float $points): bool { - if ($userId <= 0 || $points < 750.0 || !$this->hasTable('community_admin_applications')) { + if ($userId <= 0 || !$this->hasTable('community_admin_applications')) { + return false; + } + if (!$this->resolveLevelRight($points, 'can_apply_for_forum_admin')) { return false; } @@ -450,7 +453,7 @@ final class CommunityAccess public function canHighlightHelpful(float $points): bool { - return $points >= 500.0; + return $this->resolveLevelRight($points, 'can_highlight_helpful'); } public function supportsApplications(): bool @@ -492,4 +495,11 @@ final class CommunityAccess return $this->tableCache[$table] = false; } } + + private function resolveLevelRight(float $points, string $rightKey): bool + { + $community = new Community($this->pdo, $this->communityConfig); + $level = $community->membershipLevelMeta($points); + return !empty($level['rights'][$rightKey]); + } } diff --git a/src/App/SystemSettings.php b/src/App/SystemSettings.php index a1d59e6..c2e585f 100644 --- a/src/App/SystemSettings.php +++ b/src/App/SystemSettings.php @@ -13,6 +13,7 @@ final class SystemSettings 'site_maintenance_mode' => '0', 'site_maintenance_message' => 'Papa-Kind-Treff ist gerade kurz in Wartung. Bitte versuche es in Kürze erneut.', 'place_data_provider' => 'osm', + 'community_levels_json' => '', ]; public function __construct(private \PDO $pdo)