feat: add user editing password reset and deletion
This commit is contained in:
+44
-3
@@ -17,6 +17,7 @@ require_once __DIR__ . '/../app/Domain/SLA/SlaAgreement.php';
|
||||
require_once __DIR__ . '/../app/Domain/SLA/SlaThresholdClassifier.php';
|
||||
require_once __DIR__ . '/../app/Domain/User/PasswordPolicy.php';
|
||||
require_once __DIR__ . '/../app/Domain/User/UserRecord.php';
|
||||
require_once __DIR__ . '/../app/Domain/User/UserAdminService.php';
|
||||
require_once __DIR__ . '/../app/Domain/User/RoleRecord.php';
|
||||
require_once __DIR__ . '/../app/Domain/User/PermissionMatrix.php';
|
||||
require_once __DIR__ . '/../app/Domain/User/RolePermissionService.php';
|
||||
@@ -658,6 +659,41 @@ if ($route === 'clients') {
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($route === 'user_edit') {
|
||||
require_permission('users.manage');
|
||||
if (($_SERVER['REQUEST_METHOD'] ?? 'GET') !== 'POST') { http_response_code(405); exit('Edit requires POST'); }
|
||||
verify_csrf();
|
||||
$targetId = filter_var(scalar_input($_POST['user_id'] ?? null), FILTER_VALIDATE_INT);
|
||||
$existingUsers = db()->query('SELECT id, name, email, role_id, is_active, role_name FROM users u JOIN roles r ON r.id = u.role_id')->fetchAll();
|
||||
$target = null; foreach ($existingUsers as $candidate) if ((int)$candidate['id'] === (int)$targetId) { $target = $candidate; break; }
|
||||
if (!$target) { http_response_code(404); exit('User not found'); }
|
||||
$payload = ['name' => $_POST['name'] ?? null, 'email' => $_POST['email'] ?? null, 'role_id' => $_POST['role_id'] ?? null, 'is_active' => isset($_POST['is_active']) ? '1' : '0'];
|
||||
$service = new \App\Domain\User\UserAdminService();
|
||||
$result = $service->validateForEdit((int)$targetId, $payload, $existingUsers);
|
||||
if (trim(scalar_input($_POST['password'] ?? null)) !== '') { $passwordCheck = $service->validatePasswordReset(['id' => $targetId], $_POST['password']); if (!$passwordCheck['valid']) $result['errors'] = [...$result['errors'], ...$passwordCheck['errors']]; }
|
||||
$roleCheck = db()->prepare('SELECT id FROM roles WHERE id = :id'); $roleCheck->execute(['id' => $result['role_id'] ?? 0]); if (!$roleCheck->fetchColumn()) $result['errors']['role_id'] = 'Selected role does not exist.';
|
||||
if (!$result['errors']) {
|
||||
$pdo = db(); $pdo->beginTransaction();
|
||||
$pdo->prepare('UPDATE users SET name = :name, email = :email, role_id = :role, is_active = :active WHERE id = :id')->execute(['name' => $result['name'], 'email' => $result['email'], 'role' => $result['role_id'], 'active' => $result['is_active'] ? 1 : 0, 'id' => $targetId]);
|
||||
if (trim(scalar_input($_POST['password'] ?? null)) !== '') $pdo->prepare('UPDATE users SET password_hash = :hash WHERE id = :id')->execute(['hash' => password_hash(scalar_input($_POST['password']), PASSWORD_DEFAULT), 'id' => $targetId]);
|
||||
audit('user_updated', 'user', (int)$targetId, ['password_changed' => trim(scalar_input($_POST['password'] ?? null)) !== '']); $pdo->commit(); header('Location: /?route=users&updated=1'); exit;
|
||||
}
|
||||
$_SESSION['user_edit_errors'] = $result['errors']; header('Location: /?route=users&edit_user=' . (int)$targetId); exit;
|
||||
}
|
||||
|
||||
if ($route === 'user_delete') {
|
||||
if (($user['role_name'] ?? '') !== 'Administrator') { http_response_code(403); exit('Forbidden'); }
|
||||
if (($_SERVER['REQUEST_METHOD'] ?? 'GET') !== 'POST') { http_response_code(405); exit('Delete requires POST'); }
|
||||
verify_csrf();
|
||||
$targetId = filter_var(scalar_input($_POST['user_id'] ?? null), FILTER_VALIDATE_INT);
|
||||
if (!$targetId || (int)$targetId === (int)$user['id']) { http_response_code(400); exit('User cannot be deleted.'); }
|
||||
$targetStmt = db()->prepare('SELECT u.id, u.name, u.role_id, r.name AS role_name FROM users u JOIN roles r ON r.id = u.role_id WHERE u.id = :id'); $targetStmt->execute(['id' => $targetId]); $target = $targetStmt->fetch();
|
||||
$service = new \App\Domain\User\UserAdminService();
|
||||
if (!$target || $service->isProtectedAdministrator($target)) { http_response_code(403); exit('Protected Administrator cannot be deleted.'); }
|
||||
$linked = db()->prepare('SELECT (SELECT COUNT(*) FROM jobcard_assignments WHERE user_id = :id_a) + (SELECT COUNT(*) FROM time_entries WHERE technician_id = :id_b)'); $linked->execute(['id_a' => $targetId, 'id_b' => $targetId]);
|
||||
if ((int)$linked->fetchColumn() > 0) { http_response_code(409); exit('User has assigned jobcards or time entries; deactivate the user instead.'); }
|
||||
audit('user_deleted', 'user', (int)$targetId, ['name' => $target['name']]); db()->prepare('DELETE FROM users WHERE id = :id')->execute(['id' => $targetId]); header('Location: /?route=users&deleted=1'); exit;
|
||||
}
|
||||
if ($route === 'users') {
|
||||
require_permission('users.manage');
|
||||
$userErrors = [];
|
||||
@@ -682,10 +718,15 @@ if ($route === 'users') {
|
||||
}
|
||||
}
|
||||
$roles = db()->query('SELECT id, name FROM roles ORDER BY name')->fetchAll();
|
||||
$users = db()->query('SELECT u.id, u.name, u.email, u.is_active, u.last_login_at, r.name AS role_name FROM users u JOIN roles r ON r.id = u.role_id ORDER BY u.name')->fetchAll();
|
||||
$users = db()->query('SELECT u.id, u.name, u.email, u.role_id, u.is_active, u.last_login_at, r.name AS role_name FROM users u JOIN roles r ON r.id = u.role_id ORDER BY u.name')->fetchAll();
|
||||
$editUser = null;
|
||||
$editUserId = filter_var(scalar_input($_GET['edit_user'] ?? null), FILTER_VALIDATE_INT);
|
||||
foreach ($users as $listed) if ($editUserId && (int)$listed['id'] === (int)$editUserId) { $editUser = $listed; break; }
|
||||
if (isset($_SESSION['user_edit_errors'])) { $userErrors = (array)$_SESSION['user_edit_errors']; unset($_SESSION['user_edit_errors']); }
|
||||
render_header('Users');
|
||||
echo '<div class="d-flex justify-content-between align-items-center mb-4"><div><h1 class="h3 mb-1">Users</h1><p class="text-muted mb-0">Create and review system accounts.</p></div><button class="btn btn-primary" data-bs-toggle="collapse" data-bs-target="#new-user">New user</button></div>' . (isset($_GET['created']) ? '<div class="alert alert-success">User created successfully.</div>' : '') . ($userErrors ? '<div class="alert alert-danger">' . e(implode(' ', $userErrors)) . '</div>' : '') . '<div class="collapse mb-4" id="new-user"><div class="card"><div class="card-body"><form method="post" class="row g-3"><input type="hidden" name="_csrf" value="' . e(csrf_token()) . '"><div class="col-md-4"><label class="form-label">Name</label><input class="form-control" name="name" required></div><div class="col-md-4"><label class="form-label">Email</label><input class="form-control" type="email" name="email" required></div><div class="col-md-4"><label class="form-label">Role</label><select class="form-select" name="role_id" required><option value="">Choose role</option>'; foreach ($roles as $role) echo '<option value="' . (int)$role['id'] . '">' . e($role['name']) . '</option>'; echo '</select></div><div class="col-md-6"><label class="form-label">Initial password</label><input class="form-control" type="password" name="password" minlength="12" required><div class="form-text">Use at least 12 characters with upper/lowercase, number and symbol.</div></div><div class="col-12"><button class="btn btn-primary">Create user</button></div></form></div></div></div><div class="card"><div class="table-responsive"><table class="table align-middle mb-0"><thead><tr><th>Name</th><th>Email</th><th>Role</th><th>Status</th><th>Last login</th></tr></thead><tbody>';
|
||||
foreach ($users as $listedUser) echo '<tr><td>' . e($listedUser['name']) . '</td><td>' . e($listedUser['email']) . '</td><td>' . e($listedUser['role_name']) . '</td><td>' . e($listedUser['is_active'] ? 'Active' : 'Inactive') . '</td><td>' . e((string)($listedUser['last_login_at'] ?? 'Never')) . '</td></tr>';
|
||||
if ($editUser) echo '<div class="card mb-4"><div class="card-body"><div class="d-flex justify-content-between"><h2 class="h5">Edit user</h2><a href="/?route=users">Cancel</a></div><form method="post" action="/?route=user_edit" class="row g-3"><input type="hidden" name="_csrf" value="' . e(csrf_token()) . '"><input type="hidden" name="user_id" value="' . (int)$editUser['id'] . '"><div class="col-md-4"><label class="form-label">Name</label><input class="form-control" name="name" value="' . e($editUser['name']) . '" required></div><div class="col-md-4"><label class="form-label">Email</label><input class="form-control" type="email" name="email" value="' . e($editUser['email']) . '" required></div><div class="col-md-4"><label class="form-label">Role</label><select class="form-select" name="role_id" required>'; foreach ($roles as $role) echo '<option value="' . (int)$role['id'] . '"' . ((int)$role['id'] === (int)$editUser['role_id'] ? ' selected' : '') . '>' . e($role['name']) . '</option>'; echo '</select></div><div class="col-md-4"><label class="form-label">New password <span class="text-muted">(optional)</span></label><input class="form-control" type="password" name="password" minlength="12"><div class="form-text">Leave blank to keep the current password.</div></div><div class="col-md-4 form-check pt-4"><input class="form-check-input" type="checkbox" name="is_active" value="1" id="edit-active"' . ($editUser['is_active'] ? ' checked' : '') . '><label class="form-check-label" for="edit-active">Active user</label></div><div class="col-12"><button class="btn btn-primary">Save user changes</button></div></form></div></div>';
|
||||
echo '<div class="d-flex justify-content-between align-items-center mb-4"><div><h1 class="h3 mb-1">Users</h1><p class="text-muted mb-0">Create and review system accounts.</p></div><button class="btn btn-primary" data-bs-toggle="collapse" data-bs-target="#new-user">New user</button></div>' . (isset($_GET['created']) ? '<div class="alert alert-success">User created successfully.</div>' : '') . ($userErrors ? '<div class="alert alert-danger">' . e(implode(' ', $userErrors)) . '</div>' : '') . '<div class="collapse mb-4" id="new-user"><div class="card"><div class="card-body"><form method="post" class="row g-3"><input type="hidden" name="_csrf" value="' . e(csrf_token()) . '"><div class="col-md-4"><label class="form-label">Name</label><input class="form-control" name="name" required></div><div class="col-md-4"><label class="form-label">Email</label><input class="form-control" type="email" name="email" required></div><div class="col-md-4"><label class="form-label">Role</label><select class="form-select" name="role_id" required><option value="">Choose role</option>'; foreach ($roles as $role) echo '<option value="' . (int)$role['id'] . '">' . e($role['name']) . '</option>'; echo '</select></div><div class="col-md-6"><label class="form-label">Initial password</label><input class="form-control" type="password" name="password" minlength="12" required><div class="form-text">Use at least 12 characters with upper/lowercase, number and symbol.</div></div><div class="col-12"><button class="btn btn-primary">Create user</button></div></form></div></div></div><div class="card"><div class="table-responsive"><table class="table align-middle mb-0"><thead><tr><th>Name</th><th>Email</th><th>Role</th><th>Status</th><th>Last login</th><th>Actions</th></tr></thead><tbody>';
|
||||
foreach ($users as $listedUser) echo '<tr><td>' . e($listedUser['name']) . '</td><td>' . e($listedUser['email']) . '</td><td>' . e($listedUser['role_name']) . '</td><td>' . e($listedUser['is_active'] ? 'Active' : 'Inactive') . '</td><td>' . e((string)($listedUser['last_login_at'] ?? 'Never')) . '</td><td><a class="btn btn-sm btn-outline-primary" href="/?route=users&edit_user=' . (int)$listedUser['id'] . '">Edit</a> ' . (($user['role_name'] === 'Administrator' && (int)$listedUser['id'] !== (int)$user['id'] && $listedUser['role_name'] !== 'Administrator') ? '<form method="post" action="/?route=user_delete" class="d-inline" onsubmit="return confirm(\'Delete this user permanently?\');"><input type="hidden" name="_csrf" value="' . e(csrf_token()) . '"><input type="hidden" name="user_id" value="' . (int)$listedUser['id'] . '"><button class="btn btn-sm btn-outline-danger">Delete</button></form>' : '') . '</td></tr>';
|
||||
echo '</tbody></table></div></div>'; render_footer(); exit;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user