871 lines
33 KiB
PHP
871 lines
33 KiB
PHP
<?php
|
|
|
|
namespace App\Controllers\Setting;
|
|
|
|
use App\Controllers\BaseController;
|
|
use App\Libraries\UserSessionRegistry;
|
|
use App\Models\UserProfileModel;
|
|
use CodeIgniter\Shield\Entities\User;
|
|
use CodeIgniter\Shield\Models\UserModel;
|
|
|
|
class UserManagement extends BaseController
|
|
{
|
|
private function profileModel(): UserProfileModel
|
|
{
|
|
return model(UserProfileModel::class);
|
|
}
|
|
private function currentUserId(): int
|
|
{
|
|
return function_exists('auth') ? (int) (auth()->user()->id ?? 0) : 0;
|
|
}
|
|
|
|
private function canManageAdmins(): bool
|
|
{
|
|
return function_exists('auth') && auth()->loggedIn() && auth()->user()->can('users.manage-admins');
|
|
}
|
|
|
|
private function availableRoles(): array
|
|
{
|
|
$roles = (array) setting('AuthGroups.groups');
|
|
if (! $this->canManageAdmins()) {
|
|
foreach (['superadmin', 'admin', 'developer'] as $privilegedRole) {
|
|
unset($roles[$privilegedRole]);
|
|
}
|
|
}
|
|
|
|
return $roles;
|
|
}
|
|
|
|
private function isPrivilegedUser(User $user): bool
|
|
{
|
|
return $user->inGroup('superadmin', 'admin', 'developer') || $user->can('users.manage-admins');
|
|
}
|
|
|
|
public function index(): string
|
|
{
|
|
$profileModel = $this->profileModel();
|
|
$filters = [
|
|
'role' => (string) ($this->request->getGet('role') ?? ''),
|
|
'active' => (string) ($this->request->getGet('active') ?? ''),
|
|
'search' => (string) ($this->request->getGet('search') ?? ''),
|
|
];
|
|
|
|
$users = $profileModel->getUsersList($filters);
|
|
|
|
return view('Setting/Admin/UserManagement/index', [
|
|
'users' => $users,
|
|
'roles' => $this->availableRoles(),
|
|
'filters' => $filters,
|
|
]);
|
|
}
|
|
|
|
public function create(): string
|
|
{
|
|
$profileModel = $this->profileModel();
|
|
|
|
return view('Setting/Admin/UserManagement/create', [
|
|
'roles' => $this->availableRoles(),
|
|
'doctors' => $profileModel->getActiveDoctors(),
|
|
'nurses' => $profileModel->getActiveNurses(),
|
|
'selected_doctor_id' => (int) ($this->request->getGet('doctor_id') ?? 0),
|
|
'selected_nurse_id' => (int) ($this->request->getGet('nurse_id') ?? 0),
|
|
]);
|
|
}
|
|
|
|
public function store()
|
|
{
|
|
$username = trim((string) $this->request->getPost('username'));
|
|
$email = trim((string) $this->request->getPost('email'));
|
|
if ($email === '' && $username !== '') {
|
|
$email = strtolower($username) . '@hospital.local';
|
|
$postData = $this->request->getPost();
|
|
$postData['email'] = $email;
|
|
$this->request->setGlobal('post', $postData);
|
|
}
|
|
|
|
$rules = [
|
|
'username' => 'required|min_length[3]|max_length[30]|regex_match[/^[a-zA-Z0-9._-]+$/]',
|
|
'email' => 'required|valid_email|max_length[254]',
|
|
'password' => 'required|min_length[8]',
|
|
'role' => 'required',
|
|
'person_name' => 'permit_empty|max_length[120]',
|
|
'phone_no' => 'permit_empty|regex_match[/^[0-9+()\\s-]{7,20}$/]',
|
|
'doctor_id' => 'permit_empty|is_natural_no_zero',
|
|
'nurse_id' => 'permit_empty|is_natural_no_zero',
|
|
'department' => 'permit_empty|max_length[100]',
|
|
'designation' => 'permit_empty|max_length[100]',
|
|
'employee_code' => 'permit_empty|max_length[50]',
|
|
];
|
|
|
|
$profileModel = $this->profileModel();
|
|
|
|
if (! $this->validate($rules)) {
|
|
if ($this->request->isAJAX()) {
|
|
return view('Setting/Admin/UserManagement/create', [
|
|
'roles' => $this->availableRoles(),
|
|
'doctors' => $profileModel->getActiveDoctors(),
|
|
'nurses' => $profileModel->getActiveNurses(),
|
|
'errors' => $this->validator->getErrors(),
|
|
'formData' => $this->request->getPost(),
|
|
]);
|
|
}
|
|
|
|
return redirect()->back()->withInput()->with('errors', $this->validator->getErrors());
|
|
}
|
|
|
|
$role = strtolower((string) $this->request->getPost('role'));
|
|
$allowedRoles = array_keys($this->availableRoles());
|
|
if (! in_array($role, $allowedRoles, true)) {
|
|
$errors = ['role' => 'Invalid role selected.'];
|
|
if ($this->request->isAJAX()) {
|
|
return view('Setting/Admin/UserManagement/create', [
|
|
'roles' => $this->availableRoles(),
|
|
'doctors' => $profileModel->getActiveDoctors(),
|
|
'nurses' => $profileModel->getActiveNurses(),
|
|
'errors' => $errors,
|
|
'formData' => $this->request->getPost(),
|
|
]);
|
|
}
|
|
|
|
return redirect()->back()->withInput()->with('errors', $errors);
|
|
}
|
|
|
|
$user = new User();
|
|
$user->username = (string) $this->request->getPost('username');
|
|
$user->email = (string) $this->request->getPost('email');
|
|
$user->password = (string) $this->request->getPost('password');
|
|
$user->active = 1;
|
|
|
|
$userModel = model(UserModel::class);
|
|
try {
|
|
if (! $userModel->save($user)) {
|
|
throw new \RuntimeException(implode(' ', $userModel->errors()));
|
|
}
|
|
} catch (\Throwable $e) {
|
|
return view('Setting/Admin/UserManagement/create', [
|
|
'roles' => $this->availableRoles(),
|
|
'doctors' => $profileModel->getActiveDoctors(),
|
|
'nurses' => $profileModel->getActiveNurses(),
|
|
'errors' => ['user' => $e->getMessage() !== '' ? $e->getMessage() : 'Unable to create user.'],
|
|
'formData' => $this->request->getPost(),
|
|
]);
|
|
}
|
|
|
|
$savedUser = $userModel->find($userModel->getInsertID());
|
|
if ($savedUser !== null) {
|
|
$savedUser->addGroup($role);
|
|
|
|
$personName = trim((string) $this->request->getPost('person_name'));
|
|
$phoneNo = trim((string) $this->request->getPost('phone_no'));
|
|
$doctorId = (int) $this->request->getPost('doctor_id');
|
|
$nurseId = (int) $this->request->getPost('nurse_id');
|
|
$department = trim((string) $this->request->getPost('department'));
|
|
$designation = trim((string) $this->request->getPost('designation'));
|
|
$employeeCode = trim((string) $this->request->getPost('employee_code'));
|
|
|
|
$profileModel->saveProfile((int) $savedUser->id, [
|
|
'full_name' => $personName,
|
|
'phone_no' => $phoneNo,
|
|
'doctor_id' => $doctorId > 0 ? $doctorId : null,
|
|
'nurse_id' => $nurseId > 0 ? $nurseId : null,
|
|
'department' => $department,
|
|
'designation' => $designation,
|
|
'employee_code' => $employeeCode,
|
|
]);
|
|
|
|
$this->syncIdentityMeta((int) $savedUser->id, $personName, $phoneNo);
|
|
$this->auditClinicalUpdate('user_management', 'create', $savedUser->id, null, [
|
|
'username' => $savedUser->username,
|
|
'role' => $role,
|
|
'active' => 1,
|
|
'doctor_id' => $doctorId > 0 ? $doctorId : null,
|
|
'nurse_id' => $nurseId > 0 ? $nurseId : null,
|
|
'department' => $department,
|
|
'employee_code' => $employeeCode,
|
|
]);
|
|
}
|
|
|
|
if ($this->request->isAJAX()) {
|
|
return view('Setting/Admin/UserManagement/index', [
|
|
'users' => $profileModel->getUsersList(),
|
|
'roles' => $this->availableRoles(),
|
|
'message' => 'User created successfully.',
|
|
]);
|
|
}
|
|
|
|
return redirect()
|
|
->to('setting/admin/user-management')
|
|
->with('message', 'User created successfully.');
|
|
}
|
|
|
|
public function permissions(): string
|
|
{
|
|
$userModel = model(UserModel::class);
|
|
|
|
$users = $userModel
|
|
->withIdentities()
|
|
->withGroups()
|
|
->findAll();
|
|
|
|
$selectedUserId = (int) $this->request->getGet('user_id');
|
|
$selectedUser = null;
|
|
if ($selectedUserId) {
|
|
$selectedUser = $userModel->withPermissions()->find($selectedUserId);
|
|
}
|
|
|
|
$roleContext = $this->rolePermissionContext($selectedUser);
|
|
|
|
return view('Setting/Admin/UserManagement/permissions', [
|
|
'users' => $users,
|
|
'permissions' => setting('AuthGroups.permissions'),
|
|
'selectedUser' => $selectedUser,
|
|
'selectedRoleTitles' => $roleContext['titles'],
|
|
'inheritedPermissions' => $roleContext['permissions'],
|
|
]);
|
|
}
|
|
|
|
public function edit(int $userId): string
|
|
{
|
|
$userModel = model(UserModel::class);
|
|
$user = $userModel
|
|
->withIdentities()
|
|
->withGroups()
|
|
->find($userId);
|
|
|
|
$profileModel = $this->profileModel();
|
|
|
|
if ($user === null) {
|
|
return view('Setting/Admin/UserManagement/index', [
|
|
'users' => $profileModel->getUsersList(),
|
|
'roles' => $this->availableRoles(),
|
|
'errors' => ['User not found.'],
|
|
]);
|
|
}
|
|
|
|
if ($this->isPrivilegedUser($user) && ! $this->canManageAdmins()) {
|
|
return view('Setting/Admin/UserManagement/index', [
|
|
'users' => $profileModel->getUsersList(),
|
|
'roles' => $this->availableRoles(),
|
|
'errors' => ['You cannot edit an administrator account.'],
|
|
]);
|
|
}
|
|
|
|
$emailIdentity = $user->getEmailIdentity();
|
|
$email = $emailIdentity ? (string) ($emailIdentity->secret ?? '') : '';
|
|
$profile = $profileModel->getProfile($userId);
|
|
|
|
return view('Setting/Admin/UserManagement/edit', [
|
|
'user' => $user,
|
|
'email' => $email,
|
|
'person_name' => trim((string) ($profile['full_name'] ?? '')),
|
|
'phone_no' => trim((string) ($profile['phone_no'] ?? '')),
|
|
'doctor_id' => (int) ($profile['doctor_id'] ?? 0),
|
|
'nurse_id' => (int) ($profile['nurse_id'] ?? 0),
|
|
'department' => trim((string) ($profile['department'] ?? '')),
|
|
'designation' => trim((string) ($profile['designation'] ?? '')),
|
|
'employee_code' => trim((string) ($profile['employee_code'] ?? '')),
|
|
'roles' => $this->availableRoles(),
|
|
'current_role' => (string) (($user->getGroups() ?? [])[0] ?? ''),
|
|
'doctors' => $profileModel->getActiveDoctors(),
|
|
'nurses' => $profileModel->getActiveNurses(),
|
|
]);
|
|
}
|
|
|
|
public function update(int $userId)
|
|
{
|
|
$userModel = model(UserModel::class);
|
|
$user = $userModel->withGroups()->find($userId);
|
|
$profileModel = $this->profileModel();
|
|
|
|
if ($user === null) {
|
|
return view('Setting/Admin/UserManagement/index', [
|
|
'users' => $profileModel->getUsersList(),
|
|
'roles' => $this->availableRoles(),
|
|
'errors' => ['User not found.'],
|
|
]);
|
|
}
|
|
|
|
if ($this->isPrivilegedUser($user) && ! $this->canManageAdmins()) {
|
|
return view('Setting/Admin/UserManagement/index', [
|
|
'users' => $profileModel->getUsersList(),
|
|
'roles' => $this->availableRoles(),
|
|
'errors' => ['You cannot edit an administrator account.'],
|
|
]);
|
|
}
|
|
|
|
$username = trim((string) $this->request->getPost('username'));
|
|
$email = trim((string) $this->request->getPost('email'));
|
|
$personName = trim((string) $this->request->getPost('person_name'));
|
|
$phoneNo = trim((string) $this->request->getPost('phone_no'));
|
|
$doctorId = (int) $this->request->getPost('doctor_id');
|
|
$nurseId = (int) $this->request->getPost('nurse_id');
|
|
$department = trim((string) $this->request->getPost('department'));
|
|
$designation = trim((string) $this->request->getPost('designation'));
|
|
$employeeCode = trim((string) $this->request->getPost('employee_code'));
|
|
$password = (string) $this->request->getPost('password');
|
|
$active = $this->request->getPost('active') ? 1 : 0;
|
|
$role = strtolower(trim((string) $this->request->getPost('role')));
|
|
$oldGroups = $user->getGroups() ?? [];
|
|
$oldState = [
|
|
'username' => (string) ($user->username ?? ''),
|
|
'groups' => $oldGroups,
|
|
'active' => (int) ($user->active ?? 0),
|
|
];
|
|
|
|
$errors = [];
|
|
if ($username === '' || strlen($username) < 3 || strlen($username) > 30 || preg_match('/\A[a-zA-Z0-9._-]+\z/', $username) !== 1) {
|
|
$errors['username'] = 'Login ID must be 3-30 chars and only letters, numbers, dot (.), hyphen (-), underscore (_).';
|
|
}
|
|
if ($email === '' || ! filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
|
$errors['email'] = 'Valid email is required.';
|
|
}
|
|
if ($personName !== '' && mb_strlen($personName) > 120) {
|
|
$errors['person_name'] = 'Person name must be maximum 120 characters.';
|
|
}
|
|
if ($phoneNo !== '' && preg_match('/^[0-9+\-()\s]{7,20}$/', $phoneNo) !== 1) {
|
|
$errors['phone_no'] = 'Phone format is invalid.';
|
|
}
|
|
if ($password !== '' && strlen($password) < 8) {
|
|
$errors['password'] = 'Password must be at least 8 characters.';
|
|
}
|
|
if (! array_key_exists($role, $this->availableRoles())) {
|
|
$errors['role'] = 'Invalid role selected.';
|
|
}
|
|
if ($userId === $this->currentUserId() && $active !== 1) {
|
|
$errors['active'] = 'You cannot deactivate your own account.';
|
|
}
|
|
|
|
$tables = config('Auth')->tables;
|
|
$usersTable = (string) ($tables['users'] ?? 'users');
|
|
$identitiesTable = (string) ($tables['identities'] ?? 'auth_identities');
|
|
|
|
if (empty($errors)) {
|
|
$duplicateUsername = $this->db->table($usersTable)
|
|
->select('id')
|
|
->where('username', $username)
|
|
->where('id !=', $userId)
|
|
->get(1)
|
|
->getRowArray();
|
|
if (! empty($duplicateUsername)) {
|
|
$errors['username'] = 'Login ID already used by another user.';
|
|
}
|
|
|
|
$duplicateEmail = $this->db->table($identitiesTable)
|
|
->select('id')
|
|
->where('type', 'email_password')
|
|
->where('secret', $email)
|
|
->where('user_id !=', $userId)
|
|
->get(1)
|
|
->getRowArray();
|
|
if (! empty($duplicateEmail)) {
|
|
$errors['email'] = 'Email already used by another user.';
|
|
}
|
|
}
|
|
|
|
if (! empty($errors)) {
|
|
return view('Setting/Admin/UserManagement/edit', [
|
|
'user' => $userModel->withIdentities()->find($userId),
|
|
'email' => $email,
|
|
'person_name' => $personName,
|
|
'phone_no' => $phoneNo,
|
|
'doctor_id' => $doctorId,
|
|
'nurse_id' => $nurseId,
|
|
'department' => $department,
|
|
'designation' => $designation,
|
|
'employee_code' => $employeeCode,
|
|
'roles' => $this->availableRoles(),
|
|
'current_role' => $role,
|
|
'doctors' => $profileModel->getActiveDoctors(),
|
|
'nurses' => $profileModel->getActiveNurses(),
|
|
'errors' => $errors,
|
|
'formData' => [
|
|
'username' => $username,
|
|
'email' => $email,
|
|
'person_name' => $personName,
|
|
'phone_no' => $phoneNo,
|
|
'doctor_id' => $doctorId,
|
|
'nurse_id' => $nurseId,
|
|
'department' => $department,
|
|
'designation' => $designation,
|
|
'employee_code' => $employeeCode,
|
|
'active' => $active,
|
|
'role' => $role,
|
|
],
|
|
]);
|
|
}
|
|
|
|
$user->username = $username;
|
|
$user->email = $email;
|
|
$user->active = $active;
|
|
if ($password !== '') {
|
|
$user->password = $password;
|
|
}
|
|
|
|
$userModel->save($user);
|
|
$user->syncGroups($role);
|
|
|
|
$profileModel->saveProfile($userId, [
|
|
'full_name' => $personName,
|
|
'phone_no' => $phoneNo,
|
|
'doctor_id' => $doctorId > 0 ? $doctorId : null,
|
|
'nurse_id' => $nurseId > 0 ? $nurseId : null,
|
|
'department' => $department,
|
|
'designation' => $designation,
|
|
'employee_code' => $employeeCode,
|
|
]);
|
|
|
|
$this->syncIdentityMeta($userId, $personName, $phoneNo);
|
|
$this->auditClinicalUpdate('user_management', 'update', $userId, $oldState, [
|
|
'username' => $username,
|
|
'groups' => [$role],
|
|
'active' => $active,
|
|
'doctor_id' => $doctorId > 0 ? $doctorId : null,
|
|
'nurse_id' => $nurseId > 0 ? $nurseId : null,
|
|
'department' => $department,
|
|
'employee_code' => $employeeCode,
|
|
]);
|
|
|
|
if ($active !== 1) {
|
|
(new UserSessionRegistry($this->db))->revokeUser($userId, $this->currentUserId(), 'account_deactivated');
|
|
}
|
|
|
|
return view('Setting/Admin/UserManagement/index', [
|
|
'users' => $profileModel->getUsersList(),
|
|
'roles' => $this->availableRoles(),
|
|
'message' => 'User updated successfully.',
|
|
]);
|
|
}
|
|
|
|
public function resetPasswordForm(int $userId): string
|
|
{
|
|
$userModel = model(UserModel::class);
|
|
$user = $userModel->find($userId);
|
|
|
|
if ($user === null) {
|
|
$users = $userModel
|
|
->withIdentities()
|
|
->withGroups()
|
|
->findAll();
|
|
|
|
return view('Setting/Admin/UserManagement/index', [
|
|
'users' => $users,
|
|
'errors' => ['User not found.'],
|
|
]);
|
|
}
|
|
|
|
if ($this->isPrivilegedUser($user) && ! $this->canManageAdmins()) {
|
|
return view('Setting/Admin/UserManagement/index', [
|
|
'users' => $userModel->withIdentities()->withGroups()->findAll(),
|
|
'errors' => ['You cannot reset an administrator password.'],
|
|
]);
|
|
}
|
|
|
|
return view('Setting/Admin/UserManagement/reset_password', [
|
|
'user' => $user,
|
|
]);
|
|
}
|
|
|
|
public function resetPassword(int $userId)
|
|
{
|
|
$userModel = model(UserModel::class);
|
|
$user = $userModel->withGroups()->find($userId);
|
|
if ($user === null) {
|
|
$users = $userModel
|
|
->withIdentities()
|
|
->withGroups()
|
|
->findAll();
|
|
|
|
return view('Setting/Admin/UserManagement/index', [
|
|
'users' => $users,
|
|
'errors' => ['User not found.'],
|
|
]);
|
|
}
|
|
if ($this->isPrivilegedUser($user) && ! $this->canManageAdmins()) {
|
|
return view('Setting/Admin/UserManagement/index', [
|
|
'users' => $userModel->withIdentities()->withGroups()->findAll(),
|
|
'errors' => ['You cannot reset an administrator password.'],
|
|
]);
|
|
}
|
|
|
|
$password = trim((string) $this->request->getPost('password'));
|
|
$passwordConfirm = trim((string) $this->request->getPost('password_confirm'));
|
|
$errors = [];
|
|
|
|
if (preg_match('/^\d{6}$/', $password) !== 1) {
|
|
$errors['password'] = 'Temporary PIN must be exactly 6 digits.';
|
|
}
|
|
|
|
if (! hash_equals($password, $passwordConfirm)) {
|
|
$errors['password_confirm'] = 'PIN and confirm PIN do not match.';
|
|
}
|
|
|
|
if (! empty($errors)) {
|
|
return view('Setting/Admin/UserManagement/reset_password', [
|
|
'user' => $user,
|
|
'errors' => $errors,
|
|
]);
|
|
}
|
|
|
|
$tables = config('Auth')->tables;
|
|
$identitiesTable = (string) ($tables['identities'] ?? 'auth_identities');
|
|
if (! $this->db->tableExists($identitiesTable)) {
|
|
$users = $userModel
|
|
->withIdentities()
|
|
->withGroups()
|
|
->findAll();
|
|
|
|
return view('Setting/Admin/UserManagement/index', [
|
|
'users' => $users,
|
|
'errors' => ['Auth identity table not found.'],
|
|
]);
|
|
}
|
|
|
|
$identity = $this->db->table($identitiesTable)
|
|
->select('id')
|
|
->where('user_id', $userId)
|
|
->where('type', 'email_password')
|
|
->get(1)
|
|
->getRowArray();
|
|
|
|
if (empty($identity['id'])) {
|
|
$users = $userModel
|
|
->withIdentities()
|
|
->withGroups()
|
|
->findAll();
|
|
|
|
return view('Setting/Admin/UserManagement/index', [
|
|
'users' => $users,
|
|
'errors' => ['User login identity not found.'],
|
|
]);
|
|
}
|
|
|
|
$this->db->table($identitiesTable)
|
|
->where('id', (int) $identity['id'])
|
|
->update([
|
|
'secret2' => password_hash($password, PASSWORD_DEFAULT),
|
|
'force_reset' => 1,
|
|
'updated_at' => date('Y-m-d H:i:s'),
|
|
]);
|
|
(new UserSessionRegistry($this->db))->revokeUser($userId, $this->currentUserId(), 'password_reset');
|
|
$this->auditClinicalUpdate('user_management', 'password_reset', $userId, null, ['force_reset' => 1]);
|
|
|
|
$users = $userModel
|
|
->withIdentities()
|
|
->withGroups()
|
|
->findAll();
|
|
|
|
return view('Setting/Admin/UserManagement/index', [
|
|
'users' => $users,
|
|
'message' => 'Temporary 6-digit PIN set successfully. User must change password on next login.',
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* @return array<string, mixed>
|
|
*/
|
|
private function decodeIdentityExtra($extra): array
|
|
{
|
|
if (is_array($extra)) {
|
|
return $extra;
|
|
}
|
|
if (is_string($extra) && trim($extra) !== '') {
|
|
$decoded = json_decode($extra, true);
|
|
if (is_array($decoded)) {
|
|
return $decoded;
|
|
}
|
|
}
|
|
|
|
return [];
|
|
}
|
|
|
|
private function syncIdentityMeta(int $userId, string $personName, string $phoneNo): void
|
|
{
|
|
$tables = config('Auth')->tables;
|
|
$identitiesTable = (string) ($tables['identities'] ?? 'auth_identities');
|
|
if (! $this->db->tableExists($identitiesTable)) {
|
|
return;
|
|
}
|
|
|
|
$row = $this->db->table($identitiesTable)
|
|
->select('id,extra')
|
|
->where('user_id', $userId)
|
|
->where('type', 'email_password')
|
|
->get(1)
|
|
->getRowArray();
|
|
|
|
if (empty($row) || empty($row['id'])) {
|
|
return;
|
|
}
|
|
|
|
$payload = $this->decodeIdentityExtra($row['extra'] ?? null);
|
|
if ($personName === '') {
|
|
unset($payload['full_name']);
|
|
} else {
|
|
$payload['full_name'] = $personName;
|
|
}
|
|
|
|
if ($phoneNo === '') {
|
|
unset($payload['phone_no']);
|
|
} else {
|
|
$payload['phone_no'] = $phoneNo;
|
|
}
|
|
|
|
$this->db->table($identitiesTable)
|
|
->where('id', (int) $row['id'])
|
|
->update([
|
|
'extra' => empty($payload) ? null : json_encode($payload, JSON_UNESCAPED_UNICODE),
|
|
]);
|
|
}
|
|
|
|
public function savePermissions()
|
|
{
|
|
$rules = [
|
|
'user_id' => 'required|is_natural_no_zero',
|
|
];
|
|
|
|
if (! $this->validate($rules)) {
|
|
if ($this->request->isAJAX()) {
|
|
$users = model(UserModel::class)
|
|
->withIdentities()
|
|
->withGroups()
|
|
->findAll();
|
|
|
|
return view('Setting/Admin/UserManagement/permissions', [
|
|
'users' => $users,
|
|
'permissions' => setting('AuthGroups.permissions'),
|
|
'selectedUser' => null,
|
|
'errors' => $this->validator->getErrors(),
|
|
]);
|
|
}
|
|
|
|
return redirect()->back()->withInput()->with('errors', $this->validator->getErrors());
|
|
}
|
|
|
|
$userId = (int) $this->request->getPost('user_id');
|
|
$permissions = (array) $this->request->getPost('permissions');
|
|
$permissions = array_values(array_filter(array_map('strtolower', $permissions)));
|
|
|
|
$allowedPermissions = array_keys(setting('AuthGroups.permissions'));
|
|
$permissions = array_values(array_intersect($permissions, $allowedPermissions));
|
|
|
|
$userModel = model(UserModel::class);
|
|
$user = $userModel->find($userId);
|
|
if ($user === null) {
|
|
$errors = ['user_id' => 'User not found.'];
|
|
if ($this->request->isAJAX()) {
|
|
$users = $userModel
|
|
->withIdentities()
|
|
->withGroups()
|
|
->findAll();
|
|
|
|
return view('Setting/Admin/UserManagement/permissions', [
|
|
'users' => $users,
|
|
'permissions' => setting('AuthGroups.permissions'),
|
|
'selectedUser' => null,
|
|
'errors' => $errors,
|
|
]);
|
|
}
|
|
|
|
return redirect()->back()->withInput()->with('errors', $errors);
|
|
}
|
|
|
|
$oldPermissions = $userModel->withPermissions()->find($userId)?->getPermissions() ?? [];
|
|
$user->syncPermissions(...$permissions);
|
|
$this->auditClinicalUpdate('user_management', 'permissions', $userId, $oldPermissions, $permissions);
|
|
|
|
if ($this->request->isAJAX()) {
|
|
$users = $userModel
|
|
->withIdentities()
|
|
->withGroups()
|
|
->findAll();
|
|
|
|
$selectedUser = $userModel->withPermissions()->find($userId);
|
|
$roleContext = $this->rolePermissionContext($selectedUser);
|
|
|
|
return view('Setting/Admin/UserManagement/permissions', [
|
|
'users' => $users,
|
|
'permissions' => setting('AuthGroups.permissions'),
|
|
'selectedUser' => $selectedUser,
|
|
'selectedRoleTitles' => $roleContext['titles'],
|
|
'inheritedPermissions' => $roleContext['permissions'],
|
|
'message' => 'Permissions updated successfully.',
|
|
]);
|
|
}
|
|
|
|
return redirect()
|
|
->to('setting/admin/user-management/permissions?user_id=' . $userId)
|
|
->with('message', 'Permissions updated successfully.');
|
|
}
|
|
|
|
/**
|
|
* @return array{titles: list<string>, permissions: list<string>}
|
|
*/
|
|
private function rolePermissionContext(?User $user): array
|
|
{
|
|
if ($user === null) {
|
|
return ['titles' => [], 'permissions' => []];
|
|
}
|
|
|
|
$availablePermissions = array_keys((array) setting('AuthGroups.permissions'));
|
|
$groups = (array) setting('AuthGroups.groups');
|
|
$matrix = (array) setting('AuthGroups.matrix');
|
|
$inheritedPermissions = [];
|
|
$roleTitles = [];
|
|
|
|
foreach ($user->getGroups() ?? [] as $role) {
|
|
$roleTitles[] = (string) ($groups[$role]['title'] ?? $role);
|
|
foreach ($matrix[$role] ?? [] as $grant) {
|
|
if (str_ends_with($grant, '.*')) {
|
|
$prefix = substr($grant, 0, -1);
|
|
foreach ($availablePermissions as $permission) {
|
|
if (str_starts_with($permission, $prefix)) {
|
|
$inheritedPermissions[] = $permission;
|
|
}
|
|
}
|
|
} elseif (in_array($grant, $availablePermissions, true)) {
|
|
$inheritedPermissions[] = $grant;
|
|
}
|
|
}
|
|
}
|
|
|
|
return [
|
|
'titles' => array_values(array_unique($roleTitles)),
|
|
'permissions' => array_values(array_unique($inheritedPermissions)),
|
|
];
|
|
}
|
|
|
|
public function sessions(): string
|
|
{
|
|
$registry = new UserSessionRegistry($this->db);
|
|
$registry->cleanup();
|
|
$rows = $registry->onlineUsers();
|
|
foreach ($rows as &$row) {
|
|
$meta = $this->decodeIdentityExtra($row['extra'] ?? null);
|
|
$row['person_name'] = trim((string) ($meta['full_name'] ?? ''));
|
|
unset($row['extra']);
|
|
}
|
|
unset($row);
|
|
|
|
return view('Setting/Admin/UserManagement/sessions', [
|
|
'sessions' => $rows,
|
|
'current_user_id' => $this->currentUserId(),
|
|
]);
|
|
}
|
|
|
|
public function forceLogout(int $userId)
|
|
{
|
|
if ($userId === $this->currentUserId()) {
|
|
return $this->response->setJSON(['update' => 0, 'error_text' => 'Use Sign Out to end your own session.']);
|
|
}
|
|
|
|
$user = model(UserModel::class)->withGroups()->find($userId);
|
|
if ($user === null) {
|
|
return $this->response->setJSON(['update' => 0, 'error_text' => 'User not found.']);
|
|
}
|
|
if ($this->isPrivilegedUser($user) && ! $this->canManageAdmins()) {
|
|
return $this->response->setStatusCode(403)->setJSON(['update' => 0, 'error_text' => 'You cannot log out an administrator.']);
|
|
}
|
|
|
|
$count = (new UserSessionRegistry($this->db))->revokeUser($userId, $this->currentUserId());
|
|
$this->auditClinicalUpdate('user_management', 'force_logout', $userId, null, ['sessions_revoked' => $count]);
|
|
|
|
return $this->response->setJSON([
|
|
'update' => 1,
|
|
'error_text' => $count > 0 ? 'User session ended.' : 'No active session found.',
|
|
'csrfName' => csrf_token(),
|
|
'csrfHash' => csrf_hash(),
|
|
]);
|
|
}
|
|
|
|
public function delete(int $userId)
|
|
{
|
|
if ($userId === $this->currentUserId()) {
|
|
return $this->response->setJSON(['update' => 0, 'error_text' => 'You cannot delete your own account.']);
|
|
}
|
|
|
|
$userModel = model(UserModel::class);
|
|
$user = $userModel->withGroups()->find($userId);
|
|
if ($user === null) {
|
|
return $this->response->setJSON(['update' => 0, 'error_text' => 'User not found.']);
|
|
}
|
|
if ($this->isPrivilegedUser($user) && ! $this->canManageAdmins()) {
|
|
return $this->response->setStatusCode(403)->setJSON(['update' => 0, 'error_text' => 'You cannot delete an administrator.']);
|
|
}
|
|
|
|
// Deactivate first to ensure no lingering access
|
|
$user->active = 0;
|
|
$userModel->save($user);
|
|
|
|
(new UserSessionRegistry($this->db))->revokeUser($userId, $this->currentUserId(), 'account_deleted');
|
|
$this->auditClinicalUpdate('user_management', 'delete', $userId, [
|
|
'username' => $user->username,
|
|
'groups' => $user->getGroups() ?? [],
|
|
], ['deleted' => true, 'soft_delete' => true]);
|
|
|
|
if (! $userModel->delete($userId)) {
|
|
return $this->response->setJSON(['update' => 0, 'error_text' => 'Unable to delete user.']);
|
|
}
|
|
|
|
return $this->response->setJSON([
|
|
'update' => 1,
|
|
'error_text' => 'User deactivated and removed from active list.',
|
|
'csrfName' => csrf_token(),
|
|
'csrfHash' => csrf_hash(),
|
|
]);
|
|
}
|
|
|
|
public function toggleStatus(int $userId)
|
|
{
|
|
if ($userId === $this->currentUserId()) {
|
|
return $this->response->setStatusCode(400)->setJSON([
|
|
'success' => 0,
|
|
'error_text' => 'You cannot deactivate your own account.',
|
|
'csrfName' => csrf_token(),
|
|
'csrfHash' => csrf_hash(),
|
|
]);
|
|
}
|
|
|
|
$userModel = model(UserModel::class);
|
|
$user = $userModel->withGroups()->find($userId);
|
|
if ($user === null) {
|
|
return $this->response->setStatusCode(404)->setJSON([
|
|
'success' => 0,
|
|
'error_text' => 'User not found.',
|
|
'csrfName' => csrf_token(),
|
|
'csrfHash' => csrf_hash(),
|
|
]);
|
|
}
|
|
|
|
if ($this->isPrivilegedUser($user) && ! $this->canManageAdmins()) {
|
|
return $this->response->setStatusCode(403)->setJSON([
|
|
'success' => 0,
|
|
'error_text' => 'You cannot change the status of an administrator account.',
|
|
'csrfName' => csrf_token(),
|
|
'csrfHash' => csrf_hash(),
|
|
]);
|
|
}
|
|
|
|
$newActive = (int) ($user->active ?? 0) === 1 ? 0 : 1;
|
|
$user->active = $newActive;
|
|
$userModel->save($user);
|
|
|
|
if ($newActive === 0) {
|
|
(new UserSessionRegistry($this->db))->revokeUser($userId, $this->currentUserId(), 'account_deactivated');
|
|
}
|
|
|
|
$this->auditClinicalUpdate('user_management', 'status_toggle', $userId, [
|
|
'active' => 1 - $newActive,
|
|
], [
|
|
'active' => $newActive,
|
|
]);
|
|
|
|
return $this->response->setJSON([
|
|
'success' => 1,
|
|
'active' => $newActive,
|
|
'message' => $newActive === 1 ? 'User activated successfully.' : 'User deactivated and active session revoked.',
|
|
'csrfName' => csrf_token(),
|
|
'csrfHash' => csrf_hash(),
|
|
]);
|
|
}
|
|
}
|