adding permission

This commit is contained in:
Flatlogic Bot 2026-02-27 18:38:01 +00:00
parent e70adf8720
commit 118aae16b0
13 changed files with 811 additions and 198 deletions

26
api/update_theme.php Normal file
View File

@ -0,0 +1,26 @@
<?php
session_start();
require_once __DIR__ . '/../db/config.php';
if (!isset($_SESSION['user_id'])) {
echo json_encode(['success' => false, 'error' => 'Not authenticated']);
exit;
}
$data = json_decode(file_get_contents('php://input'), true);
$theme = $data['theme'] ?? 'light';
// Validate theme
$allowed_themes = ['light', 'dark', 'midnight', 'forest'];
if (!in_array($theme, $allowed_themes)) {
echo json_encode(['success' => false, 'error' => 'Invalid theme']);
exit;
}
try {
$stmt = db()->prepare("UPDATE users SET theme = ? WHERE id = ?");
$stmt->execute([$theme, $_SESSION['user_id']]);
echo json_encode(['success' => true]);
} catch (PDOException $e) {
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
}

View File

@ -0,0 +1,3 @@
-- Add password reset token columns to users table
ALTER TABLE users ADD COLUMN reset_token VARCHAR(100) NULL;
ALTER TABLE users ADD COLUMN reset_token_expiry DATETIME NULL;

View File

@ -0,0 +1,2 @@
-- Migration: Add theme to users
ALTER TABLE users ADD COLUMN theme VARCHAR(20) DEFAULT 'light';

View File

@ -0,0 +1,11 @@
-- Migration: Add granular permissions to users table
ALTER TABLE users
ADD COLUMN can_view TINYINT(1) DEFAULT 1,
ADD COLUMN can_add TINYINT(1) DEFAULT 0,
ADD COLUMN can_edit TINYINT(1) DEFAULT 0,
ADD COLUMN can_delete TINYINT(1) DEFAULT 0;
-- Set defaults for existing roles
UPDATE users SET can_view = 1, can_add = 1, can_edit = 1, can_delete = 1 WHERE role = 'admin';
UPDATE users SET can_view = 1, can_add = 1, can_edit = 1, can_delete = 0 WHERE role = 'clerk';
UPDATE users SET can_view = 1, can_add = 0, can_edit = 0, can_delete = 0 WHERE role = 'staff';

150
forgot_password.php Normal file
View File

@ -0,0 +1,150 @@
<?php
require_once __DIR__ . '/includes/header.php';
require_once __DIR__ . '/mail/MailService.php';
if (isLoggedIn()) {
redirect('index.php');
}
$error = '';
$success = '';
$step = 'request'; // 'request' or 'reset'
// Fetch charity settings for logo/name
$stmt = db()->query("SELECT * FROM charity_settings WHERE id = 1");
$charity = $stmt->fetch();
// Check if we are in reset mode (token in URL)
$token = $_GET['token'] ?? '';
if ($token) {
$stmt = db()->prepare("SELECT * FROM users WHERE reset_token = ? AND reset_token_expiry > NOW()");
$stmt->execute([$token]);
$user = $stmt->fetch();
if ($user) {
$step = 'reset';
} else {
$error = 'رابط استعادة كلمة المرور غير صالح أو منتهي الصلاحية.';
$step = 'request';
}
}
// Handle POST requests
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (isset($_POST['request_reset'])) {
$email = trim($_POST['email'] ?? '');
if (filter_var($email, FILTER_VALIDATE_EMAIL)) {
$stmt = db()->prepare("SELECT id, full_name FROM users WHERE email = ?");
$stmt->execute([$email]);
$user = $stmt->fetch();
if ($user) {
$newToken = bin2hex(random_bytes(32));
$expiry = date('Y-m-d H:i:s', strtotime('+1 hour'));
$update = db()->prepare("UPDATE users SET reset_token = ?, reset_token_expiry = ? WHERE id = ?");
$update->execute([$newToken, $expiry, $user['id']]);
$protocol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 'https' : 'http';
$host = $_SERVER['HTTP_HOST'];
$resetLink = "$protocol://$host/forgot_password.php?token=$newToken";
$subject = "استعادة كلمة المرور - " . ($charity['charity_name'] ?? 'الجمعية الخيرية');
$html = "
<div dir='rtl' style='font-family: Arial, sans-serif; text-align: right;'>
<h3>مرحباً {$user['full_name']}</h3>
<p>لقد طلبت استعادة كلمة المرور الخاصة بك. يرجى الضغط على الرابط أدناه لإعادة تعيينها:</p>
<p><a href='$resetLink' style='background: #000; color: #fff; padding: 10px 20px; text-decoration: none; border-radius: 5px;'>إعادة تعيين كلمة المرور</a></p>
<p>هذا الرابط صالح لمدة ساعة واحدة فقط.</p>
<p>إذا لم تطلب هذا، يرجى تجاهل هذه الرسالة.</p>
</div>
";
$res = MailService::sendMail($email, $subject, $html);
if ($res['success']) {
$success = 'تم إرسال رابط استعادة كلمة المرور إلى بريدك الإلكتروني.';
} else {
$error = 'فشل إرسال البريد الإلكتروني. يرجى المحاولة لاحقاً أو التواصل مع الإدارة.';
}
} else {
// For security, don't reveal if email exists, but here we can be more helpful if it's a closed admin panel
$error = 'البريد الإلكتروني غير مسجل لدينا.';
}
} else {
$error = 'يرجى إدخال بريد إلكتروني صحيح.';
}
} elseif (isset($_POST['reset_password'])) {
$password = $_POST['password'] ?? '';
$confirm = $_POST['confirm_password'] ?? '';
if (strlen($password) < 6) {
$error = 'كلمة المرور يجب أن تكون 6 أحرف على الأقل.';
} elseif ($password !== $confirm) {
$error = 'كلمات المرور غير متطابقة.';
} else {
$hashed = password_hash($password, PASSWORD_DEFAULT);
$update = db()->prepare("UPDATE users SET password = ?, reset_token = NULL, reset_token_expiry = NULL WHERE id = ?");
$update->execute([$hashed, $user['id']]);
$success = 'تم تغيير كلمة المرور بنجاح. يمكنك الآن <a href="login.php">تسجيل الدخول</a>.';
$step = 'completed';
}
}
}
?>
<div class="row justify-content-center align-items-center" style="min-height: 80vh;">
<div class="col-md-4">
<div class="card p-4 shadow-sm border-0 text-center">
<div class="mb-4">
<?php if ($charity['charity_logo']): ?>
<img src="<?= htmlspecialchars($charity['charity_logo']) ?>" alt="Logo" class="mb-3" style="max-height: 80px;">
<?php endif; ?>
<h4 class="fw-bold">استعادة كلمة المرور</h4>
<p class="text-muted small">بريد <?= htmlspecialchars($charity['charity_name'] ?? 'الجمعية الخيرية') ?></p>
</div>
<?php if ($error): ?>
<div class="alert alert-danger"><?= $error ?></div>
<?php endif; ?>
<?php if ($success): ?>
<div class="alert alert-success"><?= $success ?></div>
<?php endif; ?>
<?php if ($step === 'request'): ?>
<form method="POST">
<div class="mb-3 text-start">
<label class="form-label">البريد الإلكتروني</label>
<input type="email" name="email" class="form-control" placeholder="example@domain.com" required>
<div class="form-text">أدخل البريد الإلكتروني المرتبط بحسابك.</div>
</div>
<div class="d-grid mt-4">
<button type="submit" name="request_reset" class="btn btn-dark">إرسال رابط الاستعادة</button>
</div>
<div class="mt-3">
<a href="login.php" class="text-decoration-none small text-secondary">العودة لتسجيل الدخول</a>
</div>
</form>
<?php elseif ($step === 'reset'): ?>
<form method="POST">
<div class="mb-3 text-start">
<label class="form-label">كلمة المرور الجديدة</label>
<input type="password" name="password" class="form-control" required minlength="6">
</div>
<div class="mb-3 text-start">
<label class="form-label">تأكيد كلمة المرور</label>
<input type="password" name="confirm_password" class="form-control" required minlength="6">
</div>
<div class="d-grid mt-4">
<button type="submit" name="reset_password" class="btn btn-primary">تغيير كلمة المرور</button>
</div>
</form>
<?php elseif ($step === 'completed'): ?>
<div class="mt-3">
<a href="login.php" class="btn btn-outline-dark">الذهاب لصفحة الدخول</a>
</div>
<?php endif; ?>
</div>
</div>
</div>
<?php require_once __DIR__ . '/includes/footer.php'; ?>

View File

@ -2,6 +2,11 @@
require_once __DIR__ . '/includes/header.php';
require_once __DIR__ . '/mail/MailService.php';
// Check if user has view permission
if (!canView()) {
redirect('index.php');
}
$error = '';
$success = '';
$user_id = $_SESSION['user_id'];
@ -47,6 +52,11 @@ function sendAssignmentNotification($assigned_to_id, $ref_no, $subject) {
// Handle actions
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$action = $_POST['action'] ?? '';
// Permission checks for POST actions
if (($action === 'add' && !canAdd()) || ($action === 'edit' && !canEdit())) {
$error = 'عذراً، ليس لديك الصلاحية للقيام بهذا الإجراء';
} else {
$type = 'inbound';
$ref_no = $_POST['ref_no'] ?? '';
$date_registered = $_POST['date_registered'] ?? date('Y-m-d');
@ -96,14 +106,19 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$error = 'يرجى ملء الحقول المطلوبة (رقم القيد، الموضوع)';
}
}
}
// Delete action
if (isset($_GET['action']) && $_GET['action'] === 'delete' && isset($_GET['id'])) {
if (!canDelete()) {
$error = 'عذراً، ليس لديك الصلاحية لحذف السجلات';
} else {
$id = $_GET['id'];
$stmt = db()->prepare("DELETE FROM mailbox WHERE id = ? AND type = 'inbound'");
$stmt->execute([$id]);
$success = 'تم حذف البريد بنجاح';
}
}
$search = $_GET['search'] ?? '';
$my_tasks = isset($_GET['my_tasks']) && $_GET['my_tasks'] == 1;
@ -137,10 +152,12 @@ $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()) {
$stmt = db()->prepare("SELECT * FROM mailbox WHERE id = ? AND type = 'inbound'");
$stmt->execute([$_GET['id']]);
$deepLinkData = $stmt->fetch();
}
}
function getStatusBadgeInList($mail) {
$status_name = $mail['status_name'] ?? 'غير معروف';
@ -158,9 +175,11 @@ 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()): ?>
<button type="button" class="btn btn-primary shadow-sm" onclick="openMailModal('add')">
<i class="fas fa-plus-circle me-1"></i> إضافة جديد
</button>
<?php endif; ?>
</div>
<?php if ($success): ?>
@ -243,11 +262,17 @@ function getStatusBadgeInList($mail) {
<td><?= getStatusBadgeInList($mail) ?></td>
<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()): ?>
<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()): ?>
<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>
</tr>
<?php endforeach; else: ?>
@ -261,6 +286,7 @@ function getStatusBadgeInList($mail) {
</div>
</div>
<?php if (canAdd() || canEdit()): ?>
<!-- Mail Modal -->
<div class="modal fade" id="mailModal" tabindex="-1" aria-labelledby="mailModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
@ -433,6 +459,7 @@ function confirmDelete(id) {
})
}
</script>
<?php endif; ?>
<style>
.modal-content {

View File

@ -2,6 +2,45 @@
</div>
</div>
<footer class="footer mt-auto py-3 bg-white border-top">
<div class="container-fluid px-md-4">
<div class="row align-items-center">
<div class="col-md-6 text-center text-md-start mb-2 mb-md-0">
<span class="text-muted small">
&copy; <?= date('Y') ?> <?= htmlspecialchars($charity_name) ?>. جميع الحقوق محفوظة.
</span>
</div>
<div class="col-md-6 text-center text-md-end">
<ul class="list-inline mb-0">
<li class="list-inline-item">
<a href="index.php" class="text-muted text-decoration-none small hover-primary">
<i class="fas fa-home me-1"></i> لوحة التحكم
</a>
</li>
<li class="list-inline-item ms-3">
<a href="profile.php" class="text-muted text-decoration-none small hover-primary">
<i class="fas fa-user-circle me-1"></i> الملف الشخصي
</a>
</li>
<?php if (isAdmin()): ?>
<li class="list-inline-item ms-3">
<a href="charity-settings.php" class="text-muted text-decoration-none small hover-primary">
<i class="fas fa-cog me-1"></i> الإعدادات
</a>
</li>
<?php endif; ?>
</ul>
</div>
</div>
</div>
</footer>
<style>
.hover-primary:hover {
color: #0d6efd !important;
}
</style>
<script>
// Global JS functions if needed
</script>

View File

@ -10,12 +10,30 @@ function isAdmin() {
return isset($_SESSION['user_role']) && $_SESSION['user_role'] === 'admin';
}
function canView() {
return isAdmin() || (isset($_SESSION['can_view']) && $_SESSION['can_view'] == 1);
}
function canAdd() {
return isAdmin() || (isset($_SESSION['can_add']) && $_SESSION['can_add'] == 1);
}
function canEdit() {
return isAdmin() || (isset($_SESSION['can_edit']) && $_SESSION['can_edit'] == 1);
}
function canDelete() {
return isAdmin() || (isset($_SESSION['can_delete']) && $_SESSION['can_delete'] == 1);
}
function redirect($path) {
header("Location: $path");
exit;
}
if (!isLoggedIn() && basename($_SERVER['PHP_SELF']) !== 'login.php') {
// Allowed pages when not logged in
$allowed_pages = ['login.php', 'forgot_password.php'];
if (!isLoggedIn() && !in_array(basename($_SERVER['PHP_SELF']), $allowed_pages)) {
redirect('login.php');
}
@ -29,13 +47,20 @@ $charity_favicon = $charity['charity_favicon'] ?? null;
// Fetch current user info if logged in
$current_user = null;
if (isLoggedIn()) {
$stmt = db()->prepare("SELECT full_name, profile_image FROM users WHERE id = ?");
$stmt = db()->prepare("SELECT full_name, profile_image, theme, can_view, can_add, can_edit, can_delete FROM users WHERE id = ?");
$stmt->execute([$_SESSION['user_id']]);
$current_user = $stmt->fetch();
// Update session permissions
$_SESSION['can_view'] = $current_user['can_view'];
$_SESSION['can_add'] = $current_user['can_add'];
$_SESSION['can_edit'] = $current_user['can_edit'];
$_SESSION['can_delete'] = $current_user['can_delete'];
}
$user_theme = $current_user['theme'] ?? 'light';
?>
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<html lang="ar" dir="rtl" data-theme="<?= htmlspecialchars($user_theme) ?>">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
@ -56,42 +81,136 @@ if (isLoggedIn()) {
<script src="https://cdn.ckeditor.com/ckeditor5/36.0.1/classic/ckeditor.js?v=<?php echo time(); ?>"></script>
<style>
:root {
/* Light Theme (Default) */
--bg-color: #f8f9fa;
--text-color: #212529;
--sidebar-bg: #ffffff;
--card-bg: #ffffff;
--nav-link-color: #333333;
--nav-link-hover-bg: #f0f7ff;
--primary-color: #0d6efd;
--border-color: rgba(0, 0, 0, 0.075);
--muted-text: #6c757d;
--input-bg: #ffffff;
--input-border: #dee2e6;
}
[data-theme="dark"] {
--bg-color: #121212;
--text-color: #e0e0e0;
--sidebar-bg: #1e1e1e;
--card-bg: #1e1e1e;
--nav-link-color: #bbbbbb;
--nav-link-hover-bg: #2c2c2c;
--primary-color: #3788ff;
--border-color: rgba(255, 255, 255, 0.1);
--muted-text: #999999;
--input-bg: #2d2d2d;
--input-border: #444444;
}
[data-theme="midnight"] {
--bg-color: #0b0e14;
--text-color: #cbd5e0;
--sidebar-bg: #1a202c;
--card-bg: #1a202c;
--nav-link-color: #a0aec0;
--nav-link-hover-bg: #2d3748;
--primary-color: #63b3ed;
--border-color: rgba(255, 255, 255, 0.05);
--muted-text: #718096;
--input-bg: #2d3748;
--input-border: #4a5568;
}
[data-theme="forest"] {
--bg-color: #f0f4f0;
--text-color: #2d372d;
--sidebar-bg: #ffffff;
--card-bg: #ffffff;
--nav-link-color: #4a5d4a;
--nav-link-hover-bg: #e8f0e8;
--primary-color: #2d6a4f;
--border-color: rgba(0, 0, 0, 0.05);
--muted-text: #6b8e6b;
--input-bg: #ffffff;
--input-border: #ccd5cc;
}
body {
font-family: 'Cairo', sans-serif;
background-color: #f8f9fa;
background-color: var(--bg-color);
color: var(--text-color);
display: flex;
flex-direction: column;
min-height: 100vh;
transition: background-color 0.3s ease, color 0.3s ease;
}
/* Bootstrap Overrides */
.bg-white { background-color: var(--card-bg) !important; }
.bg-light { background-color: var(--bg-color) !important; }
.text-dark { color: var(--text-color) !important; }
.text-muted { color: var(--muted-text) !important; }
.border-bottom { border-bottom: 1px solid var(--border-color) !important; }
.border-top { border-top: 1px solid var(--border-color) !important; }
.border { border: 1px solid var(--border-color) !important; }
.list-group-item { background-color: var(--card-bg); border-color: var(--border-color); color: var(--text-color); }
.form-control, .form-select { background-color: var(--input-bg); border-color: var(--input-border); color: var(--text-color); }
.form-control:focus, .form-select:focus { background-color: var(--input-bg); color: var(--text-color); border-color: var(--primary-color); }
.table { color: var(--text-color); border-color: var(--border-color); }
.table thead th { background-color: var(--bg-color); color: var(--text-color); }
.table-hover tbody tr:hover { background-color: var(--nav-link-hover-bg); }
.container-fluid.main-container {
flex: 1;
}
.sidebar {
min-height: 100vh;
background: #fff;
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
background: var(--sidebar-bg);
box-shadow: 0 0.125rem 0.25rem var(--border-color);
padding-top: 1rem;
transition: background-color 0.3s ease;
}
.nav-link {
color: #333;
color: var(--nav-link-color);
font-weight: 600;
padding: 0.8rem 1.5rem;
transition: all 0.2s ease;
}
.nav-link:hover, .nav-link.active {
background-color: #f0f7ff;
color: #0d6efd;
border-left: 4px solid #0d6efd;
background-color: var(--nav-link-hover-bg);
color: var(--primary-color);
border-left: 4px solid var(--primary-color);
}
.card {
border: none;
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
box-shadow: 0 0.125rem 0.25rem var(--border-color);
border-radius: 10px;
background-color: var(--card-bg);
color: var(--text-color);
transition: background-color 0.3s ease;
}
.btn-primary {
background-color: #0d6efd;
background-color: var(--primary-color);
border: none;
}
.btn-primary:hover {
background-color: var(--primary-color);
filter: brightness(90%);
}
.status-received { background-color: #e9ecef; color: #495057; }
.status-in_progress { background-color: #cff4fc; color: #055160; }
.status-closed { background-color: #d1e7dd; color: #0f5132; }
/* Modal Header Styling */
.modal-content {
background-color: var(--card-bg);
color: var(--text-color);
}
.modal-header.bg-primary {
background-color: #0d6efd !important;
background-color: var(--primary-color) !important;
}
.user-profile-img {
@ -99,17 +218,55 @@ if (isLoggedIn()) {
height: 80px;
border-radius: 50%;
object-fit: cover;
border: 2px solid #0d6efd;
border: 2px solid var(--primary-color);
}
.charity-logo {
max-width: 100%;
max-height: 60px;
}
.navbar {
background-color: var(--sidebar-bg) !important;
border-color: var(--border-color) !important;
}
.navbar-brand {
color: var(--text-color) !important;
}
/* Theme Switcher Styles */
.theme-switcher {
padding: 1rem 1.5rem;
border-top: 1px solid var(--border-color);
margin-top: 1rem;
}
.theme-options {
display: flex;
gap: 10px;
justify-content: center;
margin-top: 10px;
}
.theme-btn {
width: 24px;
height: 24px;
border-radius: 50%;
border: 2px solid transparent;
cursor: pointer;
transition: transform 0.2s;
}
.theme-btn:hover {
transform: scale(1.2);
}
.theme-btn.active {
border-color: var(--primary-color);
}
.theme-btn-light { background-color: #f8f9fa; border: 1px solid #ddd; }
.theme-btn-dark { background-color: #121212; }
.theme-btn-midnight { background-color: #0b0e14; }
.theme-btn-forest { background-color: #2d6a4f; }
</style>
</head>
<body>
<div class="container-fluid">
<div class="container-fluid main-container">
<div class="row">
<?php if (isLoggedIn()): ?>
<!-- Sidebar -->
@ -178,7 +335,19 @@ if (isLoggedIn()) {
<i class="fas fa-user-circle me-2"></i> الملف الشخصي
</a>
</li>
<li class="nav-item mt-4">
<!-- Theme Switcher -->
<li class="theme-switcher">
<div class="small fw-bold mb-2 text-center">المظهر</div>
<div class="theme-options">
<div class="theme-btn theme-btn-light <?= $user_theme == 'light' ? 'active' : '' ?>" onclick="setTheme('light')" title="فاتح"></div>
<div class="theme-btn theme-btn-dark <?= $user_theme == 'dark' ? 'active' : '' ?>" onclick="setTheme('dark')" title="داكن"></div>
<div class="theme-btn theme-btn-midnight <?= $user_theme == 'midnight' ? 'active' : '' ?>" onclick="setTheme('midnight')" title="منتصف الليل"></div>
<div class="theme-btn theme-btn-forest <?= $user_theme == 'forest' ? 'active' : '' ?>" onclick="setTheme('forest')" title="غابة"></div>
</div>
</li>
<li class="nav-item mt-2">
<a class="nav-link text-danger" href="logout.php">
<i class="fas fa-sign-out-alt me-2"></i> تسجيل الخروج
</a>
@ -186,6 +355,32 @@ if (isLoggedIn()) {
</ul>
</div>
</nav>
<script>
function setTheme(theme) {
document.documentElement.setAttribute('data-theme', theme);
document.querySelectorAll('.theme-btn').forEach(btn => btn.classList.remove('active'));
document.querySelector('.theme-btn-' + theme).classList.add('active');
fetch('api/update_theme.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ theme: theme })
})
.then(response => response.json())
.then(data => {
if (!data.success) console.error('Failed to update theme preference');
});
}
</script>
<?php endif; ?>
<nav class="navbar navbar-expand-md navbar-light bg-white d-md-none border-bottom mb-3"><div class="container-fluid"><span class="navbar-brand">القائمة</span><button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".sidebar" aria-controls="sidebar" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button></div></nav><main class="<?= isLoggedIn() ? 'col-md-9 ms-sm-auto col-lg-10' : 'col-12' ?> px-md-4 py-4">
<nav class="navbar navbar-expand-md navbar-light bg-white d-md-none border-bottom mb-3">
<div class="container-fluid">
<span class="navbar-brand">القائمة</span>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".sidebar" aria-controls="sidebar" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
</div>
</nav>
<main class="<?= isLoggedIn() ? 'col-md-9 ms-sm-auto col-lg-10' : 'col-12' ?> px-md-4 py-4">

View File

@ -25,6 +25,13 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$_SESSION['username'] = $user['username'];
$_SESSION['full_name'] = $user['full_name'];
$_SESSION['user_role'] = $user['role'];
// Set permissions in session immediately
$_SESSION['can_view'] = $user['can_view'] ?? 1;
$_SESSION['can_add'] = $user['can_add'] ?? 0;
$_SESSION['can_edit'] = $user['can_edit'] ?? 0;
$_SESSION['can_delete'] = $user['can_delete'] ?? 0;
redirect('index.php');
} else {
$error = 'اسم المستخدم أو كلمة المرور غير صحيحة';

View File

@ -2,6 +2,11 @@
require_once __DIR__ . '/includes/header.php';
require_once __DIR__ . '/mail/MailService.php';
// Check if user has view permission
if (!canView()) {
redirect('index.php');
}
// Safe truncation helper
if (!function_exists('truncate_text')) {
function truncate_text($text, $limit = 100) {
@ -59,6 +64,11 @@ function sendAssignmentNotification($assigned_to_id, $ref_no, $subject) {
// Handle actions
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$action = $_POST['action'] ?? '';
// Permission checks for POST actions
if (($action === 'add' && !canAdd()) || ($action === 'edit' && !canEdit())) {
$error = 'عذراً، ليس لديك الصلاحية للقيام بهذا الإجراء';
} else {
$type = 'outbound';
$ref_no = $_POST['ref_no'] ?? '';
$date_registered = $_POST['date_registered'] ?? date('Y-m-d');
@ -132,14 +142,19 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$error = 'يرجى ملء الحقول المطلوبة (رقم القيد، الموضوع)';
}
}
}
// Delete action
if (isset($_GET['action']) && $_GET['action'] === 'delete' && isset($_GET['id'])) {
if (!canDelete()) {
$error = 'عذراً، ليس لديك الصلاحية لحذف السجلات';
} else {
$id = $_GET['id'];
$stmt = db()->prepare("DELETE FROM mailbox WHERE id = ? AND type = 'outbound'");
$stmt->execute([$id]);
$success = 'تم حذف البريد بنجاح';
}
}
$search = $_GET['search'] ?? '';
$my_tasks = isset($_GET['my_tasks']) && $_GET['my_tasks'] == 1;
@ -173,10 +188,12 @@ $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()) {
$stmt = db()->prepare("SELECT * FROM mailbox WHERE id = ? AND type = 'outbound'");
$stmt->execute([$_GET['id']]);
$deepLinkData = $stmt->fetch();
}
}
function getStatusBadgeInList($mail) {
$status_name = $mail['status_name'] ?? 'غير معروف';
@ -194,9 +211,11 @@ 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()): ?>
<button type="button" class="btn btn-primary shadow-sm" onclick="openMailModal('add')">
<i class="fas fa-plus-circle me-1"></i> إضافة جديد
</button>
<?php endif; ?>
</div>
<?php if ($success): ?>
@ -279,11 +298,17 @@ function getStatusBadgeInList($mail) {
<td><?= getStatusBadgeInList($mail) ?></td>
<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()): ?>
<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()): ?>
<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>
</tr>
<?php endforeach; else: ?>
@ -297,6 +322,7 @@ function getStatusBadgeInList($mail) {
</div>
</div>
<?php if (canAdd() || canEdit()): ?>
<!-- Mail Modal -->
<div class="modal fade" id="mailModal" tabindex="-1" aria-labelledby="mailModalLabel" aria-hidden="true">
<div class="modal-dialog modal-xl">
@ -504,6 +530,7 @@ function confirmDelete(id) {
})
}
</script>
<?php endif; ?>
<style>
.ck-editor__editable_inline {

View File

@ -1,6 +1,12 @@
<?php
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.
// We'll let them see their profile at least, but maybe not this dashboard.
}
$user_id = $_SESSION['user_id'];
$user_role = $_SESSION['user_role'];
$is_admin = isAdmin();
@ -37,8 +43,8 @@ $recent_query = "SELECT m.*, s.name as status_name, s.color as status_color, u.f
LEFT JOIN mailbox_statuses s ON m.status_id = s.id
LEFT JOIN users u ON m.assigned_to = u.id";
if ($is_clerk) {
// Clerks see all recent activity
if ($is_admin || $is_clerk) {
// Admins and Clerks see all recent activity if they have view permission
$recent_stmt = db()->prepare($recent_query . " ORDER BY m.updated_at DESC LIMIT 10");
$recent_stmt->execute();
} else {
@ -71,10 +77,17 @@ function getStatusBadge($mail) {
<div>
<h2 class="fw-bold mb-1">مرحباً، <?= htmlspecialchars($current_user['full_name'] ?? $_SESSION['username']) ?>!</h2>
<p class="mb-0 opacity-75">
<?php if ($is_clerk): ?>
أنت مسجل كـ <strong>كاتب</strong>. يمكنك متابعة كافة المراسلات وإدارة المهام.
أنت مسجل كـ <strong>
<?php
if ($is_admin) echo 'مدير النظام';
elseif ($is_clerk) echo 'كاتب';
else echo 'موظف';
?>
</strong>.
<?php if ($is_admin || $is_clerk): ?>
يمكنك متابعة كافة المراسلات وإدارة المهام.
<?php else: ?>
أنت مسجل كـ <strong>موظف</strong>. تابع مهامك المسندة إليك هنا.
تابع مهامك المسندة إليك هنا.
<?php endif; ?>
</p>
</div>
@ -121,8 +134,8 @@ function getStatusBadge($mail) {
</div>
</div>
<?php if ($is_clerk): ?>
<!-- Clerk specific stats -->
<?php if ($is_admin || $is_clerk): ?>
<!-- Admin/Clerk specific stats -->
<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">
@ -197,8 +210,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()): ?>
<a href="inbound.php?action=add" class="btn btn-sm btn-outline-primary">إضافة وارد</a>
<a href="outbound.php" class="btn btn-sm btn-outline-success">إضافة صادر</a>
<?php endif; ?>
</div>
</div>
<div class="card-body p-0">
@ -253,7 +268,7 @@ function getStatusBadge($mail) {
<div class="col-lg-4">
<div class="card shadow-sm border-0 mb-4 h-100">
<div class="card-header bg-white py-3 border-bottom">
<h5 class="mb-0 fw-bold"><i class="fas fa-bell me-2 text-warning"></i> <?= $is_clerk ? 'آخر المراسلات' : 'نشاطاتي الأخيرة' ?></h5>
<h5 class="mb-0 fw-bold"><i class="fas fa-bell me-2 text-warning"></i> <?= ($is_admin || $is_clerk) ? 'آخر المراسلات' : 'نشاطاتي الأخيرة' ?></h5>
</div>
<div class="card-body p-0" style="max-height: 500px; overflow-y: auto;">
<div class="list-group list-group-flush">

103
users.php
View File

@ -16,12 +16,18 @@ 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;
if ($action === 'add') {
if ($username && $password && $full_name) {
$hashed_password = password_hash($password, PASSWORD_DEFAULT);
try {
$stmt = db()->prepare("INSERT INTO users (username, password, full_name, role) VALUES (?, ?, ?, ?)");
$stmt->execute([$username, $hashed_password, $full_name, $role]);
$stmt = db()->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]);
$success = 'تم إضافة المستخدم بنجاح';
} catch (PDOException $e) {
if ($e->getCode() == 23000) {
@ -38,11 +44,11 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
try {
if ($password) {
$hashed_password = password_hash($password, PASSWORD_DEFAULT);
$stmt = db()->prepare("UPDATE users SET username = ?, full_name = ?, role = ?, password = ? WHERE id = ?");
$stmt->execute([$username, $full_name, $role, $hashed_password, $id]);
$stmt = db()->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 = ? WHERE id = ?");
$stmt->execute([$username, $full_name, $role, $id]);
$stmt = db()->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]);
}
$success = 'تم تحديث بيانات المستخدم بنجاح';
} catch (PDOException $e) {
@ -77,7 +83,7 @@ if (isset($_GET['action']) && $_GET['action'] === 'edit' && isset($_GET['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>
<h1 class="h2">إدارة المستخدمين والصلاحيات</h1>
<button type="button" class="btn btn-primary shadow-sm" onclick="openUserModal('add')">
<i class="fas fa-user-plus me-1"></i> إضافة مستخدم جديد
</button>
@ -106,6 +112,7 @@ 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>
@ -124,6 +131,14 @@ 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">
<button type="button" class="btn btn-sm btn-outline-primary"
@ -171,12 +186,42 @@ if (isset($_GET['action']) && $_GET['action'] === 'edit' && isset($_GET['id']))
</div>
<div class="mb-3">
<label class="form-label fw-bold">الدور</label>
<select name="role" id="modalRole" class="form-select">
<select name="role" id="modalRole" class="form-select" onchange="applyRolePresets(this.value)">
<option value="staff">موظف</option>
<option value="clerk">كاتب</option>
<option value="admin">مدير</option>
</select>
</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>
</div>
</div>
<div class="modal-footer bg-light">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">إلغاء</button>
@ -190,6 +235,23 @@ if (isset($_GET['action']) && $_GET['action'] === 'edit' && isset($_GET['id']))
<script>
let userModal;
function applyRolePresets(role) {
const view = document.getElementById('permView');
const add = document.getElementById('permAdd');
const edit = document.getElementById('permEdit');
const del = document.getElementById('permDelete');
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;
} else {
view.checked = true;
add.checked = edit.checked = del.checked = false;
}
}
function openUserModal(action, data = null) {
if (!userModal) {
const modalEl = document.getElementById('userModal');
@ -213,6 +275,13 @@ 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') {
@ -220,6 +289,7 @@ function openUserModal(action, data = null) {
modalId.value = '0';
Object.keys(fields).forEach(key => fields[key].value = '');
modalRole.value = 'staff';
applyRolePresets('staff');
modalPassword.required = true;
pwdHint.textContent = '';
} else {
@ -228,6 +298,13 @@ function openUserModal(action, data = null) {
Object.keys(fields).forEach(key => {
if (fields[key]) fields[key].value = data[key] || '';
});
// 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;
modalPassword.required = false;
pwdHint.textContent = '(اتركه فارغاً للحفاظ على كلمة المرور الحالية)';
}
@ -243,7 +320,11 @@ document.addEventListener('DOMContentLoaded', function() {
'id' => $_POST['id'] ?? 0,
'username' => $_POST['username'] ?? '',
'full_name' => $_POST['full_name'] ?? '',
'role' => $_POST['role'] ?? 'staff'
'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'): ?>
@ -283,6 +364,10 @@ function confirmDelete(id) {
.modal-header.bg-primary {
background-color: #0d6efd !important;
}
.form-check-input:checked {
background-color: #198754;
border-color: #198754;
}
</style>
<?php require_once __DIR__ . '/includes/footer.php'; ?>

View File

@ -1,6 +1,11 @@
<?php
require_once __DIR__ . '/includes/header.php';
// Check if user has view permission
if (!canView()) {
redirect('index.php');
}
$id = $_GET['id'] ?? 0;
if (!$id) redirect('index.php');
@ -21,6 +26,9 @@ $error = '';
// Handle Comment submission
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['add_comment'])) {
if (!canEdit()) {
$error = 'عذراً، ليس لديك الصلاحية لإضافة تعليقات';
} else {
$comment = $_POST['comment'] ?? '';
if ($comment) {
$stmt = db()->prepare("INSERT INTO comments (mail_id, user_id, comment) VALUES (?, ?, ?)");
@ -28,9 +36,13 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['add_comment'])) {
$success = 'تم إضافة التعليق بنجاح';
}
}
}
// Handle Attachment upload
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['attachment'])) {
if (!canEdit()) {
$error = 'عذراً، ليس لديك الصلاحية لرفع مرفقات';
} else {
$file = $_FILES['attachment'];
$display_name = $_POST['display_name'] ?? '';
if ($file['error'] === 0) {
@ -49,9 +61,13 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['attachment'])) {
}
}
}
}
// Handle Attachment deletion
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['delete_attachment'])) {
if (!canDelete()) {
$error = 'عذراً، ليس لديك الصلاحية لحذف المرفقات';
} else {
$attachment_id = $_POST['attachment_id'] ?? 0;
if ($attachment_id) {
$stmt = db()->prepare("SELECT * FROM attachments WHERE id = ?");
@ -71,6 +87,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['delete_attachment']))
}
}
}
}
$comments = db()->prepare("SELECT c.*, u.full_name FROM comments c LEFT JOIN users u ON c.user_id = u.id WHERE c.mail_id = ? ORDER BY c.created_at DESC");
$comments->execute([$id]);
@ -91,7 +108,9 @@ function isPreviewable($fileName) {
<h1 class="h2">تفاصيل <?= $mail['type'] == 'inbound' ? 'البريد الوارد' : 'البريد الصادر' ?></h1>
<div class="btn-group">
<a href="<?= $mail['type'] ?>.php" class="btn btn-outline-secondary">عودة للقائمة</a>
<?php if (canEdit()): ?>
<a href="<?= $mail['type'] ?>.php?action=edit&id=<?= $mail['id'] ?>" class="btn btn-outline-primary">تعديل البيانات</a>
<?php endif; ?>
</div>
</div>
@ -205,12 +224,14 @@ function isPreviewable($fileName) {
<h5 class="mb-0 fw-bold">التعليقات والمتابعة</h5>
</div>
<div class="card-body">
<?php if (canEdit()): ?>
<form method="POST" class="mb-4">
<div class="mb-2">
<textarea name="comment" class="form-control" rows="2" placeholder="أضف تعليقاً أو ملاحظة متابعة..." required></textarea>
</div>
<button type="submit" name="add_comment" class="btn btn-sm btn-primary">إرسال تعليق</button>
</form>
<?php endif; ?>
<div class="comment-list">
<?php if ($mail_comments): foreach ($mail_comments as $c): ?>
@ -236,6 +257,7 @@ function isPreviewable($fileName) {
<h5 class="mb-0 fw-bold">المرفقات</h5>
</div>
<div class="card-body">
<?php if (canEdit()): ?>
<form method="POST" enctype="multipart/form-data" class="mb-4">
<div class="mb-2">
<label class="form-label small mb-1">اسم المرفق (يظهر في القائمة)</label>
@ -245,6 +267,7 @@ function isPreviewable($fileName) {
</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): ?>
@ -266,6 +289,8 @@ function isPreviewable($fileName) {
<i class="fas fa-eye"></i>
</button>
<?php endif; ?>
<?php if (canDelete()): ?>
<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">
@ -273,6 +298,7 @@ function isPreviewable($fileName) {
<i class="fas fa-trash"></i>
</button>
</form>
<?php endif; ?>
</div>
</div>
</div>