Files
web_template/php/layout.php

155 lines
6.1 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 link is active ('dashboard','tickets',etc.)
* $config array From config/config.php
*/
// Defensive defaults
$nonce = $nonce ?? '';
$currentUser = $currentUser ?? [];
$pageTitle = $pageTitle ?? 'Dashboard';
$activeNav = $activeNav ?? '';
$config = $config ?? [];
$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'] ?? '20260314'; ?>">
<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">
<a href="/" class="lt-nav-link <?php echo $activeNav === 'dashboard' ? 'active' : ''; ?>">Dashboard</a>
<a href="/tickets" class="lt-nav-link <?php echo $activeNav === 'tickets' ? 'active' : ''; ?>">Tickets</a>
<?php if ($isAdmin): ?>
<div class="lt-nav-dropdown">
<a href="#" class="lt-nav-link <?php echo str_starts_with($activeNav, 'admin') ? 'active' : ''; ?>">
Admin
</a>
<ul class="lt-nav-dropdown-menu">
<li><a href="/admin/templates">Templates</a></li>
<li><a href="/admin/workflow">Workflow</a></li>
<li><a href="/admin/recurring-tickets">Recurring</a></li>
<li><a href="/admin/custom-fields">Custom Fields</a></li>
<li><a href="/admin/user-activity">User Activity</a></li>
<li><a href="/admin/audit-log">Audit Log</a></li>
<li><a href="/admin/api-keys">API Keys</a></li>
</ul>
</div>
<?php endif; ?>
</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'; ?>,
};
</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'] ?? '20260314'; ?>">
</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>