1<?php
2/**
3 * ZnoteX ACP shell - sidebar panel with ZnoteX styling.
4 * Rendered by admin/index.php with $acp_content already built.
5 *
6 * @var array $acp_modules
7 * @var array|null $acp_module
8 * @var string $acp_page
9 * @var string $acp_content
10 */
11
12if (!defined('ACP_ROOT')) {
13 http_response_code(403);
14 die('Direct access denied.');
15}
16
17$acp_flashes = acp_take_flashes();
18$acp_groups = acp_nav_groups();
19$acp_admin = (string)($user_data['name'] ?? 'Admin');
20$acp_siteName = (string)($config['site_title'] ?? 'ZnoteX');
21$acp_title = $acp_module['title'] ?? 'Admin Panel';
22$acp_engine = serverEngineReal();
23?>
24<!DOCTYPE html>
25<html lang="en" dir="ltr">
26<head>
27 <meta charset="utf-8">
28 <meta name="viewport" content="width=device-width, initial-scale=1">
29 <meta name="robots" content="noindex, nofollow">
30 <title><?= h($acp_title) ?> · <?= h($acp_siteName) ?> ACP</title>
31
32 <link rel="icon" href="../assets/img/znoteX.png">
33 <link rel="shortcut icon" href="../assets/img/znoteX.png">
34 <link rel="apple-touch-icon" href="../assets/img/znoteX.png">
35
36 <link rel="stylesheet" href="../assets/fontawesome/css/font-awesome.min.css?acp=1">
37 <link rel="stylesheet" href="assets/acp.css?acp=6">
38
39 <script>
40 // Applied before first paint so the theme never flashes light-then-dark.
41 (function () {
42 try {
43 var d = document.documentElement;
44 var t = localStorage.getItem('acp.theme');
45 if (t) { d.setAttribute('data-acp-theme', t); }
46 if (localStorage.getItem('acp.sidebar_closed') === '1') { d.classList.add('acp-sidebar-closed'); }
47 } catch (e) {}
48 })();
49 </script>
50</head>
51<body class="acp acp-page-<?= h($acp_page) ?>">
52
53<div class="acp-shell">
54
55 <aside class="acp-sidebar" id="acpSidebar">
56 <a class="acp-side-home" href="<?= h(acp_url('dashboard')) ?>">
57 <i class="fa fa-home"></i>
58 <span><?= h($acp_siteName) ?></span>
59 </a>
60
61 <div class="acp-nav-filter">
62 <i class="fa fa-search"></i>
63 <input type="search" id="acpFilter" placeholder="<?= h(t('acp.shell.search_panel')) ?>" autocomplete="off" aria-label="<?= h(t('acp.shell.nav_label')) ?>">
64 </div>
65
66 <nav class="acp-nav" id="acpNav" aria-label="<?= h(t('acp.shell.nav_label')) ?>">
67 <?php
68 // A little helper, inline since it is only ever used right here:
69 // renders one nav link <li>, identical whether it sits directly in
70 // a group or inside a plugin's sub-list.
71 $acp_nav_link = function (string $key, array $mod, string $groupName): void {
72 $isExternal = !empty($mod['url']);
73 $href = $isExternal ? $mod['url'] : acp_url($key);
74 $badge = acp_badge($key);
75 $active = (!$isExternal && $key === $GLOBALS['acp_page']);
76 ?>
77 <li>
78 <a class="acp-nav-link<?= $active ? ' is-active' : '' ?>"
79 href="<?= h($href) ?>"
80 data-title="<?= h(strtolower($mod['title'] . ' ' . $groupName)) ?>"
81 <?= $isExternal ? 'target="' . h($mod['target'] ?? '_self') . '"' : '' ?>>
82 <i class="fa <?= h($mod['icon']) ?>"></i>
83 <span class="acp-nav-text"><?= h($mod['title']) ?></span>
84 <?php if ($badge !== null): ?>
85 <span class="acp-nav-badge"><?= (int)$badge ?></span>
86 <?php elseif ($isExternal): ?>
87 <i class="fa fa-external-link acp-nav-ext"></i>
88 <?php endif; ?>
89 </a>
90 </li>
91 <?php
92 };
93 ?>
94 <?php foreach ($acp_groups as $groupName => $groupModules):
95 $acp_cluster = acp_nav_cluster($groupModules);
96 ?>
97 <div class="acp-nav-group">
98 <button type="button" class="acp-nav-group-label" aria-expanded="true">
99 <span><?= h($groupName) ?></span>
100 <i class="fa fa-angle-down"></i>
101 </button>
102 <ul>
103 <?php foreach ($acp_cluster['plain'] as $key => $mod): $acp_nav_link($key, $mod, $groupName); endforeach; ?>
104
105 <?php foreach ($acp_cluster['plugins'] as $pluginKey => $cluster):
106 $pluginActive = array_key_exists($acp_page, $cluster['items']);
107 ?>
108 <li class="acp-nav-plugin<?= $pluginActive ? ' is-open' : '' ?>">
109 <button type="button" class="acp-nav-plugin-label" aria-expanded="<?= $pluginActive ? 'true' : 'false' ?>">
110 <i class="fa fa-plug"></i>
111 <span class="acp-nav-text"><?= h($cluster['label']) ?></span>
112 <i class="fa fa-angle-right acp-nav-caret"></i>
113 </button>
114 <ul>
115 <?php foreach ($cluster['items'] as $key => $mod): $acp_nav_link($key, $mod, $groupName . ' ' . $cluster['label']); endforeach; ?>
116 </ul>
117 </li>
118 <?php endforeach; ?>
119 </ul>
120 </div>
121 <?php endforeach; ?>
122 <p class="acp-nav-nomatch" hidden><?= t('acp.shell.no_match') ?></p>
123 </nav>
124 </aside>
125
126 <div class="acp-backdrop" id="acpBackdrop" hidden></div>
127
128 <section class="acp-panel">
129 <header class="acp-topbar">
130 <button type="button" class="acp-icon-btn acp-burger" id="acpBurger" aria-label="<?= h(t('acp.shell.toggle_menu')) ?>">
131 <i class="fa fa-bars"></i>
132 </button>
133 <div class="acp-top-title">
134 <strong><?= h($acp_siteName) ?></strong>
135 <span><?= h($acp_engine) ?> <?= t('acp.shell.control_panel') ?></span>
136 </div>
137 <form class="acp-top-search" method="get" action="index.php" role="search">
138 <input type="hidden" name="p" value="search">
139 <i class="fa fa-search"></i>
140 <input type="search" id="acpTopSearch" name="q" list="acpSearchList"
141 value="<?= h($acp_page === 'search' ? ($_GET['q'] ?? '') : '') ?>"
142 placeholder="<?= h(t('acp.shell.search_the_panel')) ?>" autocomplete="off" aria-label="<?= h(t('acp.shell.search_the_panel')) ?>">
143 <kbd class="acp-search-kbd" aria-hidden="true">Ctrl K</kbd>
144 <datalist id="acpSearchList">
145 <?php foreach (array_slice(acp_search_index(), 0, 300) as $acp_hit): ?>
146 <option value="<?= h($acp_hit['title']) ?>"></option>
147 <?php endforeach; ?>
148 </datalist>
149 </form>
150
151 <div class="acp-top-actions">
152 <a class="acp-icon-btn" href="../index.php" title="<?= h(t('acp.shell.view_site')) ?>" aria-label="<?= h(t('acp.shell.view_site')) ?>">
153 <i class="fa fa-globe"></i>
154 </a>
155 <button type="button" class="acp-theme-toggle" id="acpTheme" title="<?= h(t('acp.shell.toggle_theme')) ?>" aria-label="<?= h(t('acp.shell.toggle_theme')) ?>">
156 <i class="fa fa-moon-o"></i>
157 <span><?= t('acp.shell.night') ?></span>
158 </button>
159 <span class="acp-user">
160 <i class="fa fa-user-circle-o"></i>
161 <span class="acp-user-name"><?= h($acp_admin) ?></span>
162 </span>
163 <a class="acp-icon-btn" href="../login.php?logout" title="<?= h(t('acp.shell.log_out')) ?>" aria-label="<?= h(t('acp.shell.log_out')) ?>">
164 <i class="fa fa-sign-out"></i>
165 </a>
166 </div>
167 </header>
168
169 <main class="acp-content">
170
171 <div class="acp-page-head">
172 <?php if ($acp_page !== 'dashboard'): ?>
173 <nav class="acp-breadcrumb" aria-label="<?= h(t_default('acp.shell.breadcrumb_label', 'Breadcrumb')) ?>">
174 <a href="<?= h(acp_url('dashboard')) ?>"><?= h(t('acp.mod.dashboard.title')) ?></a>
175 <?php if (!empty($acp_module['group'])): ?>
176 <span class="acp-breadcrumb-sep">/</span>
177 <span><?= h($acp_module['group']) ?></span>
178 <?php endif; ?>
179 <span class="acp-breadcrumb-sep">/</span>
180 <span class="acp-breadcrumb-current"><?= h($acp_title) ?></span>
181 </nav>
182 <?php endif; ?>
183 <h1><?= h($acp_title) ?></h1>
184 <?php if (!empty($acp_module['description'])): ?>
185 <p><?= h($acp_module['description']) ?></p>
186 <?php endif; ?>
187 </div>
188
189 <?php foreach ($acp_flashes as $flash): ?>
190 <div class="acp-flash acp-flash--<?= h($flash['type']) ?>">
191 <i class="fa <?= $flash['type'] === 'success' ? 'fa-check-circle' : ($flash['type'] === 'error' ? 'fa-exclamation-triangle' : 'fa-info-circle') ?>"></i>
192 <span><?= $flash['message'] ?></span>
193 </div>
194 <?php endforeach; ?>
195
196 <?php if (!acp_log_table_exists()): ?>
197 <div class="acp-flash acp-flash--error">
198 <i class="fa fa-exclamation-triangle"></i>
199 <span>Administrative audit logging is unavailable because <code>znote_admin_log</code> is missing. Sensitive changes will not be traceable until the migration is applied.</span>
200 </div>
201 <?php endif; ?>
202
203 <?= $acp_content ?>
204 </main>
205
206 <footer class="acp-footer">
207 <span>© <?= h($acp_siteName) ?> · ZnoteX <?= h($version ?? '') ?></span>
208 <span>
209 <?= t('acp.shell.rendered_in', ['seconds' => elapsedTime()]) ?>
210 · <?= h(getClock(false, true)) ?>
211 </span>
212 </footer>
213 </section>
214</div>
215
216<script src="assets/acp.js?acp=3"></script>
217</body>
218</html>
219