adasd
All checks were successful
Deploy / deploy (push) Successful in 58s

This commit is contained in:
2026-08-17 00:05:18 +02:00
parent d3ebe6fa68
commit 9d5bad8294
14 changed files with 451 additions and 13 deletions

View File

@@ -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',

View File

@@ -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);
}
}

View File

@@ -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]);
}
}

View File

@@ -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)