Autosave: 20260314-225136

This commit is contained in:
Flatlogic Bot 2026-03-14 22:51:37 +00:00
parent 2d777395d8
commit 424728da32
7 changed files with 123 additions and 20 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,2 @@
-- Add privacy support to forum threads
ALTER TABLE forum_threads ADD COLUMN is_private TINYINT(1) NOT NULL DEFAULT 0;

View File

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

View File

@ -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'] ?? '';
<?php endif; ?>
<span class="channel-name-text">
<?php echo htmlspecialchars($c['name']); ?>
elseif ($c["type"] === "support") echo "<i class=\"fa-solid fa-ticket-alt\"></i>";
<?php if ($c['type'] === 'event' && !empty($c['event_count'])): ?>
<span class="ms-1">(<?php echo $c['event_count']; ?>)</span>
<?php endif; ?>
@ -845,6 +853,7 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
elseif ($active_channel['type'] === 'autorole') echo '<i class="fa-solid fa-shield-halved"></i>';
elseif ($active_channel['type'] === 'forum') echo '<i class="fa-solid fa-comments"></i>';
elseif ($active_channel['type'] === 'voice') echo '<i class="fa-solid fa-volume-up"></i>';
elseif ($active_channel["type"] === "support") echo "<i class=\"fa-solid fa-ticket-alt\"></i>";
elseif ($active_channel['type'] === 'poll') echo '<i class="fa-solid fa-square-poll-vertical"></i>';
else echo '<i class="fa-solid fa-hashtag"></i>';
@ -1365,11 +1374,11 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
</div>
<?php endif; ?>
</div>
<?php elseif($channel_type === 'forum' && !$active_thread): ?>
<?php elseif($channel_type === 'forum" || $channel_type === "support' && !$active_thread): ?>
<div class="forum-container p-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h2 class="mb-2">🏛️ <?php echo htmlspecialchars($current_channel_name); ?></h2>
<h2 class="mb-2"><?php echo ($channel_type === 'support') ? '🎫' : '🏛️'; ?> <?php echo htmlspecialchars($current_channel_name); ?></h2>
<div class="btn-group btn-group-sm forum-filters flex-wrap gap-1">
<?php
$s_id = $active_server_id;
@ -2724,6 +2733,7 @@ document.addEventListener('DOMContentLoaded', () => {
<option value="rules">Salon de Règlement</option>
<option value="autorole">Salon d'Auto-rôles</option>
<option value="forum">Salon Forum</option>
<option value="support">Salon de Support</option>
<option value="event">Salon Événement</option>
<option value="category">Catégorie</option>
<option value="separator">Séparateur</option>
@ -2749,6 +2759,7 @@ document.addEventListener('DOMContentLoaded', () => {
<option value="fa-lock">🔒 Privé</option>
<option value="fa-star"> Étoile</option>
<option value="fa-heart">❤️ Cœur</option>
<option value="fa-ticket-alt">🎫 Support</option>
<option value="fa-gamepad">🎮 Jeux</option>
<option value="fa-music">🎵 Musique</option>
<option value="fa-video">📹 Vidéo</option>
@ -3532,14 +3543,21 @@ document.addEventListener('DOMContentLoaded', () => {
<label class="form-label text-uppercase fw-bold" style="font-size: 0.7em; color: var(--text-muted);">Titre de la discussion</label>
<input type="text" id="new-thread-title" class="form-control" placeholder="À quoi pensez-vous ?">
</div>
<div class="mb-3">
<label class="form-label text-uppercase fw-bold" style="font-size: 0.7em; color: var(--text-muted);">Tags</label>
<div id="new-thread-tags-list" class="d-flex flex-wrap gap-2 p-2 bg-dark rounded">
<div class="mb-3">
<label class="form-label text-uppercase fw-bold" style="font-size: 0.7em; color: var(--text-muted);">Tags</label>
<div id="new-thread-tags-list" class="d-flex flex-wrap gap-2 p-2 bg-dark rounded">
<!-- Tags loaded here -->
</div>
</div>
</div>
<div class="modal-footer">
<div class="mb-3 d-none" id="new-thread-privacy-section">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="new-thread-is-private" checked>
<label class="form-check-label" for="new-thread-is-private">Ticket privé (visible uniquement par le staff et vous)</label>
</div>
</div>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annuler</button>
<button type="button" id="submit-new-thread-btn" class="btn btn-primary">Créer la discussion</button>
</div>