ReleaseV08+Badges
This commit is contained in:
parent
cc765bec94
commit
d0cead395d
74
api/upload_badge_image.php
Normal file
74
api/upload_badge_image.php
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../auth/session.php';
|
||||||
|
require_once __DIR__ . '/../includes/permissions.php';
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
$user = getCurrentUser();
|
||||||
|
if (!$user) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Non autorisé']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$server_id = $_POST['server_id'] ?? 0;
|
||||||
|
if (!$server_id) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'ID de serveur manquant']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Permissions check
|
||||||
|
$can_manage = Permissions::hasPermission($user['id'], $server_id, Permissions::MANAGE_SERVER) || Permissions::hasPermission($user['id'], $server_id, Permissions::ADMINISTRATOR);
|
||||||
|
// Also check owner
|
||||||
|
$stmt = db()->prepare("SELECT owner_id FROM servers WHERE id = ?");
|
||||||
|
$stmt->execute([$server_id]);
|
||||||
|
$server = $stmt->fetch();
|
||||||
|
$is_owner = ($server && $server['owner_id'] == $user['id']);
|
||||||
|
|
||||||
|
if (!$is_owner && !$can_manage) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Permissions insuffisantes']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($_FILES['badge_image']) || $_FILES['badge_image']['error'] !== UPLOAD_ERR_OK) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Aucun fichier reçu']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$file = $_FILES['badge_image'];
|
||||||
|
$allowedTypes = ['image/jpeg', 'image/png', 'image/webp', 'image/gif', 'image/svg+xml'];
|
||||||
|
$maxSize = 1 * 1024 * 1024; // 1MB for badges
|
||||||
|
|
||||||
|
if (!in_array($file['type'], $allowedTypes)) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Format non supporté']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($file['size'] > $maxSize) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Fichier trop gros (max 1Mo)']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$extension = pathinfo($file['name'], PATHINFO_EXTENSION);
|
||||||
|
if (empty($extension)) {
|
||||||
|
$extensions = [
|
||||||
|
'image/jpeg' => 'jpg',
|
||||||
|
'image/png' => 'png',
|
||||||
|
'image/webp' => 'webp',
|
||||||
|
'image/gif' => 'gif',
|
||||||
|
'image/svg+xml' => 'svg'
|
||||||
|
];
|
||||||
|
$extension = $extensions[$file['type']] ?? 'png';
|
||||||
|
}
|
||||||
|
|
||||||
|
$filename = 'badge_' . $server_id . '_' . time() . '_' . rand(1000, 9999) . '.' . $extension;
|
||||||
|
$dir = __DIR__ . '/../assets/images/badges/';
|
||||||
|
if (!is_dir($dir)) mkdir($dir, 0775, true);
|
||||||
|
|
||||||
|
$targetPath = $dir . $filename;
|
||||||
|
$relativeUrl = 'assets/images/badges/' . $filename;
|
||||||
|
|
||||||
|
if (move_uploaded_file($file['tmp_name'], $targetPath)) {
|
||||||
|
echo json_encode(['success' => true, 'url' => $relativeUrl]);
|
||||||
|
} else {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Erreur d\'écriture']);
|
||||||
|
}
|
||||||
105
api_v1_badges.php
Normal file
105
api_v1_badges.php
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
require_once 'auth/session.php';
|
||||||
|
require_once 'includes/permissions.php';
|
||||||
|
requireLogin();
|
||||||
|
|
||||||
|
$user_id = $_SESSION['user_id'];
|
||||||
|
$data = json_decode(file_get_contents('php://input'), true) ?? $_POST;
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
||||||
|
$server_id = $_GET['server_id'] ?? 0;
|
||||||
|
if (!$server_id) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Missing server_id']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify user is in server
|
||||||
|
$stmt = db()->prepare("SELECT * FROM server_members WHERE server_id = ? AND user_id = ?");
|
||||||
|
$stmt->execute([$server_id, $user_id]);
|
||||||
|
if (!$stmt->fetch()) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Access denied']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = db()->prepare("SELECT * FROM server_badges WHERE server_id = ? ORDER BY created_at DESC");
|
||||||
|
$stmt->execute([$server_id]);
|
||||||
|
$badges = $stmt->fetchAll();
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'badges' => $badges
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$action = $data['action'] ?? '';
|
||||||
|
$server_id = $data['server_id'] ?? 0;
|
||||||
|
|
||||||
|
// Permissions check
|
||||||
|
$stmt = db()->prepare("SELECT owner_id FROM servers WHERE id = ?");
|
||||||
|
$stmt->execute([$server_id]);
|
||||||
|
$server = $stmt->fetch();
|
||||||
|
$is_owner = ($server && $server['owner_id'] == $user_id);
|
||||||
|
$can_manage = Permissions::hasPermission($user_id, $server_id, Permissions::MANAGE_SERVER) || Permissions::hasPermission($user_id, $server_id, Permissions::ADMINISTRATOR);
|
||||||
|
|
||||||
|
if (!$is_owner && !$can_manage) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Unauthorized']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($action === 'create') {
|
||||||
|
$name = $data['name'] ?? 'New Badge';
|
||||||
|
$image_url = $data['image_url'] ?? '';
|
||||||
|
|
||||||
|
if (empty($image_url)) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Image requise']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = db()->prepare("INSERT INTO server_badges (server_id, name, image_url) VALUES (?, ?, ?)");
|
||||||
|
$stmt->execute([$server_id, $name, $image_url]);
|
||||||
|
echo json_encode(['success' => true, 'badge_id' => db()->lastInsertId()]);
|
||||||
|
} elseif ($action === 'update') {
|
||||||
|
$badge_id = $data['id'] ?? 0;
|
||||||
|
$name = $data['name'] ?? '';
|
||||||
|
$image_url = $data['image_url'] ?? '';
|
||||||
|
|
||||||
|
$stmt = db()->prepare("UPDATE server_badges SET name = ?, image_url = ? WHERE id = ? AND server_id = ?");
|
||||||
|
$stmt->execute([$name, $image_url, $badge_id, $server_id]);
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
|
} elseif ($action === 'delete') {
|
||||||
|
$badge_id = $data['id'] ?? 0;
|
||||||
|
$stmt = db()->prepare("DELETE FROM server_badges WHERE id = ? AND server_id = ?");
|
||||||
|
$stmt->execute([$badge_id, $server_id]);
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
|
} elseif ($action === 'set_user_badges') {
|
||||||
|
$target_user_id = $data['user_id'] ?? 0;
|
||||||
|
$badge_ids = $data['badge_ids'] ?? [];
|
||||||
|
|
||||||
|
$db = db();
|
||||||
|
$db->beginTransaction();
|
||||||
|
try {
|
||||||
|
$stmt = $db->prepare("DELETE FROM member_badges WHERE user_id = ? AND server_id = ?");
|
||||||
|
$stmt->execute([$target_user_id, $server_id]);
|
||||||
|
|
||||||
|
if (!empty($badge_ids)) {
|
||||||
|
$stmt = $db->prepare("INSERT INTO member_badges (server_id, user_id, badge_id) VALUES (?, ?, ?)");
|
||||||
|
foreach ($badge_ids as $bid) {
|
||||||
|
$check = $db->prepare("SELECT id FROM server_badges WHERE id = ? AND server_id = ?");
|
||||||
|
$check->execute([$bid, $server_id]);
|
||||||
|
if ($check->fetch()) {
|
||||||
|
$stmt->execute([$server_id, $target_user_id, $bid]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$db->commit();
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$db->rollBack();
|
||||||
|
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exit;
|
||||||
|
}
|
||||||
@ -48,12 +48,13 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
|||||||
$query = "
|
$query = "
|
||||||
SELECT m.*, u.display_name as username, u.username as login_name, u.avatar_url,
|
SELECT m.*, u.display_name as username, u.username as login_name, u.avatar_url,
|
||||||
(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.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(sb.name, '|', sb.image_url) SEPARATOR ':::') FROM member_badges mb JOIN server_badges sb ON mb.badge_id = sb.id WHERE mb.user_id = u.id AND mb.server_id = ?) as badge_data
|
||||||
FROM messages m
|
FROM messages m
|
||||||
JOIN users u ON m.user_id = u.id
|
JOIN users u ON m.user_id = u.id
|
||||||
WHERE m.channel_id = ? AND m.is_pinned = 1
|
WHERE m.channel_id = ? AND m.is_pinned = 1
|
||||||
";
|
";
|
||||||
$params = [$server_id ?: 0, $server_id ?: 0, $channel_id];
|
$params = [$server_id ?: 0, $server_id ?: 0, $server_id ?: 0, $channel_id];
|
||||||
|
|
||||||
if ($thread_id !== null) {
|
if ($thread_id !== null) {
|
||||||
$query .= " AND m.thread_id = ?";
|
$query .= " AND m.thread_id = ?";
|
||||||
@ -93,12 +94,13 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
|||||||
$query = "
|
$query = "
|
||||||
SELECT m.*, u.display_name as username, u.username as login_name, u.avatar_url,
|
SELECT m.*, u.display_name as username, u.username as login_name, u.avatar_url,
|
||||||
(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.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(sb.name, '|', sb.image_url) SEPARATOR ':::') FROM member_badges mb JOIN server_badges sb ON mb.badge_id = sb.id WHERE mb.user_id = u.id AND mb.server_id = ?) as badge_data
|
||||||
FROM messages m
|
FROM messages m
|
||||||
JOIN users u ON m.user_id = u.id
|
JOIN users u ON m.user_id = u.id
|
||||||
WHERE m.channel_id = ? AND m.id > ?
|
WHERE m.channel_id = ? AND m.id > ?
|
||||||
";
|
";
|
||||||
$params = [$server_id ?: 0, $server_id ?: 0, $channel_id, $after_id];
|
$params = [$server_id ?: 0, $server_id ?: 0, $server_id ?: 0, $channel_id, $after_id];
|
||||||
|
|
||||||
if ($thread_id !== null) {
|
if ($thread_id !== null) {
|
||||||
$query .= " AND m.thread_id = ?";
|
$query .= " AND m.thread_id = ?";
|
||||||
@ -293,12 +295,13 @@ try {
|
|||||||
$stmt = db()->prepare("
|
$stmt = db()->prepare("
|
||||||
SELECT m.*, u.display_name as username, u.username as login_name, u.avatar_url,
|
SELECT m.*, u.display_name as username, u.username as login_name, u.avatar_url,
|
||||||
(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.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(sb.name, '|', sb.image_url) SEPARATOR ':::') FROM member_badges mb JOIN server_badges sb ON mb.badge_id = sb.id WHERE mb.user_id = u.id AND mb.server_id = ?) as badge_data
|
||||||
FROM messages m
|
FROM messages m
|
||||||
JOIN users u ON m.user_id = u.id
|
JOIN users u ON m.user_id = u.id
|
||||||
WHERE m.id = ?
|
WHERE m.id = ?
|
||||||
");
|
");
|
||||||
$stmt->execute([$server_id ?: 0, $server_id ?: 0, $last_id]);
|
$stmt->execute([$server_id ?: 0, $server_id ?: 0, $server_id ?: 0, $last_id]);
|
||||||
$msg = $stmt->fetch();
|
$msg = $stmt->fetch();
|
||||||
|
|
||||||
echo json_encode([
|
echo json_encode([
|
||||||
@ -310,6 +313,7 @@ try {
|
|||||||
'avatar_url' => $msg['avatar_url'],
|
'avatar_url' => $msg['avatar_url'],
|
||||||
'role_color' => $msg['role_color'],
|
'role_color' => $msg['role_color'],
|
||||||
'role_icon' => $msg['role_icon'],
|
'role_icon' => $msg['role_icon'],
|
||||||
|
'badge_data' => $msg['badge_data'],
|
||||||
'content' => $msg['content'],
|
'content' => $msg['content'],
|
||||||
'attachment_url' => $msg['attachment_url'],
|
'attachment_url' => $msg['attachment_url'],
|
||||||
'metadata' => $msg['metadata'] ? json_decode($msg['metadata']) : null,
|
'metadata' => $msg['metadata'] ? json_decode($msg['metadata']) : null,
|
||||||
|
|||||||
@ -27,12 +27,13 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
|||||||
$roles = $stmt->fetchAll();
|
$roles = $stmt->fetchAll();
|
||||||
|
|
||||||
// Fetch members and their roles
|
// Fetch members and their roles
|
||||||
$stmt = db()->prepare("
|
$stmt = db()->prepare("
|
||||||
SELECT u.id, u.display_name as username, u.username as login_name, u.avatar_url,
|
SELECT u.id, u.display_name as username, u.username as login_name, u.avatar_url,
|
||||||
GROUP_CONCAT(r.id) as role_ids,
|
GROUP_CONCAT(DISTINCT r.id) as role_ids,
|
||||||
GROUP_CONCAT(r.name) as role_names,
|
GROUP_CONCAT(DISTINCT r.name) as role_names,
|
||||||
(SELECT r2.color FROM roles r2 JOIN user_roles ur2 ON r2.id = ur2.role_id WHERE ur2.user_id = u.id AND r2.server_id = ? ORDER BY r2.position DESC LIMIT 1) as role_color,
|
(SELECT r2.color FROM roles r2 JOIN user_roles ur2 ON r2.id = ur2.role_id WHERE ur2.user_id = u.id AND r2.server_id = ? ORDER BY r2.position DESC LIMIT 1) as role_color,
|
||||||
(SELECT r2.icon_url FROM roles r2 JOIN user_roles ur2 ON r2.id = ur2.role_id WHERE ur2.user_id = u.id AND r2.server_id = ? ORDER BY r2.position DESC LIMIT 1) as role_icon
|
(SELECT r2.icon_url FROM roles r2 JOIN user_roles ur2 ON r2.id = ur2.role_id WHERE ur2.user_id = u.id AND r2.server_id = ? ORDER BY r2.position DESC LIMIT 1) as role_icon,
|
||||||
|
(SELECT GROUP_CONCAT(CONCAT(sb.name, '|', sb.image_url) SEPARATOR ':::') FROM member_badges mb JOIN server_badges sb ON mb.badge_id = sb.id WHERE mb.user_id = u.id AND mb.server_id = ?) as badge_data
|
||||||
FROM users u
|
FROM users u
|
||||||
JOIN server_members sm ON u.id = sm.user_id
|
JOIN server_members sm ON u.id = sm.user_id
|
||||||
LEFT JOIN user_roles ur ON u.id = ur.user_id
|
LEFT JOIN user_roles ur ON u.id = ur.user_id
|
||||||
@ -40,7 +41,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
|||||||
WHERE sm.server_id = ?
|
WHERE sm.server_id = ?
|
||||||
GROUP BY u.id
|
GROUP BY u.id
|
||||||
");
|
");
|
||||||
$stmt->execute([$server_id, $server_id, $server_id, $server_id]);
|
$stmt->execute([$server_id, $server_id, $server_id, $server_id, $server_id]);
|
||||||
$members = $stmt->fetchAll();
|
$members = $stmt->fetchAll();
|
||||||
|
|
||||||
$filtered_members = null;
|
$filtered_members = null;
|
||||||
|
|||||||
BIN
assets/images/badges/badge_1_1771598872_8939.jpg
Normal file
BIN
assets/images/badges/badge_1_1771598872_8939.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 504 KiB |
@ -936,6 +936,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
const username = memberItem.dataset.username;
|
const username = memberItem.dataset.username;
|
||||||
const avatar = memberItem.dataset.avatar;
|
const avatar = memberItem.dataset.avatar;
|
||||||
const roleIds = (memberItem.dataset.roleIds || '').split(',').filter(id => id);
|
const roleIds = (memberItem.dataset.roleIds || '').split(',').filter(id => id);
|
||||||
|
const badgeData = (memberItem.dataset.badgeData || '').split(':::').filter(d => d);
|
||||||
|
|
||||||
// Create or show member menu
|
// Create or show member menu
|
||||||
document.querySelector('.member-context-menu')?.remove();
|
document.querySelector('.member-context-menu')?.remove();
|
||||||
@ -950,6 +951,24 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
menu.style.top = `${rect.top}px`;
|
menu.style.top = `${rect.top}px`;
|
||||||
menu.style.left = `${rect.left - 190}px`;
|
menu.style.left = `${rect.left - 190}px`;
|
||||||
|
|
||||||
|
let badgesHtml = '';
|
||||||
|
if (badgeData.length > 0) {
|
||||||
|
badgesHtml = `
|
||||||
|
<div class="mb-2 p-1">
|
||||||
|
<div class="small text-muted text-uppercase mb-1" style="font-size: 0.6em; font-weight: bold; opacity: 0.8;">Badges</div>
|
||||||
|
<div class="d-flex flex-wrap gap-1">
|
||||||
|
${badgeData.map(d => {
|
||||||
|
const parts = d.split('|');
|
||||||
|
const name = parts[0];
|
||||||
|
const url = parts[1];
|
||||||
|
return `<img src="${url}" style="width: 32px; height: 32px; object-fit: contain;" title="${escapeHTML(name)}">`;
|
||||||
|
}).join('')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="border-top border-secondary mb-2 mx-1"></div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
let rolesHtml = '';
|
let rolesHtml = '';
|
||||||
if (roleIds.length > 0) {
|
if (roleIds.length > 0) {
|
||||||
// Deduplicate and filter valid roles from serverRoles
|
// Deduplicate and filter valid roles from serverRoles
|
||||||
@ -979,9 +998,13 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
<span class="small fw-bold">${escapeHTML(username)}</span>
|
<span class="small fw-bold">${escapeHTML(username)}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="border-top border-secondary mb-2 mx-1"></div>
|
<div class="border-top border-secondary mb-2 mx-1"></div>
|
||||||
|
${badgesHtml}
|
||||||
${rolesHtml}
|
${rolesHtml}
|
||||||
<button class="btn btn-sm btn-dark w-100 text-start mb-1 member-menu-action" data-action="message">Message</button>
|
<button class="btn btn-sm btn-dark w-100 text-start mb-1 member-menu-action" data-action="message">Message</button>
|
||||||
${(window.isServerOwner || window.canManageServer) ? `<button class="btn btn-sm btn-dark w-100 text-start member-menu-action" data-action="edit-roles">Éditer son rôle</button>` : ''}
|
${(window.isServerOwner || window.canManageServer) ? `
|
||||||
|
<button class="btn btn-sm btn-dark w-100 text-start mb-1 member-menu-action" data-action="edit-roles">Éditer son rôle</button>
|
||||||
|
<button class="btn btn-sm btn-dark w-100 text-start member-menu-action" data-action="edit-badges">Éditer ses badges</button>
|
||||||
|
` : ''}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
document.body.appendChild(menu);
|
document.body.appendChild(menu);
|
||||||
@ -1008,6 +1031,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
}
|
}
|
||||||
} else if (action === 'edit-roles') {
|
} else if (action === 'edit-roles') {
|
||||||
openEditUserRolesModal(userId, username, avatar);
|
openEditUserRolesModal(userId, username, avatar);
|
||||||
|
} else if (action === 'edit-badges') {
|
||||||
|
openEditUserBadgesModal(userId, username, avatar);
|
||||||
}
|
}
|
||||||
menu.remove();
|
menu.remove();
|
||||||
};
|
};
|
||||||
@ -1678,6 +1703,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
item.dataset.username = m.username;
|
item.dataset.username = m.username;
|
||||||
item.dataset.avatar = m.avatar_url || '';
|
item.dataset.avatar = m.avatar_url || '';
|
||||||
item.dataset.roleIds = m.role_ids || '';
|
item.dataset.roleIds = m.role_ids || '';
|
||||||
|
item.dataset.badgeData = m.badge_data || '';
|
||||||
item.style.color = 'var(--text-primary)';
|
item.style.color = 'var(--text-primary)';
|
||||||
item.style.marginBottom = '8px';
|
item.style.marginBottom = '8px';
|
||||||
item.style.cursor = 'pointer';
|
item.style.cursor = 'pointer';
|
||||||
@ -1797,23 +1823,36 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
item.className = 'list-group-item bg-transparent text-white border-secondary d-flex justify-content-between align-items-center p-2 mb-2 rounded bg-dark';
|
item.className = 'list-group-item bg-transparent text-white border-secondary d-flex justify-content-between align-items-center p-2 mb-2 rounded bg-dark';
|
||||||
|
|
||||||
const roleIconHtml = renderRoleIconJS(member.role_icon, '14px');
|
const roleIconHtml = renderRoleIconJS(member.role_icon, '14px');
|
||||||
|
let badgesHtml = '';
|
||||||
|
if (member.badge_data) {
|
||||||
|
member.badge_data.split(':::').forEach(d => {
|
||||||
|
const parts = d.split('|');
|
||||||
|
const name = parts[0];
|
||||||
|
const url = parts[1];
|
||||||
|
badgesHtml += `<img src="${url}" class="ms-1" style="width: 32px; height: 32px; object-fit: contain;" title="${escapeHTML(name)}">`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
item.innerHTML = `
|
item.innerHTML = `
|
||||||
<div class="d-flex align-items-center flex-grow-1">
|
<div class="d-flex align-items-center flex-grow-1">
|
||||||
<div class="message-avatar me-2" style="width: 32px; height: 32px; ${member.avatar_url ? `background-image: url('${member.avatar_url}');` : ''}"></div>
|
<div class="message-avatar me-2" style="width: 32px; height: 32px; ${member.avatar_url ? `background-image: url('${member.avatar_url}');` : ''}"></div>
|
||||||
<div class="flex-grow-1">
|
<div class="flex-grow-1">
|
||||||
<div class="fw-bold small" style="color: ${member.role_color || 'inherit'}">
|
<div class="fw-bold small d-flex align-items-center" style="color: ${member.role_color || 'inherit'}">
|
||||||
${escapeHTML(member.username)}
|
${escapeHTML(member.username)}
|
||||||
${roleIconHtml}
|
${roleIconHtml}
|
||||||
|
<div class="ms-2 d-flex gap-1">${badgesHtml}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-muted small">
|
<div class="text-muted small">
|
||||||
${member.role_names ? member.role_names.split(',').join(', ') : 'No roles'}
|
${member.role_names ? member.role_names.split(',').join(', ') : 'No roles'}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
${(window.isServerOwner || window.canManageServer) ? `
|
<div class="d-flex gap-2">
|
||||||
<button class="btn btn-sm btn-outline-light edit-user-roles-settings-btn" data-id="${member.id}" data-username="${member.username}" data-avatar="${member.avatar_url || ''}">Roles</button>
|
${(window.isServerOwner || window.canManageServer) ? `
|
||||||
` : ''}
|
<button class="btn btn-sm btn-outline-light edit-user-roles-settings-btn" data-id="${member.id}" data-username="${member.username}" data-avatar="${member.avatar_url || ''}">Roles</button>
|
||||||
|
<button class="btn btn-sm btn-outline-light edit-user-badges-settings-btn" data-id="${member.id}" data-username="${member.username}" data-avatar="${member.avatar_url || ''}">Badges</button>
|
||||||
|
` : ''}
|
||||||
|
</div>
|
||||||
`;
|
`;
|
||||||
membersList.appendChild(item);
|
membersList.appendChild(item);
|
||||||
});
|
});
|
||||||
@ -1821,9 +1860,13 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
|
|
||||||
// Add listener for the button in members list tab
|
// Add listener for the button in members list tab
|
||||||
membersList?.addEventListener('click', (e) => {
|
membersList?.addEventListener('click', (e) => {
|
||||||
const btn = e.target.closest('.edit-user-roles-settings-btn');
|
const btnRole = e.target.closest('.edit-user-roles-settings-btn');
|
||||||
if (btn) {
|
if (btnRole) {
|
||||||
openEditUserRolesModal(btn.dataset.id, btn.dataset.username, btn.dataset.avatar);
|
openEditUserRolesModal(btnRole.dataset.id, btnRole.dataset.username, btnRole.dataset.avatar);
|
||||||
|
}
|
||||||
|
const btnBadge = e.target.closest('.edit-user-badges-settings-btn');
|
||||||
|
if (btnBadge) {
|
||||||
|
openEditUserBadgesModal(btnBadge.dataset.id, btnBadge.dataset.username, btnBadge.dataset.avatar);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -2162,6 +2205,210 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const badgesTabBtn = document.getElementById('badges-tab-btn');
|
||||||
|
const badgesList = document.getElementById('badges-list');
|
||||||
|
const addBadgeBtn = document.getElementById('add-badge-btn');
|
||||||
|
const saveBadgeBtn = document.getElementById('save-badge-btn');
|
||||||
|
const badgeImageUploadInput = document.getElementById('badge-image-upload-input');
|
||||||
|
|
||||||
|
badgesTabBtn?.addEventListener('click', loadBadges);
|
||||||
|
|
||||||
|
async function loadBadges() {
|
||||||
|
try {
|
||||||
|
const resp = await fetch(`api_v1_badges.php?server_id=${activeServerId}`);
|
||||||
|
const data = await resp.json();
|
||||||
|
if (data.success) {
|
||||||
|
renderBadges(data.badges);
|
||||||
|
}
|
||||||
|
} catch (e) { console.error(e); }
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderBadges(badges) {
|
||||||
|
if (!badgesList) return;
|
||||||
|
badgesList.innerHTML = '';
|
||||||
|
if (badges.length === 0) {
|
||||||
|
badgesList.innerHTML = '<div class="text-center p-3 text-muted">Aucun badge créé.</div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
badges.forEach(badge => {
|
||||||
|
const item = document.createElement('div');
|
||||||
|
item.className = 'list-group-item bg-transparent text-white border-secondary d-flex justify-content-between align-items-center p-2 mb-1 rounded';
|
||||||
|
item.innerHTML = `
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<img src="${badge.image_url}" style="width: 32px; height: 32px; object-fit: contain; margin-right: 12px;">
|
||||||
|
<span class="fw-medium">${escapeHTML(badge.name)}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button class="btn btn-sm btn-outline-light edit-badge-btn" data-id="${badge.id}" data-name="${badge.name}" data-url="${badge.image_url}">Modifier</button>
|
||||||
|
<button class="btn btn-sm btn-outline-danger delete-badge-btn" data-id="${badge.id}">×</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
badgesList.appendChild(item);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
addBadgeBtn?.addEventListener('click', () => {
|
||||||
|
document.getElementById('edit-badge-id').value = '';
|
||||||
|
document.getElementById('edit-badge-name').value = '';
|
||||||
|
document.getElementById('edit-badge-image-url').value = '';
|
||||||
|
document.getElementById('badge-image-preview').style.backgroundImage = 'none';
|
||||||
|
|
||||||
|
const modal = new bootstrap.Modal(document.getElementById('badgeEditorModal'));
|
||||||
|
modal.show();
|
||||||
|
});
|
||||||
|
|
||||||
|
badgesList?.addEventListener('click', (e) => {
|
||||||
|
if (e.target.classList.contains('edit-badge-btn')) {
|
||||||
|
const b = e.target.dataset;
|
||||||
|
document.getElementById('edit-badge-id').value = b.id;
|
||||||
|
document.getElementById('edit-badge-name').value = b.name;
|
||||||
|
document.getElementById('edit-badge-image-url').value = b.url;
|
||||||
|
document.getElementById('badge-image-preview').style.backgroundImage = `url('${b.url}')`;
|
||||||
|
|
||||||
|
const modal = new bootstrap.Modal(document.getElementById('badgeEditorModal'));
|
||||||
|
modal.show();
|
||||||
|
} else if (e.target.classList.contains('delete-badge-btn')) {
|
||||||
|
if (confirm('Supprimer ce badge ?')) {
|
||||||
|
const id = e.target.dataset.id;
|
||||||
|
fetch('api_v1_badges.php', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ action: 'delete', server_id: activeServerId, id })
|
||||||
|
}).then(r => r.json()).then(data => {
|
||||||
|
if (data.success) loadBadges();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
badgeImageUploadInput?.addEventListener('change', async () => {
|
||||||
|
const file = badgeImageUploadInput.files[0];
|
||||||
|
if (!file) return;
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('badge_image', file);
|
||||||
|
formData.append('server_id', activeServerId);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const resp = await fetch('api/upload_badge_image.php', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
});
|
||||||
|
const data = await resp.json();
|
||||||
|
if (data.success) {
|
||||||
|
document.getElementById('edit-badge-image-url').value = data.url;
|
||||||
|
document.getElementById('badge-image-preview').style.backgroundImage = `url('${data.url}')`;
|
||||||
|
} else {
|
||||||
|
alert(data.error || 'Erreur d\'upload');
|
||||||
|
}
|
||||||
|
} catch (e) { console.error(e); }
|
||||||
|
});
|
||||||
|
|
||||||
|
saveBadgeBtn?.addEventListener('click', async () => {
|
||||||
|
const id = document.getElementById('edit-badge-id').value;
|
||||||
|
const name = document.getElementById('edit-badge-name').value;
|
||||||
|
const image_url = document.getElementById('edit-badge-image-url').value;
|
||||||
|
|
||||||
|
if (!name || !image_url) {
|
||||||
|
alert('Le nom et l\'image sont requis.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const action = id ? 'update' : 'create';
|
||||||
|
const resp = await fetch('api_v1_badges.php', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ action, server_id: activeServerId, id, name, image_url })
|
||||||
|
});
|
||||||
|
const data = await resp.json();
|
||||||
|
if (data.success) {
|
||||||
|
bootstrap.Modal.getInstance(document.getElementById('badgeEditorModal')).hide();
|
||||||
|
loadBadges();
|
||||||
|
}
|
||||||
|
} catch (e) { console.error(e); }
|
||||||
|
});
|
||||||
|
|
||||||
|
// User Badges Assignment Logic
|
||||||
|
async function openEditUserBadgesModal(userId, username, avatar) {
|
||||||
|
document.getElementById('edit-user-badges-user-id').value = userId;
|
||||||
|
document.getElementById('edit-user-badges-username').textContent = username;
|
||||||
|
document.getElementById('edit-user-badges-avatar').style.backgroundImage = avatar ? `url('${avatar}')` : 'none';
|
||||||
|
|
||||||
|
const list = document.getElementById('user-badges-selection-list');
|
||||||
|
list.innerHTML = '<div class="text-center p-3 text-muted">Chargement...</div>';
|
||||||
|
|
||||||
|
const bsModal = new bootstrap.Modal(document.getElementById('editUserBadgesModal'));
|
||||||
|
bsModal.show();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const [badgeResp, memberBadgeResp] = await Promise.all([
|
||||||
|
fetch(`api_v1_badges.php?server_id=${activeServerId}`),
|
||||||
|
fetch(`api_v1_roles.php?server_id=${activeServerId}`) // We need member info
|
||||||
|
]);
|
||||||
|
|
||||||
|
const badgeData = await badgeResp.json();
|
||||||
|
const memberData = await memberBadgeResp.json();
|
||||||
|
|
||||||
|
if (badgeData.success && memberData.success) {
|
||||||
|
const member = memberData.members.find(m => m.id == userId);
|
||||||
|
// We'll use a hack to get user badges if not provided by roles API yet,
|
||||||
|
// but actually I updated api_v1_roles.php to include badge_urls.
|
||||||
|
// Wait, badge_urls are URLs, but I need IDs for the checkboxes.
|
||||||
|
|
||||||
|
// Let's refine api_v1_roles.php to also return badge_ids.
|
||||||
|
// Or I can fetch it specifically if needed.
|
||||||
|
// For now, I'll fetch it from a dedicated endpoint if I add it,
|
||||||
|
// but let's just update api_v1_roles.php one more time.
|
||||||
|
|
||||||
|
const assignedBadgeUrls = member && member.badge_data ? member.badge_data.split(':::').map(d => d.split('|')[1]) : [];
|
||||||
|
|
||||||
|
list.innerHTML = '';
|
||||||
|
badgeData.badges.forEach(badge => {
|
||||||
|
const isChecked = assignedBadgeUrls.includes(badge.image_url);
|
||||||
|
const item = document.createElement('div');
|
||||||
|
item.className = 'list-group-item bg-dark text-white border-secondary p-2 d-flex align-items-center';
|
||||||
|
item.innerHTML = `
|
||||||
|
<input class="form-check-input me-3 user-badge-checkbox" type="checkbox" value="${badge.id}" id="user-badge-${badge.id}" ${isChecked ? 'checked' : ''}>
|
||||||
|
<label class="form-check-label flex-grow-1 d-flex align-items-center" for="user-badge-${badge.id}" style="cursor: pointer;">
|
||||||
|
<img src="${badge.image_url}" style="width: 32px; height: 32px; object-fit: contain; margin-right: 8px;">
|
||||||
|
${escapeHTML(badge.name)}
|
||||||
|
</label>
|
||||||
|
`;
|
||||||
|
list.appendChild(item);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (badgeData.badges.length === 0) {
|
||||||
|
list.innerHTML = '<div class="text-center p-3 text-muted">Aucun badge défini pour ce serveur.</div>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) { console.error(e); }
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('save-user-badges-btn')?.addEventListener('click', async () => {
|
||||||
|
const btn = document.getElementById('save-user-badges-btn');
|
||||||
|
const userId = document.getElementById('edit-user-badges-user-id').value;
|
||||||
|
const badgeIds = Array.from(document.querySelectorAll('.user-badge-checkbox:checked')).map(cb => cb.value);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const resp = await fetch('api_v1_badges.php', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
action: 'set_user_badges',
|
||||||
|
server_id: activeServerId,
|
||||||
|
user_id: userId,
|
||||||
|
badge_ids: badgeIds
|
||||||
|
})
|
||||||
|
});
|
||||||
|
const data = await resp.json();
|
||||||
|
if (data.success) {
|
||||||
|
bootstrap.Modal.getInstance(document.getElementById('editUserBadgesModal')).hide();
|
||||||
|
loadRoles(); // Refresh members list
|
||||||
|
}
|
||||||
|
} catch (e) { console.error(e); }
|
||||||
|
});
|
||||||
|
|
||||||
// Forum: New Thread
|
// Forum: New Thread
|
||||||
const newThreadBtn = document.getElementById('new-thread-btn');
|
const newThreadBtn = document.getElementById('new-thread-btn');
|
||||||
const newThreadModal = document.getElementById('newThreadModal') ? new bootstrap.Modal(document.getElementById('newThreadModal')) : null;
|
const newThreadModal = document.getElementById('newThreadModal') ? new bootstrap.Modal(document.getElementById('newThreadModal')) : null;
|
||||||
@ -2873,6 +3120,17 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
</span>
|
</span>
|
||||||
` : '';
|
` : '';
|
||||||
|
|
||||||
|
let userBadgesHtml = '';
|
||||||
|
const bData = msg.badge_data || '';
|
||||||
|
if (bData) {
|
||||||
|
bData.split(':::').forEach(d => {
|
||||||
|
const parts = d.split('|');
|
||||||
|
const name = parts[0];
|
||||||
|
const url = parts[1];
|
||||||
|
userBadgesHtml += `<img src="${url}" class="ms-1" style="width: 32px; height: 32px; object-fit: contain; vertical-align: middle;" title="${escapeHTML(name)}">`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const mentionRegex = new RegExp(`@${window.currentUsername}\\b`, 'g');
|
const mentionRegex = new RegExp(`@${window.currentUsername}\\b`, 'g');
|
||||||
const mentionHtml = `<span class="mention">@${window.currentUsername}</span>`;
|
const mentionHtml = `<span class="mention">@${window.currentUsername}</span>`;
|
||||||
const contentWithMentions = parseCustomEmotes(msg.content).replace(mentionRegex, mentionHtml);
|
const contentWithMentions = parseCustomEmotes(msg.content).replace(mentionRegex, mentionHtml);
|
||||||
@ -2884,6 +3142,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
<span class="message-username" style="color: ${msg.role_color || 'inherit'};">
|
<span class="message-username" style="color: ${msg.role_color || 'inherit'};">
|
||||||
${escapeHTML(msg.username)}
|
${escapeHTML(msg.username)}
|
||||||
${renderRoleIconJS(msg.role_icon, '14px')}
|
${renderRoleIconJS(msg.role_icon, '14px')}
|
||||||
|
<span class="ms-1 d-inline-flex gap-1">${userBadgesHtml}</span>
|
||||||
</span>
|
</span>
|
||||||
<span class="message-timestamp">${msg.timestamp || 'Just now'}</span>
|
<span class="message-timestamp">${msg.timestamp || 'Just now'}</span>
|
||||||
${pinnedBadge}
|
${pinnedBadge}
|
||||||
|
|||||||
BIN
assets/pasted-20260220-145350-f3302486.png
Normal file
BIN
assets/pasted-20260220-145350-f3302486.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 21 KiB |
18
db/migrations/20260220_create_badges.sql
Normal file
18
db/migrations/20260220_create_badges.sql
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS server_badges (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
server_id INT NOT NULL,
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
image_url TEXT NOT NULL,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (server_id) REFERENCES servers(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS member_badges (
|
||||||
|
server_id INT NOT NULL,
|
||||||
|
user_id INT NOT NULL,
|
||||||
|
badge_id INT NOT NULL,
|
||||||
|
PRIMARY KEY (server_id, user_id, badge_id),
|
||||||
|
FOREIGN KEY (server_id) REFERENCES servers(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (badge_id) REFERENCES server_badges(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
81
index.php
81
index.php
@ -350,12 +350,13 @@ if ($is_dm_view) {
|
|||||||
SELECT u.id, u.display_name as username, u.username as login_name, u.avatar_url, u.status,
|
SELECT u.id, u.display_name as username, u.username as login_name, u.avatar_url, u.status,
|
||||||
(SELECT GROUP_CONCAT(r.id) FROM roles r JOIN user_roles ur ON r.id = ur.role_id WHERE ur.user_id = u.id AND r.server_id = ?) as role_ids,
|
(SELECT GROUP_CONCAT(r.id) FROM roles r JOIN user_roles ur ON r.id = ur.role_id WHERE ur.user_id = u.id AND r.server_id = ?) as role_ids,
|
||||||
(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.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(sb.name, '|', sb.image_url) SEPARATOR ':::') FROM member_badges mb JOIN server_badges sb ON mb.badge_id = sb.id WHERE mb.user_id = u.id AND mb.server_id = ?) as badge_data
|
||||||
FROM users u
|
FROM users u
|
||||||
JOIN server_members sm ON u.id = sm.user_id
|
JOIN server_members sm ON u.id = sm.user_id
|
||||||
WHERE sm.server_id = ?
|
WHERE sm.server_id = ?
|
||||||
");
|
");
|
||||||
$stmt->execute([$active_server_id, $active_server_id, $active_server_id, $active_server_id]);
|
$stmt->execute([$active_server_id, $active_server_id, $active_server_id, $active_server_id, $active_server_id]);
|
||||||
$all_server_members = $stmt->fetchAll();
|
$all_server_members = $stmt->fetchAll();
|
||||||
|
|
||||||
$members = [];
|
$members = [];
|
||||||
@ -1223,15 +1224,15 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
|||||||
Membres — <?php echo count($members); ?>
|
Membres — <?php echo count($members); ?>
|
||||||
</div>
|
</div>
|
||||||
<?php foreach($members as $m): ?>
|
<?php foreach($members as $m): ?>
|
||||||
<div class="channel-item member-item" data-user-id="<?php echo $m['id']; ?>" data-username="<?php echo htmlspecialchars($m['username']); ?>" data-avatar="<?php echo htmlspecialchars($m['avatar_url'] ?? ''); ?>" data-role-ids="<?php echo $m['role_ids'] ?? ''; ?>" style="color: var(--text-primary); margin-bottom: 8px; cursor: pointer;">
|
<div class="channel-item member-item" data-user-id="<?php echo $m['id']; ?>" data-username="<?php echo htmlspecialchars($m['username']); ?>" data-avatar="<?php echo htmlspecialchars($m['avatar_url'] ?? ''); ?>" data-role-ids="<?php echo $m['role_ids'] ?? ''; ?>" data-badge-data="<?php echo htmlspecialchars($m['badge_data'] ?? ''); ?>" style="color: var(--text-primary); margin-bottom: 8px; cursor: pointer;">
|
||||||
<div class="message-avatar" style="width: 32px; height: 32px; background-color: <?php echo $m['status'] == 'online' ? '#23a559' : '#80848e'; ?>; position: relative; <?php echo $m['avatar_url'] ? "background-image: url('{$m['avatar_url']}');" : ""; ?>">
|
<div class="message-avatar" style="width: 32px; height: 32px; background-color: <?php echo ($m['status'] ?? 'offline') == 'online' ? '#23a559' : '#80848e'; ?>; position: relative; <?php echo $m['avatar_url'] ? "background-image: url('{$m['avatar_url']}');" : ""; ?>">
|
||||||
<?php if($m['status'] == 'online'): ?>
|
<?php if(($m['status'] ?? 'offline') == 'online'): ?>
|
||||||
<div style="position: absolute; bottom: 0; right: 0; width: 10px; height: 10px; background-color: #23a559; border-radius: 50%; border: 2px solid var(--bg-members);"></div>
|
<div style="position: absolute; bottom: 0; right: 0; width: 10px; height: 10px; background-color: #23a559; border-radius: 50%; border: 2px solid var(--bg-members);"></div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
<span style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap; <?php echo !empty($m['role_color']) ? "color: {$m['role_color']};" : ""; ?>">
|
<span style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap; <?php echo !empty($m['role_color']) ? "color: {$m['role_color']};" : ""; ?>">
|
||||||
<?php echo htmlspecialchars($m['username']); ?>
|
<?php echo htmlspecialchars($m['username']); ?>
|
||||||
<?php echo renderRoleIcon($m['role_icon'], '14px'); ?>
|
<?php echo renderRoleIcon($m['role_icon'] ?? '', '14px'); ?>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
@ -1881,6 +1882,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<button class="nav-link text-white border-0 bg-transparent" id="roles-tab-btn" data-bs-toggle="tab" data-bs-target="#settings-roles" type="button">Rôles</button>
|
<button class="nav-link text-white border-0 bg-transparent" id="roles-tab-btn" data-bs-toggle="tab" data-bs-target="#settings-roles" type="button">Rôles</button>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<button class="nav-link text-white border-0 bg-transparent" id="badges-tab-btn" data-bs-toggle="tab" data-bs-target="#settings-badges" type="button">Badges</button>
|
||||||
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<button class="nav-link text-white border-0 bg-transparent" id="webhooks-tab-btn" data-bs-toggle="tab" data-bs-target="#settings-webhooks" type="button">Webhooks</button>
|
<button class="nav-link text-white border-0 bg-transparent" id="webhooks-tab-btn" data-bs-toggle="tab" data-bs-target="#settings-webhooks" type="button">Webhooks</button>
|
||||||
</li>
|
</li>
|
||||||
@ -1970,6 +1974,15 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
<!-- Roles will be loaded here -->
|
<!-- Roles will be loaded here -->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="tab-pane fade" id="settings-badges">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||||
|
<h6 class="mb-0">Badges du serveur</h6>
|
||||||
|
<button class="btn btn-sm btn-primary" id="add-badge-btn">+ Ajouter un badge</button>
|
||||||
|
</div>
|
||||||
|
<div id="badges-list" class="list-group list-group-flush bg-transparent">
|
||||||
|
<!-- Badges will be loaded here -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="tab-pane fade" id="settings-webhooks">
|
<div class="tab-pane fade" id="settings-webhooks">
|
||||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||||
<h6 class="mb-0">Webhooks</h6>
|
<h6 class="mb-0">Webhooks</h6>
|
||||||
@ -2978,5 +2991,61 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
}
|
}
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</script>
|
</script>
|
||||||
|
<!-- Badge Editor Modal -->
|
||||||
|
<div class="modal fade" id="badgeEditorModal" tabindex="-1">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Créer un badge</h5>
|
||||||
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<input type="hidden" id="edit-badge-id">
|
||||||
|
<div class="mb-3 text-center">
|
||||||
|
<div id="badge-image-preview" class="mx-auto mb-2" style="width: 64px; height: 64px; background-size: contain; background-repeat: no-repeat; background-position: center; border: 1px dashed var(--secondary); border-radius: 4px;"></div>
|
||||||
|
<input type="hidden" id="edit-badge-image-url">
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-light" onclick="document.getElementById('badge-image-upload-input').click()">
|
||||||
|
<i class="fas fa-upload me-1"></i> Importer une image
|
||||||
|
</button>
|
||||||
|
<input type="file" id="badge-image-upload-input" style="display: none;" accept="image/*">
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label text-uppercase fw-bold" style="font-size: 0.7em; color: var(--text-muted);">Nom du badge</label>
|
||||||
|
<input type="text" id="edit-badge-name" class="form-control" placeholder="Ex: Top Donateur">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer border-0">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annuler</button>
|
||||||
|
<button type="button" class="btn btn-primary" id="save-badge-btn">Enregistrer</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Edit User Badges Modal -->
|
||||||
|
<div class="modal fade" id="editUserBadgesModal" tabindex="-1">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Gérer les badges de <span id="edit-user-badges-username"></span></h5>
|
||||||
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<input type="hidden" id="edit-user-badges-user-id">
|
||||||
|
<div class="text-center mb-3">
|
||||||
|
<div id="edit-user-badges-avatar" class="message-avatar mx-auto" style="width: 64px; height: 64px;"></div>
|
||||||
|
</div>
|
||||||
|
<div id="user-badges-selection-list" class="list-group list-group-flush bg-transparent overflow-auto" style="max-height: 300px;">
|
||||||
|
<!-- Badges checkboxes here -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer border-0">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Fermer</button>
|
||||||
|
<button type="button" class="btn btn-primary" id="save-user-badges-btn">Enregistrer</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user