This commit is contained in:
@@ -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',
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user