Autosave: 20260314-225136
This commit is contained in:
parent
2d777395d8
commit
424728da32
@ -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');
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
});
|
||||
|
||||
|
||||
2
db/migrations/20260314_add_is_private_to_threads.sql
Normal file
2
db/migrations/20260314_add_is_private_to_threads.sql
Normal 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;
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
40
index.php
40
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'] ?? '';
|
||||
<?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>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user