DSP Project first push, date: 29/01/2026
This commit is contained in:
127
classes/Aboutus.php
Normal file
127
classes/Aboutus.php
Normal file
@@ -0,0 +1,127 @@
|
||||
<?php
|
||||
|
||||
class Aboutus {
|
||||
private $pdo;
|
||||
|
||||
public function __construct(PDO $pdo) {
|
||||
$this->pdo = $pdo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new "About Us" entry to the database.
|
||||
*
|
||||
* @param string $title_en The English title (e.g., Vision, Mission, Goal).
|
||||
* @param string $description The detailed description.
|
||||
* @param int $reg_by The ID of the user who registered this entry (from ist_tbl_users).
|
||||
* @param int $fkisp_id_of The ID of the person associated with this entry (from ist_tbl_people).
|
||||
* @return bool True on success.
|
||||
* @throws Exception If a database error occurs or title already exists.
|
||||
*/
|
||||
public function addAboutUs(string $title_en, string $description, int $reg_by, int $fkisp_id_of): bool {
|
||||
$sql = "INSERT INTO dsps_tbl_dspsabout (dspsabout_title_en, dspsabout_description, dspsabout_reg_by, fkisp_id_of)
|
||||
VALUES (:title_en, :description, :reg_by, :fkisp_id_of)";
|
||||
try {
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->bindParam(':title_en', $title_en);
|
||||
$stmt->bindParam(':description', $description);
|
||||
$stmt->bindParam(':reg_by', $reg_by);
|
||||
$stmt->bindParam(':fkisp_id_of', $fkisp_id_of);
|
||||
return $stmt->execute();
|
||||
} catch (PDOException $e) {
|
||||
if ($e->getCode() == '23000') { // Integrity constraint violation (e.g., duplicate title if UNIQUE)
|
||||
throw new Exception("An 'About Us' entry with this title already exists.");
|
||||
}
|
||||
error_log("Error adding About Us entry: " . $e->getMessage());
|
||||
throw new Exception("Could not add About Us entry. Please try again later.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an existing "About Us" entry.
|
||||
*
|
||||
* @param int $id The ID of the entry to update.
|
||||
* @param string $title_en The new English title.
|
||||
* @param string $description The new description.
|
||||
* @param int $mod_by The ID of the user who modified this entry.
|
||||
* @param int $fkisp_id_of The ID of the person associated with this entry.
|
||||
* @return bool True on success.
|
||||
* @throws Exception If a database error occurs or title already exists.
|
||||
*/
|
||||
public function updateAboutUs(int $id, string $title_en, string $description, int $mod_by, int $fkisp_id_of): bool {
|
||||
$sql = "UPDATE dsps_tbl_dspsabout
|
||||
SET dspsabout_title_en = :title_en, dspsabout_description = :description,
|
||||
dspsabout_mod_datetime = CURRENT_TIMESTAMP, dspsabout_reg_by = :mod_by, fkisp_id_of = :fkisp_id_of
|
||||
WHERE pkdspsabout_id = :id";
|
||||
try {
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->bindParam(':title_en', $title_en);
|
||||
$stmt->bindParam(':description', $description);
|
||||
$stmt->bindParam(':mod_by', $mod_by);
|
||||
$stmt->bindParam(':fkisp_id_of', $fkisp_id_of);
|
||||
$stmt->bindParam(':id', $id);
|
||||
return $stmt->execute();
|
||||
} catch (PDOException $e) {
|
||||
if ($e->getCode() == '23000') {
|
||||
throw new Exception("An 'About Us' entry with this title already exists.");
|
||||
}
|
||||
error_log("Error updating About Us entry (ID: $id): " . $e->getMessage());
|
||||
throw new Exception("Could not update About Us entry. Please try again later.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes an "About Us" entry.
|
||||
*
|
||||
* @param int $id The ID of the entry to delete.
|
||||
* @return bool True on success.
|
||||
* @throws Exception If a database error occurs.
|
||||
*/
|
||||
public function deleteAboutUs(int $id): bool {
|
||||
$sql = "DELETE FROM dsps_tbl_dspsabout WHERE pkdspsabout_id = :id";
|
||||
try {
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->bindParam(':id', $id);
|
||||
return $stmt->execute();
|
||||
} catch (PDOException $e) {
|
||||
error_log("Error deleting About Us entry (ID: $id): " . $e->getMessage());
|
||||
throw new Exception("Could not delete About Us entry. Please try again later.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a single "About Us" entry by its ID.
|
||||
*
|
||||
* @param int $id The ID of the entry.
|
||||
* @return array|false The entry data as an associative array, or false if not found.
|
||||
* @throws Exception If a database error occurs.
|
||||
*/
|
||||
public function getAboutUsById(int $id) {
|
||||
$sql = "SELECT * FROM dsps_tbl_dspsabout WHERE pkdspsabout_id = :id";
|
||||
try {
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->bindParam(':id', $id);
|
||||
$stmt->execute();
|
||||
return $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $e) {
|
||||
error_log("Error fetching About Us entry by ID ($id): " . $e->getMessage());
|
||||
throw new Exception("Could not retrieve About Us entry. Please try again later.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all "About Us" entries.
|
||||
*
|
||||
* @return array An array of "About Us" entry data.
|
||||
* @throws Exception If a database error occurs.
|
||||
*/
|
||||
public function getAllAboutUs(): array {
|
||||
$sql = "SELECT * FROM dsps_tbl_dspsabout ORDER BY dspsabout_reg_datetime ASC";
|
||||
try {
|
||||
$stmt = $this->pdo->query($sql);
|
||||
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $e) {
|
||||
error_log("Error fetching all About Us entries: " . $e->getMessage());
|
||||
throw new Exception("Could not retrieve About Us entries. Please try again later.");
|
||||
}
|
||||
}
|
||||
}
|
||||
213
classes/Announcement.php
Normal file
213
classes/Announcement.php
Normal file
@@ -0,0 +1,213 @@
|
||||
<?php
|
||||
|
||||
class Announcement {
|
||||
private $pdo;
|
||||
private string $uploadDir;
|
||||
|
||||
public function __construct(PDO $pdo) {
|
||||
$this->pdo = $pdo;
|
||||
$this->uploadDir = __DIR__ . '/../uploads/announcements/';
|
||||
// Ensure upload directory exists
|
||||
if (!is_dir($this->uploadDir) && !mkdir($this->uploadDir, 0775, true) && !is_dir($this->uploadDir)) {
|
||||
throw new RuntimeException('Unable to create announcements upload directory.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new announcement to the database.
|
||||
*
|
||||
* @param string $title The title of the announcement.
|
||||
* @param string $description The full description of the announcement.
|
||||
* @param string|null $photopath The filename of the uploaded photo, or null if no photo.
|
||||
* @param string $status The status of the announcement (e.g., 'Draft', 'Published', 'Archived').
|
||||
* @param int $reg_by The ID of the user who registered the announcement.
|
||||
* @return bool True on success, false on failure.
|
||||
* @throws Exception If a database error occurs.
|
||||
*/
|
||||
public function addAnnouncement(string $title, string $description, ?string $photopath, string $status, int $reg_by): bool {
|
||||
$sql = "INSERT INTO dsps_tbl_announcement (dspsann_title, dspsann_description, dspsann_photopath, dspsann_status, dspsann_reg_by)
|
||||
VALUES (:title, :description, :photopath, :status, :reg_by)";
|
||||
try {
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->bindParam(':title', $title);
|
||||
$stmt->bindParam(':description', $description);
|
||||
$stmt->bindParam(':photopath', $photopath);
|
||||
$stmt->bindParam(':status', $status);
|
||||
$stmt->bindParam(':reg_by', $reg_by);
|
||||
return $stmt->execute();
|
||||
} catch (PDOException $e) {
|
||||
error_log("Error adding announcement: " . $e->getMessage());
|
||||
throw new Exception("Could not add announcement. Please try again later.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an existing announcement in the database.
|
||||
*
|
||||
* @param int $id The ID of the announcement to update.
|
||||
* @param string $title The new title.
|
||||
* @param string $description The new description.
|
||||
* @param string|null $photopath The new filename of the photo, or null.
|
||||
* @param string $status The new status.
|
||||
* @param int $mod_by The ID of the user who modified the announcement.
|
||||
* @return bool True on success, false on failure.
|
||||
* @throws Exception If a database error occurs.
|
||||
*/
|
||||
public function updateAnnouncement(int $id, string $title, string $description, ?string $photopath, string $status, int $mod_by): bool {
|
||||
$sql = "UPDATE dsps_tbl_announcement
|
||||
SET dspsann_title = :title, dspsann_description = :description, dspsann_photopath = :photopath,
|
||||
dspsann_status = :status, dspsann_mod_datetime = CURRENT_TIMESTAMP, dspsann_reg_by = :mod_by
|
||||
WHERE pkdspsann_id = :id";
|
||||
try {
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->bindParam(':title', $title);
|
||||
$stmt->bindParam(':description', $description);
|
||||
$stmt->bindParam(':photopath', $photopath);
|
||||
$stmt->bindParam(':status', $status);
|
||||
$stmt->bindParam(':mod_by', $mod_by);
|
||||
$stmt->bindParam(':id', $id);
|
||||
return $stmt->execute();
|
||||
} catch (PDOException $e) {
|
||||
error_log("Error updating announcement (ID: $id): " . $e->getMessage());
|
||||
throw new Exception("Could not update announcement. Please try again later.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes an announcement from the database and its associated photo file.
|
||||
*
|
||||
* @param int $id The ID of the announcement to delete.
|
||||
* @return bool True on success, false on failure.
|
||||
* @throws Exception If a database error occurs.
|
||||
*/
|
||||
public function deleteAnnouncement(int $id): bool {
|
||||
// First, get the photo path to delete the file
|
||||
$announcement = $this->getAnnouncementById($id);
|
||||
if ($announcement && !empty($announcement['dspsann_photopath'])) {
|
||||
$filePath = $this->uploadDir . $announcement['dspsann_photopath'];
|
||||
if (file_exists($filePath)) {
|
||||
unlink($filePath); // Delete the file
|
||||
}
|
||||
}
|
||||
|
||||
$sql = "DELETE FROM dsps_tbl_announcement WHERE pkdspsann_id = :id";
|
||||
try {
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->bindParam(':id', $id);
|
||||
return $stmt->execute();
|
||||
} catch (PDOException $e) {
|
||||
error_log("Error deleting announcement (ID: $id): " . $e->getMessage());
|
||||
throw new Exception("Could not delete announcement. Please try again later.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a single announcement by its ID.
|
||||
*
|
||||
* @param int $id The ID of the announcement.
|
||||
* @return array|false The announcement data as an associative array, or false if not found.
|
||||
* @throws Exception If a database error occurs.
|
||||
*/
|
||||
public function getAnnouncementById(int $id) {
|
||||
$sql = "SELECT * FROM dsps_tbl_announcement WHERE pkdspsann_id = :id";
|
||||
try {
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->bindParam(':id', $id);
|
||||
$stmt->execute();
|
||||
return $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $e) {
|
||||
error_log("Error fetching announcement by ID ($id): " . $e->getMessage());
|
||||
throw new Exception("Could not retrieve announcement. Please try again later.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all announcements, optionally filtered by status.
|
||||
*
|
||||
* @param string|null $status Optional status to filter by (e.g., 'Published').
|
||||
* @param int|null $limit Optional limit for the number of results.
|
||||
* @return array An array of announcement data.
|
||||
* @throws Exception If a database error occurs.
|
||||
*/
|
||||
public function getAllAnnouncements(?string $status = null, ?int $limit = null): array {
|
||||
$sql = "SELECT * FROM dsps_tbl_announcement";
|
||||
$conditions = [];
|
||||
$params = [];
|
||||
|
||||
if ($status) {
|
||||
$conditions[] = "dspsann_status = :status";
|
||||
$params[':status'] = $status;
|
||||
}
|
||||
|
||||
if (!empty($conditions)) {
|
||||
$sql .= " WHERE " . implode(" AND ", $conditions);
|
||||
}
|
||||
|
||||
$sql .= " ORDER BY dspsann_reg_datetime DESC";
|
||||
|
||||
if ($limit) {
|
||||
$sql .= " LIMIT :limit";
|
||||
$params[':limit'] = $limit;
|
||||
}
|
||||
|
||||
try {
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
foreach ($params as $key => &$val) {
|
||||
$stmt->bindParam($key, $val, is_int($val) ? PDO::PARAM_INT : PDO::PARAM_STR);
|
||||
}
|
||||
$stmt->execute();
|
||||
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $e) {
|
||||
error_log("Error fetching all announcements: " . $e->getMessage());
|
||||
throw new Exception("Could not retrieve announcements. Please try again later.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the total count of announcements.
|
||||
*
|
||||
* @return int The total number of announcements.
|
||||
* @throws Exception If a database error occurs.
|
||||
*/
|
||||
public function getTotalAnnouncements(): int {
|
||||
$sql = "SELECT COUNT(*) FROM dsps_tbl_announcement";
|
||||
try {
|
||||
$stmt = $this->pdo->query($sql);
|
||||
return $stmt->fetchColumn();
|
||||
} catch (PDOException $e) {
|
||||
error_log("Error getting total announcements count: " . $e->getMessage());
|
||||
throw new Exception("Could not retrieve announcement count. Please try again later.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the upload of an announcement photo.
|
||||
*
|
||||
* @param array $file The $_FILES array for the uploaded photo.
|
||||
* @return string The unique filename of the uploaded photo.
|
||||
* @throws Exception If the upload fails or file type is invalid.
|
||||
*/
|
||||
public function handlePhotoUpload(array $file): string {
|
||||
if ($file['error'] !== UPLOAD_ERR_OK) {
|
||||
throw new Exception('File upload error: ' . $file['error']);
|
||||
}
|
||||
|
||||
$allowedTypes = ['image/jpeg', 'image/png', 'image/gif'];
|
||||
$finfo = new finfo(FILEINFO_MIME_TYPE);
|
||||
$mimeType = $finfo->file($file['tmp_name']);
|
||||
|
||||
if (!in_array($mimeType, $allowedTypes)) {
|
||||
throw new Exception('Invalid file type. Only JPEG, PNG, and GIF images are allowed.');
|
||||
}
|
||||
|
||||
$extension = pathinfo($file['name'], PATHINFO_EXTENSION);
|
||||
$uniqueFilename = uniqid('announcement_') . '.' . $extension;
|
||||
$destination = $this->uploadDir . $uniqueFilename;
|
||||
|
||||
if (!move_uploaded_file($file['tmp_name'], $destination)) {
|
||||
throw new Exception('Failed to move uploaded file.');
|
||||
}
|
||||
|
||||
return $uniqueFilename;
|
||||
}
|
||||
}
|
||||
293
classes/Classifications.php
Normal file
293
classes/Classifications.php
Normal file
@@ -0,0 +1,293 @@
|
||||
<?php
|
||||
|
||||
class Classifications {
|
||||
private $pdo;
|
||||
|
||||
public function __construct(PDO $pdo) {
|
||||
$this->pdo = $pdo;
|
||||
}
|
||||
|
||||
// --- Data Type Management (dsps_tbl_typedatasource) ---
|
||||
|
||||
/**
|
||||
* Adds a new data type.
|
||||
*
|
||||
* @param string $name_en English name of the data type.
|
||||
* @param string|null $name_kh Khmer name of the data type.
|
||||
* @param int $reg_by User ID who registered it.
|
||||
* @return bool True on success.
|
||||
* @throws Exception If a database error occurs or name already exists.
|
||||
*/
|
||||
public function addDataType(string $name_en, ?string $name_kh, int $reg_by): bool {
|
||||
$sql = "INSERT INTO dsps_tbl_typedatasource (dspstds_name_en, dspstds_name_kh, dspstds_reg_by)
|
||||
VALUES (:name_en, :name_kh, :reg_by)";
|
||||
try {
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->bindParam(':name_en', $name_en);
|
||||
$stmt->bindParam(':name_kh', $name_kh);
|
||||
$stmt->bindParam(':reg_by', $reg_by);
|
||||
return $stmt->execute();
|
||||
} catch (PDOException $e) {
|
||||
if ($e->getCode() == '23000') { // Integrity constraint violation (duplicate entry)
|
||||
throw new Exception("Data Type with this English name already exists.");
|
||||
}
|
||||
error_log("Error adding data type: " . $e->getMessage());
|
||||
throw new Exception("Could not add data type. Please try again later.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an existing data type.
|
||||
*
|
||||
* @param int $id ID of the data type to update.
|
||||
* @param string $name_en New English name.
|
||||
* @param string|null $name_kh New Khmer name.
|
||||
* @param int $mod_by User ID who modified it.
|
||||
* @return bool True on success.
|
||||
* @throws Exception If a database error occurs or name already exists.
|
||||
*/
|
||||
public function updateDataType(int $id, string $name_en, ?string $name_kh, int $mod_by): bool {
|
||||
$sql = "UPDATE dsps_tbl_typedatasource
|
||||
SET dspstds_name_en = :name_en, dspstds_name_kh = :name_kh,
|
||||
dspstds_mod_datetime = CURRENT_TIMESTAMP, dspstds_reg_by = :mod_by
|
||||
WHERE pkdspstds_id = :id";
|
||||
try {
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->bindParam(':name_en', $name_en);
|
||||
$stmt->bindParam(':name_kh', $name_kh);
|
||||
$stmt->bindParam(':mod_by', $mod_by);
|
||||
$stmt->bindParam(':id', $id);
|
||||
return $stmt->execute();
|
||||
} catch (PDOException $e) {
|
||||
if ($e->getCode() == '23000') {
|
||||
throw new Exception("Data Type with this English name already exists.");
|
||||
}
|
||||
error_log("Error updating data type (ID: $id): " . $e->getMessage());
|
||||
throw new Exception("Could not update data type. Please try again later.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a data type.
|
||||
*
|
||||
* @param int $id ID of the data type to delete.
|
||||
* @return bool True on success.
|
||||
* @throws Exception If a database error occurs or data type is in use.
|
||||
*/
|
||||
public function deleteDataType(int $id): bool {
|
||||
// Check if any data sources are using this data type
|
||||
$checkSql = "SELECT COUNT(*) FROM dsps_tbl_datasource WHERE fkdspstds_id = :id";
|
||||
$stmtCheck = $this->pdo->prepare($checkSql);
|
||||
$stmtCheck->bindParam(':id', $id);
|
||||
$stmtCheck->execute();
|
||||
if ($stmtCheck->fetchColumn() > 0) {
|
||||
throw new Exception("Cannot delete Data Type: It is currently used by one or more data sources.");
|
||||
}
|
||||
|
||||
$sql = "DELETE FROM dsps_tbl_typedatasource WHERE pkdspstds_id = :id";
|
||||
try {
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->bindParam(':id', $id);
|
||||
return $stmt->execute();
|
||||
} catch (PDOException $e) {
|
||||
error_log("Error deleting data type (ID: $id): " . $e->getMessage());
|
||||
throw new Exception("Could not delete data type. Please try again later.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a single data type by ID.
|
||||
*
|
||||
* @param int $id ID of the data type.
|
||||
* @return array|false Data type data or false if not found.
|
||||
* @throws Exception If a database error occurs.
|
||||
*/
|
||||
public function getDataTypeById(int $id) {
|
||||
$sql = "SELECT * FROM dsps_tbl_typedatasource WHERE pkdspstds_id = :id";
|
||||
try {
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->bindParam(':id', $id);
|
||||
$stmt->execute();
|
||||
return $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $e) {
|
||||
error_log("Error fetching data type by ID ($id): " . $e->getMessage());
|
||||
throw new Exception("Could not retrieve data type. Please try again later.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all data types.
|
||||
*
|
||||
* @return array An array of data type data.
|
||||
* @throws Exception If a database error occurs.
|
||||
*/
|
||||
public function getAllDataTypes(): array {
|
||||
$sql = "SELECT * FROM dsps_tbl_typedatasource ORDER BY dspstds_name_en ASC";
|
||||
try {
|
||||
$stmt = $this->pdo->query($sql);
|
||||
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $e) {
|
||||
error_log("Error fetching all data types: " . $e->getMessage());
|
||||
throw new Exception("Could not retrieve data types. Please try again later.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the total count of data types.
|
||||
*
|
||||
* @return int The total number of data types.
|
||||
* @throws Exception If a database error occurs.
|
||||
*/
|
||||
public function getTotalDataTypes(): int {
|
||||
$sql = "SELECT COUNT(*) FROM dsps_tbl_typedatasource";
|
||||
try {
|
||||
$stmt = $this->pdo->query($sql);
|
||||
return $stmt->fetchColumn();
|
||||
} catch (PDOException $e) {
|
||||
error_log("Error getting total data types count: " . $e->getMessage());
|
||||
throw new Exception("Could not retrieve data type count. Please try again later.");
|
||||
}
|
||||
}
|
||||
|
||||
// --- Category Management (dsps_tbl_dspscategory) ---
|
||||
|
||||
/**
|
||||
* Adds a new category.
|
||||
*
|
||||
* @param string $title_en English title of the category.
|
||||
* @param string|null $details Details about the category.
|
||||
* @param int $reg_by User ID who registered it.
|
||||
* @return bool True on success.
|
||||
* @throws Exception If a database error occurs or title already exists.
|
||||
*/
|
||||
public function addCategory(string $title_en, ?string $details, int $reg_by): bool {
|
||||
$sql = "INSERT INTO dsps_tbl_dspscategory (dspscate_title_en, dspscate_details, dspscate_reg_by)
|
||||
VALUES (:title_en, :details, :reg_by)";
|
||||
try {
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->bindParam(':title_en', $title_en);
|
||||
$stmt->bindParam(':details', $details);
|
||||
$stmt->bindParam(':reg_by', $reg_by);
|
||||
return $stmt->execute();
|
||||
} catch (PDOException $e) {
|
||||
if ($e->getCode() == '23000') {
|
||||
throw new Exception("Category with this English title already exists.");
|
||||
}
|
||||
error_log("Error adding category: " . $e->getMessage());
|
||||
throw new Exception("Could not add category. Please try again later.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an existing category.
|
||||
*
|
||||
* @param int $id ID of the category to update.
|
||||
* @param string $title_en New English title.
|
||||
* @param string|null $details New details.
|
||||
* @param int $mod_by User ID who modified it.
|
||||
* @return bool True on success.
|
||||
* @throws Exception If a database error occurs or title already exists.
|
||||
*/
|
||||
public function updateCategory(int $id, string $title_en, ?string $details, int $mod_by): bool {
|
||||
$sql = "UPDATE dsps_tbl_dspscategory
|
||||
SET dspscate_title_en = :title_en, dspscate_details = :details,
|
||||
dspscate_mod_datetime = CURRENT_TIMESTAMP, dspscate_reg_by = :mod_by
|
||||
WHERE pkdspscate_id = :id";
|
||||
try {
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->bindParam(':title_en', $title_en);
|
||||
$stmt->bindParam(':details', $details);
|
||||
$stmt->bindParam(':mod_by', $mod_by);
|
||||
$stmt->bindParam(':id', $id);
|
||||
return $stmt->execute();
|
||||
} catch (PDOException $e) {
|
||||
if ($e->getCode() == '23000') {
|
||||
throw new Exception("Category with this English title already exists.");
|
||||
}
|
||||
error_log("Error updating category (ID: $id): " . $e->getMessage());
|
||||
throw new Exception("Could not update category. Please try again later.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a category.
|
||||
*
|
||||
* @param int $id ID of the category to delete.
|
||||
* @return bool True on success.
|
||||
* @throws Exception If a database error occurs or category is in use.
|
||||
*/
|
||||
public function deleteCategory(int $id): bool {
|
||||
// Check if any data sources are using this category
|
||||
$checkSql = "SELECT COUNT(*) FROM dsps_tbl_datasource WHERE fkdspscate_id = :id";
|
||||
$stmtCheck = $this->pdo->prepare($checkSql);
|
||||
$stmtCheck->bindParam(':id', $id);
|
||||
$stmtCheck->execute();
|
||||
if ($stmtCheck->fetchColumn() > 0) {
|
||||
throw new Exception("Cannot delete Category: It is currently used by one or more data sources.");
|
||||
}
|
||||
|
||||
$sql = "DELETE FROM dsps_tbl_dspscategory WHERE pkdspscate_id = :id";
|
||||
try {
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->bindParam(':id', $id);
|
||||
return $stmt->execute();
|
||||
} catch (PDOException $e) {
|
||||
error_log("Error deleting category (ID: $id): " . $e->getMessage());
|
||||
throw new Exception("Could not delete category. Please try again later.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a single category by ID.
|
||||
*
|
||||
* @param int $id ID of the category.
|
||||
* @return array|false Category data or false if not found.
|
||||
* @throws Exception If a database error occurs.
|
||||
*/
|
||||
public function getCategoryById(int $id) {
|
||||
$sql = "SELECT * FROM dsps_tbl_dspscategory WHERE pkdspscate_id = :id";
|
||||
try {
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->bindParam(':id', $id);
|
||||
$stmt->execute();
|
||||
return $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $e) {
|
||||
error_log("Error fetching category by ID ($id): " . $e->getMessage());
|
||||
throw new Exception("Could not retrieve category. Please try again later.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all categories.
|
||||
*
|
||||
* @return array An array of category data.
|
||||
* @throws Exception If a database error occurs.
|
||||
*/
|
||||
public function getAllCategories(): array {
|
||||
$sql = "SELECT * FROM dsps_tbl_dspscategory ORDER BY dspscate_title_en ASC";
|
||||
try {
|
||||
$stmt = $this->pdo->query($sql);
|
||||
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $e) {
|
||||
error_log("Error fetching all categories: " . $e->getMessage());
|
||||
throw new Exception("Could not retrieve categories. Please try again later.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the total count of categories.
|
||||
*
|
||||
* @return int The total number of categories.
|
||||
* @throws Exception If a database error occurs.
|
||||
*/
|
||||
public function getTotalCategories(): int {
|
||||
$sql = "SELECT COUNT(*) FROM dsps_tbl_dspscategory";
|
||||
try {
|
||||
$stmt = $this->pdo->query($sql);
|
||||
return $stmt->fetchColumn();
|
||||
} catch (PDOException $e) {
|
||||
error_log("Error getting total categories count: " . $e->getMessage());
|
||||
throw new Exception("Could not retrieve category count. Please try again later.");
|
||||
}
|
||||
}
|
||||
}
|
||||
172
classes/Contactus.php
Normal file
172
classes/Contactus.php
Normal file
@@ -0,0 +1,172 @@
|
||||
<?php
|
||||
|
||||
class Contactus {
|
||||
private $pdo;
|
||||
|
||||
public function __construct(PDO $pdo) {
|
||||
$this->pdo = $pdo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Submits a new feedback message from a user/visitor.
|
||||
*
|
||||
* @param string $name The name of the person submitting feedback.
|
||||
* @param string|null $email The email of the person, if provided.
|
||||
* @param string $body_text The main body of the feedback message.
|
||||
* @param string|null $client_ip The IP address of the client.
|
||||
* @return bool True on success.
|
||||
* @throws Exception If a database error occurs.
|
||||
*/
|
||||
public function submitFeedback(string $name, ?string $email, string $body_text, ?string $client_ip): bool {
|
||||
$sql = "INSERT INTO dsps_tbl_feedback (dspsfb_name, dspsfb_email, dspsfb_body_text, dspsfb_client_ip, dspsfb_status)
|
||||
VALUES (:name, :email, :body_text, :client_ip, 'New')";
|
||||
try {
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->bindParam(':name', $name);
|
||||
$stmt->bindParam(':email', $email);
|
||||
$stmt->bindParam(':body_text', $body_text);
|
||||
$stmt->bindParam(':client_ip', $client_ip);
|
||||
return $stmt->execute();
|
||||
} catch (PDOException $e) {
|
||||
error_log("Error submitting feedback: " . $e->getMessage());
|
||||
throw new Exception("Could not submit feedback. Please try again later.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows a DAC Staff user to respond to a feedback message.
|
||||
*
|
||||
* @param int $feedback_id The ID of the feedback message to respond to.
|
||||
* @param string $respond_text The response text from the DAC Staff.
|
||||
* @param string $status The new status of the feedback (e.g., 'In Progress', 'Resolved').
|
||||
* @param int $res_by The user ID of the DAC Staff who responded.
|
||||
* @return bool True on success.
|
||||
* @throws Exception If a database error occurs.
|
||||
*/
|
||||
public function respondToFeedback(int $feedback_id, string $respond_text, string $status, int $res_by): bool {
|
||||
$sql = "UPDATE dsps_tbl_feedback
|
||||
SET dspsfb_respond_text = :respond_text, dspsfb_status = :status,
|
||||
dspsfb_res_datetime = CURRENT_TIMESTAMP, dspsfb_res_by = :res_by
|
||||
WHERE pkdspsfb_id = :feedback_id";
|
||||
try {
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->bindParam(':respond_text', $respond_text);
|
||||
$stmt->bindParam(':status', $status);
|
||||
$stmt->bindParam(':res_by', $res_by);
|
||||
$stmt->bindParam(':feedback_id', $feedback_id);
|
||||
return $stmt->execute();
|
||||
} catch (PDOException $e) {
|
||||
error_log("Error responding to feedback (ID: $feedback_id): " . $e->getMessage());
|
||||
throw new Exception("Could not respond to feedback. Please try again later.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a feedback message.
|
||||
*
|
||||
* @param int $feedback_id The ID of the feedback message to delete.
|
||||
* @return bool True on success.
|
||||
* @throws Exception If a database error occurs.
|
||||
*/
|
||||
public function deleteFeedback(int $feedback_id): bool {
|
||||
$sql = "DELETE FROM dsps_tbl_feedback WHERE pkdspsfb_id = :feedback_id";
|
||||
try {
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->bindParam(':feedback_id', $feedback_id);
|
||||
return $stmt->execute();
|
||||
} catch (PDOException $e) {
|
||||
error_log("Error deleting feedback (ID: $feedback_id): " . $e->getMessage());
|
||||
throw new Exception("Could not delete feedback. Please try again later.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a single feedback message by its ID.
|
||||
*
|
||||
* @param int $feedback_id The ID of the feedback message.
|
||||
* @return array|false The feedback data as an associative array, or false if not found.
|
||||
* @throws Exception If a database error occurs.
|
||||
*/
|
||||
public function getFeedbackById(int $feedback_id) {
|
||||
$sql = "SELECT * FROM dsps_tbl_feedback WHERE pkdspsfb_id = :feedback_id";
|
||||
try {
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->bindParam(':feedback_id', $feedback_id);
|
||||
$stmt->execute();
|
||||
return $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $e) {
|
||||
error_log("Error fetching feedback by ID ($feedback_id): " . $e->getMessage());
|
||||
throw new Exception("Could not retrieve feedback. Please try again later.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all feedback messages, optionally filtered by status.
|
||||
*
|
||||
* @param string|null $status Optional status to filter by (e.g., 'New', 'Resolved').
|
||||
* @return array An array of feedback data.
|
||||
* @throws Exception If a database error occurs.
|
||||
*/
|
||||
public function getAllFeedback(?string $status = null): array {
|
||||
$sql = "SELECT * FROM dsps_tbl_feedback";
|
||||
$conditions = [];
|
||||
$params = [];
|
||||
|
||||
if ($status) {
|
||||
$conditions[] = "dspsfb_status = :status";
|
||||
$params[':status'] = $status;
|
||||
}
|
||||
|
||||
if (!empty($conditions)) {
|
||||
$sql .= " WHERE " . implode(" AND ", $conditions);
|
||||
}
|
||||
|
||||
$sql .= " ORDER BY dspsfb_reg_datetime DESC";
|
||||
|
||||
try {
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
foreach ($params as $key => &$val) {
|
||||
$stmt->bindParam($key, $val, is_int($val) ? PDO::PARAM_INT : PDO::PARAM_STR);
|
||||
}
|
||||
$stmt->execute();
|
||||
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $e) {
|
||||
error_log("Error fetching all feedback: " . $e->getMessage());
|
||||
throw new Exception("Could not retrieve feedback messages. Please try again later.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the total count of feedback messages, optionally filtered by status.
|
||||
*
|
||||
* @param string|null $status Optional status to filter by.
|
||||
* @return int The total number of feedback messages.
|
||||
* @throws Exception If a database error occurs.
|
||||
*/
|
||||
public function getTotalFeedback(?string $status = null): int {
|
||||
$sql = "SELECT COUNT(*) FROM dsps_tbl_feedback";
|
||||
$conditions = [];
|
||||
$params = [];
|
||||
|
||||
if ($status) {
|
||||
$conditions[] = "dspsfb_status = :status";
|
||||
$params[':status'] = $status;
|
||||
}
|
||||
|
||||
if (!empty($conditions)) {
|
||||
$sql .= " WHERE " . implode(" AND ", $conditions);
|
||||
}
|
||||
|
||||
try {
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
foreach ($params as $key => &$val) {
|
||||
$stmt->bindParam($key, $val, is_int($val) ? PDO::PARAM_INT : PDO::PARAM_STR);
|
||||
}
|
||||
$stmt->execute();
|
||||
return $stmt->fetchColumn();
|
||||
} catch (PDOException $e) {
|
||||
error_log("Error getting total feedback count: " . $e->getMessage());
|
||||
throw new Exception("Could not retrieve feedback count. Please try again later.");
|
||||
}
|
||||
}
|
||||
}
|
||||
1489
classes/DataSource.php
Normal file
1489
classes/DataSource.php
Normal file
File diff suppressed because it is too large
Load Diff
138
classes/Faq.php
Normal file
138
classes/Faq.php
Normal file
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
|
||||
class Faq {
|
||||
private $pdo;
|
||||
|
||||
public function __construct(PDO $pdo) {
|
||||
$this->pdo = $pdo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new FAQ entry to the database.
|
||||
*
|
||||
* @param string $title_en The English question for the FAQ.
|
||||
* @param string $description The English answer for the FAQ.
|
||||
* @param int $reg_by The ID of the user who registered this entry (from ist_tbl_users).
|
||||
* @param int $fkisp_id_of The ID of the person associated with this entry (from ist_tbl_people).
|
||||
* @return bool True on success.
|
||||
* @throws Exception If a database error occurs.
|
||||
*/
|
||||
public function addFaq(string $title_en, string $description, int $reg_by, int $fkisp_id_of): bool {
|
||||
$sql = "INSERT INTO dsps_tbl_dspsfaq (dspsfaq_title_en, dspsfaq_description, dspsfaq_reg_by, fkisp_id_of)
|
||||
VALUES (:title_en, :description, :reg_by, :fkisp_id_of)";
|
||||
try {
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->bindParam(':title_en', $title_en);
|
||||
$stmt->bindParam(':description', $description);
|
||||
$stmt->bindParam(':reg_by', $reg_by);
|
||||
$stmt->bindParam(':fkisp_id_of', $fkisp_id_of);
|
||||
return $stmt->execute();
|
||||
} catch (PDOException $e) {
|
||||
error_log("Error adding FAQ entry: " . $e->getMessage());
|
||||
throw new Exception("Could not add FAQ entry. Please try again later.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an existing FAQ entry.
|
||||
*
|
||||
* @param int $id The ID of the FAQ entry to update.
|
||||
* @param string $title_en The new English question.
|
||||
* @param string $description The new English answer.
|
||||
* @param int $mod_by The ID of the user who modified this entry.
|
||||
* @param int $fkisp_id_of The ID of the person associated with this entry.
|
||||
* @return bool True on success.
|
||||
* @throws Exception If a database error occurs.
|
||||
*/
|
||||
public function updateFaq(int $id, string $title_en, string $description, int $mod_by, int $fkisp_id_of): bool {
|
||||
$sql = "UPDATE dsps_tbl_dspsfaq
|
||||
SET dspsfaq_title_en = :title_en, dspsfaq_description = :description,
|
||||
dspsfaq_mod_datetime = CURRENT_TIMESTAMP, dspsfaq_reg_by = :mod_by, fkisp_id_of = :fkisp_id_of
|
||||
WHERE pkdspsfaq_id = :id";
|
||||
try {
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->bindParam(':title_en', $title_en);
|
||||
$stmt->bindParam(':description', $description);
|
||||
$stmt->bindParam(':mod_by', $mod_by);
|
||||
$stmt->bindParam(':fkisp_id_of', $fkisp_id_of);
|
||||
$stmt->bindParam(':id', $id);
|
||||
return $stmt->execute();
|
||||
} catch (PDOException $e) {
|
||||
error_log("Error updating FAQ entry (ID: $id): " . $e->getMessage());
|
||||
throw new Exception("Could not update FAQ entry. Please try again later.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes an FAQ entry.
|
||||
*
|
||||
* @param int $id The ID of the FAQ entry to delete.
|
||||
* @return bool True on success.
|
||||
* @throws Exception If a database error occurs.
|
||||
*/
|
||||
public function deleteFaq(int $id): bool {
|
||||
$sql = "DELETE FROM dsps_tbl_dspsfaq WHERE pkdspsfaq_id = :id";
|
||||
try {
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->bindParam(':id', $id);
|
||||
return $stmt->execute();
|
||||
} catch (PDOException $e) {
|
||||
error_log("Error deleting FAQ entry (ID: $id): " . $e->getMessage());
|
||||
throw new Exception("Could not delete FAQ entry. Please try again later.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a single FAQ entry by its ID.
|
||||
*
|
||||
* @param int $id The ID of the FAQ entry.
|
||||
* @return array|false The entry data as an associative array, or false if not found.
|
||||
* @throws Exception If a database error occurs.
|
||||
*/
|
||||
public function getFaqById(int $id) {
|
||||
$sql = "SELECT * FROM dsps_tbl_dspsfaq WHERE pkdspsfaq_id = :id";
|
||||
try {
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->bindParam(':id', $id);
|
||||
$stmt->execute();
|
||||
return $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $e) {
|
||||
error_log("Error fetching FAQ entry by ID ($id): " . $e->getMessage());
|
||||
throw new Exception("Could not retrieve FAQ entry. Please try again later.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all FAQ entries.
|
||||
*
|
||||
* @return array An array of FAQ entry data.
|
||||
* @throws Exception If a database error occurs.
|
||||
*/
|
||||
public function getAllFaqs(): array {
|
||||
$sql = "SELECT * FROM dsps_tbl_dspsfaq ORDER BY dspsfaq_reg_datetime ASC";
|
||||
try {
|
||||
$stmt = $this->pdo->query($sql);
|
||||
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $e) {
|
||||
error_log("Error fetching all FAQ entries: " . $e->getMessage());
|
||||
throw new Exception("Could not retrieve FAQ entries. Please try again later.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the total count of FAQ entries.
|
||||
*
|
||||
* @return int The total number of FAQ entries.
|
||||
* @throws Exception If a database error occurs.
|
||||
*/
|
||||
public function getTotalFaqs(): int {
|
||||
$sql = "SELECT COUNT(*) FROM dsps_tbl_dspsfaq";
|
||||
try {
|
||||
$stmt = $this->pdo->query($sql);
|
||||
return $stmt->fetchColumn();
|
||||
} catch (PDOException $e) {
|
||||
error_log("Error getting total FAQ count: " . $e->getMessage());
|
||||
throw new Exception("Could not retrieve FAQ count. Please try again later.");
|
||||
}
|
||||
}
|
||||
}
|
||||
257
classes/OAuth.php
Normal file
257
classes/OAuth.php
Normal file
@@ -0,0 +1,257 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Lightweight OAuth 2.0 data/access service for DSP -> JupyterHub integration.
|
||||
*/
|
||||
class OAuthService
|
||||
{
|
||||
private const AUTH_CODE_TTL = 600; // 10 minutes
|
||||
private const ACCESS_TOKEN_TTL = 3600; // 1 hour
|
||||
private const REFRESH_TOKEN_TTL = 2592000; // 30 days
|
||||
|
||||
private PDO $pdo;
|
||||
|
||||
public function __construct(PDO $pdo)
|
||||
{
|
||||
$this->pdo = $pdo;
|
||||
}
|
||||
|
||||
public function getClient(string $clientId): ?array
|
||||
{
|
||||
$sql = "SELECT client_id, client_name, client_secret_hash, redirect_uris, allowed_scopes, is_confidential
|
||||
FROM dsp_oauth_clients
|
||||
WHERE client_id = :client_id AND is_revoked = 0";
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->execute([':client_id' => $clientId]);
|
||||
$client = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
return $client ?: null;
|
||||
}
|
||||
|
||||
public function verifyClientSecret(array $client, string $candidate): bool
|
||||
{
|
||||
if (empty($client['client_secret_hash'])) {
|
||||
return $candidate === '';
|
||||
}
|
||||
|
||||
return password_verify($candidate, $client['client_secret_hash']);
|
||||
}
|
||||
|
||||
public function isRedirectUriAllowed(array $client, string $redirectUri): bool
|
||||
{
|
||||
$allowed = array_filter(array_map('trim', preg_split('/[\s,]+/', (string) ($client['redirect_uris'] ?? ''))));
|
||||
if (empty($allowed)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($allowed as $prefix) {
|
||||
if (stripos($redirectUri, $prefix) === 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function isScopeAllowed(array $client, ?string $requestedScope): bool
|
||||
{
|
||||
$requestedScope = trim((string) $requestedScope);
|
||||
if ($requestedScope === '') {
|
||||
return true;
|
||||
}
|
||||
|
||||
$allowedScopes = array_filter(array_map('trim', explode(' ', (string) ($client['allowed_scopes'] ?? ''))));
|
||||
|
||||
if (empty($allowedScopes)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach (explode(' ', $requestedScope) as $scope) {
|
||||
if (!in_array($scope, $allowedScopes, true)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function issueAuthorizationCode(string $clientId, int $personId, string $redirectUri, ?string $scope = null): array
|
||||
{
|
||||
$code = $this->generateToken(32);
|
||||
$codeHash = $this->hashToken($code);
|
||||
$expiresAt = time() + self::AUTH_CODE_TTL;
|
||||
|
||||
$sql = "INSERT INTO dsp_oauth_auth_codes
|
||||
(code_hash, client_id, person_id, scope, redirect_uri, expires_at, created_at)
|
||||
VALUES (:code_hash, :client_id, :person_id, :scope, :redirect_uri, FROM_UNIXTIME(:expires_at), NOW())";
|
||||
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->execute([
|
||||
':code_hash' => $codeHash,
|
||||
':client_id' => $clientId,
|
||||
':person_id' => $personId,
|
||||
':scope' => $scope,
|
||||
':redirect_uri' => $redirectUri,
|
||||
':expires_at' => $expiresAt,
|
||||
]);
|
||||
|
||||
return [
|
||||
'code' => $code,
|
||||
'expires_at' => $expiresAt,
|
||||
];
|
||||
}
|
||||
|
||||
public function consumeAuthorizationCode(string $code, string $clientId): ?array
|
||||
{
|
||||
$codeHash = $this->hashToken($code);
|
||||
|
||||
$sql = "SELECT code_hash, client_id, person_id, scope, redirect_uri, UNIX_TIMESTAMP(expires_at) AS expires_at
|
||||
FROM dsp_oauth_auth_codes
|
||||
WHERE code_hash = :code_hash";
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->execute([':code_hash' => $codeHash]);
|
||||
$record = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$record) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Delete regardless of outcome
|
||||
$deleteStmt = $this->pdo->prepare("DELETE FROM dsp_oauth_auth_codes WHERE code_hash = :code_hash");
|
||||
$deleteStmt->execute([':code_hash' => $codeHash]);
|
||||
|
||||
if ((int) $record['expires_at'] < time()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($record['client_id'] !== $clientId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $record;
|
||||
}
|
||||
|
||||
public function issueTokens(string $clientId, int $personId, ?string $scope = null, bool $includeRefresh = true): array
|
||||
{
|
||||
$accessToken = $this->generateToken(43);
|
||||
$accessHash = $this->hashToken($accessToken);
|
||||
$accessExpiresAt = time() + self::ACCESS_TOKEN_TTL;
|
||||
|
||||
$refreshToken = null;
|
||||
$refreshHash = null;
|
||||
$refreshExpiresAt = null;
|
||||
|
||||
if ($includeRefresh) {
|
||||
$refreshToken = $this->generateToken(43);
|
||||
$refreshHash = $this->hashToken($refreshToken);
|
||||
$refreshExpiresAt = time() + self::REFRESH_TOKEN_TTL;
|
||||
}
|
||||
|
||||
$sql = "INSERT INTO dsp_oauth_access_tokens
|
||||
(token_hash, client_id, person_id, scope, expires_at, refresh_token_hash, refresh_expires_at, created_at)
|
||||
VALUES (:token_hash, :client_id, :person_id, :scope, FROM_UNIXTIME(:expires_at),
|
||||
:refresh_hash, " . ($refreshExpiresAt ? "FROM_UNIXTIME(:refresh_expires_at)" : "NULL") . ", NOW())";
|
||||
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->execute([
|
||||
':token_hash' => $accessHash,
|
||||
':client_id' => $clientId,
|
||||
':person_id' => $personId,
|
||||
':scope' => $scope,
|
||||
':expires_at' => $accessExpiresAt,
|
||||
':refresh_hash' => $refreshHash,
|
||||
':refresh_expires_at' => $refreshExpiresAt,
|
||||
]);
|
||||
|
||||
return [
|
||||
'access_token' => $accessToken,
|
||||
'access_expires_at' => $accessExpiresAt,
|
||||
'refresh_token' => $refreshToken,
|
||||
'refresh_expires_at' => $refreshExpiresAt,
|
||||
'token_type' => 'Bearer',
|
||||
'scope' => $scope,
|
||||
];
|
||||
}
|
||||
|
||||
public function exchangeRefreshToken(string $clientId, string $refreshToken): ?array
|
||||
{
|
||||
$refreshHash = $this->hashToken($refreshToken);
|
||||
|
||||
$sql = "SELECT token_hash, client_id, person_id, scope, UNIX_TIMESTAMP(refresh_expires_at) AS refresh_expires_at
|
||||
FROM dsp_oauth_access_tokens
|
||||
WHERE refresh_token_hash = :refresh_hash AND is_revoked = 0";
|
||||
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->execute([':refresh_hash' => $refreshHash]);
|
||||
$record = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$record) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($record['client_id'] !== $clientId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!empty($record['refresh_expires_at']) && (int) $record['refresh_expires_at'] < time()) {
|
||||
$this->revokeTokenByHash($record['token_hash']);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Revoke old access token
|
||||
$this->revokeTokenByHash($record['token_hash']);
|
||||
|
||||
// Issue new pair
|
||||
return $this->issueTokens($clientId, (int) $record['person_id'], $record['scope'], true);
|
||||
}
|
||||
|
||||
public function getAccessToken(string $token): ?array
|
||||
{
|
||||
$hash = $this->hashToken($token);
|
||||
$sql = "SELECT token_hash, client_id, person_id, scope,
|
||||
UNIX_TIMESTAMP(expires_at) AS expires_at,
|
||||
refresh_token_hash,
|
||||
UNIX_TIMESTAMP(refresh_expires_at) AS refresh_expires_at
|
||||
FROM dsp_oauth_access_tokens
|
||||
WHERE token_hash = :hash AND is_revoked = 0";
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->execute([':hash' => $hash]);
|
||||
$record = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$record) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ((int) $record['expires_at'] < time()) {
|
||||
$this->revokeTokenByHash($record['token_hash']);
|
||||
return null;
|
||||
}
|
||||
|
||||
return $record;
|
||||
}
|
||||
|
||||
public function revokeTokenByHash(string $hash): void
|
||||
{
|
||||
$sql = "UPDATE dsp_oauth_access_tokens SET is_revoked = 1, revoked_at = NOW()
|
||||
WHERE token_hash = :hash";
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->execute([':hash' => $hash]);
|
||||
}
|
||||
|
||||
public function recordTokenUsage(string $hash): void
|
||||
{
|
||||
$sql = "UPDATE dsp_oauth_access_tokens SET last_used_at = NOW() WHERE token_hash = :hash";
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->execute([':hash' => $hash]);
|
||||
}
|
||||
|
||||
private function generateToken(int $length): string
|
||||
{
|
||||
return rtrim(strtr(base64_encode(random_bytes($length)), '+/', '-_'), '=');
|
||||
}
|
||||
|
||||
private function hashToken(string $token): string
|
||||
{
|
||||
return hash('sha256', $token);
|
||||
}
|
||||
}
|
||||
144
classes/Permission.php
Normal file
144
classes/Permission.php
Normal file
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
// classes/Permission.php
|
||||
class Permission {
|
||||
private $pdo;
|
||||
private $columnExistenceCache = [];
|
||||
|
||||
public function __construct($pdo) {
|
||||
$this->pdo = $pdo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a user has a specific permission for a data source.
|
||||
* @param int $personId The person's ID (fkisp_id).
|
||||
* @param int $dataSourceId The data source ID (pkdspsds_id).
|
||||
* @param string $permissionType The type of permission (e.g., 'Read', 'Download').
|
||||
* @return bool True if the permission is granted, false otherwise.
|
||||
*/
|
||||
public function hasPermission($personId, $dataSourceId, $permissionType) {
|
||||
$sql = "SELECT COUNT(*) FROM dsps_tbl_datasource_permission
|
||||
WHERE fkisp_id_of = ? AND fkdspsds_id = ?
|
||||
AND dspsdsp_permission = ? AND dspsdsp_status = 'Approved'";
|
||||
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->execute([$personId, $dataSourceId, $permissionType]);
|
||||
return $stmt->fetchColumn() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a pending request for a user and data source, if one exists.
|
||||
* @param int $personId The person's ID (fkisp_id).
|
||||
* @param int $dataSourceId The data source ID (pkdspsds_id).
|
||||
* @param string $permissionType The type of permission.
|
||||
* @return array|false The request data as an array, or false if not found.
|
||||
*/
|
||||
public function getPendingRequest($personId, $dataSourceId, $permissionType) {
|
||||
$sql = "SELECT * FROM dsps_tbl_datasource_permission
|
||||
WHERE fkisp_id_of = ? AND fkdspsds_id = ?
|
||||
AND dspsdsp_permission = ? AND dspsdsp_status = 'Pending'";
|
||||
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->execute([$personId, $dataSourceId, $permissionType]);
|
||||
return $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new permission request to the database.
|
||||
* @param int $personId The person's ID (fkisp_id).
|
||||
* @param int $dataSourceId The data source ID (pkdspsds_id).
|
||||
* @param string $permissionType The type of permission requested.
|
||||
* @param string $status The initial status of the request (e.g., 'Pending').
|
||||
* @param string $notes The user's justification for the request.
|
||||
* @return bool True on success, false on failure.
|
||||
*/
|
||||
public function addPermissionRequest($personId, $dataSourceId, $permissionType, $status, $notes, ?string $proofPath = null) {
|
||||
$hasProofColumn = $this->ensurePermissionProofColumn();
|
||||
|
||||
if ($hasProofColumn) {
|
||||
$sql = "INSERT INTO dsps_tbl_datasource_permission (fkisp_id_of, fkdspsds_id, dspsdsp_permission, dspsdsp_notes, dspsdsp_proof_path, dspsdsp_status, dspsdsp_datetime)
|
||||
VALUES (?, ?, ?, ?, ?, ?, NOW())";
|
||||
$params = [$personId, $dataSourceId, $permissionType, $notes, $proofPath, $status];
|
||||
} else {
|
||||
$sql = "INSERT INTO dsps_tbl_datasource_permission (fkisp_id_of, fkdspsds_id, dspsdsp_permission, dspsdsp_notes, dspsdsp_status, dspsdsp_datetime)
|
||||
VALUES (?, ?, ?, ?, ?, NOW())";
|
||||
$params = [$personId, $dataSourceId, $permissionType, $notes, $status];
|
||||
}
|
||||
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
return $stmt->execute($params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all permission requests for a specific user.
|
||||
* This method is needed for the 'my_permissions.php' script.
|
||||
* @param int $personId The person's ID (fkisp_id).
|
||||
* @return array An array of all permission requests for the given person.
|
||||
*/
|
||||
public function getPermissionsByPersonId($personId) {
|
||||
$hasProofColumn = $this->ensurePermissionProofColumn();
|
||||
$proofSelect = $hasProofColumn
|
||||
? 'pr.dspsdsp_proof_path AS dspspr_proof_path'
|
||||
: 'NULL AS dspspr_proof_path';
|
||||
|
||||
$sql = "SELECT
|
||||
ds.dspsds_title_en AS ds_title,
|
||||
pr.dspsdsp_permission AS dspspr_permission_type,
|
||||
pr.dspsdsp_reg_datetime AS dspspr_request_date,
|
||||
pr.dspsdsp_status AS dspspr_status,
|
||||
pr.dspsdsp_notes AS dspspr_notes,
|
||||
$proofSelect
|
||||
FROM dsps_tbl_datasource_permission pr
|
||||
JOIN dsps_tbl_datasource ds ON pr.fkdspsds_id = ds.pkdspsds_id
|
||||
WHERE pr.fkisp_id_of = ?";
|
||||
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->execute([$personId]);
|
||||
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
private function tableColumnExists(string $table, string $column): bool {
|
||||
$cacheKey = $table . '.' . $column;
|
||||
if (array_key_exists($cacheKey, $this->columnExistenceCache)) {
|
||||
return $this->columnExistenceCache[$cacheKey];
|
||||
}
|
||||
|
||||
if (!preg_match('/^[a-zA-Z0-9_]+$/', $table)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$sql = sprintf('SHOW COLUMNS FROM `%s` LIKE :column', $table);
|
||||
try {
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->bindParam(':column', $column, PDO::PARAM_STR);
|
||||
$stmt->execute();
|
||||
$exists = (bool) $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $e) {
|
||||
error_log('Error checking column existence: ' . $e->getMessage());
|
||||
// Assume the column exists if we cannot verify (safer than silently skipping writes)
|
||||
$exists = true;
|
||||
}
|
||||
|
||||
$this->columnExistenceCache[$cacheKey] = $exists;
|
||||
return $exists;
|
||||
}
|
||||
|
||||
private function ensurePermissionProofColumn(): bool {
|
||||
$table = 'dsps_tbl_datasource_permission';
|
||||
$column = 'dspsdsp_proof_path';
|
||||
$cacheKey = $table . '.' . $column;
|
||||
|
||||
if ($this->tableColumnExists($table, $column)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$alterSql = "ALTER TABLE `{$table}` ADD COLUMN `{$column}` VARCHAR(255) DEFAULT NULL AFTER dspsdsp_notes";
|
||||
try {
|
||||
$this->pdo->exec($alterSql);
|
||||
$this->columnExistenceCache[$cacheKey] = true;
|
||||
return true;
|
||||
} catch (PDOException $e) {
|
||||
error_log('Failed to add proof column: ' . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
45
classes/PermissionManager.php
Normal file
45
classes/PermissionManager.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
// classes/PermissionManager.php
|
||||
// This class handles all logic related to checking and requesting user permissions for data sources.
|
||||
|
||||
class PermissionManager
|
||||
{
|
||||
private $pdo;
|
||||
|
||||
public function __construct($pdo)
|
||||
{
|
||||
$this->pdo = $pdo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a specific person has a specific permission for a data source.
|
||||
* @param int $personId The ID of the person.
|
||||
* @param int $dataSourceId The ID of the data source.
|
||||
* @param string $permissionType The type of permission to check ('Read' or 'Download').
|
||||
* @return bool True if the permission exists, false otherwise.
|
||||
*/
|
||||
public function hasPermission($personId, $dataSourceId, $permissionType)
|
||||
{
|
||||
try {
|
||||
// Using a prepared statement to prevent SQL injection
|
||||
$sql = "SELECT COUNT(*) FROM dspsds_person_permissions
|
||||
WHERE fk_dspsdspp_person_id = :personId
|
||||
AND fk_dspsdspp_dspsds_id = :dataSourceId
|
||||
AND dspsdspp_permission = :permissionType";
|
||||
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->bindParam(':personId', $personId, PDO::PARAM_INT);
|
||||
$stmt->bindParam(':dataSourceId', $dataSourceId, PDO::PARAM_INT);
|
||||
$stmt->bindParam(':permissionType', $permissionType, PDO::PARAM_STR);
|
||||
|
||||
$stmt->execute();
|
||||
|
||||
return $stmt->fetchColumn() > 0;
|
||||
|
||||
} catch (PDOException $e) {
|
||||
// Log the error but don't expose it to the user
|
||||
error_log("Database error in hasPermission: " . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
188
classes/Slide.php
Normal file
188
classes/Slide.php
Normal file
@@ -0,0 +1,188 @@
|
||||
<?php
|
||||
|
||||
class Slide {
|
||||
private $pdo;
|
||||
private string $uploadDir;
|
||||
|
||||
public function __construct(PDO $pdo) {
|
||||
$this->pdo = $pdo;
|
||||
$this->uploadDir = __DIR__ . '/../uploads/slides/';
|
||||
// Ensure upload directory exists
|
||||
if (!is_dir($this->uploadDir) && !mkdir($this->uploadDir, 0775, true) && !is_dir($this->uploadDir)) {
|
||||
throw new RuntimeException('Unable to create slides upload directory.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new slide to the database.
|
||||
*
|
||||
* @param string $title_en The English title of the slide.
|
||||
* @param string $description The full description of the slide.
|
||||
* @param string $photoname The filename of the uploaded photo.
|
||||
* @param int $reg_by The ID of the user who registered the slide.
|
||||
* @param int $fkisp_id_of The person ID of the user who registered the slide.
|
||||
* @return bool True on success, false on failure.
|
||||
* @throws Exception If a database error occurs.
|
||||
*/
|
||||
public function addSlide(string $title_en, string $description, string $photoname, int $reg_by, int $fkisp_id_of): bool {
|
||||
$sql = "INSERT INTO dsps_tbl_dspsslide (dspsslide_title_en, dspsslide_description, dspsslide_photoname, dspsslide_reg_by, fkisp_id_of)
|
||||
VALUES (:title_en, :description, :photoname, :reg_by, :fkisp_id_of)";
|
||||
try {
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->bindParam(':title_en', $title_en);
|
||||
$stmt->bindParam(':description', $description);
|
||||
$stmt->bindParam(':photoname', $photoname);
|
||||
$stmt->bindParam(':reg_by', $reg_by, PDO::PARAM_INT);
|
||||
$stmt->bindParam(':fkisp_id_of', $fkisp_id_of, PDO::PARAM_INT);
|
||||
return $stmt->execute();
|
||||
} catch (PDOException $e) {
|
||||
error_log("Error adding slide: " . $e->getMessage());
|
||||
throw new Exception("Could not add slide. Please try again later.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an existing slide in the database.
|
||||
*
|
||||
* @param int $id The ID of the slide to update.
|
||||
* @param string $title_en The new English title.
|
||||
* @param string $description The new description.
|
||||
* @param string $photoname The new filename of the photo.
|
||||
* @param int $mod_by The ID of the user who modified the slide.
|
||||
* @param int $fkisp_id_of The person ID of the user who modified the slide.
|
||||
* @return bool True on success, false on failure.
|
||||
* @throws Exception If a database error occurs.
|
||||
*/
|
||||
public function updateSlide(int $id, string $title_en, string $description, string $photoname, int $mod_by, int $fkisp_id_of): bool {
|
||||
$sql = "UPDATE dsps_tbl_dspsslide
|
||||
SET dspsslide_title_en = :title_en, dspsslide_description = :description, dspsslide_photoname = :photoname,
|
||||
dspsslide_mod_datetime = CURRENT_TIMESTAMP, dspsslide_reg_by = :mod_by, fkisp_id_of = :fkisp_id_of
|
||||
WHERE pkdspsslide_id = :id";
|
||||
try {
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->bindParam(':title_en', $title_en);
|
||||
$stmt->bindParam(':description', $description);
|
||||
$stmt->bindParam(':photoname', $photoname);
|
||||
$stmt->bindParam(':mod_by', $mod_by, PDO::PARAM_INT);
|
||||
$stmt->bindParam(':fkisp_id_of', $fkisp_id_of, PDO::PARAM_INT);
|
||||
$stmt->bindParam(':id', $id, PDO::PARAM_INT);
|
||||
return $stmt->execute();
|
||||
} catch (PDOException $e) {
|
||||
error_log("Error updating slide (ID: $id): " . $e->getMessage());
|
||||
throw new Exception("Could not update slide. Please try again later.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a slide from the database and its associated photo file.
|
||||
*
|
||||
* @param int $id The ID of the slide to delete.
|
||||
* @return bool True on success, false on failure.
|
||||
* @throws Exception If a database error occurs.
|
||||
*/
|
||||
public function deleteSlide(int $id): bool {
|
||||
// First, get the photo path to delete the file
|
||||
$slide = $this->getSlideById($id);
|
||||
if ($slide && !empty($slide['dspsslide_photoname'])) {
|
||||
$filePath = $this->uploadDir . $slide['dspsslide_photoname'];
|
||||
if (file_exists($filePath)) {
|
||||
unlink($filePath); // Delete the file
|
||||
}
|
||||
}
|
||||
|
||||
$sql = "DELETE FROM dsps_tbl_dspsslide WHERE pkdspsslide_id = :id";
|
||||
try {
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->bindParam(':id', $id, PDO::PARAM_INT);
|
||||
return $stmt->execute();
|
||||
} catch (PDOException $e) {
|
||||
error_log("Error deleting slide (ID: $id): " . $e->getMessage());
|
||||
throw new Exception("Could not delete slide. Please try again later.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a single slide by its ID.
|
||||
*
|
||||
* @param int $id The ID of the slide.
|
||||
* @return array|false The slide data as an associative array, or false if not found.
|
||||
* @throws Exception If a database error occurs.
|
||||
*/
|
||||
public function getSlideById(int $id) {
|
||||
$sql = "SELECT * FROM dsps_tbl_dspsslide WHERE pkdspsslide_id = :id";
|
||||
try {
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->bindParam(':id', $id, PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
return $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $e) {
|
||||
error_log("Error fetching slide by ID ($id): " . $e->getMessage());
|
||||
throw new Exception("Could not retrieve slide. Please try again later.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all slides.
|
||||
*
|
||||
* @return array An array of slide data.
|
||||
* @throws Exception If a database error occurs.
|
||||
*/
|
||||
public function getAllSlides(): array {
|
||||
$sql = "SELECT * FROM dsps_tbl_dspsslide ORDER BY pkdspsslide_id ASC"; // Order by ID or a custom sort order
|
||||
try {
|
||||
$stmt = $this->pdo->query($sql);
|
||||
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $e) {
|
||||
error_log("Error fetching all slides: " . $e->getMessage());
|
||||
throw new Exception("Could not retrieve slides. Please try again later.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the total count of slides.
|
||||
*
|
||||
* @return int The total number of slides.
|
||||
* @throws Exception If a database error occurs.
|
||||
*/
|
||||
public function getTotalSlides(): int {
|
||||
$sql = "SELECT COUNT(*) FROM dsps_tbl_dspsslide";
|
||||
try {
|
||||
$stmt = $this->pdo->query($sql);
|
||||
return $stmt->fetchColumn();
|
||||
} catch (PDOException $e) {
|
||||
error_log("Error getting total slides count: " . $e->getMessage());
|
||||
throw new Exception("Could not retrieve slide count. Please try again later.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the upload of a slide photo.
|
||||
*
|
||||
* @param array $file The $_FILES array for the uploaded photo.
|
||||
* @return string The unique filename of the uploaded photo.
|
||||
* @throws Exception If the upload fails or file type is invalid.
|
||||
*/
|
||||
public function handlePhotoUpload(array $file): string {
|
||||
if ($file['error'] !== UPLOAD_ERR_OK) {
|
||||
throw new Exception('File upload error: ' . $file['error']);
|
||||
}
|
||||
|
||||
$allowedTypes = ['image/jpeg', 'image/png', 'image/gif'];
|
||||
$finfo = new finfo(FILEINFO_MIME_TYPE);
|
||||
$mimeType = $finfo->file($file['tmp_name']);
|
||||
|
||||
if (!in_array($mimeType, $allowedTypes)) {
|
||||
throw new Exception('Invalid file type. Only JPEG, PNG, and GIF images are allowed.');
|
||||
}
|
||||
|
||||
$extension = pathinfo($file['name'], PATHINFO_EXTENSION);
|
||||
$uniqueFilename = uniqid('slide_') . '.' . $extension;
|
||||
$destination = $this->uploadDir . $uniqueFilename;
|
||||
|
||||
if (!move_uploaded_file($file['tmp_name'], $destination)) {
|
||||
throw new Exception('Failed to move uploaded file.');
|
||||
}
|
||||
|
||||
return $uniqueFilename;
|
||||
}
|
||||
}
|
||||
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