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

BIN
.DS_Store vendored

Binary file not shown.

View File

@@ -134,7 +134,7 @@ if($is_jwt_valid && str_contains($version, 'v')) {
//------------------------------------------ //------------------------------------------
// Check for maintenance mode, exclude debug user // Check for maintenance mode, exclude debug user
//------------------------------------------ //------------------------------------------
if(maintenance_mode == false|| debug_id == $user_data['id']){ if(maintenance_mode == false || debug_id == $user_data['id']){
//------------------------------------------ //------------------------------------------
// Build up version and check if file is available // Build up version and check if file is available

View File

@@ -14,9 +14,6 @@ if (empty($partner->soldto) || $partner->soldto == ''){$soldto_search = '%';} el
//default whereclause //default whereclause
$whereclause = ''; $whereclause = '';
// For testing, disable account hierarchy filtering
// list($whereclause,$condition) = getWhereclauselvl2("",$permission,$partner,'get');
//NEW ARRAY //NEW ARRAY
$criterias = []; $criterias = [];
$clause = ''; $clause = '';

View File

@@ -31,8 +31,6 @@ if (empty($partner->soldto) || $partner->soldto == ''){$soldto_search = '%';} el
//default whereclause //default whereclause
$whereclause = ''; $whereclause = '';
list($whereclause,$condition) = getWhereclauselvl2('',$permission,$partner,'get');
//NEW ARRAY //NEW ARRAY
$criterias = []; $criterias = [];
$clause = ''; $clause = '';
@@ -110,11 +108,6 @@ else {
$stmt = $pdo->prepare($sql); $stmt = $pdo->prepare($sql);
//Bind to query
if (str_contains($whereclause, ':condition')){
$stmt->bindValue('condition', $condition, PDO::PARAM_STR);
}
if (!empty($criterias)){ if (!empty($criterias)){
foreach ($criterias as $key => $value){ foreach ($criterias as $key => $value){
$key_condition = ':'.$key; $key_condition = ':'.$key;

View File

@@ -14,9 +14,6 @@ if (empty($partner->soldto) || $partner->soldto == ''){$soldto_search = '%';} el
//default whereclause //default whereclause
$whereclause = ''; $whereclause = '';
// Tags are global, so no account hierarchy filtering
// list($whereclause,$condition) = getWhereclauselvl2("",$permission,$partner,'get');
//NEW ARRAY //NEW ARRAY
$criterias = []; $criterias = [];
$clause = ''; $clause = '';

View File

@@ -1,13 +1,17 @@
<?php <?php
defined($security_key) or exit; defined($security_key) or exit;
//------------------------------------------
//Connect to DB
//------------------------------------------
$pdo = dbConnect($dbname);
//------------------------------------------ //------------------------------------------
// Application related calls // Application related calls
//------------------------------------------ //------------------------------------------
$request = explode('/', trim($_SERVER['PATH_INFO'],'/')); $request = explode('/', trim($_SERVER['PATH_INFO'],'/'));
$action = $request[2] ?? ''; $action = $request[2] ?? '';
if ($action == 'init'){ if ($action == 'init'){
include './settings/systemservicetool_init.php'; include './settings/systemservicetool_init.php';
echo json_encode($init); echo json_encode($init);
@@ -33,6 +37,43 @@ elseif ($action == 'questions' && (isset($_GET['type']) && $_GET['type'] != ''))
} }
//Return JSON //Return JSON
echo json_encode($arrayQuestions); echo json_encode($arrayQuestions);
}
elseif ($action == 'products') {
$sql = "SELECT * FROM products";
$stmt = $pdo->prepare($sql);
$stmt->execute();
//Get results
$messages = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo json_encode($messages);
}
elseif ($action == 'equipments' && (isset($_GET['serialnumber']) && $_GET['serialnumber'] != '' && !isset($_GET['validate']))) {
$sql = "SELECT e.rowID as equipmentID, e.*, p.productcode, p.productname, p.product_media, psl.starts_at,psl.expires_at,psl.status as license_status from equipment e LEFT JOIN products p ON e.productrowid = p.rowID LEFT JOIN products_software_licenses psl ON e.sw_version_license = psl.license_key WHERE e.serialnumber = ?";
$stmt = $pdo->prepare($sql);
$stmt->execute([$_GET['serialnumber']]);
//Get results
$messages = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo json_encode($messages);
}
elseif ($action == 'equipments' && (isset($_GET['serialnumber']) && $_GET['serialnumber'] != '' && isset($_GET['validate']))){
$sql = "SELECT count(rowID) as rowID from equipment e WHERE e.serialnumber = ?";
$stmt = $pdo->prepare($sql);
$stmt->execute([$_GET['serialnumber']]);
$messages = $stmt->fetch();
if ($messages[0] == 1) {
echo json_encode(array('SN'=> TRUE));
}
else {
echo json_encode(array('SN'=> FALSE));
}
} }
else { else {
http_response_code(400); http_response_code(400);

View File

@@ -24,7 +24,7 @@ if(isset($get_content) && $get_content!=''){
$v = explode("=", $y); $v = explode("=", $y);
//INCLUDE VARIABLES IN ARRAY //INCLUDE VARIABLES IN ARRAY
$criterias[$v[0]] = $v[1]; $criterias[$v[0]] = $v[1];
if ($v[0] == 'page' || $v[0] =='p' || $v[0] =='totals' || $v[0] =='success_msg' || $v[0] =='sort'){ if ($v[0] == 'page' || $v[0] =='p' || $v[0] =='totals' || $v[0] =='success_msg' || $v[0] =='sort' || $v[0] =='all'){
//do nothing //do nothing
} }
elseif ($v[0] == 'rowid') { elseif ($v[0] == 'rowid') {
@@ -50,6 +50,11 @@ if(isset($get_content) && $get_content!=''){
} }
} }
//Filter system roles for users without delete permission on user_roles
if (isAllowed('user_roles', $profile, $permission, 'D') !== 1) {
$clause .= ' AND r.is_system != 1';
}
//Build WHERE clause //Build WHERE clause
$whereclause = ''; $whereclause = '';
if ($clause != ''){ if ($clause != ''){
@@ -81,6 +86,12 @@ if (isset($criterias['totals']) && $criterias['totals'] ==''){
//Request for total rows //Request for total rows
$sql = 'SELECT count(*) as count FROM user_roles r '.$whereclause; $sql = 'SELECT count(*) as count FROM user_roles r '.$whereclause;
} }
elseif (isset($criterias['all']) && $criterias['all'] ==''){
//Return all records (no paging)
$sql = 'SELECT r.*,
(SELECT COUNT(*) FROM role_access_permissions WHERE role_id = r.rowID) as permission_count
FROM user_roles r '.$whereclause.' ORDER BY '.$sort;
}
else { else {
//SQL with permission count //SQL with permission count
$sql = 'SELECT r.*, $sql = 'SELECT r.*,
@@ -129,6 +140,11 @@ if(isset($criterias['totals']) && $criterias['totals']==''){
$messages = $stmt->fetch(); $messages = $stmt->fetch();
$messages = $messages[0]; $messages = $messages[0];
} }
elseif(isset($criterias['all']) && $criterias['all']==''){
//Return all records (no paging)
$stmt->execute();
$messages = $stmt->fetchAll(PDO::FETCH_ASSOC);
}
else { else {
$current_page = isset($criterias['p']) && is_numeric($criterias['p']) ? (int)$criterias['p'] : 1; $current_page = isset($criterias['p']) && is_numeric($criterias['p']) ? (int)$criterias['p'] : 1;
$stmt->bindValue('page', ($current_page - 1) * $page_rows, PDO::PARAM_INT); $stmt->bindValue('page', ($current_page - 1) * $page_rows, PDO::PARAM_INT);

View File

@@ -389,8 +389,8 @@ if (isset($post_content['sn']) && (isset($post_content['payload']) || isset($pos
// Create license // Create license
$sql = 'INSERT INTO products_software_licenses $sql = 'INSERT INTO products_software_licenses
(version_id, license_type, license_key, status, starts_at, expires_at, transaction_id, created, createdby) (version_id, license_type, license_key, status, starts_at, expires_at, transaction_id, accounthierarchy,created, createdby)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)'; VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)';
$stmt = $pdo->prepare($sql); $stmt = $pdo->prepare($sql);
$stmt->execute([ $stmt->execute([
$sw_version_consent, $sw_version_consent,
@@ -400,6 +400,7 @@ if (isset($post_content['sn']) && (isset($post_content['payload']) || isset($pos
date('Y-m-d H:i:s'), date('Y-m-d H:i:s'),
'2099-12-31 23:59:59', // effectively permanent '2099-12-31 23:59:59', // effectively permanent
'Customer_consent', 'Customer_consent',
$account,
date('Y-m-d H:i:s'), date('Y-m-d H:i:s'),
$user $user
]); ]);

View File

@@ -25,12 +25,12 @@ $criterias = [];
//ADD STANDARD PARAMETERS TO ARRAY BASED ON INSERT OR UPDATE //ADD STANDARD PARAMETERS TO ARRAY BASED ON INSERT OR UPDATE
if ($command == 'update'){ if ($command == 'update'){
$post_content['updatedby'] = $username;; $post_content['updatedby'] = $username;
$post_content['updated'] = $date; $post_content['updated'] = $date;
} }
elseif ($command == 'insert'){ elseif ($command == 'insert'){
$post_content['created'] = $date; $post_content['created'] = $date;
$post_content['createdby'] = $username;; $post_content['createdby'] = $username;
} }
//CREAT NEW ARRAY AND MAP TO CLAUSE //CREAT NEW ARRAY AND MAP TO CLAUSE

BIN
assets/.DS_Store vendored

Binary file not shown.

View File

@@ -0,0 +1,222 @@
-- ===================================================
-- PROFILE TO RBAC MIGRATION SCRIPT
-- Date: 2025-01-22
-- Description: Migrate from settingsprofiles.php to user_roles RBAC system
-- Note: Uses existing access_elements table (already populated)
-- ===================================================
START TRANSACTION;
-- ===================================================
-- PHASE 1: CREATE ROLES (matching existing profiles)
-- ===================================================
INSERT INTO `user_roles` (`name`, `description`, `is_active`, `created`, `createdby`) VALUES
('Standard', 'Basic user access - view equipment, history, service reports', 1, NOW(), 1),
('Superuser', 'Extended access - manage equipment, products, users', 1, NOW(), 1),
('Admin', 'Administrative access - full management capabilities', 1, NOW(), 1),
('AdminPlus', 'System administrator - complete system access', 1, NOW(), 1),
('Build', 'Build tool access only', 1, NOW(), 1),
('Commerce', 'E-commerce and catalog management', 1, NOW(), 1),
('Distribution', 'Distribution partner access', 1, NOW(), 1),
('Firmware', 'Firmware/software update access only', 1, NOW(), 1),
('Garage', 'Car testing and diagnostics', 1, NOW(), 1),
('Interface', 'API/Interface access', 1, NOW(), 1),
('Service', 'Service technician access', 1, NOW(), 1),
('Other', 'Miscellaneous access level', 1, NOW(), 1)
ON DUPLICATE KEY UPDATE `description` = VALUES(`description`);
-- ===================================================
-- PHASE 2: CREATE ROLE_ACCESS_PERMISSIONS MAPPINGS
-- ===================================================
-- Get role IDs
SET @role_standard = (SELECT rowID FROM user_roles WHERE name = 'Standard' LIMIT 1);
SET @role_superuser = (SELECT rowID FROM user_roles WHERE name = 'Superuser' LIMIT 1);
SET @role_admin = (SELECT rowID FROM user_roles WHERE name = 'Admin' LIMIT 1);
SET @role_adminplus = (SELECT rowID FROM user_roles WHERE name = 'AdminPlus' LIMIT 1);
SET @role_build = (SELECT rowID FROM user_roles WHERE name = 'Build' LIMIT 1);
SET @role_commerce = (SELECT rowID FROM user_roles WHERE name = 'Commerce' LIMIT 1);
SET @role_distribution = (SELECT rowID FROM user_roles WHERE name = 'Distribution' LIMIT 1);
SET @role_firmware = (SELECT rowID FROM user_roles WHERE name = 'Firmware' LIMIT 1);
SET @role_garage = (SELECT rowID FROM user_roles WHERE name = 'Garage' LIMIT 1);
SET @role_interface = (SELECT rowID FROM user_roles WHERE name = 'Interface' LIMIT 1);
SET @role_service = (SELECT rowID FROM user_roles WHERE name = 'Service' LIMIT 1);
SET @role_other = (SELECT rowID FROM user_roles WHERE name = 'Other' LIMIT 1);
-- ===================================================
-- STANDARD ROLE PERMISSIONS (Read-only)
-- Profile: application,firmwaretool,histories,history,servicereport,servicereports,dashboard,profile,equipment,equipments,products_software
-- ===================================================
INSERT INTO `role_access_permissions` (`role_id`, `access_id`, `can_create`, `can_read`, `can_update`, `can_delete`)
SELECT @role_standard, rowID, 0, 1, 0, 0 FROM access_elements WHERE access_path IN (
'application', 'firmwaretool', 'histories', 'history', 'servicereport', 'servicereports',
'dashboard', 'profile', 'equipment', 'equipments', 'products_software'
)
ON DUPLICATE KEY UPDATE can_read = 1;
-- ===================================================
-- SUPERUSER ROLE PERMISSIONS (Create, Read, Update)
-- Profile: application,assets,firmwaretool,histories,history,history_manage,marketing,partner,partners,
-- servicereport,servicereports,admin,dashboard,profile,equipment,equipment_manage,
-- equipment_manage_edit,equipments,equipments_mass_update,product,product_manage,products,
-- products_software,products_versions,user,user_manage,users
-- ===================================================
INSERT INTO `role_access_permissions` (`role_id`, `access_id`, `can_create`, `can_read`, `can_update`, `can_delete`)
SELECT @role_superuser, rowID, 1, 1, 1, 0 FROM access_elements WHERE access_path IN (
'application', 'firmwaretool', 'histories', 'history', 'history_manage',
'marketing', 'partner', 'partners', 'servicereport', 'servicereports',
'dashboard', 'profile', 'equipment', 'equipment_manage',
'equipments', 'equipments_mass_update', 'product', 'product_manage', 'products',
'products_software', 'products_versions', 'user', 'users'
)
ON DUPLICATE KEY UPDATE can_create = 1, can_read = 1, can_update = 1;
-- ===================================================
-- ADMIN ROLE PERMISSIONS (Full CRUD)
-- ===================================================
INSERT INTO `role_access_permissions` (`role_id`, `access_id`, `can_create`, `can_read`, `can_update`, `can_delete`)
SELECT @role_admin, rowID, 1, 1, 1, 1 FROM access_elements WHERE access_path IN (
'application', 'buildtool', 'cartest', 'cartest_manage', 'cartests',
'changelog', 'communication', 'communication_send', 'communications', 'firmwaretool',
'histories', 'history', 'history_manage', 'marketing', 'partner', 'partners',
'servicereport', 'servicereports', 'software_available', 'software_download',
'software_update', 'softwaretool', 'account', 'accounts', 'dashboard', 'profile',
'contract', 'contract_manage', 'contracts', 'equipment', 'equipment_data',
'equipment_healthindex', 'equipment_history', 'equipment_manage',
'equipments', 'equipments_mass_update', 'product', 'product_manage', 'products',
'products_software', 'products_software_assignment', 'products_software_assignments',
'products_software_licenses', 'products_versions', 'report_build',
'report_contracts_billing', 'report_healthindex', 'rma', 'rma_history',
'rma_manage', 'rmas', 'user', 'users'
)
ON DUPLICATE KEY UPDATE can_create = 1, can_read = 1, can_update = 1, can_delete = 1;
-- ===================================================
-- ADMINPLUS ROLE PERMISSIONS (Full access to everything)
-- ===================================================
INSERT INTO `role_access_permissions` (`role_id`, `access_id`, `can_create`, `can_read`, `can_update`, `can_delete`)
SELECT @role_adminplus, rowID, 1, 1, 1, 1 FROM access_elements WHERE is_active = 1
ON DUPLICATE KEY UPDATE can_create = 1, can_read = 1, can_update = 1, can_delete = 1;
-- ===================================================
-- BUILD ROLE PERMISSIONS
-- Profile: application,buildtool,firmwaretool,dashboard,profile,products_software
-- ===================================================
INSERT INTO `role_access_permissions` (`role_id`, `access_id`, `can_create`, `can_read`, `can_update`, `can_delete`)
SELECT @role_build, rowID, 1, 1, 1, 0 FROM access_elements WHERE access_path IN (
'application', 'buildtool', 'firmwaretool', 'dashboard', 'profile', 'products_software'
)
ON DUPLICATE KEY UPDATE can_create = 1, can_read = 1, can_update = 1;
-- ===================================================
-- COMMERCE ROLE PERMISSIONS
-- ===================================================
INSERT INTO `role_access_permissions` (`role_id`, `access_id`, `can_create`, `can_read`, `can_update`, `can_delete`)
SELECT @role_commerce, rowID, 1, 1, 1, 1 FROM access_elements WHERE access_path IN (
'application', 'catalog', 'categories', 'category', 'checkout', 'discount', 'discounts',
'identity', 'invoice', 'media', 'media_manage', 'order', 'orders', 'partner', 'partners',
'placeorder', 'pricelists', 'pricelists_items', 'pricelists_manage', 'shipping',
'shipping_manage', 'shopping_cart', 'taxes', 'transactions', 'transactions_items',
'translation_manage', 'translations', 'translations_details', 'uploader',
'dashboard', 'profile', 'product', 'product_manage', 'products', 'products_attributes',
'products_attributes_items', 'products_attributes_manage', 'products_categories',
'products_configurations', 'products_media', 'products_software', 'products_versions',
'user', 'users'
)
ON DUPLICATE KEY UPDATE can_create = 1, can_read = 1, can_update = 1, can_delete = 1;
-- ===================================================
-- DISTRIBUTION ROLE PERMISSIONS
-- ===================================================
INSERT INTO `role_access_permissions` (`role_id`, `access_id`, `can_create`, `can_read`, `can_update`, `can_delete`)
SELECT @role_distribution, rowID, 1, 1, 1, 0 FROM access_elements WHERE access_path IN (
'application', 'firmwaretool', 'histories', 'history', 'history_manage',
'marketing', 'partner', 'partners', 'servicereport', 'servicereports',
'dashboard', 'profile', 'equipment', 'equipment_manage',
'equipments', 'equipments_mass_update', 'product', 'product_manage', 'products',
'products_software', 'products_versions', 'user', 'users'
)
ON DUPLICATE KEY UPDATE can_create = 1, can_read = 1, can_update = 1;
-- ===================================================
-- FIRMWARE ROLE PERMISSIONS
-- Profile: application,software_available,software_download,software_update,softwaretool,
-- transactions,transactions_items,products_software_versions
-- ===================================================
INSERT INTO `role_access_permissions` (`role_id`, `access_id`, `can_create`, `can_read`, `can_update`, `can_delete`)
SELECT @role_firmware, rowID, 0, 1, 1, 0 FROM access_elements WHERE access_path IN (
'application', 'software_available', 'software_download', 'software_update',
'softwaretool', 'transactions', 'transactions_items', 'products_software_versions'
)
ON DUPLICATE KEY UPDATE can_read = 1, can_update = 1;
-- ===================================================
-- GARAGE ROLE PERMISSIONS
-- Profile: application,cartest,cartest_manage,cartests,dashboard,profile,products_versions
-- ===================================================
INSERT INTO `role_access_permissions` (`role_id`, `access_id`, `can_create`, `can_read`, `can_update`, `can_delete`)
SELECT @role_garage, rowID, 1, 1, 1, 0 FROM access_elements WHERE access_path IN (
'application', 'cartest', 'cartest_manage', 'cartests', 'dashboard', 'profile', 'products_versions'
)
ON DUPLICATE KEY UPDATE can_create = 1, can_read = 1, can_update = 1;
-- ===================================================
-- INTERFACE ROLE PERMISSIONS
-- Profile: application,firmwaretool,invoice,payment,transactions,transactions_items,
-- contract,contracts,equipment_manage,equipments,products_software,products_versions,users
-- ===================================================
INSERT INTO `role_access_permissions` (`role_id`, `access_id`, `can_create`, `can_read`, `can_update`, `can_delete`)
SELECT @role_interface, rowID, 1, 1, 1, 0 FROM access_elements WHERE access_path IN (
'application', 'firmwaretool', 'invoice', 'payment', 'transactions', 'transactions_items',
'contract', 'contracts', 'equipment_manage', 'equipments', 'products_software',
'products_versions', 'users'
)
ON DUPLICATE KEY UPDATE can_create = 1, can_read = 1, can_update = 1;
-- ===================================================
-- SERVICE ROLE PERMISSIONS
-- Profile: application,assets,firmwaretool,histories,history,history_manage,marketing,partner,partners,
-- servicereport,servicereports,admin,dashboard,profile,equipment,equipment_manage,equipments,
-- products_software,user,user_manage,users
-- ===================================================
INSERT INTO `role_access_permissions` (`role_id`, `access_id`, `can_create`, `can_read`, `can_update`, `can_delete`)
SELECT @role_service, rowID, 1, 1, 1, 0 FROM access_elements WHERE access_path IN (
'application', 'firmwaretool', 'histories', 'history', 'history_manage',
'marketing', 'partner', 'partners', 'servicereport', 'servicereports',
'dashboard', 'profile', 'equipment', 'equipment_manage', 'equipments', 'products_software',
'user', 'users'
)
ON DUPLICATE KEY UPDATE can_create = 1, can_read = 1, can_update = 1;
-- ===================================================
-- OTHER ROLE PERMISSIONS
-- Profile: application,assets,firmwaretool,histories,history,history_manage,marketing,partner,partners,
-- servicereport,servicereports,admin,dashboard,profile,equipment,equipment_manage,equipments,products_software
-- ===================================================
INSERT INTO `role_access_permissions` (`role_id`, `access_id`, `can_create`, `can_read`, `can_update`, `can_delete`)
SELECT @role_other, rowID, 0, 1, 1, 0 FROM access_elements WHERE access_path IN (
'application', 'firmwaretool', 'histories', 'history', 'history_manage',
'marketing', 'partner', 'partners', 'servicereport', 'servicereports',
'dashboard', 'profile', 'equipment', 'equipment_manage', 'equipments', 'products_software'
)
ON DUPLICATE KEY UPDATE can_read = 1, can_update = 1;
-- ===================================================
-- VERIFICATION QUERIES
-- ===================================================
-- Check roles created
SELECT rowID, name, description, is_active FROM user_roles ORDER BY rowID;
-- Check permissions per role
SELECT ur.name as role_name, COUNT(rap.rowID) as permission_count
FROM user_roles ur
LEFT JOIN role_access_permissions rap ON ur.rowID = rap.role_id
GROUP BY ur.rowID, ur.name
ORDER BY ur.rowID;
-- ===================================================
-- Change ROLLBACK to COMMIT when ready to apply
-- ===================================================
COMMIT;

View File

@@ -0,0 +1,228 @@
-- ===================================================
-- USER TO RBAC ROLE ASSIGNMENT MIGRATION SCRIPT
-- Date: 2025-01-22
-- Description: Migrate users from settings/view fields to user_role_assignments
-- Prerequisites: Run migration_profiles_to_rbac.sql first to create roles
-- ===================================================
START TRANSACTION;
-- ===================================================
-- MAPPING REFERENCE:
--
-- users.settings field values -> role names:
-- 'standard_profile' or empty with view 0-2 -> Standard
-- 'superuser_profile' or view=2 -> Superuser
-- 'admin_profile' or view=4 -> Admin
-- 'adminplus_profile' or view=5 -> AdminPlus
-- 'build' -> Build
-- 'commerce' -> Commerce
-- 'distribution' -> Distribution
-- 'firmware' -> Firmware
-- 'garage' -> Garage
-- 'interface' -> Interface
-- 'service' -> Service
-- 'other' -> Other
--
-- users.view field (legacy permission level):
-- 1 = SuperUser
-- 2 = Create & Update
-- 3 = Read-only
-- 4 = Admin
-- 5 = Admin+
-- ===================================================
-- Get role IDs
SET @role_standard = (SELECT rowID FROM user_roles WHERE name = 'Standard' LIMIT 1);
SET @role_superuser = (SELECT rowID FROM user_roles WHERE name = 'Superuser' LIMIT 1);
SET @role_admin = (SELECT rowID FROM user_roles WHERE name = 'Admin' LIMIT 1);
SET @role_adminplus = (SELECT rowID FROM user_roles WHERE name = 'AdminPlus' LIMIT 1);
SET @role_build = (SELECT rowID FROM user_roles WHERE name = 'Build' LIMIT 1);
SET @role_commerce = (SELECT rowID FROM user_roles WHERE name = 'Commerce' LIMIT 1);
SET @role_distribution = (SELECT rowID FROM user_roles WHERE name = 'Distribution' LIMIT 1);
SET @role_firmware = (SELECT rowID FROM user_roles WHERE name = 'Firmware' LIMIT 1);
SET @role_garage = (SELECT rowID FROM user_roles WHERE name = 'Garage' LIMIT 1);
SET @role_interface = (SELECT rowID FROM user_roles WHERE name = 'Interface' LIMIT 1);
SET @role_service = (SELECT rowID FROM user_roles WHERE name = 'Service' LIMIT 1);
SET @role_other = (SELECT rowID FROM user_roles WHERE name = 'Other' LIMIT 1);
-- ===================================================
-- PHASE 1: MIGRATE USERS BY SETTINGS FIELD (profile name)
-- ===================================================
-- Users with 'standard_profile' setting
INSERT INTO `user_role_assignments` (`user_id`, `role_id`, `is_active`, `assigned_by`, `assigned_at`, `created`, `createdby`)
SELECT id, @role_standard, 1, 'migration_script', NOW(), NOW(), 1
FROM users
WHERE settings = 'standard_profile'
ON DUPLICATE KEY UPDATE updated = NOW();
-- Users with 'superuser_profile' setting
INSERT INTO `user_role_assignments` (`user_id`, `role_id`, `is_active`, `assigned_by`, `assigned_at`, `created`, `createdby`)
SELECT id, @role_superuser, 1, 'migration_script', NOW(), NOW(), 1
FROM users
WHERE settings = 'superuser_profile'
ON DUPLICATE KEY UPDATE updated = NOW();
-- Users with 'admin_profile' setting
INSERT INTO `user_role_assignments` (`user_id`, `role_id`, `is_active`, `assigned_by`, `assigned_at`, `created`, `createdby`)
SELECT id, @role_admin, 1, 'migration_script', NOW(), NOW(), 1
FROM users
WHERE settings = 'admin_profile'
ON DUPLICATE KEY UPDATE updated = NOW();
-- Users with 'adminplus_profile' setting
INSERT INTO `user_role_assignments` (`user_id`, `role_id`, `is_active`, `assigned_by`, `assigned_at`, `created`, `createdby`)
SELECT id, @role_adminplus, 1, 'migration_script', NOW(), NOW(), 1
FROM users
WHERE settings = 'adminplus_profile'
ON DUPLICATE KEY UPDATE updated = NOW();
-- Users with 'build' setting
INSERT INTO `user_role_assignments` (`user_id`, `role_id`, `is_active`, `assigned_by`, `assigned_at`, `created`, `createdby`)
SELECT id, @role_build, 1, 'migration_script', NOW(), NOW(), 1
FROM users
WHERE settings = 'build'
ON DUPLICATE KEY UPDATE updated = NOW();
-- Users with 'commerce' setting
INSERT INTO `user_role_assignments` (`user_id`, `role_id`, `is_active`, `assigned_by`, `assigned_at`, `created`, `createdby`)
SELECT id, @role_commerce, 1, 'migration_script', NOW(), NOW(), 1
FROM users
WHERE settings = 'commerce'
ON DUPLICATE KEY UPDATE updated = NOW();
-- Users with 'distribution' setting
INSERT INTO `user_role_assignments` (`user_id`, `role_id`, `is_active`, `assigned_by`, `assigned_at`, `created`, `createdby`)
SELECT id, @role_distribution, 1, 'migration_script', NOW(), NOW(), 1
FROM users
WHERE settings = 'distribution'
ON DUPLICATE KEY UPDATE updated = NOW();
-- Users with 'firmware' setting
INSERT INTO `user_role_assignments` (`user_id`, `role_id`, `is_active`, `assigned_by`, `assigned_at`, `created`, `createdby`)
SELECT id, @role_firmware, 1, 'migration_script', NOW(), NOW(), 1
FROM users
WHERE settings = 'firmware'
ON DUPLICATE KEY UPDATE updated = NOW();
-- Users with 'garage' setting
INSERT INTO `user_role_assignments` (`user_id`, `role_id`, `is_active`, `assigned_by`, `assigned_at`, `created`, `createdby`)
SELECT id, @role_garage, 1, 'migration_script', NOW(), NOW(), 1
FROM users
WHERE settings = 'garage'
ON DUPLICATE KEY UPDATE updated = NOW();
-- Users with 'interface' setting
INSERT INTO `user_role_assignments` (`user_id`, `role_id`, `is_active`, `assigned_by`, `assigned_at`, `created`, `createdby`)
SELECT id, @role_interface, 1, 'migration_script', NOW(), NOW(), 1
FROM users
WHERE settings = 'interface'
ON DUPLICATE KEY UPDATE updated = NOW();
-- Users with 'service' setting
INSERT INTO `user_role_assignments` (`user_id`, `role_id`, `is_active`, `assigned_by`, `assigned_at`, `created`, `createdby`)
SELECT id, @role_service, 1, 'migration_script', NOW(), NOW(), 1
FROM users
WHERE settings = 'service'
ON DUPLICATE KEY UPDATE updated = NOW();
-- Users with 'other' setting
INSERT INTO `user_role_assignments` (`user_id`, `role_id`, `is_active`, `assigned_by`, `assigned_at`, `created`, `createdby`)
SELECT id, @role_other, 1, 'migration_script', NOW(), NOW(), 1
FROM users
WHERE settings = 'other'
ON DUPLICATE KEY UPDATE updated = NOW();
-- ===================================================
-- PHASE 2: MIGRATE USERS WITH EMPTY/NULL SETTINGS (use view field)
-- Only for users not already assigned a role
-- ===================================================
-- Users with view=5 (Admin+) and no settings
INSERT INTO `user_role_assignments` (`user_id`, `role_id`, `is_active`, `assigned_by`, `assigned_at`, `created`, `createdby`)
SELECT u.id, @role_adminplus, 1, 'migration_script', NOW(), NOW(), 1
FROM users u
LEFT JOIN user_role_assignments ura ON u.id = ura.user_id AND ura.is_active = 1
WHERE (u.settings IS NULL OR u.settings = '')
AND u.view = '5'
AND ura.rowID IS NULL
ON DUPLICATE KEY UPDATE updated = NOW();
-- Users with view=4 (Admin) and no settings
INSERT INTO `user_role_assignments` (`user_id`, `role_id`, `is_active`, `assigned_by`, `assigned_at`, `created`, `createdby`)
SELECT u.id, @role_admin, 1, 'migration_script', NOW(), NOW(), 1
FROM users u
LEFT JOIN user_role_assignments ura ON u.id = ura.user_id AND ura.is_active = 1
WHERE (u.settings IS NULL OR u.settings = '')
AND u.view = '4'
AND ura.rowID IS NULL
ON DUPLICATE KEY UPDATE updated = NOW();
-- Users with view=1 (SuperUser) and no settings
INSERT INTO `user_role_assignments` (`user_id`, `role_id`, `is_active`, `assigned_by`, `assigned_at`, `created`, `createdby`)
SELECT u.id, @role_superuser, 1, 'migration_script', NOW(), NOW(), 1
FROM users u
LEFT JOIN user_role_assignments ura ON u.id = ura.user_id AND ura.is_active = 1
WHERE (u.settings IS NULL OR u.settings = '')
AND u.view = '1'
AND ura.rowID IS NULL
ON DUPLICATE KEY UPDATE updated = NOW();
-- Users with view=2 or view=3 (Create/Update or Read-only) and no settings -> Standard
INSERT INTO `user_role_assignments` (`user_id`, `role_id`, `is_active`, `assigned_by`, `assigned_at`, `created`, `createdby`)
SELECT u.id, @role_standard, 1, 'migration_script', NOW(), NOW(), 1
FROM users u
LEFT JOIN user_role_assignments ura ON u.id = ura.user_id AND ura.is_active = 1
WHERE (u.settings IS NULL OR u.settings = '')
AND u.view IN ('2', '3')
AND ura.rowID IS NULL
ON DUPLICATE KEY UPDATE updated = NOW();
-- ===================================================
-- PHASE 3: CATCH-ALL - Any remaining users without role -> Standard
-- ===================================================
INSERT INTO `user_role_assignments` (`user_id`, `role_id`, `is_active`, `assigned_by`, `assigned_at`, `created`, `createdby`)
SELECT u.id, @role_standard, 1, 'migration_script', NOW(), NOW(), 1
FROM users u
LEFT JOIN user_role_assignments ura ON u.id = ura.user_id AND ura.is_active = 1
WHERE ura.rowID IS NULL
ON DUPLICATE KEY UPDATE updated = NOW();
-- ===================================================
-- VERIFICATION QUERIES
-- ===================================================
-- Check migration results: users per role
SELECT
ur.name as role_name,
COUNT(ura.user_id) as user_count
FROM user_roles ur
LEFT JOIN user_role_assignments ura ON ur.rowID = ura.role_id AND ura.is_active = 1
GROUP BY ur.rowID, ur.name
ORDER BY user_count DESC;
-- Check for users without role assignments (should be 0)
SELECT COUNT(*) as users_without_role
FROM users u
LEFT JOIN user_role_assignments ura ON u.id = ura.user_id AND ura.is_active = 1
WHERE ura.rowID IS NULL;
-- Compare old vs new: show users with their old settings and new role
SELECT
u.id,
u.username,
u.settings as old_profile,
u.view as old_view_level,
ur.name as new_role
FROM users u
LEFT JOIN user_role_assignments ura ON u.id = ura.user_id AND ura.is_active = 1
LEFT JOIN user_roles ur ON ura.role_id = ur.rowID
ORDER BY u.id
LIMIT 50;
-- ===================================================
-- Change ROLLBACK to COMMIT when ready to apply
-- ===================================================
COMMIT;

View File

@@ -515,10 +515,19 @@ echo <<<EOT
// Intercept form submissions // Intercept form submissions
setupFormInterception(); setupFormInterception();
// Intercept fetch and XMLHttpRequest // Intercept fetch and XMLHttpRequest
interceptNetworkRequests(); interceptNetworkRequests();
// Intercept form submissions to show loading
function setupFormInterception() {
document.querySelectorAll('form').forEach(function(form) {
form.addEventListener('submit', function() {
showLoading();
});
});
}
// Intercept all network requests (fetch and XMLHttpRequest) // Intercept all network requests (fetch and XMLHttpRequest)
function interceptNetworkRequests() { function interceptNetworkRequests() {
// Track active requests // Track active requests
@@ -1637,42 +1646,25 @@ function getProfile($profile, $permission){
// Always allowed collections: [collection => allowed_actions_string] // Always allowed collections: [collection => allowed_actions_string]
$always_allowed = [ $always_allowed = [
'com_log' => 'U', 'com_log' => 'CRU',
'application' => 'CRU',
'user_permissions' => 'R', 'user_permissions' => 'R',
'software_update' => 'R', 'software_update' => 'R',
'software_download' => 'R', 'software_download' => 'R',
'software_available' => 'R', 'software_available' => 'R',
'history' => 'U', 'history' => 'RU',
'payment' => 'U', 'payment' => 'U'
'marketing_files' => 'CRUD',
'marketing_folders' => 'CRUD',
'marketing_tags' => 'CRUD',
'marketing_upload' => 'CRUD',
'marketing_delete' => 'CRUD'
]; ];
// Debug log - initial call
if(debug){
$perm_count = is_array($permissions) ? count($permissions) : 'not_array';
$test = "$date - isAllowed called: access_element=$access_element, basic_permission_level=$basic_permission_level, action=$action, permissions_count=$perm_count".PHP_EOL;
error_log($test, 3, $filelocation);
}
// 1. Check if basic_permission_level is 4 (System-admin+) - always allow // 1. Check if basic_permission_level is 4 (System-admin+) - always allow
if ($basic_permission_level !== null && $basic_permission_level == 4) { if ($basic_permission_level !== null && $basic_permission_level == 4) {
if(debug){
$test = "$date - Allowed by system permission (level 5)".PHP_EOL;
error_log($test, 3, $filelocation);
}
return 1; return 1;
} }
// 2. Check always_allowed list // 2. Check always_allowed list
if (isset($always_allowed[$access_element]) && str_contains($always_allowed[$access_element], $action)) { if (isset($always_allowed[$access_element]) && str_contains($always_allowed[$access_element], $action)) {
if(debug){
$test = "$date - Allowed by always_allowed list".PHP_EOL;
error_log($test, 3, $filelocation);
}
return 1; return 1;
} }
@@ -1691,20 +1683,21 @@ function getProfile($profile, $permission){
$permission_key = $action_map[$action] ?? null; $permission_key = $action_map[$action] ?? null;
if ($permission_key && isset($element_permissions[$permission_key]) && $element_permissions[$permission_key] == 1) { if ($permission_key && isset($element_permissions[$permission_key]) && $element_permissions[$permission_key] == 1) {
if(debug){
$test = "$date - Allowed by RBAC permissions: $access_element -> $permission_key = 1".PHP_EOL;
error_log($test, 3, $filelocation);
}
return 1; return 1;
} }
if(debug){ if(debug){
$test = "$date - isAllowed called: access_element=$access_element, basic_permission_level=$basic_permission_level, action=$action".PHP_EOL;
error_log($test, 3, $filelocation);
$perm_value = $element_permissions[$permission_key] ?? 'not_set'; $perm_value = $element_permissions[$permission_key] ?? 'not_set';
$test = "$date - RBAC check failed: $access_element -> $permission_key = $perm_value".PHP_EOL; $test = "$date - RBAC check failed: $access_element -> $permission_key = $perm_value".PHP_EOL;
error_log($test, 3, $filelocation); error_log($test, 3, $filelocation);
} }
} else { } else {
if(debug){ if(debug){
$test = "$date - isAllowed called: access_element=$access_element, basic_permission_level=$basic_permission_level, action=$action".PHP_EOL;
error_log($test, 3, $filelocation);
$test = "$date - Access element '$access_element' not found in permissions array".PHP_EOL; $test = "$date - Access element '$access_element' not found in permissions array".PHP_EOL;
error_log($test, 3, $filelocation); error_log($test, 3, $filelocation);
} }
@@ -1712,9 +1705,12 @@ function getProfile($profile, $permission){
// Not allowed // Not allowed
if(debug){ if(debug){
$test = "$date - isAllowed called: access_element=$access_element, basic_permission_level=$basic_permission_level, action=$action".PHP_EOL;
error_log($test, 3, $filelocation);
$test = "$date - Not allowed: access_element=$access_element, action=$action".PHP_EOL; $test = "$date - Not allowed: access_element=$access_element, action=$action".PHP_EOL;
error_log($test, 3, $filelocation); error_log($test, 3, $filelocation);
} }
return 0; return 0;
} }
@@ -3913,27 +3909,29 @@ function dateInRange($start_date, $end_date, $date_check)
function getLatestVersion($productcode,$token){ function getLatestVersion($productcode,$token){
//CALL TO API TO GET ALL ACTIVE CONTRACTS //$pdo = dbConnect($dbname);
$api_url = '/v2/products_software/productcode='.$productcode;
$responses = ioAPIv2($api_url,'',$token);
//Decode Payload //CALL TO API TO GET ALL ACTIVE CONTRACTS
if (!empty($responses)){$responses = json_decode($responses,true); $api_url = '/v2/products_software/productcode='.$productcode;
} $responses = ioAPIv2($api_url,'',$token);
else{
$responses = $output = array( //Decode Payload
"productcode" => "", if (!empty($responses)){$responses = json_decode($responses,true);
"version"=> "", }
"mandatory"=> "", else{
"latest"=> "", $responses = $output = array(
"software"=> "", "productcode" => "",
"source" => "", "version"=> "",
"source_type" => "" "mandatory"=> "",
); "latest"=> "",
;} "software"=> "",
"source" => "",
"source_type" => ""
);
;}
//DEFAULT OUTPUT //DEFAULT OUTPUT
return $responses; return $responses;
} }
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++ // +++++++++++++++++++++++++++++++++++++++++++++++++++++++

View File

@@ -249,15 +249,19 @@ class MarketingFileManager {
emptyState.style.display = 'none'; emptyState.style.display = 'none';
try { 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 // 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) { if (this.filters.search) {
url += `&search=${encodeURIComponent(this.filters.search)}`; url += `&search=${encodeURIComponent(this.filters.search)}`;
} }
if (this.filters.tag) { if (this.filters.tag) {
url += `&tag=${encodeURIComponent(this.filters.tag)}`; url += `&tag=${encodeURIComponent(this.filters.tag)}`;
} }
@@ -289,21 +293,33 @@ class MarketingFileManager {
if (data && data.length > 0) { if (data && data.length > 0) {
let files = data; let files = data;
// Client-side file type filtering // Client-side file type filtering
if (this.filters.fileTypes.length > 0) { if (this.filters.fileTypes.length > 0) {
files = files.filter(file => files = files.filter(file =>
this.filters.fileTypes.includes(file.file_type.toLowerCase()) this.filters.fileTypes.includes(file.file_type.toLowerCase())
); );
} }
if (files.length === 0) { 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 { } else {
this.renderFiles(files); this.renderFiles(files);
} }
} else { } 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) { } catch (error) {
console.error('Error loading files:', error); console.error('Error loading files:', error);
@@ -372,12 +388,73 @@ class MarketingFileManager {
renderFiles(files) { renderFiles(files) {
const container = document.getElementById('filesContainer'); const container = document.getElementById('filesContainer');
container.innerHTML = ''; container.innerHTML = '';
files.forEach(file => { files.forEach(file => {
const fileElement = this.createFileElement(file); const fileElement = this.createFileElement(file);
container.appendChild(fileElement); 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) { createFileElement(file) {
const fileElement = document.createElement('div'); const fileElement = document.createElement('div');
@@ -385,7 +462,7 @@ class MarketingFileManager {
fileElement.setAttribute('data-file-id', file.id); fileElement.setAttribute('data-file-id', file.id);
const thumbnail = this.getThumbnail(file); 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 = ` fileElement.innerHTML = `
<div class="file-thumbnail"> <div class="file-thumbnail">
@@ -429,11 +506,20 @@ class MarketingFileManager {
fileElement.querySelector('.edit-btn').addEventListener('click', () => { fileElement.querySelector('.edit-btn').addEventListener('click', () => {
this.editFile(file); 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', () => { fileElement.addEventListener('dblclick', () => {
this.previewFile(file); this.previewFile(file);
}); });
return fileElement; return fileElement;
} }
@@ -843,15 +929,41 @@ class MarketingFileManager {
updateFileTypeFilters() { updateFileTypeFilters() {
this.filters.fileTypes = []; this.filters.fileTypes = [];
document.querySelectorAll('.file-type-filters input[type="checkbox"]:checked').forEach(checkbox => { document.querySelectorAll('.file-type-filters input[type="checkbox"]:checked').forEach(checkbox => {
const types = checkbox.value.split(','); const types = checkbox.value.split(',');
this.filters.fileTypes.push(...types); this.filters.fileTypes.push(...types);
}); });
this.loadFiles(); 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) { populateTagFilter(tags) {
const select = document.getElementById('tagFilter'); const select = document.getElementById('tagFilter');
select.innerHTML = '<option value="">All Tags</option>'; select.innerHTML = '<option value="">All Tags</option>';

View File

@@ -100,6 +100,45 @@ if (document.readyState === 'loading') {
checkBrowserCompatibility(); checkBrowserCompatibility();
} }
// Shared serial port reference for upload.js to use
window.sharedSerialPort = null;
// Override requestPort to minimize user prompts
// This intercepts all requestPort calls (including from upload.js) to reuse authorized ports
if ('serial' in navigator) {
const originalRequestPort = navigator.serial.requestPort.bind(navigator.serial);
navigator.serial.requestPort = async function(options) {
// If we have a shared port, return it instead of prompting
if (window.sharedSerialPort) {
console.log('Using shared serial port (no prompt needed)');
return window.sharedSerialPort;
}
// Try already-authorized ports matching the filters
const ports = await navigator.serial.getPorts();
if (ports.length > 0 && options?.filters) {
const match = ports.find(p => {
const info = p.getInfo();
return options.filters.some(f =>
info.usbVendorId === f.usbVendorId &&
info.usbProductId === f.usbProductId
);
});
if (match) {
console.log('Using previously authorized port (no prompt needed)');
window.sharedSerialPort = match;
return match;
}
}
// Fallback: original prompt behavior
const port = await originalRequestPort(options);
window.sharedSerialPort = port; // Store for future use
return port;
};
}
// Function to log communication to API (reused from scripts.js) // Function to log communication to API (reused from scripts.js)
async function logCommunication(data, direction) { async function logCommunication(data, direction) {
// Only log if debug mode is enabled // Only log if debug mode is enabled
@@ -400,7 +439,11 @@ async function closePortAfterRead() {
await port.close(); await port.close();
await logCommunication('Port closed successfully', 'info'); await logCommunication('Port closed successfully', 'info');
// Reset for next connection // Keep port reference in sharedSerialPort for upload.js to reuse
// This prevents the need for another user prompt during firmware upload
window.sharedSerialPort = port;
// Reset local variables for next connection
reader = null; reader = null;
writer = null; writer = null;
readableStreamClosed = null; readableStreamClosed = null;
@@ -410,7 +453,12 @@ async function closePortAfterRead() {
console.error('Error closing port after read:', error); console.error('Error closing port after read:', error);
await logCommunication(`Error closing port: ${error.message}`, 'error'); await logCommunication(`Error closing port: ${error.message}`, 'error');
// Force reset even on error // Keep port reference even on error if port exists
if (port) {
window.sharedSerialPort = port;
}
// Force reset local variables even on error
reader = null; reader = null;
writer = null; writer = null;
readableStreamClosed = null; readableStreamClosed = null;

View File

@@ -440,9 +440,15 @@ $view .= '<div class="content-block">
<td>'.getRelativeTime($responses->created).'</td> <td>'.getRelativeTime($responses->created).'</td>
</tr> </tr>
<tr> <tr>
<td style="width:25%;">'.$general_updated.'</td> <td style="width:25%;">'.$general_updated.'</td>';
<td><a href="#" onclick="openLogModal(); return false;" class="link-with-icon">'.getRelativeTime($responses->updated).'<i class="fa-solid fa-up-right-from-square"></i></a></td>
</tr> if ($update_allowed === 1){
$view .= '<td><a href="#" onclick="openLogModal(); return false;" class="link-with-icon">'.getRelativeTime($responses->updated).'<i class="fa-solid fa-up-right-from-square"></i></a></td>';
} else {
$view .= '<td>'.getRelativeTime($responses->updated).'</td>';
}
$view .= ' </tr>
</table> </table>
</div> </div>
</div> </div>

View File

@@ -104,7 +104,7 @@ if (isset($_POST['email_invoice'])) {
$attachment = $dompdf->output(); $attachment = $dompdf->output();
$attachment_name = $file_name . '.pdf'; $attachment_name = $file_name . '.pdf';
$header_redirect = 'Location: index.php?page=order&id=' . $order_id . '&success=invoice_sent'; $header_redirect = 'Location: index.php?page=order&txn_id=' . $order_id . '&success=invoice_sent';
// Send to PHPMailer // Send to PHPMailer
send_mail($to, $subject, $message, $attachment, $attachment_name); send_mail($to, $subject, $message, $attachment, $attachment_name);
@@ -120,7 +120,7 @@ if (isset($_POST['email_invoice_to_admin'])) {
$attachment = $dompdf->output(); $attachment = $dompdf->output();
$attachment_name = $file_name . '.pdf'; $attachment_name = $file_name . '.pdf';
$header_redirect = 'Location: index.php?page=order&id=' . $order_id . '&success=invoice_sent_admin'; $header_redirect = 'Location: index.php?page=order&txn_id=' . $order_id . '&success=invoice_sent_admin';
// Send to bookkeeping if configured // Send to bookkeeping if configured
if (invoice_bookkeeping && email_bookkeeping) { if (invoice_bookkeeping && email_bookkeeping) {
@@ -144,7 +144,7 @@ if (isset($_GET['show_invoice'])) {
} }
// If no action specified, redirect back // If no action specified, redirect back
header('Location: index.php?page=order&id=' . $order_number); header('Location: index.php?page=order&txn_id=' . $order_number);
exit; exit;
?> ?>

View File

@@ -106,23 +106,28 @@ if (isset($_GET['page']) && $_GET['page'] == 'logout') {
die(); die();
} }
//=====================================
//DEFINE WHERE TO SEND THE USER TO. GET first assigned view in the profile if not available use dashboard
/*=====================================
$allowed_views = explode(',',$_SESSION['authorization']['profile']);
$ignoreViews = ['profile','assets','sales'];
// If dashboard is in the profile, prioritize it
if (in_array('dashboard', $allowed_views) && file_exists('dashboard.php')) {
$allowed_views = 'dashboard';
} else {
$allowed_views = findExistingView($allowed_views, 'dashboard', $ignoreViews);
}
*/
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// SIMPLE ROUTING SYSTEM // SIMPLE ROUTING SYSTEM
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
$page = $_GET['page'] ?? 'dashboard'; if (isset($_GET['page'])) {
$page = $_GET['page'];
} else {
// Get first available page from user's permissions using the menu structure
$default_page = null;
if (!empty($_SESSION['authorization']['permissions'])) {
include_once dirname(__FILE__).'/settings/settingsmenu.php';
$filteredMenu = filterMenuByPermissions($main_menu, $_SESSION['authorization']['permissions']);
// Get first menu item's URL as default page
foreach ($filteredMenu as $section) {
if (isset($section['main_menu']['url'])) {
$default_page = $section['main_menu']['url'];
break;
}
}
}
$page = $default_page ?? 'dashboard';
}
// Sanitize page parameter to prevent directory traversal // Sanitize page parameter to prevent directory traversal
$page = preg_replace('/[^a-zA-Z0-9_-]/', '', $page); $page = preg_replace('/[^a-zA-Z0-9_-]/', '', $page);
@@ -135,10 +140,6 @@ try {
$file_exists = file_exists($page_file); $file_exists = file_exists($page_file);
$is_allowed = $file_exists ? isAllowed($page, $_SESSION['authorization']['permissions'], $_SESSION['authorization']['permission'], 'R') : 0; $is_allowed = $file_exists ? isAllowed($page, $_SESSION['authorization']['permissions'], $_SESSION['authorization']['permission'], 'R') : 0;
if (debug) {
debuglog("Routing: page={$page}, file_exists={$file_exists}, is_allowed={$is_allowed}");
}
if ($file_exists && $is_allowed !== 0) { if ($file_exists && $is_allowed !== 0) {
include $page_file; include $page_file;
} else { } else {
@@ -166,9 +167,6 @@ try {
<i class="' . $error_icon . '"></i> <i class="' . $error_icon . '"></i>
</div> </div>
<p style="color: var(--gray-500, #6b7280); margin-bottom: 30px;">Please check the URL or navigate using the menu.</p> <p style="color: var(--gray-500, #6b7280); margin-bottom: 30px;">Please check the URL or navigate using the menu.</p>
<a href="index.php?page=dashboard" class="btn">
<i class="fa-solid fa-house"></i> Return to Dashboard
</a>
</div>'; </div>';
template_footer(); template_footer();
} }
@@ -195,9 +193,6 @@ try {
</div> </div>
<p style="color: var(--gray-500, #6b7280); margin-bottom: 30px;">Please try again or contact the system administrator.</p> <p style="color: var(--gray-500, #6b7280); margin-bottom: 30px;">Please try again or contact the system administrator.</p>
<div style="display: flex; gap: 10px; justify-content: center;"> <div style="display: flex; gap: 10px; justify-content: center;">
<a href="index.php?page=dashboard" class="btn">
<i class="fa-solid fa-house"></i> Return to Dashboard
</a>
<button onclick="location.reload()" class="btn"> <button onclick="location.reload()" class="btn">
<i class="fa-solid fa-rotate-right"></i> Reload Page <i class="fa-solid fa-rotate-right"></i> Reload Page
</button> </button>

View File

@@ -78,13 +78,13 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
//Decode Payload //Decode Payload
if (!empty($responses)){$responses = json_decode($responses,true);}else{$responses = '400';} if (!empty($responses)){$responses = json_decode($responses,true);}else{$responses = '400';}
if ($responses === 'NOK'){ if ($responses === 'NOK' || $responses === 'NULL' || $responses === 'NULL '){
$retry++; $retry++;
$password_err = $password_err_1 ?? 'Not authorized, please retry'; $password_err = $password_err_1 ?? 'Not authorized, please retry';
} elseif ($responses == '1'){ } elseif ($responses == '1'){
$password_err = $password_err_2 ?? 'Too many login attempts. User blocked, please contact your administrator'; $password_err = $password_err_2 ?? 'Too many login attempts. User blocked, please contact your administrator';
} else { } elseif (!empty($responses['userkey']) && ctype_xdigit($responses['userkey'])) {
// Start a new session // Start a new session
session_start(); session_start();
@@ -93,14 +93,13 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$_SESSION['authorization'] = $responses; $_SESSION['authorization'] = $responses;
$language_user = trim($_SESSION['authorization']['language']) ?? 'US'; $language_user = trim($_SESSION['authorization']['language']) ?? 'US';
if($responses->profile == 'firmwaretool,products_software,application'){
header('location: index.php?page=firmwaretool');
exit();
} else { header('location: index.php?language='.$language_user.'');
header('location: index.php?language='.$language_user.''); exit();
exit();
} } else {
$retry++;
$password_err = $password_err_1 ?? 'Not authorized, please retry';
} }
} }
else { else {

View File

@@ -85,7 +85,7 @@ $view = '
// //
//------------------------------------ //------------------------------------
if ($update_allowed_edit === 1){ if ($update_allowed_edit === 1){
$view .= '<a href="index.php?page=order_manage&id='.$_GET['id'].'" class="btn">✏️</a>'; $view .= '<a href="index.php?page=order_manage&id='.$order['header']['id'].'" class="btn">✏️</a>';
} }
$view .= '</div>'; $view .= '</div>';
@@ -310,7 +310,7 @@ $view .='
<i class="fa-solid fa-bars fa-sm"></i>Giftcards <i class="fa-solid fa-bars fa-sm"></i>Giftcards
</div> </div>
<div class="table order-table"> <div class="table order-table">
<a href="index.php?page=order&id=' . $_GET['id'] . '&add_giftcard" class="btn">Relate giftcards</a> <a href="index.php?page=order&id=' . $order['header']['id'] . '&add_giftcard" class="btn">Relate giftcards</a>
<table> <table>
<thead> <thead>
<tr> <tr>

View File

@@ -17,6 +17,7 @@ if (isAllowed($page,$_SESSION['authorization']['permissions'],$_SESSION['authori
exit; exit;
} }
//PAGE Security //PAGE Security
$page = 'partner';
$update_allowed = isAllowed($page ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'U'); $update_allowed = isAllowed($page ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'U');
$delete_allowed = isAllowed($page ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'D'); $delete_allowed = isAllowed($page ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'D');
$create_allowed = isAllowed($page ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'C'); $create_allowed = isAllowed($page ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'C');

View File

@@ -2,7 +2,7 @@
//================================================================= //=================================================================
//Software version SERVICE Tool==================================== //Software version SERVICE Tool====================================
//================================================================= /*=================================================================
$latest_version = getLatestVersion('EPSK01',$clientsecret) ?? ''; $latest_version = getLatestVersion('EPSK01',$clientsecret) ?? '';
$service_tool_current_version = ($latest_version !='') ? $latest_version['version'] : ''; $service_tool_current_version = ($latest_version !='') ? $latest_version['version'] : '';
@@ -11,7 +11,7 @@ $software_download_url = 'https://'.$_SERVER['SERVER_NAME'].'/firmware'.'/';
//getSoftware (legacy) //getSoftware (legacy)
$software_url = ($latest_version !='') ? $latest_version['source'] : 'https://'.$_SERVER['SERVER_NAME'].'/firmware'.'/'.$service_tool_current_filename; $software_url = ($latest_version !='') ? $latest_version['source'] : 'https://'.$_SERVER['SERVER_NAME'].'/firmware'.'/'.$service_tool_current_filename;
*/
//================================================================= //=================================================================
//SERVICE Tool manual =================================== //SERVICE Tool manual ===================================
//================================================================= //=================================================================
@@ -90,9 +90,9 @@ $init = array(
"ManualURL"=> $manual_url, "ManualURL"=> $manual_url,
"termsURL"=> "https://emergency-plug.com/en/terms-and-conditions", "termsURL"=> "https://emergency-plug.com/en/terms-and-conditions",
"Application" => array( "Application" => array(
"current_version" => $service_tool_current_version, "current_version" => '',
"current_filename" => $service_tool_current_filename, "current_filename" => '',
"location" => $software_download_url "location" => ''
) )
); );

View File

@@ -197,14 +197,10 @@ $view .= '
</div>'; </div>';
if (isset($_GET['equipmentID'])){$returnpage = 'equipment&equipmentID='.$_GET['equipmentID']; } else {$returnpage = 'dashboard';}
//SHOW BACK BUTTON ONLY FOR PORTAL USERS //SHOW BACK BUTTON ONLY FOR PORTAL USERS
if (isAllowed($page ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'R') != 0){ if (isAllowed($page ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'R') != 0){
$view .= ' $view .= '
<div class="title-actions"> <div class="title-actions">
<a href="index.php?page='.$returnpage.'" class="btn alt mar-right-2"><i class="fa-solid fa-arrow-left"></i></a>
<button class="btn" onclick="showInstructions()" style=""> <button class="btn" onclick="showInstructions()" style="">
<i class="fa-solid fa-circle-question"></i> <i class="fa-solid fa-circle-question"></i>
</button> </button>

View File

@@ -591,6 +591,8 @@ main .content-block-wrapper .content-block {
width: 100%; width: 100%;
margin: 0 10px; margin: 0 10px;
border-radius: 3px; border-radius: 3px;
max-height: 300px;
overflow-y: auto;
} }
main .content-block-wrapper .content-block:first-child { main .content-block-wrapper .content-block:first-child {
@@ -3140,6 +3142,61 @@ main .content-block .button-container {
border-top: 1px solid #eee; border-top: 1px solid #eee;
} }
/* Generic Modal Styles */
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
z-index: 1000;
align-items: center;
justify-content: center;
}
.modal .modal-content {
background: white;
border-radius: 12px;
max-width: 500px;
width: 90%;
max-height: 90vh;
overflow-y: auto;
margin: 20px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.4);
}
.modal .modal-header {
padding: 20px 25px;
border-bottom: 1px solid #e0e0e0;
}
.modal .modal-header h3 {
margin: 0;
color: #333;
display: flex;
align-items: center;
gap: 10px;
}
.modal .modal-body {
padding: 25px;
}
.modal .modal-body p {
margin: 0 0 10px 0;
}
.btn.danger {
background-color: #e74c3c;
color: white;
}
.btn.danger:hover {
background-color: #c0392b;
}
/* Registration Modal Styles */ /* Registration Modal Styles */
.reg-modal { .reg-modal {

View File

@@ -402,6 +402,94 @@
font-size: 0.75rem; font-size: 0.75rem;
} }
.tag.clickable {
cursor: pointer;
transition: all 0.2s;
}
.tag.clickable:hover {
background: var(--color-primary, #007cba);
color: white;
}
/* Folder Tiles */
.folder-tile {
background: var(--color-white, #fff);
border: 1px solid var(--color-border, #dee2e6);
border-radius: 8px;
padding: 1rem;
cursor: pointer;
transition: all 0.2s;
position: relative;
}
.folder-tile:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
transform: translateY(-2px);
border-color: var(--color-primary, #005655);
}
.folder-tile.grid-item {
text-align: center;
}
.folder-tile.list-item {
display: flex;
align-items: center;
gap: 1rem;
text-align: left;
}
.folder-tile-icon {
width: 100%;
height: 150px;
margin-bottom: 1rem;
display: flex;
align-items: center;
justify-content: center;
background: var(--color-light-grey, #f8f9fa);
border-radius: 4px;
font-size: 4rem;
color: var(--color-primary, #005655);
}
.folder-tile.list-item .folder-tile-icon {
width: 60px;
height: 60px;
flex-shrink: 0;
font-size: 2rem;
margin-bottom: 0;
}
.folder-tile-info {
text-align: center;
}
.folder-tile.list-item .folder-tile-info {
flex: 1;
text-align: left;
}
.folder-tile-name {
font-weight: 600;
margin-bottom: 0.5rem;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.folder-tile-meta {
display: flex;
justify-content: center;
gap: 0.5rem;
font-size: 0.8rem;
color: var(--color-text-light, #6c757d);
}
.folder-tile.list-item .folder-tile-meta {
justify-content: flex-start;
}
/* Loading and Empty States */ /* Loading and Empty States */
.loading-indicator, .empty-state { .loading-indicator, .empty-state {
display: flex; display: flex;

276
user.php
View File

@@ -12,33 +12,25 @@ include_once './settings/settings_redirector.php';
//SET ORIGIN FOR NAVIGATION //SET ORIGIN FOR NAVIGATION
$_SESSION['prev_origin_user'] = $_SERVER['REQUEST_URI']; $_SESSION['prev_origin_user'] = $_SERVER['REQUEST_URI'];
$page = 'user'; $page = 'user';
//Check if allowed //Check if allowed
if (isAllowed($page,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'R') === 0){ if (isAllowed($page,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'R') === 0){
header('location: index.php'); header('location: index.php');
exit; exit;
} }
//PAGE Security //PAGE Security
$page_manage = 'user_manage'; $page_manage = 'user_manage';
$update_allowed = isAllowed($page ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'U'); $update_allowed = isAllowed($page ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'U');
$update_allowed_edit = isAllowed($page_manage ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'U');
$delete_allowed = isAllowed($page_manage ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'D'); $delete_allowed = isAllowed($page_manage ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'D');
$create_allowed = isAllowed($page_manage ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'C'); $create_allowed = isAllowed($page_manage ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'C');
//GET Details from URL //GET Details from URL
$user_ID = $_GET['id'] ?? ''; $user_ID = $_GET['id'] ?? '';
if ($user_ID == ''){ // Determine if this is a new user creation
header('location: index.php?page=users'); $is_new_user = empty($user_ID);
exit;
}
//CALL TO API FOR User information
$api_url = '/v2/users/id='.$user_ID;
$responses = ioServer($api_url,'');
//Decode Payload
if (!empty($responses)){$responses = json_decode($responses);}else{$responses = null;}
$user = $responses[0];
//Helper function to convert service hex string to 1/0 //Helper function to convert service hex string to 1/0
function isServiceActive($service) { function isServiceActive($service) {
@@ -49,13 +41,53 @@ function isServiceActive($service) {
return 0; return 0;
} }
$service_active = isServiceActive($user->service); if ($is_new_user) {
// Check create permission for new users
if ($create_allowed !== 1) {
header('location: index.php?page=users');
exit;
}
// Create empty user object with default values
$user = (object)[
'rowID' => '',
'username' => '',
'email' => '',
'userkey' => '1',
'view' => 3,
'settings' => '',
'service' => 0,
'language' => '',
'login_count' => 0,
'partnerhierarchy' => json_encode($_SESSION['authorization']['partnerhierarchy'] ?? new stdClass()),
'created' => null,
'updated' => null,
'lastlogin' => null,
'updatedby' => null
];
$service_active = 0;
$role_assignments = null;
} else {
//CALL TO API FOR User information
$api_url = '/v2/users/id='.$user_ID;
$responses = ioServer($api_url,'');
//Decode Payload
if (!empty($responses)){$responses = json_decode($responses);}else{$responses = null;}
$user = $responses[0] ?? null;
//CALL TO API FOR User Role Assignments // If user not found, redirect
$api_url = '/v2/user_role_assignments/user_id='.$user_ID; if ($user === null) {
$role_assignments = ioServer($api_url,''); header('location: index.php?page=users');
//Decode Payload exit;
if (!empty($role_assignments)){$role_assignments = json_decode($role_assignments);}else{$role_assignments = null;} }
$service_active = isServiceActive($user->service);
//CALL TO API FOR User Role Assignments
$api_url = '/v2/user_role_assignments/user_id='.$user_ID;
$role_assignments = ioServer($api_url,'');
//Decode Payload
if (!empty($role_assignments)){$role_assignments = json_decode($role_assignments);}else{$role_assignments = null;}
}
//CALL TO API FOR All Available Roles //CALL TO API FOR All Available Roles
$api_url = '/v2/user_roles/status=1&p=1'; $api_url = '/v2/user_roles/status=1&p=1';
@@ -70,10 +102,56 @@ if (!empty($all_roles_response)){
$all_roles = []; $all_roles = [];
} }
//------------------------------
// Handle POST for creating new user
//------------------------------
if (isset($_POST['create_user']) && $create_allowed === 1 && $is_new_user) {
// Build user data for new user
$user_data = [
'userkey' => $_POST['userkey'] ?? 1,
'username' => $_POST['username'] ?? '',
'email' => $_POST['email'] ?? '',
'view' => $_POST['view'] ?? 3,
'settings' => $_POST['settings'] ?? '',
'service' => $_POST['service'] ?? 0,
'language' => $_POST['language'] ?? '',
'login_count' => 0,
'salesid' => $_POST['salesid'] ?? '',
'soldto' => $_POST['soldto'] ?? '',
'shipto' => $_POST['shipto'] ?? '',
'location' => $_POST['location'] ?? ''
];
$data = json_encode($user_data, JSON_UNESCAPED_UNICODE);
$response = ioServer('/v2/users', $data);
// Get the new user ID from the response
$new_user = json_decode($response);
$new_user_id = $new_user->id ?? null;
// Save role assignments for new user if we have an ID and roles are selected
if ($new_user_id && !empty($_POST['roles'])) {
$role_data = [
'batch_update' => true,
'user_id' => (int)$new_user_id,
'roles' => array_map('intval', $_POST['roles'])
];
$data = json_encode($role_data, JSON_UNESCAPED_UNICODE);
ioServer('/v2/user_role_assignments', $data);
}
if ($new_user_id) {
header('Location: index.php?page=user&id='.$new_user_id.'&success_msg=1');
} else {
header('Location: index.php?page=users&success_msg=1');
}
exit;
}
//------------------------------ //------------------------------
// Handle POST for inline edit (user AND roles) // Handle POST for inline edit (user AND roles)
//------------------------------ //------------------------------
if (isset($_POST['save_user']) && $update_allowed_edit === 1) { if (isset($_POST['save_user']) && $update_allowed === 1 && !$is_new_user) {
// Build user data using existing field names // Build user data using existing field names
$user_data = [ $user_data = [
'id' => $user_ID, 'id' => $user_ID,
@@ -109,7 +187,7 @@ if (isset($_POST['save_user']) && $update_allowed_edit === 1) {
} }
// Handle password reset // Handle password reset
if (isset($_POST['reset']) && $update_allowed_edit === 1) { if (isset($_POST['reset']) && $update_allowed === 1) {
$data = json_encode(['id' => $user_ID, 'reset' => 'reset'], JSON_UNESCAPED_UNICODE); $data = json_encode(['id' => $user_ID, 'reset' => 'reset'], JSON_UNESCAPED_UNICODE);
ioServer('/v2/users', $data); ioServer('/v2/users', $data);
header('Location: index.php?page=user&id='.$user_ID.'&success_msg=4'); header('Location: index.php?page=user&id='.$user_ID.'&success_msg=4');
@@ -117,7 +195,7 @@ if (isset($_POST['reset']) && $update_allowed_edit === 1) {
} }
// Handle unblock // Handle unblock
if (isset($_POST['unblock']) && $update_allowed_edit === 1) { if (isset($_POST['unblock']) && $update_allowed === 1) {
$data = json_encode(['id' => $user_ID, 'login_count' => '0'], JSON_UNESCAPED_UNICODE); $data = json_encode(['id' => $user_ID, 'login_count' => '0'], JSON_UNESCAPED_UNICODE);
ioServer('/v2/users', $data); ioServer('/v2/users', $data);
header('Location: index.php?page=user&id='.$user_ID.'&success_msg=5'); header('Location: index.php?page=user&id='.$user_ID.'&success_msg=5');
@@ -172,13 +250,24 @@ if (isset($_GET['success_msg'])) {
} }
template_header(($user_title ?? 'User'), 'user', 'view'); template_header(($user_title ?? 'User'), 'user', 'view');
if ($is_new_user) {
$page_title = ($user_new ?? 'New User');
} else {
$page_title = ($user_h2 ?? 'User').' - '.$user->username;
}
$view = ' $view = '
<div class="content-title responsive-flex-wrap responsive-pad-bot-3"> <div class="content-title responsive-flex-wrap responsive-pad-bot-3">
<h2 class="responsive-width-100">'.($user_h2 ?? 'User').' - '.$user->username.'</h2> <h2 class="responsive-width-100">'.$page_title.'</h2>
<a href="index.php?page='.($_SESSION['origin'] ?? 'users').'&p='.($_SESSION['p'] ?? '1').($_SESSION['status'] ?? '').($_SESSION['sort'] ?? '').($_SESSION['search'] ?? '').'" class="btn alt mar-right-2">←</a> <a href="index.php?page='.($_SESSION['origin'] ?? 'users').'&p='.($_SESSION['p'] ?? '1').($_SESSION['status'] ?? '').($_SESSION['sort'] ?? '').($_SESSION['search'] ?? '').'" class="btn alt mar-right-2">←</a>
'; ';
if ($update_allowed_edit === 1){ if ($is_new_user) {
// New user mode - show save button directly
$view .= '<button type="submit" form="userForm" id="saveBtn" class="btn">💾</button>';
} elseif ($update_allowed === 1) {
// Edit mode - show edit/save toggle
$view .= '<a href="javascript:void(0);" id="editBtn" class="btn mar-right-2" onclick="toggleUserEdit()">✏️</a>'; $view .= '<a href="javascript:void(0);" id="editBtn" class="btn mar-right-2" onclick="toggleUserEdit()">✏️</a>';
$view .= '<button type="submit" form="userForm" id="saveBtn" class="btn" style="display:none;">💾</button>'; $view .= '<button type="submit" form="userForm" id="saveBtn" class="btn" style="display:none;">💾</button>';
} }
@@ -194,12 +283,17 @@ if (isset($success_msg)){
} }
// Start form wrapper for edit mode // Start form wrapper for edit mode
$form_action = $is_new_user ? 'create_user' : 'save_user';
$view .= '<form id="userForm" action="" method="post"> $view .= '<form id="userForm" action="" method="post">
<input type="hidden" name="save_user" value="1"> <input type="hidden" name="'.$form_action.'" value="1">
<input type="hidden" name="id" value="'.$user_ID.'">'; <input type="hidden" name="id" value="'.$user_ID.'">';
$view .= '<div class="content-block-wrapper">'; $view .= '<div class="content-block-wrapper">';
// Display styles for view/edit mode (new users start in edit mode)
$view_style = $is_new_user ? 'display:none;' : '';
$edit_style = $is_new_user ? '' : 'display:none;';
// User Information Block // User Information Block
$view .= ' <div class="content-block order-details"> $view .= ' <div class="content-block order-details">
<div class="block-header"> <div class="block-header">
@@ -208,8 +302,8 @@ $view .= ' <div class="content-block order-details">
<div class="order-detail"> <div class="order-detail">
<h3>'.($general_status ?? 'Status').'</h3> <h3>'.($general_status ?? 'Status').'</h3>
<p> <p>
<span class="view-mode status '.$status_class.'">'.$status_text.'</span> <span class="view-mode status '.$status_class.'" style="'.$view_style.'">'.$status_text.'</span>
<select class="edit-mode" name="userkey" style="display:none;"> <select class="edit-mode" name="userkey" style="'.$edit_style.'">
<option value="1"'.($is_active ? ' selected' : '').'>'.($enabled ?? 'Active').'</option> <option value="1"'.($is_active ? ' selected' : '').'>'.($enabled ?? 'Active').'</option>
<option value="0"'.(!$is_active ? ' selected' : '').'>'.($disabled ?? 'Inactive').'</option> <option value="0"'.(!$is_active ? ' selected' : '').'>'.($disabled ?? 'Inactive').'</option>
</select> </select>
@@ -218,22 +312,22 @@ $view .= ' <div class="content-block order-details">
<div class="order-detail"> <div class="order-detail">
<h3>'.($User_username ?? 'Username').'</h3> <h3>'.($User_username ?? 'Username').'</h3>
<p> <p>
<span class="view-mode">'.$user->username.'</span> <span class="view-mode" style="'.$view_style.'">'.$user->username.'</span>
<input type="text" class="edit-mode" name="username" value="'.$user->username.'" style="display:none;" pattern="^\S+$" required> <input type="text" class="edit-mode" name="username" value="'.$user->username.'" style="'.$edit_style.'" pattern="^\S+$" required>
</p> </p>
</div> </div>
<div class="order-detail"> <div class="order-detail">
<h3>'.($User_email ?? 'Email').'</h3> <h3>'.($User_email ?? 'Email').'</h3>
<p> <p>
<span class="view-mode">'.$user->email.'</span> <span class="view-mode" style="'.$view_style.'">'.$user->email.'</span>
<input type="email" class="edit-mode" name="email" value="'.$user->email.'" style="display:none;" required> <input type="email" class="edit-mode" name="email" value="'.$user->email.'" style="'.$edit_style.'" required>
</p> </p>
</div> </div>
<div class="order-detail"> <div class="order-detail">
<h3>'.($User_language ?? 'Language').'</h3> <h3>'.($User_language ?? 'Language').'</h3>
<p> <p>
<span class="view-mode">'.($user->language ?? '-').'</span> <span class="view-mode" style="'.$view_style.'">'.($user->language ?? '-').'</span>
<select class="edit-mode" name="language" style="display:none;"> <select class="edit-mode" name="language" style="'.$edit_style.'">
<option value="">-</option>'; <option value="">-</option>';
foreach ($supportedLanguages as $language){ foreach ($supportedLanguages as $language){
$view .= '<option value="'.$language.'"'.(($user->language == $language) ? ' selected' : '').'>'.$language.'</option>'; $view .= '<option value="'.$language.'"'.(($user->language == $language) ? ' selected' : '').'>'.$language.'</option>';
@@ -249,7 +343,7 @@ $view .='<div class="content-block order-details" id="rolesBlock">
<div class="block-header"> <div class="block-header">
<i class="fa-solid fa-user-shield fa-sm"></i>'.($view_user_roles ?? 'Assigned Roles').' <i class="fa-solid fa-user-shield fa-sm"></i>'.($view_user_roles ?? 'Assigned Roles').'
</div> </div>
<div class="view-mode-roles">'; <div class="view-mode-roles" style="'.($is_new_user ? 'display:none;' : '').'">';
// Get list of already assigned role IDs // Get list of already assigned role IDs
$assigned_role_ids = []; $assigned_role_ids = [];
@@ -290,9 +384,9 @@ if (!empty($role_assignments)){
$view .= '</div>'; // Close view-mode-roles $view .= '</div>'; // Close view-mode-roles
// EDIT MODE - Show all roles with checkboxes (only if user has edit permission) // EDIT MODE - Show all roles with checkboxes (only if user has edit permission or is new user)
if ($update_allowed_edit === 1 && !empty($all_roles)){ if (($update_allowed === 1 || $is_new_user) && !empty($all_roles)){
$view .= '<div class="edit-mode-roles" style="display:none;">'; $view .= '<div class="edit-mode-roles" style="'.($is_new_user ? '' : 'display:none;').'">';
foreach ($all_roles as $role){ foreach ($all_roles as $role){
$is_checked = in_array($role->rowID, $assigned_role_ids) ? ' checked' : ''; $is_checked = in_array($role->rowID, $assigned_role_ids) ? ' checked' : '';
@@ -326,7 +420,7 @@ $view .= '<div class="content-block">
<tr> <tr>
<td style="width:25%;">'.($User_permission ?? 'Permission Level').'</td> <td style="width:25%;">'.($User_permission ?? 'Permission Level').'</td>
<td> <td>
<span class="view-mode">'; <span class="view-mode" style="'.$view_style.'">';
// Display permission level text // Display permission level text
switch($user->view){ switch($user->view){
@@ -339,7 +433,7 @@ switch($user->view){
} }
$view .= '</span> $view .= '</span>
<select class="edit-mode" name="view" style="display:none;"> <select class="edit-mode" name="view" style="'.$edit_style.'">
<option value="3"'.($user->view == 3 ? ' selected' : '').'>'.($permission3 ?? 'Admin').'</option> <option value="3"'.($user->view == 3 ? ' selected' : '').'>'.($permission3 ?? 'Admin').'</option>
<option value="2"'.($user->view == 2 ? ' selected' : '').'>'.($permission2 ?? 'Edit').'</option> <option value="2"'.($user->view == 2 ? ' selected' : '').'>'.($permission2 ?? 'Edit').'</option>
<option value="1"'.($user->view == 1 ? ' selected' : '').'>'.($permission1 ?? 'View').'</option>'; <option value="1"'.($user->view == 1 ? ' selected' : '').'>'.($permission1 ?? 'View').'</option>';
@@ -358,10 +452,10 @@ $view .= ' </select>
<tr> <tr>
<td style="width:25%;">'.($User_profile ?? 'Profile').'</td> <td style="width:25%;">'.($User_profile ?? 'Profile').'</td>
<td> <td>
<span class="view-mode">'.($user->settings ?? '-').'</span>'; <span class="view-mode" style="'.$view_style.'">'.($user->settings ?? '-').'</span>';
if ($_SESSION['authorization']['permission'] == 3 || $_SESSION['authorization']['permission'] == 4){ if ($_SESSION['authorization']['permission'] == 3 || $_SESSION['authorization']['permission'] == 4){
$view .= '<select class="edit-mode" name="settings" style="display:none;"> $view .= '<select class="edit-mode" name="settings" style="'.$edit_style.'">
<option value="">-</option>'; <option value="">-</option>';
foreach ($all_profiles as $profile) { foreach ($all_profiles as $profile) {
$view .= '<option value="'.$profile.'"'.($user->settings == $profile ? ' selected' : '').'>'.$profile.'</option>'; $view .= '<option value="'.$profile.'"'.($user->settings == $profile ? ' selected' : '').'>'.$profile.'</option>';
@@ -376,8 +470,8 @@ $view .= ' </td>
<tr> <tr>
<td style="width:25%;">'.($User_service ?? 'Service Access').'</td> <td style="width:25%;">'.($User_service ?? 'Service Access').'</td>
<td> <td>
<span class="view-mode">'.(($service_active == 1) ? ($enabled ?? 'Enabled') : ($disabled ?? 'Disabled')).'</span> <span class="view-mode" style="'.$view_style.'">'.(($service_active == 1) ? ($enabled ?? 'Enabled') : ($disabled ?? 'Disabled')).'</span>
<select class="edit-mode" name="service" style="display:none;"> <select class="edit-mode" name="service" style="'.$edit_style.'">
<option value="1"'.(($service_active == 1) ? ' selected' : '').'>'.($enabled ?? 'Enabled').'</option> <option value="1"'.(($service_active == 1) ? ' selected' : '').'>'.($enabled ?? 'Enabled').'</option>
<option value="0"'.(($service_active == 0) ? ' selected' : '').'>'.($disabled ?? 'Disabled').'</option> <option value="0"'.(($service_active == 0) ? ' selected' : '').'>'.($disabled ?? 'Disabled').'</option>
</select> </select>
@@ -404,15 +498,15 @@ if ($_SESSION['authorization']['permission'] == 3 || $_SESSION['authorization'][
$view .= '<tr> $view .= '<tr>
<td style="width:25%;">'.($general_salesid ?? 'Sales ID').'</td> <td style="width:25%;">'.($general_salesid ?? 'Sales ID').'</td>
<td> <td>
<span class="view-mode">'.($partner_data->salesid ?? '-').'</span> <span class="view-mode" style="'.$view_style.'">'.($partner_data->salesid ?? '-').'</span>
<span class="edit-mode" style="display:none;">'.$salesid_dropdown.'</span> <span class="edit-mode" style="'.$edit_style.'">'.$salesid_dropdown.'</span>
</td> </td>
</tr> </tr>
<tr> <tr>
<td style="width:25%;">'.($general_soldto ?? 'Sold To').'</td> <td style="width:25%;">'.($general_soldto ?? 'Sold To').'</td>
<td> <td>
<span class="view-mode">'.($partner_data->soldto ?? '-').'</span> <span class="view-mode" style="'.$view_style.'">'.($partner_data->soldto ?? '-').'</span>
<span class="edit-mode" style="display:none;">'.$soldto_dropdown.'</span> <span class="edit-mode" style="'.$edit_style.'">'.$soldto_dropdown.'</span>
</td> </td>
</tr>'; </tr>';
} }
@@ -423,65 +517,67 @@ $location_dropdown = listPartner('location', $_SESSION['authorization']['permiss
$view .= '<tr> $view .= '<tr>
<td style="width:25%;">'.($general_shipto ?? 'Ship To').'</td> <td style="width:25%;">'.($general_shipto ?? 'Ship To').'</td>
<td> <td>
<span class="view-mode">'.($partner_data->shipto ?? '-').'</span> <span class="view-mode" style="'.$view_style.'">'.($partner_data->shipto ?? '-').'</span>
<span class="edit-mode" style="display:none;">'.$shipto_dropdown.'</span> <span class="edit-mode" style="'.$edit_style.'">'.$shipto_dropdown.'</span>
</td> </td>
</tr> </tr>
<tr> <tr>
<td style="width:25%;">'.($general_location ?? 'Location').'</td> <td style="width:25%;">'.($general_location ?? 'Location').'</td>
<td> <td>
<span class="view-mode">'.($partner_data->location ?? '-').'</span> <span class="view-mode" style="'.$view_style.'">'.($partner_data->location ?? '-').'</span>
<span class="edit-mode" style="display:none;">'.$location_dropdown.'</span> <span class="edit-mode" style="'.$edit_style.'">'.$location_dropdown.'</span>
</td> </td>
</tr> </tr>
</table> </table>
</div> </div>
</div>'; </div>';
// Metadata Block // Metadata Block (hide for new users)
$view .= '<div class="content-block"> if (!$is_new_user) {
<div class="block-header"> $view .= '<div class="content-block">
<i class="fa-solid fa-bars fa-sm"></i>'.($tab3 ?? 'Details').' <div class="block-header">
</div> <i class="fa-solid fa-bars fa-sm"></i>'.($tab3 ?? 'Details').'
<div class="table order-table"> </div>
<table> <div class="table order-table">
<tr> <table>
<td style="width:25%;">'.($general_created ?? 'Created').'</td> <tr>
<td>'.getRelativeTime($user->created).'</td> <td style="width:25%;">'.($general_created ?? 'Created').'</td>
</tr> <td>'.getRelativeTime($user->created).'</td>
<tr> </tr>
<td style="width:25%;">'.($User_lastlogin ?? 'Last Login').'</td> <tr>
<td>'.($user->lastlogin ? getRelativeTime($user->lastlogin) : '-').'</td> <td style="width:25%;">'.($User_lastlogin ?? 'Last Login').'</td>
</tr> <td>'.($user->lastlogin ? getRelativeTime($user->lastlogin) : '-').'</td>
<tr> </tr>
<td style="width:25%;">'.($general_updated ?? 'Updated').'</td> <tr>
<td>'.($user->updated ? getRelativeTime($user->updated) : '-').'</td> <td style="width:25%;">'.($general_updated ?? 'Updated').'</td>
</tr> <td>'.($user->updated ? getRelativeTime($user->updated) : '-').'</td>
<tr> </tr>
<td style="width:25%;">'.($general_updatedby ?? 'Updated By').'</td> <tr>
<td>'.($user->updatedby ?? '-').'</td> <td style="width:25%;">'.($general_updatedby ?? 'Updated By').'</td>
</tr> <td>'.($user->updatedby ?? '-').'</td>
<tr> </tr>
<td style="width:25%;">'.($User_pw_login_count ?? 'Login Attempts').'</td> <tr>
<td> <td style="width:25%;">'.($User_pw_login_count ?? 'Login Attempts').'</td>
<span class="view-mode">'.$user->login_count.'</span>'; <td>
<span class="view-mode">'.$user->login_count.'</span>';
if ($_SESSION['authorization']['permission'] == 3 || $_SESSION['authorization']['permission'] == 4){ if ($_SESSION['authorization']['permission'] == 3 || $_SESSION['authorization']['permission'] == 4){
$view .= '<input type="number" class="edit-mode" name="login_count" value="'.$user->login_count.'" style="display:none; width: 80px;">'; $view .= '<input type="number" class="edit-mode" name="login_count" value="'.$user->login_count.'" style="display:none; width: 80px;">';
} else { } else {
$view .= '<input type="hidden" name="login_count" value="'.$user->login_count.'">'; $view .= '<input type="hidden" name="login_count" value="'.$user->login_count.'">';
}
$view .= ' </td>
</tr>
</table>
</div>
</div>';
} }
$view .= '</form>
$view .= ' </td>
</tr>
</table>
</div>
</div>
</form>
'; ';
// Actions Block (outside form for separate actions) // Actions Block (outside form for separate actions, hide for new users)
if ($update_allowed_edit === 1){ if ($update_allowed === 1 && !$is_new_user){
$view .= '<div class="content-block"> $view .= '<div class="content-block">
<div class="block-header"> <div class="block-header">
<i class="fa-solid fa-bolt fa-sm"></i>'.($general_actions ?? 'Actions').' <i class="fa-solid fa-bolt fa-sm"></i>'.($general_actions ?? 'Actions').'

View File

@@ -24,6 +24,17 @@ $update_allowed = isAllowed($page ,$_SESSION['authorization']['permissions'],$_S
$update_allowed_edit = isAllowed($page_manage ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'U'); $update_allowed_edit = isAllowed($page_manage ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'U');
$delete_allowed = isAllowed($page_manage ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'D'); $delete_allowed = isAllowed($page_manage ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'D');
$create_allowed = isAllowed($page_manage ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'C'); $create_allowed = isAllowed($page_manage ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'C');
$system_role_allowed = isAllowed('user_roles' ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'D');
//Handle AJAX request for role permissions (copy functionality)
if (isset($_GET['action']) && $_GET['action'] === 'get_role_permissions' && isset($_GET['source_role_id'])) {
header('Content-Type: application/json');
$source_role_id = intval($_GET['source_role_id']);
$api_url = '/v2/role_access_permissions/role_id='.$source_role_id;
$role_perms = ioServer($api_url,'');
echo $role_perms;
exit;
}
//GET Details from URL //GET Details from URL
$GET_VALUES = urlGETdetails($_GET) ?? ''; $GET_VALUES = urlGETdetails($_GET) ?? '';
@@ -63,17 +74,28 @@ $assignments = ioServer($api_url,'');
//Decode Payload //Decode Payload
if (!empty($assignments)){$assignments = json_decode($assignments);}else{$assignments = null;} if (!empty($assignments)){$assignments = json_decode($assignments);}else{$assignments = null;}
//CALL TO API FOR All User Roles (for copy dropdown)
$api_url = '/v2/user_roles/status=1&all=';
$all_roles = ioServer($api_url,'');
//Decode Payload
if (!empty($all_roles)){$all_roles = json_decode($all_roles);}else{$all_roles = null;}
//------------------------------ //------------------------------
// Handle POST for inline edit // Handle POST for inline edit
//------------------------------ //------------------------------
if (isset($_POST['save_permissions']) && $update_allowed_edit === 1) { if (isset($_POST['save_permissions']) && $update_allowed_edit === 1) {
// Update role info (name, description, status) // Update role info (name, description, status, system role)
$role_data = json_encode([ $role_data_array = [
'rowID' => $role_id, 'rowID' => $role_id,
'name' => $_POST['name'] ?? '', 'name' => $_POST['name'] ?? '',
'description' => $_POST['description'] ?? '', 'description' => $_POST['description'] ?? '',
'is_active' => $_POST['is_active'] ?? 1 'is_active' => $_POST['is_active'] ?? 1
], JSON_UNESCAPED_UNICODE); ];
// Only allow is_system to be changed if user has delete permission on user_roles
if ($system_role_allowed === 1) {
$role_data_array['is_system'] = isset($_POST['is_system']) ? 1 : 0;
}
$role_data = json_encode($role_data_array, JSON_UNESCAPED_UNICODE);
ioServer('/v2/user_roles', $role_data); ioServer('/v2/user_roles', $role_data);
// Process permission updates // Process permission updates
@@ -132,6 +154,21 @@ if (isset($_POST['save_permissions']) && $update_allowed_edit === 1) {
exit; exit;
} }
//------------------------------
// Handle POST for delete
//------------------------------
if (isset($_POST['delete_role']) && $delete_allowed === 1) {
$role_data = json_encode([
'rowID' => $role_id,
'delete' => 'delete'
], JSON_UNESCAPED_UNICODE);
ioServer('/v2/user_roles', $role_data);
// Redirect to roles list with success message
header('Location: index.php?page='.$_SESSION['origin'].'&success_msg=3');
exit;
}
//------------------------------ //------------------------------
//Variables //Variables
@@ -161,7 +198,11 @@ $view = '
if ($update_allowed_edit === 1){ if ($update_allowed_edit === 1){
$view .= '<a href="javascript:void(0);" id="editBtn" class="btn mar-right-2" onclick="togglePermissionsEdit()">✏️</a>'; $view .= '<a href="javascript:void(0);" id="editBtn" class="btn mar-right-2" onclick="togglePermissionsEdit()">✏️</a>';
$view .= '<button type="submit" form="permissionsForm" id="saveBtn" class="btn" style="display:none;">💾</button>'; $view .= '<button type="submit" form="permissionsForm" id="saveBtn" class="btn mar-right-2" style="display:none;">💾</button>';
}
if ($delete_allowed === 1){
$view .= '<a href="javascript:void(0);" id="deleteBtn" class="btn alt" onclick="confirmDeleteRole()">🗑️</a>';
} }
$view .= '</div>'; $view .= '</div>';
@@ -174,6 +215,32 @@ if (isset($success_msg)){
</div>'; </div>';
} }
// Delete form (hidden)
if ($delete_allowed === 1){
$view .= '<form id="deleteRoleForm" action="" method="post" style="display:none;">
<input type="hidden" name="delete_role" value="1">
<input type="hidden" name="rowID" value="'.$role_id.'">
</form>';
// Delete confirmation modal
$view .= '<div id="deleteModal" class="modal" style="display:none;">
<div class="modal-content">
<div class="modal-header">
<h3><i class="fa-solid fa-triangle-exclamation" style="color:#e74c3c;"></i> '.($confirm_delete_title ?? 'Confirm Delete').'</h3>
</div>
<div class="modal-body">
<p>'.($confirm_delete_role ?? 'Are you sure you want to delete this role?').'</p>
<p><strong>'.$responses->name.'</strong></p>
<p class="warning-text" style="color:#e74c3c; margin-top:10px;">'.($delete_role_warning ?? 'This will also remove all permissions and user assignments associated with this role.').'</p>
</div>
<div class="modal-footer">
<button type="button" class="btn alt" onclick="closeDeleteModal()">'.($cancel ?? 'Cancel').'</button>
<button type="button" class="btn danger" onclick="executeDeleteRole()">'.($delete ?? 'Delete').'</button>
</div>
</div>
</div>';
}
// Start form wrapper for edit mode // Start form wrapper for edit mode
$view .= '<form id="permissionsForm" action="" method="post"> $view .= '<form id="permissionsForm" action="" method="post">
<input type="hidden" name="save_permissions" value="1"> <input type="hidden" name="save_permissions" value="1">
@@ -210,6 +277,31 @@ $view .= ' <div class="content-block order-details">
<textarea class="edit-mode" name="description" style="display:none;">'.($responses->description ?? '').'</textarea> <textarea class="edit-mode" name="description" style="display:none;">'.($responses->description ?? '').'</textarea>
</p> </p>
</div> </div>
<div class="order-detail">
<h3>'.($role_system ?? 'System Role').'</h3>
<p>
<span class="'.($system_role_allowed === 1 ? 'view-mode' : '').'">'.($responses->is_system == 1 ? '<i class="fa-solid fa-check" style="color:green;"></i> '.($yes ?? 'Yes') : '<i class="fa-solid fa-times" style="color:red;"></i> '.($no ?? 'No')).'</span>
'.($system_role_allowed === 1 ? '<label class="edit-mode" style="display:none;">
<input type="checkbox" name="is_system" value="1"'.($responses->is_system == 1 ? ' checked' : '').'> '.($role_system_label ?? 'Mark as system role').'
</label>' : '').'
</p>
</div>
<div class="order-detail edit-mode-block" style="display:none;">
<h3>'.($copy_from_role ?? 'Copy Permissions From').'</h3>
<p>
<select id="copyFromRole" onchange="copyPermissionsFromRole(this.value)" style="width:100%; max-width:300px;">
<option value="">'.($select_role ?? '-- Select Role --').'</option>';
if (!empty($all_roles)){
foreach ($all_roles as $r){
if ($r->rowID != $role_id){
$view .= '<option value="'.$r->rowID.'">'.$r->name.'</option>';
}
}
}
$view .= ' </select>
<small style="display:block; margin-top:5px; color:#666;">'.($copy_permissions_hint ?? 'Selecting a role will override current permissions').'</small>
</p>
</div>
</div> </div>
'; ';
@@ -350,17 +442,20 @@ function togglePermissionsEdit() {
var saveBtn = document.getElementById("saveBtn"); var saveBtn = document.getElementById("saveBtn");
var viewElements = document.querySelectorAll(".view-mode"); var viewElements = document.querySelectorAll(".view-mode");
var editElements = document.querySelectorAll(".edit-mode"); var editElements = document.querySelectorAll(".edit-mode");
var editBlockElements = document.querySelectorAll(".edit-mode-block");
var editOnlyRows = document.querySelectorAll(".edit-only-row"); var editOnlyRows = document.querySelectorAll(".edit-only-row");
var i; var i;
if (permissionsEditMode) { if (permissionsEditMode) {
for (i = 0; i < viewElements.length; i++) { viewElements[i].style.display = "none"; } for (i = 0; i < viewElements.length; i++) { viewElements[i].style.display = "none"; }
for (i = 0; i < editElements.length; i++) { editElements[i].style.display = "inline"; } for (i = 0; i < editElements.length; i++) { editElements[i].style.display = "inline"; }
for (i = 0; i < editBlockElements.length; i++) { editBlockElements[i].style.display = "block"; }
for (i = 0; i < editOnlyRows.length; i++) { editOnlyRows[i].style.display = "table-row"; } for (i = 0; i < editOnlyRows.length; i++) { editOnlyRows[i].style.display = "table-row"; }
editBtn.style.display = "none"; editBtn.style.display = "none";
saveBtn.style.display = "inline-block"; saveBtn.style.display = "inline-block";
} else { } else {
for (i = 0; i < viewElements.length; i++) { viewElements[i].style.display = "inline"; } for (i = 0; i < viewElements.length; i++) { viewElements[i].style.display = "inline"; }
for (i = 0; i < editElements.length; i++) { editElements[i].style.display = "none"; } for (i = 0; i < editElements.length; i++) { editElements[i].style.display = "none"; }
for (i = 0; i < editBlockElements.length; i++) { editBlockElements[i].style.display = "none"; }
for (i = 0; i < editOnlyRows.length; i++) { editOnlyRows[i].style.display = "none"; } for (i = 0; i < editOnlyRows.length; i++) { editOnlyRows[i].style.display = "none"; }
editBtn.style.display = "inline-block"; editBtn.style.display = "inline-block";
saveBtn.style.display = "none"; saveBtn.style.display = "none";
@@ -378,6 +473,66 @@ function toggleColumn(type) {
for (var i = 0; i < checkboxes.length; i++) { for (var i = 0; i < checkboxes.length; i++) {
checkboxes[i].checked = !allChecked; checkboxes[i].checked = !allChecked;
} }
}
async function copyPermissionsFromRole(roleId) {
if (!roleId) return;
if (!confirm("'.($confirm_copy_permissions ?? 'This will override all current permission settings. Continue?').'")) {
document.getElementById("copyFromRole").value = "";
return;
}
try {
// Call PHP page which will use ioServer to get permissions
const response = await fetch("index.php?page=user_role&action=get_role_permissions&source_role_id=" + roleId);
if (!response.ok) throw new Error("Failed to fetch");
const permissions = await response.json();
// Create a lookup map of permissions by access_id
var permMap = {};
if (permissions && permissions.length > 0) {
for (var i = 0; i < permissions.length; i++) {
permMap[permissions[i].access_id] = permissions[i];
}
}
// Get all permission checkboxes and reset them
var allCheckboxes = document.querySelectorAll("input[type=checkbox][name^=\\"permissions[\\"]");
for (var i = 0; i < allCheckboxes.length; i++) {
allCheckboxes[i].checked = false;
}
// Apply copied permissions
for (var accessId in permMap) {
var perm = permMap[accessId];
var cbCreate = document.querySelector("input[name=\\"permissions[" + accessId + "][C]\\"]");
var cbRead = document.querySelector("input[name=\\"permissions[" + accessId + "][R]\\"]");
var cbUpdate = document.querySelector("input[name=\\"permissions[" + accessId + "][U]\\"]");
var cbDelete = document.querySelector("input[name=\\"permissions[" + accessId + "][D]\\"]");
if (cbCreate && perm.can_create == 1) cbCreate.checked = true;
if (cbRead && perm.can_read == 1) cbRead.checked = true;
if (cbUpdate && perm.can_update == 1) cbUpdate.checked = true;
if (cbDelete && perm.can_delete == 1) cbDelete.checked = true;
}
// Reset dropdown
document.getElementById("copyFromRole").value = "";
} catch (error) {
console.error("Error copying permissions:", error);
alert("'.($error_copy_permissions ?? 'Failed to copy permissions. Please try again.').'");
document.getElementById("copyFromRole").value = "";
}
}
function confirmDeleteRole() {
document.getElementById("deleteModal").style.display = "flex";
}
function closeDeleteModal() {
document.getElementById("deleteModal").style.display = "none";
}
function executeDeleteRole() {
document.getElementById("deleteRoleForm").submit();
}'; }';
template_footer($js); template_footer($js);

View File

@@ -23,6 +23,7 @@ if (isAllowed($page,$_SESSION['authorization']['permissions'],$_SESSION['authori
exit; exit;
} }
//PAGE Security //PAGE Security
$page = 'user';
$update_allowed = isAllowed($page ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'U'); $update_allowed = isAllowed($page ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'U');
$delete_allowed = isAllowed($page ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'D'); $delete_allowed = isAllowed($page ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'D');
$create_allowed = isAllowed($page ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'C'); $create_allowed = isAllowed($page ,$_SESSION['authorization']['permissions'],$_SESSION['authorization']['permission'],'C');

View File

@@ -157,8 +157,8 @@ try {
// Create license // Create license
$sql = 'INSERT INTO products_software_licenses $sql = 'INSERT INTO products_software_licenses
(version_id, license_type, license_key, status, starts_at, expires_at, transaction_id, created, createdby) (version_id, license_type, license_key, status, starts_at, expires_at, transaction_id,accounthierarchy, created, createdby)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)'; VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)';
$stmt = $pdo->prepare($sql); $stmt = $pdo->prepare($sql);
$stmt->execute([ $stmt->execute([
$item['item_id'], // version_id $item['item_id'], // version_id
@@ -168,6 +168,7 @@ try {
date('Y-m-d H:i:s'), date('Y-m-d H:i:s'),
'2099-12-31 23:59:59', // effectively permanent '2099-12-31 23:59:59', // effectively permanent
$orderId, $orderId,
'{"salesid":"21-Total Safety Solutions B.V.","soldto":""}',
date('Y-m-d H:i:s'), date('Y-m-d H:i:s'),
'webhook' // created by webhook 'webhook' // created by webhook
]); ]);

View File

@@ -203,8 +203,8 @@ try {
// Create license // Create license
$sql = 'INSERT INTO products_software_licenses $sql = 'INSERT INTO products_software_licenses
(version_id, license_type, license_key, status, starts_at, expires_at, transaction_id, created, createdby) (version_id, license_type, license_key, status, starts_at, expires_at, transaction_id, accounthierarchy,created, createdby)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)'; VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)';
$stmt = $pdo->prepare($sql); $stmt = $pdo->prepare($sql);
$stmt->execute([ $stmt->execute([
$item['item_id'], // version_id $item['item_id'], // version_id
@@ -214,6 +214,7 @@ try {
date('Y-m-d H:i:s'), date('Y-m-d H:i:s'),
'2099-12-31 23:59:59', // effectively permanent '2099-12-31 23:59:59', // effectively permanent
$orderId, $orderId,
'{"salesid":"21-Total Safety Solutions B.V.","soldto":""}',
date('Y-m-d H:i:s'), date('Y-m-d H:i:s'),
'webhook_paypal' // created by PayPal webhook 'webhook_paypal' // created by PayPal webhook
]); ]);