From b9026c7808ebb7c73f8fa9dbb0a4f50b40d27e88 Mon Sep 17 00:00:00 2001 From: Lars Gebhardt-Kusche Date: Sun, 19 Jul 2026 22:08:55 +0200 Subject: [PATCH] asddad --- config/community_forums.php | 8 +- partials/landing/account/dashboard.php | 22 ++++ public/page/staging-users.php | 102 ++++++++++++++++ src/App/AccountPages.php | 13 ++- src/App/Community.php | 33 ++++-- src/App/CommunityMigration.php | 154 +++++++++++++++++++++++++ 6 files changed, 319 insertions(+), 13 deletions(-) create mode 100644 public/page/staging-users.php create mode 100644 src/App/CommunityMigration.php diff --git a/config/community_forums.php b/config/community_forums.php index 05e52c3..2920f43 100644 --- a/config/community_forums.php +++ b/config/community_forums.php @@ -43,10 +43,10 @@ return [ 'icon' => '🎒', ], [ - 'slug' => 'freizeitideen', - 'title' => 'Freizeitideen', - 'description' => 'Ideen für Ausflüge, Beschäftigung, schlechtes Wetter und spontane freie Nachmittage.', - 'icon' => '🌤️', + 'slug' => 'sonstiges', + 'title' => 'Sonstiges', + 'description' => 'Alles, was thematisch dazugehört, aber in keinen der anderen Bereiche sauber hineinpasst.', + 'icon' => '🧩', ], ], ], diff --git a/partials/landing/account/dashboard.php b/partials/landing/account/dashboard.php index 50bb54e..cd97f99 100755 --- a/partials/landing/account/dashboard.php +++ b/partials/landing/account/dashboard.php @@ -30,6 +30,28 @@ $allowNoKidsChecked = $editEvent ? ((int)$editEvent['allow_kids'] === 0) : false
+ +
+
Admin
+

Community-Migration

+ +
    +
  • Status:
  • +
  • Fehlende Bausteine:
  • +
+ +

Migrationsstatus konnte nicht ermittelt werden.

+ +
+ + +
+

+ Führt die für Rollen, Bewerbungen, Meldungen, Bewertungen und Forenstruktur benötigten Datenbank-Erweiterungen aus. +

+
+ +
Community

Dein Community-Status

diff --git a/public/page/staging-users.php b/public/page/staging-users.php new file mode 100644 index 0000000..cf3dfef --- /dev/null +++ b/public/page/staging-users.php @@ -0,0 +1,102 @@ +pdo(); +$users = []; +$error = ''; + +try { + if (!$pdo) { + throw new RuntimeException('Keine Datenbankverbindung verfügbar.'); + } + + $hasRolesTable = false; + try { + $stmt = $pdo->query("SELECT 1 FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'user_roles' LIMIT 1"); + $hasRolesTable = (bool)$stmt->fetchColumn(); + } catch (Throwable) { + $hasRolesTable = false; + } + + $sql = ' + SELECT u.id, + u.email, + u.status, + u.created_at, + COALESCE(p.display_name, "") AS display_name + FROM users u + LEFT JOIN user_profiles p ON p.user_id = u.id + ORDER BY u.id ASC + '; + $stmt = $pdo->query($sql); + $users = $stmt->fetchAll(PDO::FETCH_ASSOC) ?: []; + + if ($hasRolesTable && $users) { + $roleStmt = $pdo->query('SELECT user_id, role FROM user_roles ORDER BY user_id ASC, role ASC'); + $rolesByUser = []; + foreach ($roleStmt->fetchAll(PDO::FETCH_ASSOC) ?: [] as $row) { + $rolesByUser[(int)$row['user_id']][] = (string)$row['role']; + } + foreach ($users as &$user) { + $user['roles'] = $rolesByUser[(int)$user['id']] ?? []; + } + unset($user); + } +} catch (Throwable $e) { + $error = $e->getMessage(); +} +?> +
+
+
+

Staging: Registrierte Benutzer

+
+ +
+ +
+ Fehler: +
+ + +

Diese Seite ist nur im Staging verfügbar.

+ + +

Keine Benutzer gefunden.

+ +
+ + + + + + + + + + + + + + + + + + + + + + + +
IDAnzeigenameE-MailStatusRegistriertRollen
+
+ +
+
+
diff --git a/src/App/AccountPages.php b/src/App/AccountPages.php index 589c588..b623613 100755 --- a/src/App/AccountPages.php +++ b/src/App/AccountPages.php @@ -157,6 +157,7 @@ final class AccountPages $communityConfig = file_exists($communityCfg) ? require $communityCfg : []; $community = $pdo ? new Community($pdo, $communityConfig) : null; $communityAccess = $pdo ? new CommunityAccess($pdo, $communityConfig) : null; + $communityMigration = $pdo ? new CommunityMigration($pdo) : null; if ($_SERVER['REQUEST_METHOD'] === 'POST') { $action = $_POST['action'] ?? ''; @@ -296,6 +297,12 @@ final class AccountPages } $communityAccess->submitApplication($userId, (string)($_POST['motivation'] ?? '')); $info = 'Deine Bewerbung wurde eingereicht.'; + } elseif ($action === 'community_migrate') { + if (!$communityAccess || !$communityMigration || !$communityAccess->canManageApplications($userId)) { + throw new \RuntimeException('Keine Berechtigung für die Community-Migration.'); + } + $communityMigration->apply(); + $info = 'Die Community-Migration wurde ausgeführt.'; } } catch (\Throwable $e) { $error = $e->getMessage(); @@ -377,6 +384,8 @@ final class AccountPages 'reply_blocked' => false, 'reason' => null, ]; + $communityIsSiteAdmin = $communityAccess ? $communityAccess->canManageApplications($userId) : false; + $communityMigrationStatus = ($communityMigration && $communityIsSiteAdmin) ? $communityMigration->status() : null; return compact( 'flash', @@ -392,7 +401,9 @@ final class AccountPages 'communityRoles', 'communityApplication', 'communityCanApply', - 'communityRestrictions' + 'communityRestrictions', + 'communityIsSiteAdmin', + 'communityMigrationStatus' ); } diff --git a/src/App/Community.php b/src/App/Community.php index 0b27a4f..1327755 100755 --- a/src/App/Community.php +++ b/src/App/Community.php @@ -140,16 +140,12 @@ final class Community $params[$ph2] = '%' . $tok . '%'; $i++; } + $defaultBoard = $this->getBoardBySlug('wochenendideen'); $boardJoin = ''; $boardSelect = "NULL AS board_slug, NULL AS board_title, NULL AS category_slug, NULL AS category_title"; - $boardWhere = ''; if ($this->hasColumn('forum_threads', 'board_id') && $this->hasTable('forum_boards') && $this->hasTable('forum_categories')) { $boardJoin = ' LEFT JOIN forum_boards fb ON fb.id = ft.board_id LEFT JOIN forum_categories fc ON fc.id = fb.category_id '; $boardSelect = 'fb.slug AS board_slug, fb.title AS board_title, fc.slug AS category_slug, fc.title AS category_title'; - if ($boardSlug !== null && $boardSlug !== '') { - $boardWhere = ' AND fb.slug = :boardSlug'; - $params[':boardSlug'] = $boardSlug; - } } $where = $conditions ? ('AND ' . implode(' AND ', $conditions)) : ''; @@ -182,7 +178,7 @@ final class Community JOIN users u ON u.id = ft.user_id LEFT JOIN user_profiles p ON p.user_id = u.id $boardJoin - WHERE 1=1 $where $boardWhere + WHERE 1=1 $where ORDER BY ft.created_at DESC LIMIT :lim"; $stmt = $this->pdo->prepare($sql); @@ -191,7 +187,12 @@ final class Community } $stmt->bindValue(':lim', $limit, \PDO::PARAM_INT); $stmt->execute(); - return $stmt->fetchAll(\PDO::FETCH_ASSOC) ?: []; + $rows = $stmt->fetchAll(\PDO::FETCH_ASSOC) ?: []; + $rows = array_map(fn(array $row) => $this->applyFallbackBoard($row, $defaultBoard), $rows); + if ($boardSlug !== null && $boardSlug !== '') { + $rows = array_values(array_filter($rows, fn(array $row): bool => (string)($row['board_slug'] ?? '') === $boardSlug)); + } + return $rows; } public function listThreads(int $limit = 50, ?string $boardSlug = null): array @@ -210,7 +211,10 @@ final class Community $stmt = $this->pdo->prepare("SELECT $select FROM forum_threads ft LEFT JOIN user_profiles p ON p.user_id = ft.user_id $join WHERE ft.id = :id"); $stmt->execute([':id' => $id]); $row = $stmt->fetch(\PDO::FETCH_ASSOC); - return $row ?: null; + if (!$row) { + return null; + } + return $this->applyFallbackBoard($row, $this->getBoardBySlug('wochenendideen')); } public function listPosts(int $threadId): array @@ -477,6 +481,19 @@ final class Community } } + private function applyFallbackBoard(array $row, ?array $defaultBoard): array + { + if (!empty($row['board_slug']) || !$defaultBoard) { + return $row; + } + + $row['board_slug'] = $defaultBoard['slug'] ?? null; + $row['board_title'] = $defaultBoard['title'] ?? null; + $row['category_slug'] = $defaultBoard['category_slug'] ?? null; + $row['category_title'] = $defaultBoard['category_title'] ?? null; + return $row; + } + private function hasTable(string $table): bool { if (array_key_exists($table, $this->tableCache)) { diff --git a/src/App/CommunityMigration.php b/src/App/CommunityMigration.php new file mode 100644 index 0000000..0b7a53d --- /dev/null +++ b/src/App/CommunityMigration.php @@ -0,0 +1,154 @@ + $this->hasTable('forum_categories'), + 'forum_boards' => $this->hasTable('forum_boards'), + 'forum_post_feedback' => $this->hasTable('forum_post_feedback'), + 'forum_reports' => $this->hasTable('forum_reports'), + 'user_roles' => $this->hasTable('user_roles'), + 'community_admin_applications' => $this->hasTable('community_admin_applications'), + 'community_user_restrictions' => $this->hasTable('community_user_restrictions'), + 'forum_threads.board_id' => $this->hasColumn('forum_threads', 'board_id'), + 'forum_posts.highlighted_by' => $this->hasColumn('forum_posts', 'highlighted_by'), + 'forum_posts.highlighted_at' => $this->hasColumn('forum_posts', 'highlighted_at'), + ]; + + $missing = array_keys(array_filter($checks, static fn(bool $ok): bool => !$ok)); + + return [ + 'complete' => $missing === [], + 'checks' => $checks, + 'missing' => $missing, + ]; + } + + public function apply(): array + { + $statements = [ + 'CREATE TABLE IF NOT EXISTS forum_categories ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + slug VARCHAR(120) NOT NULL UNIQUE, + title VARCHAR(180) NOT NULL, + sort_order SMALLINT UNSIGNED NOT NULL DEFAULT 0 + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci', + + 'CREATE TABLE IF NOT EXISTS forum_boards ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + category_id BIGINT UNSIGNED NOT NULL, + slug VARCHAR(120) NOT NULL UNIQUE, + title VARCHAR(180) NOT NULL, + description TEXT NULL, + icon VARCHAR(32) NULL, + sort_order SMALLINT UNSIGNED NOT NULL DEFAULT 0, + INDEX idx_fb_category (category_id) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci', + + 'ALTER TABLE forum_threads + ADD COLUMN IF NOT EXISTS board_id BIGINT UNSIGNED NULL, + ADD INDEX IF NOT EXISTS idx_ft_board (board_id)', + + 'ALTER TABLE forum_posts + ADD COLUMN IF NOT EXISTS highlighted_by BIGINT UNSIGNED NULL, + ADD COLUMN IF NOT EXISTS highlighted_at DATETIME NULL', + + 'CREATE TABLE IF NOT EXISTS forum_post_feedback ( + post_id BIGINT UNSIGNED NOT NULL, + user_id BIGINT UNSIGNED NOT NULL, + value TINYINT NOT NULL, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (post_id, user_id), + INDEX idx_fpf_value (value) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci', + + 'CREATE TABLE IF NOT EXISTS forum_reports ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + reporter_user_id BIGINT UNSIGNED NOT NULL, + target_type ENUM("thread","post") NOT NULL, + target_id BIGINT UNSIGNED NOT NULL, + reason TEXT NOT NULL, + status ENUM("open","resolved","dismissed") NOT NULL DEFAULT "open", + moderator_user_id BIGINT UNSIGNED NULL, + moderator_note TEXT NULL, + resolved_at DATETIME NULL, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + INDEX idx_fr_status (status), + INDEX idx_fr_target (target_type, target_id) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci', + + 'CREATE TABLE IF NOT EXISTS user_roles ( + user_id BIGINT UNSIGNED NOT NULL, + role ENUM("forum_admin","site_admin","owner") NOT NULL, + assigned_by BIGINT UNSIGNED NULL, + assigned_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (user_id, role) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci', + + 'CREATE TABLE IF NOT EXISTS community_admin_applications ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + user_id BIGINT UNSIGNED NOT NULL, + motivation TEXT NOT NULL, + status ENUM("open","approved","rejected") NOT NULL DEFAULT "open", + decision_reason TEXT NULL, + decided_by BIGINT UNSIGNED NULL, + decided_at DATETIME NULL, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + INDEX idx_caa_status (status) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci', + + 'CREATE TABLE IF NOT EXISTS community_user_restrictions ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + user_id BIGINT UNSIGNED NOT NULL, + restriction_type ENUM("thread_create_blocked","reply_blocked") NOT NULL, + reason TEXT NULL, + active TINYINT(1) NOT NULL DEFAULT 1, + created_by BIGINT UNSIGNED NOT NULL, + expires_at DATETIME NULL, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + INDEX idx_cur_user_active (user_id, active) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci', + ]; + + foreach ($statements as $sql) { + $this->pdo->exec($sql); + } + + // Initial owner/site-admin bootstrap for user 1 if roles table exists. + if ($this->hasTable('user_roles')) { + $stmt = $this->pdo->prepare('INSERT IGNORE INTO user_roles (user_id, role, assigned_by) VALUES (1, "owner", 1)'); + $stmt->execute(); + $stmt = $this->pdo->prepare('INSERT IGNORE INTO user_roles (user_id, role, assigned_by) VALUES (1, "site_admin", 1)'); + $stmt->execute(); + } + + return $this->status(); + } + + private function hasTable(string $table): bool + { + $stmt = $this->pdo->prepare('SELECT 1 FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = :table LIMIT 1'); + $stmt->execute(['table' => $table]); + return (bool)$stmt->fetchColumn(); + } + + private function hasColumn(string $table, string $column): bool + { + $stmt = $this->pdo->prepare('SELECT 1 FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = :table AND column_name = :column LIMIT 1'); + $stmt->execute([ + 'table' => $table, + 'column' => $column, + ]); + return (bool)$stmt->fetchColumn(); + } +}