DSP Project first push, date: 29/01/2026

This commit is contained in:
Sok Ponlork
2026-01-29 14:31:48 +07:00
parent 951262afb3
commit 644b624d2d
1857 changed files with 163516 additions and 0 deletions

119
admin/app_log.php Normal file
View File

@@ -0,0 +1,119 @@
<?php
session_start();
require_once __DIR__ . '/../config.php';
require_once __DIR__ . '/../includes/auth.php';
redirect_if_not_role('DAC Staff', '../index.php');
$logPath = realpath(__DIR__ . '/../logs/app.log');
$canReadLog = $logPath && is_readable($logPath);
$lineLimit = filter_input(INPUT_GET, 'lines', FILTER_VALIDATE_INT);
$lineLimit = $lineLimit ?: 200;
$lineLimit = max(50, min(2000, $lineLimit));
$logEntries = [];
$logMeta = [
'size' => null,
'modified' => null,
];
if ($canReadLog) {
$logMeta['size'] = filesize($logPath);
$logMeta['modified'] = filemtime($logPath);
if (isset($_GET['download'])) {
header('Content-Type: text/plain');
header('Content-Disposition: attachment; filename="app.log"');
header('Content-Length: ' . $logMeta['size']);
readfile($logPath);
exit;
}
try {
$logEntries = tail_file($logPath, $lineLimit);
} catch (RuntimeException $e) {
$canReadLog = false;
$logError = $e->getMessage();
}
} else {
$logError = 'The application log file is missing or unreadable.';
}
function tail_file(string $path, int $lines): array
{
$file = new SplFileObject($path, 'r');
$file->seek(PHP_INT_MAX);
$lastLine = $file->key();
$startLine = max($lastLine - $lines + 1, 0);
$file->seek($startLine);
$buffer = [];
while (!$file->eof()) {
$buffer[] = rtrim($file->current(), "\r\n");
$file->next();
}
return $buffer;
}
$lastUpdated = $logMeta['modified'] ? date('Y-m-d H:i:s', $logMeta['modified']) . ' UTC' : 'Unknown';
$logSizeHuman = $logMeta['size'] !== null ? number_format($logMeta['size'] / 1024, 1) . ' KB' : 'Unknown';
?>
<!DOCTYPE html>
<html lang="en">
<?php include_once __DIR__ . '/../includes/header_admin.php'; ?>
<body>
<div class="wrapper">
<?php include_once __DIR__ . '/../includes/nav_admin.php'; ?>
<div class="main-content">
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h1 class="h4 mb-1">Application Log</h1>
<p class="text-muted mb-0">Streaming the latest <?php echo htmlspecialchars($lineLimit); ?> lines from <code>logs/app.log</code>.</p>
</div>
<div class="text-end">
<small class="text-muted d-block">Last updated: <?php echo htmlspecialchars($lastUpdated); ?></small>
<small class="text-muted">Size: <?php echo htmlspecialchars($logSizeHuman); ?></small>
</div>
</div>
<div class="card shadow-sm border-0 mb-4">
<div class="card-body d-flex flex-wrap gap-2 align-items-center">
<form class="d-flex align-items-center gap-2" method="get">
<label for="lines" class="form-label mb-0">Lines to display</label>
<select class="form-select form-select-sm w-auto" id="lines" name="lines" onchange="this.form.submit()">
<?php foreach ([100, 200, 500, 1000, 2000] as $option): ?>
<option value="<?php echo $option; ?>" <?php echo ($lineLimit === $option) ? 'selected' : ''; ?>>
<?php echo $option; ?>
</option>
<?php endforeach; ?>
</select>
</form>
<?php if ($canReadLog): ?>
<a class="btn btn-sm btn-outline-secondary" href="?lines=<?php echo $lineLimit; ?>">
<i class="fas fa-sync-alt me-1"></i> Refresh
</a>
<a class="btn btn-sm btn-outline-primary" href="?download=1">
<i class="fas fa-download me-1"></i> Download
</a>
<?php endif; ?>
</div>
</div>
<?php if (!$canReadLog): ?>
<div class="alert alert-danger rounded shadow-sm">
<strong>Cannot read log file.</strong>
<div><?php echo htmlspecialchars($logError ?? 'Unknown error.'); ?></div>
</div>
<?php else: ?>
<div class="card shadow-sm border-0">
<div class="card-body">
<pre class="bg-dark text-light p-3 rounded small" style="max-height: 60vh; overflow:auto;"><?php echo htmlspecialchars(implode("\n", $logEntries)); ?></pre>
</div>
</div>
<?php endif; ?>
</div>
</div>
<?php include_once __DIR__ . '/../includes/footer_admin.php'; ?>
</body>
</html>

171
admin/dashboard.php Normal file
View File

@@ -0,0 +1,171 @@
<?php
// Start session and include necessary files
session_start();
require_once '../config.php';
require_once '../includes/auth.php';
require_once '../classes/User.php';
require_once '../classes/Announcement.php';
require_once '../classes/DataSource.php'; // Still needed for overall data source counts
require_once '../classes/Classifications.php'; // For classification counts
require_once '../classes/Aboutus.php';
require_once '../classes/Contactus.php'; // For feedback/contact messages
require_once '../classes/Faq.php';
require_once '../classes/Slide.php'; // Include Slide class
// Redirect if not logged in or not a DAC Staff
redirect_if_not_logged_in('../index.php');
redirect_if_not_role('DAC Staff', '../index.php');
// Initialize classes
$user = new User($pdo);
$announcement = new Announcement($pdo);
$dataSource = new DataSource($pdo);
$classification = new Classifications($pdo);
$aboutUs = new Aboutus($pdo);
$contactUs = new Contactus($pdo);
$faq = new Faq($pdo);
$slideManager = new Slide($pdo);
// Fetch dashboard data
$totalUsers = $user->getTotalUsers();
$totalAnnouncements = $announcement->getTotalAnnouncements();
$totalDataSources = $dataSource->getTotalDataSources();
$totalCategories = $classification->getTotalCategories();
$totalDataTypes = $classification->getTotalDataTypes();
$totalFeedback = $contactUs->getTotalFeedback();
$totalFaqs = $faq->getTotalFaqs();
$totalSlides = $slideManager->getTotalSlides();
$pendingPermissions = $dataSource->getPendingPermissionRequestsCount(); // This correctly gets ALL pending for admin dashboard
?>
<!DOCTYPE html>
<html lang="en">
<!-- Header -->
<?php
// Include header file for admin pages
include_once("../includes/header_admin.php");
?>
<body>
<div class="wrapper">
<!-- Sidebar -->
<?php
// Include header file for admin pages
include_once("../includes/nav_admin.php");
?>
<!-- Main Content -->
<div class="main-content">
<nav class="navbar navbar-expand-lg navbar-light bg-light mb-4 rounded-3">
<div class="container-fluid">
<a class="navbar-brand" href="#">Admin Dashboard</a>
<div class="d-flex">
<span class="navbar-text me-3">
Welcome, <?php echo htmlspecialchars($_SESSION['username']); ?>!
</span>
</div>
</div>
</nav>
<?php
// Display session messages
if (isset($_SESSION['message'])) {
echo '<div class="alert alert-' . $_SESSION['message_type'] . ' alert-dismissible fade show rounded-pill" role="alert">' . htmlspecialchars($_SESSION['message']) . '<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button></div>';
unset($_SESSION['message']);
unset($_SESSION['message_type']);
}
?>
<div class="row g-4">
<div class="col-md-4 col-sm-6">
<div class="card text-center p-4">
<div class="card-body">
<i class="fas fa-users card-icon mb-3"></i>
<h5 class="card-title">Total Users</h5>
<p class="card-text fs-2 fw-bold text-primary"><?php echo $totalUsers; ?></p>
</div>
</div>
</div>
<div class="col-md-4 col-sm-6">
<div class="card text-center p-4">
<div class="card-body">
<i class="fas fa-bullhorn card-icon mb-3"></i>
<h5 class="card-title">Total Announcements</h5>
<p class="card-text fs-2 fw-bold text-success"><?php echo $totalAnnouncements; ?></p>
</div>
</div>
</div>
<div class="col-md-4 col-sm-6">
<div class="card text-center p-4">
<div class="card-body">
<i class="fas fa-database card-icon mb-3"></i>
<h5 class="card-title">Total Data Sources</h5>
<p class="card-text fs-2 fw-bold text-info"><?php echo $totalDataSources; ?></p>
</div>
</div>
</div>
<div class="col-md-4 col-sm-6">
<div class="card text-center p-4">
<div class="card-body">
<i class="fas fa-folder-open card-icon mb-3"></i>
<h5 class="card-title">Data Categories</h5>
<p class="card-text fs-2 fw-bold text-warning"><?php echo $totalCategories; ?></p>
</div>
</div>
</div>
<div class="col-md-4 col-sm-6">
<div class="card text-center p-4">
<div class="card-body">
<i class="fas fa-file-alt card-icon mb-3"></i>
<h5 class="card-title">Data Types</h5>
<p class="card-text fs-2 fw-bold text-danger"><?php echo $totalDataTypes; ?></p>
</div>
</div>
</div>
<div class="col-md-4 col-sm-6">
<div class="card text-center p-4">
<div class="card-body">
<i class="fas fa-handshake card-icon mb-3"></i>
<h5 class="card-title">Pending Permissions</h5>
<p class="card-text fs-2 fw-bold text-secondary"><?php echo $pendingPermissions; ?></p>
</div>
</div>
</div>
<div class="col-md-4 col-sm-6">
<div class="card text-center p-4">
<div class="card-body">
<i class="fas fa-comments card-icon mb-3"></i>
<h5 class="card-title">New Feedback</h5>
<p class="card-text fs-2 fw-bold text-primary"><?php echo $totalFeedback; ?></p>
</div>
</div>
</div>
<div class="col-md-4 col-sm-6">
<div class="card text-center p-4">
<div class="card-body">
<i class="fas fa-question card-icon mb-3"></i>
<h5 class="card-title">Total FAQs</h5>
<p class="card-text fs-2 fw-bold text-success"><?php echo $totalFaqs; ?></p>
</div>
</div>
</div>
<div class="col-md-4 col-sm-6">
<div class="card text-center p-4">
<div class="card-body">
<i class="fas fa-images card-icon mb-3"></i>
<h5 class="card-title">Total Slides</h5>
<p class="card-text fs-2 fw-bold text-warning"><?php echo $totalSlides; ?></p>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Footer -->
<?php
// Include Footer file for owner pages
include_once("../includes/footer_admin.php");
?>
</body>
</html>

216
admin/manage_aboutus.php Normal file
View File

@@ -0,0 +1,216 @@
<?php
// Start session and include necessary files
session_start();
require_once '../config.php';
require_once '../includes/auth.php';
require_once '../classes/Aboutus.php';
require_once '../classes/User.php'; // To get person_id for the Aboutus class
// Redirect if not logged in or not a DAC Staff
redirect_if_not_logged_in('../index.php');
redirect_if_not_role('DAC Staff', '../index.php');
// Initialize Aboutus class
$aboutUs = new Aboutus($pdo);
$user = new User($pdo); // To get the person_id of the logged-in user
$action = $_GET['action'] ?? '';
$id = $_GET['id'] ?? null;
// Handle form submissions
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$action_type = $_POST['action_type'] ?? ''; // 'add' or 'edit' or 'delete'
$title = trim($_POST['title'] ?? '');
$description = trim($_POST['description'] ?? '');
$about_id = $_POST['about_id'] ?? null;
// Get the person_id of the currently logged-in user
$currentUserDetails = $user->getUserDetails($_SESSION['user_id']);
$fkisp_id_of = $currentUserDetails['fkisp_id_of'];
if ($action_type === 'delete') {
if ($about_id) {
try {
$aboutUs->deleteAboutUs($about_id);
set_message('About Us entry deleted successfully!', 'success');
} catch (Exception $e) {
set_message('Error deleting About Us entry: ' . $e->getMessage(), 'danger');
}
}
} else {
if (empty($title) || empty($description)) {
set_message('Title and description cannot be empty.', 'danger');
} else {
try {
if ($action_type === 'add') {
$aboutUs->addAboutUs($title, $description, $_SESSION['user_id'], $fkisp_id_of);
set_message('About Us entry added successfully!', 'success');
} elseif ($action_type === 'edit' && $about_id) {
$aboutUs->updateAboutUs($about_id, $title, $description, $_SESSION['user_id'], $fkisp_id_of);
set_message('About Us entry updated successfully!', 'success');
}
} catch (Exception $e) {
set_message('Error: ' . $e->getMessage(), 'danger');
}
}
}
header('Location: manage_aboutus.php');
exit();
}
// Fetch About Us entries for display
$aboutUsEntries = $aboutUs->getAllAboutUs();
// Prepare data for editing if action is 'edit'
$editAboutUs = null;
if ($action === 'edit' && $id) {
$editAboutUs = $aboutUs->getAboutUsById($id);
}
?>
<!DOCTYPE html>
<html lang="en">
<!-- Header -->
<?php
// Include header file for admin pages
include_once("../includes/header_admin.php");
?>
<body>
<div class="wrapper">
<!-- Sidebar -->
<?php
// Include header file for admin pages
include_once("../includes/nav_admin.php");
?>
<!-- Main Content -->
<div class="main-content">
<nav class="navbar navbar-expand-lg navbar-light bg-light mb-4 rounded-3">
<div class="container-fluid">
<a class="navbar-brand" href="#">Manage About Us</a>
<div class="d-flex">
<span class="navbar-text me-3">
Welcome, <?php echo htmlspecialchars($_SESSION['username']); ?>!
</span>
</div>
</div>
</nav>
<?php
// Display session messages
if (isset($_SESSION['message'])) {
echo '<div class="alert alert-' . $_SESSION['message_type'] . ' alert-dismissible fade show rounded" role="alert">' . htmlspecialchars($_SESSION['message']) . '<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button></div>';
unset($_SESSION['message']);
unset($_SESSION['message_type']);
}
?>
<div class="card mb-4">
<div class="card-header bg-primary text-white">
<h5 class="mb-0"><?php echo $editAboutUs ? 'Edit' : 'Add New'; ?> About Us Entry</h5>
</div>
<div class="card-body">
<form action="manage_aboutus.php" method="POST">
<input type="hidden" name="action_type" value="<?php echo $editAboutUs ? 'edit' : 'add'; ?>">
<?php if ($editAboutUs): ?>
<input type="hidden" name="about_id" value="<?php echo htmlspecialchars($editAboutUs['pkdspsabout_id']); ?>">
<?php endif; ?>
<div class="mb-3">
<label for="title" class="form-label">Title</label>
<input type="text" class="form-control rounded" id="title" name="title" value="<?php echo htmlspecialchars($editAboutUs['dspsabout_title_en'] ?? ''); ?>" required>
</div>
<div class="mb-3">
<label for="description" class="form-label d-flex justify-content-between align-items-center">
<span>Description</span>
</label>
<textarea class="form-control rounded-3" id="description" name="description" rows="8" required><?php echo htmlspecialchars($editAboutUs['dspsabout_description'] ?? ''); ?></textarea>
<div class="form-text">Format paragraphs, bullet lists, and links so the public About Us page is easy to read.</div>
</div>
<button type="submit" class="btn btn-primary rounded">
<i class="fas fa-<?php echo $editAboutUs ? 'save' : 'plus'; ?> me-2"></i> <?php echo $editAboutUs ? 'Update' : 'Add'; ?> Entry
</button>
<?php if ($editAboutUs): ?>
<a href="manage_aboutus.php" class="btn btn-secondary rounded ms-2">Cancel Edit</a>
<?php endif; ?>
</form>
</div>
</div>
<div class="card">
<div class="card-header text-white" style="background-color: #28a745;">
<h5 class="mb-0">All About Us Entries</h5>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover table-striped">
<thead>
<tr>
<th>ID</th>
<th>Title</th>
<th>Description</th>
<th>Reg. Date</th>
<th>Mod. Date</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php if (!empty($aboutUsEntries)): ?>
<?php foreach ($aboutUsEntries as $entry): ?>
<tr>
<td><?php echo htmlspecialchars($entry['pkdspsabout_id']); ?></td>
<td><?php echo htmlspecialchars($entry['dspsabout_title_en']); ?></td>
<td><?php echo htmlspecialchars(substr($entry['dspsabout_description'], 0, 100)) . (strlen($entry['dspsabout_description']) > 100 ? '...' : ''); ?></td>
<td><?php echo htmlspecialchars($entry['dspsabout_reg_datetime']); ?></td>
<td><?php echo htmlspecialchars($entry['dspsabout_mod_datetime']); ?></td>
<td>
<a href="manage_aboutus.php?action=edit&id=<?php echo htmlspecialchars($entry['pkdspsabout_id']); ?>" class="btn btn-sm btn-warning rounded btn-action">
<i class="fas fa-edit"></i>
</a>
<form action="manage_aboutus.php" method="POST" class="d-inline" onsubmit="return confirm('Are you sure you want to delete this About Us entry?');">
<input type="hidden" name="action_type" value="delete">
<input type="hidden" name="about_id" value="<?php echo htmlspecialchars($entry['pkdspsabout_id']); ?>">
<button type="submit" class="btn btn-sm btn-danger rounded btn-action">
<i class="fas fa-trash-alt"></i>
</button>
</form>
</td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr>
<td colspan="6" class="text-center">No About Us entries found.</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<!-- Footer -->
<?php
// Include Footer file for owner pages
include_once("../includes/footer_admin.php");
?>
<script src="https://cdn.jsdelivr.net/npm/@ckeditor/ckeditor5-build-classic@38.1.1/build/ckeditor.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function () {
var textarea = document.querySelector('#description');
if (textarea && typeof ClassicEditor !== 'undefined') {
ClassicEditor
.create(textarea, {
toolbar: [
'heading','|','bold','italic','underline','bulletedList','numberedList','blockQuote',
'|','link','insertTable','undo','redo'
]
})
.catch(function (error) {
console.error('Failed to initialise rich text editor', error);
});
}
});
</script>
</body>
</html>

View File

@@ -0,0 +1,260 @@
<?php
// Start session and include necessary files
session_start();
require_once '../config.php';
require_once '../includes/auth.php';
require_once '../classes/Announcement.php';
// Redirect if not logged in or not a DAC Staff
redirect_if_not_logged_in('../index.php');
redirect_if_not_role('DAC Staff', '../index.php');
// Initialize Announcement class
$announcement = new Announcement($pdo);
$action = $_GET['action'] ?? '';
$id = $_GET['id'] ?? null;
// Handle form submissions
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$action_type = $_POST['action_type'] ?? ''; // 'add' or 'edit' or 'delete'
$title = trim($_POST['title'] ?? '');
$description = trim($_POST['description'] ?? '');
$status = $_POST['status'] ?? 'Draft';
$announcement_id = $_POST['announcement_id'] ?? null;
$current_photo = $_POST['current_photo'] ?? ''; // For editing, keep track of existing photo
if ($action_type === 'delete') {
if ($announcement_id) {
try {
$announcement->deleteAnnouncement($announcement_id);
set_message('Announcement deleted successfully!', 'success');
} catch (Exception $e) {
set_message('Error deleting announcement: ' . $e->getMessage(), 'danger');
}
}
} else {
// Handle photo upload
$photoPath = $current_photo; // Default to current photo if not uploading new
if (isset($_FILES['photo']) && $_FILES['photo']['error'] === UPLOAD_ERR_OK) {
try {
$photoPath = $announcement->handlePhotoUpload($_FILES['photo']);
// If editing and a new photo is uploaded, delete the old one
if ($action_type === 'edit' && !empty($current_photo) && $current_photo !== $photoPath) {
unlink('../uploads/announcements/' . $current_photo); // Delete old file
}
} catch (Exception $e) {
set_message('Photo upload error: ' . $e->getMessage(), 'danger');
header('Location: manage_announcements.php');
exit();
}
}
if (empty($title) || empty($description)) {
set_message('Title and description cannot be empty.', 'danger');
} else {
try {
if ($action_type === 'add') {
$announcement->addAnnouncement($title, $description, $photoPath, $status, $_SESSION['user_id']);
set_message('Announcement added successfully!', 'success');
} elseif ($action_type === 'edit' && $announcement_id) {
$announcement->updateAnnouncement($announcement_id, $title, $description, $photoPath, $status, $_SESSION['user_id']);
set_message('Announcement updated successfully!', 'success');
}
} catch (Exception $e) {
set_message('Error: ' . $e->getMessage(), 'danger');
}
}
}
header('Location: manage_announcements.php');
exit();
}
// Fetch announcements for display
$announcements = $announcement->getAllAnnouncements();
// Prepare data for editing if action is 'edit'
$editAnnouncement = null;
if ($action === 'edit' && $id) {
$editAnnouncement = $announcement->getAnnouncementById($id);
}
?>
<!DOCTYPE html>
<html lang="en">
<!-- Header -->
<?php
// Include header file for admin pages
include_once("../includes/header_admin.php");
?>
<body>
<div class="wrapper">
<!-- Sidebar -->
<?php
// Include header file for admin pages
include_once("../includes/nav_admin.php");
?>
<!-- Main Content -->
<div class="main-content">
<nav class="navbar navbar-expand-lg navbar-light bg-light mb-4 rounded-3">
<div class="container-fluid">
<a class="navbar-brand" href="#">Manage Announcements</a>
<div class="d-flex">
<span class="navbar-text me-3">
Welcome, <?php echo htmlspecialchars($_SESSION['username']); ?>!
</span>
</div>
</div>
</nav>
<?php
// Display session messages
if (isset($_SESSION['message'])) {
echo '<div class="alert alert-' . $_SESSION['message_type'] . ' alert-dismissible fade show rounded" role="alert">' . htmlspecialchars($_SESSION['message']) . '<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button></div>';
unset($_SESSION['message']);
unset($_SESSION['message_type']);
}
?>
<div class="card mb-4">
<div class="card-header bg-primary text-white">
<h5 class="mb-0"><?php echo $editAnnouncement ? 'Edit' : 'Add New'; ?> Announcement</h5>
</div>
<div class="card-body">
<form action="manage_announcements.php" method="POST" enctype="multipart/form-data">
<input type="hidden" name="action_type" value="<?php echo $editAnnouncement ? 'edit' : 'add'; ?>">
<?php if ($editAnnouncement): ?>
<input type="hidden" name="announcement_id" value="<?php echo htmlspecialchars($editAnnouncement['pkdspsann_id']); ?>">
<input type="hidden" name="current_photo" value="<?php echo htmlspecialchars($editAnnouncement['dspsann_photopath']); ?>">
<?php endif; ?>
<div class="mb-3">
<label for="title" class="form-label">Title</label>
<input type="text" class="form-control rounded" id="title" name="title" value="<?php echo htmlspecialchars($editAnnouncement['dspsann_title'] ?? ''); ?>" required>
</div>
<div class="mb-3">
<label for="description" class="form-label d-flex justify-content-between align-items-center">
<span>Description</span>
</label>
<textarea class="form-control rounded-3" id="description" name="description" rows="8" required><?php echo htmlspecialchars($editAnnouncement['dspsann_description'] ?? ''); ?></textarea>
<div class="form-text">Use the toolbar to format bullet lists, emphasize important actions, and link to additional resources.</div>
</div>
<div class="mb-3">
<label for="photo" class="form-label">Photo (Optional)</label>
<input type="file" class="form-control rounded" id="photo" name="photo" accept="image/*">
<?php if ($editAnnouncement && !empty($editAnnouncement['dspsann_photopath'])): ?>
<div class="mt-2">
Current Photo: <img src="../uploads/announcements/<?php echo htmlspecialchars($editAnnouncement['dspsann_photopath']); ?>" alt="Announcement Photo" class="announcement-img">
</div>
<?php endif; ?>
</div>
<div class="mb-3">
<label for="status" class="form-label">Status</label>
<select class="form-select rounded" id="status" name="status" required>
<option value="Draft" <?php echo ($editAnnouncement && $editAnnouncement['dspsann_status'] == 'Draft') ? 'selected' : ''; ?>>Draft</option>
<option value="Published" <?php echo ($editAnnouncement && $editAnnouncement['dspsann_status'] == 'Published') ? 'selected' : ''; ?>>Published</option>
<option value="Archived" <?php echo ($editAnnouncement && $editAnnouncement['dspsann_status'] == 'Archived') ? 'selected' : ''; ?>>Archived</option>
</select>
</div>
<button type="submit" class="btn btn-primary rounded">
<i class="fas fa-<?php echo $editAnnouncement ? 'save' : 'plus'; ?> me-2"></i> <?php echo $editAnnouncement ? 'Update' : 'Add'; ?> Announcement
</button>
<?php if ($editAnnouncement): ?>
<a href="manage_announcements.php" class="btn btn-secondary rounded ms-2">Cancel Edit</a>
<?php endif; ?>
</form>
</div>
</div>
<div class="card">
<div class="card-header text-white" style="background-color: #28a745;">
<h5 class="mb-0">All Announcements</h5>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover table-striped">
<thead>
<tr>
<th>ID</th>
<th>Title</th>
<th>Description</th>
<th>Photo</th>
<th>Status</th>
<th>Reg. Date</th>
<th>Mod. Date</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php if (!empty($announcements)): ?>
<?php foreach ($announcements as $ann): ?>
<tr>
<td><?php echo htmlspecialchars($ann['pkdspsann_id']); ?></td>
<td><?php echo htmlspecialchars($ann['dspsann_title']); ?></td>
<td><?php echo htmlspecialchars(substr($ann['dspsann_description'], 0, 100)) . (strlen($ann['dspsann_description']) > 100 ? '...' : ''); ?></td>
<td>
<?php if (!empty($ann['dspsann_photopath'])): ?>
<img src="../uploads/announcements/<?php echo htmlspecialchars($ann['dspsann_photopath']); ?>" alt="Photo" class="announcement-img">
<?php else: ?>
N/A
<?php endif; ?>
</td>
<td><span class="badge bg-<?php
if ($ann['dspsann_status'] == 'Published') echo 'success';
else if ($ann['dspsann_status'] == 'Draft') echo 'warning';
else echo 'secondary';
?>"><?php echo htmlspecialchars($ann['dspsann_status']); ?></span></td>
<td><?php echo htmlspecialchars($ann['dspsann_reg_datetime']); ?></td>
<td><?php echo htmlspecialchars($ann['dspsann_mod_datetime']); ?></td>
<td>
<a href="manage_announcements.php?action=edit&id=<?php echo htmlspecialchars($ann['pkdspsann_id']); ?>" class="btn btn-sm btn-warning rounded btn-action">
<i class="fas fa-edit"></i>
</a>
<form action="manage_announcements.php" method="POST" class="d-inline" onsubmit="return confirm('Are you sure you want to delete this announcement? This action cannot be undone.');">
<input type="hidden" name="action_type" value="delete">
<input type="hidden" name="announcement_id" value="<?php echo htmlspecialchars($ann['pkdspsann_id']); ?>">
<button type="submit" class="btn btn-sm btn-danger rounded btn-action">
<i class="fas fa-trash-alt"></i>
</button>
</form>
</td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr>
<td colspan="8" class="text-center">No announcements found.</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<!-- Footer -->
<?php
// Include Footer file for owner pages
include_once("../includes/footer_admin.php");
?>
<script src="https://cdn.jsdelivr.net/npm/@ckeditor/ckeditor5-build-classic@38.1.1/build/ckeditor.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function () {
var textarea = document.querySelector('#description');
if (textarea && typeof ClassicEditor !== 'undefined') {
ClassicEditor
.create(textarea, {
toolbar: [
'heading','|','bold','italic','underline','bulletedList','numberedList','blockQuote',
'|','link','insertTable','undo','redo'
]
})
.catch(function (error) {
console.error('Failed to initialise rich text editor', error);
});
}
});
</script>
</body>
</html>

View File

@@ -0,0 +1,269 @@
<?php
// Start session and include necessary files
session_start();
require_once '../config.php';
require_once '../includes/auth.php';
require_once '../classes/Classifications.php'; // New Classifications class
// Redirect if not logged in or not a DAC Staff
redirect_if_not_logged_in('../index.php');
redirect_if_not_role('DAC Staff', '../index.php');
// Initialize Classifications class
$classification = new Classifications($pdo);
$action = $_GET['action'] ?? '';
$id = $_GET['id'] ?? null;
// Handle form submissions
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$type = $_POST['type'] ?? ''; // 'datatype' or 'category'
$action_type = $_POST['action_type'] ?? ''; // 'add' or 'edit'
$name_en = trim($_POST['name_en'] ?? '');
$name_kh = trim($_POST['name_kh'] ?? '');
$details = trim($_POST['details'] ?? '');
$record_id = $_POST['record_id'] ?? null;
if (empty($name_en)) {
set_message('English name cannot be empty.', 'danger');
header('Location: manage_classifications.php');
exit();
}
try {
if ($type === 'datatype') {
if ($action_type === 'add') {
$classification->addDataType($name_en, $name_kh, $_SESSION['user_id']);
set_message('Data Type added successfully!', 'success');
} elseif ($action_type === 'edit' && $record_id) {
$classification->updateDataType($record_id, $name_en, $name_kh, $_SESSION['user_id']);
set_message('Data Type updated successfully!', 'success');
} elseif ($action_type === 'delete' && $record_id) {
$classification->deleteDataType($record_id);
set_message('Data Type deleted successfully!', 'success');
}
} elseif ($type === 'category') {
if ($action_type === 'add') {
$classification->addCategory($name_en, $details, $_SESSION['user_id']);
set_message('Category added successfully!', 'success');
} elseif ($action_type === 'edit' && $record_id) {
$classification->updateCategory($record_id, $name_en, $details, $_SESSION['user_id']);
set_message('Category updated successfully!', 'success');
} elseif ($action_type === 'delete' && $record_id) {
$classification->deleteCategory($record_id);
set_message('Category deleted successfully!', 'success');
}
}
} catch (Exception $e) {
set_message('Error: ' . $e->getMessage(), 'danger');
}
header('Location: manage_classifications.php');
exit();
}
// Fetch data for display
$dataTypes = $classification->getAllDataTypes();
$categories = $classification->getAllCategories();
// Prepare data for editing if action is 'edit'
$editDataType = null;
$editCategory = null;
if ($action === 'edit' && $id) {
if ($_GET['type'] === 'datatype') {
$editDataType = $classification->getDataTypeById($id);
} elseif ($_GET['type'] === 'category') {
$editCategory = $classification->getCategoryById($id);
}
}
?>
<!DOCTYPE html>
<html lang="en">
<!-- Header -->
<?php
// Include header file for admin pages
include_once("../includes/header_admin.php");
?>
<body>
<div class="wrapper">
<!-- Sidebar -->
<?php
// Include header file for admin pages
include_once("../includes/nav_admin.php");
?>
<!-- Main Content -->
<div class="main-content">
<nav class="navbar navbar-expand-lg navbar-light bg-light mb-4 rounded-3">
<div class="container-fluid">
<a class="navbar-brand" href="#">Manage Classifications</a>
<div class="d-flex">
<span class="navbar-text me-3">
Welcome, <?php echo htmlspecialchars($_SESSION['username']); ?>!
</span>
</div>
</div>
</nav>
<?php
// Display session messages
if (isset($_SESSION['message'])) {
echo '<div class="alert alert-' . $_SESSION['message_type'] . ' alert-dismissible fade show rounded" role="alert">' . htmlspecialchars($_SESSION['message']) . '<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button></div>';
unset($_SESSION['message']);
unset($_SESSION['message_type']);
}
?>
<!-- Data Type Management -->
<div class="card mb-4">
<div class="card-header bg-primary text-white">
<h5 class="mb-0">Manage Data Types</h5>
</div>
<div class="card-body">
<form action="manage_classifications.php" method="POST" class="mb-4">
<input type="hidden" name="type" value="datatype">
<input type="hidden" name="action_type" value="<?php echo $editDataType ? 'edit' : 'add'; ?>">
<?php if ($editDataType): ?>
<input type="hidden" name="record_id" value="<?php echo htmlspecialchars($editDataType['pkdspstds_id']); ?>">
<?php endif; ?>
<div class="row g-3 align-items-end">
<div class="col-md-5">
<label for="dataTypeNameEn" class="form-label">Data Type Name (English)</label>
<input type="text" class="form-control rounded" id="dataTypeNameEn" name="name_en" value="<?php echo htmlspecialchars($editDataType['dspstds_name_en'] ?? ''); ?>" required>
</div>
<div class="col-md-5">
<label for="dataTypeNameKh" class="form-label">Data Type Name (Khmer)</label>
<input type="text" class="form-control rounded" id="dataTypeNameKh" name="name_kh" value="<?php echo htmlspecialchars($editDataType['dspstds_name_kh'] ?? ''); ?>">
</div>
<div class="col-md-2">
<button type="submit" class="btn btn-primary w-100 rounded">
<i class="fas fa-<?php echo $editDataType ? 'save' : 'plus'; ?> me-2"></i> <?php echo $editDataType ? 'Update' : 'Add'; ?> Data Type
</button>
</div>
</div>
</form>
<div class="table-responsive">
<table class="table table-hover table-striped">
<thead>
<tr>
<th>ID</th>
<th>English Name</th>
<th>Khmer Name</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php if (!empty($dataTypes)): ?>
<?php foreach ($dataTypes as $dataType): ?>
<tr>
<td><?php echo htmlspecialchars($dataType['pkdspstds_id']); ?></td>
<td><?php echo htmlspecialchars($dataType['dspstds_name_en']); ?></td>
<td><?php echo htmlspecialchars($dataType['dspstds_name_kh']); ?></td>
<td>
<a href="manage_classifications.php?action=edit&type=datatype&id=<?php echo htmlspecialchars($dataType['pkdspstds_id']); ?>" class="btn btn-sm btn-warning rounded btn-action">
<i class="fas fa-edit"></i>
</a>
<form action="manage_classifications.php" method="POST" class="d-inline" onsubmit="return confirm('Are you sure you want to delete this Data Type?');">
<input type="hidden" name="type" value="datatype">
<input type="hidden" name="action_type" value="delete">
<input type="hidden" name="record_id" value="<?php echo htmlspecialchars($dataType['pkdspstds_id']); ?>">
<button type="submit" class="btn btn-sm btn-danger rounded btn-action">
<i class="fas fa-trash-alt"></i>
</button>
</form>
</td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr>
<td colspan="4" class="text-center">No data types found.</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
<!-- Category Management -->
<div class="card">
<div class="card-header bg-success text-white">
<h5 class="mb-0">Manage Categories</h5>
</div>
<div class="card-body">
<form action="manage_classifications.php" method="POST" class="mb-4">
<input type="hidden" name="type" value="category">
<input type="hidden" name="action_type" value="<?php echo $editCategory ? 'edit' : 'add'; ?>">
<?php if ($editCategory): ?>
<input type="hidden" name="record_id" value="<?php echo htmlspecialchars($editCategory['pkdspscate_id']); ?>">
<?php endif; ?>
<div class="row g-3">
<div class="col-md-6">
<label for="categoryTitleEn" class="form-label">Category Title (English)</label>
<input type="text" class="form-control rounded" id="categoryTitleEn" name="name_en" value="<?php echo htmlspecialchars($editCategory['dspscate_title_en'] ?? ''); ?>" required>
</div>
<div class="col-md-6">
<label for="categoryDetails" class="form-label">Details</label>
<textarea class="form-control rounded-3" id="categoryDetails" name="details" rows="3"><?php echo htmlspecialchars($editCategory['dspscate_details'] ?? ''); ?></textarea>
</div>
<div class="col-12">
<button type="submit" class="btn btn-success rounded">
<i class="fas fa-<?php echo $editCategory ? 'save' : 'plus'; ?> me-2"></i> <?php echo $editCategory ? 'Update' : 'Add'; ?> Category
</button>
</div>
</div>
</form>
<div class="table-responsive">
<table class="table table-hover table-striped">
<thead>
<tr>
<th>ID</th>
<th>English Title</th>
<th>Details</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php if (!empty($categories)): ?>
<?php foreach ($categories as $category): ?>
<tr>
<td><?php echo htmlspecialchars($category['pkdspscate_id']); ?></td>
<td><?php echo htmlspecialchars($category['dspscate_title_en']); ?></td>
<td><?php echo htmlspecialchars($category['dspscate_details']); ?></td>
<td>
<a href="manage_classifications.php?action=edit&type=category&id=<?php echo htmlspecialchars($category['pkdspscate_id']); ?>" class="btn btn-sm btn-warning rounded btn-action">
<i class="fas fa-edit"></i>
</a>
<form action="manage_classifications.php" method="POST" class="d-inline" onsubmit="return confirm('Are you sure you want to delete this Category?');">
<input type="hidden" name="type" value="category">
<input type="hidden" name="action_type" value="delete">
<input type="hidden" name="record_id" value="<?php echo htmlspecialchars($category['pkdspscate_id']); ?>">
<button type="submit" class="btn btn-sm btn-danger rounded btn-action">
<i class="fas fa-trash-alt"></i>
</button>
</form>
</td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr>
<td colspan="4" class="text-center">No categories found.</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<!-- Footer -->
<?php
// Include Footer file for owner pages
include_once("../includes/footer_admin.php");
?>
</body>
</html>

213
admin/manage_contactus.php Normal file
View File

@@ -0,0 +1,213 @@
<?php
// Start session and include necessary files
session_start();
require_once '../config.php';
require_once '../includes/auth.php';
require_once '../classes/Contactus.php'; // This class handles dsps_tbl_feedback
// Redirect if not logged in or not a DAC Staff
redirect_if_not_logged_in('../index.php');
redirect_if_not_role('DAC Staff', '../index.php');
// Initialize Contactus class (for feedback management)
$contactUs = new Contactus($pdo);
$action = $_GET['action'] ?? '';
$id = $_GET['id'] ?? null;
// Handle form submissions for responding to feedback
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$action_type = $_POST['action_type'] ?? ''; // 'respond' or 'delete'
$feedback_id = $_POST['feedback_id'] ?? null;
$respond_text = trim($_POST['respond_text'] ?? '');
$status = $_POST['status'] ?? 'New'; // Default status for response
if ($action_type === 'respond') {
if (empty($respond_text)) {
set_message('Response text cannot be empty.', 'danger');
} elseif ($feedback_id) {
try {
$contactUs->respondToFeedback($feedback_id, $respond_text, $status, $_SESSION['user_id']);
set_message('Feedback responded to successfully!', 'success');
} catch (Exception $e) {
set_message('Error responding to feedback: ' . $e->getMessage(), 'danger');
}
}
} elseif ($action_type === 'delete') {
if ($feedback_id) {
try {
$contactUs->deleteFeedback($feedback_id);
set_message('Feedback deleted successfully!', 'success');
} catch (Exception $e) {
set_message('Error deleting feedback: ' . $e->getMessage(), 'danger');
}
}
}
header('Location: manage_contactus.php');
exit();
}
// Fetch feedback entries for display
$feedbackEntries = $contactUs->getAllFeedback();
// Prepare data for responding if action is 'respond'
$respondFeedback = null;
if ($action === 'respond' && $id) {
$respondFeedback = $contactUs->getFeedbackById($id);
}
?>
<!DOCTYPE html>
<html lang="en">
<!-- Header -->
<?php
// Include header file for admin pages
include_once("../includes/header_admin.php");
?>
<body>
<div class="wrapper">
<!-- Sidebar -->
<?php
// Include header file for admin pages
include_once("../includes/nav_admin.php");
?>
<!-- Main Content -->
<div class="main-content">
<nav class="navbar navbar-expand-lg navbar-light bg-light mb-4 rounded-3">
<div class="container-fluid">
<a class="navbar-brand" href="#">Manage Contact Us (Feedback)</a>
<div class="d-flex">
<span class="navbar-text me-3">
Welcome, <?php echo htmlspecialchars($_SESSION['username']); ?>!
</span>
</div>
</div>
</nav>
<?php
// Display session messages
if (isset($_SESSION['message'])) {
echo '<div class="alert alert-' . $_SESSION['message_type'] . ' alert-dismissible fade show rounded" role="alert">' . htmlspecialchars($_SESSION['message']) . '<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button></div>';
unset($_SESSION['message']);
unset($_SESSION['message_type']);
}
?>
<?php if ($respondFeedback): ?>
<div class="card mb-4">
<div class="card-header bg-primary text-white">
<h5 class="mb-0">Respond to Feedback #<?php echo htmlspecialchars($respondFeedback['pkdspsfb_id']); ?></h5>
</div>
<div class="card-body">
<div class="mb-3">
<strong>From:</strong> <?php echo htmlspecialchars($respondFeedback['dspsfb_name']); ?> (<?php echo htmlspecialchars($respondFeedback['dspsfb_email']); ?>)
</div>
<div class="mb-3">
<strong>Submitted On:</strong> <?php echo htmlspecialchars($respondFeedback['dspsfb_reg_datetime']); ?>
</div>
<div class="mb-3">
<strong>Message:</strong>
<p class="border p-3 rounded-3 bg-light"><?php echo nl2br(htmlspecialchars($respondFeedback['dspsfb_body_text'])); ?></p>
</div>
<?php if (!empty($respondFeedback['dspsfb_respond_text'])): ?>
<div class="mb-3">
<strong>Previous Response:</strong>
<p class="border p-3 rounded-3 bg-light text-muted"><?php echo nl2br(htmlspecialchars($respondFeedback['dspsfb_respond_text'])); ?></p>
</div>
<?php endif; ?>
<form action="manage_contactus.php" method="POST">
<input type="hidden" name="action_type" value="respond">
<input type="hidden" name="feedback_id" value="<?php echo htmlspecialchars($respondFeedback['pkdspsfb_id']); ?>">
<div class="mb-3">
<label for="respond_text" class="form-label">Your Response</label>
<textarea class="form-control rounded-3" id="respond_text" name="respond_text" rows="5" required><?php echo htmlspecialchars($respondFeedback['dspsfb_respond_text'] ?? ''); ?></textarea>
</div>
<div class="mb-3">
<label for="status" class="form-label">Status</label>
<select class="form-select rounded" id="status" name="status" required>
<option value="New" <?php echo ($respondFeedback['dspsfb_status'] == 'New') ? 'selected' : ''; ?>>New</option>
<option value="In Progress" <?php echo ($respondFeedback['dspsfb_status'] == 'In Progress') ? 'selected' : ''; ?>>In Progress</option>
<option value="Resolved" <?php echo ($respondFeedback['dspsfb_status'] == 'Resolved') ? 'selected' : ''; ?>>Resolved</option>
<option value="Archived" <?php echo ($respondFeedback['dspsfb_status'] == 'Archived') ? 'selected' : ''; ?>>Archived</option>
</select>
</div>
<button type="submit" class="btn btn-primary rounded">
<i class="fas fa-reply me-2"></i> Send Response
</button>
<a href="manage_contactus.php" class="btn btn-secondary rounded ms-2">Cancel</a>
</form>
</div>
</div>
<?php endif; ?>
<div class="card">
<div class="card-header text-white" style="background-color: #28a745;">
<h5 class="mb-0">All Feedback Messages</h5>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover table-striped">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Email</th>
<th>Message</th>
<th>Status</th>
<th>Submitted On</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php if (!empty($feedbackEntries)): ?>
<?php foreach ($feedbackEntries as $feedback): ?>
<tr>
<td><?php echo htmlspecialchars($feedback['pkdspsfb_id']); ?></td>
<td><?php echo htmlspecialchars($feedback['dspsfb_name']); ?></td>
<td><?php echo htmlspecialchars($feedback['dspsfb_email']); ?></td>
<td><?php echo htmlspecialchars(substr($feedback['dspsfb_body_text'], 0, 100)) . (strlen($feedback['dspsfb_body_text']) > 100 ? '...' : ''); ?></td>
<td><span class="badge bg-<?php
if ($feedback['dspsfb_status'] == 'New') echo 'danger';
else if ($feedback['dspsfb_status'] == 'In Progress') echo 'warning';
else if ($feedback['dspsfb_status'] == 'Resolved') echo 'success';
else echo 'secondary';
?>"><?php echo htmlspecialchars($feedback['dspsfb_status']); ?></span></td>
<td><?php echo htmlspecialchars($feedback['dspsfb_reg_datetime']); ?></td>
<td>
<a href="manage_contactus.php?action=respond&id=<?php echo htmlspecialchars($feedback['pkdspsfb_id']); ?>" class="btn btn-sm btn-primary rounded btn-action">
<i class="fas fa-reply"></i> Respond
</a>
<form action="manage_contactus.php" method="POST" class="d-inline" onsubmit="return confirm('Are you sure you want to delete this feedback?');">
<input type="hidden" name="action_type" value="delete">
<input type="hidden" name="feedback_id" value="<?php echo htmlspecialchars($feedback['pkdspsfb_id']); ?>">
<button type="submit" class="btn btn-sm btn-danger rounded btn-action">
<i class="fas fa-trash-alt"></i> Delete
</button>
</form>
</td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr>
<td colspan="7" class="text-center">No feedback messages found.</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<!-- Footer -->
<?php
// Include Footer file for owner pages
include_once("../includes/footer_admin.php");
?>
</body>
</html>

View File

@@ -0,0 +1,195 @@
<?php
// admin/manage_datasources.php
session_start();
require_once '../config.php';
require_once '../includes/auth.php';
require_once '../classes/DataSource.php';
redirect_if_not_logged_in('../index.php');
redirect_if_not_role('DAC Staff', '../index.php');
$dataSourceManager = new DataSource($pdo);
$search_query = trim($_GET['search'] ?? '');
$status_filter = trim($_GET['status_filter'] ?? '');
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$action = $_POST['action'] ?? '';
$datasourceId = isset($_POST['datasource_id']) ? (int)$_POST['datasource_id'] : 0;
if ($action === 'change_status' && $datasourceId > 0) {
$newStatus = trim($_POST['new_status'] ?? '');
try {
$dataSourceManager->updateDataSourceStatus($datasourceId, $newStatus, (int) $_SESSION['user_id']);
set_message('Data source status updated successfully.', 'success');
} catch (Exception $e) {
set_message('Failed to update status: ' . $e->getMessage(), 'danger');
}
} elseif ($action === 'delete' && $datasourceId > 0) {
try {
if ($dataSourceManager->deleteDataSource($datasourceId)) {
set_message('Data source deleted.', 'success');
} else {
set_message('Unable to delete data source.', 'danger');
}
} catch (Exception $e) {
set_message('Deletion failed: ' . $e->getMessage(), 'danger');
}
}
$redirectUrl = 'manage_datasources.php';
$params = [];
if ($search_query !== '') {
$params['search'] = urlencode($search_query);
}
if ($status_filter !== '') {
$params['status_filter'] = urlencode($status_filter);
}
if (!empty($params)) {
$redirectUrl .= '?' . http_build_query($params);
}
header('Location: ' . $redirectUrl);
exit();
}
$dataSources = $dataSourceManager->getAllDataSourcesDetailed(
$search_query !== '' ? $search_query : null,
$status_filter !== '' ? $status_filter : null
);
$statuses = ['Active', 'Inactive', 'Pending Review', 'Published'];
$uploadsWebPath = '../uploads/datasources/';
?>
<!DOCTYPE html>
<html lang="en">
<?php include_once("../includes/header_admin.php"); ?>
<body>
<div class="wrapper">
<?php include_once("../includes/nav_admin.php"); ?>
<div class="main-content">
<nav class="navbar navbar-expand-lg navbar-light bg-light mb-4 rounded-3">
<div class="container-fluid">
<a class="navbar-brand" href="#">Manage Data Sources</a>
<div class="d-flex">
<span class="navbar-text me-3">
Welcome, <?php echo htmlspecialchars($_SESSION['username']); ?>!
</span>
</div>
</div>
</nav>
<?php
if (isset($_SESSION['message'])) {
echo '<div class="alert alert-' . $_SESSION['message_type'] . ' alert-dismissible fade show rounded" role="alert">'
. htmlspecialchars($_SESSION['message']) .
'<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button></div>';
unset($_SESSION['message'], $_SESSION['message_type']);
}
?>
<div class="card mb-4">
<div class="card-header bg-primary text-white d-flex justify-content-between align-items-center">
<h5 class="mb-0">All Data Sources</h5>
</div>
<div class="card-body">
<form class="row g-3 align-items-end mb-4" method="GET" action="manage_datasources.php">
<div class="col-md-5">
<label for="searchDataSource" class="form-label">Search</label>
<input type="text" class="form-control rounded" id="searchDataSource" name="search" value="<?= htmlspecialchars($search_query) ?>" placeholder="Title, owner, or type">
</div>
<div class="col-md-4">
<label for="statusFilter" class="form-label">Status</label>
<select class="form-select rounded" id="statusFilter" name="status_filter">
<option value="">All statuses</option>
<?php foreach ($statuses as $status): ?>
<option value="<?= htmlspecialchars($status) ?>" <?= $status_filter === $status ? 'selected' : '' ?>><?= htmlspecialchars($status) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-3">
<button type="submit" class="btn btn-info rounded me-2"><i class="fas fa-filter me-2"></i>Apply</button>
<a href="manage_datasources.php" class="btn btn-outline-secondary rounded"><i class="fas fa-sync-alt me-2"></i>Reset</a>
</div>
</form>
<div class="table-responsive">
<table class="table table-hover align-middle">
<thead class="table-light">
<tr>
<th>ID</th>
<th>Title</th>
<th>Owner</th>
<th>Type</th>
<th>Category</th>
<th>Status</th>
<th>Files</th>
<th>Registered</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php if (!empty($dataSources)): ?>
<?php foreach ($dataSources as $ds): ?>
<tr>
<td><?= htmlspecialchars($ds['pkdspsds_id']) ?></td>
<td><?= htmlspecialchars($ds['dspsds_title_en']) ?></td>
<td><?= htmlspecialchars($ds['owner_name']) ?></td>
<td><?= htmlspecialchars($ds['data_type_name']) ?></td>
<td><?= htmlspecialchars($ds['category_name']) ?></td>
<td>
<form class="d-flex align-items-center gap-2" method="POST">
<input type="hidden" name="datasource_id" value="<?= (int)$ds['pkdspsds_id'] ?>">
<input type="hidden" name="action" value="change_status">
<select class="form-select form-select-sm" name="new_status">
<?php foreach ($statuses as $status): ?>
<option value="<?= htmlspecialchars($status) ?>" <?= $ds['dspsds_status'] === $status ? 'selected' : '' ?>><?= htmlspecialchars($status) ?></option>
<?php endforeach; ?>
</select>
<button type="submit" class="btn btn-sm btn-outline-primary rounded">Update</button>
</form>
</td>
<td>
<?php
$fileColumns = [
'dspsds_filename' => 'Primary',
'dspsds_filename1' => 'Doc 1',
'dspsds_filename2' => 'Doc 2',
'dspsds_filename3' => 'Doc 3',
];
$links = [];
foreach ($fileColumns as $column => $label) {
$fileName = $ds[$column] ?? '';
if ($fileName === '') {
continue;
}
$isUrl = preg_match('/^https?:\\/\\//i', $fileName) === 1;
$target = $isUrl ? $fileName : $uploadsWebPath . rawurlencode($fileName);
$links[] = '<a class="btn btn-sm btn-outline-secondary rounded-pill me-1 mb-1" href="' . htmlspecialchars($target) . '" target="_blank" rel="noopener" title="' . htmlspecialchars($label) . '"><i class="fas fa-paperclip"></i></a>';
}
echo !empty($links) ? implode('', $links) : '<span class="text-muted">—</span>';
?>
</td>
<td><?= htmlspecialchars(date('Y-m-d', strtotime($ds['dspsds_reg_datetime'] ?? 'now'))) ?></td>
<td>
<form method="POST" onsubmit="return confirm('Delete this data source? This cannot be undone.');">
<input type="hidden" name="datasource_id" value="<?= (int)$ds['pkdspsds_id'] ?>">
<input type="hidden" name="action" value="delete">
<button type="submit" class="btn btn-sm btn-outline-danger rounded"><i class="fas fa-trash-alt"></i></button>
</form>
</td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr>
<td colspan="9" class="text-center text-muted py-4">No data sources found.</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<?php include_once("../includes/footer_admin.php"); ?>
</body>
</html>

218
admin/manage_faq.php Normal file
View File

@@ -0,0 +1,218 @@
<?php
// Start session and include necessary files
session_start();
require_once '../config.php';
require_once '../includes/auth.php';
require_once '../classes/Faq.php';
require_once '../classes/User.php'; // To get person_id for the Faq class
// Redirect if not logged in or not a DAC Staff
redirect_if_not_logged_in('../index.php');
redirect_if_not_role('DAC Staff', '../index.php');
// Initialize Faq class
$faq = new Faq($pdo);
$user = new User($pdo); // To get the person_id of the logged-in user
$action = $_GET['action'] ?? '';
$id = $_GET['id'] ?? null;
// Handle form submissions
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$action_type = $_POST['action_type'] ?? ''; // 'add' or 'edit' or 'delete'
$title = trim($_POST['title'] ?? ''); // This is the question
$description = trim($_POST['description'] ?? ''); // This is the answer
$faq_id = $_POST['faq_id'] ?? null;
// Get the person_id of the currently logged-in user
$currentUserDetails = $user->getUserDetails($_SESSION['user_id']);
$fkisp_id_of = $currentUserDetails['fkisp_id_of'];
if ($action_type === 'delete') {
if ($faq_id) {
try {
$faq->deleteFaq($faq_id);
set_message('FAQ entry deleted successfully!', 'success');
} catch (Exception $e) {
set_message('Error deleting FAQ entry: ' . $e->getMessage(), 'danger');
}
}
} else {
if (empty($title) || empty($description)) {
set_message('Question and Answer cannot be empty.', 'danger');
} else {
try {
if ($action_type === 'add') {
$faq->addFaq($title, $description, $_SESSION['user_id'], $fkisp_id_of);
set_message('FAQ entry added successfully!', 'success');
} elseif ($action_type === 'edit' && $faq_id) {
$faq->updateFaq($faq_id, $title, $description, $_SESSION['user_id'], $fkisp_id_of);
set_message('FAQ entry updated successfully!', 'success');
}
} catch (Exception $e) {
set_message('Error: ' . $e->getMessage(), 'danger');
}
}
}
header('Location: manage_faq.php');
exit();
}
// Fetch FAQ entries for display
$faqEntries = $faq->getAllFaqs();
// Prepare data for editing if action is 'edit'
$editFaq = null;
if ($action === 'edit' && $id) {
$editFaq = $faq->getFaqById($id);
}
?>
<!DOCTYPE html>
<html lang="en">
<!-- Header -->
<?php
// Include header file for admin pages
include_once("../includes/header_admin.php");
?>
<body>
<div class="wrapper">
<!-- Sidebar -->
<?php
// Include header file for admin pages
include_once("../includes/nav_admin.php");
?>
<!-- Main Content -->
<div class="main-content">
<nav class="navbar navbar-expand-lg navbar-light bg-light mb-4 rounded-3">
<div class="container-fluid">
<a class="navbar-brand" href="#">Manage FAQ</a>
<div class="d-flex">
<span class="navbar-text me-3">
Welcome, <?php echo htmlspecialchars($_SESSION['username']); ?>!
</span>
</div>
</div>
</nav>
<?php
// Display session messages
if (isset($_SESSION['message'])) {
echo '<div class="alert alert-' . $_SESSION['message_type'] . ' alert-dismissible fade show rounded" role="alert">' . htmlspecialchars($_SESSION['message']) . '<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button></div>';
unset($_SESSION['message']);
unset($_SESSION['message_type']);
}
?>
<div class="card mb-4">
<div class="card-header bg-primary text-white">
<h5 class="mb-0"><?php echo $editFaq ? 'Edit' : 'Add New'; ?> FAQ Entry</h5>
</div>
<div class="card-body">
<form action="manage_faq.php" method="POST">
<input type="hidden" name="action_type" value="<?php echo $editFaq ? 'edit' : 'add'; ?>">
<?php if ($editFaq): ?>
<input type="hidden" name="faq_id" value="<?php echo htmlspecialchars($editFaq['pkdspsfaq_id']); ?>">
<?php endif; ?>
<div class="mb-3">
<label for="title" class="form-label">Question</label>
<input type="text" class="form-control rounded" id="title" name="title" value="<?php echo htmlspecialchars($editFaq['dspsfaq_title_en'] ?? ''); ?>" required>
</div>
<div class="mb-3">
<label for="description" class="form-label d-flex justify-content-between align-items-center">
<span>Answer</span>
</label>
<textarea class="form-control rounded-3" id="description" name="description" rows="6" required><?php echo htmlspecialchars($editFaq['dspsfaq_description'] ?? ''); ?></textarea>
<div class="form-text">Rich formatting appears on the public FAQ page—emphasise key steps and link to related resources.</div>
</div>
<button type="submit" class="btn btn-primary rounded">
<i class="fas fa-<?php echo $editFaq ? 'save' : 'plus'; ?> me-2"></i> <?php echo $editFaq ? 'Update' : 'Add'; ?> FAQ
</button>
<?php if ($editFaq): ?>
<a href="manage_faq.php" class="btn btn-secondary rounded ms-2">Cancel Edit</a>
<?php endif; ?>
</form>
</div>
</div>
<div class="card">
<div class="card-header text-white" style="background-color: #28a745;">
<h5 class="mb-0">All FAQ Entries</h5>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover table-striped">
<thead>
<tr>
<th>ID</th>
<th>Question</th>
<th>Answer</th>
<th>Reg. Date</th>
<th>Mod. Date</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php if (!empty($faqEntries)): ?>
<?php foreach ($faqEntries as $entry): ?>
<tr>
<td><?php echo htmlspecialchars($entry['pkdspsfaq_id']); ?></td>
<td><?php echo htmlspecialchars($entry['dspsfaq_title_en']); ?></td>
<td><?php echo htmlspecialchars(substr($entry['dspsfaq_description'], 0, 100)) . (strlen($entry['dspsfaq_description']) > 100 ? '...' : ''); ?></td>
<td><?php echo htmlspecialchars($entry['dspsfaq_reg_datetime']); ?></td>
<td><?php echo htmlspecialchars($entry['dspsfaq_mod_datetime']); ?></td>
<td>
<a href="manage_faq.php?action=edit&id=<?php echo htmlspecialchars($entry['pkdspsfaq_id']); ?>" class="btn btn-sm btn-warning rounded btn-action">
<i class="fas fa-edit"></i>
</a>
<form action="manage_faq.php" method="POST" class="d-inline" onsubmit="return confirm('Are you sure you want to delete this FAQ entry?');">
<input type="hidden" name="action_type" value="delete">
<input type="hidden" name="faq_id" value="<?php echo htmlspecialchars($entry['pkdspsfaq_id']); ?>">
<button type="submit" class="btn btn-sm btn-danger rounded btn-action">
<i class="fas fa-trash-alt"></i>
</button>
</form>
</td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr>
<td colspan="6" class="text-center">No FAQ entries found.</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<!-- Footer -->
<?php
// Include Footer file for owner pages
include_once("../includes/footer_admin.php");
?>
<script src="https://cdn.jsdelivr.net/npm/@ckeditor/ckeditor5-build-classic@38.1.1/build/ckeditor.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function () {
var textarea = document.querySelector('#description');
if (textarea && typeof ClassicEditor !== 'undefined') {
ClassicEditor
.create(textarea, {
toolbar: [
'heading','|','bold','italic','underline','bulletedList','numberedList','blockQuote',
'|','link','insertTable','undo','redo'
]
})
.catch(function (error) {
console.error('Failed to initialise rich text editor', error);
});
}
});
</script>
</body>
</html>

View File

@@ -0,0 +1,310 @@
<?php
// Start session and include necessary files
session_start();
require_once '../config.php';
require_once '../includes/auth.php';
require_once '../classes/DataSource.php'; // Contains permission management methods
// Redirect if not logged in or not a DAC Staff
redirect_if_not_logged_in('../index.php');
redirect_if_not_role('DAC Staff', '../index.php');
// Initialize DataSource class
$dataSourceManager = new DataSource($pdo);
// --- Handle Search and Filter Parameters ---
$search_query = trim($_GET['search'] ?? '');
$filter_status = trim($_GET['status_filter'] ?? '');
// Handle form submissions for updating permission status
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$action_type = $_POST['action_type'] ?? '';
$permission_id = $_POST['permission_id'] ?? null;
if ($action_type === 'update_permission_status' && $permission_id) {
$new_status = trim($_POST['new_status'] ?? '');
$notes = trim($_POST['notes'] ?? '');
// --- SECURITY CHECK: Verify permission ID and associated data source existence ---
try {
$permission_details = $dataSourceManager->getPermissionRequestById($permission_id);
if (!$permission_details) {
set_message("Permission request not found or invalid.", "danger");
header('Location: manage_permissions_admin.php');
exit();
}
// Optional: You could add a check here to ensure the data source itself is still active
// $dataSource = $dataSourceManager->getDataSourceById($permission_details['fkdspsds_id']);
// if (!$dataSource) { /* handle error */ }
if (!in_array($new_status, ['Approved', 'Pending', 'Rejected', 'Revoked'])) {
set_message('Invalid permission status selected.', 'danger');
} else {
// The reg_by for permission updates is the user who is logged in (DAC Staff)
$dataSourceManager->updatePermissionStatus(
(int) $permission_id,
$new_status,
(int) $_SESSION['user_id'],
$notes
);
set_message('Permission status updated successfully!', 'success');
}
} catch (Exception $e) {
set_message('Error updating permission status: ' . $e->getMessage(), 'danger');
}
}
// Redirect to self, preserving search/filter parameters if they exist
$redirect_url = 'manage_permissions_admin.php';
$query_params = [];
if (!empty($search_query)) {
$query_params['search'] = urlencode($search_query);
}
if (!empty($filter_status)) {
$query_params['status_filter'] = urlencode($filter_status);
}
if (!empty($query_params)) {
$redirect_url .= '?' . http_build_query($query_params);
}
header('Location: ' . $redirect_url);
exit();
}
// Fetch all permission requests based on search and filter parameters
$allPermissions = $dataSourceManager->getAllPermissionRequests($filter_status, $search_query);
?>
<!DOCTYPE html>
<html lang="en">
<!-- Header -->
<?php
// Include header file for admin pages
include_once("../includes/header_admin.php");
?>
<body>
<div class="wrapper">
<!-- Sidebar -->
<?php
// Include header file for admin pages
include_once("../includes/nav_admin.php");
?>
<!-- Main Content -->
<div class="main-content">
<nav class="navbar navbar-expand-lg navbar-light bg-light mb-4 rounded-3">
<div class="container-fluid">
<a class="navbar-brand" href="#">Manage All Permissions</a>
<div class="d-flex">
<span class="navbar-text me-3">
Welcome, <?php echo htmlspecialchars($_SESSION['username']); ?>!
</span>
</div>
</div>
</nav>
<?php
// Display session messages
if (isset($_SESSION['message'])) {
echo '<div class="alert alert-' . $_SESSION['message_type'] . ' alert-dismissible fade show rounded-pill" role="alert">' . htmlspecialchars($_SESSION['message']) . '<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button></div>';
unset($_SESSION['message']);
unset($_SESSION['message_type']);
}
?>
<div class="card mb-4">
<div class="card-header bg-primary text-white d-flex justify-content-between align-items-center">
<h5 class="mb-0">All Data Access Requests</h5>
<!-- Removed the "Create Mock Data Source" button -->
</div>
<div class="card-body">
<!-- Search and Filter Form -->
<form action="manage_permissions_admin.php" method="GET" class="mb-4">
<div class="row g-3 align-items-end">
<div class="col-md-5">
<label for="searchPermissionInput" class="form-label visually-hidden">Search Permissions</label>
<input type="text" class="form-control rounded-pill" id="searchPermissionInput" name="search" placeholder="Search by data source, requester, owner..." value="<?= htmlspecialchars($search_query) ?>">
</div>
<div class="col-md-4">
<label for="statusFilter" class="form-label visually-hidden">Filter by Status</label>
<select class="form-select rounded-pill" id="statusFilter" name="status_filter">
<option value="">All Statuses</option>
<option value="Pending" <?= ($filter_status == 'Pending' ? 'selected' : '') ?>>Pending</option>
<option value="Approved" <?= ($filter_status == 'Approved' ? 'selected' : '') ?>>Approved</option>
<option value="Rejected" <?= ($filter_status == 'Rejected' ? 'selected' : '') ?>>Rejected</option>
<option value="Revoked" <?= ($filter_status == 'Revoked' ? 'selected' : '') ?>>Revoked</option>
</select>
</div>
<div class="col-md-3 d-flex justify-content-end">
<button type="submit" class="btn btn-info rounded-pill me-2"><i class="fas fa-filter me-2"></i>Apply Filter</button>
<a href="manage_permissions_admin.php" class="btn btn-secondary rounded-pill"><i class="fas fa-sync-alt me-2"></i>Reset</a>
</div>
</div>
</form>
<div class="table-responsive">
<table class="table table-hover table-striped">
<thead>
<tr>
<th>Req. ID</th>
<th>Data Source</th>
<th>Requester</th>
<th>Data Owner</th>
<th>Permission Type</th>
<th>Request Notes</th>
<th>Proof</th>
<th>Status</th>
<th>Requested On</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php if (!empty($allPermissions)): ?>
<?php foreach ($allPermissions as $permission): ?>
<tr>
<td><?php echo htmlspecialchars($permission['pkdspsdsp_id']); ?></td>
<td><?php echo htmlspecialchars($permission['dspsds_title_en']); ?></td>
<td><?php echo htmlspecialchars($permission['requester_firstname'] . ' ' . $permission['requester_lastname']); ?></td>
<td><?php echo htmlspecialchars($permission['owner_firstname'] . ' ' . $permission['owner_lastname']); ?></td>
<td><?php echo htmlspecialchars($permission['dspsdsp_permission']); ?></td>
<td><?php echo !empty($permission['dspsdsp_notes']) ? nl2br(htmlspecialchars($permission['dspsdsp_notes'])) : '<span class="text-muted">—</span>'; ?></td>
<td>
<?php if (!empty($permission['dspsdsp_proof_path'])): ?>
<?php
$proofPath = $permission['dspsdsp_proof_path'];
$isExternal = preg_match('/^https?:\\/\\//i', $proofPath) === 1;
$linkTarget = $isExternal ? $proofPath : '../uploads/' . $proofPath;
?>
<a href="<?php echo htmlspecialchars($linkTarget); ?>" class="btn btn-sm btn-outline-primary rounded-pill" target="_blank" rel="noopener">
<i class="fas fa-file-pdf me-1"></i> View
</a>
<?php else: ?>
<span class="text-muted">N/A</span>
<?php endif; ?>
</td>
<td>
<span class="badge bg-<?php
if ($permission['dspsdsp_status'] == 'Approved') echo 'success';
else if ($permission['dspsdsp_status'] == 'Pending') echo 'warning';
else if ($permission['dspsdsp_status'] == 'Rejected') echo 'danger';
else echo 'secondary';
?>">
<?php echo htmlspecialchars($permission['dspsdsp_status']); ?>
</span>
</td>
<td><?php echo htmlspecialchars($permission['dspsdsp_reg_datetime']); ?></td>
<td>
<button type="button" class="btn btn-sm btn-info rounded-pill btn-action" data-bs-toggle="modal" data-bs-target="#managePermissionModal"
data-permission-id="<?php echo htmlspecialchars($permission['pkdspsdsp_id']); ?>"
data-data-source="<?php echo htmlspecialchars($permission['dspsds_title_en']); ?>"
data-requester="<?php echo htmlspecialchars($permission['requester_firstname'] . ' ' . $permission['requester_lastname']); ?>"
data-permission-type="<?php echo htmlspecialchars($permission['dspsdsp_permission']); ?>"
data-notes="<?php echo htmlspecialchars($permission['dspsdsp_notes']); ?>"
data-current-status="<?php echo htmlspecialchars($permission['dspsdsp_status']); ?>">
<i class="fas fa-cogs"></i> Manage
</button>
</td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr>
<td colspan="9" class="text-center">No permission requests found.</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<!-- Manage Permission Modal -->
<div class="modal fade" id="managePermissionModal" tabindex="-1" aria-labelledby="managePermissionModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content rounded shadow-lg">
<div class="modal-header text-white rounded-top" style="background-color: #28a745;">
<h5 class="modal-title" id="managePermissionModalLabel">Manage Permission Request</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body p-4">
<form action="manage_permissions_admin.php" method="POST">
<input type="hidden" name="action_type" value="update_permission_status">
<input type="hidden" name="permission_id" id="modalPermissionId">
<div class="mb-3">
<label for="modalDataSource" class="form-label">Data Source</label>
<input type="text" class="form-control rounded-pill" id="modalDataSource" readonly>
</div>
<div class="mb-3">
<label for="modalRequester" class="form-label">Requester</label>
<input type="text" class="form-control rounded-pill" id="modalRequester" readonly>
</div>
<div class="mb-3">
<label for="modalPermissionType" class="form-label">Permission Type</label>
<input type="text" class="form-control rounded-pill" id="modalPermissionType" readonly>
</div>
<div class="mb-3">
<label for="modalRequestNotes" class="form-label">Requester Notes</label>
<textarea class="form-control rounded-3" id="modalRequestNotes" rows="3" readonly></textarea>
</div>
<div class="mb-3">
<label for="newPermissionStatus" class="form-label">Update Status</label>
<select class="form-select rounded-pill" id="newPermissionStatus" name="new_status" required>
<option value="Pending">Pending</option>
<option value="Approved">Approved</option>
<option value="Rejected">Rejected</option>
<option value="Revoked">Revoked</option>
</select>
</div>
<div class="mb-3">
<label for="adminNotes" class="form-label">Admin Notes (Optional)</label>
<textarea class="form-control rounded-3" id="adminNotes" name="notes" rows="3"></textarea>
</div>
<div class="d-grid">
<button type="submit" class="btn btn-primary rounded-pill">Update Permission</button>
</div>
</form>
</div>
</div>
</div>
</div>
<script>
// JavaScript to populate the modal fields when "Manage" button is clicked
var managePermissionModal = document.getElementById('managePermissionModal');
managePermissionModal.addEventListener('show.bs.modal', function (event) {
var button = event.relatedTarget; // Button that triggered the modal
var permissionId = button.getAttribute('data-permission-id');
var dataSource = button.getAttribute('data-data-source');
var requester = button.getAttribute('data-requester');
var permissionType = button.getAttribute('data-permission-type');
var notes = button.getAttribute('data-notes');
var currentStatus = button.getAttribute('data-current-status');
var modalPermissionId = managePermissionModal.querySelector('#modalPermissionId');
var modalDataSource = managePermissionModal.querySelector('#modalDataSource');
var modalRequester = managePermissionModal.querySelector('#modalRequester');
var modalPermissionType = managePermissionModal.querySelector('#modalPermissionType');
var modalRequestNotes = managePermissionModal.querySelector('#modalRequestNotes');
var newPermissionStatusSelect = managePermissionModal.querySelector('#newPermissionStatus');
modalPermissionId.value = permissionId;
modalDataSource.value = dataSource;
modalRequester.value = requester;
modalPermissionType.value = permissionType;
modalRequestNotes.value = notes;
newPermissionStatusSelect.value = currentStatus; // Set default selected option to current status
// Clear admin notes field for new entry
managePermissionModal.querySelector('#adminNotes').value = '';
});
</script>
<!-- Footer -->
<?php
// Include Footer file for owner pages
include_once("../includes/footer_admin.php");
?>
</body>
</html>

258
admin/manage_slides.php Normal file
View File

@@ -0,0 +1,258 @@
<?php
// Start session and include necessary files
session_start();
require_once '../config.php';
require_once '../includes/auth.php';
require_once '../classes/Slide.php';
require_once '../classes/User.php'; // Needed to get fkisp_id_of for slide creation/modification
// Redirect if not logged in or not a DAC Staff
redirect_if_not_logged_in('../index.php');
redirect_if_not_role('DAC Staff', '../index.php');
// Initialize Slide and User classes
$slideManager = new Slide($pdo);
$userManager = new User($pdo);
$action = $_GET['action'] ?? '';
$id = $_GET['id'] ?? null;
// Get current user's person ID for fkisp_id_of
$currentUserDetails = $userManager->getUserDetails($_SESSION['user_id']);
$fkisp_id_of = $currentUserDetails['fkisp_id_of'];
// Handle form submissions
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$action_type = $_POST['action_type'] ?? ''; // 'add' or 'edit' or 'delete'
$title_en = trim($_POST['title_en'] ?? '');
$description = trim($_POST['description'] ?? '');
$slide_id = $_POST['slide_id'] ?? null;
$current_photo = $_POST['current_photo'] ?? ''; // For editing, keep track of existing photo
if ($action_type === 'delete') {
if ($slide_id) {
try {
$slideManager->deleteSlide($slide_id);
set_message('Slide deleted successfully!', 'success');
} catch (Exception $e) {
set_message('Error deleting slide: ' . $e->getMessage(), 'danger');
}
}
} else {
// Handle photo upload
$photoPath = $current_photo; // Default to current photo if not uploading new
if (isset($_FILES['photo']) && $_FILES['photo']['error'] === UPLOAD_ERR_OK) {
try {
$photoPath = $slideManager->handlePhotoUpload($_FILES['photo']);
// If editing and a new photo is uploaded, delete the old one
if ($action_type === 'edit' && !empty($current_photo) && $current_photo !== $photoPath) {
// Ensure the old photo path is not empty and different from the new one
if (!empty($current_photo) && file_exists('../uploads/slides/' . $current_photo)) {
unlink('../uploads/slides/' . $current_photo); // Delete old file
}
}
} catch (Exception $e) {
set_message('Photo upload error: ' . $e->getMessage(), 'danger');
header('Location: manage_slides.php');
exit();
}
} elseif ($action_type === 'add' && (!isset($_FILES['photo']) || $_FILES['photo']['error'] !== UPLOAD_ERR_OK)) {
// For adding, a photo is required.
set_message('Please upload a photo for the slide.', 'danger');
header('Location: manage_slides.php');
exit();
}
if (empty($title_en) || empty($description)) {
set_message('Title and description cannot be empty.', 'danger');
} else {
try {
if ($action_type === 'add') {
$slideManager->addSlide($title_en, $description, $photoPath, $_SESSION['user_id'], $fkisp_id_of);
set_message('Slide added successfully!', 'success');
} elseif ($action_type === 'edit' && $slide_id) {
$slideManager->updateSlide($slide_id, $title_en, $description, $photoPath, $_SESSION['user_id'], $fkisp_id_of);
set_message('Slide updated successfully!', 'success');
}
} catch (Exception $e) {
set_message('Error: ' . $e->getMessage(), 'danger');
}
}
}
header('Location: manage_slides.php');
exit();
}
// Fetch slides for display
$slides = $slideManager->getAllSlides();
// Prepare data for editing if action is 'edit'
$editSlide = null;
if ($action === 'edit' && $id) {
$editSlide = $slideManager->getSlideById($id);
}
?>
<!DOCTYPE html>
<html lang="en">
<!-- Header -->
<?php
// Include header file for admin pages
include_once("../includes/header_admin.php");
?>
<body>
<div class="wrapper">
<!-- Sidebar -->
<?php
// Include header file for admin pages
include_once("../includes/nav_admin.php");
?>
<!-- Main Content -->
<div class="main-content">
<nav class="navbar navbar-expand-lg navbar-light bg-light mb-4 rounded-3">
<div class="container-fluid">
<a class="navbar-brand" href="#">Manage Slides</a>
<div class="d-flex">
<span class="navbar-text me-3">
Welcome, <?php echo htmlspecialchars($_SESSION['username']); ?>!
</span>
</div>
</div>
</nav>
<?php
// Display session messages
if (isset($_SESSION['message'])) {
echo '<div class="alert alert-' . $_SESSION['message_type'] . ' alert-dismissible fade show rounded" role="alert">' . htmlspecialchars($_SESSION['message']) . '<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button></div>';
unset($_SESSION['message']);
unset($_SESSION['message_type']);
}
?>
<div class="card mb-4">
<div class="card-header bg-primary text-white">
<h5 class="mb-0"><?php echo $editSlide ? 'Edit' : 'Add New'; ?> Slide</h5>
</div>
<div class="card-body">
<form action="manage_slides.php" method="POST" enctype="multipart/form-data">
<input type="hidden" name="action_type" value="<?php echo $editSlide ? 'edit' : 'add'; ?>">
<?php if ($editSlide): ?>
<input type="hidden" name="slide_id" value="<?php echo htmlspecialchars($editSlide['pkdspsslide_id']); ?>">
<input type="hidden" name="current_photo" value="<?php echo htmlspecialchars($editSlide['dspsslide_photoname']); ?>">
<?php endif; ?>
<div class="mb-3">
<label for="title_en" class="form-label">Slide Title (English)</label>
<input type="text" class="form-control rounded" id="title_en" name="title_en" value="<?php echo htmlspecialchars($editSlide['dspsslide_title_en'] ?? ''); ?>" required>
</div>
<div class="mb-3">
<label for="description" class="form-label d-flex justify-content-between align-items-center">
<span>Description</span>
</label>
<textarea class="form-control rounded-3" id="description" name="description" rows="5" required><?php echo htmlspecialchars($editSlide['dspsslide_description'] ?? ''); ?></textarea>
<div class="form-text">Formatted text appears on the public carousel, so emphasise key phrases and provide concise summaries.</div>
</div>
<div class="mb-3">
<label for="photo" class="form-label">Slide Photo (JPG, PNG, GIF)</label>
<input type="file" class="form-control rounded" id="photo" name="photo" accept="image/*" <?php echo $editSlide ? '' : 'required'; ?>>
<?php if ($editSlide && !empty($editSlide['dspsslide_photoname'])): ?>
<div class="mt-2">
Current Photo: <img src="../uploads/slides/<?php echo htmlspecialchars($editSlide['dspsslide_photoname']); ?>" alt="Slide Photo" class="slide-img-thumbnail">
</div>
<?php endif; ?>
</div>
<button type="submit" class="btn btn-primary rounded">
<i class="fas fa-<?php echo $editSlide ? 'save' : 'plus'; ?> me-2"></i> <?php echo $editSlide ? 'Update' : 'Add'; ?> Slide
</button>
<?php if ($editSlide): ?>
<a href="manage_slides.php" class="btn btn-secondary rounded ms-2">Cancel Edit</a>
<?php endif; ?>
</form>
</div>
</div>
<div class="card">
<div class="card-header text-white" style="background-color: #28a745;">
<h5 class="mb-0">All Slides</h5>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover table-striped">
<thead>
<tr>
<th>ID</th>
<th>Title</th>
<th>Description</th>
<th>Photo</th>
<th>Reg. Date</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php if (!empty($slides)): ?>
<?php foreach ($slides as $slide): ?>
<tr>
<td><?php echo htmlspecialchars($slide['pkdspsslide_id']); ?></td>
<td><?php echo htmlspecialchars($slide['dspsslide_title_en']); ?></td>
<td><?php echo htmlspecialchars(substr($slide['dspsslide_description'], 0, 100)) . (strlen($slide['dspsslide_description']) > 100 ? '...' : ''); ?></td>
<td>
<?php if (!empty($slide['dspsslide_photoname'])): ?>
<img src="../uploads/slides/<?php echo htmlspecialchars($slide['dspsslide_photoname']); ?>" alt="Slide Photo" class="slide-img-thumbnail">
<?php else: ?>
N/A
<?php endif; ?>
</td>
<td><?php echo htmlspecialchars($slide['dspsslide_reg_datetime']); ?></td>
<td>
<a href="manage_slides.php?action=edit&id=<?php echo htmlspecialchars($slide['pkdspsslide_id']); ?>" class="btn btn-sm btn-warning rounded btn-action">
<i class="fas fa-edit"></i>
</a>
<form action="manage_slides.php" method="POST" class="d-inline" onsubmit="return confirm('Are you sure you want to delete this slide? This action cannot be undone.');">
<input type="hidden" name="action_type" value="delete">
<input type="hidden" name="slide_id" value="<?php echo htmlspecialchars($slide['pkdspsslide_id']); ?>">
<button type="submit" class="btn btn-sm btn-danger rounded btn-action">
<i class="fas fa-trash-alt"></i>
</button>
</form>
</td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr>
<td colspan="6" class="text-center">No slides found.</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<!-- Footer -->
<?php
// Include Footer file for owner pages
include_once("../includes/footer_admin.php");
?>
<script src="https://cdn.jsdelivr.net/npm/@ckeditor/ckeditor5-build-classic@38.1.1/build/ckeditor.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function () {
var textarea = document.querySelector('#description');
if (textarea && typeof ClassicEditor !== 'undefined') {
ClassicEditor
.create(textarea, {
toolbar: [
'heading','|','bold','italic','underline','bulletedList','numberedList','blockQuote',
'|','link','insertTable','undo','redo'
]
})
.catch(function (error) {
console.error('Failed to initialise rich text editor', error);
});
}
});
</script>
</body>
</html>

782
admin/manage_users.php Normal file
View File

@@ -0,0 +1,782 @@
<?php
// Start session and include necessary files
session_start();
require_once '../config.php';
require_once '../includes/auth.php';
require_once '../classes/User.php';
// Redirect if not logged in or not a DAC Staff
redirect_if_not_logged_in('../index.php');
redirect_if_not_role('DAC Staff', '../index.php');
// Initialize User class
$userManager = new User($pdo);
// --- Handle Search and Filter Parameters ---
$search_query = trim($_GET['search'] ?? '');
$filter_status = trim($_GET['status_filter'] ?? ''); // New filter for user status
$filters_active = ($search_query !== '' || $filter_status !== '');
// Handle form submissions
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$action_type = $_POST['action_type'] ?? '';
$user_id = $_POST['user_id'] ?? null;
if ($action_type === 'update_status' && $user_id) {
$new_status = $_POST['new_status'] ?? '';
$requested_r_access = $_POST['can_run_r'] ?? null;
$has_valid_r_flag = in_array($requested_r_access, ['0', '1'], true);
// Prevent a DAC Staff from deactivating themselves
if ($user_id == $_SESSION['user_id'] && $new_status == 'Inactive') {
set_message('You cannot deactivate your own account.', 'danger');
header('Location: manage_users.php');
exit();
}
if (!in_array($new_status, ['DAC Staff', 'Data Contributor', 'Data Owner', 'Data User', 'Inactive'])) {
set_message('Invalid user status selected.', 'danger');
} else {
try {
$admin_user_id = (int) $_SESSION['user_id'];
$userManager->updateUserStatus($user_id, $new_status, $admin_user_id);
if ($has_valid_r_flag) {
$userManager->updateUserRJupyterAccess($user_id, $requested_r_access === '1', $admin_user_id);
if ((int)$user_id === (int)$_SESSION['user_id']) {
$_SESSION['can_run_r'] = ($requested_r_access === '1');
}
}
$message = 'User status updated successfully!';
if ($has_valid_r_flag) {
$message = 'User status and R/Jupyter access updated successfully!';
}
set_message($message, 'success');
} catch (Exception $e) {
set_message('Error updating user status: ' . $e->getMessage(), 'danger');
}
}
} elseif ($action_type === 'reset_password' && $user_id) {
$new_password = $_POST['new_password'] ?? '';
$confirm_password = $_POST['confirm_password'] ?? '';
if (empty($new_password) || empty($confirm_password)) {
set_message('Please provide and confirm the new password.', 'danger');
} elseif ($new_password !== $confirm_password) {
set_message('Passwords do not match. Please try again.', 'danger');
} elseif (strlen($new_password) < 8) {
set_message('Password must be at least 8 characters long.', 'danger');
} else {
try {
$admin_user_id = (int) $_SESSION['user_id'];
$userManager->changePassword((int)$user_id, $new_password, $admin_user_id);
set_message('Password reset successfully.', 'success');
} catch (Exception $e) {
set_message('Error resetting password: ' . $e->getMessage(), 'danger');
}
}
} elseif ($action_type === 'add_user') {
// --- Handle Add New User Submission ---
$id_card = trim($_POST['id_card'] ?? '');
$first_name_en = trim($_POST['first_name_en'] ?? '');
$last_name_en = trim($_POST['last_name_en'] ?? '');
$sex = trim($_POST['sex'] ?? '');
$dob = trim($_POST['dob'] ?? '');
$phone_number = trim($_POST['phone_number'] ?? '');
$email = trim($_POST['email'] ?? '');
$username = trim($_POST['username'] ?? '');
$password = $_POST['password'] ?? '';
$confirm_password = $_POST['confirm_password'] ?? '';
$user_role_new = trim($_POST['user_role_new'] ?? 'Data User'); // Role for new user
// Server-side validation for new user
if (empty($first_name_en) || empty($last_name_en) || empty($sex) || empty($dob) || empty($username) || empty($password) || empty($confirm_password)) {
set_message("All required fields for new user must be filled.", "danger");
} elseif ($password !== $confirm_password) {
set_message("Passwords do not match for new user.", "danger");
} elseif (!empty($email) && !filter_var($email, FILTER_VALIDATE_EMAIL)) {
set_message("Invalid email format for new user.", "danger");
} else {
// Prepare data for User class
$person_data = [
'id_card' => $id_card,
'first_name_en' => $first_name_en,
'last_name_en' => $last_name_en,
'sex' => $sex,
'dob' => $dob,
'pob' => null, // Add if you collect this
'nationality' => 'Cambodian', // Default or collect
'marital_status' => 'Single', // Default or collect
'phone_number' => $phone_number,
'email' => $email,
'telegram' => null, // Add if you collect this
'note' => null // Add if you collect this
];
$user_data = [
'username' => $username,
'password' => $password,
'status' => 'Data User', // Default status for new registrations
'can_run_r' => !empty($_POST['user_can_run_r'])
];
try {
if ($userManager->registerUser($person_data, $user_data)) {
set_message("New user '" . htmlspecialchars($username) . "' registered successfully!", "success");
} else {
// This else might be redundant if registerUser always throws on failure
set_message("Failed to register new user due to an unknown error.", "danger");
}
} catch (Exception $e) {
set_message('Error registering new user: ' . $e->getMessage(), 'danger');
}
}
}
// Redirect to self, preserving search/filter parameters if they exist
$redirect_url = 'manage_users.php';
$query_params = [];
if (!empty($search_query)) {
$query_params['search'] = urlencode($search_query);
}
if (!empty($filter_status)) {
$query_params['status_filter'] = urlencode($filter_status);
}
if (!empty($query_params)) {
$redirect_url .= '?' . http_build_query($query_params);
}
header('Location: ' . $redirect_url);
exit();
}
// Fetch users based on search and filter parameters
// We will modify getAllUsers in classes/User.php to accept these parameters
$users = $userManager->getAllUsers($search_query, $filter_status);
$totalUsers = count($users);
$activeUsers = 0;
$inactiveUsers = 0;
$dacStaffCount = 0;
$ownerCount = 0;
$contributorCount = 0;
$rAccessCount = 0;
foreach ($users as $user) {
$status = $user['isu_status'] ?? '';
$isActive = $status !== 'Inactive';
if ($isActive) {
$activeUsers++;
} else {
$inactiveUsers++;
}
if ($status === 'DAC Staff') {
$dacStaffCount++;
} elseif ($status === 'Data Owner') {
$ownerCount++;
} elseif ($status === 'Data Contributor') {
$contributorCount++;
}
if (!empty($user['isu_can_run_r'])) {
$rAccessCount++;
}
}
$summaryMetrics = [
[
'label' => 'Total Users',
'value' => $totalUsers,
'icon' => 'fa-users',
'class' => 'bg-primary-subtle text-primary',
'icon_class' => 'text-primary'
],
[
'label' => 'Active Accounts',
'value' => $activeUsers,
'icon' => 'fa-user-check',
'class' => 'bg-success-subtle text-success',
'icon_class' => 'text-success'
],
[
'label' => 'With R/Jupyter',
'value' => $rAccessCount,
'icon' => 'fa-flask',
'class' => 'bg-info-subtle text-info',
'icon_class' => 'text-info'
],
[
'label' => 'Inactive',
'value' => $inactiveUsers,
'icon' => 'fa-user-slash',
'class' => 'bg-warning-subtle text-warning',
'icon_class' => 'text-warning'
],
];
?>
<!DOCTYPE html>
<html lang="en">
<!-- Header -->
<?php
// Include header file for admin pages
include_once("../includes/header_admin.php");
?>
<style>
.summary-cards .card {
border: none;
border-left: 4px solid rgba(0, 0, 0, 0.05);
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.summary-cards .card:hover {
transform: translateY(-4px);
box-shadow: 0 0.75rem 1.5rem rgba(15, 68, 130, 0.15);
}
.summary-icon {
font-size: 1.75rem;
width: 3.25rem;
height: 3.25rem;
display: grid;
place-items: center;
border-radius: 0.85rem;
background: rgba(255, 255, 255, 0.6);
}
.summary-value {
font-size: 1.75rem;
font-weight: 700;
letter-spacing: -0.02em;
}
.summary-label {
text-transform: uppercase;
letter-spacing: 0.08em;
font-size: 0.75rem;
font-weight: 600;
}
.status-badges .badge {
font-weight: 600;
padding: 0.45rem 0.65rem;
}
.table thead th {
background-color: #f1f5f9;
border-bottom: none;
text-transform: uppercase;
font-size: 0.75rem;
letter-spacing: 0.08em;
}
.table-hover tbody tr:hover {
background-color: rgba(20, 86, 185, 0.05);
}
.table td {
vertical-align: middle;
}
.table-responsive {
scrollbar-width: thin;
}
.table-responsive::-webkit-scrollbar {
height: 6px;
}
.table-responsive::-webkit-scrollbar-thumb {
background-color: rgba(0, 0, 0, 0.2);
border-radius: 3px;
}
.btn-action-group .btn {
min-width: 9rem;
}
@media (max-width: 767.98px) {
.summary-value {
font-size: 1.4rem;
}
.btn-action-group .btn {
min-width: auto;
}
}
</style>
<body>
<div class="wrapper">
<!-- Sidebar -->
<?php
// Include header file for admin pages
include_once("../includes/nav_admin.php");
?>
<!-- Main Content -->
<div class="main-content">
<nav class="navbar navbar-expand-lg navbar-light bg-light mb-4 rounded-3">
<div class="container-fluid">
<a class="navbar-brand" href="#">Manage Users</a>
<div class="d-flex align-items-center gap-2">
<span class="navbar-text d-none d-md-inline">
Welcome, <?php echo htmlspecialchars($_SESSION['username']); ?>!
</span>
<button class="btn btn-outline-primary btn-sm d-md-none" type="button" data-bs-toggle="collapse" data-bs-target="#manageUsersToolbar" aria-expanded="false" aria-controls="manageUsersToolbar">
<i class="fas fa-sliders-h"></i>
</button>
</div>
</div>
</nav>
<?php
// Display session messages
if (isset($_SESSION['message'])) {
echo '<div class="alert alert-' . $_SESSION['message_type'] . ' alert-dismissible fade show rounded" role="alert">' . htmlspecialchars($_SESSION['message']) . '<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button></div>';
unset($_SESSION['message']);
unset($_SESSION['message_type']);
}
?>
<div class="summary-cards row g-3 mb-3">
<?php foreach ($summaryMetrics as $metric): ?>
<div class="col-12 col-md-6 col-xl-3">
<div class="card shadow-sm rounded-4 <?php echo $metric['class']; ?>">
<div class="card-body d-flex align-items-center gap-3">
<div class="summary-icon <?php echo htmlspecialchars($metric['icon_class']); ?>">
<i class="fas <?php echo htmlspecialchars($metric['icon']); ?>"></i>
</div>
<div>
<div class="summary-value"><?php echo number_format($metric['value']); ?></div>
<div class="summary-label text-secondary-emphasis">
<?php echo htmlspecialchars($metric['label']); ?>
</div>
</div>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
<div class="status-badges d-flex flex-wrap gap-2 mb-4">
<span class="badge bg-primary-subtle text-primary">DAC Staff: <?php echo number_format($dacStaffCount); ?></span>
<span class="badge bg-info-subtle text-info">Data Owners: <?php echo number_format($ownerCount); ?></span>
<span class="badge bg-primary-subtle text-primary">Contributors: <?php echo number_format($contributorCount); ?></span>
<span class="badge bg-success-subtle text-success">With R/Jupyter: <?php echo number_format($rAccessCount); ?></span>
</div>
<div class="card mb-4">
<div class="card-header bg-primary text-white d-flex justify-content-between align-items-center">
<h5 class="mb-0">All Registered Users</h5>
<div class="d-flex gap-2">
<button type="button" class="btn btn-outline-light d-none d-md-inline-flex rounded" data-bs-toggle="collapse" data-bs-target="#manageUsersToolbar" aria-expanded="<?php echo $filters_active ? 'true' : 'false'; ?>" aria-controls="manageUsersToolbar">
<i class="fas fa-filter me-2"></i>Filters
</button>
<button type="button" class="btn btn-success rounded" data-bs-toggle="modal" data-bs-target="#addUserModal">
<i class="fas fa-user-plus me-2"></i> Add New User
</button>
</div>
</div>
<div class="card-body">
<!-- Search and Filter Form -->
<div class="collapse<?php echo $filters_active ? ' show' : ''; ?>" id="manageUsersToolbar">
<form action="manage_users.php" method="GET" class="mb-4">
<div class="row g-3 align-items-end">
<div class="col-12 col-md-5">
<label for="searchUserInput" class="form-label visually-hidden">Search Users</label>
<div class="input-group shadow-sm rounded-pill overflow-hidden">
<span class="input-group-text bg-white border-0 text-muted">
<i class="fas fa-magnifying-glass"></i>
</span>
<input type="text" class="form-control border-0" id="searchUserInput" name="search" placeholder="Search by username, name, email..." value="<?= htmlspecialchars($search_query) ?>" />
</div>
</div>
<div class="col-12 col-md-4">
<label for="statusFilter" class="form-label visually-hidden">Filter by Role</label>
<div class="input-group shadow-sm rounded-pill overflow-hidden">
<span class="input-group-text bg-white border-0 text-muted">
<i class="fas fa-layer-group"></i>
</span>
<select class="form-select border-0" id="statusFilter" name="status_filter">
<option value="">All Roles</option>
<option value="DAC Staff" <?= ($filter_status == 'DAC Staff' ? 'selected' : '') ?>>DAC Staff</option>
<option value="Data Contributor" <?= ($filter_status == 'Data Contributor' ? 'selected' : '') ?>>Data Contributor</option>
<option value="Data Owner" <?= ($filter_status == 'Data Owner' ? 'selected' : '') ?>>Data Owner</option>
<option value="Data User" <?= ($filter_status == 'Data User' ? 'selected' : '') ?>>Data User</option>
<option value="Inactive" <?= ($filter_status == 'Inactive' ? 'selected' : '') ?>>Inactive</option>
</select>
</div>
</div>
<div class="col-12 col-md-3">
<div class="d-flex gap-2 justify-content-md-end">
<button type="submit" class="btn btn-info rounded-pill flex-fill flex-md-grow-0 px-md-4">
<i class="fas fa-filter me-2"></i>Apply
</button>
<a href="manage_users.php" class="btn btn-outline-secondary rounded-pill flex-fill flex-md-grow-0 px-md-4">
<i class="fas fa-sync-alt me-2"></i>Reset
</a>
</div>
</div>
</div>
</form>
</div>
<div class="table-responsive">
<table class="table table-striped table-hover align-middle">
<thead>
<tr>
<th>No.</th>
<th>Username</th>
<th>Full Name</th>
<th>Email</th>
<th>Phone</th>
<th>Current Role</th>
<th>R/Jupyter</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php if (!empty($users)): ?>
<?php $rowNumber = 1; ?>
<?php foreach ($users as $user): ?>
<tr>
<td><?php echo $rowNumber++; ?></td>
<td><?php echo htmlspecialchars($user['isu_name']); ?></td>
<td><?php echo htmlspecialchars($user['isp_firstname_en'] . ' ' . $user['isp_lastname_en']); ?></td>
<td><?php echo htmlspecialchars($user['isp_email'] ?? 'N/A'); ?></td>
<td><?php echo htmlspecialchars($user['isp_phone_number'] ?? 'N/A'); ?></td>
<td>
<?php
$roleClass = 'bg-secondary-subtle text-secondary';
if ($user['isu_status'] == 'DAC Staff') {
$roleClass = 'bg-danger-subtle text-danger';
} elseif ($user['isu_status'] == 'Data Owner') {
$roleClass = 'bg-info-subtle text-info';
} elseif ($user['isu_status'] == 'Data User') {
$roleClass = 'bg-success-subtle text-success';
} elseif ($user['isu_status'] == 'Data Contributor') {
$roleClass = 'bg-primary-subtle text-primary';
}
?>
<span class="badge rounded-pill <?php echo $roleClass; ?>">
<?php echo htmlspecialchars($user['isu_status']); ?>
</span>
</td>
<td>
<?php if (!empty($user['isu_can_run_r'])): ?>
<span class="badge rounded-pill bg-success-subtle text-success">Enabled</span>
<?php else: ?>
<span class="badge rounded-pill bg-secondary-subtle text-secondary">Disabled</span>
<?php endif; ?>
</td>
<td>
<div class="d-flex flex-wrap gap-2 btn-action-group">
<button type="button"
class="btn btn-outline-primary rounded-pill"
data-bs-toggle="modal"
data-bs-target="#editUserModal"
data-bs-tooltip="true"
title="Change role or adjust R/Jupyter access"
data-user-id="<?php echo htmlspecialchars($user['pkisu_id']); ?>"
data-username="<?php echo htmlspecialchars($user['isu_name']); ?>"
data-current-status="<?php echo htmlspecialchars($user['isu_status']); ?>"
data-can-run-r="<?php echo !empty($user['isu_can_run_r']) ? '1' : '0'; ?>">
<i class="fas fa-user-gear"></i>
</button>
<button type="button"
class="btn btn-outline-danger rounded-pill"
data-bs-toggle="modal"
data-bs-target="#resetPasswordModal"
data-bs-tooltip="true"
title="Set a new password for this account"
data-user-id="<?php echo htmlspecialchars($user['pkisu_id']); ?>"
data-username="<?php echo htmlspecialchars($user['isu_name']); ?>">
<i class="fas fa-key"></i>
</button>
</div>
</td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr>
<td colspan="8" class="text-center py-4 text-muted">No users match the current filters.</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<!-- Edit User Role Modal -->
<div class="modal fade" id="editUserModal" tabindex="-1" aria-labelledby="editUserModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content rounded shadow-lg">
<div class="modal-header text-white rounded-top" style="background-color: #28a745;">
<h5 class="modal-title" id="editUserModalLabel">Change User Role</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body p-4">
<form action="manage_users.php" method="POST">
<input type="hidden" name="action_type" value="update_status">
<input type="hidden" name="user_id" id="modalUserId">
<div class="mb-3">
<label for="modalUsername" class="form-label">Username</label>
<input type="text" class="form-control rounded" id="modalUsername" readonly>
</div>
<div class="mb-3">
<label for="modalCurrentStatus" class="form-label">Current Role</label>
<input type="text" class="form-control rounded" id="modalCurrentStatus" readonly>
</div>
<div class="mb-3">
<label for="newStatus" class="form-label">New Role</label>
<select class="form-select rounded" id="newStatus" name="new_status" required>
<option value="Data User">Data User</option>
<option value="Data Contributor">Data Contributor</option>
<option value="Data Owner">Data Owner</option>
<option value="DAC Staff">DAC Staff</option>
<option value="Inactive">Inactive</option>
</select>
</div>
<div class="mb-3">
<label for="modalRAccess" class="form-label">R/Jupyter Access</label>
<select class="form-select rounded" id="modalRAccess" name="can_run_r" required>
<option value="1">Enabled</option>
<option value="0">Disabled</option>
</select>
</div>
<div class="d-grid">
<button type="submit" class="btn btn-primary rounded">Update Role</button>
</div>
</form>
</div>
</div>
</div>
</div>
<!-- Reset Password Modal -->
<div class="modal fade" id="resetPasswordModal" tabindex="-1" aria-labelledby="resetPasswordModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content rounded shadow-lg">
<div class="modal-header text-white rounded-top" style="background-color: #28a745;">
<h5 class="modal-title" id="resetPasswordModalLabel">Reset User Password</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body p-4">
<form action="manage_users.php" method="POST" id="resetPasswordForm" autocomplete="off">
<input type="hidden" name="action_type" value="reset_password">
<input type="hidden" name="user_id" id="resetUserId">
<div class="mb-3">
<label for="resetUsername" class="form-label">Username</label>
<input type="text" class="form-control rounded" id="resetUsername" readonly>
</div>
<div class="mb-3">
<label for="newPassword" class="form-label">New Password <span class="text-danger">*</span></label>
<input type="password" class="form-control rounded" id="newPassword" name="new_password" required minlength="8">
</div>
<div class="mb-3">
<label for="confirmNewPassword" class="form-label">Confirm New Password <span class="text-danger">*</span></label>
<input type="password" class="form-control rounded" id="confirmNewPassword" name="confirm_password" required minlength="8">
</div>
<div class="alert alert-warning rounded d-flex align-items-start gap-2">
<i class="fas fa-exclamation-triangle mt-1"></i>
<span>Resetting the password will immediately replace the user's existing credentials. Provide the new password to the user securely.</span>
</div>
<div class="d-grid">
<button type="submit" class="btn btn-primary rounded"><i class="fas fa-key me-2"></i>Reset Password</button>
</div>
</form>
</div>
</div>
</div>
</div>
<!-- Add New User Modal -->
<div class="modal fade" id="addUserModal" tabindex="-1" aria-labelledby="addUserModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-lg">
<div class="modal-content rounded shadow-lg">
<div class="modal-header text-white rounded-top" style="background-color: #28a745;">
<h5 class="modal-title" id="addUserModalLabel">Add New User</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body p-4">
<form action="manage_users.php" method="POST" id="addUserForm">
<input type="hidden" name="action_type" value="add_user">
<div class="row">
<div class="col-md-6">
<h6 class="mb-3 text-primary">Personal Information</h6>
<div class="mb-3">
<label for="addFirstName" class="form-label">First Name (EN) <span class="text-danger">*</span></label>
<input type="text" class="form-control rounded" id="addFirstName" name="first_name_en" required>
</div>
<div class="mb-3">
<label for="addLastName" class="form-label">Last Name (EN) <span class="text-danger">*</span></label>
<input type="text" class="form-control rounded" id="addLastName" name="last_name_en" required>
</div>
<div class="mb-3">
<label for="addSex" class="form-label">Sex <span class="text-danger">*</span></label>
<select class="form-select rounded" id="addSex" name="sex" required>
<option value="">Select...</option>
<option value="Male">Male</option>
<option value="Female">Female</option>
<option value="Other">Other</option>
</select>
</div>
<div class="mb-3">
<label for="addDob" class="form-label">Date of Birth <span class="text-danger">*</span></label>
<input type="date" class="form-control rounded" id="addDob" name="dob" required>
</div>
<div class="mb-3">
<label for="addPob" class="form-label">Place of Birth</label>
<input type="text" class="form-control rounded" id="addPob" name="pob">
</div>
<div class="mb-3">
<label for="addNationality" class="form-label">Nationality</label>
<input type="text" class="form-control rounded" id="addNationality" name="nationality" value="Cambodian">
</div>
</div>
<div class="col-md-6">
<h6 class="mb-3 text-primary">Contact Information</h6>
<div class="mb-3">
<label for="addPhoneNumber" class="form-label">Phone Number</label>
<input type="tel" class="form-control rounded" id="addPhoneNumber" name="phone_number">
</div>
<div class="mb-3">
<label for="addEmail" class="form-label">Email address</label>
<input type="email" class="form-control rounded" id="addEmail" name="email">
</div>
<div class="mb-3">
<label for="addTelegram" class="form-label">Telegram</label>
<input type="text" class="form-control rounded" id="addTelegram" name="telegram">
</div>
<div class="mb-3">
<label for="addNote" class="form-label">Note</label>
<textarea class="form-control rounded" id="addNote" name="note" rows="2"></textarea>
</div>
<h6 class="mb-3 text-primary">Account Information</h6>
<div class="mb-3">
<label for="addUsername" class="form-label">Username <span class="text-danger">*</span></label>
<input type="text" class="form-control rounded" id="addUsername" name="username" required>
</div>
<div class="mb-3">
<label for="addPassword" class="form-label">Password <span class="text-danger">*</span></label>
<input type="password" class="form-control rounded" id="addPassword" name="password" required>
</div>
<div class="mb-3">
<label for="addConfirmPassword" class="form-label">Confirm Password <span class="text-danger">*</span></label>
<input type="password" class="form-control rounded" id="addConfirmPassword" name="confirm_password" required>
</div>
<div class="mb-3">
<label for="addUserRoleNew" class="form-label">Assign Role</label>
<select class="form-select rounded" id="addUserRoleNew" name="user_role_new" required>
<option value="Data User">Data User</option>
<option value="Data Contributor">Data Contributor</option>
<option value="Data Owner">Data Owner</option>
<option value="DAC Staff">DAC Staff</option>
<option value="Inactive">Inactive</option>
</select>
</div>
<div class="form-check form-switch mb-3">
<input class="form-check-input" type="checkbox" role="switch" id="addUserRAccess" name="user_can_run_r" value="1">
<label class="form-check-label" for="addUserRAccess">Allow R/Jupyter Access</label>
</div>
</div>
</div>
<div class="d-grid mt-4">
<button type="submit" class="btn btn-primary rounded">Add User</button>
</div>
</form>
</div>
</div>
</div>
</div>
<script>
window.addEventListener('DOMContentLoaded', function () {
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-tooltip="true"]'));
tooltipTriggerList.forEach(function (tooltipTriggerEl) {
if (window.bootstrap && bootstrap.Tooltip) {
new bootstrap.Tooltip(tooltipTriggerEl);
}
});
// JavaScript to populate the modal fields when "Change Role" button is clicked
var editUserModal = document.getElementById('editUserModal');
if (editUserModal) {
editUserModal.addEventListener('show.bs.modal', function (event) {
var button = event.relatedTarget; // Button that triggered the modal
var userId = button.getAttribute('data-user-id');
var username = button.getAttribute('data-username');
var currentStatus = button.getAttribute('data-current-status');
var canRunR = button.getAttribute('data-can-run-r') || '0';
var modalUserId = editUserModal.querySelector('#modalUserId');
var modalUsername = editUserModal.querySelector('#modalUsername');
var modalCurrentStatus = editUserModal.querySelector('#modalCurrentStatus');
var newStatusSelect = editUserModal.querySelector('#newStatus');
var modalRAccess = editUserModal.querySelector('#modalRAccess');
modalUserId.value = userId;
modalUsername.value = username;
modalCurrentStatus.value = currentStatus;
newStatusSelect.value = currentStatus; // Set default selected option to current status
if (modalRAccess) {
modalRAccess.value = canRunR;
}
});
}
// JavaScript for password confirmation in Add New User Modal
var addUserForm = document.getElementById('addUserForm');
if (addUserForm) {
addUserForm.addEventListener('submit', function(event) {
var password = document.getElementById('addPassword').value;
var confirmPassword = document.getElementById('addConfirmPassword').value;
if (password !== confirmPassword) {
alert('Passwords do not match!');
event.preventDefault(); // Prevent form submission
return false;
}
return true;
});
}
// Populate Reset Password modal
var resetPasswordModal = document.getElementById('resetPasswordModal');
if (resetPasswordModal) {
resetPasswordModal.addEventListener('show.bs.modal', function (event) {
var trigger = event.relatedTarget;
var userIdField = document.getElementById('resetUserId');
var usernameField = document.getElementById('resetUsername');
var newPasswordField = document.getElementById('newPassword');
var confirmPasswordField = document.getElementById('confirmNewPassword');
if (trigger && userIdField && usernameField) {
userIdField.value = trigger.getAttribute('data-user-id') || '';
usernameField.value = trigger.getAttribute('data-username') || '';
}
if (newPasswordField) newPasswordField.value = '';
if (confirmPasswordField) confirmPasswordField.value = '';
});
}
var resetPasswordForm = document.getElementById('resetPasswordForm');
if (resetPasswordForm) {
resetPasswordForm.addEventListener('submit', function (event) {
var newPassword = document.getElementById('newPassword').value;
var confirmPassword = document.getElementById('confirmNewPassword').value;
if (newPassword !== confirmPassword) {
alert('Passwords do not match!');
event.preventDefault();
return false;
}
if (newPassword.length < 8) {
alert('Password must be at least 8 characters.');
event.preventDefault();
return false;
}
return true;
});
}
});
</script>
<!-- Footer -->
<?php
// Include Footer file for owner pages
include_once("../includes/footer_admin.php");
?>
</body>
</html>

162
admin/r_in_jupyter.php Normal file
View File

@@ -0,0 +1,162 @@
<?php
// admin/r_in_jupyter.php
session_start();
require_once '../config.php';
require_once '../includes/auth.php';
require_once '../classes/DataSource.php';
require_once '../includes/jupyter_helpers.php';
// Limit access to DAC Staff (Admin role)
redirect_if_not_role('DAC Staff', '../index.php');
$hasRJupyterAccess = has_r_access();
$workspaceSync = ['synced' => [], 'missing' => [], 'workspace_dir' => null];
$workspaceRelativeDir = null;
$workspaceError = null;
if ($hasRJupyterAccess && isset($_SESSION['person_id'])) {
$dataSourceManager = new DataSource($pdo);
try {
$workspaceSync = $dataSourceManager->prepareJupyterWorkspace(
(int) $_SESSION['person_id'],
dirname(__DIR__) . '/uploads/jupyter_workspace'
);
$workspaceRelativeDir = 'datasources/user_' . (int) $_SESSION['person_id'];
} catch (Exception $e) {
$workspaceError = $e->getMessage();
}
}
$jupyterBaseUrl = dsp_jupyter_base_url();
$jupyterToken = dsp_jupyter_token();
$jupyterIframeUrl = dsp_jupyter_iframe_url(
$jupyterBaseUrl,
$jupyterToken,
isset($_SESSION['person_id']) ? (int) $_SESSION['person_id'] : null
);
?>
<!DOCTYPE html>
<html lang="en">
<?php include_once("../includes/header_admin.php"); ?>
<body>
<div class="wrapper">
<?php include_once("../includes/nav_admin.php"); ?>
<div class="main-content">
<nav class="navbar navbar-expand-lg navbar-light bg-light mb-4 rounded-3">
<div class="container-fluid">
<a class="navbar-brand" href="#">R in JupyterHub</a>
<div class="d-flex">
<span class="navbar-text me-3">
Welcome, <?php echo htmlspecialchars($_SESSION['username']); ?>!
</span>
</div>
</div>
</nav>
<div class="card shadow-sm rounded mb-4">
<div class="card-header bg-light rounded-top d-flex align-items-center justify-content-between">
<h5 class="mb-0">Full JupyterLab Access</h5>
<?php if ($hasRJupyterAccess): ?>
<span class="badge bg-success-subtle text-success rounded-pill">
<i class="fas fa-flask me-1"></i> Enabled
</span>
<?php else: ?>
<span class="badge bg-warning-subtle text-warning rounded-pill">
<i class="fas fa-lock me-1"></i> Disabled
</span>
<?php endif; ?>
</div>
<div class="card-body">
<?php if ($hasRJupyterAccess): ?>
<?php if ($workspaceError): ?>
<div class="alert alert-danger rounded mb-3">
<strong>Workspace error:</strong> <?= htmlspecialchars($workspaceError) ?>
</div>
<?php else: ?>
<p class="mb-3">
Approved data sources have been synced to
<code><?= htmlspecialchars($workspaceRelativeDir ?: 'datasources') ?></code>
inside the Jupyter environment. Only files you are approved to use are available.
</p>
<?php if (!empty($workspaceSync['synced'])): ?>
<div class="table-responsive mb-3">
<table class="table table-sm table-striped align-middle">
<thead class="table-light">
<tr>
<th>#</th>
<th>Data Source</th>
<th>Data Type</th>
<th>Category</th>
<th>Filename</th>
</tr>
</thead>
<tbody>
<?php foreach ($workspaceSync['synced'] as $idx => $syncedItem): ?>
<tr>
<td><?= $idx + 1 ?></td>
<td><?= htmlspecialchars($syncedItem['title']) ?></td>
<td><?= htmlspecialchars($syncedItem['data_type'] ?? 'N/A') ?></td>
<td><?= htmlspecialchars($syncedItem['category'] ?? 'N/A') ?></td>
<td><code><?= htmlspecialchars(basename($syncedItem['relative_path'])) ?></code></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php else: ?>
<div class="alert alert-info rounded mb-3">
No approved data sources were found for your account. Use Manage Users to approve access.
</div>
<?php endif; ?>
<?php if (!empty($workspaceSync['missing'])): ?>
<div class="alert alert-warning rounded mb-3">
<strong>Some datasets could not be synced:</strong>
<ul class="mb-0">
<?php foreach ($workspaceSync['missing'] as $missingItem): ?>
<li><?= htmlspecialchars($missingItem['title']) ?> — <?= htmlspecialchars($missingItem['reason']) ?></li>
<?php endforeach; ?>
</ul>
</div>
<?php endif; ?>
<?php endif; ?>
<p class="mb-3">
Use the embedded Jupyter workspace to manage R notebooks, explore uploaded datasets, and
collaborate with Data Owners. This view runs with your admin permissions.
</p>
<div class="ratio ratio-16x9 border rounded overflow-hidden">
<iframe
src="<?= htmlspecialchars($jupyterIframeUrl, ENT_QUOTES, 'UTF-8') ?>"
title="R in JupyterHub for DAC Staff"
allowfullscreen
loading="lazy"
referrerpolicy="no-referrer"
></iframe>
</div>
<p class="mt-3 mb-0">
Prefer the full window? <a href="<?= htmlspecialchars($jupyterIframeUrl, ENT_QUOTES, 'UTF-8') ?>" target="_blank" rel="noopener">Open Jupyter in a new tab</a>.
</p>
<?php else: ?>
<div class="alert alert-warning rounded d-flex align-items-start gap-3">
<i class="fas fa-circle-info mt-1"></i>
<div>
<strong>R in JupyterHub is currently disabled for your account.</strong><br>
Visit <a href="manage_users.php">Manage Users</a> to enable R/Jupyter access for yourself or ask another DAC Staff member to toggle the permission.
</div>
</div>
<p class="mb-0">
Once access is enabled, refresh this page to launch the JupyterLab workspace.
</p>
<?php endif; ?>
</div>
</div>
<div class="alert alert-secondary border-0 bg-light-subtle text-muted small">
Need the current Jupyter configuration? Visit
<a href="../install_config.php#r-in-jupyter-service" class="alert-link">Install &amp; Configuration</a>
for defaults, overrides, and runtime details.
</div>
</div>
</div>
<?php include_once("../includes/footer_admin.php"); ?>
</body>
</html>