hasRole('Admin') && !$user->hasRole('SuperUser')) { header('Location: import_dashboard.php'); exit; } $db = DBHandlerSelect::getInstance(); $pdo = $db->getConnection(); /* * Default values for new users. */ $defaultStatus = 'Active'; /* * Avatar upload directory. * This path assumes this page is inside /public and avatars are stored in /upload/users. */ $avatarUploadDir = dirname(__DIR__) . '/upload/users/'; $avatarPublicPath = '../upload/users/'; if (!is_dir($avatarUploadDir)) { mkdir($avatarUploadDir, 0755, true); } /* * Create CSRF token. */ if (empty($_SESSION['user_admin_csrf'])) { $_SESSION['user_admin_csrf'] = bin2hex(random_bytes(32)); } $csrfToken = $_SESSION['user_admin_csrf']; function cleanInput($value) { return trim((string)($value ?? '')); } function redirectWithMessage($type, $message) { $_SESSION['user_admin_flash'] = [ 'type' => $type, 'message' => $message ]; header('Location: ' . $_SERVER['PHP_SELF']); exit; } function getEnvValue($key, $default = '') { if (isset($_ENV[$key]) && $_ENV[$key] !== '') { return $_ENV[$key]; } $value = getenv($key); if ($value !== false && $value !== '') { return $value; } return $default; } function uploadAvatarIfPresent($inputName, $avatarUploadDir) { if ( empty($_FILES[$inputName]) || empty($_FILES[$inputName]['name']) || $_FILES[$inputName]['error'] === UPLOAD_ERR_NO_FILE ) { return null; } if ($_FILES[$inputName]['error'] !== UPLOAD_ERR_OK) { throw new Exception('Avatar upload failed.'); } $maxSize = 3 * 1024 * 1024; if ($_FILES[$inputName]['size'] > $maxSize) { throw new Exception('Avatar is too large. Maximum size is 3 MB.'); } $allowedMimeTypes = [ 'image/jpeg' => 'jpg', 'image/png' => 'png', 'image/webp' => 'webp', 'image/gif' => 'gif' ]; $finfo = new finfo(FILEINFO_MIME_TYPE); $mimeType = $finfo->file($_FILES[$inputName]['tmp_name']); if (!isset($allowedMimeTypes[$mimeType])) { throw new Exception('Invalid avatar format. Allowed formats: JPG, PNG, WEBP, GIF.'); } $extension = $allowedMimeTypes[$mimeType]; $filename = 'user_' . time() . '_' . bin2hex(random_bytes(6)) . '.' . $extension; $destination = rtrim($avatarUploadDir, '/') . '/' . $filename; if (!move_uploaded_file($_FILES[$inputName]['tmp_name'], $destination)) { throw new Exception('Unable to save avatar.'); } return $filename; } function isRoleAllowedForManagement(PDO $pdo, $roleId) { /* * Admin role cannot be assigned from this page. */ $stmt = $pdo->prepare(" SELECT id FROM auth_roles WHERE id = :id AND name <> 'Admin' LIMIT 1 "); $stmt->execute([':id' => (int)$roleId]); return (bool)$stmt->fetch(PDO::FETCH_ASSOC); } function isUserAdmin(PDO $pdo, $userId) { $stmt = $pdo->prepare(" SELECT u.id FROM auth_users u INNER JOIN auth_roles r ON r.id = u.role_id WHERE u.id = :id AND r.name = 'Admin' LIMIT 1 "); $stmt->execute([':id' => (int)$userId]); return (bool)$stmt->fetch(PDO::FETCH_ASSOC); } function smtpReadLine($socket) { $response = ''; while ($line = fgets($socket, 515)) { $response .= $line; if (isset($line[3]) && $line[3] === ' ') { break; } } return $response; } function smtpCommand($socket, $command, array $expectedCodes) { if ($command !== null) { fwrite($socket, $command . "\r\n"); } $response = smtpReadLine($socket); $code = (int)substr($response, 0, 3); if (!in_array($code, $expectedCodes, true)) { throw new Exception('SMTP error after command [' . $command . ']: ' . trim($response)); } return $response; } function buildSmartTrfWelcomeEmailHtml($firstName, $lastName, $email, $temporaryPassword, $loginUrl) { $fullName = trim($firstName . ' ' . $lastName); $safeFullName = htmlspecialchars($fullName, ENT_QUOTES, 'UTF-8'); $safeEmail = htmlspecialchars($email, ENT_QUOTES, 'UTF-8'); $safePassword = htmlspecialchars($temporaryPassword, ENT_QUOTES, 'UTF-8'); $safeLoginUrl = htmlspecialchars($loginUrl, ENT_QUOTES, 'UTF-8'); return ' Welcome to SmartTRF
SmartTRF
Your digital workspace for sample and TRF management

Welcome, ' . $safeFullName . '

Your SmartTRF account has been created. You can now access the platform using the credentials below.

Access credentials
Email ' . $safeEmail . '
Temporary password ' . $safePassword . '
Security recommendation
This is a temporary password. For security reasons, please change it after your first login.
Access SmartTRF
If the button does not work, copy and paste this link into your browser:
' . $safeLoginUrl . '
This message was automatically generated by SmartTRF. Please do not reply to this email.
SmartTRF ยท C.E. Soft
'; } function buildSmartTrfWelcomeEmailText($firstName, $lastName, $email, $temporaryPassword, $loginUrl) { $fullName = trim($firstName . ' ' . $lastName); return "Welcome to SmartTRF\n\n" . "Hello " . $fullName . ",\n\n" . "Your SmartTRF account has been created.\n\n" . "Access credentials:\n" . "Email: " . $email . "\n" . "Temporary password: " . $temporaryPassword . "\n\n" . "For security reasons, please change this temporary password after your first login.\n\n" . "Login URL:\n" . $loginUrl . "\n\n" . "This message was automatically generated by SmartTRF."; } function sendSmartTrfWelcomeEmail($toEmail, $firstName, $lastName, $temporaryPassword) { $smtpHost = getEnvValue('MAIL_HOST'); $smtpPort = (int)getEnvValue('MAIL_PORT', 465); $smtpUsername = getEnvValue('MAIL_USERNAME'); $smtpPassword = getEnvValue('MAIL_PASSWORD'); $smtpEncryption = strtolower(getEnvValue('MAIL_ENCRYPTION', 'ssl')); $fromAddress = getEnvValue('MAIL_FROM_ADDRESS', $smtpUsername); $fromName = getEnvValue('MAIL_FROM_NAME', 'SmartTRF'); $loginUrl = getEnvValue('APP_URL', 'https://trf.cesoft.io/public/login'); if ($smtpHost === '' || $smtpUsername === '' || $smtpPassword === '' || $fromAddress === '') { throw new Exception('SMTP configuration is incomplete.'); } $subject = 'Welcome to SmartTRF - Your account has been created'; $htmlBody = buildSmartTrfWelcomeEmailHtml($firstName, $lastName, $toEmail, $temporaryPassword, $loginUrl); $textBody = buildSmartTrfWelcomeEmailText($firstName, $lastName, $toEmail, $temporaryPassword, $loginUrl); $boundary = 'smarttrf_' . bin2hex(random_bytes(12)); $encodedSubject = '=?UTF-8?B?' . base64_encode($subject) . '?='; $encodedFromName = '=?UTF-8?B?' . base64_encode($fromName) . '?='; $headers = []; $headers[] = 'From: ' . $encodedFromName . ' <' . $fromAddress . '>'; $headers[] = 'To: <' . $toEmail . '>'; $headers[] = 'Subject: ' . $encodedSubject; $headers[] = 'MIME-Version: 1.0'; $headers[] = 'Content-Type: multipart/alternative; boundary="' . $boundary . '"'; $headers[] = 'Date: ' . date('r'); $headers[] = 'Message-ID: <' . bin2hex(random_bytes(16)) . '@smarttrf>'; $message = ''; $message .= '--' . $boundary . "\r\n"; $message .= "Content-Type: text/plain; charset=UTF-8\r\n"; $message .= "Content-Transfer-Encoding: 8bit\r\n\r\n"; $message .= $textBody . "\r\n\r\n"; $message .= '--' . $boundary . "\r\n"; $message .= "Content-Type: text/html; charset=UTF-8\r\n"; $message .= "Content-Transfer-Encoding: 8bit\r\n\r\n"; $message .= $htmlBody . "\r\n\r\n"; $message .= '--' . $boundary . "--\r\n"; $smtpMessage = implode("\r\n", $headers) . "\r\n\r\n" . $message; $transportHost = ($smtpEncryption === 'ssl' ? 'ssl://' : '') . $smtpHost; $socket = stream_socket_client( $transportHost . ':' . $smtpPort, $errno, $errstr, 30, STREAM_CLIENT_CONNECT ); if (!$socket) { throw new Exception('SMTP connection failed: ' . $errstr . ' (' . $errno . ')'); } stream_set_timeout($socket, 30); try { smtpCommand($socket, null, [220]); smtpCommand($socket, 'EHLO ' . ($_SERVER['SERVER_NAME'] ?? 'localhost'), [250]); if ($smtpEncryption === 'tls') { smtpCommand($socket, 'STARTTLS', [220]); if (!stream_socket_enable_crypto($socket, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) { throw new Exception('Unable to enable TLS encryption.'); } smtpCommand($socket, 'EHLO ' . ($_SERVER['SERVER_NAME'] ?? 'localhost'), [250]); } smtpCommand($socket, 'AUTH LOGIN', [334]); smtpCommand($socket, base64_encode($smtpUsername), [334]); smtpCommand($socket, base64_encode($smtpPassword), [235]); smtpCommand($socket, 'MAIL FROM:<' . $fromAddress . '>', [250]); smtpCommand($socket, 'RCPT TO:<' . $toEmail . '>', [250, 251]); smtpCommand($socket, 'DATA', [354]); fwrite($socket, $smtpMessage . "\r\n.\r\n"); smtpCommand($socket, null, [250]); smtpCommand($socket, 'QUIT', [221]); } finally { fclose($socket); } return true; } /* * Load roles available for creation/edit. * Admin is intentionally excluded. */ $stmtRoles = $pdo->query(" SELECT id, name, display_name FROM auth_roles WHERE name <> 'Admin' ORDER BY display_name ASC, name ASC "); $manageableRoles = $stmtRoles->fetchAll(PDO::FETCH_ASSOC); if (empty($manageableRoles)) { redirectWithMessage('error', 'No manageable roles found. Please create at least one non-admin role.'); } $defaultRoleId = (int)$manageableRoles[0]['id']; /* * Handle form actions. */ if ($_SERVER['REQUEST_METHOD'] === 'POST') { try { $postedToken = $_POST['csrf_token'] ?? ''; if (!hash_equals($csrfToken, $postedToken)) { throw new Exception('Invalid security token.'); } $action = cleanInput($_POST['action'] ?? ''); if ($action === 'create') { $firstName = cleanInput($_POST['first_name'] ?? ''); $lastName = cleanInput($_POST['last_name'] ?? ''); $email = cleanInput($_POST['email'] ?? ''); $password = (string)($_POST['password'] ?? ''); $roleId = (int)($_POST['role_id'] ?? 0); $limsUserId = cleanInput($_POST['lims_user_id'] ?? ''); $limsGlobalUserId = cleanInput($_POST['lims_global_user_id'] ?? ''); if ($firstName === '' || $lastName === '' || $email === '' || $password === '' || $roleId <= 0) { throw new Exception('Please fill all required fields.'); } if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { throw new Exception('Invalid email address.'); } if (!isRoleAllowedForManagement($pdo, $roleId)) { throw new Exception('You cannot assign the Admin role from this page.'); } $stmt = $pdo->prepare("SELECT id FROM auth_users WHERE email = :email LIMIT 1"); $stmt->execute([':email' => $email]); if ($stmt->fetch()) { throw new Exception('This email already exists.'); } $avatarFilename = uploadAvatarIfPresent('avatar', $avatarUploadDir); $passwordHash = password_hash($password, PASSWORD_DEFAULT); $sql = " INSERT INTO auth_users ( email, username, password, first_name, last_name, avatar, role_id, status, lims_user_id, lims_global_user_id, created_at, updated_at ) VALUES ( :email, NULL, :password, :first_name, :last_name, :avatar, :role_id, :status, :lims_user_id, :lims_global_user_id, NOW(), NOW() ) "; $stmt = $pdo->prepare($sql); $stmt->execute([ ':email' => $email, ':password' => $passwordHash, ':first_name' => $firstName, ':last_name' => $lastName, ':avatar' => $avatarFilename, ':role_id' => $roleId, ':status' => $defaultStatus, ':lims_user_id' => ($limsUserId !== '' ? (int)$limsUserId : null), ':lims_global_user_id' => ($limsGlobalUserId !== '' ? (int)$limsGlobalUserId : null) ]); try { sendSmartTrfWelcomeEmail($email, $firstName, $lastName, $password); redirectWithMessage('success', 'User created successfully. Welcome email sent.'); } catch (Exception $mailException) { error_log('SmartTRF welcome email error: ' . $mailException->getMessage()); redirectWithMessage('warning', 'User created successfully, but the welcome email was not sent: ' . $mailException->getMessage()); } } if ($action === 'update') { $userId = (int)($_POST['user_id'] ?? 0); $firstName = cleanInput($_POST['first_name'] ?? ''); $lastName = cleanInput($_POST['last_name'] ?? ''); $email = cleanInput($_POST['email'] ?? ''); $password = (string)($_POST['password'] ?? ''); $roleId = (int)($_POST['role_id'] ?? 0); $limsUserId = cleanInput($_POST['lims_user_id'] ?? ''); $limsGlobalUserId = cleanInput($_POST['lims_global_user_id'] ?? ''); if ($userId <= 0) { throw new Exception('Invalid user ID.'); } if (isUserAdmin($pdo, $userId)) { throw new Exception('Admin users can be viewed but cannot be edited from this page.'); } if ($firstName === '' || $lastName === '' || $email === '' || $roleId <= 0) { throw new Exception('Please fill all required fields.'); } if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { throw new Exception('Invalid email address.'); } if (!isRoleAllowedForManagement($pdo, $roleId)) { throw new Exception('You cannot assign the Admin role from this page.'); } $stmt = $pdo->prepare("SELECT id FROM auth_users WHERE email = :email AND id <> :id LIMIT 1"); $stmt->execute([ ':email' => $email, ':id' => $userId ]); if ($stmt->fetch()) { throw new Exception('This email is already used by another user.'); } $avatarFilename = uploadAvatarIfPresent('avatar', $avatarUploadDir); $fields = [ 'email = :email', 'first_name = :first_name', 'last_name = :last_name', 'role_id = :role_id', 'lims_user_id = :lims_user_id', 'lims_global_user_id = :lims_global_user_id', 'updated_at = NOW()' ]; $params = [ ':email' => $email, ':first_name' => $firstName, ':last_name' => $lastName, ':role_id' => $roleId, ':lims_user_id' => ($limsUserId !== '' ? (int)$limsUserId : null), ':lims_global_user_id' => ($limsGlobalUserId !== '' ? (int)$limsGlobalUserId : null), ':id' => $userId ]; if ($avatarFilename !== null) { $fields[] = 'avatar = :avatar'; $params[':avatar'] = $avatarFilename; } if ($password !== '') { $fields[] = 'password = :password'; $params[':password'] = password_hash($password, PASSWORD_DEFAULT); } $sql = "UPDATE auth_users SET " . implode(', ', $fields) . " WHERE id = :id"; $stmt = $pdo->prepare($sql); $stmt->execute($params); redirectWithMessage('success', 'User updated successfully.'); } if ($action === 'delete') { $userId = (int)($_POST['user_id'] ?? 0); if ($userId <= 0) { throw new Exception('Invalid user ID.'); } if (isset($iduserlogin) && (int)$iduserlogin === $userId) { throw new Exception('You cannot delete your own user.'); } if (isUserAdmin($pdo, $userId)) { throw new Exception('Admin users can be viewed but cannot be deleted from this page.'); } $stmt = $pdo->prepare("DELETE FROM auth_users WHERE id = :id"); $stmt->execute([':id' => $userId]); redirectWithMessage('success', 'User deleted successfully.'); } throw new Exception('Invalid action.'); } catch (Exception $e) { redirectWithMessage('error', $e->getMessage()); } } /* * Load users with role information. */ $stmt = $pdo->query(" SELECT u.id, u.first_name, u.last_name, u.email, u.avatar, u.status, u.role_id, u.lims_user_id, u.lims_global_user_id, u.created_at, u.updated_at, r.name AS role_name, r.display_name AS role_display_name FROM auth_users u LEFT JOIN auth_roles r ON r.id = u.role_id ORDER BY u.first_name ASC, u.last_name ASC, u.email ASC "); $users = $stmt->fetchAll(PDO::FETCH_ASSOC); $flash = $_SESSION['user_admin_flash'] ?? null; unset($_SESSION['user_admin_flash']); ?> User Administration - <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?>
User Administration
Create, edit, change password and delete system users.
Avatar First Name Last Name Email Role Accettatore LIMS Global Status Actions
Avatar
Loading LIMS users and acceptors...