adding permissions

This commit is contained in:
Flatlogic Bot 2026-02-28 05:28:11 +00:00
parent 607b9d8838
commit 37abbe5d1e
12 changed files with 717 additions and 415 deletions

View File

@ -2,8 +2,8 @@
require_once __DIR__ . '/includes/header.php';
require_once __DIR__ . '/mail/MailService.php';
// Only admins can access this page
if (!isAdmin()) {
// Only users with settings view permission can access this page
if (!canView('settings')) {
redirect("index.php");
}
@ -12,8 +12,12 @@ $error_msg = '';
// Handle Re-enable SMTP
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['enable_smtp'])) {
if (canEdit('settings')) {
db()->query("UPDATE smtp_settings SET is_enabled = 1, consecutive_failures = 0 WHERE id = 1");
$_SESSION['success'] = 'تم إعادة تفعيل SMTP وتصفير عداد الأخطاء';
} else {
$_SESSION['error'] = 'عذراً، ليس لديك الصلاحية لتعديل الإعدادات';
}
redirect('charity-settings.php');
}
@ -27,6 +31,9 @@ $smtp = $stmt->fetch();
// Handle Charity Settings Update
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['update_charity'])) {
if (!canEdit('settings')) {
$_SESSION['error'] = 'عذراً، ليس لديك الصلاحية لتعديل الإعدادات';
} else {
$charity_name = $_POST['charity_name'];
$charity_email = $_POST['charity_email'];
$charity_phone = $_POST['charity_phone'];
@ -56,11 +63,15 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['update_charity'])) {
$stmt = db()->prepare("UPDATE charity_settings SET charity_name = ?, charity_email = ?, charity_phone = ?, charity_address = ?, charity_logo = ?, charity_favicon = ? WHERE id = 1");
$stmt->execute([$charity_name, $charity_email, $charity_phone, $charity_address, $charity_logo, $charity_favicon]);
$_SESSION['success'] = 'تم تحديث إعدادات النظام بنجاح';
}
redirect('charity-settings.php');
}
// Handle SMTP Settings Update
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['update_smtp'])) {
if (!canEdit('settings')) {
$_SESSION['error'] = 'عذراً، ليس لديك الصلاحية لتعديل الإعدادات';
} else {
$stmt = db()->prepare("UPDATE smtp_settings SET smtp_host = ?, smtp_port = ?, smtp_secure = ?, smtp_user = ?, smtp_pass = ?, from_email = ?, from_name = ?, reply_to = ?, max_failures = ? WHERE id = 1");
$stmt->execute([
$_POST['smtp_host'],
@ -74,11 +85,15 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['update_smtp'])) {
(int)$_POST['max_failures']
]);
$_SESSION['success'] = 'تم تحديث إعدادات البريد (SMTP) بنجاح';
}
redirect('charity-settings.php');
}
// Handle Test Email
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['test_email_addr'])) {
if (!canEdit('settings')) {
$_SESSION['error'] = 'عذراً، ليس لديك الصلاحية للقيام بهذا الإجراء';
} else {
$to = $_POST['test_email_addr'];
$res = MailService::sendMail($to, "رسالة تجريبية - Test Email", "<p>إذا كنت ترى هذه الرسالة، فإن إعدادات SMTP تعمل بشكل صحيح.</p>");
if ($res['success']) {
@ -86,11 +101,15 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['test_email_addr'])) {
} else {
$_SESSION['error'] = "فشل إرسال الرسالة التجريبية: " . $res['error'];
}
}
redirect('charity-settings.php');
}
// Handle Status Operations
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['add_status'])) {
if (!canEdit('settings')) {
$_SESSION['error'] = 'عذراً، ليس لديك الصلاحية لتعديل الإعدادات';
} else {
$name = $_POST['status_name'];
$color = $_POST['status_color'];
$is_default = isset($_POST['is_default']) ? 1 : 0;
@ -98,10 +117,14 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['add_status'])) {
$stmt = db()->prepare("INSERT INTO mailbox_statuses (name, color, is_default) VALUES (?, ?, ?)");
$stmt->execute([$name, $color, $is_default]);
$_SESSION['success'] = 'تم إضافة نوع الحالة بنجاح';
}
redirect('charity-settings.php');
}
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['update_status'])) {
if (!canEdit('settings')) {
$_SESSION['error'] = 'عذراً، ليس لديك الصلاحية لتعديل الإعدادات';
} else {
$id = $_POST['status_id'];
$name = $_POST['status_name'];
$color = $_POST['status_color'];
@ -110,10 +133,14 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['update_status'])) {
$stmt = db()->prepare("UPDATE mailbox_statuses SET name = ?, color = ?, is_default = ? WHERE id = ?");
$stmt->execute([$name, $color, $is_default, $id]);
$_SESSION['success'] = 'تم تحديث نوع الحالة بنجاح';
}
redirect('charity-settings.php');
}
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['delete_status'])) {
if (!canDelete('settings')) {
$_SESSION['error'] = 'عذراً، ليس لديك الصلاحية لحذف الإعدادات';
} else {
$id = $_POST['status_id'];
$count = db()->prepare("SELECT COUNT(*) FROM mailbox WHERE status_id = ?");
$count->execute([$id]);
@ -123,6 +150,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['delete_status'])) {
db()->prepare("DELETE FROM mailbox_statuses WHERE id = ?")->execute([$id]);
$_SESSION['success'] = 'تم حذف نوع الحالة بنجاح';
}
}
redirect('charity-settings.php');
}
@ -228,7 +256,9 @@ $post_max = ini_get('post_max_size');
</div>
</div>
<div class="text-end mt-4">
<?php if (canEdit('settings')): ?>
<button type="submit" class="btn btn-dark px-4">حفظ جميع التغييرات</button>
<?php endif; ?>
</div>
</form>
</div>
@ -240,9 +270,11 @@ $post_max = ini_get('post_max_size');
<?php if (!$smtp['is_enabled']): ?>
<div class="alert alert-danger py-2 px-3 mb-0 d-flex align-items-center">
<small><i class="fas fa-exclamation-triangle me-2"></i> SMTP معطل حالياً</small>
<?php if (canEdit('settings')): ?>
<form method="POST" class="ms-3">
<button type="submit" name="enable_smtp" class="btn btn-sm btn-outline-danger">تفعيل الآن</button>
</form>
<?php endif; ?>
</div>
<?php else: ?>
<div class="badge bg-success p-2">
@ -301,7 +333,9 @@ $post_max = ini_get('post_max_size');
<input type="number" name="max_failures" class="form-control" value="<?= htmlspecialchars($smtp['max_failures'] ?? 5) ?>">
</div>
</div>
<?php if (canEdit('settings')): ?>
<button type="submit" class="btn btn-primary">حفظ إعدادات البريد</button>
<?php endif; ?>
</form>
<div class="mt-5 p-4 bg-light rounded border">
@ -310,7 +344,7 @@ $post_max = ini_get('post_max_size');
<form method="POST">
<div class="input-group" style="max-width: 450px;">
<input type="email" name="test_email_addr" class="form-control" placeholder="بريد الوجهة (example@mail.com)" required>
<button class="btn btn-secondary" type="submit"><i class="fas fa-paper-plane me-2"></i> إرسال اختبار</button>
<button class="btn btn-secondary" type="submit" <?= !canEdit('settings') ? 'disabled' : '' ?>><i class="fas fa-paper-plane me-2"></i> إرسال اختبار</button>
</div>
</form>
</div>
@ -320,7 +354,9 @@ $post_max = ini_get('post_max_size');
<div class="tab-pane fade" id="statuses" role="tabpanel">
<div class="d-flex justify-content-between align-items-center mb-4">
<h5 class="fw-bold text-primary mb-0">أنواع حالات البريد</h5>
<?php if (canAdd('settings')): ?>
<button class="btn btn-sm btn-primary" onclick="new bootstrap.Modal(document.getElementById('addStatusModal')).show()"><i class="fas fa-plus me-2"></i> إضافة حالة جديدة</button>
<?php endif; ?>
</div>
<div class="table-responsive">
@ -345,8 +381,10 @@ $post_max = ini_get('post_max_size');
<?php endif; ?>
</td>
<td class="text-end">
<?php if (canEdit('settings')): ?>
<button type="button" class="btn btn-sm btn-outline-primary" onclick="editStatus(<?= $status['id'] ?>, '<?= htmlspecialchars($status['name'], ENT_QUOTES) ?>', '<?= $status['color'] ?>', <?= $status['is_default'] ?>)"><i class="fas fa-edit"></i></button>
<?php if (!$status['is_default']): ?>
<?php endif; ?>
<?php if (canDelete('settings') && !$status['is_default']): ?>
<form method="POST" onsubmit="return confirm('هل أنت متأكد من حذف هذه الحالة؟');" style="display:inline;">
<input type="hidden" name="status_id" value="<?= $status['id'] ?>">
<input type="hidden" name="delete_status" value="1">
@ -429,7 +467,9 @@ $post_max = ini_get('post_max_size');
<h6 class="fw-bold mb-2 text-warning-emphasis"><i class="fas fa-tools me-2"></i> أدوات الصيانة</h6>
<p class="small mb-3">هذه الأدوات مخصصة لمدير النظام فقط. يرجى توخي الحذر عند الاستخدام.</p>
<div class="d-flex gap-2">
<?php if (canEdit('settings')): ?>
<button class="btn btn-sm btn-outline-warning" onclick="alert('قريباً: نسخة احتياطية لقاعدة البيانات')"><i class="fas fa-database me-1"></i> نسخة احتياطية</button>
<?php endif; ?>
<button class="btn btn-sm btn-outline-secondary" onclick="location.reload()"><i class="fas fa-sync-alt me-1"></i> تحديث الحالة</button>
</div>
</div>

View File

@ -0,0 +1,50 @@
-- Migration: Add per-page granular permissions
CREATE TABLE IF NOT EXISTS user_permissions (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
page VARCHAR(50) NOT NULL,
can_view TINYINT(1) DEFAULT 0,
can_add TINYINT(1) DEFAULT 0,
can_edit TINYINT(1) DEFAULT 0,
can_delete TINYINT(1) DEFAULT 0,
UNIQUE KEY user_page (user_id, page),
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);
-- Seed permissions for existing users based on their roles
-- Inbound Mail
INSERT IGNORE INTO user_permissions (user_id, page, can_view, can_add, can_edit, can_delete)
SELECT id, 'inbound', can_view, can_add, can_edit, can_delete FROM users;
-- Outbound Mail
INSERT IGNORE INTO user_permissions (user_id, page, can_view, can_add, can_edit, can_delete)
SELECT id, 'outbound', can_view, can_add, can_edit, can_delete FROM users;
-- Internal Mail
INSERT IGNORE INTO user_permissions (user_id, page, can_view, can_add, can_edit, can_delete)
SELECT id, 'internal', can_view, can_add, can_edit, can_delete FROM users;
-- Users (Only Admins)
INSERT IGNORE INTO user_permissions (user_id, page, can_view, can_add, can_edit, can_delete)
SELECT id, 'users',
IF(role = 'admin', 1, 0),
IF(role = 'admin', 1, 0),
IF(role = 'admin', 1, 0),
IF(role = 'admin', 1, 0)
FROM users;
-- Settings (Only Admins)
INSERT IGNORE INTO user_permissions (user_id, page, can_view, can_add, can_edit, can_delete)
SELECT id, 'settings',
IF(role = 'admin', 1, 0),
IF(role = 'admin', 1, 0),
IF(role = 'admin', 1, 0),
IF(role = 'admin', 1, 0)
FROM users;
-- Reports
INSERT IGNORE INTO user_permissions (user_id, page, can_view, can_add, can_edit, can_delete)
SELECT id, 'reports',
IF(role IN ('admin', 'clerk'), 1, 0),
0, 0, 0
FROM users;

View File

@ -3,7 +3,7 @@ require_once __DIR__ . '/includes/header.php';
require_once __DIR__ . '/mail/MailService.php';
// Check if user has view permission
if (!canView()) {
if (!canView('inbound')) {
redirect('index.php');
}
@ -54,7 +54,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$action = $_POST['action'] ?? '';
// Permission checks for POST actions
if (($action === 'add' && !canAdd()) || ($action === 'edit' && !canEdit())) {
if (($action === 'add' && !canAdd('inbound')) || ($action === 'edit' && !canEdit('inbound'))) {
$error = 'عذراً، ليس لديك الصلاحية للقيام بهذا الإجراء';
} else {
$type = 'inbound';
@ -134,7 +134,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// Delete action
if (isset($_GET['action']) && $_GET['action'] === 'delete' && isset($_GET['id'])) {
if (!canDelete()) {
if (!canDelete('inbound')) {
$error = 'عذراً، ليس لديك الصلاحية لحذف السجلات';
} else {
$id = $_GET['id'];
@ -206,7 +206,7 @@ $users_list = db()->query("SELECT id, full_name FROM users ORDER BY full_name")-
// Handle Deep Link for Edit
$deepLinkData = null;
if (isset($_GET['action']) && $_GET['action'] === 'edit' && isset($_GET['id'])) {
if (canEdit()) {
if (canEdit('inbound')) {
$stmt = db()->prepare("SELECT * FROM mailbox WHERE id = ? AND type = 'inbound'");
$stmt->execute([$_GET['id']]);
$deepLinkData = $stmt->fetch();
@ -229,7 +229,7 @@ function getStatusBadgeInList($mail) {
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 class="h2">البريد الوارد</h1>
<?php if (canAdd()): ?>
<?php if (canAdd('inbound')): ?>
<button type="button" class="btn btn-primary shadow-sm" onclick="openMailModal('add')">
<i class="fas fa-plus-circle me-1"></i> إضافة جديد
</button>
@ -317,14 +317,14 @@ function getStatusBadgeInList($mail) {
<td class="pe-4 text-center">
<a href="view_mail.php?id=<?= $mail['id'] ?>" class="btn btn-sm btn-outline-info" title="عرض التفاصيل"><i class="fas fa-eye"></i></a>
<?php if (canEdit()): ?>
<?php if (canEdit('inbound')): ?>
<button type="button" class="btn btn-sm btn-outline-primary"
onclick='openMailModal("edit", <?= json_encode($mail) ?>)' title="تعديل">
<i class="fas fa-edit"></i>
</button>
<?php endif; ?>
<?php if (canDelete()): ?>
<?php if (canDelete('inbound')): ?>
<a href="javascript:void(0)" onclick="confirmDelete(<?= $mail['id'] ?>)" class="btn btn-sm btn-outline-danger" title="حذف"><i class="fas fa-trash"></i></a>
<?php endif; ?>
</td>
@ -363,7 +363,7 @@ function getStatusBadgeInList($mail) {
<?php endif; ?>
</div>
<?php if (canAdd() || canEdit()): ?>
<?php if (canAdd('inbound') || canEdit('inbound')): ?>
<!-- Mail Modal -->
<div class="modal fade" id="mailModal" tabindex="-1" aria-labelledby="mailModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">

View File

@ -9,7 +9,6 @@ function isLoggedIn() {
}
function isAdmin() {
// Check session first
if (isset($_SESSION['user_role']) && strtolower($_SESSION['user_role']) === 'admin') return true;
if (isset($_SESSION['role']) && strtolower($_SESSION['role']) === 'admin') return true;
return false;
@ -25,29 +24,40 @@ function redirect($path) {
}
// Permission helpers
function canView() {
function canView($page = null) {
if (isAdmin()) return true;
if ($page) {
return $_SESSION['permissions'][$page]['view'] ?? false;
}
return $_SESSION['can_view'] ?? false;
}
function canAdd() {
function canAdd($page = null) {
if (isAdmin()) return true;
if ($page) {
return $_SESSION['permissions'][$page]['add'] ?? false;
}
return $_SESSION['can_add'] ?? false;
}
function canEdit() {
function canEdit($page = null) {
if (isAdmin()) return true;
if ($page) {
return $_SESSION['permissions'][$page]['edit'] ?? false;
}
return $_SESSION['can_edit'] ?? false;
}
function canViewInternal() {
function canDelete($page = null) {
if (isAdmin()) return true;
return canView();
if ($page) {
return $_SESSION['permissions'][$page]['delete'] ?? false;
}
return $_SESSION['can_delete'] ?? false;
}
function canDelete() {
if (isAdmin()) return true;
return $_SESSION['can_delete'] ?? false;
function canViewInternal() {
return canView('internal');
}
// Fetch user info (theme and permissions)
@ -67,6 +77,22 @@ if (isLoggedIn()) {
$_SESSION['name'] = $current_user['full_name'] ?: $current_user['username'];
$_SESSION['user_role'] = strtolower($current_user['role']);
$_SESSION['role'] = strtolower($current_user['role']);
// Load granular permissions
if (!isset($_SESSION['permissions']) || empty($_SESSION['permissions'])) {
$perm_stmt = db()->prepare("SELECT * FROM user_permissions WHERE user_id = ?");
$perm_stmt->execute([$_SESSION['user_id']]);
$perms = $perm_stmt->fetchAll();
$_SESSION['permissions'] = [];
foreach ($perms as $p) {
$_SESSION['permissions'][$p['page']] = [
'view' => (bool)$p['can_view'],
'add' => (bool)$p['can_add'],
'edit' => (bool)$p['can_edit'],
'delete' => (bool)$p['can_delete']
];
}
}
}
}
@ -235,18 +261,27 @@ $charity_info = $charity_stmt->fetch();
</a>
</li>
<?php if (canView('inbound') || canView('outbound')): ?>
<div class="sidebar-heading">البريد الخارجي</div>
<?php endif; ?>
<?php if (canView('inbound')): ?>
<li class="nav-item">
<a class="nav-link <?= basename($_SERVER['PHP_SELF']) == 'inbound.php' ? 'active' : '' ?>" href="inbound.php">
<a class="nav-link <?= (basename($_SERVER['PHP_SELF']) == 'inbound.php' && !isset($_GET['my_tasks'])) ? 'active' : '' ?>" href="inbound.php">
<i class="fas fa-download me-2"></i> البريد الوارد
</a>
</li>
<?php endif; ?>
<?php if (canView('outbound')): ?>
<li class="nav-item">
<a class="nav-link <?= basename($_SERVER['PHP_SELF']) == 'outbound.php' ? 'active' : '' ?>" href="outbound.php">
<i class="fas fa-upload me-2"></i> البريد الصادر
</a>
</li>
<?php endif; ?>
<?php if (canView('internal')): ?>
<div class="sidebar-heading">البريد الداخلي</div>
<li class="nav-item">
<a class="nav-link <?= basename($_SERVER['PHP_SELF']) == 'internal_inbox.php' ? 'active' : '' ?>" href="internal_inbox.php">
@ -258,28 +293,38 @@ $charity_info = $charity_stmt->fetch();
<i class="fas fa-paper-plane me-2"></i> الصادر الداخلي
</a>
</li>
<?php endif; ?>
<div class="sidebar-heading">التقارير</div>
<?php if (isAdmin()): ?>
<?php if (canView('reports')): ?>
<li class="nav-item">
<a class="nav-link <?= basename($_SERVER['PHP_SELF']) == 'overdue_report.php' ? 'active' : '' ?>" href="overdue_report.php">
<i class="fas fa-clock me-2"></i> بريد متأخر
</a>
</li>
<?php endif; ?>
<?php if (canView('inbound')): ?>
<li class="nav-item">
<a class="nav-link <?= (basename($_SERVER['PHP_SELF']) == 'inbound.php' && isset($_GET['my_tasks'])) ? 'active' : '' ?>" href="inbound.php?my_tasks=1">
<i class="fas fa-tasks me-2"></i> مهامي الحالية
</a>
</li>
<?php endif; ?>
<?php if (isAdmin()): ?>
<?php if (canView('users') || canView('settings')): ?>
<div class="sidebar-heading">الإدارة</div>
<?php endif; ?>
<?php if (canView('users')): ?>
<li class="nav-item">
<a class="nav-link <?= basename($_SERVER['PHP_SELF']) == 'users.php' ? 'active' : '' ?>" href="users.php">
<i class="fas fa-users me-2"></i> إدارة المستخدمين
</a>
</li>
<?php endif; ?>
<?php if (canView('settings')): ?>
<li class="nav-item">
<a class="nav-link <?= (basename($_SERVER['PHP_SELF']) == 'charity-settings.php' && !isset($_GET['tab'])) ? 'active' : '' ?>" href="charity-settings.php" onclick="localStorage.setItem('activeSettingsTab', '#general');">
<i class="fas fa-cog me-2"></i> الإعدادات

View File

@ -10,8 +10,8 @@ $user_id = $_SESSION['user_id'];
$is_admin = isAdmin();
// Stats
$total_inbound = db()->query("SELECT COUNT(*) FROM mailbox WHERE type = 'inbound'")->fetchColumn();
$total_outbound = db()->query("SELECT COUNT(*) FROM mailbox WHERE type = 'outbound'")->fetchColumn();
$total_inbound = canView('inbound') ? db()->query("SELECT COUNT(*) FROM mailbox WHERE type = 'inbound'")->fetchColumn() : 0;
$total_outbound = canView('outbound') ? db()->query("SELECT COUNT(*) FROM mailbox WHERE type = 'outbound'")->fetchColumn() : 0;
// Fetch statuses for badge and count
$statuses_data = db()->query("SELECT * FROM mailbox_statuses")->fetchAll(PDO::FETCH_UNIQUE);
@ -26,35 +26,60 @@ foreach ($statuses_data as $id => $s) {
}
$in_progress_count = 0;
if ($in_progress_id) {
$stmt = db()->prepare("SELECT COUNT(*) FROM mailbox WHERE status_id = ?");
$where_types = [];
if (canView('inbound')) $where_types[] = "'inbound'";
if (canView('outbound')) $where_types[] = "'outbound'";
if (!empty($where_types)) {
$types_sql = implode(',', $where_types);
$stmt = db()->prepare("SELECT COUNT(*) FROM mailbox WHERE status_id = ? AND type IN ($types_sql)");
$stmt->execute([$in_progress_id]);
$in_progress_count = $stmt->fetchColumn();
}
}
// My Assignments
$my_assignments = db()->prepare("SELECT m.*, s.name as status_name, s.color as status_color
$my_assignments = [];
$assignment_types = [];
if (canView('inbound')) $assignment_types[] = "'inbound'";
if (canView('outbound')) $assignment_types[] = "'outbound'";
if (canView('internal')) $assignment_types[] = "'internal'";
if (!empty($assignment_types)) {
$types_sql = implode(',', $assignment_types);
$my_assignments = db()->prepare("SELECT m.*, s.name as status_name, s.color as status_color
FROM mailbox m
LEFT JOIN mailbox_statuses s ON m.status_id = s.id
WHERE m.assigned_to = ?
WHERE m.assigned_to = ? AND m.type IN ($types_sql)
ORDER BY m.created_at DESC LIMIT 5");
$my_assignments->execute([$user_id]);
$my_assignments = $my_assignments->fetchAll();
$my_assignments->execute([$user_id]);
$my_assignments = $my_assignments->fetchAll();
}
// Recent Mail (Global for Admin/Clerk, otherwise limited)
$recent_mail_query = "SELECT m.*, s.name as status_name, s.color as status_color, u.full_name as assigned_to_name
$recent_mail = [];
$recent_types = [];
if (canView('inbound')) $recent_types[] = "'inbound'";
if (canView('outbound')) $recent_types[] = "'outbound'";
if (!empty($recent_types)) {
$types_sql = implode(',', $recent_types);
$recent_mail_query = "SELECT m.*, s.name as status_name, s.color as status_color, u.full_name as assigned_to_name
FROM mailbox m
LEFT JOIN mailbox_statuses s ON m.status_id = s.id
LEFT JOIN users u ON m.assigned_to = u.id";
LEFT JOIN users u ON m.assigned_to = u.id
WHERE m.type IN ($types_sql)";
if (!$is_admin && ($_SESSION['user_role'] ?? '') !== 'clerk') {
$recent_mail_query .= " WHERE m.assigned_to = ? OR m.created_by = ?";
if (!$is_admin && ($_SESSION['user_role'] ?? '') !== 'clerk') {
$recent_mail_query .= " AND (m.assigned_to = ? OR m.created_by = ?)";
$recent_stmt = db()->prepare($recent_mail_query . " ORDER BY m.created_at DESC LIMIT 10");
$recent_stmt->execute([$user_id, $user_id]);
} else {
} else {
$recent_stmt = db()->prepare($recent_mail_query . " ORDER BY m.created_at DESC LIMIT 10");
$recent_stmt->execute();
}
$recent_mail = $recent_stmt->fetchAll();
}
$recent_mail = $recent_stmt->fetchAll();
function getStatusBadge($mail) {
$status_name = $mail['status_name'] ?? 'غير معروف';
@ -74,19 +99,26 @@ function getStatusBadge($mail) {
<h1 class="h2">لوحة التحكم الإدارية</h1>
<div class="btn-toolbar mb-2 mb-md-0">
<div class="btn-group me-2">
<?php if (canView('settings')): ?>
<a href="charity-settings.php" class="btn btn-sm btn-outline-dark"><i class="fas fa-cog me-1"></i> الإعدادات</a>
<?php endif; ?>
<?php if (canAdd('inbound')): ?>
<a href="inbound.php?action=add" class="btn btn-sm btn-outline-primary">إضافة بريد وارد</a>
<?php endif; ?>
<?php if (canAdd('outbound')): ?>
<a href="outbound.php" class="btn btn-sm btn-outline-secondary">إضافة بريد صادر</a>
<?php endif; ?>
</div>
</div>
</div>
<!-- Overdue Alert -->
<?php
$overdue_count = db()->query("SELECT COUNT(*) FROM mailbox WHERE due_date < CURDATE() AND status_id IN (SELECT id FROM mailbox_statuses WHERE name != 'closed')")->fetchColumn();
if ($overdue_count > 0):
?>
<div class="row mb-4">
if (canView('reports')):
$overdue_count = db()->query("SELECT COUNT(*) FROM mailbox WHERE due_date < CURDATE() AND status_id IN (SELECT id FROM mailbox_statuses WHERE name != 'closed') AND type != 'internal'")->fetchColumn();
if ($overdue_count > 0):
?>
<div class="row mb-4">
<div class="col-12">
<div class="alert alert-danger shadow-sm border-0 d-flex align-items-center justify-content-between mb-0">
<div>
@ -96,11 +128,15 @@ if ($overdue_count > 0):
<a href="overdue_report.php" class="btn btn-danger btn-sm">عرض التقرير</a>
</div>
</div>
</div>
<?php endif; ?>
</div>
<?php
endif;
endif;
?>
<!-- Stats Cards -->
<div class="row g-4 mb-4">
<?php if (canView('inbound')): ?>
<div class="col-md-3">
<div class="card h-100 p-3 shadow-sm border-0 border-start border-primary border-4">
<div class="d-flex align-items-center justify-content-between">
@ -114,6 +150,9 @@ if ($overdue_count > 0):
</div>
</div>
</div>
<?php endif; ?>
<?php if (canView('outbound')): ?>
<div class="col-md-3">
<div class="card h-100 p-3 shadow-sm border-0 border-start border-success border-4">
<div class="d-flex align-items-center justify-content-between">
@ -127,6 +166,9 @@ if ($overdue_count > 0):
</div>
</div>
</div>
<?php endif; ?>
<?php if (canView('inbound') || canView('outbound')): ?>
<div class="col-md-3">
<div class="card h-100 p-3 shadow-sm border-0 border-start border-info border-4">
<div class="d-flex align-items-center justify-content-between">
@ -140,6 +182,9 @@ if ($overdue_count > 0):
</div>
</div>
</div>
<?php endif; ?>
<?php if (canView('users')): ?>
<div class="col-md-3">
<div class="card h-100 p-3 shadow-sm border-0 border-start border-warning border-4">
<div class="d-flex align-items-center justify-content-between">
@ -153,6 +198,7 @@ if ($overdue_count > 0):
</div>
</div>
</div>
<?php endif; ?>
</div>
<?php if (!empty($my_assignments)): ?>
@ -204,6 +250,7 @@ if ($overdue_count > 0):
</div>
<?php endif; ?>
<?php if (!empty($recent_mail)): ?>
<!-- Recent Mail -->
<div class="card shadow-sm border-0 mb-4">
<div class="card-header bg-white py-3">
@ -228,7 +275,6 @@ if ($overdue_count > 0):
</tr>
</thead>
<tbody>
<?php if ($recent_mail): ?>
<?php foreach ($recent_mail as $mail): ?>
<tr style="cursor: pointer;" onclick="window.location='view_mail.php?id=<?= $mail['id'] ?>'">
<td class="ps-4 fw-bold text-primary"><?= $mail['ref_no'] ?></td>
@ -261,15 +307,11 @@ if ($overdue_count > 0):
<td class="pe-4 text-center"><?= date('Y-m-d', strtotime($mail['date_registered'])) ?></td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr>
<td colspan="8" class="text-center py-4 text-muted">لا يوجد بريد مسجل حالياً</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
<?php endif; ?>
<?php require_once __DIR__ . '/includes/footer.php'; ?>

View File

@ -1,9 +1,9 @@
<?php
require_once __DIR__ . '/includes/header.php';
// Every logged-in user can access their own internal mail
if (!isLoggedIn()) {
redirect('login.php');
// Every logged-in user can access their own internal mail if they have permission
if (!canView('internal')) {
redirect('index.php');
}
$user_id = $_SESSION['user_id'];
@ -61,9 +61,11 @@ function getStatusBadgeInternal($mail) {
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 class="h2"><i class="fas fa-inbox me-2 text-primary"></i> بريد الموظفين - الوارد</h1>
<div class="btn-toolbar mb-2 mb-md-0">
<?php if (canAdd('internal')): ?>
<a href="internal_outbox.php?action=compose" class="btn btn-primary shadow-sm">
<i class="fas fa-paper-plane me-1"></i> إرسال رسالة جديدة
</a>
<?php endif; ?>
</div>
</div>

View File

@ -2,8 +2,8 @@
require_once __DIR__ . '/includes/header.php';
require_once __DIR__ . '/mail/MailService.php';
if (!isLoggedIn()) {
redirect('login.php');
if (!canView('internal')) {
redirect('index.php');
}
$user_id = $_SESSION['user_id'];
@ -12,6 +12,9 @@ $error = '';
// Handle composing a new message
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'compose') {
if (!canAdd('internal')) {
$error = 'عذراً، ليس لديك الصلاحية لإرسال رسائل';
} else {
$recipient_id = $_POST['recipient_id'] ?? null;
$subject = $_POST['subject'] ?? '';
$description = $_POST['description'] ?? '';
@ -50,12 +53,12 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['
$recipient = $stmt_recp->fetch();
if ($recipient && !empty($recipient['email'])) {
$email_subject = "رسالة داخلية جديدة من " . $_SESSION['username'];
$email_subject = "رسالة داخلية جديدة من " . $_SESSION['name'];
$htmlBody = "
<div dir='rtl' style='font-family: Arial, sans-serif;'>
<h3>لديك رسالة داخلية جديدة</h3>
<p><strong>الموضوع:</strong> " . htmlspecialchars($subject) . "</p>
<p><strong>المرسل:</strong> " . htmlspecialchars($_SESSION['username']) . "</p>
<p><strong>المرسل:</strong> " . htmlspecialchars($_SESSION['name']) . "</p>
<hr>
<div>" . $description . "</div>
<br>
@ -75,6 +78,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['
} else {
$error = 'يرجى اختيار المرسل إليه وكتابة الموضوع';
}
}
}
// Get session messages
@ -143,9 +147,11 @@ function getStatusBadgeInternal($mail) {
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 class="h2"><i class="fas fa-paper-plane me-2 text-success"></i> بريد الموظفين - الصادر</h1>
<div class="btn-toolbar mb-2 mb-md-0">
<?php if (canAdd('internal')): ?>
<button type="button" class="btn btn-primary shadow-sm" data-bs-toggle="modal" data-bs-target="#composeModal">
<i class="fas fa-plus-circle me-1"></i> إنشاء رسالة جديدة
</button>
<?php endif; ?>
</div>
</div>
@ -296,6 +302,7 @@ function getStatusBadgeInternal($mail) {
<script>
document.addEventListener('DOMContentLoaded', function() {
if (typeof ClassicEditor !== 'undefined') {
ClassicEditor
.create(document.querySelector('#composeEditor'), {
language: 'ar',
@ -310,6 +317,7 @@ document.addEventListener('DOMContentLoaded', function() {
.catch(error => {
console.error(error);
});
}
<?php if (isset($_GET['action']) && $_GET['action'] === 'compose'): ?>
var myModal = new bootstrap.Modal(document.getElementById('composeModal'));

View File

@ -3,7 +3,7 @@ require_once __DIR__ . '/includes/header.php';
require_once __DIR__ . '/mail/MailService.php';
// Check if user has view permission
if (!canView()) {
if (!canView('outbound')) {
redirect('index.php');
}
@ -66,7 +66,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$action = $_POST['action'] ?? '';
// Permission checks for POST actions
if (($action === 'add' && !canAdd()) || ($action === 'edit' && !canEdit())) {
if (($action === 'add' && !canAdd('outbound')) || ($action === 'edit' && !canEdit('outbound'))) {
$error = 'عذراً، ليس لديك الصلاحية للقيام بهذا الإجراء';
} else {
$type = 'outbound';
@ -147,7 +147,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// Delete action
if (isset($_GET['action']) && $_GET['action'] === 'delete' && isset($_GET['id'])) {
if (!canDelete()) {
if (!canDelete('outbound')) {
$error = 'عذراً، ليس لديك الصلاحية لحذف السجلات';
} else {
$id = $_GET['id'];
@ -219,7 +219,7 @@ $users_list = db()->query("SELECT id, full_name FROM users ORDER BY full_name")-
// Handle Deep Link for Edit
$deepLinkData = null;
if (isset($_GET['action']) && $_GET['action'] === 'edit' && isset($_GET['id'])) {
if (canEdit()) {
if (canEdit('outbound')) {
$stmt = db()->prepare("SELECT * FROM mailbox WHERE id = ? AND type = 'outbound'");
$stmt->execute([$_GET['id']]);
$deepLinkData = $stmt->fetch();
@ -242,7 +242,7 @@ function getStatusBadgeInList($mail) {
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 class="h2">البريد الصادر</h1>
<?php if (canAdd()): ?>
<?php if (canAdd('outbound')): ?>
<button type="button" class="btn btn-primary shadow-sm" onclick="openMailModal('add')">
<i class="fas fa-plus-circle me-1"></i> إضافة جديد
</button>
@ -330,14 +330,14 @@ function getStatusBadgeInList($mail) {
<td class="pe-4 text-center">
<a href="view_mail.php?id=<?= $mail['id'] ?>" class="btn btn-sm btn-outline-info" title="عرض التفاصيل"><i class="fas fa-eye"></i></a>
<?php if (canEdit()): ?>
<?php if (canEdit('outbound')): ?>
<button type="button" class="btn btn-sm btn-outline-primary"
onclick='openMailModal("edit", <?= json_encode($mail) ?>)' title="تعديل">
<i class="fas fa-edit"></i>
</button>
<?php endif; ?>
<?php if (canDelete()): ?>
<?php if (canDelete('outbound')): ?>
<a href="javascript:void(0)" onclick="confirmDelete(<?= $mail['id'] ?>)" class="btn btn-sm btn-outline-danger" title="حذف"><i class="fas fa-trash"></i></a>
<?php endif; ?>
</td>
@ -376,7 +376,7 @@ function getStatusBadgeInList($mail) {
<?php endif; ?>
</div>
<?php if (canAdd() || canEdit()): ?>
<?php if (canAdd('outbound') || canEdit('outbound')): ?>
<!-- Mail Modal -->
<div class="modal fade" id="mailModal" tabindex="-1" aria-labelledby="mailModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">

View File

@ -1,7 +1,7 @@
<?php
require_once 'includes/header.php';
if (!isAdmin()) {
if (!canView('reports')) {
redirect('index.php');
}

View File

@ -2,8 +2,8 @@
require_once __DIR__ . '/includes/header.php';
// Check if user has view permission
if (!canView()) {
// If they can't even view, they shouldn't be here, but header.php already handles basic login.
if (!isLoggedIn()) {
redirect('login.php');
}
$user_id = $_SESSION['user_id'];
@ -16,42 +16,61 @@ $stmt = db()->prepare("SELECT COUNT(*) FROM mailbox WHERE assigned_to = ?");
$stmt->execute([$user_id]);
$my_total_assignments = $stmt->fetchColumn();
$stmt = db()->prepare("SELECT COUNT(*) FROM mailbox WHERE assigned_to = ? AND status != 'closed'");
$stmt = db()->prepare("SELECT COUNT(*) FROM mailbox WHERE assigned_to = ? AND status_id IN (SELECT id FROM mailbox_statuses WHERE name != 'closed')");
$stmt->execute([$user_id]);
$my_pending_tasks = $stmt->fetchColumn();
// Global Stats (for Clerks or if we want to show them)
$total_inbound = db()->query("SELECT COUNT(*) FROM mailbox WHERE type = 'inbound'")->fetchColumn();
$total_outbound = db()->query("SELECT COUNT(*) FROM mailbox WHERE type = 'outbound'")->fetchColumn();
$total_inbound = canView('inbound') ? db()->query("SELECT COUNT(*) FROM mailbox WHERE type = 'inbound'")->fetchColumn() : 0;
$total_outbound = canView('outbound') ? db()->query("SELECT COUNT(*) FROM mailbox WHERE type = 'outbound'")->fetchColumn() : 0;
// Fetch statuses for badge and count
$statuses_data = db()->query("SELECT * FROM mailbox_statuses")->fetchAll(PDO::FETCH_UNIQUE);
// My Assignments
$my_assignments = db()->prepare("SELECT m.*, s.name as status_name, s.color as status_color
$my_assignments = [];
$assignment_types = [];
if (canView('inbound')) $assignment_types[] = "'inbound'";
if (canView('outbound')) $assignment_types[] = "'outbound'";
if (canView('internal')) $assignment_types[] = "'internal'";
if (!empty($assignment_types)) {
$types_sql = implode(',', $assignment_types);
$my_assignments = db()->prepare("SELECT m.*, s.name as status_name, s.color as status_color
FROM mailbox m
LEFT JOIN mailbox_statuses s ON m.status_id = s.id
WHERE m.assigned_to = ?
WHERE m.assigned_to = ? AND m.type IN ($types_sql)
ORDER BY m.created_at DESC LIMIT 10");
$my_assignments->execute([$user_id]);
$my_assignments = $my_assignments->fetchAll();
$my_assignments->execute([$user_id]);
$my_assignments = $my_assignments->fetchAll();
}
// Recent Activity
$recent_query = "SELECT m.*, s.name as status_name, s.color as status_color, u.full_name as assigned_to_name
$recent_activity = [];
$recent_types = [];
if (canView('inbound')) $recent_types[] = "'inbound'";
if (canView('outbound')) $recent_types[] = "'outbound'";
if (canView('internal')) $recent_types[] = "'internal'";
if (!empty($recent_types)) {
$types_sql = implode(',', $recent_types);
$recent_query = "SELECT m.*, s.name as status_name, s.color as status_color, u.full_name as assigned_to_name
FROM mailbox m
LEFT JOIN mailbox_statuses s ON m.status_id = s.id
LEFT JOIN users u ON m.assigned_to = u.id";
LEFT JOIN users u ON m.assigned_to = u.id
WHERE m.type IN ($types_sql)";
if ($is_admin || $is_clerk) {
if ($is_admin || $is_clerk) {
// Admins and Clerks see all recent activity EXCEPT internal mail they are not part of
$recent_stmt = db()->prepare($recent_query . " WHERE m.type != 'internal' OR m.assigned_to = ? OR m.created_by = ? ORDER BY m.updated_at DESC LIMIT 10");
$recent_stmt = db()->prepare($recent_query . " AND (m.type != 'internal' OR m.assigned_to = ? OR m.created_by = ?) ORDER BY m.updated_at DESC LIMIT 10");
$recent_stmt->execute([$user_id, $user_id]);
} else {
} else {
// Staff see only theirs
$recent_stmt = db()->prepare($recent_query . " WHERE m.assigned_to = ? OR m.created_by = ? ORDER BY m.updated_at DESC LIMIT 10");
$recent_stmt = db()->prepare($recent_query . " AND (m.assigned_to = ? OR m.created_by = ?) ORDER BY m.updated_at DESC LIMIT 10");
$recent_stmt->execute([$user_id, $user_id]);
}
$recent_activity = $recent_stmt->fetchAll();
}
$recent_activity = $recent_stmt->fetchAll();
function getStatusBadge($mail) {
$status_name = $mail['status_name'] ?? 'غير معروف';
@ -74,7 +93,7 @@ function getStatusBadge($mail) {
</div>
<div class="d-flex justify-content-between align-items-center position-relative">
<div>
<h2 class="fw-bold mb-1">مرحباً، <?= htmlspecialchars($current_user['full_name'] ?? $_SESSION['username']) ?>!</h2>
<h2 class="fw-bold mb-1">مرحباً، <?= htmlspecialchars($current_user['full_name'] ?? $_SESSION['name']) ?>!</h2>
<p class="mb-0 opacity-75">
أنت مسجل كـ <strong>
<?php
@ -135,6 +154,7 @@ function getStatusBadge($mail) {
<?php if ($is_admin || $is_clerk): ?>
<!-- Admin/Clerk specific stats -->
<?php if (canView('inbound')): ?>
<div class="col-md-3">
<div class="card h-100 p-3 shadow-sm border-0 border-start border-info border-4">
<div class="d-flex align-items-center">
@ -148,6 +168,8 @@ function getStatusBadge($mail) {
</div>
</div>
</div>
<?php endif; ?>
<?php if (canView('outbound')): ?>
<div class="col-md-3">
<div class="card h-100 p-3 shadow-sm border-0 border-start border-success border-4">
<div class="d-flex align-items-center">
@ -161,6 +183,7 @@ function getStatusBadge($mail) {
</div>
</div>
</div>
<?php endif; ?>
<?php else: ?>
<!-- Staff specific stats -->
<div class="col-md-3">
@ -198,6 +221,7 @@ function getStatusBadge($mail) {
</div>
</div>
</div>
</div>
<?php endif; ?>
</div>
@ -208,8 +232,10 @@ function getStatusBadge($mail) {
<div class="card-header bg-white py-3 border-bottom d-flex justify-content-between align-items-center">
<h5 class="mb-0 fw-bold"><i class="fas fa-clipboard-list me-2 text-primary"></i> مهامي المسندة</h5>
<div class="btn-group">
<?php if (canAdd()): ?>
<?php if (canAdd('inbound')): ?>
<a href="inbound.php?action=add" class="btn btn-sm btn-outline-primary">إضافة وارد</a>
<?php endif; ?>
<?php if (canAdd('outbound')): ?>
<a href="outbound.php" class="btn btn-sm btn-outline-success">إضافة صادر</a>
<?php endif; ?>
</div>
@ -227,7 +253,7 @@ function getStatusBadge($mail) {
</tr>
</thead>
<tbody>
<?php if ($my_assignments): ?>
<?php if (!empty($my_assignments)): ?>
<?php foreach ($my_assignments as $mail): ?>
<tr style="cursor: pointer;" onclick="window.location='view_mail.php?id=<?= $mail['id'] ?>'">
<td class="ps-4 fw-bold text-primary"><?= $mail['ref_no'] ?></td>
@ -270,7 +296,7 @@ function getStatusBadge($mail) {
</div>
<div class="card-body p-0" style="max-height: 500px; overflow-y: auto;">
<div class="list-group list-group-flush">
<?php if ($recent_activity): ?>
<?php if (!empty($recent_activity)): ?>
<?php foreach ($recent_activity as $act): ?>
<a href="view_mail.php?id=<?= $act['id'] ?>" class="list-group-item list-group-item-action p-3 border-0 border-bottom">
<div class="d-flex w-100 justify-content-between mb-1">
@ -294,7 +320,9 @@ function getStatusBadge($mail) {
</div>
</div>
<div class="card-footer bg-light text-center py-2">
<?php if (canView('inbound')): ?>
<a href="inbound.php" class="small text-decoration-none">عرض كافة المراسلات <i class="fas fa-chevron-left ms-1"></i></a>
<?php endif; ?>
</div>
</div>
</div>

244
users.php
View File

@ -1,13 +1,22 @@
<?php
require_once __DIR__ . '/includes/header.php';
if (!isAdmin()) {
if (!canView('users')) {
redirect('index.php');
}
$error = '';
$success = '';
$modules = [
'inbound' => 'البريد الوارد',
'outbound' => 'البريد الصادر',
'internal' => 'البريد الداخلي',
'users' => 'إدارة المستخدمين',
'settings' => 'الإعدادات',
'reports' => 'التقارير'
];
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$action = $_POST['action'] ?? '';
$username = $_POST['username'] ?? '';
@ -16,21 +25,39 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$password = $_POST['password'] ?? '';
$id = $_POST['id'] ?? 0;
// Permissions
$can_view = isset($_POST['can_view']) ? 1 : 0;
$can_add = isset($_POST['can_add']) ? 1 : 0;
$can_edit = isset($_POST['can_edit']) ? 1 : 0;
$can_delete = isset($_POST['can_delete']) ? 1 : 0;
// Global permissions (legacy/fallback)
$can_view = isset($_POST['can_view_global']) ? 1 : 0;
$can_add = isset($_POST['can_add_global']) ? 1 : 0;
$can_edit = isset($_POST['can_edit_global']) ? 1 : 0;
$can_delete = isset($_POST['can_delete_global']) ? 1 : 0;
if ($action === 'add') {
if (!canAdd('users')) redirect('users.php');
if ($username && $password && $full_name) {
$hashed_password = password_hash($password, PASSWORD_DEFAULT);
try {
$stmt = db()->prepare("INSERT INTO users (username, password, full_name, role, can_view, can_add, can_edit, can_delete) VALUES (?, ?, ?, ?, ?, ?, ?, ?)");
$pdo = db();
$pdo->beginTransaction();
$stmt = $pdo->prepare("INSERT INTO users (username, password, full_name, role, can_view, can_add, can_edit, can_delete) VALUES (?, ?, ?, ?, ?, ?, ?, ?)");
$stmt->execute([$username, $hashed_password, $full_name, $role, $can_view, $can_add, $can_edit, $can_delete]);
$user_id = $pdo->lastInsertId();
// Save page permissions
$perm_stmt = $pdo->prepare("INSERT INTO user_permissions (user_id, page, can_view, can_add, can_edit, can_delete) VALUES (?, ?, ?, ?, ?, ?)");
foreach ($modules as $module => $label) {
$m_view = isset($_POST["perm_{$module}_view"]) ? 1 : 0;
$m_add = isset($_POST["perm_{$module}_add"]) ? 1 : 0;
$m_edit = isset($_POST["perm_{$module}_edit"]) ? 1 : 0;
$m_delete = isset($_POST["perm_{$module}_delete"]) ? 1 : 0;
$perm_stmt->execute([$user_id, $module, $m_view, $m_add, $m_edit, $m_delete]);
}
$pdo->commit();
$_SESSION['success'] = 'تم إضافة المستخدم بنجاح';
redirect('users.php');
} catch (PDOException $e) {
if (isset($pdo)) $pdo->rollBack();
if ($e->getCode() == 23000) {
$error = 'اسم المستخدم موجود مسبقاً';
} else {
@ -41,19 +68,43 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$error = 'يرجى ملء جميع الحقول المطلوبة';
}
} elseif ($action === 'edit') {
if (!canEdit('users')) redirect('users.php');
if ($username && $full_name && $id) {
try {
$pdo = db();
$pdo->beginTransaction();
if ($password) {
$hashed_password = password_hash($password, PASSWORD_DEFAULT);
$stmt = db()->prepare("UPDATE users SET username = ?, full_name = ?, role = ?, password = ?, can_view = ?, can_add = ?, can_edit = ?, can_delete = ? WHERE id = ?");
$stmt = $pdo->prepare("UPDATE users SET username = ?, full_name = ?, role = ?, password = ?, can_view = ?, can_add = ?, can_edit = ?, can_delete = ? WHERE id = ?");
$stmt->execute([$username, $full_name, $role, $hashed_password, $can_view, $can_add, $can_edit, $can_delete, $id]);
} else {
$stmt = db()->prepare("UPDATE users SET username = ?, full_name = ?, role = ?, can_view = ?, can_add = ?, can_edit = ?, can_delete = ? WHERE id = ?");
$stmt = $pdo->prepare("UPDATE users SET username = ?, full_name = ?, role = ?, can_view = ?, can_add = ?, can_edit = ?, can_delete = ? WHERE id = ?");
$stmt->execute([$username, $full_name, $role, $can_view, $can_add, $can_edit, $can_delete, $id]);
}
// Update page permissions
$pdo->prepare("DELETE FROM user_permissions WHERE user_id = ?")->execute([$id]);
$perm_stmt = $pdo->prepare("INSERT INTO user_permissions (user_id, page, can_view, can_add, can_edit, can_delete) VALUES (?, ?, ?, ?, ?, ?)");
foreach ($modules as $module => $label) {
$m_view = isset($_POST["perm_{$module}_view"]) ? 1 : 0;
$m_add = isset($_POST["perm_{$module}_add"]) ? 1 : 0;
$m_edit = isset($_POST["perm_{$module}_edit"]) ? 1 : 0;
$m_delete = isset($_POST["perm_{$module}_delete"]) ? 1 : 0;
$perm_stmt->execute([$id, $module, $m_view, $m_add, $m_edit, $m_delete]);
}
$pdo->commit();
// Refresh own session if editing self
if ($id == $_SESSION['user_id']) {
unset($_SESSION['permissions']);
}
$_SESSION['success'] = 'تم تحديث بيانات المستخدم بنجاح';
redirect('users.php');
} catch (PDOException $e) {
if (isset($pdo)) $pdo->rollBack();
$error = 'حدث خطأ: ' . $e->getMessage();
}
} else {
@ -63,6 +114,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
}
if (isset($_GET['action']) && $_GET['action'] === 'delete' && isset($_GET['id'])) {
if (!canDelete('users')) redirect('users.php');
if ($_GET['id'] != $_SESSION['user_id']) {
$stmt = db()->prepare("DELETE FROM users WHERE id = ?");
$stmt->execute([$_GET['id']]);
@ -86,20 +138,32 @@ if (isset($_SESSION['error'])) {
$stmt = db()->query("SELECT * FROM users ORDER BY created_at DESC");
$users = $stmt->fetchAll();
// Fetch permissions for all users
$user_perms = [];
$perm_stmt = db()->query("SELECT * FROM user_permissions");
while ($row = $perm_stmt->fetch()) {
$user_perms[$row['user_id']][$row['page']] = $row;
}
// Handle Deep Link for Edit
$deepLinkData = null;
if (isset($_GET['action']) && $_GET['action'] === 'edit' && isset($_GET['id'])) {
$stmt = db()->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$_GET['id']]);
$deepLinkData = $stmt->fetch();
if ($deepLinkData) {
$deepLinkData['page_permissions'] = $user_perms[$deepLinkData['id']] ?? [];
}
}
?>
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 class="h2">إدارة المستخدمين والصلاحيات</h1>
<?php if (canAdd('users')): ?>
<button type="button" class="btn btn-primary shadow-sm" onclick="openUserModal('add')">
<i class="fas fa-user-plus me-1"></i> إضافة مستخدم جديد
</button>
<?php endif; ?>
</div>
<?php if ($success): ?>
@ -125,7 +189,6 @@ if (isset($_GET['action']) && $_GET['action'] === 'edit' && isset($_GET['id']))
<th class="ps-4">الاسم الكامل</th>
<th>اسم المستخدم</th>
<th>الدور</th>
<th>الصلاحيات</th>
<th>تاريخ الإنشاء</th>
<th class="pe-4 text-center">الإجراءات</th>
</tr>
@ -144,21 +207,15 @@ if (isset($_GET['action']) && $_GET['action'] === 'edit' && isset($_GET['id']))
<span class="badge bg-secondary">موظف</span>
<?php endif; ?>
</td>
<td>
<div class="d-flex gap-1">
<span class="badge <?= $user['can_view'] ? 'bg-success' : 'bg-light text-muted' ?>" title="عرض">ع</span>
<span class="badge <?= $user['can_add'] ? 'bg-success' : 'bg-light text-muted' ?>" title="إضافة">إ</span>
<span class="badge <?= $user['can_edit'] ? 'bg-success' : 'bg-light text-muted' ?>" title="تعديل">ت</span>
<span class="badge <?= $user['can_delete'] ? 'bg-success' : 'bg-light text-muted' ?>" title="حذف">ح</span>
</div>
</td>
<td><?= $user['created_at'] ?></td>
<td class="pe-4 text-center">
<?php if (canEdit('users')): ?>
<button type="button" class="btn btn-sm btn-outline-primary"
onclick="openUserModal('edit', <?= htmlspecialchars(json_encode($user), ENT_QUOTES, 'UTF-8') ?>)">
<i class="fas fa-edit"></i> تعديل
onclick="openUserModal('edit', <?= htmlspecialchars(json_encode(array_merge($user, ['page_permissions' => $user_perms[$user['id']] ?? []])), ENT_QUOTES, 'UTF-8') ?>)">
<i class="fas fa-edit"></i> تعديل الصلاحيات
</button>
<?php if ($user['id'] != $_SESSION['user_id']): ?>
<?php endif; ?>
<?php if (canDelete('users') && $user['id'] != $_SESSION['user_id']): ?>
<a href="javascript:void(0)" onclick="confirmDelete(<?= $user['id'] ?>)" class="btn btn-sm btn-outline-danger">
<i class="fas fa-trash"></i> حذف
</a>
@ -174,7 +231,7 @@ if (isset($_GET['action']) && $_GET['action'] === 'edit' && isset($_GET['id']))
<!-- User Modal -->
<div class="modal fade" id="userModal" tabindex="-1" aria-labelledby="userModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-dialog modal-lg">
<div class="modal-content border-0 shadow">
<div class="modal-header bg-primary text-white">
<h5 class="modal-title fw-bold" id="userModalLabel">إضافة مستخدم جديد</h5>
@ -185,19 +242,23 @@ if (isset($_GET['action']) && $_GET['action'] === 'edit' && isset($_GET['id']))
<input type="hidden" name="action" id="modalAction" value="add">
<input type="hidden" name="id" id="modalId" value="0">
<div class="mb-3">
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label fw-bold">الاسم الكامل</label>
<input type="text" name="full_name" id="modalFullName" class="form-control" required>
</div>
<div class="mb-3">
<div class="col-md-6 mb-3">
<label class="form-label fw-bold">اسم المستخدم</label>
<input type="text" name="username" id="modalUsername" class="form-control" required>
</div>
<div class="mb-3">
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label fw-bold">كلمة المرور <span id="pwdHint" class="text-muted small"></span></label>
<input type="password" name="password" id="modalPassword" class="form-control">
</div>
<div class="mb-3">
<div class="col-md-6 mb-3">
<label class="form-label fw-bold">الدور</label>
<select name="role" id="modalRole" class="form-select" onchange="applyRolePresets(this.value)">
<option value="staff">موظف</option>
@ -205,36 +266,50 @@ if (isset($_GET['action']) && $_GET['action'] === 'edit' && isset($_GET['id']))
<option value="admin">مدير</option>
</select>
</div>
</div>
<div class="mb-3">
<label class="form-label fw-bold d-block">الصلاحيات</label>
<div class="row g-2 bg-light p-3 rounded">
<div class="col-6">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="can_view" id="permView" value="1">
<label class="form-check-label" for="permView">عرض البيانات</label>
</div>
</div>
<div class="col-6">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="can_add" id="permAdd" value="1">
<label class="form-check-label" for="permAdd">إضافة سجلات</label>
</div>
</div>
<div class="col-6">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="can_edit" id="permEdit" value="1">
<label class="form-check-label" for="permEdit">تعديل سجلات</label>
</div>
</div>
<div class="col-6">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="can_delete" id="permDelete" value="1">
<label class="form-check-label" for="permDelete">حذف سجلات</label>
</div>
</div>
</div>
<hr>
<h6 class="fw-bold mb-3"><i class="fas fa-lock me-2 text-primary"></i> صلاحيات الوصول لكل صفحة</h6>
<div class="table-responsive">
<table class="table table-bordered table-sm align-middle">
<thead class="bg-light">
<tr class="text-center">
<th class="text-start ps-3">الصفحة / الموديول</th>
<th>عرض</th>
<th>إضافة</th>
<th>تعديل</th>
<th>حذف</th>
</tr>
</thead>
<tbody>
<?php foreach ($modules as $key => $label): ?>
<tr class="text-center">
<td class="text-start ps-3 fw-bold"><?= $label ?></td>
<td>
<input class="form-check-input" type="checkbox" name="perm_<?= $key ?>_view" id="perm_<?= $key ?>_view" value="1">
</td>
<td>
<input class="form-check-input" type="checkbox" name="perm_<?= $key ?>_add" id="perm_<?= $key ?>_add" value="1">
</td>
<td>
<input class="form-check-input" type="checkbox" name="perm_<?= $key ?>_edit" id="perm_<?= $key ?>_edit" value="1">
</td>
<td>
<input class="form-check-input" type="checkbox" name="perm_<?= $key ?>_delete" id="perm_<?= $key ?>_delete" value="1">
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<!-- Hidden legacy fields for backward compatibility if needed -->
<input type="hidden" name="can_view_global" id="permViewGlobal" value="1">
<input type="hidden" name="can_add_global" id="permAddGlobal" value="0">
<input type="hidden" name="can_edit_global" id="permEditGlobal" value="0">
<input type="hidden" name="can_delete_global" id="permDeleteGlobal" value="0">
</div>
<div class="modal-footer bg-light">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">إلغاء</button>
@ -247,22 +322,34 @@ if (isset($_GET['action']) && $_GET['action'] === 'edit' && isset($_GET['id']))
<script>
let userModal;
const modules = <?= json_encode(array_keys($modules)) ?>;
function applyRolePresets(role) {
const view = document.getElementById('permView');
const add = document.getElementById('permAdd');
const edit = document.getElementById('permEdit');
const del = document.getElementById('permDelete');
modules.forEach(m => {
const view = document.getElementById(`perm_${m}_view`);
const add = document.getElementById(`perm_${m}_add`);
const edit = document.getElementById(`perm_${m}_edit`);
const del = document.getElementById(`perm_${m}_delete`);
if (role === 'admin') {
view.checked = add.checked = edit.checked = del.checked = true;
} else if (role === 'clerk') {
view.checked = add.checked = edit.checked = true;
del.checked = false;
if (m === 'users' || m === 'settings') {
view.checked = add.checked = edit.checked = del.checked = false;
} else {
view.checked = true;
add.checked = edit.checked = del.checked = false;
view.checked = add.checked = edit.checked = true;
del.checked = (m === 'reports' ? false : false);
}
} else {
if (m === 'inbound' || m === 'outbound' || m === 'internal') {
view.checked = true;
add.checked = (m === 'internal'); // Staff can send internal mail
edit.checked = del.checked = false;
} else {
view.checked = add.checked = edit.checked = del.checked = false;
}
}
});
}
function openUserModal(action, data = null) {
@ -288,13 +375,6 @@ function openUserModal(action, data = null) {
role: document.getElementById('modalRole')
};
const perms = {
can_view: document.getElementById('permView'),
can_add: document.getElementById('permAdd'),
can_edit: document.getElementById('permEdit'),
can_delete: document.getElementById('permDelete')
};
modalAction.value = action;
if (action === 'add') {
@ -313,10 +393,13 @@ function openUserModal(action, data = null) {
});
// Set permissions checkboxes
perms.can_view.checked = data.can_view == 1;
perms.can_add.checked = data.can_add == 1;
perms.can_edit.checked = data.can_edit == 1;
perms.can_delete.checked = data.can_delete == 1;
modules.forEach(m => {
const p = data.page_permissions && data.page_permissions[m] ? data.page_permissions[m] : {};
document.getElementById(`perm_${m}_view`).checked = p.can_view == 1;
document.getElementById(`perm_${m}_add`).checked = p.can_add == 1;
document.getElementById(`perm_${m}_edit`).checked = p.can_edit == 1;
document.getElementById(`perm_${m}_delete`).checked = p.can_delete == 1;
});
modalPassword.required = false;
pwdHint.textContent = '(اتركه فارغاً للحفاظ على كلمة المرور الحالية)';
@ -328,18 +411,6 @@ function openUserModal(action, data = null) {
document.addEventListener('DOMContentLoaded', function() {
<?php if ($deepLinkData): ?>
openUserModal('edit', <?= json_encode($deepLinkData) ?>);
<?php elseif ($error && isset($_POST['action'])): ?>
const errorData = <?= json_encode([
'id' => $_POST['id'] ?? 0,
'username' => $_POST['username'] ?? '',
'full_name' => $_POST['full_name'] ?? '',
'role' => $_POST['role'] ?? 'staff',
'can_view' => $_POST['can_view'] ?? 0,
'can_add' => $_POST['can_add'] ?? 0,
'can_edit' => $_POST['can_edit'] ?? 0,
'can_delete' => $_POST['can_delete'] ?? 0
]) ?>;
openUserModal('<?= $_POST['action'] ?>', errorData);
<?php elseif (isset($_GET['action']) && $_GET['action'] === 'add'): ?>
openUserModal('add');
<?php endif; ?>
@ -381,6 +452,9 @@ function confirmDelete(id) {
background-color: #198754;
border-color: #198754;
}
.table-sm th, .table-sm td {
padding: 0.5rem;
}
</style>
<?php require_once __DIR__ . '/includes/footer.php'; ?>

View File

@ -2,11 +2,6 @@
require_once __DIR__ . '/includes/header.php';
require_once __DIR__ . '/mail/MailService.php';
// Check if user has view permission
if (!canView()) {
redirect('index.php');
}
$id = $_GET['id'] ?? 0;
if (!$id) redirect('index.php');
@ -22,6 +17,11 @@ $mail = $stmt->fetch();
if (!$mail) redirect('index.php');
// Check if user has view permission for this mail type
if (!canView($mail['type'])) {
redirect('index.php');
}
// Security check for internal mail: only sender or recipient can view
// Even admins should only see their own internal mail for privacy
if ($mail['type'] === 'internal') {
@ -35,7 +35,8 @@ $error = '';
// Handle Comment submission
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['add_comment'])) {
if (!canEdit() && $mail['type'] !== 'internal') {
// For internal mail, users can always comment if involved. For others, check edit permission.
if ($mail['type'] !== 'internal' && !canEdit($mail['type'])) {
$error = 'عذراً، ليس لديك الصلاحية لإضافة تعليقات';
} else {
$comment = $_POST['comment'] ?? '';
@ -52,11 +53,12 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['add_comment'])) {
$referred_user = $stmt_u->fetch();
if ($referred_user && !empty($referred_user['email'])) {
$sender_name = $_SESSION['full_name'] ?? 'زميلك';
$sender_name = $_SESSION['name'] ?? 'زميلك';
$mail_subject = "إحالة بريد: " . $mail['subject'];
$mail_link = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http") . "://$_SERVER[HTTP_HOST]" . dirname($_SERVER['PHP_SELF']) . "/view_mail.php?id=" . $id;
$html = "
<div dir='rtl'>
<h3>مرحباً " . htmlspecialchars($referred_user['full_name']) . "</h3>
<p>قام <strong>" . htmlspecialchars($sender_name) . "</strong> بإحالة بريد إليك مع التعليق التالي:</p>
<blockquote style='background: #f9f9f9; padding: 10px; border-left: 5px solid #ccc;'>
@ -68,6 +70,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['add_comment'])) {
<li><strong>الموضوع:</strong> " . htmlspecialchars($mail['subject']) . "</li>
</ul>
<p><a href='{$mail_link}' style='display: inline-block; padding: 10px 20px; background-color: #007bff; color: white; text-decoration: none; border-radius: 5px;'>عرض البريد</a></p>
</div>
";
$txt = "قام {$sender_name} بإحالة بريد إليك: {$mail['subject']}\n\nالتعليق: {$comment}\n\nعرض البريد: {$mail_link}";
@ -84,7 +87,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['add_comment'])) {
// Handle Attachment upload
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['attachment'])) {
if (!canEdit() && $mail['type'] !== 'internal') {
if ($mail['type'] !== 'internal' && !canEdit($mail['type'])) {
$error = 'عذراً، ليس لديك الصلاحية لرفع مرفقات';
} else {
$file = $_FILES['attachment'];
@ -110,7 +113,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['attachment'])) {
// Handle Attachment deletion
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['delete_attachment'])) {
if (!canDelete() && $mail['type'] !== 'internal') {
if ($mail['type'] !== 'internal' && !canDelete($mail['type'])) {
$error = 'عذراً، ليس لديك الصلاحية لحذف المرفقات';
} else {
$attachment_id = $_POST['attachment_id'] ?? 0;
@ -182,7 +185,7 @@ if ($mail['type'] == 'internal') {
<h1 class="h2">تفاصيل <?= $type_label ?></h1>
<div class="btn-group">
<a href="<?= $back_link ?>" class="btn btn-outline-secondary">عودة للقائمة</a>
<?php if (canEdit() && $mail['type'] !== 'internal'): ?>
<?php if ($mail['type'] !== 'internal' && canEdit($mail['type'])): ?>
<a href="<?= $mail['type'] ?>.php?action=edit&id=<?= $mail['id'] ?>" class="btn btn-outline-primary">تعديل البيانات</a>
<?php endif; ?>
</div>
@ -315,6 +318,7 @@ if ($mail['type'] == 'internal') {
<h5 class="mb-0 fw-bold">الردود والمتابعة</h5>
</div>
<div class="card-body">
<?php if ($mail['type'] === 'internal' || canEdit($mail['type'])): ?>
<form method="POST" class="mb-4 bg-light p-3 rounded border">
<div class="mb-2">
<label class="form-label small fw-bold">إضافة <?= $mail['type'] == 'internal' ? 'رد' : 'تعليق' ?></label>
@ -334,6 +338,7 @@ if ($mail['type'] == 'internal') {
<?php endif; ?>
<button type="submit" name="add_comment" class="btn btn-sm btn-primary">إرسال <?= $mail['type'] == 'internal' ? 'الرد' : 'التعليق' ?></button>
</form>
<?php endif; ?>
<div class="comment-list">
<?php if ($mail_comments): foreach ($mail_comments as $c): ?>
@ -366,6 +371,7 @@ if ($mail['type'] == 'internal') {
<h5 class="mb-0 fw-bold">المرفقات</h5>
</div>
<div class="card-body">
<?php if ($mail['type'] === 'internal' || canEdit($mail['type'])): ?>
<form method="POST" enctype="multipart/form-data" class="mb-4">
<div class="mb-2">
<label class="form-label small mb-1">اسم المرفق (يظهر في القائمة)</label>
@ -375,6 +381,7 @@ if ($mail['type'] == 'internal') {
</div>
<button type="submit" class="btn btn-sm btn-secondary w-100">رفع ملف</button>
</form>
<?php endif; ?>
<div class="list-group list-group-flush">
<?php if ($mail_attachments): foreach ($mail_attachments as $a): ?>
@ -397,7 +404,7 @@ if ($mail['type'] == 'internal') {
</button>
<?php endif; ?>
<?php if (canDelete() || $mail['type'] == 'internal'): ?>
<?php if ($mail['type'] == 'internal' || canDelete($mail['type'])): ?>
<form method="POST" class="d-inline delete-attachment-form">
<input type="hidden" name="attachment_id" value="<?= $a['id'] ?>">
<input type="hidden" name="delete_attachment" value="1">
@ -479,6 +486,7 @@ if ($mail['type'] == 'internal') {
document.querySelectorAll('.delete-btn').forEach(btn => {
btn.addEventListener('click', function() {
const form = this.closest('form');
if (typeof Swal !== 'undefined') {
Swal.fire({
title: 'هل أنت متأكد؟',
text: "سيتم حذف المرفق نهائياً!",
@ -493,6 +501,11 @@ if ($mail['type'] == 'internal') {
form.submit();
}
});
} else {
if (confirm('هل أنت متأكد من الحذف؟')) {
form.submit();
}
}
});
});
</script>