Files
web_template/php/layout.php
T

170 lines
6.8 KiB
PHP
Raw Normal View History

<?php
/**
* LOTUSGUILD TERMINAL DESIGN SYSTEM — PHP Base Layout
* Copy this into your PHP app as the base view template.
*
* Requires:
* - middleware/SecurityHeadersMiddleware.php (provides $nonce)
* - middleware/CsrfMiddleware.php (provides CSRF token)
* - middleware/AuthMiddleware.php (provides $currentUser)
* - config/config.php (provides $config)
*
* Usage:
* Call SecurityHeadersMiddleware::apply() before any output.
* Then include this file (or extend it) in your view.
*
* Variables expected:
* $nonce string CSP nonce from SecurityHeadersMiddleware::getNonce()
* $currentUser array ['username', 'name', 'is_admin', 'groups']
* $pageTitle string Page <title> suffix
* $activeNav string Which nav key is active — must match a $navLinks entry
* $config array From config/config.php
* $navLinks array Navigation items:
* [['href' => '/path', 'key' => 'mykey', 'label' => 'My Page'], ...]
* Nested (dropdown):
* ['label' => 'Admin', 'key' => 'admin', 'adminOnly' => true, 'children' => [
* ['href' => '/admin/users', 'label' => 'Users'],
* ]]
*/
// Defensive defaults
$nonce = $nonce ?? '';
$currentUser = $currentUser ?? [];
$pageTitle = $pageTitle ?? 'Dashboard';
$activeNav = $activeNav ?? '';
$config = $config ?? [];
$navLinks = $navLinks ?? [];
$isAdmin = $currentUser['is_admin'] ?? false;
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?php echo htmlspecialchars($pageTitle); ?> — <?php echo htmlspecialchars($config['APP_NAME'] ?? 'LotusGuild'); ?></title>
<meta name="description" content="LotusGuild infrastructure management">
<!-- Unified design system CSS -->
<link rel="stylesheet" href="/web_template/base.css">
<!-- App-specific CSS (extends base, never overrides variables without good reason) -->
<link rel="stylesheet" href="/assets/css/app.css?v=<?php echo $config['CSS_VERSION'] ?? '1'; ?>">
<link rel="icon" href="/assets/images/favicon.png" type="image/png">
</head>
<body>
<!-- Boot overlay — runs once per session via lt.boot.run() -->
<div id="lt-boot" class="lt-boot-overlay"
data-app-name="<?php echo htmlspecialchars($config['APP_NAME'] ?? 'APP'); ?>"
style="display:none">
<pre id="lt-boot-text" class="lt-boot-text"></pre>
</div>
<!-- =========================================================
HEADER
========================================================= -->
<header class="lt-header">
<div class="lt-header-left">
<div class="lt-brand">
<a href="/" class="lt-brand-title" style="text-decoration:none">
<?php echo htmlspecialchars($config['APP_NAME'] ?? 'APP'); ?>
</a>
<span class="lt-brand-subtitle"><?php echo htmlspecialchars($config['APP_SUBTITLE'] ?? 'LotusGuild Infrastructure'); ?></span>
</div>
<nav class="lt-nav" aria-label="Main navigation">
<?php foreach ($navLinks as $link): ?>
<?php
$skipAdminOnly = !empty($link['adminOnly']) && !$isAdmin;
if ($skipAdminOnly) continue;
?>
<?php if (!empty($link['children'])): ?>
<?php $parentActive = str_starts_with($activeNav, $link['key']); ?>
<div class="lt-nav-dropdown">
<a href="#" class="lt-nav-link <?php echo $parentActive ? 'active' : ''; ?>">
<?php echo htmlspecialchars($link['label']); ?> ▾
</a>
<ul class="lt-nav-dropdown-menu">
<?php foreach ($link['children'] as $child): ?>
<li><a href="<?php echo htmlspecialchars($child['href']); ?>"><?php echo htmlspecialchars($child['label']); ?></a></li>
<?php endforeach; ?>
</ul>
</div>
<?php else: ?>
<a href="<?php echo htmlspecialchars($link['href']); ?>"
class="lt-nav-link <?php echo $activeNav === $link['key'] ? 'active' : ''; ?>">
<?php echo htmlspecialchars($link['label']); ?>
</a>
<?php endif; ?>
<?php endforeach; ?>
</nav>
</div>
<div class="lt-header-right">
<?php if (!empty($currentUser['name'])): ?>
<span class="lt-header-user"><?php echo htmlspecialchars($currentUser['name']); ?></span>
<?php endif; ?>
<?php if ($isAdmin): ?>
<span class="lt-badge-admin">admin</span>
<?php endif; ?>
</div>
</header>
<!-- =========================================================
MAIN CONTENT — provided by the including view
========================================================= -->
<main class="lt-main lt-container">
<!-- CONTENT SLOT: the including view outputs its content here -->
<?php
// If using output buffering pattern:
// ob_start(); include 'views/DashboardView.php'; $content = ob_get_clean();
// echo $content;
//
// Or simply include views inline after including this layout.
?>
</main>
<!-- =========================================================
SCRIPTS
All <script> tags MUST include the CSP nonce attribute.
========================================================= -->
<!-- Inject runtime config + CSRF token (nonce required for CSP) -->
<script nonce="<?php echo $nonce; ?>">
window.CSRF_TOKEN = '<?php echo CsrfMiddleware::getToken(); ?>';
window.APP_TIMEZONE = '<?php echo htmlspecialchars($config['TIMEZONE'] ?? 'UTC'); ?>';
window.APP_TIMEZONE_ABBREV = '<?php echo htmlspecialchars($config['TIMEZONE_ABBREV'] ?? 'UTC'); ?>';
window.CURRENT_USER = {
id: <?php echo (int)($currentUser['user_id'] ?? 0); ?>,
username: <?php echo json_encode($currentUser['username'] ?? ''); ?>,
isAdmin: <?php echo $isAdmin ? 'true' : 'false'; ?>,
};
// App-specific config: set window.APP_CONFIG in your app's own <script> block,
// not here. This file is shared across all apps.
</script>
<!-- Unified design system JS -->
<script nonce="<?php echo $nonce; ?>" src="/web_template/base.js"></script>
<!-- App-specific JS (cache-busted) -->
<script nonce="<?php echo $nonce; ?>"
src="/assets/js/app.js?v=<?php echo $config['JS_VERSION'] ?? '1'; ?>">
</script>
<!-- Per-page inline JS goes here in the including view, e.g.: -->
<!--
<script nonce="<?php echo $nonce; ?>">
lt.sortTable.init('ticket-table');
lt.tableNav.init('ticket-table');
lt.keys.on('n', () => window.location.href = '/ticket/create');
lt.autoRefresh.start(() => fetch('/api/status').then(r=>r.json()).then(updateUI), 30000);
</script>
-->
</body>
</html>