Implement RBAC migration and role management enhancements

- Added AJAX functionality to fetch role permissions for copying.
- Introduced system role management with permission checks for updates.
- Implemented role deletion with confirmation modal and backend handling.
- Enhanced user role assignment migration scripts to transition from legacy profiles to RBAC.
- Created SQL migration scripts for user roles and permissions mapping.
- Updated user interface to support new role management features including copy permissions and system role indicators.
This commit is contained in:
“VeLiTi”
2026-01-27 15:10:21 +01:00
parent aeda4e4cb9
commit f7a91737bc
30 changed files with 1285 additions and 236 deletions

View File

@@ -249,15 +249,19 @@ class MarketingFileManager {
emptyState.style.display = 'none';
try {
// Use proper folder ID (null for root, or the folder ID)
const folderId = this.currentFolder ? this.currentFolder : 'null';
// Add cache busting to prevent browser caching
let url = `index.php?page=marketing&action=marketing_files&folder_id=${folderId}&limit=50&_t=${Date.now()}`;
let url = `index.php?page=marketing&action=marketing_files&limit=50&_t=${Date.now()}`;
// Only filter by folder if no tag filter is active (tag search is across all folders)
if (!this.filters.tag) {
const folderId = this.currentFolder ? this.currentFolder : 'null';
url += `&folder_id=${folderId}`;
}
if (this.filters.search) {
url += `&search=${encodeURIComponent(this.filters.search)}`;
}
if (this.filters.tag) {
url += `&tag=${encodeURIComponent(this.filters.tag)}`;
}
@@ -289,21 +293,33 @@ class MarketingFileManager {
if (data && data.length > 0) {
let files = data;
// Client-side file type filtering
if (this.filters.fileTypes.length > 0) {
files = files.filter(file =>
files = files.filter(file =>
this.filters.fileTypes.includes(file.file_type.toLowerCase())
);
}
if (files.length === 0) {
emptyState.style.display = 'block';
// No files after filtering, check for subfolders
const subfolders = this.getSubfolders(this.currentFolder);
if (subfolders.length > 0) {
this.renderFolderTiles(subfolders);
} else {
emptyState.style.display = 'block';
}
} else {
this.renderFiles(files);
}
} else {
emptyState.style.display = 'block';
// No files, check for subfolders
const subfolders = this.getSubfolders(this.currentFolder);
if (subfolders.length > 0) {
this.renderFolderTiles(subfolders);
} else {
emptyState.style.display = 'block';
}
}
} catch (error) {
console.error('Error loading files:', error);
@@ -372,12 +388,73 @@ class MarketingFileManager {
renderFiles(files) {
const container = document.getElementById('filesContainer');
container.innerHTML = '';
files.forEach(file => {
const fileElement = this.createFileElement(file);
container.appendChild(fileElement);
});
}
getSubfolders(folderId) {
// Find immediate children of the specified folder
if (!folderId || folderId === '') {
// Root folder - return top-level folders
return this.folders;
}
// Recursively search for the folder and return its children
const findFolder = (folders, targetId) => {
for (const folder of folders) {
if (folder.id === targetId) {
return folder.children || [];
}
if (folder.children && folder.children.length > 0) {
const found = findFolder(folder.children, targetId);
if (found) return found;
}
}
return [];
};
return findFolder(this.folders, folderId);
}
renderFolderTiles(subfolders) {
const container = document.getElementById('filesContainer');
container.innerHTML = '';
subfolders.forEach(folder => {
const folderElement = this.createFolderTileElement(folder);
container.appendChild(folderElement);
});
}
createFolderTileElement(folder) {
const folderElement = document.createElement('div');
folderElement.className = `folder-tile ${this.viewMode}-item`;
folderElement.setAttribute('data-folder-id', folder.id);
folderElement.innerHTML = `
<div class="folder-tile-icon">
<i class="fa fa-folder"></i>
</div>
<div class="folder-tile-info">
<div class="folder-tile-name" title="${this.escapeHtml(folder.folder_name)}">
${this.escapeHtml(folder.folder_name)}
</div>
<div class="folder-tile-meta">
<span class="folder-file-count">${folder.file_count || 0} files</span>
</div>
</div>
`;
// Click to navigate to folder
folderElement.addEventListener('click', () => {
this.selectFolder(folder.id);
});
return folderElement;
}
createFileElement(file) {
const fileElement = document.createElement('div');
@@ -385,7 +462,7 @@ class MarketingFileManager {
fileElement.setAttribute('data-file-id', file.id);
const thumbnail = this.getThumbnail(file);
const tags = file.tags.map(tag => `<span class="tag">${this.escapeHtml(tag)}</span>`).join('');
const tags = file.tags.map(tag => `<span class="tag clickable" data-tag="${this.escapeHtml(tag)}">${this.escapeHtml(tag)}</span>`).join('');
fileElement.innerHTML = `
<div class="file-thumbnail">
@@ -429,11 +506,20 @@ class MarketingFileManager {
fileElement.querySelector('.edit-btn').addEventListener('click', () => {
this.editFile(file);
});
// Make tags clickable to filter by tag
fileElement.querySelectorAll('.tag.clickable').forEach(tagElement => {
tagElement.addEventListener('click', (e) => {
e.stopPropagation();
const tagName = tagElement.getAttribute('data-tag');
this.filterByTag(tagName);
});
});
fileElement.addEventListener('dblclick', () => {
this.previewFile(file);
});
return fileElement;
}
@@ -843,15 +929,41 @@ class MarketingFileManager {
updateFileTypeFilters() {
this.filters.fileTypes = [];
document.querySelectorAll('.file-type-filters input[type="checkbox"]:checked').forEach(checkbox => {
const types = checkbox.value.split(',');
this.filters.fileTypes.push(...types);
});
this.loadFiles();
}
filterByTag(tagName) {
// Set the tag filter
this.filters.tag = tagName;
// Update the dropdown to show the selected tag
const tagSelect = document.getElementById('tagFilter');
if (tagSelect) {
tagSelect.value = tagName;
}
// Clear folder selection to search across all folders
this.currentFolder = '';
// Update folder tree UI to show root as active
document.querySelectorAll('.folder-item').forEach(item => {
item.classList.remove('active');
});
const rootFolder = document.querySelector('.folder-item.root');
if (rootFolder) {
rootFolder.classList.add('active');
}
// Reload files with the tag filter
this.loadFiles();
}
populateTagFilter(tags) {
const select = document.getElementById('tagFilter');
select.innerHTML = '<option value="">All Tags</option>';