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:
BIN
assets/.DS_Store
vendored
BIN
assets/.DS_Store
vendored
Binary file not shown.
65
assets/database/user_rbac
Normal file
65
assets/database/user_rbac
Normal 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;
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
BIN
assets/images/.DS_Store
vendored
BIN
assets/images/.DS_Store
vendored
Binary file not shown.
@@ -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);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user