DSP Project first push, date: 29/01/2026
This commit is contained in:
386
classes/User.php
Normal file
386
classes/User.php
Normal file
@@ -0,0 +1,386 @@
|
||||
<?php
|
||||
|
||||
class User {
|
||||
private $pdo;
|
||||
|
||||
public function __construct(PDO $pdo) {
|
||||
$this->pdo = $pdo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a new user and their personal information in the database.
|
||||
* @param array $person_data An array of personal information.
|
||||
* @param array $user_data An array of user account data.
|
||||
* @return bool True on success, false on failure.
|
||||
*/
|
||||
public function registerUser($person_data, $user_data)
|
||||
{
|
||||
// Start a transaction to ensure both person and user data are saved or neither is
|
||||
$this->pdo->beginTransaction();
|
||||
|
||||
try {
|
||||
// Check for duplicate ID card, phone, or email before inserting
|
||||
$dupConditions = [];
|
||||
$dupParams = [];
|
||||
$duplicateLabels = [];
|
||||
if (!empty($person_data['id_card'])) {
|
||||
$dupConditions[] = "isp_idcard = :id_card";
|
||||
$dupParams[':id_card'] = $person_data['id_card'];
|
||||
$duplicateLabels[] = 'ID card';
|
||||
}
|
||||
if (!empty($person_data['phone_number'])) {
|
||||
$dupConditions[] = "isp_phone_number = :phone";
|
||||
$dupParams[':phone'] = $person_data['phone_number'];
|
||||
$duplicateLabels[] = 'phone number';
|
||||
}
|
||||
if (!empty($person_data['email'])) {
|
||||
$dupConditions[] = "isp_email = :email";
|
||||
$dupParams[':email'] = $person_data['email'];
|
||||
$duplicateLabels[] = 'email';
|
||||
}
|
||||
|
||||
if (!empty($dupConditions)) {
|
||||
$check_sql = "SELECT pkisp_id FROM ist_tbl_people WHERE " . implode(' OR ', $dupConditions);
|
||||
$check_stmt = $this->pdo->prepare($check_sql);
|
||||
$check_stmt->execute($dupParams);
|
||||
|
||||
if ($check_stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||
$this->pdo->rollBack();
|
||||
$duplicateMessage = 'information';
|
||||
if (!empty($duplicateLabels)) {
|
||||
if (count($duplicateLabels) === 1) {
|
||||
$duplicateMessage = $duplicateLabels[0];
|
||||
} elseif (count($duplicateLabels) === 2) {
|
||||
$duplicateMessage = implode(' or ', $duplicateLabels);
|
||||
} else {
|
||||
$last = array_pop($duplicateLabels);
|
||||
$duplicateMessage = implode(', ', $duplicateLabels) . ", or {$last}";
|
||||
}
|
||||
}
|
||||
set_message("A user with this {$duplicateMessage} already exists.", "danger");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for duplicate username
|
||||
$check_username_sql = "SELECT pkisu_id FROM ist_tbl_users WHERE isu_name = :username";
|
||||
$check_username_stmt = $this->pdo->prepare($check_username_sql);
|
||||
$check_username_stmt->execute([':username' => $user_data['username']]);
|
||||
|
||||
if ($check_username_stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||
$this->pdo->rollBack();
|
||||
set_message("This username is already taken. Please choose another one.", "danger");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 1. Insert into ist_tbl_people
|
||||
$person_sql = "
|
||||
INSERT INTO ist_tbl_people (
|
||||
isp_idcard, isp_firstname_en, isp_lastname_en, isp_sex,
|
||||
isp_dob, isp_pob, isp_nationality, isp_marital_status,
|
||||
isp_phone_number, isp_email, isp_telegram, isp_note
|
||||
) VALUES (
|
||||
:id_card, :first_name_en, :last_name_en, :sex,
|
||||
:dob, :pob, :nationality, :marital_status,
|
||||
:phone_number, :email, :telegram, :note
|
||||
)
|
||||
";
|
||||
$person_stmt = $this->pdo->prepare($person_sql);
|
||||
$person_stmt->execute([
|
||||
':id_card' => ($person_data['id_card'] ?? '') !== '' ? $person_data['id_card'] : null,
|
||||
':first_name_en' => $person_data['first_name_en'],
|
||||
':last_name_en' => $person_data['last_name_en'],
|
||||
':sex' => $person_data['sex'],
|
||||
':dob' => $person_data['dob'],
|
||||
':pob' => $person_data['pob'],
|
||||
':nationality' => $person_data['nationality'],
|
||||
':marital_status' => $person_data['marital_status'],
|
||||
':phone_number' => $person_data['phone_number'],
|
||||
':email' => $person_data['email'],
|
||||
':telegram' => $person_data['telegram'],
|
||||
':note' => $person_data['note']
|
||||
]);
|
||||
|
||||
// Get the ID of the newly inserted person record
|
||||
$person_id = $this->pdo->lastInsertId();
|
||||
|
||||
// 2. Insert into ist_tbl_users
|
||||
$user_sql = "
|
||||
INSERT INTO ist_tbl_users (
|
||||
fkisp_id_of, isu_name, isu_password, isu_status, isu_can_run_r
|
||||
) VALUES (
|
||||
:fkisp_id_of, :username, :password, :status, :can_run_r
|
||||
)
|
||||
";
|
||||
|
||||
$user_stmt = $this->pdo->prepare($user_sql);
|
||||
$user_stmt->execute([
|
||||
':fkisp_id_of' => $person_id,
|
||||
':username' => $user_data['username'],
|
||||
':password' => password_hash($user_data['password'], PASSWORD_DEFAULT), // Hash the password
|
||||
':status' => $user_data['status'],
|
||||
':can_run_r' => empty($user_data['can_run_r']) ? 0 : 1
|
||||
]);
|
||||
|
||||
// Commit the transaction
|
||||
$this->pdo->commit();
|
||||
return true;
|
||||
|
||||
} catch (PDOException $e) {
|
||||
// Roll back the transaction on any error
|
||||
$this->pdo->rollBack();
|
||||
// Log the detailed error
|
||||
error_log("Registration failed: " . $e->getMessage());
|
||||
set_message("Registration failed due to a database error. Please try again.", "danger");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticates a user based on username and password.
|
||||
*
|
||||
* @param string $username The user's username.
|
||||
* @param string $password The user's plain-text password.
|
||||
* @return array|false User data (pkisu_id, fkisp_id_of, isu_name, isu_status) on success, false on failure.
|
||||
* @throws Exception If a database error occurs.
|
||||
*/
|
||||
public function authenticateUser(string $username, string $password) {
|
||||
$sql = "SELECT pkisu_id, fkisp_id_of, isu_name, isu_password, isu_status, isu_can_run_r
|
||||
FROM ist_tbl_users
|
||||
WHERE isu_name = :username AND isu_status != 'Inactive'"; // Do not allow login for inactive users
|
||||
try {
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->bindParam(':username', $username);
|
||||
$stmt->execute();
|
||||
$user = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($user && password_verify($password, $user['isu_password'])) {
|
||||
// Remove password hash before returning user data
|
||||
unset($user['isu_password']);
|
||||
return $user;
|
||||
}
|
||||
return false;
|
||||
} catch (PDOException $e) {
|
||||
error_log("Error authenticating user: " . $e->getMessage());
|
||||
throw new Exception("Authentication failed due to a server error. Please try again later.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves full user details by user ID (pkisu_id).
|
||||
*
|
||||
* @param int $user_id The pkisu_id of the user.
|
||||
* @return array|false The combined user and person data, or false if not found.
|
||||
* @throws Exception If a database error occurs.
|
||||
*/
|
||||
public function getUserDetails(int $user_id) {
|
||||
$sql = "SELECT u.*, p.*
|
||||
FROM ist_tbl_users u
|
||||
JOIN ist_tbl_people p ON u.fkisp_id_of = p.pkisp_id
|
||||
WHERE u.pkisu_id = :user_id";
|
||||
try {
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->bindParam(':user_id', $user_id, PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
return $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $e) {
|
||||
error_log("Error fetching user details for ID ($user_id): " . $e->getMessage());
|
||||
throw new Exception("Could not retrieve user details. Please try again later.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all users, optionally filtered by status and/or search query.
|
||||
*
|
||||
* @param string|null $search_query Optional search term for username, first name, last name, email, phone.
|
||||
* @param string|null $status_filter Optional status to filter by (e.g., 'Data Owner').
|
||||
* @return array An array of user data.
|
||||
* @throws Exception If a database error occurs.
|
||||
*/
|
||||
public function getAllUsers(?string $search_query = null, ?string $status_filter = null): array {
|
||||
$sql = "SELECT u.pkisu_id, u.isu_name, u.isu_status, u.isu_reg_datetime, u.isu_mod_datetime,
|
||||
u.isu_can_run_r,
|
||||
p.isp_firstname_en, p.isp_lastname_en, p.isp_email, p.isp_phone_number
|
||||
FROM ist_tbl_users u
|
||||
JOIN ist_tbl_people p ON u.fkisp_id_of = p.pkisp_id";
|
||||
$conditions = [];
|
||||
$params = [];
|
||||
|
||||
if ($status_filter) {
|
||||
$conditions[] = "u.isu_status = :status_filter";
|
||||
$params[':status_filter'] = $status_filter;
|
||||
}
|
||||
|
||||
if ($search_query) {
|
||||
$search_term = '%' . $search_query . '%';
|
||||
$conditions[] = "(u.isu_name LIKE :search_query OR
|
||||
p.isp_firstname_en LIKE :search_query OR
|
||||
p.isp_lastname_en LIKE :search_query OR
|
||||
p.isp_email LIKE :search_query OR
|
||||
p.isp_phone_number LIKE :search_query)";
|
||||
$params[':search_query'] = $search_term;
|
||||
}
|
||||
|
||||
if (!empty($conditions)) {
|
||||
$sql .= " WHERE " . implode(" AND ", $conditions);
|
||||
}
|
||||
|
||||
$sql .= " ORDER BY u.isu_reg_datetime DESC";
|
||||
|
||||
try {
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
foreach ($params as $key => &$val) {
|
||||
// Use PDO::PARAM_STR for all search/filter parameters, as they are strings
|
||||
$stmt->bindParam($key, $val, PDO::PARAM_STR);
|
||||
}
|
||||
$stmt->execute();
|
||||
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $e) {
|
||||
error_log("Error fetching all users: " . $e->getMessage());
|
||||
throw new Exception("Could not retrieve user list. Please try again later.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the total count of registered users.
|
||||
*
|
||||
* @return int The total number of users.
|
||||
* @throws Exception If a database error occurs.
|
||||
*/
|
||||
public function getTotalUsers(): int {
|
||||
$sql = "SELECT COUNT(*) FROM ist_tbl_users";
|
||||
try {
|
||||
$stmt = $this->pdo->query($sql);
|
||||
return $stmt->fetchColumn();
|
||||
} catch (PDOException $e) {
|
||||
error_log("Error getting total users count: " . $e->getMessage());
|
||||
throw new Exception("Could not retrieve user count. Please try again later.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a user's status (role).
|
||||
*
|
||||
* @param int $user_id The ID of the user to update.
|
||||
* @param string $new_status The new status ('DAC Staff', 'Data Owner', 'Data User', 'Inactive').
|
||||
* @param int $mod_by The ID of the user performing the modification.
|
||||
* @return bool True on success.
|
||||
* @throws Exception If a database error occurs.
|
||||
*/
|
||||
public function updateUserStatus(int $user_id, string $new_status, int $mod_by): bool {
|
||||
$sql = "UPDATE ist_tbl_users
|
||||
SET isu_status = :new_status, isu_mod_datetime = CURRENT_TIMESTAMP, isu_regby_id = :mod_by
|
||||
WHERE pkisu_id = :user_id";
|
||||
try {
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->bindParam(':new_status', $new_status);
|
||||
$stmt->bindParam(':mod_by', $mod_by, PDO::PARAM_INT);
|
||||
$stmt->bindParam(':user_id', $user_id, PDO::PARAM_INT);
|
||||
return $stmt->execute();
|
||||
} catch (PDOException $e) {
|
||||
error_log("Error updating user status (ID: $user_id): " . $e->getMessage());
|
||||
throw new Exception("Could not update user status. Please try again later.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Grants or revokes R/Jupyter access for a user.
|
||||
*
|
||||
* @param int $user_id The pkisu_id of the user.
|
||||
* @param bool $can_run_r Whether the user should have access.
|
||||
* @param int $mod_by The ID of the admin performing the change.
|
||||
* @return bool True on success.
|
||||
* @throws Exception If a database error occurs.
|
||||
*/
|
||||
public function updateUserRJupyterAccess(int $user_id, bool $can_run_r, int $mod_by): bool {
|
||||
$sql = "UPDATE ist_tbl_users
|
||||
SET isu_can_run_r = :can_run_r, isu_mod_datetime = CURRENT_TIMESTAMP, isu_regby_id = :mod_by
|
||||
WHERE pkisu_id = :user_id";
|
||||
try {
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$flag = $can_run_r ? 1 : 0;
|
||||
$stmt->bindParam(':can_run_r', $flag, PDO::PARAM_INT);
|
||||
$stmt->bindParam(':mod_by', $mod_by, PDO::PARAM_INT);
|
||||
$stmt->bindParam(':user_id', $user_id, PDO::PARAM_INT);
|
||||
return $stmt->execute();
|
||||
} catch (PDOException $e) {
|
||||
error_log("Error updating R/Jupyter access (ID: $user_id): " . $e->getMessage());
|
||||
throw new Exception("Could not update R/Jupyter access. Please try again later.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a user's personal information.
|
||||
*
|
||||
* @param int $person_id The pkisp_id of the person to update.
|
||||
* @param array $person_data Associative array with fields to update (e.g., isp_firstname_en, isp_phone_number).
|
||||
* @param int $mod_by The ID of the user performing the modification.
|
||||
* @return bool True on success.
|
||||
* @throws Exception If a database error occurs or duplicate entry.
|
||||
*/
|
||||
public function updatePersonInfo(int $person_id, array $person_data, int $mod_by): bool {
|
||||
$setClauses = [];
|
||||
$params = [':person_id' => $person_id, ':mod_by' => $mod_by];
|
||||
|
||||
foreach ($person_data as $key => $value) {
|
||||
// Only allow specific fields to be updated
|
||||
if (in_array($key, [
|
||||
'isp_idcard', 'isp_firstname_en', 'isp_lastname_en', 'isp_sex', 'isp_dob',
|
||||
'isp_pob', 'isp_nationality', 'isp_marital_status', 'isp_phone_number',
|
||||
'isp_email', 'isp_telegram', 'isp_note'
|
||||
])) {
|
||||
$setClauses[] = "$key = :$key";
|
||||
$params[":$key"] = ($value === '' ? null : $value);
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($setClauses)) {
|
||||
return false; // No fields to update
|
||||
}
|
||||
|
||||
$sql = "UPDATE ist_tbl_people
|
||||
SET " . implode(', ', $setClauses) . ", isp_mod_datetime = CURRENT_TIMESTAMP, isp_regby_id = :mod_by
|
||||
WHERE pkisp_id = :person_id";
|
||||
try {
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
return $stmt->rowCount() > 0;
|
||||
} catch (PDOException $e) {
|
||||
if ($e->getCode() == '23000') {
|
||||
throw new Exception("A duplicate entry was found for ID card, email, or phone number.");
|
||||
}
|
||||
error_log("Error updating person info (ID: $person_id): " . $e->getMessage());
|
||||
throw new Exception("Could not update personal information. Please try again later.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes a user's password.
|
||||
*
|
||||
* @param int $user_id The pkisu_id of the user.
|
||||
* @param string $new_password The new plain-text password.
|
||||
* @param int $mod_by The ID of the user performing the modification.
|
||||
* @return bool True on success.
|
||||
* @throws Exception If password hashing fails or database error.
|
||||
*/
|
||||
public function changePassword(int $user_id, string $new_password, int $mod_by): bool {
|
||||
$hashed_password = password_hash($new_password, PASSWORD_DEFAULT);
|
||||
if ($hashed_password === false) {
|
||||
throw new Exception("Failed to hash new password.");
|
||||
}
|
||||
|
||||
$sql = "UPDATE ist_tbl_users
|
||||
SET isu_password = :hashed_password, isu_mod_datetime = CURRENT_TIMESTAMP, isu_regby_id = :mod_by
|
||||
WHERE pkisu_id = :user_id";
|
||||
try {
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->bindParam(':hashed_password', $hashed_password);
|
||||
$stmt->bindParam(':mod_by', $mod_by, PDO::PARAM_INT);
|
||||
$stmt->bindParam(':user_id', $user_id, PDO::PARAM_INT);
|
||||
return $stmt->execute();
|
||||
} catch (PDOException $e) {
|
||||
error_log("Error changing password for user (ID: $user_id): " . $e->getMessage());
|
||||
throw new Exception("Could not change password. Please try again later.");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user