371 lines
19 KiB
PHP
371 lines
19 KiB
PHP
<?php
|
|
require_once __DIR__ . "/../includes/functions.php";
|
|
require_permission("users_view");
|
|
require_once __DIR__ . '/../db/config.php';
|
|
$pdo = db();
|
|
|
|
$message = '';
|
|
|
|
// Handle Add/Edit User
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
|
|
$action = $_POST['action'];
|
|
$username = trim($_POST['username']);
|
|
$full_name = trim($_POST['full_name']);
|
|
$full_name_ar = trim($_POST['full_name_ar'] ?? '');
|
|
$email = trim($_POST['email']);
|
|
$group_id = (int)$_POST['group_id'];
|
|
$is_active = isset($_POST['is_active']) ? 1 : 0;
|
|
$is_ratable = isset($_POST['is_ratable']) ? 1 : 0;
|
|
$id = isset($_POST['id']) ? (int)$_POST['id'] : null;
|
|
|
|
$profile_pic = null;
|
|
if ($id) {
|
|
$stmt = $pdo->prepare("SELECT profile_pic FROM users WHERE id = ?");
|
|
$stmt->execute([$id]);
|
|
$profile_pic = $stmt->fetchColumn();
|
|
}
|
|
|
|
if (isset($_FILES['profile_pic']) && $_FILES['profile_pic']['error'] === UPLOAD_ERR_OK) {
|
|
$uploadDir = __DIR__ . '/../assets/images/users/';
|
|
if (!is_dir($uploadDir)) mkdir($uploadDir, 0755, true);
|
|
|
|
$file_ext = strtolower(pathinfo($_FILES['profile_pic']['name'], PATHINFO_EXTENSION));
|
|
if (in_array($file_ext, ['jpg', 'jpeg', 'png', 'gif', 'webp'])) {
|
|
$new_file_name = 'user_' . ($id ?: uniqid()) . '_' . uniqid() . '.' . $file_ext;
|
|
if (move_uploaded_file($_FILES['profile_pic']['tmp_name'], $uploadDir . $new_file_name)) {
|
|
$profile_pic = 'assets/images/users/' . $new_file_name;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (empty($username)) {
|
|
$message = '<div class="alert alert-danger">Username is required.</div>';
|
|
} else {
|
|
try {
|
|
if ($action === 'edit_user' && $id) {
|
|
if (!has_permission('users_edit') && !has_permission('users_add')) {
|
|
$message = '<div class="alert alert-danger">Access Denied: You do not have permission to edit users.</div>';
|
|
} else {
|
|
$sql = "UPDATE users SET username = ?, full_name = ?, full_name_ar = ?, email = ?, group_id = ?, is_active = ?, is_ratable = ?, profile_pic = ? WHERE id = ?";
|
|
$params = [$username, $full_name, $full_name_ar, $email, $group_id, $is_active, $is_ratable, $profile_pic, $id];
|
|
|
|
if (!empty($_POST['password'])) {
|
|
$password = password_hash($_POST['password'], PASSWORD_DEFAULT);
|
|
$sql = "UPDATE users SET username = ?, full_name = ?, full_name_ar = ?, email = ?, group_id = ?, is_active = ?, is_ratable = ?, profile_pic = ?, password = ? WHERE id = ?";
|
|
$params = [$username, $full_name, $full_name_ar, $email, $group_id, $is_active, $is_ratable, $profile_pic, $password, $id];
|
|
}
|
|
|
|
$stmt = $pdo->prepare($sql);
|
|
$stmt->execute($params);
|
|
$message = '<div class="alert alert-success">User updated successfully!</div>';
|
|
}
|
|
} elseif ($action === 'add_user') {
|
|
if (!has_permission('users_add')) {
|
|
$message = '<div class="alert alert-danger">Access Denied: You do not have permission to add users.</div>';
|
|
} else {
|
|
$password = password_hash($_POST['password'] ?: '123456', PASSWORD_DEFAULT);
|
|
$stmt = $pdo->prepare("INSERT INTO users (username, password, full_name, full_name_ar, email, group_id, is_active, is_ratable, profile_pic) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)");
|
|
$stmt->execute([$username, $password, $full_name, $full_name_ar, $email, $group_id, $is_active, $is_ratable, $profile_pic]);
|
|
$message = '<div class="alert alert-success">User created successfully!</div>';
|
|
}
|
|
}
|
|
} catch (PDOException $e) {
|
|
if ($e->getCode() == 23000) {
|
|
$message = '<div class="alert alert-danger">Username or Email already exists.</div>';
|
|
} else {
|
|
$message = '<div class="alert alert-danger">Database error: ' . $e->getMessage() . '</div>';
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Handle Delete
|
|
if (isset($_GET['delete'])) {
|
|
if (!has_permission('users_del')) {
|
|
$message = '<div class="alert alert-danger">Access Denied: You do not have permission to delete users.</div>';
|
|
} else {
|
|
$id = $_GET['delete'];
|
|
// Don't allow deleting current user
|
|
if ($id == $_SESSION['user']['id']) {
|
|
$message = '<div class="alert alert-danger text-center">You cannot delete your own account.</div>';
|
|
} else {
|
|
$pdo->prepare("DELETE FROM users WHERE id = ?")->execute([$id]);
|
|
header("Location: users.php");
|
|
exit;
|
|
}
|
|
}
|
|
}
|
|
|
|
$groups = $pdo->query("SELECT * FROM user_groups ORDER BY name ASC")->fetchAll();
|
|
|
|
$query = "SELECT u.*, g.name as group_name
|
|
FROM users u
|
|
LEFT JOIN user_groups g ON u.group_id = g.id
|
|
ORDER BY u.id DESC";
|
|
$users_pagination = paginate_query($pdo, $query);
|
|
$users = $users_pagination['data'];
|
|
|
|
include 'includes/header.php';
|
|
?>
|
|
|
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
<div>
|
|
<h2 class="fw-bold mb-1">User Management</h2>
|
|
<p class="text-muted mb-0">Manage staff accounts, roles and access permissions</p>
|
|
</div>
|
|
<?php if (has_permission('users_add')): ?>
|
|
<button class="btn btn-primary btn-lg shadow-sm" data-bs-toggle="modal" data-bs-target="#userModal" onclick="prepareAddForm()" style="border-radius: 12px;">
|
|
<i class="bi bi-plus-lg"></i> Add New User
|
|
</button>
|
|
<?php endif; ?>
|
|
</div>
|
|
|
|
<?= $message ?>
|
|
|
|
<div class="card border-0 shadow-sm rounded-4 overflow-hidden">
|
|
<div class="card-body p-0">
|
|
<!-- Pagination Controls -->
|
|
<div class="p-3 border-bottom bg-light">
|
|
<?php render_pagination_controls($users_pagination); ?>
|
|
</div>
|
|
<div class="table-responsive">
|
|
<table class="table table-hover align-middle mb-0">
|
|
<thead class="bg-light">
|
|
<tr>
|
|
<th class="ps-4">User</th>
|
|
<th>Arabic Name</th>
|
|
<th>Email</th>
|
|
<th>Role / Group</th>
|
|
<th>Status</th>
|
|
<th>Ratable</th>
|
|
<th class="text-end pe-4">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach ($users as $user): ?>
|
|
<tr>
|
|
<td class="ps-4">
|
|
<div class="d-flex align-items-center">
|
|
<?php if ($user['profile_pic']): ?>
|
|
<img src="../<?= htmlspecialchars($user['profile_pic']) ?>" class="rounded-circle me-3 border border-2 border-white shadow-sm" width="48" height="48" style="object-fit: cover;">
|
|
<?php else: ?>
|
|
<div class="bg-primary bg-gradient rounded-circle d-flex align-items-center justify-content-center text-white me-3 shadow-sm" style="width:48px;height:48px; font-weight:700; font-size: 1.2rem;"><?= strtoupper(substr($user['username'],0,1)) ?></div>
|
|
<?php endif; ?>
|
|
<div>
|
|
<div class="fw-bold text-dark fs-6"><?= htmlspecialchars($user['full_name'] ?: $user['username']) ?></div>
|
|
<small class="text-muted fw-medium">@<?= htmlspecialchars($user['username']) ?></small>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td><?= htmlspecialchars($user['full_name_ar'] ?: '-') ?></td>
|
|
<td><?= htmlspecialchars($user['email'] ?: '-') ?></td>
|
|
<td><span class="badge bg-light text-dark border px-2 py-1"><?= htmlspecialchars($user['group_name'] ?: 'None') ?></span></td>
|
|
<td>
|
|
<?php if ($user['is_active']): ?>
|
|
<span class="badge bg-success-subtle text-success px-3 py-1 rounded-pill">Active</span>
|
|
<?php else: ?>
|
|
<span class="badge bg-danger-subtle text-danger px-3 py-1 rounded-pill">Inactive</span>
|
|
<?php endif; ?>
|
|
</td>
|
|
<td>
|
|
<?php if ($user['is_ratable']): ?>
|
|
<span class="badge bg-info-subtle text-info px-3 py-1 rounded-pill">Yes</span>
|
|
<?php else: ?>
|
|
<span class="badge bg-secondary-subtle text-secondary px-3 py-1 rounded-pill">No</span>
|
|
<?php endif; ?>
|
|
</td>
|
|
<td class="text-end pe-4">
|
|
<?php if (has_permission('users_edit') || has_permission('users_add')): ?>
|
|
<button type="button" class="btn btn-sm btn-outline-primary rounded-pill px-3 me-1"
|
|
data-bs-toggle="modal" data-bs-target="#userModal"
|
|
onclick='prepareEditForm(<?= htmlspecialchars(json_encode($user), ENT_QUOTES, "UTF-8") ?>)' title="Edit Profile">
|
|
<i class="bi bi-pencil me-1"></i> Edit</button>
|
|
<?php endif; ?>
|
|
|
|
<?php if (has_permission('users_del')): ?>
|
|
<a href="?delete=<?= $user['id'] ?>" class="btn btn-sm btn-outline-danger rounded-pill px-3" onclick="return confirm('Permanently delete this user account?')">
|
|
<i class="bi bi-trash"></i>
|
|
</a>
|
|
<?php endif; ?>
|
|
</td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
<?php if (empty($users)): ?>
|
|
<tr>
|
|
<td colspan="7" class="text-center py-5 text-muted">No users found.</td>
|
|
</tr>
|
|
<?php endif; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<!-- Bottom Pagination -->
|
|
<div class="p-3 border-top bg-light">
|
|
<?php render_pagination_controls($users_pagination); ?>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- User Modal -->
|
|
<?php if (has_permission('users_add') || has_permission('users_edit')): ?>
|
|
<div class="modal fade" id="userModal" tabindex="-1" aria-hidden="true">
|
|
<div class="modal-dialog modal-dialog-centered">
|
|
<div class="modal-content border-0 shadow-lg rounded-4">
|
|
<div class="modal-header bg-primary text-white border-0 py-3">
|
|
<h5 class="modal-title fw-bold" id="userModalTitle">Add New User</h5>
|
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<form method="POST" id="userForm" enctype="multipart/form-data">
|
|
<div class="modal-body p-4">
|
|
<input type="hidden" name="action" id="userAction" value="add_user">
|
|
<input type="hidden" name="id" id="userId">
|
|
|
|
<div class="mb-4 text-center" id="userImagePreviewContainer" style="display: none;">
|
|
<img src="" id="userImagePreview" class="img-fluid rounded-circle border border-4 border-white shadow mb-2" style="width: 100px; height: 100px; object-fit: cover;">
|
|
<p class="small text-muted mb-0">Current Profile Picture</p>
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<label class="form-label small fw-bold text-muted">FULL NAME</label>
|
|
<div class="input-group">
|
|
<input type="text" name="full_name" id="userFullName" class="form-control rounded-start-3 border-0 bg-light">
|
|
<button class="btn btn-outline-secondary border-0 bg-light" type="button" id="btnTranslate">
|
|
<i class="bi bi-translate text-primary"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label small fw-bold text-muted">ARABIC FULL NAME</label>
|
|
<input type="text" name="full_name_ar" id="userFullNameAr" class="form-control rounded-3 border-0 bg-light" dir="rtl">
|
|
</div>
|
|
<div class="row g-3">
|
|
<div class="col-md-6 mb-3">
|
|
<label class="form-label small fw-bold text-muted">USERNAME <span class="text-danger">*</span></label>
|
|
<input type="text" name="username" id="userUsername" class="form-control rounded-3 border-0 bg-light" required>
|
|
</div>
|
|
<div class="col-md-6 mb-3">
|
|
<label class="form-label small fw-bold text-muted">EMAIL</label>
|
|
<input type="email" name="email" id="userEmail" class="form-control rounded-3 border-0 bg-light">
|
|
</div>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label small fw-bold text-muted">PASSWORD <span id="pwdLabel" class="text-danger">*</span></label>
|
|
<input type="password" name="password" id="userPassword" class="form-control rounded-3 border-0 bg-light" placeholder="Min. 6 characters">
|
|
<small class="text-muted" id="pwdHint" style="display:none;">Leave blank to keep the current password.</small>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label small fw-bold text-muted">ROLE / USER GROUP <span class="text-danger">*</span></label>
|
|
<select name="group_id" id="userGroupId" class="form-select rounded-3 border-0 bg-light" required>
|
|
<option value="">Select Group</option>
|
|
<?php foreach ($groups as $group): ?>
|
|
<option value="<?= $group['id'] ?>"><?= htmlspecialchars($group['name']) ?></option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
<div class="mb-4">
|
|
<label class="form-label small fw-bold text-muted">PROFILE PICTURE</label>
|
|
<input type="file" name="profile_pic" id="userProfilePicFile" class="form-control rounded-3 border-0 bg-light" accept="image/*">
|
|
</div>
|
|
<div class="row bg-light p-3 rounded-4 g-2">
|
|
<div class="col-6">
|
|
<div class="form-check form-switch m-0">
|
|
<input class="form-check-input" type="checkbox" name="is_active" id="userIsActive" checked>
|
|
<label class="form-check-label fw-bold small" for="userIsActive">Active Account</label>
|
|
</div>
|
|
</div>
|
|
<div class="col-6">
|
|
<div class="form-check form-switch m-0">
|
|
<input class="form-check-input" type="checkbox" name="is_ratable" id="userIsRatable" checked>
|
|
<label class="form-check-label fw-bold small" for="userIsRatable">Ratable Staff</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer border-0 p-4 pt-0">
|
|
<button type="button" class="btn btn-light rounded-pill px-4" data-bs-dismiss="modal">Cancel</button>
|
|
<button type="submit" class="btn btn-primary rounded-pill px-4 fw-bold shadow-sm">Save User Profile</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
function prepareAddForm() {
|
|
document.getElementById('userModalTitle').innerText = 'Add New User';
|
|
document.getElementById('userAction').value = 'add_user';
|
|
document.getElementById('userForm').reset();
|
|
document.getElementById('userId').value = '';
|
|
document.getElementById('pwdLabel').style.display = 'inline';
|
|
document.getElementById('pwdHint').style.display = 'none';
|
|
document.getElementById('userPassword').required = true;
|
|
document.getElementById('userImagePreviewContainer').style.display = 'none';
|
|
}
|
|
|
|
function prepareEditForm(user) {
|
|
if (!user) return;
|
|
document.getElementById('userModalTitle').innerText = 'Edit User: ' + (user.full_name || user.username);
|
|
document.getElementById('userAction').value = 'edit_user';
|
|
document.getElementById('userId').value = user.id;
|
|
document.getElementById('userFullName').value = user.full_name || '';
|
|
document.getElementById('userFullNameAr').value = user.full_name_ar || '';
|
|
document.getElementById('userUsername').value = user.username || '';
|
|
document.getElementById('userEmail').value = user.email || '';
|
|
document.getElementById('userGroupId').value = user.group_id || '';
|
|
document.getElementById('userIsActive').checked = user.is_active == 1;
|
|
document.getElementById('userIsRatable').checked = user.is_ratable == 1;
|
|
document.getElementById('userPassword').required = false;
|
|
document.getElementById('pwdLabel').style.display = 'none';
|
|
document.getElementById('pwdHint').style.display = 'block';
|
|
|
|
if (user.profile_pic) {
|
|
const preview = document.getElementById('userImagePreview');
|
|
preview.src = '../' + user.profile_pic;
|
|
document.getElementById('userImagePreviewContainer').style.display = 'block';
|
|
} else {
|
|
document.getElementById('userImagePreviewContainer').style.display = 'none';
|
|
}
|
|
}
|
|
|
|
document.getElementById('btnTranslate').addEventListener('click', function() {
|
|
const text = document.getElementById('userFullName').value;
|
|
if (!text) {
|
|
alert('Please enter a full name first.');
|
|
return;
|
|
}
|
|
|
|
const btn = this;
|
|
const originalHtml = btn.innerHTML;
|
|
btn.disabled = true;
|
|
btn.innerHTML = '<span class="spinner-border spinner-border-sm text-primary" role="status" aria-hidden="true"></span>';
|
|
|
|
fetch('../api/translate.php', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
text: text,
|
|
target_lang: 'Arabic'
|
|
}),
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
document.getElementById('userFullNameAr').value = data.translated_text;
|
|
} else {
|
|
alert('Translation failed: ' + (data.error || 'Unknown error'));
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
alert('An error occurred during translation.');
|
|
})
|
|
.finally(() => {
|
|
btn.disabled = false;
|
|
btn.innerHTML = originalHtml;
|
|
});
|
|
});
|
|
</script>
|
|
<?php endif; ?>
|
|
|
|
<?php include 'includes/footer.php'; ?>
|