correctif permissions

This commit is contained in:
Flatlogic Bot 2026-02-18 16:15:26 +00:00
parent 09fa2a7096
commit 5329017efa
7 changed files with 187 additions and 89 deletions

View File

@ -27,15 +27,27 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') {
$everyone_role_id = $everyone['id'] ?? 0;
}
// Fetch permissions for this channel
// Fetch permissions for this channel (roles and users)
$stmt = db()->prepare("
SELECT cp.*, r.name as role_name, r.color as role_color
SELECT cp.*, r.name as role_name, r.color as role_color,
u.display_name as member_name, u.avatar_url as member_avatar
FROM channel_permissions cp
JOIN roles r ON cp.role_id = r.id
LEFT JOIN roles r ON cp.role_id = r.id
LEFT JOIN users u ON cp.user_id = u.id
WHERE cp.channel_id = ?
");
$stmt->execute([$channel_id]);
$permissions = $stmt->fetchAll();
$permissions = [];
while($row = $stmt->fetch()) {
if ($row['user_id']) {
$row['display_name'] = $row['member_name'] ?? 'Unknown Member';
$row['type'] = 'member';
} else {
$row['display_name'] = $row['role_name'] ?? 'Unknown Role';
$row['type'] = 'role';
}
$permissions[] = $row;
}
// Check if @everyone is in permissions, if not add it manually to show up by default
$has_everyone = false;
@ -54,10 +66,13 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') {
array_unshift($permissions, [
'channel_id' => (int)$channel_id,
'role_id' => (int)$everyone_role_id,
'user_id' => null,
'allow_permissions' => 0,
'deny_permissions' => 0,
'role_name' => $r['name'],
'role_color' => $r['color']
'role_color' => $r['color'],
'display_name' => $r['name'],
'type' => 'role'
]);
}
}
@ -68,10 +83,16 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') {
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$channel_id = $data['channel_id'] ?? 0;
$role_id = $data['role_id'] ?? 0;
$role_id = $data['role_id'] ?? null;
$target_user_id = $data['user_id'] ?? null;
$allow = $data['allow'] ?? 0;
$deny = $data['deny'] ?? 0;
if (!$role_id && !$target_user_id) {
echo json_encode(['success' => false, 'error' => 'Missing role_id or user_id']);
exit;
}
// Check permissions: Owner or MANAGE_CHANNELS or ADMINISTRATOR
require_once 'includes/permissions.php';
$stmt = db()->prepare("SELECT server_id FROM channels WHERE id = ?");
@ -89,11 +110,11 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if ($is_owner || $can_manage) {
$stmt = db()->prepare("
INSERT INTO channel_permissions (channel_id, role_id, allow_permissions, deny_permissions)
VALUES (?, ?, ?, ?)
INSERT INTO channel_permissions (channel_id, role_id, user_id, allow_permissions, deny_permissions)
VALUES (?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE allow_permissions = VALUES(allow_permissions), deny_permissions = VALUES(deny_permissions)
");
$stmt->execute([$channel_id, $role_id, $allow, $deny]);
$stmt->execute([$channel_id, $role_id, $target_user_id, $allow, $deny]);
echo json_encode(['success' => true]);
} else {
echo json_encode(['success' => false, 'error' => 'Unauthorized']);
@ -103,7 +124,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if ($_SERVER['REQUEST_METHOD'] === 'DELETE') {
$channel_id = $data['channel_id'] ?? 0;
$role_id = $data['role_id'] ?? 0;
$role_id = $data['role_id'] ?? null;
$target_user_id = $data['user_id'] ?? null;
// Check permissions
require_once 'includes/permissions.php';
@ -121,8 +143,13 @@ if ($_SERVER['REQUEST_METHOD'] === 'DELETE') {
Permissions::hasPermission($user_id, $server_id, Permissions::ADMINISTRATOR);
if ($is_owner || $can_manage) {
$stmt = db()->prepare("DELETE FROM channel_permissions WHERE channel_id = ? AND role_id = ?");
$stmt->execute([$channel_id, $role_id]);
if ($role_id !== null) {
$stmt = db()->prepare("DELETE FROM channel_permissions WHERE channel_id = ? AND role_id = ? AND user_id IS NULL");
$stmt->execute([$channel_id, $role_id]);
} else if ($target_user_id !== null) {
$stmt = db()->prepare("DELETE FROM channel_permissions WHERE channel_id = ? AND user_id = ? AND role_id IS NULL");
$stmt->execute([$channel_id, $target_user_id]);
}
echo json_encode(['success' => true]);
} else {
echo json_encode(['success' => false, 'error' => 'Unauthorized']);

View File

@ -1069,6 +1069,20 @@ document.addEventListener('DOMContentLoaded', () => {
await loadRolesForPermissions(channelId);
});
const searchChannelPerms = document.getElementById('search-channel-perms');
searchChannelPerms?.addEventListener('input', () => {
const query = searchChannelPerms.value.toLowerCase();
const items = channelPermissionsRolesList.querySelectorAll('.list-group-item');
items.forEach(item => {
const name = item.textContent.toLowerCase();
if (name.includes(query)) {
item.classList.remove('d-none');
} else {
item.classList.add('d-none');
}
});
});
async function loadChannelPermissions(channelId) {
channelPermissionsRolesList.innerHTML = '<div class="text-center p-3 text-muted small">Loading...</div>';
const resp = await fetch(`api_v1_channel_permissions.php?channel_id=${channelId}`);
@ -1081,7 +1095,7 @@ document.addEventListener('DOMContentLoaded', () => {
async function loadRolesForPermissions(channelId) {
if (!addPermRoleList) return;
addPermRoleList.innerHTML = '<li><span class="dropdown-item text-muted">Loading roles...</span></li>';
addPermRoleList.innerHTML = '<li><span class="dropdown-item text-muted">Loading...</span></li>';
try {
const resp = await fetch(`api_v1_roles.php?server_id=${activeServerId}`);
@ -1090,57 +1104,73 @@ document.addEventListener('DOMContentLoaded', () => {
if (data.success) {
addPermRoleList.innerHTML = '';
// Filter out roles already in overrides
const existingRoleIds = channelPermissionsData.map(p => parseInt(p.role_id));
// Roles Section
const existingRoleIds = channelPermissionsData.filter(p => p.type === 'role').map(p => parseInt(p.role_id));
const availableRoles = data.roles.filter(role => !existingRoleIds.includes(parseInt(role.id)));
if (availableRoles.length === 0) {
addPermRoleList.innerHTML = '<li><span class="dropdown-item disabled text-muted">No more roles to add</span></li>';
if (window.canManageServer) {
const divider = document.createElement('li');
divider.innerHTML = '<hr class="dropdown-divider border-secondary opacity-25">';
addPermRoleList.appendChild(divider);
const createLink = document.createElement('li');
createLink.innerHTML = '<a class="dropdown-item small text-info" href="#" data-bs-toggle="modal" data-bs-target="#serverSettingsModal" style="font-size: 0.8em;"><i class="fa-solid fa-gear me-1"></i> Create roles in Server Settings</a>';
addPermRoleList.appendChild(createLink);
}
return;
if (availableRoles.length > 0) {
const header = document.createElement('li');
header.innerHTML = '<h6 class="dropdown-header text-uppercase" style="font-size: 0.65em; color: #949ba4;">Roles</h6>';
addPermRoleList.appendChild(header);
availableRoles.forEach(role => {
const li = document.createElement('li');
li.innerHTML = `<a class="dropdown-item d-flex align-items-center gap-2 py-2" href="#">
<div style="width: 14px; height: 14px; border-radius: 50%; background-color: ${role.color || '#99aab5'}; border: 1px solid var(--separator);"></div>
<span style="color: var(--text-primary); font-size: 0.9em;">${role.name}</span>
</a>`;
li.onclick = async (e) => {
e.preventDefault();
const postResp = await fetch('api_v1_channel_permissions.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ channel_id: channelId, role_id: role.id, allow: 0, deny: 0 })
});
const postData = await postResp.json();
if (postData.success) {
await loadChannelPermissions(channelId);
await loadRolesForPermissions(channelId);
selectOverrideItem(role.id, role.name, 'role');
}
};
addPermRoleList.appendChild(li);
});
}
// Add Roles section
const header = document.createElement('li');
header.innerHTML = '<h6 class="dropdown-header text-uppercase" style="font-size: 0.65em; color: #949ba4;">Roles</h6>';
addPermRoleList.appendChild(header);
// Members Section
const existingUserIds = channelPermissionsData.filter(p => p.type === 'member').map(p => parseInt(p.user_id));
const availableMembers = data.members.filter(m => !existingUserIds.includes(parseInt(m.id)));
availableRoles.forEach(role => {
const li = document.createElement('li');
li.innerHTML = `<a class="dropdown-item d-flex align-items-center gap-2 py-2" href="#">
<div style="width: 14px; height: 14px; border-radius: 50%; background-color: ${role.color || '#99aab5'}; border: 1px solid var(--separator);"></div>
<span style="color: var(--text-primary); font-size: 0.9em;">${role.name}</span>
</a>`;
li.onclick = async (e) => {
e.preventDefault();
const postResp = await fetch('api_v1_channel_permissions.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ channel_id: channelId, role_id: role.id, allow: 0, deny: 0 })
});
const postData = await postResp.json();
if (postData.success) {
await loadChannelPermissions(channelId);
await loadRolesForPermissions(channelId);
selectOverrideRole(role.id, role.name);
} else {
alert("Error adding permission: " + (postData.error || "Unknown error"));
}
};
addPermRoleList.appendChild(li);
});
} else {
addPermRoleList.innerHTML = `<li><span class="dropdown-item text-danger">Error: ${data.error || 'Failed to load'}</span></li>`;
if (availableMembers.length > 0) {
const header = document.createElement('li');
header.innerHTML = '<h6 class="dropdown-header text-uppercase mt-2" style="font-size: 0.65em; color: #949ba4;">Members</h6>';
addPermRoleList.appendChild(header);
availableMembers.forEach(m => {
const li = document.createElement('li');
li.innerHTML = `<a class="dropdown-item d-flex align-items-center gap-2 py-2" href="#">
<img src="${m.avatar_url || 'assets/images/default-avatar.png'}" style="width: 20px; height: 20px; border-radius: 50%;">
<span style="color: var(--text-primary); font-size: 0.9em;">${m.username}</span>
</a>`;
li.onclick = async (e) => {
e.preventDefault();
const postResp = await fetch('api_v1_channel_permissions.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ channel_id: channelId, user_id: m.id, allow: 0, deny: 0 })
});
const postData = await postResp.json();
if (postData.success) {
await loadChannelPermissions(channelId);
await loadRolesForPermissions(channelId);
selectOverrideItem(m.id, m.username, 'member');
}
};
addPermRoleList.appendChild(li);
});
}
}
} catch (err) {
addPermRoleList.innerHTML = '<li><span class="dropdown-item text-danger">Network error</span></li>';
console.error(err);
}
}
@ -1148,14 +1178,14 @@ document.addEventListener('DOMContentLoaded', () => {
function renderRoleOverridesList(channelId) {
channelPermissionsRolesList.innerHTML = '';
if (channelPermissionsData.length === 0) {
channelPermissionsRolesList.innerHTML = '<div class="text-center p-3 small" style="color: var(--text-primary);">No overrides configured for this channel.</div>';
channelPermissionsRolesList.innerHTML = '<div class="text-center p-3 small" style="color: var(--text-primary);">No overrides configured.</div>';
return;
}
// Sort: @everyone always at top, then by name
const sortedData = [...channelPermissionsData].sort((a, b) => {
const nameA = (a.role_name || '').toLowerCase();
const nameB = (b.role_name || '').toLowerCase();
if (a.type !== b.type) return a.type === 'role' ? -1 : 1;
const nameA = (a.display_name || '').toLowerCase();
const nameB = (b.display_name || '').toLowerCase();
const isAEveryone = nameA.includes('everyone');
const isBEveryone = nameB.includes('everyone');
if (isAEveryone && !isBEveryone) return -1;
@ -1165,34 +1195,46 @@ document.addEventListener('DOMContentLoaded', () => {
sortedData.forEach(p => {
const item = document.createElement('div');
item.className = `list-group-item list-group-item-action bg-transparent text-white border-0 mb-1 p-2 small d-flex align-items-center ${currentSelectedOverrideRole == p.role_id ? 'active' : ''}`;
const isActive = currentSelectedOverrideRole == (p.type === 'role' ? p.role_id : p.user_id) && currentSelectedOverrideType === p.type;
item.className = `list-group-item list-group-item-action bg-transparent text-white border-0 mb-1 p-2 small d-flex align-items-center ${isActive ? 'active' : ''}`;
item.style.cursor = 'pointer';
let icon = '';
if (p.type === 'role') {
icon = `<div style="width: 8px; height: 8px; border-radius: 50%; background-color: ${p.role_color || '#99aab5'}; margin-right: 8px; flex-shrink: 0;"></div>`;
} else {
icon = `<img src="${p.member_avatar || 'assets/images/default-avatar.png'}" style="width: 16px; height: 16px; border-radius: 50%; margin-right: 8px; flex-shrink: 0;">`;
}
item.innerHTML = `
<div style="width: 8px; height: 8px; border-radius: 50%; background-color: ${p.role_color || '#99aab5'}; margin-right: 8px; flex-shrink: 0;"></div>
<span class="flex-grow-1 text-truncate">${p.role_name || 'Unknown Role'}</span>
${icon}
<span class="flex-grow-1 text-truncate">${p.display_name}</span>
`;
item.onclick = () => selectOverrideRole(p.role_id, p.role_name || 'Unknown Role');
item.onclick = () => selectOverrideItem(p.type === 'role' ? p.role_id : p.user_id, p.display_name, p.type);
channelPermissionsRolesList.appendChild(item);
});
}
function selectOverrideRole(roleId, roleName) {
currentSelectedOverrideRole = roleId;
let currentSelectedOverrideType = 'role';
function selectOverrideItem(id, name, type) {
currentSelectedOverrideRole = id;
currentSelectedOverrideType = type;
const channelId = document.getElementById('edit-channel-id').value;
// Update list active state
renderRoleOverridesList(channelId);
selectedPermRoleName.textContent = roleName;
selectedPermRoleName.textContent = name;
noRoleSelectedView.classList.add('d-none');
channelPermissionsSettings.classList.remove('d-none');
// Load existing permissions for this role
const p = channelPermissionsData.find(perm => perm.role_id == roleId) || { allow_permissions: 0, deny_permissions: 0 };
const p = channelPermissionsData.find(perm => {
if (type === 'role') return perm.role_id == id && perm.type === 'role';
return perm.user_id == id && perm.type === 'member';
}) || { allow_permissions: 0, deny_permissions: 0 };
// Update toggles
updateToggleUI(1, p.allow_permissions, p.deny_permissions); // View Channel
updateToggleUI(2, p.allow_permissions, p.deny_permissions); // Send Messages
updateToggleUI(1, p.allow_permissions, p.deny_permissions);
updateToggleUI(2, p.allow_permissions, p.deny_permissions);
}
function updateToggleUI(bit, allowPerms, denyPerms) {
@ -1212,10 +1254,17 @@ document.addEventListener('DOMContentLoaded', () => {
if (!currentSelectedOverrideRole) return;
const channelId = document.getElementById('edit-channel-id').value;
const payload = { channel_id: channelId };
if (currentSelectedOverrideType === 'role') {
payload.role_id = currentSelectedOverrideRole;
} else {
payload.user_id = currentSelectedOverrideRole;
}
await fetch('api_v1_channel_permissions.php', {
method: 'DELETE',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ channel_id: channelId, role_id: currentSelectedOverrideRole })
body: JSON.stringify(payload)
});
currentSelectedOverrideRole = null;
@ -1230,11 +1279,17 @@ document.addEventListener('DOMContentLoaded', () => {
const bit = parseInt(group.dataset.permBit);
const val = e.target.value;
const channelId = document.getElementById('edit-channel-id').value;
const roleId = currentSelectedOverrideRole;
const id = currentSelectedOverrideRole;
const type = currentSelectedOverrideType;
let p = channelPermissionsData.find(perm => perm.role_id == roleId);
let p = channelPermissionsData.find(perm => {
if (type === 'role') return perm.role_id == id && perm.type === 'role';
return perm.user_id == id && perm.type === 'member';
});
if (!p) {
p = { role_id: roleId, allow_permissions: 0, deny_permissions: 0 };
p = { channel_id: channelId, allow_permissions: 0, deny_permissions: 0 };
if (type === 'role') p.role_id = id; else p.user_id = id;
}
let allow = parseInt(p.allow_permissions);
@ -1247,10 +1302,13 @@ document.addEventListener('DOMContentLoaded', () => {
if (val === 'allow') allow |= bit;
if (val === 'deny') deny |= bit;
const payload = { channel_id: channelId, allow, deny };
if (type === 'role') payload.role_id = id; else payload.user_id = id;
await fetch('api_v1_channel_permissions.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ channel_id: channelId, role_id: roleId, allow, deny })
body: JSON.stringify(payload)
});
// Update local data

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

View File

@ -1 +1 @@
{"de65a0b0b1a29c9a":{"id":"de65a0b0b1a29c9a","user_id":2,"name":"swefpifh ᵇʰᶠʳ","avatar_url":"","last_seen":1771343410040}}
{"b2dca0f015f8373d":{"id":"b2dca0f015f8373d","user_id":3,"name":"swefheim","avatar_url":"","last_seen":1771431264239}}

View File

@ -1 +1 @@
{"920464469dc771ed":{"id":"920464469dc771ed","user_id":3,"name":"swefheim","avatar_url":"","last_seen":1771343410598}}
{"42293ef053eb80b0":{"id":"42293ef053eb80b0","user_id":2,"name":"swefpifh ᵇʰᶠʳ","avatar_url":"","last_seen":1771431325495}}

View File

@ -1999,14 +1999,20 @@ async function handleSaveUserSettings(btn) {
<div class="col-4 border-end border-secondary d-flex flex-column" style="background-color: #2b2d31;">
<div class="p-3 border-bottom border-secondary d-flex justify-content-between align-items-center">
<span class="small fw-bold text-uppercase" style="font-size: 0.7em; color: #dbdee1;">Roles / Members</span>
<div class="dropdown">
<button class="btn btn-sm btn-link text-white p-0" type="button" data-bs-toggle="dropdown" title="Add Role or Member" style="text-decoration: none; opacity: 0.8;">
<i class="fa-solid fa-plus-circle" style="font-size: 1.1rem;"></i>
</button>
<ul class="dropdown-menu dropdown-menu-dark shadow border-secondary" id="add-permission-role-list" style="max-height: 300px; overflow-y: auto; min-width: 200px;">
<!-- Roles loaded here -->
</ul>
</div>
<div class="d-flex align-items-center gap-2">
<div class="dropdown">
<button class="btn btn-sm btn-link text-white p-0" type="button" data-bs-toggle="dropdown" title="Add Role or Member" style="text-decoration: none; opacity: 0.8;">
<i class="fa-solid fa-plus-circle" style="font-size: 1.1rem;"></i>
</button>
<ul class="dropdown-menu dropdown-menu-dark shadow border-secondary" id="add-permission-role-list" style="max-height: 300px; overflow-y: auto; min-width: 200px;">
<!-- Roles loaded here -->
</ul>
</div>
<div class="search-container position-relative" style="width: 80px;">
<input type="text" id="search-channel-perms" class="form-control form-control-sm bg-dark text-white border-0 py-0" style="font-size: 0.75em; border-radius: 4px;" placeholder="@everyone">
</div>
</div>
</div>
<div id="channel-permissions-roles-list" class="list-group list-group-flush overflow-auto flex-grow-1" style="max-height: 350px; overflow-x: hidden;">
<!-- List of roles with overrides -->
</div>

View File

@ -632,3 +632,10 @@
{"date":"2026-02-17 15:47:36","method":"POST","post":{"avatar_url":"","display_name":"swefpifh \u1d47\u02b0\u1da0\u02b3","theme":"dark","voice_mode":"ptt","voice_ptt_key":"v","voice_vox_threshold":"0.06","dnd_mode":"1","sound_notifications":"1"},"session":{"user_id":2},"user_id":2,"db_success":true}
{"date":"2026-02-17 15:48:00","method":"POST","post":{"avatar_url":"","display_name":"swefheim","theme":"light","voice_mode":"vox","voice_ptt_key":"v","voice_vox_threshold":"0","dnd_mode":"0","sound_notifications":"0"},"session":{"user_id":3},"user_id":3,"db_success":true}
{"date":"2026-02-17 15:48:41","method":"POST","post":{"avatar_url":"","display_name":"swefpifh \u1d47\u02b0\u1da0\u02b3","theme":"dark","voice_mode":"vox","voice_ptt_key":"v","voice_vox_threshold":"0.06","dnd_mode":"1","sound_notifications":"1"},"session":{"user_id":2},"user_id":2,"db_success":true}
2026-02-18 16:03:28 - GET /?fl_project=38443 - POST: []
2026-02-18 16:03:33 - GET / - POST: []
2026-02-18 16:03:54 - GET /index.php?server_id=1&channel_id=3 - POST: []
2026-02-18 16:04:41 - GET /?server_id=1&channel_id=18 - POST: []
2026-02-18 16:04:48 - GET /?server_id=1&channel_id=18 - POST: []
2026-02-18 16:12:25 - GET /?fl_project=38443 - POST: []
2026-02-18 16:14:55 - GET /?server_id=1&channel_id=18 - POST: []