xgit simple git

nexus

nexus

clone git clone https://kb.hax.al/nexus

admin/categories.php


1<?php
2require_once __DIR__ . '/../includes/bootstrap.php';
3must_admin();
4$PAGE_TITLE = 'Categories';
5$ADMIN_PAGE = 'categories';
6$flash = null;
7 
8// Role permission helper
9function roleSelect(string $name, string $current, string $label): string {
10    $opts = [
11        'guest'     => '๐ŸŒ Everyone (guests)',
12        'member'    => '๐Ÿ‘ค Members only',
13        'moderator' => '๐Ÿ›ก๏ธ Moderators+',
14        'admin'     => '๐Ÿ‘‘ Admins only',
15    ];
16    $html = '<div class="fg" style="margin-bottom:8px">'
17          . '<label style="font-size:12px;color:var(--muted)">' . htmlspecialchars($label) . '</label>'
18          . '<select name="' . htmlspecialchars($name) . '" class="fi" style="padding:6px 8px;font-size:13px">';
19    foreach ($opts as $val => $lbl) {
20        $html .= '<option value="' . $val . '"' . ($current === $val ? ' selected' : '') . '>' . $lbl . '</option>';
21    }
22    return $html . '</select></div>';
23}
24 
25if ($_SERVER['REQUEST_METHOD'] === 'POST' && csrf_ok()) {
26    $action     = post('action');
27    $validRoles = ['guest', 'member', 'moderator', 'admin'];
28 
29    if ($action === 'create') {
30        $name = post('name');
31        if ($name) {
32            $readRole  = in_array(post('read_role'),  $validRoles) ? post('read_role')  : 'guest';
33            $postRole  = in_array(post('post_role'),  $validRoles) ? post('post_role')  : 'member';
34            $replyRole = in_array(post('reply_role'), $validRoles) ? post('reply_role') : 'member';
35            $slug = unique_slug($name, 'categories');
36            DB::insert(
37                'INSERT INTO categories (name,slug,description,color,icon,position,parent_id,read_role,post_role,reply_role)
38                 VALUES (?,?,?,?,?,?,?,?,?,?)',
39                [$name, $slug, post('desc'), post('color', '#3b82f6'), post('icon', '๐Ÿ’ฌ'),
40                 (int)post('pos', 99), post('parent_id') ?: null, $readRole, $postRole, $replyRole]
41            );
42            $flash = ['ok', 'Category created!'];
43        }
44    } elseif ($action === 'update') {
45        $readRole  = in_array(post('read_role'),  $validRoles) ? post('read_role')  : 'guest';
46        $postRole  = in_array(post('post_role'),  $validRoles) ? post('post_role')  : 'member';
47        $replyRole = in_array(post('reply_role'), $validRoles) ? post('reply_role') : 'member';
48        DB::run(
49            'UPDATE categories SET name=?,description=?,color=?,icon=?,position=?,read_role=?,post_role=?,reply_role=? WHERE id=?',
50            [post('name'), post('desc'), post('color', '#3b82f6'), post('icon', '๐Ÿ’ฌ'),
51             (int)post('pos'), $readRole, $postRole, $replyRole, (int)post('id')]
52        );
53        $flash = ['ok', 'Saved!'];
54    } elseif ($action === 'delete') {
55        $cid = (int)post('id');
56        $tc  = (int)DB::val('SELECT COUNT(*) FROM topics WHERE category_id=?', [$cid]);
57        if ($tc > 0) {
58            $flash = ['err', 'Cannot delete a category that has topics.'];
59        } else {
60            DB::run('DELETE FROM categories WHERE id=?', [$cid]);
61            $flash = ['ok', 'Deleted.'];
62        }
63    }
64}
65 
66$cats = DB::rows('SELECT * FROM categories ORDER BY position, id');
67include __DIR__ . '/../views/partials/admin_layout.php';
68?>
69 
70<?php if ($flash): ?>
71  <div class="alert <?= $flash[0] ?>"><?= e($flash[1]) ?></div>
72<?php endif; ?>
73 
74<div class="admin-two-col">
75 
76  <!-- โ”€โ”€ Category list โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ -->
77  <div class="acard">
78    <div class="acard-head"><h2>All Categories</h2></div>
79    <table class="atable">
80      <thead>
81        <tr><th>Name</th><th>Permissions</th><th>Topics</th><th>Posts</th><th></th></tr>
82      </thead>
83      <tbody>
84        <?php foreach ($cats as $c): ?>
85          <tr id="cr-<?= $c['id'] ?>">
86            <td>
87              <span style="display:inline-block;width:10px;height:10px;border-radius:50%;background:<?= e($c['color']) ?>;margin-right:6px;vertical-align:middle"></span>
88              <?= e($c['icon']) ?> <?= e($c['name']) ?>
89            </td>
90            <td style="font-size:11px;color:var(--muted)">
91              Read: <strong><?= e($c['read_role'] ?? 'guest') ?></strong> ยท
92              Post: <strong><?= e($c['post_role'] ?? 'member') ?></strong> ยท
93              Reply: <strong><?= e($c['reply_role'] ?? 'member') ?></strong>
94            </td>
95            <td><?= $c['topic_count'] ?></td>
96            <td><?= $c['post_count'] ?></td>
97            <td style="white-space:nowrap">
98              <button class="btn-ghost btn-sm" onclick="toggleEdit(<?= $c['id'] ?>)">Edit</button>
99              <form method="POST" style="display:inline">
100                <?= csrf_input() ?>
101                <input type="hidden" name="action" value="delete">
102                <input type="hidden" name="id" value="<?= $c['id'] ?>">
103                <button class="btn-danger btn-sm" onclick="return confirm('Delete this category?')">Delete</button>
104              </form>
105            </td>
106          </tr>
107 
108          <!-- Inline edit row -->
109          <tr id="ce-<?= $c['id'] ?>" style="display:none;background:#f8fafc">
110            <td colspan="5" style="padding:16px 18px">
111              <form method="POST">
112                <?= csrf_input() ?>
113                <input type="hidden" name="action" value="update">
114                <input type="hidden" name="id" value="<?= $c['id'] ?>">
115 
116                <div style="display:grid;grid-template-columns:1fr 60px 80px 70px;gap:10px;margin-bottom:10px">
117                  <div class="fg" style="margin:0">
118                    <label style="font-size:12px;color:var(--muted)">Name</label>
119                    <input type="text" name="name" value="<?= e($c['name']) ?>" class="fi" required>
120                  </div>
121                  <div class="fg" style="margin:0">
122                    <label style="font-size:12px;color:var(--muted)">Icon</label>
123                    <input type="text" name="icon" value="<?= e($c['icon']) ?>" class="fi" style="font-size:1.3rem;text-align:center">
124                  </div>
125                  <div class="fg" style="margin:0">
126                    <label style="font-size:12px;color:var(--muted)">Color</label>
127                    <input type="color" name="color" value="<?= e($c['color']) ?>" class="fi" style="height:40px;padding:2px">
128                  </div>
129                  <div class="fg" style="margin:0">
130                    <label style="font-size:12px;color:var(--muted)">Order</label>
131                    <input type="number" name="pos" value="<?= $c['position'] ?>" class="fi" min="0">
132                  </div>
133                </div>
134 
135                <div style="margin-bottom:12px">
136                  <label style="font-size:12px;color:var(--muted)">Description</label>
137                  <input type="text" name="desc" value="<?= e($c['description']) ?>" class="fi" placeholder="Short description">
138                </div>
139 
140                <div style="background:#fff;border:1px solid #e2e8f0;border-radius:8px;padding:14px;margin-bottom:12px">
141                  <div style="font-size:12px;font-weight:600;color:#64748b;margin-bottom:10px">๐Ÿ” Role Permissions</div>
142                  <div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:12px">
143                    <?= roleSelect('read_role',  $c['read_role']  ?? 'guest',  'Who can read') ?>
144                    <?= roleSelect('post_role',  $c['post_role']  ?? 'member', 'Who can post topics') ?>
145                    <?= roleSelect('reply_role', $c['reply_role'] ?? 'member', 'Who can reply') ?>
146                  </div>
147                </div>
148 
149                <div style="display:flex;gap:8px;justify-content:flex-end">
150                  <button type="button" class="btn-ghost btn-sm" onclick="toggleEdit(<?= $c['id'] ?>)">Cancel</button>
151                  <button type="submit" class="btn-primary btn-sm">Save Changes</button>
152                </div>
153              </form>
154            </td>
155          </tr>
156        <?php endforeach; ?>
157 
158        <?php if (empty($cats)): ?>
159          <tr><td colspan="5" style="text-align:center;padding:24px;color:var(--muted)">No categories yet.</td></tr>
160        <?php endif; ?>
161      </tbody>
162    </table>
163  </div>
164 
165  <!-- โ”€โ”€ Create category โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ -->
166  <div class="acard">
167    <div class="acard-head"><h2>Create Category</h2></div>
168    <div class="acard-body">
169      <form method="POST">
170        <?= csrf_input() ?>
171        <input type="hidden" name="action" value="create">
172 
173        <div class="fg">
174          <label>Name <span class="req">*</span></label>
175          <input type="text" name="name" class="fi" required placeholder="Category name">
176        </div>
177 
178        <div class="fg">
179          <label>Description</label>
180          <input type="text" name="desc" class="fi" placeholder="Short description (optional)">
181        </div>
182 
183        <div style="display:grid;grid-template-columns:1fr 70px 90px 80px;gap:10px;margin-bottom:16px">
184          <div class="fg" style="margin:0">
185            <label>Icon</label>
186            <input type="text" name="icon" class="fi" value="๐Ÿ’ฌ" style="font-size:1.3rem;text-align:center">
187          </div>
188          <div class="fg" style="margin:0">
189            <label>Color</label>
190            <input type="color" name="color" class="fi" value="#3b82f6" style="height:40px;padding:2px">
191          </div>
192          <div class="fg" style="margin:0">
193            <label>Position</label>
194            <input type="number" name="pos" class="fi" value="99" min="0">
195          </div>
196        </div>
197 
198        <div style="background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px;padding:14px;margin-bottom:16px">
199          <div style="font-size:13px;font-weight:600;margin-bottom:10px">๐Ÿ” Role Permissions</div>
200          <div style="display:grid;grid-template-columns:1fr;gap:8px">
201            <?= roleSelect('read_role',  'guest',  'Who can read this category') ?>
202            <?= roleSelect('post_role',  'member', 'Who can create topics') ?>
203            <?= roleSelect('reply_role', 'member', 'Who can reply to topics') ?>
204          </div>
205        </div>
206 
207        <div class="fg">
208          <label>Parent Category <small>(optional)</small></label>
209          <select name="parent_id" class="fi">
210            <option value="">None (top-level)</option>
211            <?php foreach (array_filter($cats, fn($c) => !$c['parent_id']) as $c): ?>
212              <option value="<?= $c['id'] ?>"><?= e($c['icon']) ?> <?= e($c['name']) ?></option>
213            <?php endforeach; ?>
214          </select>
215        </div>
216 
217        <button type="submit" class="btn-primary" style="width:100%">Create Category</button>
218      </form>
219    </div>
220  </div>
221 
222</div>
223 
224<script>
225function toggleEdit(id) {
226  var row  = document.getElementById('cr-' + id);
227  var edit = document.getElementById('ce-' + id);
228  var show = edit.style.display === 'none';
229  edit.style.display = show ? '' : 'none';
230  row.style.opacity  = show ? '0.4' : '1';
231}
232</script>
233 
234<?php include __DIR__ . '/../views/partials/admin_layout_end.php'; ?>