This commit is contained in:
@@ -5,6 +5,10 @@ namespace App;
|
||||
|
||||
final class Community
|
||||
{
|
||||
private ?array $forumStructure = null;
|
||||
private array $tableCache = [];
|
||||
private array $columnCache = [];
|
||||
|
||||
public function __construct(private \PDO $pdo, private array $config)
|
||||
{
|
||||
}
|
||||
@@ -24,11 +28,41 @@ final class Community
|
||||
|
||||
public function createThread(int $userId, string $title, string $body): void
|
||||
{
|
||||
$this->createThreadInBoard($userId, null, $title, $body);
|
||||
}
|
||||
|
||||
public function createThreadInBoard(int $userId, ?string $boardSlug, string $title, string $body): void
|
||||
{
|
||||
$title = trim($title);
|
||||
$body = trim($body);
|
||||
if ($title === '' || $body === '') {
|
||||
throw new \RuntimeException('Titel und Text sind erforderlich.');
|
||||
}
|
||||
|
||||
if ($this->hasColumn('forum_threads', 'board_id') && $this->hasTable('forum_boards')) {
|
||||
$boardId = null;
|
||||
if ($boardSlug !== null && $boardSlug !== '') {
|
||||
$board = $this->getBoardBySlug($boardSlug);
|
||||
if (!$board) {
|
||||
throw new \RuntimeException('Forum-Bereich nicht gefunden.');
|
||||
}
|
||||
$boardId = (int)$board['id'];
|
||||
}
|
||||
|
||||
$stmt = $this->pdo->prepare('INSERT INTO forum_threads (board_id, user_id, title, body) VALUES (:boardId, :uid, :title, :body)');
|
||||
$stmt->bindValue(':boardId', $boardId, $boardId === null ? \PDO::PARAM_NULL : \PDO::PARAM_INT);
|
||||
$stmt->bindValue(':uid', $userId, \PDO::PARAM_INT);
|
||||
$stmt->bindValue(':title', $title, \PDO::PARAM_STR);
|
||||
$stmt->bindValue(':body', $body, \PDO::PARAM_STR);
|
||||
$stmt->execute();
|
||||
return;
|
||||
}
|
||||
|
||||
$stmt = $this->pdo->prepare('INSERT INTO forum_threads (user_id, title, body) VALUES (:uid, :title, :body)');
|
||||
$stmt->execute([
|
||||
':uid' => $userId,
|
||||
':title' => trim($title),
|
||||
':body' => trim($body),
|
||||
':title' => $title,
|
||||
':body' => $body,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -42,7 +76,57 @@ final class Community
|
||||
]);
|
||||
}
|
||||
|
||||
public function searchThreads(string $query, int $limit = 50): array
|
||||
public function updateThread(int $threadId, string $title, string $body): void
|
||||
{
|
||||
$stmt = $this->pdo->prepare('UPDATE forum_threads SET title = :title, body = :body, updated_at = NOW() WHERE id = :id');
|
||||
$stmt->execute([
|
||||
'id' => $threadId,
|
||||
'title' => trim($title),
|
||||
'body' => trim($body),
|
||||
]);
|
||||
}
|
||||
|
||||
public function deleteThread(int $threadId): void
|
||||
{
|
||||
$stmt = $this->pdo->prepare('DELETE FROM forum_threads WHERE id = :id');
|
||||
$stmt->execute(['id' => $threadId]);
|
||||
}
|
||||
|
||||
public function updatePost(int $postId, string $body): void
|
||||
{
|
||||
$stmt = $this->pdo->prepare('UPDATE forum_posts SET body = :body, updated_at = NOW() WHERE id = :id');
|
||||
$stmt->execute([
|
||||
'id' => $postId,
|
||||
'body' => trim($body),
|
||||
]);
|
||||
}
|
||||
|
||||
public function deletePost(int $postId): void
|
||||
{
|
||||
$stmt = $this->pdo->prepare('DELETE FROM forum_posts WHERE id = :id');
|
||||
$stmt->execute(['id' => $postId]);
|
||||
}
|
||||
|
||||
public function toggleHelpfulHighlight(int $postId, ?int $userId): void
|
||||
{
|
||||
if (!$this->hasColumn('forum_posts', 'highlighted_at')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($userId === null) {
|
||||
$stmt = $this->pdo->prepare('UPDATE forum_posts SET highlighted_by = NULL, highlighted_at = NULL WHERE id = :id');
|
||||
$stmt->execute(['id' => $postId]);
|
||||
return;
|
||||
}
|
||||
|
||||
$stmt = $this->pdo->prepare('UPDATE forum_posts SET highlighted_by = :uid, highlighted_at = NOW() WHERE id = :id');
|
||||
$stmt->execute([
|
||||
'uid' => $userId,
|
||||
'id' => $postId,
|
||||
]);
|
||||
}
|
||||
|
||||
public function searchThreads(string $query, int $limit = 50, ?string $boardSlug = null): array
|
||||
{
|
||||
$conditions = [];
|
||||
$params = [];
|
||||
@@ -56,11 +140,24 @@ final class Community
|
||||
$params[$ph2] = '%' . $tok . '%';
|
||||
$i++;
|
||||
}
|
||||
$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)) : '';
|
||||
|
||||
$sql = "SELECT ft.id, ft.title, ft.body, ft.created_at, ft.updated_at,
|
||||
u.id as uid, u.created_at as user_created,
|
||||
p.display_name,
|
||||
$boardSelect,
|
||||
(SELECT COUNT(*) FROM forum_posts fp WHERE fp.thread_id = ft.id) AS answers,
|
||||
COALESCE(
|
||||
(SELECT MAX(fp.created_at) FROM forum_posts fp WHERE fp.thread_id = ft.id),
|
||||
@@ -84,7 +181,8 @@ final class Community
|
||||
FROM forum_threads ft
|
||||
JOIN users u ON u.id = ft.user_id
|
||||
LEFT JOIN user_profiles p ON p.user_id = u.id
|
||||
WHERE 1=1 $where
|
||||
$boardJoin
|
||||
WHERE 1=1 $where $boardWhere
|
||||
ORDER BY ft.created_at DESC
|
||||
LIMIT :lim";
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
@@ -96,14 +194,20 @@ final class Community
|
||||
return $stmt->fetchAll(\PDO::FETCH_ASSOC) ?: [];
|
||||
}
|
||||
|
||||
public function listThreads(int $limit = 50): array
|
||||
public function listThreads(int $limit = 50, ?string $boardSlug = null): array
|
||||
{
|
||||
return $this->searchThreads('', $limit);
|
||||
return $this->searchThreads('', $limit, $boardSlug);
|
||||
}
|
||||
|
||||
public function getThread(int $id): ?array
|
||||
{
|
||||
$stmt = $this->pdo->prepare('SELECT ft.*, p.display_name FROM forum_threads ft LEFT JOIN user_profiles p ON p.user_id = ft.user_id WHERE ft.id = :id');
|
||||
$select = 'ft.*, p.display_name';
|
||||
$join = '';
|
||||
if ($this->hasColumn('forum_threads', 'board_id') && $this->hasTable('forum_boards') && $this->hasTable('forum_categories')) {
|
||||
$select .= ', fb.slug AS board_slug, fb.title AS board_title, fc.slug AS category_slug, fc.title AS category_title';
|
||||
$join = ' LEFT JOIN forum_boards fb ON fb.id = ft.board_id LEFT JOIN forum_categories fc ON fc.id = fb.category_id ';
|
||||
}
|
||||
$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;
|
||||
@@ -111,11 +215,97 @@ final class Community
|
||||
|
||||
public function listPosts(int $threadId): array
|
||||
{
|
||||
$stmt = $this->pdo->prepare('SELECT fp.*, p.display_name FROM forum_posts fp LEFT JOIN user_profiles p ON p.user_id = fp.user_id WHERE fp.thread_id = :id ORDER BY fp.created_at ASC');
|
||||
$select = 'fp.*, p.display_name';
|
||||
if ($this->hasColumn('forum_posts', 'highlighted_at')) {
|
||||
$select .= ', hp.display_name AS highlighted_by_name';
|
||||
} else {
|
||||
$select .= ', NULL AS highlighted_by_name';
|
||||
}
|
||||
$join = ' LEFT JOIN user_profiles p ON p.user_id = fp.user_id ';
|
||||
if ($this->hasColumn('forum_posts', 'highlighted_at')) {
|
||||
$join .= ' LEFT JOIN user_profiles hp ON hp.user_id = fp.highlighted_by ';
|
||||
}
|
||||
$stmt = $this->pdo->prepare("SELECT $select FROM forum_posts fp $join WHERE fp.thread_id = :id ORDER BY fp.created_at ASC");
|
||||
$stmt->execute([':id' => $threadId]);
|
||||
return $stmt->fetchAll(\PDO::FETCH_ASSOC) ?: [];
|
||||
}
|
||||
|
||||
public function getPost(int $postId): ?array
|
||||
{
|
||||
$stmt = $this->pdo->prepare('SELECT * FROM forum_posts WHERE id = :id LIMIT 1');
|
||||
$stmt->execute(['id' => $postId]);
|
||||
$row = $stmt->fetch(\PDO::FETCH_ASSOC);
|
||||
return $row ?: null;
|
||||
}
|
||||
|
||||
public function listForumCategories(): array
|
||||
{
|
||||
$structure = $this->forumStructure();
|
||||
$boards = [];
|
||||
foreach ($structure as $category) {
|
||||
foreach ($category['boards'] as $board) {
|
||||
$boards[$board['slug']] = $board;
|
||||
}
|
||||
}
|
||||
|
||||
$threadsByBoard = [];
|
||||
foreach ($this->listThreads(200) as $thread) {
|
||||
$slug = (string)($thread['board_slug'] ?? '');
|
||||
if ($slug === '' || !isset($boards[$slug])) {
|
||||
continue;
|
||||
}
|
||||
$threadsByBoard[$slug][] = $thread;
|
||||
}
|
||||
|
||||
foreach ($structure as &$category) {
|
||||
foreach ($category['boards'] as &$board) {
|
||||
$items = $threadsByBoard[$board['slug']] ?? [];
|
||||
$board['thread_count'] = count($items);
|
||||
$replyCount = 0;
|
||||
foreach ($items as $item) {
|
||||
$replyCount += (int)($item['answers'] ?? 0);
|
||||
}
|
||||
$board['post_count'] = $replyCount;
|
||||
$board['latest_thread'] = $items[0] ?? null;
|
||||
}
|
||||
unset($board);
|
||||
}
|
||||
unset($category);
|
||||
|
||||
return $structure;
|
||||
}
|
||||
|
||||
public function getBoardBySlug(string $slug): ?array
|
||||
{
|
||||
foreach ($this->forumStructure() as $category) {
|
||||
foreach ($category['boards'] as $board) {
|
||||
if ($board['slug'] === $slug) {
|
||||
if ($this->hasTable('forum_boards')) {
|
||||
$stmt = $this->pdo->prepare('
|
||||
SELECT fb.id, fb.slug, fb.title, fb.description, fb.icon, fc.slug AS category_slug, fc.title AS category_title
|
||||
FROM forum_boards fb
|
||||
JOIN forum_categories fc ON fc.id = fb.category_id
|
||||
WHERE fb.slug = :slug
|
||||
LIMIT 1
|
||||
');
|
||||
$stmt->execute(['slug' => $slug]);
|
||||
$dbBoard = $stmt->fetch(\PDO::FETCH_ASSOC);
|
||||
if ($dbBoard) {
|
||||
return array_merge($board, $dbBoard);
|
||||
}
|
||||
}
|
||||
|
||||
return array_merge($board, [
|
||||
'category_slug' => $category['slug'],
|
||||
'category_title' => $category['title'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function computePoints(int $userId): float
|
||||
{
|
||||
// Primär: aggregierte Werte aus user_points_totals, Fallback: Summe aus user_points
|
||||
@@ -223,4 +413,92 @@ final class Community
|
||||
$fallback = $levels ? $levels[count($levels)-1] : ['label' => 'New Daddy','icon' => ''];
|
||||
return ['label' => $fallback['label'], 'icon' => $fallback['icon'] ?? ''];
|
||||
}
|
||||
|
||||
private function forumStructure(): array
|
||||
{
|
||||
if ($this->forumStructure !== null) {
|
||||
return $this->forumStructure;
|
||||
}
|
||||
|
||||
$file = dirname(__DIR__, 2) . '/config/community_forums.php';
|
||||
$this->forumStructure = file_exists($file) ? (array) require $file : [];
|
||||
$this->syncForumStructure();
|
||||
|
||||
return $this->forumStructure;
|
||||
}
|
||||
|
||||
private function syncForumStructure(): void
|
||||
{
|
||||
if (!$this->hasTable('forum_categories') || !$this->hasTable('forum_boards')) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($this->forumStructure as $categoryIndex => $category) {
|
||||
$stmt = $this->pdo->prepare('INSERT INTO forum_categories (slug, title, sort_order) VALUES (:slug, :title, :sortOrder) ON DUPLICATE KEY UPDATE title = VALUES(title), sort_order = VALUES(sort_order)');
|
||||
$stmt->execute([
|
||||
'slug' => $category['slug'],
|
||||
'title' => $category['title'],
|
||||
'sortOrder' => $categoryIndex,
|
||||
]);
|
||||
|
||||
$catIdStmt = $this->pdo->prepare('SELECT id FROM forum_categories WHERE slug = :slug LIMIT 1');
|
||||
$catIdStmt->execute(['slug' => $category['slug']]);
|
||||
$categoryId = (int)$catIdStmt->fetchColumn();
|
||||
|
||||
foreach ($category['boards'] as $boardIndex => $board) {
|
||||
$boardStmt = $this->pdo->prepare('
|
||||
INSERT INTO forum_boards (category_id, slug, title, description, icon, sort_order)
|
||||
VALUES (:categoryId, :slug, :title, :description, :icon, :sortOrder)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
category_id = VALUES(category_id),
|
||||
title = VALUES(title),
|
||||
description = VALUES(description),
|
||||
icon = VALUES(icon),
|
||||
sort_order = VALUES(sort_order)
|
||||
');
|
||||
$boardStmt->execute([
|
||||
'categoryId' => $categoryId,
|
||||
'slug' => $board['slug'],
|
||||
'title' => $board['title'],
|
||||
'description' => $board['description'],
|
||||
'icon' => $board['icon'] ?? null,
|
||||
'sortOrder' => $boardIndex,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function hasTable(string $table): bool
|
||||
{
|
||||
if (array_key_exists($table, $this->tableCache)) {
|
||||
return $this->tableCache[$table];
|
||||
}
|
||||
|
||||
try {
|
||||
$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 $this->tableCache[$table] = (bool)$stmt->fetchColumn();
|
||||
} catch (\Throwable) {
|
||||
return $this->tableCache[$table] = false;
|
||||
}
|
||||
}
|
||||
|
||||
private function hasColumn(string $table, string $column): bool
|
||||
{
|
||||
$key = $table . '.' . $column;
|
||||
if (array_key_exists($key, $this->columnCache)) {
|
||||
return $this->columnCache[$key];
|
||||
}
|
||||
|
||||
try {
|
||||
$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 $this->columnCache[$key] = (bool)$stmt->fetchColumn();
|
||||
} catch (\Throwable) {
|
||||
return $this->columnCache[$key] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user