DSP Project first push, date: 29/01/2026
This commit is contained in:
88
includes/auth.php
Normal file
88
includes/auth.php
Normal file
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
// includes/auth.php
|
||||
// Handles session management and basic authentication checks.
|
||||
|
||||
// Function to set a session message (for alerts)
|
||||
function set_message($message, $type = 'info') {
|
||||
$_SESSION['message'] = $message;
|
||||
$_SESSION['message_type'] = $type;
|
||||
}
|
||||
/**
|
||||
* Retrieves and clears a session message.
|
||||
* @return array|null The message array or null if no message exists.
|
||||
*/
|
||||
function get_message() {
|
||||
if (isset($_SESSION['message'])) {
|
||||
$message = $_SESSION['message'];
|
||||
unset($_SESSION['message']);
|
||||
return $message;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Function to check if a user is logged in
|
||||
function is_logged_in() {
|
||||
return isset($_SESSION['user_id']);
|
||||
}
|
||||
|
||||
// Function to check if the logged-in user has a specific role
|
||||
function has_role($required_role) {
|
||||
if (!is_logged_in()) {
|
||||
return false;
|
||||
}
|
||||
// For simplicity, this assumes a direct match.
|
||||
// In a real app, you might have an array of roles or more complex logic.
|
||||
return $_SESSION['user_status'] === $required_role;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the current user is allowed to run R/Jupyter integrations.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
function has_r_access(): bool {
|
||||
return !empty($_SESSION['can_run_r']);
|
||||
}
|
||||
|
||||
// Function to redirect if not logged in
|
||||
function redirect_if_not_logged_in($redirect_path = '../index.php') {
|
||||
if (!is_logged_in()) {
|
||||
set_message("Please login to access this page.", "warning");
|
||||
$scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
|
||||
if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO'])) {
|
||||
$scheme = $_SERVER['HTTP_X_FORWARDED_PROTO'];
|
||||
}
|
||||
$host = $_SERVER['HTTP_HOST'] ?? '';
|
||||
if ($host && str_starts_with($redirect_path, '../')) {
|
||||
$path = '/' . ltrim($redirect_path, './');
|
||||
header("Location: {$scheme}://{$host}{$path}");
|
||||
} else {
|
||||
header("Location: " . $redirect_path);
|
||||
}
|
||||
exit();
|
||||
}
|
||||
}
|
||||
|
||||
// Function to redirect if user does not have required role
|
||||
function redirect_if_not_role($required_role, $redirect_path = '../index.php') {
|
||||
if (!has_role($required_role)) {
|
||||
set_message("You do not have permission to access this page.", "danger");
|
||||
header("Location: " . $redirect_path);
|
||||
exit();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirects away if the user lacks R/Jupyter access rights.
|
||||
*
|
||||
* @param string $redirect_path Where to redirect when access is denied.
|
||||
*/
|
||||
function redirect_if_no_r_access($redirect_path = '../index.php') {
|
||||
if (!has_r_access()) {
|
||||
set_message("You do not have R/Jupyter access. Please contact DAC Staff.", "danger");
|
||||
header("Location: " . $redirect_path);
|
||||
exit();
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
8
includes/footer_admin.php
Normal file
8
includes/footer_admin.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
|
||||
<script>
|
||||
function toggleSidebar() {
|
||||
document.querySelector('.sidebar').classList.toggle('show');
|
||||
document.getElementById('overlay').classList.toggle('show');
|
||||
}
|
||||
</script>
|
||||
8
includes/footer_contributor.php
Normal file
8
includes/footer_contributor.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
|
||||
<script>
|
||||
function toggleSidebar() {
|
||||
document.querySelector('.sidebar').classList.toggle('show');
|
||||
document.getElementById('overlay').classList.toggle('show');
|
||||
}
|
||||
</script>
|
||||
8
includes/footer_owner.php
Normal file
8
includes/footer_owner.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
|
||||
<script>
|
||||
function toggleSidebar() {
|
||||
document.querySelector('.sidebar').classList.toggle('show');
|
||||
document.getElementById('overlay').classList.toggle('show');
|
||||
}
|
||||
</script>
|
||||
67
includes/footer_public.php
Normal file
67
includes/footer_public.php
Normal file
@@ -0,0 +1,67 @@
|
||||
<footer class="footer rounded-top">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-4 mb-3">
|
||||
<h5>Introduction to NIPH</h5>
|
||||
<p>The National Institute of Public Health (NIPH) is dedicated to advancing public health through research, education, laboratories, and services. Our data sharing platform aims to provide accessible and reliable health data for informed decision-making.</p>
|
||||
</div>
|
||||
<div class="col-md-4 mb-3">
|
||||
<h5>Contact Us</h5>
|
||||
<ul class="list-unstyled">
|
||||
<li><i class="fas fa-map-marker-alt me-2"></i> St.(289), Phnom Penh, Cambodia</li>
|
||||
<li><i class="fas fa-phone me-2"></i> +855 12 345 678</li>
|
||||
<li><i class="fas fa-envelope me-2"></i> dac@niph.org.kh</li>
|
||||
</ul>
|
||||
<ul class="list-unstyled">
|
||||
<!-- Social Links -->
|
||||
<div class="social-icons ms-lg-3 d-flex align-items-center">
|
||||
<a href="#" class="text-decoration-none text-primary" aria-label="Facebook"><i class="fab fa-facebook-f" style="font-size:24px"></i></a>
|
||||
<a href="#" class="text-decoration-none text-danger" aria-label="YouTube"><i class="fab fa-youtube" style="font-size:24px"></i></a>
|
||||
<a href="#" class="text-decoration-none" aria-label="Telegram" style="color: #0088cc;font-size:24px"><i class="fab fa-telegram-plane"></i></a>
|
||||
</div>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-md-4 mb-3">
|
||||
<h5>Quick Links</h5>
|
||||
<ul class="list-unstyled">
|
||||
<li><a href="index.php?page=home">Home</a></li>
|
||||
<li><a href="index.php?page=classifications">Dataset</a></li>
|
||||
<li><a href="index.php?page=announcements">Announcements</a></li>
|
||||
<li><a href="index.php?page=about">About Us</a></li>
|
||||
<li><a href="index.php?page=contact">Contact Us</a></li>
|
||||
<li><a href="index.php?page=faq">FAQ</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center pt-3 border-top border-light">
|
||||
<p class="mb-0">© <?php echo date('Y'); ?> NIPH Data Sharing Platform. All rights reserved.</p>
|
||||
</div>
|
||||
|
||||
<!-- Bottom Navigation Bar for Mobile -->
|
||||
<br>
|
||||
<nav class="d-md-none fixed-bottom bg-white border-top bottom-nav">
|
||||
<div class="d-flex justify-content-around" style="height: 64px;">
|
||||
<!-- Home -->
|
||||
<a class="d-flex flex-column align-items-center justify-content-center text-center text-decoration-none flex-fill <?= ($page == 'home' ? 'active' : '') ?>" aria-current="page" href="index.php?page=home"><i class='fas fa-home' style='color:#28a745;'></i>
|
||||
<span class="small fw-medium">HOME</span></a>
|
||||
<!-- Classification -->
|
||||
<a class="d-flex flex-column align-items-center justify-content-center text-center text-decoration-none flex-fill <?= ($page == 'classifications' ? 'active' : '') ?>" href="index.php?page=classifications"><i class='fas fa-layer-group' style='color:#28a745;'></i>
|
||||
<span class="small fw-medium">DATASET</span></a>
|
||||
<!-- Announcement -->
|
||||
<a class="d-flex flex-column align-items-center justify-content-center text-center text-decoration-none flex-fill <?= ($page == 'announcements' ? 'active' : '') ?>" href="index.php?page=announcements"><i class='fas fa-volume-up' style='color:#28a745;'></i>
|
||||
<span class="small fw-medium">ANNOUNCE</span></a>
|
||||
<!-- About US -->
|
||||
<a class="d-flex flex-column align-items-center justify-content-center text-center text-decoration-none flex-fill <?= ($page == 'about' ? 'active' : '') ?>" href="index.php?page=about"><i class='fa fa-info-circle' style='color:#28a745;'></i>
|
||||
<span class="small fw-medium">ABOUT</span></a>
|
||||
<!-- Contact US -->
|
||||
<a class="d-flex flex-column align-items-center justify-content-center text-center text-decoration-none flex-fill <?= ($page == 'contact' ? 'active' : '') ?>" href="index.php?page=contact"><i class='fas fa-address-card' style='color:#28a745;'></i>
|
||||
<span class="small fw-medium">CONTACT</span></a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
</div>
|
||||
</footer>
|
||||
<!-- Bootstrap JS Bundle with Popper -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
|
||||
8
includes/footer_user.php
Normal file
8
includes/footer_user.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
|
||||
<script>
|
||||
function toggleSidebar() {
|
||||
document.querySelector('.sidebar').classList.toggle('show');
|
||||
document.getElementById('overlay').classList.toggle('show');
|
||||
}
|
||||
</script>
|
||||
163
includes/header_admin.php
Normal file
163
includes/header_admin.php
Normal file
@@ -0,0 +1,163 @@
|
||||
<!-- Head tag -->
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>NIPH Data Sharing Platform</title>
|
||||
<link rel="icon" type="image/x-icon" href="/assets/images/niphlogo.ico">
|
||||
|
||||
<!-- Bootstrap -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" />
|
||||
|
||||
<!-- FontAwesome -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css" />
|
||||
|
||||
<!-- Custom styles -->
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Inter', sans-serif;
|
||||
background-color: #f8f9fa;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
width: 250px;
|
||||
background-color: #0b4076ff;
|
||||
color: white;
|
||||
min-height: 100vh;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 1030;
|
||||
border-radius: 0 12px 12px 0;
|
||||
padding: 0;
|
||||
overflow-y: auto;
|
||||
max-height: 100vh;
|
||||
}
|
||||
|
||||
.sidebar .admin-header {
|
||||
background-color: #28a745;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
font-size: 1.25rem;
|
||||
padding: 16px 0;
|
||||
letter-spacing: 1px;
|
||||
border-bottom: 1px solid #157347;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.sidebar .nav-link {
|
||||
color: #adb5bd;
|
||||
padding: 12px 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-radius: 8px;
|
||||
font-weight: 500;
|
||||
transition: background-color 0.2s, color 0.2s;
|
||||
}
|
||||
|
||||
.sidebar .nav-link i {
|
||||
margin-right: 12px;
|
||||
width: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.sidebar .nav-link:hover {
|
||||
background-color: #495057;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.sidebar .nav-link.active {
|
||||
background-color: #1456b9ff;
|
||||
color: white;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
margin-left: 250px;
|
||||
padding: 1.5rem 2rem;
|
||||
flex-grow: 1;
|
||||
transition: margin-left 0.3s ease;
|
||||
}
|
||||
|
||||
#overlay {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 56px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
z-index: 1029;
|
||||
}
|
||||
|
||||
#mobile-topbar {
|
||||
height: 56px;
|
||||
background: #198754;
|
||||
color: white;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 1040;
|
||||
}
|
||||
|
||||
#mobile-topbar h4 {
|
||||
font-size: 1.125rem;
|
||||
}
|
||||
|
||||
#mobile-topbar button {
|
||||
border-radius: 50%;
|
||||
border: 1.5px solid rgba(255, 255, 255, 0.7);
|
||||
background: transparent;
|
||||
color: white;
|
||||
padding: 0.35rem 0.5rem;
|
||||
font-size: 1.2rem;
|
||||
transition: background-color 0.2s, color 0.2s;
|
||||
}
|
||||
|
||||
#mobile-topbar button:hover {
|
||||
background-color: rgba(255, 255, 255, 0.15);
|
||||
color: #fff;
|
||||
border-color: #fff;
|
||||
}
|
||||
|
||||
@media (max-width: 991.98px) {
|
||||
.sidebar {
|
||||
left: -250px;
|
||||
padding-top: 56px;
|
||||
}
|
||||
|
||||
.sidebar.show {
|
||||
left: 0;
|
||||
box-shadow: 2px 0 12px rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
|
||||
.main-content {
|
||||
margin-left: 0;
|
||||
margin-top: 56px;
|
||||
}
|
||||
|
||||
#overlay.show {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
.slide-img-thumbnail {
|
||||
max-width: 150px;
|
||||
height: auto;
|
||||
border-radius: 5px;
|
||||
display: block;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.announcement-img {
|
||||
max-width: 100px;
|
||||
height: auto;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
150
includes/header_contributor.php
Normal file
150
includes/header_contributor.php
Normal file
@@ -0,0 +1,150 @@
|
||||
<!-- Head tag -->
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>NIPH Data Sharing Platform</title>
|
||||
<link rel="icon" type="image/x-icon" href="/assets/images/niphlogo.ico">
|
||||
|
||||
<!-- Bootstrap -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" />
|
||||
|
||||
<!-- FontAwesome -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css" />
|
||||
|
||||
<!-- Custom styles -->
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Inter', sans-serif;
|
||||
background-color: #f8f9fa;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
width: 250px;
|
||||
background-color: #0b4076ff;
|
||||
color: white;
|
||||
min-height: 100vh;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 1030;
|
||||
border-radius: 0 12px 12px 0;
|
||||
padding: 0;
|
||||
overflow-y: auto;
|
||||
max-height: 100vh;
|
||||
}
|
||||
|
||||
.sidebar .admin-header {
|
||||
background-color: #28a745;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
font-size: 1.25rem;
|
||||
padding: 16px 0;
|
||||
letter-spacing: 1px;
|
||||
border-bottom: 1px solid #157347;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.sidebar .nav-link {
|
||||
color: #adb5bd;
|
||||
padding: 12px 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-radius: 8px;
|
||||
font-weight: 500;
|
||||
transition: background-color 0.2s, color 0.2s;
|
||||
}
|
||||
|
||||
.sidebar .nav-link i {
|
||||
margin-right: 12px;
|
||||
width: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.sidebar .nav-link:hover {
|
||||
background-color: #495057;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.sidebar .nav-link.active {
|
||||
background-color: #1456b9ff;
|
||||
color: white;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
margin-left: 250px;
|
||||
padding: 1.5rem 2rem;
|
||||
flex-grow: 1;
|
||||
transition: margin-left 0.3s ease;
|
||||
}
|
||||
|
||||
#overlay {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 56px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
z-index: 1029;
|
||||
}
|
||||
|
||||
#mobile-topbar {
|
||||
height: 56px;
|
||||
background: #198754;
|
||||
color: white;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 1040;
|
||||
}
|
||||
|
||||
#mobile-topbar h4 {
|
||||
font-size: 1.125rem;
|
||||
}
|
||||
|
||||
#mobile-topbar button {
|
||||
border-radius: 50%;
|
||||
border: 1.5px solid rgba(255, 255, 255, 0.7);
|
||||
background: transparent;
|
||||
color: white;
|
||||
padding: 0.35rem 0.5rem;
|
||||
font-size: 1.2rem;
|
||||
transition: background-color 0.2s, color 0.2s;
|
||||
}
|
||||
|
||||
#mobile-topbar button:hover {
|
||||
background-color: rgba(255, 255, 255, 0.15);
|
||||
color: #fff;
|
||||
border-color: #fff;
|
||||
}
|
||||
|
||||
@media (max-width: 991.98px) {
|
||||
.sidebar {
|
||||
left: -250px;
|
||||
padding-top: 56px;
|
||||
}
|
||||
|
||||
.sidebar.show {
|
||||
left: 0;
|
||||
box-shadow: 2px 0 12px rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
|
||||
.main-content {
|
||||
margin-left: 0;
|
||||
margin-top: 56px;
|
||||
}
|
||||
|
||||
#overlay.show {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
150
includes/header_owner.php
Normal file
150
includes/header_owner.php
Normal file
@@ -0,0 +1,150 @@
|
||||
<!-- Head tag -->
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>NIPH Data Sharing Platform</title>
|
||||
<link rel="icon" type="image/x-icon" href="/assets/images/niphlogo.ico">
|
||||
|
||||
<!-- Bootstrap -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" />
|
||||
|
||||
<!-- FontAwesome -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css" />
|
||||
|
||||
<!-- Custom styles -->
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Inter', sans-serif;
|
||||
background-color: #f8f9fa;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
width: 250px;
|
||||
background-color: #0b4076ff;
|
||||
color: white;
|
||||
min-height: 100vh;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 1030;
|
||||
border-radius: 0 12px 12px 0;
|
||||
padding: 0;
|
||||
overflow-y: auto;
|
||||
max-height: 100vh;
|
||||
}
|
||||
|
||||
.sidebar .admin-header {
|
||||
background-color: #28a745;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
font-size: 1.25rem;
|
||||
padding: 16px 0;
|
||||
letter-spacing: 1px;
|
||||
border-bottom: 1px solid #157347;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.sidebar .nav-link {
|
||||
color: #adb5bd;
|
||||
padding: 12px 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-radius: 8px;
|
||||
font-weight: 500;
|
||||
transition: background-color 0.2s, color 0.2s;
|
||||
}
|
||||
|
||||
.sidebar .nav-link i {
|
||||
margin-right: 12px;
|
||||
width: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.sidebar .nav-link:hover {
|
||||
background-color: #495057;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.sidebar .nav-link.active {
|
||||
background-color: #1456b9ff;
|
||||
color: white;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
margin-left: 250px;
|
||||
padding: 1.5rem 2rem;
|
||||
flex-grow: 1;
|
||||
transition: margin-left 0.3s ease;
|
||||
}
|
||||
|
||||
#overlay {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 56px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
z-index: 1029;
|
||||
}
|
||||
|
||||
#mobile-topbar {
|
||||
height: 56px;
|
||||
background: #198754;
|
||||
color: white;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 1040;
|
||||
}
|
||||
|
||||
#mobile-topbar h4 {
|
||||
font-size: 1.125rem;
|
||||
}
|
||||
|
||||
#mobile-topbar button {
|
||||
border-radius: 50%;
|
||||
border: 1.5px solid rgba(255, 255, 255, 0.7);
|
||||
background: transparent;
|
||||
color: white;
|
||||
padding: 0.35rem 0.5rem;
|
||||
font-size: 1.2rem;
|
||||
transition: background-color 0.2s, color 0.2s;
|
||||
}
|
||||
|
||||
#mobile-topbar button:hover {
|
||||
background-color: rgba(255, 255, 255, 0.15);
|
||||
color: #fff;
|
||||
border-color: #fff;
|
||||
}
|
||||
|
||||
@media (max-width: 991.98px) {
|
||||
.sidebar {
|
||||
left: -250px;
|
||||
padding-top: 56px;
|
||||
}
|
||||
|
||||
.sidebar.show {
|
||||
left: 0;
|
||||
box-shadow: 2px 0 12px rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
|
||||
.main-content {
|
||||
margin-left: 0;
|
||||
margin-top: 56px;
|
||||
}
|
||||
|
||||
#overlay.show {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
107
includes/header_public.php
Normal file
107
includes/header_public.php
Normal file
@@ -0,0 +1,107 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>NIPH Data Sharing Platform</title>
|
||||
<link rel="icon" type="image/x-icon" href="/assets/images/niphlogo.ico">
|
||||
<!-- Bootstrap CSS -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<!-- Font Awesome for social icons -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
|
||||
<!-- Custom CSS for Inter font and additional styling -->
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
|
||||
body {
|
||||
font-family: 'Inter', sans-serif;
|
||||
background-color: #f8f9fa; /* Light background */
|
||||
padding-top: 100px; /* Adjust for fixed navbar */
|
||||
}
|
||||
.navbar {
|
||||
background-color: #ffffff; /* White background for navbar */
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,.05); /* Subtle shadow */
|
||||
}
|
||||
.navbar-brand img {
|
||||
height: 40px; /* Adjust logo size */
|
||||
margin-right: 10px;
|
||||
}
|
||||
.nav-link {
|
||||
color: #0261bfff !important; /* Dark text for links */
|
||||
font-weight: 500;
|
||||
margin-right: 15px;
|
||||
}
|
||||
.nav-link:hover {
|
||||
color: #007bff !important; /* Blue on hover */
|
||||
}
|
||||
|
||||
/* Let the search container in the mobile controls take up remaining space */
|
||||
.mobile-controls .search-container {
|
||||
flex-grow: 1;
|
||||
}
|
||||
.btn-outline-primary {
|
||||
border-color: #007bff;
|
||||
color: #007bff;
|
||||
}
|
||||
.btn-outline-primary:hover {
|
||||
background-color: #007bff;
|
||||
color: #fff;
|
||||
}
|
||||
.social-icons a {
|
||||
color: #6c757d; /* Gray for social icons */
|
||||
margin-left: 15px;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
.social-icons a:hover {
|
||||
color: #007bff;
|
||||
}
|
||||
.carousel-item img {
|
||||
max-height: 500px; /* Limit carousel image height */
|
||||
object-fit: cover;
|
||||
}
|
||||
.footer {
|
||||
background-color: #28a745; /* Dark background for footer */
|
||||
color: #f8f9fa; /* Light text */
|
||||
padding: 40px 0;
|
||||
margin-top: 50px;
|
||||
}
|
||||
.footer a {
|
||||
color: #0261bfff;
|
||||
text-decoration: none;
|
||||
}
|
||||
.footer a:hover {
|
||||
color: #ffffff;
|
||||
}
|
||||
.search-container {
|
||||
position: relative;
|
||||
margin-right: 20px;
|
||||
}
|
||||
.search-input {
|
||||
border-radius: 20px;
|
||||
padding-right: 40px; /* Space for icon */
|
||||
}
|
||||
.search-icon {
|
||||
position: absolute;
|
||||
right: 15px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
color: #6c757d;
|
||||
}
|
||||
.cpanel-sidebar {
|
||||
background-color: #f1f1f1;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
.cpanel-sidebar .nav-link {
|
||||
color: #333;
|
||||
padding: 10px 15px;
|
||||
margin-bottom: 5px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.cpanel-sidebar .nav-link.active,
|
||||
.cpanel-sidebar .nav-link:hover {
|
||||
background-color: #007bff;
|
||||
color: #fff !important;
|
||||
}
|
||||
.logotxt{
|
||||
color: #28a745;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
149
includes/header_user.php
Normal file
149
includes/header_user.php
Normal file
@@ -0,0 +1,149 @@
|
||||
<!-- Head tag -->
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Data User Dashboard - NIPH DSP</title>
|
||||
|
||||
<!-- Bootstrap -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" />
|
||||
|
||||
<!-- FontAwesome -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css" />
|
||||
|
||||
<!-- Custom styles -->
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Inter', sans-serif;
|
||||
background-color: #f8f9fa;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
width: 250px;
|
||||
background-color: #0b4076ff;
|
||||
color: white;
|
||||
min-height: 100vh;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 1030;
|
||||
border-radius: 0 12px 12px 0;
|
||||
padding: 0;
|
||||
overflow-y: auto;
|
||||
max-height: 100vh;
|
||||
}
|
||||
|
||||
.sidebar .admin-header {
|
||||
background-color: #28a745;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
font-size: 1.25rem;
|
||||
padding: 16px 0;
|
||||
letter-spacing: 1px;
|
||||
border-bottom: 1px solid #157347;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.sidebar .nav-link {
|
||||
color: #adb5bd;
|
||||
padding: 12px 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-radius: 8px;
|
||||
font-weight: 500;
|
||||
transition: background-color 0.2s, color 0.2s;
|
||||
}
|
||||
|
||||
.sidebar .nav-link i {
|
||||
margin-right: 12px;
|
||||
width: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.sidebar .nav-link:hover {
|
||||
background-color: #495057;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.sidebar .nav-link.active {
|
||||
background-color: #1456b9ff;
|
||||
color: white;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
margin-left: 250px;
|
||||
padding: 1.5rem 2rem;
|
||||
flex-grow: 1;
|
||||
transition: margin-left 0.3s ease;
|
||||
}
|
||||
|
||||
#overlay {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 56px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
z-index: 1029;
|
||||
}
|
||||
|
||||
#mobile-topbar {
|
||||
height: 56px;
|
||||
background: #198754;
|
||||
color: white;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 1040;
|
||||
}
|
||||
|
||||
#mobile-topbar h4 {
|
||||
font-size: 1.125rem;
|
||||
}
|
||||
|
||||
#mobile-topbar button {
|
||||
border-radius: 50%;
|
||||
border: 1.5px solid rgba(255, 255, 255, 0.7);
|
||||
background: transparent;
|
||||
color: white;
|
||||
padding: 0.35rem 0.5rem;
|
||||
font-size: 1.2rem;
|
||||
transition: background-color 0.2s, color 0.2s;
|
||||
}
|
||||
|
||||
#mobile-topbar button:hover {
|
||||
background-color: rgba(255, 255, 255, 0.15);
|
||||
color: #fff;
|
||||
border-color: #fff;
|
||||
}
|
||||
|
||||
@media (max-width: 991.98px) {
|
||||
.sidebar {
|
||||
left: -250px;
|
||||
padding-top: 56px;
|
||||
}
|
||||
|
||||
.sidebar.show {
|
||||
left: 0;
|
||||
box-shadow: 2px 0 12px rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
|
||||
.main-content {
|
||||
margin-left: 0;
|
||||
margin-top: 56px;
|
||||
}
|
||||
|
||||
#overlay.show {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
63
includes/jupyter_config_reference.php
Normal file
63
includes/jupyter_config_reference.php
Normal file
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
// includes/jupyter_config_reference.php
|
||||
// Shared reference panel outlining Jupyter configuration values.
|
||||
|
||||
$jupyterDefaults = dsp_jupyter_defaults();
|
||||
$resolvedBaseUrl = dsp_jupyter_base_url();
|
||||
$resolvedToken = dsp_jupyter_token();
|
||||
$resolvedPort = dsp_jupyter_port();
|
||||
$envOverrides = dsp_jupyter_env_overrides();
|
||||
$workspaceRoot = $workspaceRelativeDir ?? $jupyterDefaults['workspace_root'];
|
||||
|
||||
$workspaceBaseMessage = $workspaceRoot;
|
||||
if (!empty($_SESSION['person_id'])) {
|
||||
$workspaceBaseMessage = sprintf('%s/user_%d', rtrim($jupyterDefaults['workspace_root'], '/'), (int) $_SESSION['person_id']);
|
||||
}
|
||||
// Determine relative path back to the application root for links.
|
||||
$currentScript = $_SERVER['PHP_SELF'] ?? '';
|
||||
$scriptDir = trim(dirname($currentScript), '/');
|
||||
$segmentCount = $scriptDir === '' ? 0 : substr_count($scriptDir, '/') + 1;
|
||||
$rootPrefix = $segmentCount ? str_repeat('../', $segmentCount) : '';
|
||||
$installConfigHref = $rootPrefix . 'install_config.php#r-in-jupyter-service';
|
||||
?>
|
||||
<section class="mb-4">
|
||||
<div class="card shadow-sm border-0">
|
||||
<div class="card-body">
|
||||
<h2 class="h5 mb-3 text-secondary">Jupyter Service Reference</h2>
|
||||
<p class="text-muted">
|
||||
Configuration guidance (defaults, overrides, and security notes) now lives on the
|
||||
<a href="<?= htmlspecialchars($installConfigHref, ENT_QUOTES, 'UTF-8') ?>">Install & Configuration</a>
|
||||
page under <em>R in JupyterHub Service</em>.
|
||||
</p>
|
||||
<p class="text-muted mb-4">
|
||||
Use the snapshot below to confirm how this environment is currently resolving the notebook endpoint.
|
||||
</p>
|
||||
<dl class="row mb-4">
|
||||
<dt class="col-sm-4 text-muted small text-uppercase">Notebook Base URL</dt>
|
||||
<dd class="col-sm-8 mb-3"><code><?= htmlspecialchars($resolvedBaseUrl) ?></code></dd>
|
||||
|
||||
<dt class="col-sm-4 text-muted small text-uppercase">Published Port</dt>
|
||||
<dd class="col-sm-8 mb-3"><code><?= htmlspecialchars($resolvedPort) ?></code></dd>
|
||||
|
||||
<dt class="col-sm-4 text-muted small text-uppercase">Authentication Token</dt>
|
||||
<dd class="col-sm-8 mb-3"><code><?= htmlspecialchars($resolvedToken) ?></code></dd>
|
||||
|
||||
<dt class="col-sm-4 text-muted small text-uppercase">Workspace Mount</dt>
|
||||
<dd class="col-sm-8 mb-0"><code><?= htmlspecialchars($workspaceBaseMessage) ?></code></dd>
|
||||
</dl>
|
||||
<h3 class="h6 text-uppercase text-muted mb-3">Active Environment Overrides</h3>
|
||||
<?php
|
||||
$activeOverrides = array_filter($envOverrides, static fn($value) => $value !== null && $value !== '');
|
||||
?>
|
||||
<?php if ($activeOverrides): ?>
|
||||
<ul class="list-unstyled small text-muted mb-0">
|
||||
<?php foreach ($activeOverrides as $variable => $value): ?>
|
||||
<li><code><?= htmlspecialchars($variable) ?></code>: <code><?= htmlspecialchars($value) ?></code></li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<?php else: ?>
|
||||
<p class="text-muted small mb-0">No overrides detected; defaults from the Docker stack are in effect.</p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
203
includes/jupyter_helpers.php
Normal file
203
includes/jupyter_helpers.php
Normal file
@@ -0,0 +1,203 @@
|
||||
<?php
|
||||
// includes/jupyter_helpers.php
|
||||
// Centralised helpers for Jupyter configuration and display logic.
|
||||
|
||||
/**
|
||||
* Returns the default configuration values documented on the Install & Configuration page.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
function dsp_jupyter_defaults(): array {
|
||||
return [
|
||||
'base_url' => 'https://localhost',
|
||||
'token' => 'dsp-token',
|
||||
'workspace_root' => 'datasources',
|
||||
'port' => '443',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the external base URL for the Jupyter service.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function dsp_jupyter_base_url(): string {
|
||||
$configured = getenv('JUPYTER_EXTERNAL_URL');
|
||||
if ($configured) {
|
||||
return rtrim($configured, '/');
|
||||
}
|
||||
|
||||
$defaults = dsp_jupyter_defaults();
|
||||
$scheme = (!empty($_SERVER['HTTPS']) && strtolower((string) $_SERVER['HTTPS']) !== 'off') ? 'https' : 'http';
|
||||
$hostHeader = $_SERVER['HTTP_HOST'] ?? '';
|
||||
|
||||
if ($hostHeader) {
|
||||
$hostname = explode(':', $hostHeader)[0];
|
||||
$port = dsp_jupyter_port();
|
||||
$authority = $hostname;
|
||||
|
||||
$portIsDefault = ($scheme === 'http' && $port === '80') || ($scheme === 'https' && $port === '443');
|
||||
if (!$portIsDefault) {
|
||||
$authority .= ':' . $port;
|
||||
}
|
||||
|
||||
return sprintf('%s://%s', $scheme, $authority);
|
||||
}
|
||||
|
||||
return rtrim($defaults['base_url'], '/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the token used to authenticate with Jupyter.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function dsp_jupyter_token(): string {
|
||||
$authStrategy = strtolower((string) getenv('JUPYTERHUB_AUTH_STRATEGY'));
|
||||
if ($authStrategy === 'oauth') {
|
||||
return '';
|
||||
}
|
||||
|
||||
$defaults = dsp_jupyter_defaults();
|
||||
|
||||
$envToken = getenv('JUPYTER_TOKEN');
|
||||
if ($envToken !== false) {
|
||||
return $envToken;
|
||||
}
|
||||
|
||||
return $defaults['token'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the username JupyterHub should use for the active user.
|
||||
*
|
||||
* The template can be customised via JUPYTERHUB_USERNAME_TEMPLATE.
|
||||
* Supported placeholders:
|
||||
* {person_id} - numeric person identifier from the session
|
||||
* {username} - DSP username from the session
|
||||
* {email} - Session email if available
|
||||
*
|
||||
* @param int|null $personId Optional explicit person ID
|
||||
* @return string|null Sanitised username or null when it cannot be determined.
|
||||
*/
|
||||
function dsp_resolve_jupyterhub_username(?int $personId, ?string $username = null, ?string $email = null): ?string {
|
||||
if ($personId === null || $personId <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$template = getenv('JUPYTERHUB_USERNAME_TEMPLATE') ?: 'user_{person_id}';
|
||||
$usernameRaw = str_replace(
|
||||
['{person_id}', '{username}', '{email}'],
|
||||
[
|
||||
(string) $personId,
|
||||
(string) $username,
|
||||
(string) $email,
|
||||
],
|
||||
$template
|
||||
);
|
||||
|
||||
$usernameSanitised = preg_replace('/[^a-zA-Z0-9._-]+/', '-', $usernameRaw);
|
||||
$usernameSanitised = trim((string) $usernameSanitised, "-_.");
|
||||
|
||||
return $usernameSanitised !== '' ? $usernameSanitised : null;
|
||||
}
|
||||
|
||||
function dsp_jupyterhub_username(?int $personId = null): ?string {
|
||||
if ($personId === null) {
|
||||
$personId = isset($_SESSION['person_id']) ? (int) $_SESSION['person_id'] : null;
|
||||
}
|
||||
|
||||
$username = $_SESSION['username'] ?? null;
|
||||
$email = $_SESSION['email'] ?? null;
|
||||
|
||||
return dsp_resolve_jupyterhub_username($personId, $username, $email);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the per-user route served by JupyterHub.
|
||||
*
|
||||
* The path template can be overridden with JUPYTERHUB_USER_PATH (e.g. "user/{username}/lab").
|
||||
*
|
||||
* @param string $baseUrl Hub base URL.
|
||||
* @param int|null $personId Optional explicit person ID.
|
||||
* @return string Absolute path including the user segment, without trailing slash.
|
||||
*/
|
||||
function dsp_jupyterhub_user_route(string $baseUrl, ?int $personId = null): string {
|
||||
$baseUrl = rtrim($baseUrl, '/');
|
||||
|
||||
$pathTemplate = getenv('JUPYTERHUB_USER_PATH');
|
||||
if ($pathTemplate === false || $pathTemplate === '') {
|
||||
return $baseUrl;
|
||||
}
|
||||
|
||||
$personId = $personId ?? (isset($_SESSION['person_id']) ? (int) $_SESSION['person_id'] : null);
|
||||
$username = dsp_jupyterhub_username($personId);
|
||||
|
||||
if ($username === null) {
|
||||
return $baseUrl;
|
||||
}
|
||||
|
||||
$relativePath = str_replace(
|
||||
['{username}', '{person_id}'],
|
||||
[$username, (string) $personId],
|
||||
ltrim($pathTemplate, '/')
|
||||
);
|
||||
|
||||
return rtrim($baseUrl . '/' . $relativePath, '/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the iframe URL using the resolved base URL and token.
|
||||
*
|
||||
* @param string|null $baseUrl Optional override of the base URL.
|
||||
* @param string|null $token Optional override of the token.
|
||||
* @param int|null $personId Optional override of the person ID used for the route.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function dsp_jupyter_iframe_url(?string $baseUrl = null, ?string $token = null, ?int $personId = null): string {
|
||||
$resolvedBase = rtrim($baseUrl ?: dsp_jupyter_base_url(), '/');
|
||||
$userRoute = dsp_jupyterhub_user_route($resolvedBase, $personId);
|
||||
$finalToken = $token ?: dsp_jupyter_token();
|
||||
|
||||
return $finalToken
|
||||
? sprintf('%s?token=%s', $userRoute, urlencode($finalToken))
|
||||
: $userRoute;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the host port the Jupyter service is published on.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function dsp_jupyter_port(): string {
|
||||
$configured = getenv('JUPYTER_PORT');
|
||||
if ($configured === false || $configured === '') {
|
||||
$configured = getenv('JUPYTERHUB_PORT');
|
||||
}
|
||||
if ($configured) {
|
||||
return (string) $configured;
|
||||
}
|
||||
|
||||
$defaults = dsp_jupyter_defaults();
|
||||
|
||||
return $defaults['port'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Captures the environment overrides impacting the embedded Jupyter configuration.
|
||||
*
|
||||
* @return array<string, string|null>
|
||||
*/
|
||||
function dsp_jupyter_env_overrides(): array {
|
||||
return [
|
||||
'JUPYTER_EXTERNAL_URL' => getenv('JUPYTER_EXTERNAL_URL') ?: null,
|
||||
'JUPYTER_TOKEN' => getenv('JUPYTER_TOKEN') ?: null,
|
||||
'JUPYTER_PORT' => getenv('JUPYTER_PORT') ?: null,
|
||||
'JUPYTERHUB_PORT' => getenv('JUPYTERHUB_PORT') ?: null,
|
||||
'DSP_APP_ORIGINS' => getenv('DSP_APP_ORIGINS') ?: null,
|
||||
'DSP_FRAME_ANCESTORS' => getenv('DSP_FRAME_ANCESTORS') ?: null,
|
||||
'JUPYTERHUB_USERNAME_TEMPLATE' => getenv('JUPYTERHUB_USERNAME_TEMPLATE') ?: null,
|
||||
'JUPYTERHUB_USER_PATH' => getenv('JUPYTERHUB_USER_PATH') ?: null,
|
||||
];
|
||||
}
|
||||
119
includes/nav_admin.php
Normal file
119
includes/nav_admin.php
Normal file
@@ -0,0 +1,119 @@
|
||||
<?php
|
||||
$currentScript = $_SERVER['PHP_SELF'] ?? '';
|
||||
$currentPage = basename($currentScript);
|
||||
$scriptDir = trim(dirname($currentScript), '/');
|
||||
$segmentCount = $scriptDir === '' ? 0 : substr_count($scriptDir, '/') + 1;
|
||||
$rootPrefix = $segmentCount ? str_repeat('../', $segmentCount) : '';
|
||||
?>
|
||||
|
||||
<!-- Mobile Topbar -->
|
||||
<div id="mobile-topbar" class="d-lg-none d-flex justify-content-between align-items-center px-3 shadow-sm bg-success text-white">
|
||||
<h4 class="m-0 fw-bold">DSP ADMIN</h4>
|
||||
<button class="btn btn-outline-light rounded" onclick="toggleSidebar()">
|
||||
<i class="fas fa-bars"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Mobile overlay -->
|
||||
<div id="overlay" onclick="toggleSidebar()"></div>
|
||||
|
||||
<!-- Sidebar -->
|
||||
<nav class="sidebar d-lg-block shadow">
|
||||
<!-- Desktop only header -->
|
||||
<div class="admin-header d-none d-lg-block text-center">
|
||||
ADMIN-DAC STAFF
|
||||
</div>
|
||||
|
||||
<ul class="nav flex-column py-0">
|
||||
<li class="nav-item mt-0 border-top border-secondary pt-2">
|
||||
<?php
|
||||
$sections = [
|
||||
'Data Management' => [
|
||||
'admin/dashboard.php' => ['Dashboard', 'fa-tachometer-alt'],
|
||||
'admin/manage_users.php' => ['Manage Users', 'fa-users'],
|
||||
'admin/manage_datasources.php' => ['Manage Data Sources', 'fa-database'],
|
||||
'admin/manage_permissions_admin.php' => ['Data Permissions', 'fa-user-lock'],
|
||||
'admin/manage_classifications.php' => ['Classifications', 'fa-tags'],
|
||||
],
|
||||
'Content Management' => [
|
||||
'admin/manage_announcements.php' => ['Announcements', 'fa-bullhorn'],
|
||||
'admin/manage_faq.php' => ['FAQ', 'fa-question-circle'],
|
||||
'admin/manage_slides.php' => ['Slides', 'fa-images'],
|
||||
'admin/manage_aboutus.php' => ['About Us', 'fa-info-circle'],
|
||||
'admin/manage_contactus.php' => ['Contact Us', 'fa-envelope'],
|
||||
],
|
||||
'Resources' => [
|
||||
'user_guide.php' => ['User Guide', 'fa-book-open'],
|
||||
'install_config.php' => ['Install & Config', 'fa-tools'],
|
||||
'admin/app_log.php' => ['Application Log', 'fa-scroll'],
|
||||
],
|
||||
'Account' => [
|
||||
'profile.php' => ['My Profile', 'fa-id-card'],
|
||||
],
|
||||
];
|
||||
|
||||
if (!empty($_SESSION['can_run_r'])) {
|
||||
$sections['Data Management']['admin/r_in_jupyter.php'] = ['R in JupyterHub', 'fa-code'];
|
||||
}
|
||||
|
||||
echo '<style>
|
||||
.nav-section {
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
border: 1px solid rgba(255,255,255,0.06);
|
||||
border-radius: 10px;
|
||||
padding: 0.9rem 0.75rem;
|
||||
margin: 0 0.75rem 1.2rem 0.75rem;
|
||||
}
|
||||
.nav-section legend {
|
||||
font-size: 0.72rem;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
color: rgba(255,255,255,0.55);
|
||||
width: auto;
|
||||
padding: 0 0.4rem;
|
||||
margin-left: 0.35rem;
|
||||
}
|
||||
</style>';
|
||||
|
||||
foreach ($sections as $sectionLabel => $items) {
|
||||
echo '<fieldset class="nav-section"><legend>' . htmlspecialchars($sectionLabel) . '</legend>';
|
||||
foreach ($items as $file => [$title, $icon]) {
|
||||
$targetPath = parse_url($file, PHP_URL_PATH);
|
||||
$targetPath = ($targetPath === null || $targetPath === false) ? $file : $targetPath;
|
||||
$active = ($currentPage === basename($targetPath)) ? 'active' : '';
|
||||
$isExternal = preg_match('#^https?://#', $file) === 1;
|
||||
if ($isExternal) {
|
||||
$href = $file;
|
||||
} else {
|
||||
$href = $rootPrefix . ltrim($targetPath, '/');
|
||||
$query = parse_url($file, PHP_URL_QUERY);
|
||||
if (!empty($query)) {
|
||||
$href .= '?' . $query;
|
||||
}
|
||||
$fragment = parse_url($file, PHP_URL_FRAGMENT);
|
||||
if (!empty($fragment)) {
|
||||
$href .= '#' . $fragment;
|
||||
}
|
||||
}
|
||||
$hrefEsc = htmlspecialchars($href, ENT_QUOTES, 'UTF-8');
|
||||
$titleEsc = htmlspecialchars($title, ENT_QUOTES, 'UTF-8');
|
||||
$iconEsc = htmlspecialchars($icon, ENT_QUOTES, 'UTF-8');
|
||||
echo <<<HTML
|
||||
<li class="nav-item">
|
||||
<a class="nav-link $active" href="$hrefEsc">
|
||||
<i class="fas $iconEsc me-2"></i> $titleEsc
|
||||
</a>
|
||||
</li>
|
||||
HTML;
|
||||
}
|
||||
echo '</fieldset>';
|
||||
}
|
||||
?>
|
||||
</li>
|
||||
<li class="nav-item mt-2 border-top border-secondary pt-2">
|
||||
<a class="nav-link text-danger" href="<?php echo htmlspecialchars($rootPrefix . 'logout.php', ENT_QUOTES, 'UTF-8'); ?>">
|
||||
<i class="fas fa-sign-out-alt me-2"></i> Logout
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
109
includes/nav_contributor.php
Normal file
109
includes/nav_contributor.php
Normal file
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
$currentScript = $_SERVER['PHP_SELF'] ?? '';
|
||||
$currentPage = basename($currentScript);
|
||||
$scriptDir = trim(dirname($currentScript), '/');
|
||||
$segmentCount = $scriptDir === '' ? 0 : substr_count($scriptDir, '/') + 1;
|
||||
$rootPrefix = $segmentCount ? str_repeat('../', $segmentCount) : '';
|
||||
?>
|
||||
|
||||
<!-- Mobile Topbar -->
|
||||
<div id="mobile-topbar" class="d-lg-none d-flex justify-content-between align-items-center px-3 shadow-sm bg-success text-white">
|
||||
<h4 class="m-0 fw-bold">DSP DATA Contributor</h4>
|
||||
<button class="btn btn-outline-light rounded" onclick="toggleSidebar()">
|
||||
<i class="fas fa-bars"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Mobile overlay -->
|
||||
<div id="overlay" onclick="toggleSidebar()"></div>
|
||||
|
||||
<!-- Sidebar -->
|
||||
<nav class="sidebar d-lg-block shadow">
|
||||
<!-- Desktop only header -->
|
||||
<div class="admin-header d-none d-lg-block text-center">
|
||||
DATA Contributor
|
||||
</div>
|
||||
|
||||
<ul class="nav flex-column py-0">
|
||||
<li class="nav-item mt-0 border-top border-secondary pt-2">
|
||||
<?php
|
||||
$sections = [
|
||||
'Data Management' => [
|
||||
'data_hybrid/dashboard.php' => ['Dashboard', 'fa-tachometer-alt'],
|
||||
'data_hybrid/manage_my_datasources.php' => ['Data Sources', 'fa-database'],
|
||||
'data_hybrid/manage_permissions.php' => ['Data Permissions', 'fa-user-shield'],
|
||||
'data_hybrid/my_permissions.php' => ['My data Request', 'fa-user-shield'],
|
||||
'data_hybrid/my_downloads.php' => ['My Downloads', 'fa-chart-bar'],
|
||||
'data_hybrid/browse_datasources.php' => ['Browse All Data', 'fa-database'],
|
||||
],
|
||||
'Resources' => [
|
||||
'user_guide.php' => ['User Guide', 'fa-book-open'],
|
||||
],
|
||||
'Account' => [
|
||||
'profile.php' => ['My Profile', 'fa-id-card'],
|
||||
],
|
||||
];
|
||||
if (!empty($_SESSION['can_run_r'])) {
|
||||
$sections['Data Management']['data_hybrid/r_in_jupyter.php'] = ['R in JupyterHub', 'fa-code'];
|
||||
}
|
||||
|
||||
echo '<style>
|
||||
.nav-section {
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
border: 1px solid rgba(255,255,255,0.06);
|
||||
border-radius: 10px;
|
||||
padding: 0.9rem 0.75rem;
|
||||
margin: 0 0.75rem 1.2rem 0.75rem;
|
||||
}
|
||||
.nav-section legend {
|
||||
font-size: 0.72rem;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
color: rgba(255,255,255,0.55);
|
||||
width: auto;
|
||||
padding: 0 0.4rem;
|
||||
margin-left: 0.35rem;
|
||||
}
|
||||
</style>';
|
||||
|
||||
foreach ($sections as $sectionLabel => $items) {
|
||||
echo '<fieldset class="nav-section"><legend>' . htmlspecialchars($sectionLabel) . '</legend>';
|
||||
foreach ($items as $file => [$title, $icon]) {
|
||||
$targetPath = parse_url($file, PHP_URL_PATH);
|
||||
$targetPath = ($targetPath === null || $targetPath === false) ? $file : $targetPath;
|
||||
$active = ($currentPage === basename($targetPath)) ? 'active' : '';
|
||||
$isExternal = preg_match('#^https?://#', $file) === 1;
|
||||
if ($isExternal) {
|
||||
$href = $file;
|
||||
} else {
|
||||
$href = $rootPrefix . ltrim($targetPath, '/');
|
||||
$query = parse_url($file, PHP_URL_QUERY);
|
||||
if (!empty($query)) {
|
||||
$href .= '?' . $query;
|
||||
}
|
||||
if ($fragment = parse_url($file, PHP_URL_FRAGMENT)) {
|
||||
$href .= '#' . $fragment;
|
||||
}
|
||||
}
|
||||
$hrefEsc = htmlspecialchars($href, ENT_QUOTES, 'UTF-8');
|
||||
$titleEsc = htmlspecialchars($title, ENT_QUOTES, 'UTF-8');
|
||||
$iconEsc = htmlspecialchars($icon, ENT_QUOTES, 'UTF-8');
|
||||
echo <<<HTML
|
||||
<li class="nav-item">
|
||||
<a class="nav-link $active" href="$hrefEsc">
|
||||
<i class="fas $iconEsc me-2"></i> $titleEsc
|
||||
</a>
|
||||
</li>
|
||||
HTML;
|
||||
}
|
||||
echo '</fieldset>';
|
||||
}
|
||||
?>
|
||||
</li>
|
||||
<li class="nav-item mt-2 border-top border-secondary pt-2">
|
||||
<a class="nav-link text-danger" href="<?php echo htmlspecialchars($rootPrefix . 'logout.php', ENT_QUOTES, 'UTF-8'); ?>">
|
||||
<i class="fas fa-sign-out-alt me-2"></i> Logout
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
108
includes/nav_owner.php
Normal file
108
includes/nav_owner.php
Normal file
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
$currentScript = $_SERVER['PHP_SELF'] ?? '';
|
||||
$currentPage = basename($currentScript);
|
||||
$scriptDir = trim(dirname($currentScript), '/');
|
||||
$segmentCount = $scriptDir === '' ? 0 : substr_count($scriptDir, '/') + 1;
|
||||
$rootPrefix = $segmentCount ? str_repeat('../', $segmentCount) : '';
|
||||
?>
|
||||
|
||||
<!-- Mobile Topbar -->
|
||||
<div id="mobile-topbar" class="d-lg-none d-flex justify-content-between align-items-center px-3 shadow-sm bg-success text-white">
|
||||
<h4 class="m-0 fw-bold">DSP DATA OWNER</h4>
|
||||
<button class="btn btn-outline-light rounded" onclick="toggleSidebar()">
|
||||
<i class="fas fa-bars"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Mobile overlay -->
|
||||
<div id="overlay" onclick="toggleSidebar()"></div>
|
||||
|
||||
<!-- Sidebar -->
|
||||
<nav class="sidebar d-lg-block shadow">
|
||||
<!-- Desktop only header -->
|
||||
<div class="admin-header d-none d-lg-block text-center">
|
||||
DATA OWNER
|
||||
</div>
|
||||
|
||||
<ul class="nav flex-column py-0">
|
||||
<li class="nav-item mt-0 border-top border-secondary pt-2">
|
||||
<?php
|
||||
$sections = [
|
||||
'Data Management' => [
|
||||
'data_owner/dashboard.php' => ['Dashboard', 'fa-tachometer-alt'],
|
||||
'data_owner/manage_my_datasources.php' => ['My Data Sources', 'fa-database'],
|
||||
'data_owner/manage_permissions.php' => ['Data Permissions', 'fa-user-shield'],
|
||||
],
|
||||
'Resources' => [
|
||||
'user_guide.php' => ['User Guide', 'fa-book-open'],
|
||||
],
|
||||
'Account' => [
|
||||
'profile.php' => ['My Profile', 'fa-id-card'],
|
||||
],
|
||||
];
|
||||
|
||||
if (!empty($_SESSION['can_run_r'])) {
|
||||
$sections['Data Management']['data_owner/run_r_scripts.php'] = ['R in JupyterHub', 'fa-code'];
|
||||
}
|
||||
|
||||
echo '<style>
|
||||
.nav-section {
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
border: 1px solid rgba(255,255,255,0.06);
|
||||
border-radius: 10px;
|
||||
padding: 0.9rem 0.75rem;
|
||||
margin: 0 0.75rem 1.2rem 0.75rem;
|
||||
}
|
||||
.nav-section legend {
|
||||
font-size: 0.72rem;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
color: rgba(255,255,255,0.55);
|
||||
width: auto;
|
||||
padding: 0 0.4rem;
|
||||
margin-left: 0.35rem;
|
||||
}
|
||||
</style>';
|
||||
|
||||
foreach ($sections as $sectionLabel => $items) {
|
||||
echo '<fieldset class="nav-section"><legend>' . htmlspecialchars($sectionLabel) . '</legend>';
|
||||
foreach ($items as $file => [$title, $icon]) {
|
||||
$targetPath = parse_url($file, PHP_URL_PATH);
|
||||
$targetPath = ($targetPath === null || $targetPath === false) ? $file : $targetPath;
|
||||
$active = ($currentPage === basename($targetPath)) ? 'active' : '';
|
||||
$isExternal = preg_match('#^https?://#', $file) === 1;
|
||||
if ($isExternal) {
|
||||
$href = $file;
|
||||
} else {
|
||||
$href = $rootPrefix . ltrim($targetPath, '/');
|
||||
$query = parse_url($file, PHP_URL_QUERY);
|
||||
if (!empty($query)) {
|
||||
$href .= '?' . $query;
|
||||
}
|
||||
$fragment = parse_url($file, PHP_URL_FRAGMENT);
|
||||
if (!empty($fragment)) {
|
||||
$href .= '#' . $fragment;
|
||||
}
|
||||
}
|
||||
$hrefEsc = htmlspecialchars($href, ENT_QUOTES, 'UTF-8');
|
||||
$titleEsc = htmlspecialchars($title, ENT_QUOTES, 'UTF-8');
|
||||
$iconEsc = htmlspecialchars($icon, ENT_QUOTES, 'UTF-8');
|
||||
echo <<<HTML
|
||||
<li class="nav-item">
|
||||
<a class="nav-link $active" href="$hrefEsc">
|
||||
<i class="fas $iconEsc me-2"></i> $titleEsc
|
||||
</a>
|
||||
</li>
|
||||
HTML;
|
||||
}
|
||||
echo '</fieldset>';
|
||||
}
|
||||
?>
|
||||
</li>
|
||||
<li class="nav-item mt-2 border-top border-secondary pt-2">
|
||||
<a class="nav-link text-danger" href="<?php echo htmlspecialchars($rootPrefix . 'logout.php', ENT_QUOTES, 'UTF-8'); ?>">
|
||||
<i class="fas fa-sign-out-alt me-2"></i> Logout
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
85
includes/nav_public.php
Normal file
85
includes/nav_public.php
Normal file
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
// includes/nav_public.php
|
||||
?>
|
||||
<!-- Public Navigation -->
|
||||
<nav class="navbar navbar-expand-lg navbar-light fixed-top shadow-sm rounded-bottom">
|
||||
<div class="container">
|
||||
<a class="navbar-brand d-flex align-items-center" href="index.php">
|
||||
<img src="assets/images/niphlogo.png" alt="NIPH Logo" class="rounded me-2" style="width:72px;height:72px;">
|
||||
<div class="lh-sm">
|
||||
<span class="d-block fw-bold text-success text-uppercase" style="letter-spacing:0.08em;">NIPH</span>
|
||||
<span class="d-block fw-semibold text-success" style="font-size:0.95rem;">Data Sharing Platform</span>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#publicNavbar" aria-controls="publicNavbar" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
|
||||
<div class="collapse navbar-collapse" id="publicNavbar">
|
||||
<ul class="navbar-nav ms-lg-4 me-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link <?= ($page == 'home' ? 'active' : '') ?>" href="index.php?page=home">
|
||||
<i class="fas fa-home text-success me-1"></i>HOME
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link <?= ($page == 'classifications' ? 'active' : '') ?>" href="index.php?page=classifications">
|
||||
<i class="fas fa-layer-group text-success me-1"></i>DATASET
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link <?= ($page == 'announcements' ? 'active' : '') ?>" href="index.php?page=announcements">
|
||||
<i class="fas fa-volume-up text-success me-1"></i>ANNOUNCE
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link <?= ($page == 'about' ? 'active' : '') ?>" href="index.php?page=about">
|
||||
<i class="fa fa-info-circle text-success me-1"></i>ABOUT
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link <?= ($page == 'contact' ? 'active' : '') ?>" href="index.php?page=contact">
|
||||
<i class="fas fa-address-card text-success me-1"></i>CONTACT
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="d-flex flex-column flex-lg-row align-items-lg-center gap-2">
|
||||
<a class="nav-link <?= ($page == 'faq' ? 'active' : '') ?>" href="index.php?page=faq">
|
||||
<i class="fas fa-question-circle text-success me-1"></i>FAQ
|
||||
</a>
|
||||
|
||||
<?php if (!$is_logged_in): ?>
|
||||
<div class="d-flex flex-column flex-sm-row gap-2">
|
||||
<button class="btn btn-outline-success rounded-pill" data-bs-toggle="modal" data-bs-target="#registerModal">
|
||||
<i class="fas fa-user-plus me-2"></i>Register
|
||||
</button>
|
||||
<button class="btn btn-success rounded-pill" data-bs-toggle="modal" data-bs-target="#loginModal">
|
||||
<i class="fas fa-lock me-2"></i>Login
|
||||
</button>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="d-flex flex-column flex-sm-row gap-2">
|
||||
<?php if ($user_role == 'DAC Staff'): ?>
|
||||
<a class="btn btn-outline-success rounded-pill" href="admin/dashboard.php">
|
||||
<i class="fas fa-tachometer-alt me-2"></i>Dashboard
|
||||
</a>
|
||||
<?php elseif ($user_role == 'Data Owner'): ?>
|
||||
<a class="btn btn-outline-success rounded-pill" href="data_owner/dashboard.php">
|
||||
<i class="fas fa-tachometer-alt me-2"></i>Dashboard
|
||||
</a>
|
||||
<?php elseif ($user_role == 'Data User'): ?>
|
||||
<a class="btn btn-outline-success rounded-pill" href="data_user/dashboard.php">
|
||||
<i class="fas fa-tachometer-alt me-2"></i>Dashboard
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
<a class="btn btn-outline-secondary rounded-pill" href="logout.php">
|
||||
<i class="fas fa-sign-out-alt me-2"></i>Logout
|
||||
</a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
108
includes/nav_user.php
Normal file
108
includes/nav_user.php
Normal file
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
$currentScript = $_SERVER['PHP_SELF'] ?? '';
|
||||
$currentPage = basename($currentScript);
|
||||
$scriptDir = trim(dirname($currentScript), '/');
|
||||
$segmentCount = $scriptDir === '' ? 0 : substr_count($scriptDir, '/') + 1;
|
||||
$rootPrefix = $segmentCount ? str_repeat('../', $segmentCount) : '';
|
||||
?>
|
||||
|
||||
<!-- Mobile Topbar -->
|
||||
<div id="mobile-topbar" class="d-lg-none d-flex justify-content-between align-items-center px-3 shadow-sm bg-success text-white">
|
||||
<h4 class="m-0 fw-bold">DSP DATA USER</h4>
|
||||
<button class="btn btn-outline-light rounded" onclick="toggleSidebar()">
|
||||
<i class="fas fa-bars"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Mobile overlay -->
|
||||
<div id="overlay" onclick="toggleSidebar()"></div>
|
||||
|
||||
<!-- Sidebar -->
|
||||
<nav class="sidebar d-lg-block shadow">
|
||||
<!-- Desktop only header -->
|
||||
<div class="admin-header d-none d-lg-block text-center">
|
||||
DATA USER
|
||||
</div>
|
||||
|
||||
<ul class="nav flex-column py-0">
|
||||
<li class="nav-item mt-0 border-top border-secondary pt-2">
|
||||
<?php
|
||||
$sections = [
|
||||
'My Access' => [
|
||||
'data_user/dashboard.php' => ['Dashboard', 'fa-tachometer-alt'],
|
||||
'data_user/my_permissions.php' => ['My data Request', 'fa-user-shield'],
|
||||
'data_user/my_downloads.php' => ['My Downloads', 'fa-chart-bar'],
|
||||
'data_user/browse_datasources.php' => ['Browse All Data', 'fa-database'],
|
||||
],
|
||||
'Resources' => [
|
||||
'user_guide.php' => ['User Guide', 'fa-book-open'],
|
||||
],
|
||||
'Account' => [
|
||||
'profile.php' => ['My Profile', 'fa-id-card'],
|
||||
],
|
||||
];
|
||||
if (!empty($_SESSION['can_run_r'])) {
|
||||
$sections['My Access']['data_user/r_in_jupyter.php'] = ['R in JupyterHub', 'fa-code'];
|
||||
}
|
||||
|
||||
echo '<style>
|
||||
.nav-section {
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
border: 1px solid rgba(255,255,255,0.06);
|
||||
border-radius: 10px;
|
||||
padding: 0.9rem 0.75rem;
|
||||
margin: 0 0.75rem 1.2rem 0.75rem;
|
||||
}
|
||||
.nav-section legend {
|
||||
font-size: 0.72rem;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
color: rgba(255,255,255,0.55);
|
||||
width: auto;
|
||||
padding: 0 0.4rem;
|
||||
margin-left: 0.35rem;
|
||||
}
|
||||
</style>';
|
||||
|
||||
foreach ($sections as $sectionLabel => $items) {
|
||||
echo '<fieldset class="nav-section"><legend>' . htmlspecialchars($sectionLabel) . '</legend>';
|
||||
foreach ($items as $file => [$title, $icon]) {
|
||||
$targetPath = parse_url($file, PHP_URL_PATH);
|
||||
$targetPath = ($targetPath === null || $targetPath === false) ? $file : $targetPath;
|
||||
$active = ($currentPage === basename($targetPath)) ? 'active' : '';
|
||||
$isExternal = preg_match('#^https?://#', $file) === 1;
|
||||
if ($isExternal) {
|
||||
$href = $file;
|
||||
} else {
|
||||
$href = $rootPrefix . ltrim($targetPath, '/');
|
||||
$query = parse_url($file, PHP_URL_QUERY);
|
||||
if (!empty($query)) {
|
||||
$href .= '?' . $query;
|
||||
}
|
||||
$fragment = parse_url($file, PHP_URL_FRAGMENT);
|
||||
if (!empty($fragment)) {
|
||||
$href .= '#' . $fragment;
|
||||
}
|
||||
}
|
||||
$hrefEsc = htmlspecialchars($href, ENT_QUOTES, 'UTF-8');
|
||||
$titleEsc = htmlspecialchars($title, ENT_QUOTES, 'UTF-8');
|
||||
$iconEsc = htmlspecialchars($icon, ENT_QUOTES, 'UTF-8');
|
||||
echo <<<HTML
|
||||
<li class="nav-item">
|
||||
<a class="nav-link $active" href="$hrefEsc">
|
||||
<i class="fas $iconEsc me-2"></i> $titleEsc
|
||||
</a>
|
||||
</li>
|
||||
HTML;
|
||||
}
|
||||
echo '</fieldset>';
|
||||
}
|
||||
?>
|
||||
</li>
|
||||
<li class="nav-item mt-2 border-top border-secondary pt-2">
|
||||
<a class="nav-link text-danger" href="<?php echo htmlspecialchars($rootPrefix . 'logout.php', ENT_QUOTES, 'UTF-8'); ?>">
|
||||
<i class="fas fa-sign-out-alt me-2"></i> Logout
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
Reference in New Issue
Block a user