Sindbad~EG File Manager
<?php
require_once '../../config/config.php';
checkLogin();
// Only superusers can access this module
if (!isSuperuser()) {
$_SESSION['error'] = "Access denied. Only superusers can manage modules.";
header('Location: ' . BASE_URL . 'dashboard.php');
exit;
}
$pageTitle = "Module Management - " . APP_NAME;
$db = Database::getInstance()->getConnection();
// Handle AJAX requests for toggling module access
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
header('Content-Type: application/json');
try {
if ($_POST['action'] === 'toggle_access') {
$moduleId = (int)$_POST['module_id'];
$accessLevel = $_POST['access_level'];
$isEnabled = filter_var($_POST['is_enabled'], FILTER_VALIDATE_BOOLEAN);
// Check if module exists
$stmt = $db->prepare("SELECT module_name FROM module_management WHERE id = ?");
$stmt->execute([$moduleId]);
$module = $stmt->fetch();
if (!$module) {
throw new Exception("Module not found");
}
// Prevent disabling superuser access (but allow disabling for other levels)
if ($accessLevel === 'superuser' && !$isEnabled) {
throw new Exception("Cannot disable superuser access");
}
// Update or insert access level
$stmt = $db->prepare("
INSERT INTO module_access_levels (module_id, access_level, is_enabled, enabled_by)
VALUES (?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
is_enabled = VALUES(is_enabled),
enabled_by = VALUES(enabled_by),
updated_at = CURRENT_TIMESTAMP
");
$stmt->execute([$moduleId, $accessLevel, $isEnabled ? 1 : 0, $_SESSION['user_id']]);
// Log the change
$stmt = $db->prepare("
INSERT INTO module_access_audit (module_id, access_level, action, performed_by, reason)
VALUES (?, ?, ?, ?, ?)
");
$action = $isEnabled ? 'enabled' : 'disabled';
$reason = "Module access {$action} for {$accessLevel} level";
$stmt->execute([$moduleId, $accessLevel, $action, $_SESSION['user_id'], $reason]);
echo json_encode([
'success' => true,
'message' => "Module access updated successfully"
]);
exit;
} elseif ($_POST['action'] === 'toggle_module') {
$moduleId = (int)$_POST['module_id'];
$isActive = filter_var($_POST['is_active'], FILTER_VALIDATE_BOOLEAN);
// Check if module is system critical
$stmt = $db->prepare("SELECT is_system_critical, module_name FROM module_management WHERE id = ?");
$stmt->execute([$moduleId]);
$module = $stmt->fetch();
if (!$module) {
throw new Exception("Module not found");
}
if (!empty($module['is_system_critical']) && !$isActive) {
throw new Exception("Cannot deactivate system critical module: " . $module['module_name']);
}
// Toggle module active status
$stmt = $db->prepare("UPDATE module_management SET is_active = ? WHERE id = ?");
$stmt->execute([$isActive, $moduleId]);
echo json_encode([
'success' => true,
'message' => "Module " . ($isActive ? 'activated' : 'deactivated') . " successfully"
]);
exit;
}
} catch (Exception $e) {
http_response_code(400);
echo json_encode([
'success' => false,
'message' => $e->getMessage()
]);
exit;
}
}
// Get all modules with their access levels
$query = "
SELECT
m.*,
GROUP_CONCAT(
CONCAT(mal.access_level, ':', IF(mal.is_enabled, '1', '0'))
ORDER BY FIELD(mal.access_level, 'assembly', 'district', 'area', 'superuser')
SEPARATOR '|'
) as access_config
FROM module_management m
LEFT JOIN module_access_levels mal ON m.id = mal.module_id
GROUP BY m.id
ORDER BY m.category, m.display_order
";
$allModules = $db->query($query)->fetchAll(PDO::FETCH_ASSOC);
// Get statistics
$stats = [
'total_modules' => count($allModules),
'active_modules' => count(array_filter($allModules, fn($m) => !empty($m['is_active']))),
'critical_modules' => count(array_filter($allModules, fn($m) => !empty($m['is_system_critical']))),
'categories' => count(array_unique(array_column($allModules, 'category')))
];
// Get recent access changes
$recentChanges = $db->query("
SELECT
maa.*,
m.module_name,
u.full_name as performed_by_name
FROM module_access_audit maa
JOIN module_management m ON maa.module_id = m.id
JOIN users u ON maa.performed_by = u.id
ORDER BY maa.performed_at DESC
LIMIT 10
")->fetchAll();
include '../../includes/header.php';
?>
<?php include '../../includes/sidebar.php'; ?>
<!-- Main Content -->
<main class="flex-1 md:ml-64 mt-16">
<div class="container mx-auto px-4 py-8">
<!-- Header -->
<div class="mb-8">
<div class="flex items-center justify-between mb-4">
<div>
<h1 class="text-3xl font-bold text-gray-800">
<i class="fas fa-sliders-h text-blue-500 mr-3"></i>Module Management
</h1>
<p class="text-gray-600 mt-2">Control module access for different admin levels</p>
</div>
<div>
<?php echo getUserAccessBadge(); ?>
</div>
</div>
</div>
<!-- Success/Error Messages -->
<?php if (isset($_SESSION['success'])): ?>
<div class="mb-6 bg-green-100 border-l-4 border-green-500 text-green-700 p-4 rounded-lg shadow-md animate-fade-in">
<div class="flex items-center">
<i class="fas fa-check-circle mr-3 text-xl"></i>
<p><?php echo htmlspecialchars($_SESSION['success']); ?></p>
</div>
</div>
<?php unset($_SESSION['success']); ?>
<?php endif; ?>
<?php if (isset($_SESSION['error'])): ?>
<div class="mb-6 bg-red-100 border-l-4 border-red-500 text-red-700 p-4 rounded-lg shadow-md animate-fade-in">
<div class="flex items-center">
<i class="fas fa-exclamation-circle mr-3 text-xl"></i>
<p><?php echo htmlspecialchars($_SESSION['error']); ?></p>
</div>
</div>
<?php unset($_SESSION['error']); ?>
<?php endif; ?>
<!-- Statistics Cards -->
<div class="grid grid-cols-1 md:grid-cols-4 gap-6 mb-8">
<!-- Total Modules -->
<div class="bg-white rounded-xl shadow-lg p-6 border-l-4 border-blue-500">
<div class="flex items-center justify-between">
<div>
<p class="text-gray-500 text-sm font-medium mb-1">Total Modules</p>
<h3 class="text-3xl font-bold text-gray-800"><?php echo $stats['total_modules']; ?></h3>
</div>
<div class="rounded-full p-4" style="background: linear-gradient(135deg, #1E40AF 0%, #9333EA 100%);">
<i class="fas fa-cube text-3xl text-white"></i>
</div>
</div>
</div>
<!-- Active Modules -->
<div class="bg-white rounded-xl shadow-lg p-6 border-l-4 border-green-500">
<div class="flex items-center justify-between">
<div>
<p class="text-gray-500 text-sm font-medium mb-1">Active Modules</p>
<h3 class="text-3xl font-bold text-gray-800"><?php echo $stats['active_modules']; ?></h3>
</div>
<div class="bg-green-100 rounded-full p-4">
<i class="fas fa-check-circle text-3xl text-green-500"></i>
</div>
</div>
</div>
<!-- Critical Modules -->
<div class="bg-white rounded-xl shadow-lg p-6 border-l-4 border-yellow-500">
<div class="flex items-center justify-between">
<div>
<p class="text-gray-500 text-sm font-medium mb-1">Critical Modules</p>
<h3 class="text-3xl font-bold text-gray-800"><?php echo $stats['critical_modules']; ?></h3>
</div>
<div class="bg-yellow-100 rounded-full p-4">
<i class="fas fa-exclamation-triangle text-3xl text-yellow-500"></i>
</div>
</div>
</div>
<!-- Categories -->
<div class="bg-white rounded-xl shadow-lg p-6 border-l-4 border-purple-500">
<div class="flex items-center justify-between">
<div>
<p class="text-gray-500 text-sm font-medium mb-1">Categories</p>
<h3 class="text-3xl font-bold text-gray-800"><?php echo $stats['categories']; ?></h3>
</div>
<div class="bg-purple-100 rounded-full p-4">
<i class="fas fa-tags text-3xl text-purple-500"></i>
</div>
</div>
</div>
</div>
<!-- Access Level Legend -->
<div class="bg-white rounded-xl shadow-lg p-6 mb-8">
<h3 class="text-lg font-bold text-gray-800 mb-4">
<i class="fas fa-info-circle text-blue-500 mr-2"></i>Access Level Guide
</h3>
<div class="grid grid-cols-1 md:grid-cols-4 gap-4">
<div class="flex items-center space-x-3 p-3 bg-yellow-50 rounded-lg">
<i class="fas fa-church text-2xl text-yellow-600"></i>
<div>
<p class="font-semibold text-yellow-800">Assembly Admin</p>
<p class="text-xs text-yellow-600">Single assembly access</p>
</div>
</div>
<div class="flex items-center space-x-3 p-3 bg-green-50 rounded-lg">
<i class="fas fa-map-marked-alt text-2xl text-green-600"></i>
<div>
<p class="font-semibold text-green-800">District Admin</p>
<p class="text-xs text-green-600">All assemblies in district</p>
</div>
</div>
<div class="flex items-center space-x-3 p-3 bg-blue-50 rounded-lg">
<i class="fas fa-map text-2xl text-blue-600"></i>
<div>
<p class="font-semibold text-blue-800">Area Admin</p>
<p class="text-xs text-blue-600">All districts in area</p>
</div>
</div>
<div class="flex items-center space-x-3 p-3 bg-purple-50 rounded-lg">
<i class="fas fa-crown text-2xl text-purple-600"></i>
<div>
<p class="font-semibold text-purple-800">Superuser</p>
<p class="text-xs text-purple-600">Full system access</p>
</div>
</div>
</div>
</div>
<!-- Modules Management Table -->
<?php
$categories = [];
// Parse access configuration and group by category
foreach ($allModules as $key => $mod) {
// Set defaults for columns that might not exist
$mod['is_system_critical'] = $mod['is_system_critical'] ?? false;
$mod['category'] = $mod['category'] ?? 'General';
$mod['module_description'] = $mod['module_description'] ?? '';
// Parse access levels from access_config
$mod['access_levels'] = [];
if (!empty($mod['access_config'])) {
$configs = explode('|', $mod['access_config']);
foreach ($configs as $config) {
if (strpos($config, ':') !== false) {
list($level, $enabled) = explode(':', $config);
$mod['access_levels'][$level] = ($enabled === '1');
}
}
}
// Set defaults for missing levels
foreach (['assembly', 'district', 'area', 'superuser'] as $level) {
if (!isset($mod['access_levels'][$level])) {
$mod['access_levels'][$level] = true;
}
}
$categories[$mod['category']][] = $mod;
}
?>
<?php foreach ($categories as $category => $categoryModules): ?>
<div class="bg-white rounded-xl shadow-lg mb-6">
<div class="p-6 border-b border-gray-200">
<h3 class="text-xl font-bold text-gray-800">
<i class="fas fa-folder-open text-blue-500 mr-2"></i>
<?php echo htmlspecialchars($category); ?>
<span class="text-sm font-normal text-gray-500 ml-2">(<?php echo count($categoryModules); ?> modules)</span>
</h3>
</div>
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Module</th>
<th class="px-6 py-3 text-center text-xs font-medium text-gray-500 uppercase tracking-wider">Assembly</th>
<th class="px-6 py-3 text-center text-xs font-medium text-gray-500 uppercase tracking-wider">District</th>
<th class="px-6 py-3 text-center text-xs font-medium text-gray-500 uppercase tracking-wider">Area</th>
<th class="px-6 py-3 text-center text-xs font-medium text-gray-500 uppercase tracking-wider">Superuser</th>
<th class="px-6 py-3 text-center text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th>
<th class="px-6 py-3 text-center text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
<?php foreach ($categoryModules as $module):
// Check if module is disabled (inactive)
$isDisabled = empty($module['is_active']);
$rowClass = $isDisabled ? 'bg-gray-100 opacity-60' : 'hover:bg-gray-50';
?>
<tr class="<?php echo $rowClass; ?> transition">
<td class="px-6 py-4">
<div class="flex items-center">
<div class="flex-shrink-0 w-10 h-10 rounded-full <?php echo $isDisabled ? 'bg-gray-400' : 'bg-gradient-to-br from-blue-500 to-purple-500'; ?> flex items-center justify-center">
<i class="fas fa-<?php echo htmlspecialchars($module['module_icon']); ?> text-white"></i>
</div>
<div class="ml-4">
<div class="text-sm font-semibold <?php echo $isDisabled ? 'text-gray-500' : 'text-gray-900'; ?>">
<?php echo htmlspecialchars($module['module_name']); ?>
<?php if ($isDisabled): ?>
<span class="ml-2 px-2 py-1 text-xs font-semibold rounded-full bg-gray-300 text-gray-700">
<i class="fas fa-ban"></i> Disabled
</span>
<?php endif; ?>
<?php if (!empty($module['is_system_critical'])): ?>
<span class="ml-2 px-2 py-1 text-xs font-semibold rounded-full bg-red-100 text-red-800">
<i class="fas fa-lock"></i> Critical
</span>
<?php endif; ?>
</div>
<div class="text-xs text-gray-500"><?php echo htmlspecialchars($module['module_description'] ?? 'No description'); ?></div>
</div>
</div>
</td>
<!-- Access Level Toggles -->
<?php foreach (['assembly', 'district', 'area', 'superuser'] as $level):
// Only lock the superuser column (superusers can now disable critical modules for other levels)
$isDisabledToggle = ($level === 'superuser');
?>
<td class="px-6 py-4 text-center">
<label class="relative inline-flex items-center <?php echo $isDisabledToggle ? 'cursor-not-allowed opacity-50' : 'cursor-pointer'; ?>">
<input
type="checkbox"
class="sr-only peer access-toggle"
data-module-id="<?php echo $module['id']; ?>"
data-access-level="<?php echo $level; ?>"
<?php echo !empty($module['access_levels'][$level]) ? 'checked' : ''; ?>
<?php echo $isDisabledToggle ? 'disabled' : ''; ?>
>
<div class="toggle-switch" style="width: 44px; height: 24px; border-radius: 9999px; position: relative; transition: background-color 0.2s; background-color: <?php echo !empty($module['access_levels'][$level]) ? '#2563EB' : '#E5E7EB'; ?>; <?php echo $isDisabledToggle ? 'pointer-events: none;' : ''; ?>">
<div class="toggle-slider" style="position: absolute; top: 2px; left: 2px; background-color: white; border: 1px solid #D1D5DB; border-radius: 9999px; height: 20px; width: 20px; transition: transform 0.2s; transform: <?php echo !empty($module['access_levels'][$level]) ? 'translateX(20px)' : 'translateX(0)'; ?>;"></div>
</div>
</label>
</td>
<?php endforeach; ?>
<!-- Module Status -->
<td class="px-6 py-4 text-center">
<?php if (!empty($module['is_active'])): ?>
<span class="px-3 py-1 text-xs font-semibold rounded-full bg-green-100 text-green-800">
<i class="fas fa-check-circle"></i> Active
</span>
<?php else: ?>
<span class="px-3 py-1 text-xs font-semibold rounded-full bg-gray-100 text-gray-800">
<i class="fas fa-times-circle"></i> Inactive
</span>
<?php endif; ?>
</td>
<!-- Actions -->
<td class="px-6 py-4 text-center">
<button
class="toggle-module-btn text-<?php echo $module['is_active'] ? 'red' : 'green'; ?>-600 hover:text-<?php echo $module['is_active'] ? 'red' : 'green'; ?>-900"
data-module-id="<?php echo $module['id']; ?>"
data-is-active="<?php echo $module['is_active'] ? '0' : '1'; ?>"
data-module-name="<?php echo htmlspecialchars($module['module_name']); ?>"
<?php echo !empty($module['is_system_critical']) ? 'disabled title="Cannot disable critical modules"' : ''; ?>
>
<i class="fas fa-power-off text-lg"></i>
</button>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
<?php endforeach; ?>
<!-- Recent Changes Log -->
<?php if (!empty($recentChanges)): ?>
<div class="bg-white rounded-xl shadow-lg p-6">
<h3 class="text-xl font-bold text-gray-800 mb-4">
<i class="fas fa-history text-blue-500 mr-2"></i>Recent Access Changes
</h3>
<div class="space-y-3">
<?php foreach ($recentChanges as $change): ?>
<div class="flex items-center justify-between p-3 bg-gray-50 rounded-lg">
<div class="flex items-center space-x-3">
<i class="fas fa-<?php echo $change['action'] === 'enabled' ? 'check' : 'times'; ?>-circle text-<?php echo $change['action'] === 'enabled' ? 'green' : 'red'; ?>-500"></i>
<div>
<p class="text-sm font-semibold text-gray-800">
<?php echo htmlspecialchars($change['module_name']); ?> -
<span class="text-<?php echo $change['action'] === 'enabled' ? 'green' : 'red'; ?>-600">
<?php echo ucfirst($change['access_level']); ?> <?php echo ucfirst($change['action']); ?>
</span>
</p>
<p class="text-xs text-gray-500">
By <?php echo htmlspecialchars($change['performed_by_name']); ?> •
<?php echo timeAgo($change['performed_at']); ?>
</p>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
</div>
</main>
<!-- JavaScript -->
<script>
document.addEventListener('DOMContentLoaded', function() {
// Handle access level toggle
document.querySelectorAll('.access-toggle').forEach(toggle => {
toggle.addEventListener('change', function() {
// Prevent action if checkbox is disabled
if (this.disabled) {
console.log('Toggle is disabled, ignoring');
return;
}
const moduleId = this.dataset.moduleId;
const accessLevel = this.dataset.accessLevel;
const isEnabled = this.checked;
// Get the toggle elements for visual update
const toggleSwitch = this.nextElementSibling;
const toggleSlider = toggleSwitch.querySelector('.toggle-slider');
// Update visual state immediately (using inline styles)
if (isEnabled) {
toggleSwitch.style.backgroundColor = '#2563EB';
toggleSlider.style.transform = 'translateX(20px)';
} else {
toggleSwitch.style.backgroundColor = '#E5E7EB';
toggleSlider.style.transform = 'translateX(0)';
}
// Send AJAX request
fetch(window.location.href, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
action: 'toggle_access',
module_id: moduleId,
access_level: accessLevel,
is_enabled: isEnabled ? '1' : '0'
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
showNotification('success', data.message);
} else {
// Revert toggle on error - both checkbox and visual
this.checked = !isEnabled;
if (!isEnabled) {
toggleSwitch.style.backgroundColor = '#2563EB';
toggleSlider.style.transform = 'translateX(20px)';
} else {
toggleSwitch.style.backgroundColor = '#E5E7EB';
toggleSlider.style.transform = 'translateX(0)';
}
showNotification('error', data.message || 'Failed to update');
}
})
.catch(error => {
console.error('Toggle error:', error);
// Revert toggle on error - both checkbox and visual
this.checked = !isEnabled;
if (!isEnabled) {
toggleSwitch.style.backgroundColor = '#2563EB';
toggleSlider.style.transform = 'translateX(20px)';
} else {
toggleSwitch.style.backgroundColor = '#E5E7EB';
toggleSlider.style.transform = 'translateX(0)';
}
showNotification('error', 'An error occurred. Please try again.');
});
});
});
// Handle module activate/deactivate
document.querySelectorAll('.toggle-module-btn').forEach(btn => {
btn.addEventListener('click', function() {
const moduleId = this.dataset.moduleId;
const isActive = this.dataset.isActive === '1';
const moduleName = this.dataset.moduleName;
if (confirm(`Are you sure you want to ${isActive ? 'activate' : 'deactivate'} ${moduleName}?`)) {
fetch('', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
action: 'toggle_module',
module_id: moduleId,
is_active: isActive ? '1' : '0'
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
showNotification('success', data.message);
setTimeout(() => location.reload(), 1500);
} else {
showNotification('error', data.message);
}
})
.catch(error => {
showNotification('error', 'An error occurred. Please try again.');
});
}
});
});
function showNotification(type, message) {
const color = type === 'success' ? 'green' : 'red';
const icon = type === 'success' ? 'check-circle' : 'exclamation-circle';
const notification = document.createElement('div');
notification.className = `fixed top-20 right-4 bg-${color}-100 border-l-4 border-${color}-500 text-${color}-700 p-4 rounded-lg shadow-lg animate-fade-in z-50`;
notification.innerHTML = `
<div class="flex items-center">
<i class="fas fa-${icon} mr-3 text-xl"></i>
<p>${message}</p>
</div>
`;
document.body.appendChild(notification);
setTimeout(() => {
notification.remove();
}, 3000);
}
});
</script>
<?php include '../../includes/footer.php'; ?>
Sindbad File Manager Version 1.0, Coded By Sindbad EG ~ The Terrorists