From 424728da32f449dd9119a64d75242fbb493e8966 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Sat, 14 Mar 2026 22:51:37 +0000 Subject: [PATCH] Autosave: 20260314-225136 --- api_v1_channels.php | 9 ++++- api_v1_tags.php | 21 +++++++++- api_v1_threads.php | 30 +++++++++++--- assets/js/main.js | 11 +++++ .../20260314_add_is_private_to_threads.sql | 2 + includes/permissions.php | 30 +++++++++++++- index.php | 40 ++++++++++++++----- 7 files changed, 123 insertions(+), 20 deletions(-) create mode 100644 db/migrations/20260314_add_is_private_to_threads.sql diff --git a/api_v1_channels.php b/api_v1_channels.php index 2eca980..d0ac881 100644 --- a/api_v1_channels.php +++ b/api_v1_channels.php @@ -127,6 +127,13 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $stmt = db()->prepare("INSERT INTO channels (server_id, name, type, allow_file_sharing, ai_moderation_enabled, message_limit, icon, category_id, position, rules_role_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); $stmt->execute([$server_id, $name, $type, $allow_file_sharing, $ai_moderation_enabled, $message_limit, $icon, $category_id, $nextPos, $rules_role_id]); $channel_id = db()->lastInsertId(); + + if ($type === 'support') { + $stmtTags = db()->prepare("INSERT INTO forum_tags (channel_id, name, color) VALUES (?, ?, ?)"); + $stmtTags->execute([$channel_id, 'Ticket prioritaire', '#dc3545']); + $stmtTags->execute([$channel_id, 'Ticket non prioritaire', '#6c757d']); + $stmtTags->execute([$channel_id, 'Ticket urgent', '#ffc107']); + } header('Location: index.php?server_id=' . $server_id . '&channel_id=' . $channel_id); exit; @@ -135,4 +142,4 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { } } } -header('Location: index.php'); +header('Location: index.php'); \ No newline at end of file diff --git a/api_v1_tags.php b/api_v1_tags.php index ae4e341..8da7fdb 100644 --- a/api_v1_tags.php +++ b/api_v1_tags.php @@ -46,9 +46,28 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { echo json_encode(['success' => true, 'tag_id' => db()->lastInsertId()]); } elseif ($action === 'delete') { $tag_id = $data['tag_id'] ?? 0; + + // Check if it's a default tag in a support channel + $stmtCheck = db()->prepare(" + SELECT t.name, c.type + FROM forum_tags t + JOIN channels c ON t.channel_id = c.id + WHERE t.id = ? + "); + $stmtCheck->execute([$tag_id]); + $tag = $stmtCheck->fetch(); + + if ($tag && $tag['type'] === 'support') { + $defaults = ['Ticket prioritaire', 'Ticket non prioritaire', 'Ticket urgent']; + if (in_array($tag['name'], $defaults)) { + echo json_encode(['success' => false, 'error' => 'Default support tags cannot be deleted']); + exit; + } + } + $stmt = db()->prepare("DELETE FROM forum_tags WHERE id = ? AND channel_id = ?"); $stmt->execute([$tag_id, $channel_id]); echo json_encode(['success' => true]); } exit; -} +} \ No newline at end of file diff --git a/api_v1_threads.php b/api_v1_threads.php index 2903d23..2a487d0 100644 --- a/api_v1_threads.php +++ b/api_v1_threads.php @@ -4,10 +4,13 @@ require_once 'auth/session.php'; require_once 'includes/permissions.php'; requireLogin(); +$user_id = $_SESSION['user_id']; + if ($_SERVER['REQUEST_METHOD'] === 'POST') { $channel_id = $_POST['channel_id'] ?? 0; $title = $_POST['title'] ?? ''; - $user_id = $_SESSION['user_id']; + $is_private = isset($_POST['is_private']) && $_POST['is_private'] == '1' ? 1 : 0; + $content = $_POST['content'] ?? ''; if (!$channel_id || !$title) { echo json_encode(['success' => false, 'error' => 'Missing data']); @@ -26,8 +29,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { try { db()->beginTransaction(); - $stmt = db()->prepare("INSERT INTO forum_threads (channel_id, user_id, title) VALUES (?, ?, ?)"); - $stmt->execute([$channel_id, $user_id, $title]); + $stmt = db()->prepare("INSERT INTO forum_threads (channel_id, user_id, title, is_private) VALUES (?, ?, ?, ?)"); + $stmt->execute([$channel_id, $user_id, $title, $is_private]); $thread_id = db()->lastInsertId(); if (!empty($tag_ids)) { @@ -36,6 +39,23 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { if ($tag_id) $stmtTag->execute([$thread_id, $tag_id]); } } + + if ($content) { + $stmtMsg = db()->prepare("INSERT INTO messages (channel_id, thread_id, user_id, content) VALUES (?, ?, ?, ?)"); + $stmtMsg->execute([$channel_id, $thread_id, $user_id, $content]); + $message_id = db()->lastInsertId(); + + // Default reactions for support tickets + $stmtChan = db()->prepare("SELECT type FROM channels WHERE id = ?"); + $stmtChan->execute([$channel_id]); + $chan = $stmtChan->fetch(); + if ($chan && $chan['type'] === 'support') { + $stmtReact = db()->prepare("INSERT IGNORE INTO message_reactions (message_id, user_id, emoji) VALUES (?, ?, ?)"); + $stmtReact->execute([$message_id, $user_id, '👍']); + $stmtReact->execute([$message_id, $user_id, '👎']); + } + } + db()->commit(); echo json_encode(['success' => true, 'thread_id' => $thread_id]); } catch (Exception $e) { @@ -55,8 +75,6 @@ if ($_SERVER['REQUEST_METHOD'] === 'PATCH' || $_SERVER['REQUEST_METHOD'] === 'DE $action = 'delete'; } - $user_id = $_SESSION['user_id']; - if (!$thread_id) { echo json_encode(['success' => false, 'error' => 'Missing thread_id']); exit; @@ -133,4 +151,4 @@ if ($_SERVER['REQUEST_METHOD'] === 'PATCH' || $_SERVER['REQUEST_METHOD'] === 'DE echo json_encode(['success' => false, 'error' => $e->getMessage()]); } exit; -} +} \ No newline at end of file diff --git a/assets/js/main.js b/assets/js/main.js index 3f22b38..20b558e 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -2665,6 +2665,17 @@ document.addEventListener('DOMContentLoaded', () => { } } catch (e) { console.error(e); } + const privacySection = document.getElementById("new-thread-privacy-section"); + if (window.activeChannelType === "support") { + privacySection.classList.remove("d-none"); + document.getElementById("newThreadModal").querySelector(".modal-title").textContent = "Nouveau Ticket"; + document.getElementById("submit-new-thread-btn").textContent = "Créer le ticket"; + } else { + privacySection.classList.add("d-none"); + document.getElementById("newThreadModal").querySelector(".modal-title").textContent = "Nouvelle discussion"; + document.getElementById("submit-new-thread-btn").textContent = "Créer la discussion"; + } + newThreadModal.show(); }); diff --git a/db/migrations/20260314_add_is_private_to_threads.sql b/db/migrations/20260314_add_is_private_to_threads.sql new file mode 100644 index 0000000..b9bc3dd --- /dev/null +++ b/db/migrations/20260314_add_is_private_to_threads.sql @@ -0,0 +1,2 @@ +-- Add privacy support to forum threads +ALTER TABLE forum_threads ADD COLUMN is_private TINYINT(1) NOT NULL DEFAULT 0; diff --git a/includes/permissions.php b/includes/permissions.php index 80bc29f..ce064d0 100644 --- a/includes/permissions.php +++ b/includes/permissions.php @@ -53,6 +53,34 @@ class Permissions { return self::canDoInChannel($user_id, $channel_id, self::SEND_MESSAGES); } + public static function canViewThread($user_id, $thread_id) { + $stmt = db()->prepare(" + SELECT t.*, c.type as channel_type, c.server_id + FROM forum_threads t + JOIN channels c ON t.channel_id = c.id + WHERE t.id = ? + "); + $stmt->execute([$thread_id]); + $thread = $stmt->fetch(); + if (!$thread) return false; + + // If it's not private, anyone who can view the channel can view the thread + if (!$thread['is_private']) { + return self::canViewChannel($user_id, $thread['channel_id']); + } + + // If private: + // 1. Creator can view + if ($thread['user_id'] == $user_id) return true; + + // 2. Admins/Moderators can view + if (self::hasPermission($user_id, $thread['server_id'], self::ADMINISTRATOR)) return true; + if (self::hasPermission($user_id, $thread['server_id'], self::MANAGE_MESSAGES)) return true; + if (self::hasPermission($user_id, $thread['server_id'], self::MANAGE_CHANNELS)) return true; + + return false; + } + public static function canDoInChannel($user_id, $channel_id, $permission) { $stmt = db()->prepare("SELECT server_id FROM channels WHERE id = ?"); $stmt->execute([$channel_id]); @@ -127,4 +155,4 @@ class Permissions { // Fallback to base permissions return self::hasPermission($user_id, $server_id, $permission); } -} +} \ No newline at end of file diff --git a/index.php b/index.php index 4eb1ba3..9b3390d 100644 --- a/index.php +++ b/index.php @@ -234,10 +234,11 @@ if ($is_dm_view) { $active_server = $s; $is_owner = ($s['owner_id'] == $current_user_id); $can_manage_channels = Permissions::hasPermission($current_user_id, $active_server_id, Permissions::MANAGE_CHANNELS) || $is_owner; - $can_manage_server = Permissions::hasPermission($current_user_id, $active_server_id, Permissions::MANAGE_SERVER) || + $can_manage_server = Permissions::hasPermission($current_user_id, $active_server_id, Permissions::MANAGE_SERVER) || Permissions::hasPermission($current_user_id, $active_server_id, Permissions::MANAGE_MESSAGES) || - Permissions::hasPermission($current_user_id, $active_server_id, Permissions::ADMINISTRATOR) || + Permissions::hasPermission($current_user_id, $active_server_id, Permissions::ADMINISTRATOR) || $is_owner; + $can_manage_support = $can_manage_server || Permissions::hasPermission($current_user_id, $active_server_id, Permissions::MANAGE_CHANNELS); // Event permissions $can_create_event = Permissions::canDoInChannel($current_user_id, $active_channel_id, Permissions::CREATE_EVENT); @@ -297,26 +298,32 @@ if ($is_dm_view) { } elseif ($channel_type === 'autorole') { $stmt = db()->prepare("SELECT ca.*, r.name as role_name FROM channel_autoroles ca JOIN roles r ON ca.role_id = r.id WHERE ca.channel_id = ? ORDER BY ca.id ASC"); $stmt->execute([$active_channel_id]); - $autoroles = $stmt->fetchAll(); - } elseif ($channel_type === 'forum') { + } elseif ($channel_type === "forum" || $channel_type === "support") { $tag_where = ""; + $private_where = ""; $query_params = [$active_server_id, $active_server_id, $active_channel_id]; if (!empty($selected_tag_ids)) { - $placeholders = implode(',', array_fill(0, count($selected_tag_ids), '?')); + $placeholders = implode(",", array_fill(0, count($selected_tag_ids), "?")); $tag_where = " AND EXISTS (SELECT 1 FROM thread_tags tt WHERE tt.thread_id = t.id AND tt.tag_id IN ($placeholders))"; foreach ($selected_tag_ids as $tid) $query_params[] = $tid; } + if (!$can_manage_support) { + $private_where = " AND (t.is_private = 0 OR t.user_id = ?)"; + $query_params[] = $current_user_id; + } + $stmt = db()->prepare(" SELECT t.*, u.display_name as username, u.avatar_url, (SELECT COUNT(*) FROM messages m WHERE m.thread_id = t.id) as message_count, (SELECT MAX(created_at) FROM messages m WHERE m.thread_id = t.id) as last_message_at, (SELECT r.color FROM roles r JOIN user_roles ur ON r.id = ur.role_id WHERE ur.user_id = u.id AND r.server_id = ? ORDER BY r.position DESC LIMIT 1) as role_color, - (SELECT r.icon_url FROM roles r JOIN user_roles ur ON r.id = ur.role_id WHERE ur.user_id = u.id AND r.server_id = ? ORDER BY r.position DESC LIMIT 1) as role_icon + (SELECT r.icon_url FROM roles r JOIN user_roles ur ON r.id = ur.role_id WHERE ur.user_id = u.id AND r.server_id = ? ORDER BY r.position DESC LIMIT 1) as role_icon, + (SELECT GROUP_CONCAT(CONCAT(ft.name, "-", ft.color) SEPARATOR "|") FROM thread_tags tt JOIN forum_tags ft ON tt.tag_id = ft.id WHERE tt.thread_id = t.id) as tags FROM forum_threads t JOIN users u ON t.user_id = u.id - WHERE t.channel_id = ? $tag_where + WHERE t.channel_id = ? $tag_where $private_where ORDER BY t.is_pinned DESC, last_message_at DESC "); $stmt->execute($query_params); @@ -699,6 +706,7 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? ''; + elseif ($c["type"] === "support") echo ""; () @@ -845,6 +853,7 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? ''; elseif ($active_channel['type'] === 'autorole') echo ''; elseif ($active_channel['type'] === 'forum') echo ''; elseif ($active_channel['type'] === 'voice') echo ''; + elseif ($active_channel["type"] === "support") echo ""; elseif ($active_channel['type'] === 'poll') echo ''; else echo ''; @@ -1365,11 +1374,11 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? ''; - +
-

🏛️

+

{ + @@ -2749,6 +2759,7 @@ document.addEventListener('DOMContentLoaded', () => { + @@ -3532,14 +3543,21 @@ document.addEventListener('DOMContentLoaded', () => {
+
+ +
-
-