Add user role management functionality with CRUD operations and permissions handling

- Created user_role.php for viewing and editing user roles and their permissions.
- Implemented inline editing for role details and permissions.
- Added user_role_manage.php for creating and managing user roles.
- Introduced user_roles.php for listing all user roles with pagination and filtering options.
- Integrated API calls for fetching and updating role data and permissions.
- Enhanced user interface with success messages and navigation controls.
This commit is contained in:
“VeLiTi”
2026-01-19 11:16:54 +01:00
parent 3db13b9ebf
commit 782050c3ca
35 changed files with 4071 additions and 370 deletions

BIN
assets/.DS_Store vendored

Binary file not shown.

65
assets/database/user_rbac Normal file
View File

@@ -0,0 +1,65 @@
CREATE TABLE `user_roles` (
`rowID` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(100) NOT NULL,
`description` text DEFAULT NULL,
`is_active` tinyint(1) NOT NULL DEFAULT 1,
`created` timestamp NULL DEFAULT current_timestamp(),
`createdby` int(11) DEFAULT NULL,
`updated` timestamp NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
`updatedby` int(11) DEFAULT NULL,
PRIMARY KEY (`rowID`),
UNIQUE KEY `unique_role_name` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=19 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
CREATE TABLE `user_role_assignments` (
`rowID` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`role_id` int(11) NOT NULL,
`is_active` tinyint(1) NOT NULL DEFAULT 1,
`assigned_by` varchar(255) DEFAULT NULL,
`assigned_at` timestamp NULL DEFAULT current_timestamp(),
`expires_at` timestamp NULL DEFAULT NULL,
`created` timestamp NULL DEFAULT current_timestamp(),
`createdby` int(11) DEFAULT NULL,
`updated` timestamp NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
`updatedby` int(11) DEFAULT NULL,
PRIMARY KEY (`rowID`),
UNIQUE KEY `unique_user_role_active` (`user_id`,`role_id`,`is_active`),
KEY `role_id` (`role_id`),
CONSTRAINT `user_role_assignments_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`rowID`) ON DELETE CASCADE,
CONSTRAINT `user_role_assignments_ibfk_2` FOREIGN KEY (`role_id`) REFERENCES `user_roles` (`rowID`) ON DELETE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=26 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
REATE TABLE `role_access_permissions` (
`rowID` int(11) NOT NULL AUTO_INCREMENT,
`role_id` int(11) NOT NULL,
`access_id` int(11) NOT NULL,
`can_create` tinyint(1) NOT NULL DEFAULT 0,
`can_read` tinyint(1) NOT NULL DEFAULT 1,
`can_update` tinyint(1) NOT NULL DEFAULT 0,
`can_delete` tinyint(1) NOT NULL DEFAULT 0,
`created` timestamp NULL DEFAULT current_timestamp(),
`createdby` int(11) DEFAULT NULL,
`updated` timestamp NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
`updatedby` int(11) DEFAULT NULL,
PRIMARY KEY (`rowID`),
UNIQUE KEY `unique_role_view` (`role_id`,`access_id`),
KEY `access_id` (`access_id`),
CONSTRAINT `role_view_permissions_ibfk_1` FOREIGN KEY (`role_id`) REFERENCES `user_roles` (`rowID`) ON DELETE CASCADE,
CONSTRAINT `role_view_permissions_ibfk_2` FOREIGN KEY (`access_id`) REFERENCES `system_views` (`rowID`) ON DELETE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=112 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
CREATE TABLE `access_elements` (
`rowID` int(11) NOT NULL AUTO_INCREMENT,
`access_group` varchar(100) NOT NULL,
`access_name` varchar(100) NOT NULL,
`access_path` varchar(255) NOT NULL,
`description` text DEFAULT NULL,
`is_active` tinyint(1) NOT NULL DEFAULT 1,
`created` timestamp NULL DEFAULT current_timestamp(),
`createdby` int(11) DEFAULT NULL,
`updated` timestamp NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
`updatedby` int(11) DEFAULT NULL,
PRIMARY KEY (`rowID`),
UNIQUE KEY `unique_access_path` (`access_path`)
) ENGINE=InnoDB AUTO_INCREMENT=393 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

View File

@@ -1541,6 +1541,12 @@ function getProfile($profile, $permission){
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
function isAllowed($page,$profile,$permission,$action){
//++++++++++++++++
//OVERRIDE
//++++++++++++++++
return 1;
//++++++++++++++++
//Include settingsa
include dirname(__FILE__,2).'/settings/settings_redirector.php';
@@ -1573,13 +1579,17 @@ function getProfile($profile, $permission){
// Debug log
if(debug){
debuglog("isAllowed called: page=$page, permission=$permission, action=$action");
$test = "$date - isAllowed called: page=$page, permission=$permission, action=$action".PHP_EOL;
$filelocation = dirname(__FILE__,2).'/log/permission_log_'.date('d').'.txt';
error_log($test, 3, $filelocation);
}
// 1. Check always allowed
if (isset($always_allowed[$page]) && str_contains($always_allowed[$page], $action)) {
if(debug){
debuglog("Allowed by always_allowed");
$test = "$date - Allowed by always_allowed".PHP_EOL;
$filelocation = dirname(__FILE__,2).'/log/permission_log_'.date('d').'.txt';
error_log($test, 3, $filelocation);
}
return 1;
@@ -1593,13 +1603,18 @@ function getProfile($profile, $permission){
$page_access = str_contains($profile,$page) > 0 ? 1 : 0; //CHECK USER IS ALLOWED TO ACCESS PAGE
if(debug){
debuglog("user_permission=$user_permission, page_action=$page_action, page_access=$page_access");
$test = "$date - user_permission=$user_permission, page_action=$page_action, page_access=$page_access".PHP_EOL;
$filelocation = dirname(__FILE__,2).'/log/permission_log_'.date('d').'.txt';
error_log($test, 3, $filelocation);
}
// 2. Check user permissions (standard)
if ($page_access == 1 && $page_action == 1){
if(debug){
debuglog("Allowed by user permissions");
$test = "$date - Allowed by user permissions".PHP_EOL;
$filelocation = dirname(__FILE__,2).'/log/permission_log_'.date('d').'.txt';
error_log($test, 3, $filelocation);
}
return 1;
}
@@ -1609,11 +1624,15 @@ function getProfile($profile, $permission){
foreach ($group_permissions as $granting_page => $grants) {
if (str_contains($profile, $granting_page)) {
if(debug){
debuglog("Found granting_page: $granting_page");
$test = "$date - Found granting_page: $granting_page".PHP_EOL;
$filelocation = dirname(__FILE__,2).'/log/permission_log_'.date('d').'.txt';
error_log($test, 3, $filelocation);
}
if (isset($grants[$page]) && str_contains($grants[$page], $action)) {
if(debug){
debuglog("Allowed by group permissions");
$test = "$date - Allowed by group permissions".PHP_EOL;
$filelocation = dirname(__FILE__,2).'/log/permission_log_'.date('d').'.txt';
error_log($test, 3, $filelocation);
}
return 1;
}
@@ -1622,7 +1641,9 @@ function getProfile($profile, $permission){
}
if(debug){
debuglog("Not allowed");
$test = "$date - Not allowed".PHP_EOL;
$filelocation = dirname(__FILE__,2).'/log/permission_log_'.date('d').'.txt';
error_log($test, 3, $filelocation);
}
// Not allowed
return 0;
@@ -2866,7 +2887,7 @@ function uploadrequest($key){
//------------------------------------------
function debuglog($error){
include_once dirname(__FILE__,2).'/settings/config_redirector.php';
$test = $error.PHP_EOL;
$test = $error.PHP_EOL;
$filelocation = dirname(__FILE__,2).'/log/log_'.date('d').'.txt';
error_log($test, 3, $filelocation);
}

Binary file not shown.

View File

@@ -576,6 +576,8 @@ function displaySoftwareOptions(options) {
const price = parseFloat(option.price);
const isFree = price === 0;
const isCurrent = option.is_current === true || option.is_current === 1;
const dealerInfo = option.dealer_info || {};
const isDealer = dealerInfo.is_dealer === 1 || dealerInfo.is_dealer === '1';
// Create card with gradient background
const card = document.createElement("div");
@@ -680,75 +682,138 @@ function displaySoftwareOptions(options) {
margin-top: auto;
`;
const priceText = document.createElement("div");
priceText.style.cssText = `
font-size: ${isCurrent ? '18px' : '28px'};
font-weight: ${isCurrent ? '600' : '800'};
color: ${isCurrent ? '#6c757d' : (isFree ? '#04AA6D' : '#FF6B35')};
margin-bottom: 15px;
text-align: center;
letter-spacing: 0.5px;
`;
// Check if this is a dealer customer - show dealer contact info instead of price/buy button
if (isDealer && !isCurrent && !isFree) {
// Dealer info section - replaces price and buy button
const dealerSection = document.createElement("div");
dealerSection.style.cssText = `
background: linear-gradient(135deg, rgb(255, 107, 53) 0%, rgb(255, 69, 0) 100%);
border-radius: 4px;
padding: 15px;
text-align: center;
`;
if (isCurrent) {
priceText.innerHTML = '<i class="fa-solid fa-check-circle"></i> INSTALLED';
// Contact dealer message
const dealerMessage = document.createElement("div");
dealerMessage.style.cssText = `
color: white;
font-size: 14px;
font-weight: 600;
margin-bottom: 12px;
`;
dealerMessage.innerHTML = '<i class="fa-solid fa-handshake" style="margin-right: 8px;"></i>Contact your dealer for pricing and upgrade options';
dealerSection.appendChild(dealerMessage);
// Dealer contact details
const dealerDetails = document.createElement("div");
dealerDetails.style.cssText = `
background: white;
border-radius: 4px;
padding: 12px;
text-align: left;
font-size: 13px;
color: #333;
`;
let dealerHtml = '';
if (dealerInfo.name) {
dealerHtml += `<div style="font-weight: 600; margin-bottom: 8px; color: #1565c0;"><i class="fa-solid fa-building" style="margin-right: 8px; width: 16px;"></i>${dealerInfo.name}</div>`;
}
if (dealerInfo.address || dealerInfo.city || dealerInfo.postalcode || dealerInfo.country) {
dealerHtml += `<div style="margin-bottom: 6px; color: #666; display: flex; align-items: flex-start;"><i class="fa-solid fa-location-dot" style="margin-right: 8px; width: 16px; color: #999; margin-top: 2px;"></i><div>`;
if (dealerInfo.address) {
dealerHtml += `<div>${dealerInfo.address}</div>`;
}
if (dealerInfo.postalcode || dealerInfo.city) {
dealerHtml += `<div>${[dealerInfo.postalcode, dealerInfo.city].filter(Boolean).join(' ')}</div>`;
}
if (dealerInfo.country) {
dealerHtml += `<div>${dealerInfo.country}</div>`;
}
dealerHtml += `</div></div>`;
}
if (dealerInfo.email) {
dealerHtml += `<div style="margin-bottom: 6px;"><i class="fa-solid fa-envelope" style="margin-right: 8px; width: 16px; color: #999;"></i><a href="mailto:${dealerInfo.email}" style="color: #1565c0; text-decoration: none;">${dealerInfo.email}</a></div>`;
}
if (dealerInfo.phone) {
dealerHtml += `<div><i class="fa-solid fa-phone" style="margin-right: 8px; width: 16px; color: #999;"></i><a href="tel:${dealerInfo.phone}" style="color: #1565c0; text-decoration: none;">${dealerInfo.phone}</a></div>`;
}
dealerDetails.innerHTML = dealerHtml;
dealerSection.appendChild(dealerDetails);
priceSection.appendChild(dealerSection);
} else {
priceText.innerHTML = isFree
? 'Free'
: `${option.currency || "€"} ${price.toFixed(2)} <small style="font-size: 12px; font-weight: 400; color: #888;">(excl. VAT)</small>`;
// Standard price display for non-dealer customers
const priceText = document.createElement("div");
priceText.style.cssText = `
font-size: ${isCurrent ? '18px' : '28px'};
font-weight: ${isCurrent ? '600' : '800'};
color: ${isCurrent ? '#6c757d' : (isFree ? '#04AA6D' : '#FF6B35')};
margin-bottom: 15px;
text-align: center;
letter-spacing: 0.5px;
`;
if (isCurrent) {
priceText.innerHTML = '<i class="fa-solid fa-check-circle"></i> INSTALLED';
} else {
priceText.innerHTML = isFree
? 'Free'
: `${option.currency || "€"} ${price.toFixed(2)} <small style="font-size: 12px; font-weight: 400; color: #888;">(excl. VAT)</small>`;
}
priceSection.appendChild(priceText);
// Action button with gradient for paid
const actionBtn = document.createElement("button");
actionBtn.className = "btn";
actionBtn.style.cssText = `
width: 100%;
background: ${isCurrent ? '#6c757d' : (isFree ? 'linear-gradient(135deg, #04AA6D 0%, #038f5a 100%)' : 'linear-gradient(135deg, #FF6B35 0%, #FF4500 100%)')};
color: white;
border: none;
cursor: ${isCurrent ? 'not-allowed' : 'pointer'};
transition: all 0.3s ease;
opacity: ${isCurrent ? '0.5' : '1'};
box-shadow: ${isCurrent ? 'none' : '0 4px 12px rgba(0,0,0,0.15)'};
letter-spacing: 0.5px;
text-transform: uppercase;
`;
if (isCurrent) {
actionBtn.innerHTML = '<i class="fa-solid fa-check"></i> Currently Installed';
actionBtn.disabled = true;
} else if (isFree) {
actionBtn.innerHTML = '<i class="fa-solid fa-download"></i>';
actionBtn.onclick = () => selectUpgrade(option);
actionBtn.onmouseenter = () => {
actionBtn.style.background = 'linear-gradient(135deg, #038f5a 0%, #026b43 100%)';
actionBtn.style.transform = 'translateY(-2px)';
actionBtn.style.boxShadow = '0 6px 16px rgba(0,0,0,0.2)';
};
actionBtn.onmouseleave = () => {
actionBtn.style.background = 'linear-gradient(135deg, #04AA6D 0%, #038f5a 100%)';
actionBtn.style.transform = 'translateY(0)';
actionBtn.style.boxShadow = '0 4px 12px rgba(0,0,0,0.15)';
};
} else {
actionBtn.innerHTML = '<i class="fa-solid fa-shopping-cart"></i>';
actionBtn.onclick = () => selectUpgrade(option);
actionBtn.onmouseenter = () => {
actionBtn.style.background = 'linear-gradient(135deg, #FF4500 0%, #CC3700 100%)';
actionBtn.style.transform = 'translateY(-2px)';
actionBtn.style.boxShadow = '0 6px 16px rgba(255,107,53,0.4)';
};
actionBtn.onmouseleave = () => {
actionBtn.style.background = 'linear-gradient(135deg, #FF6B35 0%, #FF4500 100%)';
actionBtn.style.transform = 'translateY(0)';
actionBtn.style.boxShadow = '0 4px 12px rgba(0,0,0,0.15)';
};
}
priceSection.appendChild(actionBtn);
}
priceSection.appendChild(priceText);
// Action button with gradient for paid
const actionBtn = document.createElement("button");
actionBtn.className = "btn";
actionBtn.style.cssText = `
width: 100%;
background: ${isCurrent ? '#6c757d' : (isFree ? 'linear-gradient(135deg, #04AA6D 0%, #038f5a 100%)' : 'linear-gradient(135deg, #FF6B35 0%, #FF4500 100%)')};
color: white;
border: none;
cursor: ${isCurrent ? 'not-allowed' : 'pointer'};
transition: all 0.3s ease;
opacity: ${isCurrent ? '0.5' : '1'};
box-shadow: ${isCurrent ? 'none' : '0 4px 12px rgba(0,0,0,0.15)'};
letter-spacing: 0.5px;
text-transform: uppercase;
`;
if (isCurrent) {
actionBtn.innerHTML = '<i class="fa-solid fa-check"></i> Currently Installed';
actionBtn.disabled = true;
} else if (isFree) {
actionBtn.innerHTML = '<i class="fa-solid fa-download"></i>';
actionBtn.onclick = () => selectUpgrade(option);
actionBtn.onmouseenter = () => {
actionBtn.style.background = 'linear-gradient(135deg, #038f5a 0%, #026b43 100%)';
actionBtn.style.transform = 'translateY(-2px)';
actionBtn.style.boxShadow = '0 6px 16px rgba(0,0,0,0.2)';
};
actionBtn.onmouseleave = () => {
actionBtn.style.background = 'linear-gradient(135deg, #04AA6D 0%, #038f5a 100%)';
actionBtn.style.transform = 'translateY(0)';
actionBtn.style.boxShadow = '0 4px 12px rgba(0,0,0,0.15)';
};
} else {
actionBtn.innerHTML = '<i class="fa-solid fa-shopping-cart"></i>';
actionBtn.onclick = () => selectUpgrade(option);
actionBtn.onmouseenter = () => {
actionBtn.style.background = 'linear-gradient(135deg, #FF4500 0%, #CC3700 100%)';
actionBtn.style.transform = 'translateY(-2px)';
actionBtn.style.boxShadow = '0 6px 16px rgba(255,107,53,0.4)';
};
actionBtn.onmouseleave = () => {
actionBtn.style.background = 'linear-gradient(135deg, #FF6B35 0%, #FF4500 100%)';
actionBtn.style.transform = 'translateY(0)';
actionBtn.style.boxShadow = '0 4px 12px rgba(0,0,0,0.15)';
};
}
priceSection.appendChild(actionBtn);
card.appendChild(priceSection);
grid.appendChild(card);
});