- [index.php](/Users/tyemeclifford/Documents/GH/MyKeyser/index.php): events now appear before featured businesses, plus forum pretty-route fallback.
[events.php](/Users/tyemeclifford/Documents/GH/MyKeyser/events.php) and [admin/events.php](/Users/tyemeclifford/Documents/GH/MyKeyser/admin/events.php): event image uploads. [menu.php](/Users/tyemeclifford/Documents/GH/MyKeyser/menu.php), [admin/menus.php](/Users/tyemeclifford/Documents/GH/MyKeyser/admin/menus.php), and [business.php](/Users/tyemeclifford/Documents/GH/MyKeyser/business.php): one configurable menu item image slot. [account.php](/Users/tyemeclifford/Documents/GH/MyKeyser/account.php): circular avatar upload and crop controls. [forum.php](/Users/tyemeclifford/Documents/GH/MyKeyser/forum.php), [admin/forum.php](/Users/tyemeclifford/Documents/GH/MyKeyser/admin/forum.php), [.htaccess](/Users/tyemeclifford/Documents/GH/MyKeyser/.htaccess), and [forum/index.php](/Users/tyemeclifford/Documents/GH/MyKeyser/forum/index.php): forum, categories, topic/reply uploads, permalinks, toggle/moderation. [includes/db.php](/Users/tyemeclifford/Documents/GH/MyKeyser/includes/db.php) and [includes/functions.php](/Users/tyemeclifford/Documents/GH/MyKeyser/includes/functions.php): schema upgrades and shared upload/image helpers. [assets/css/style.css](/Users/tyemeclifford/Documents/GH/MyKeyser/assets/css/style.css): neon styling for forum, images, avatars, attachments.
This commit is contained in:
+223
@@ -0,0 +1,223 @@
|
||||
<?php
|
||||
require_once dirname(__DIR__).'/includes/boot.php';
|
||||
requireAdmin();
|
||||
|
||||
function forumCategorySlugForAdmin(string $name, int $id = 0): string {
|
||||
$base = makeSlug($name) ?: 'category';
|
||||
$slug = $base;
|
||||
$i = 1;
|
||||
while (qval("SELECT id FROM forum_categories WHERE slug=? AND id!=?", [$slug, $id])) {
|
||||
$slug = $base.'-'.$i++;
|
||||
}
|
||||
return $slug;
|
||||
}
|
||||
|
||||
if (isPost()) {
|
||||
csrfCheck();
|
||||
$act = p('_act');
|
||||
|
||||
if ($act === 'settings') {
|
||||
setSetting('forum_enabled', p('forum_enabled','0') === '1' ? '1' : '0');
|
||||
setSetting('forum_upload_max_mb', (string)max(1, (float)p('forum_upload_max_mb','5')));
|
||||
$allowed = strtolower(ps('forum_allowed_filetypes'));
|
||||
$allowed = implode(',', uploadAllowedExts($allowed, ['jpg','jpeg','png','gif','webp','pdf','txt','doc','docx']));
|
||||
setSetting('forum_allowed_filetypes', $allowed);
|
||||
setSetting('forum_image_resize_threshold_mb', (string)max(0.5, (float)p('forum_image_resize_threshold_mb','1')));
|
||||
setSetting('forum_image_max_width', (string)max(640, min(3200, (int)p('forum_image_max_width','1600'))));
|
||||
setSetting('forum_image_quality', (string)max(40, min(95, (int)p('forum_image_quality','82'))));
|
||||
flash('Forum settings saved.', 'success');
|
||||
go('/admin/forum.php');
|
||||
}
|
||||
|
||||
if ($act === 'add_category') {
|
||||
$name = ps('name');
|
||||
if ($name !== '') {
|
||||
qrun("INSERT INTO forum_categories(name,slug,description,sort_order,is_active)VALUES(?,?,?,?,?)",
|
||||
[$name, forumCategorySlugForAdmin($name), ps('description'), (int)p('sort_order',0), (int)p('is_active',1)]);
|
||||
flash('Category added.', 'success');
|
||||
}
|
||||
go('/admin/forum.php#categories');
|
||||
}
|
||||
|
||||
if ($act === 'edit_category') {
|
||||
$id = (int)p('id');
|
||||
$name = ps('name');
|
||||
if ($id && $name !== '') {
|
||||
qrun("UPDATE forum_categories SET name=?,slug=?,description=?,sort_order=?,is_active=? WHERE id=?",
|
||||
[$name, forumCategorySlugForAdmin($name, $id), ps('description'), (int)p('sort_order',0), (int)p('is_active',1), $id]);
|
||||
flash('Category updated.', 'success');
|
||||
}
|
||||
go('/admin/forum.php#categories');
|
||||
}
|
||||
|
||||
if ($act === 'delete_category') {
|
||||
$id = (int)p('id');
|
||||
$topicCount = (int)qval("SELECT COUNT(*) FROM forum_topics WHERE category_id=?", [$id]);
|
||||
if ($topicCount > 0) {
|
||||
qrun("UPDATE forum_categories SET is_active=0 WHERE id=?", [$id]);
|
||||
flash('Category has topics, so it was deactivated instead of deleted.', 'warning');
|
||||
} else {
|
||||
qrun("DELETE FROM forum_categories WHERE id=?", [$id]);
|
||||
flash('Category deleted.', 'success');
|
||||
}
|
||||
go('/admin/forum.php#categories');
|
||||
}
|
||||
|
||||
if (in_array($act, ['toggle_pin','toggle_lock','delete_topic'], true)) {
|
||||
$id = (int)p('id');
|
||||
if ($act === 'toggle_pin') {
|
||||
qrun("UPDATE forum_topics SET is_pinned=1-is_pinned WHERE id=?", [$id]);
|
||||
flash('Topic pin status updated.', 'success');
|
||||
} elseif ($act === 'toggle_lock') {
|
||||
qrun("UPDATE forum_topics SET is_locked=1-is_locked WHERE id=?", [$id]);
|
||||
flash('Topic lock status updated.', 'success');
|
||||
} else {
|
||||
qrun("DELETE FROM forum_topics WHERE id=?", [$id]);
|
||||
flash('Topic deleted.', 'success');
|
||||
}
|
||||
go('/admin/forum.php#topics');
|
||||
}
|
||||
}
|
||||
|
||||
$adminTitle = 'Forum';
|
||||
include __DIR__.'/admin_header.php';
|
||||
|
||||
$forumEnabled = setting('forum_enabled','1');
|
||||
$forumUploadMax = setting('forum_upload_max_mb','5');
|
||||
$forumAllowed = setting('forum_allowed_filetypes','jpg,jpeg,png,gif,webp,pdf,txt,doc,docx');
|
||||
$resizeThreshold = setting('forum_image_resize_threshold_mb','1');
|
||||
$resizeMaxWidth = setting('forum_image_max_width','1600');
|
||||
$resizeQuality = setting('forum_image_quality','82');
|
||||
$counts = [
|
||||
'Topics' => qval("SELECT COUNT(*) FROM forum_topics"),
|
||||
'Replies' => qval("SELECT COUNT(*) FROM forum_replies"),
|
||||
'Categories' => qval("SELECT COUNT(*) FROM forum_categories"),
|
||||
'Attachments' => qval("SELECT COUNT(*) FROM forum_attachments"),
|
||||
];
|
||||
$categories = qall("
|
||||
SELECT fc.*, COUNT(ft.id) topic_count
|
||||
FROM forum_categories fc
|
||||
LEFT JOIN forum_topics ft ON ft.category_id=fc.id
|
||||
GROUP BY fc.id
|
||||
ORDER BY fc.sort_order, fc.name
|
||||
");
|
||||
$topics = qall("
|
||||
SELECT ft.*, fc.name category_name, u.username,
|
||||
(SELECT COUNT(*) FROM forum_replies fr WHERE fr.topic_id=ft.id) reply_count
|
||||
FROM forum_topics ft
|
||||
JOIN forum_categories fc ON fc.id=ft.category_id
|
||||
JOIN users u ON u.id=ft.user_id
|
||||
ORDER BY ft.is_pinned DESC, datetime(ft.updated_at) DESC
|
||||
LIMIT 60
|
||||
");
|
||||
?>
|
||||
<?php adminTitle('Forum Controls', 'Enable the forum, manage upload rules, categories, and recent topics.') ?>
|
||||
|
||||
<div class="stat-cards">
|
||||
<?php foreach($counts as $label=>$value): ?>
|
||||
<div class="stat-card"><div class="sc-n"><?=$value?></div><div class="sc-l"><?=e($label)?></div></div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<div class="g2" style="gap:1.5rem">
|
||||
<div class="ibox" style="border-color:var(--gold-d)">
|
||||
<div class="ibox-title">FORUM SETTINGS</div>
|
||||
<form method="POST"><?=csrfField()?><input type="hidden" name="_act" value="settings">
|
||||
<div class="fg">
|
||||
<label class="flabel">FORUM VISIBILITY</label>
|
||||
<div style="display:flex;gap:1rem;flex-wrap:wrap">
|
||||
<label style="display:flex;gap:.45rem;align-items:center;color:var(--green-l);cursor:pointer"><input type="radio" name="forum_enabled" value="1"<?=$forumEnabled==='1'?' checked':''?>> Enabled for members</label>
|
||||
<label style="display:flex;gap:.45rem;align-items:center;color:var(--red-l);cursor:pointer"><input type="radio" name="forum_enabled" value="0"<?=$forumEnabled==='0'?' checked':''?>> Disabled for non-admins</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="fg"><label class="flabel">FILE MAX MB</label><input type="number" name="forum_upload_max_mb" class="finput" min="1" step="0.5" value="<?=e($forumUploadMax)?>"></div>
|
||||
<div class="fg"><label class="flabel">ALLOWED FILE TYPES</label><input type="text" name="forum_allowed_filetypes" class="finput" value="<?=e($forumAllowed)?>"><div class="fhint">Comma-separated extensions.</div></div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="fg"><label class="flabel">RESIZE IMAGES OVER MB</label><input type="number" name="forum_image_resize_threshold_mb" class="finput" min="0.5" step="0.5" value="<?=e($resizeThreshold)?>"></div>
|
||||
<div class="fg"><label class="flabel">MAX IMAGE WIDTH</label><input type="number" name="forum_image_max_width" class="finput" min="640" max="3200" step="80" value="<?=e($resizeMaxWidth)?>"></div>
|
||||
</div>
|
||||
<div class="fg"><label class="flabel">IMAGE QUALITY</label><input type="number" name="forum_image_quality" class="finput" min="40" max="95" value="<?=e($resizeQuality)?>"></div>
|
||||
<button class="btn btn-primary">Save Forum Settings</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="ibox" id="categories" style="border-color:var(--gold-d)">
|
||||
<div class="ibox-title">ADD CATEGORY</div>
|
||||
<form method="POST"><?=csrfField()?><input type="hidden" name="_act" value="add_category">
|
||||
<div class="form-row">
|
||||
<div class="fg"><label class="flabel">NAME</label><input type="text" name="name" class="finput" required></div>
|
||||
<div class="fg"><label class="flabel">SORT ORDER</label><input type="number" name="sort_order" class="finput" value="0"></div>
|
||||
</div>
|
||||
<div class="fg"><label class="flabel">DESCRIPTION</label><input type="text" name="description" class="finput"></div>
|
||||
<div class="fg"><label class="flabel">STATUS</label><select name="is_active" class="fselect"><option value="1">Active</option><option value="0">Inactive</option></select></div>
|
||||
<button class="btn btn-primary btn-sm">Add Category</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="admin-sec-title" style="margin-top:1.5rem">CATEGORIES</div>
|
||||
<div class="tbl-wrap" style="margin-bottom:2rem">
|
||||
<table class="dtbl">
|
||||
<thead><tr><th>Name</th><th>Description</th><th>Topics</th><th>Status</th><th>Order</th><th>Actions</th></tr></thead>
|
||||
<tbody>
|
||||
<?php foreach($categories as $cat): ?>
|
||||
<tr>
|
||||
<td class="td-name"><?=e($cat['name'])?></td>
|
||||
<td style="font-size:.8rem"><?=e($cat['description'])?></td>
|
||||
<td><?=$cat['topic_count']?></td>
|
||||
<td><?=$cat['is_active']?'<span class="badge badge-green">Active</span>':'<span class="badge badge-gray">Inactive</span>'?></td>
|
||||
<td><?=$cat['sort_order']?></td>
|
||||
<td>
|
||||
<details>
|
||||
<summary class="btn btn-xs btn-outline" style="display:inline-flex">Edit</summary>
|
||||
<form method="POST" class="admin-inline-editor">
|
||||
<?=csrfField()?><input type="hidden" name="_act" value="edit_category"><input type="hidden" name="id" value="<?=$cat['id']?>">
|
||||
<div class="form-row">
|
||||
<input type="text" name="name" class="finput" value="<?=e($cat['name'])?>" required>
|
||||
<input type="number" name="sort_order" class="finput" value="<?=$cat['sort_order']?>">
|
||||
</div>
|
||||
<input type="text" name="description" class="finput" value="<?=e($cat['description'])?>">
|
||||
<select name="is_active" class="fselect"><option value="1"<?=$cat['is_active']?' selected':''?>>Active</option><option value="0"<?=!$cat['is_active']?' selected':''?>>Inactive</option></select>
|
||||
<button class="btn btn-success btn-xs">Save</button>
|
||||
</form>
|
||||
</details>
|
||||
<form method="POST" style="display:inline"><?=csrfField()?><input type="hidden" name="_act" value="delete_category"><input type="hidden" name="id" value="<?=$cat['id']?>"><button class="btn btn-xs btn-danger" data-confirm="Delete this category?">Delete</button></form>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="admin-sec-title" id="topics">RECENT TOPICS</div>
|
||||
<div class="tbl-wrap">
|
||||
<table class="dtbl">
|
||||
<thead><tr><th>Topic</th><th>Category</th><th>By</th><th>Replies</th><th>Status</th><th>Actions</th></tr></thead>
|
||||
<tbody>
|
||||
<?php foreach($topics as $topic): ?>
|
||||
<tr>
|
||||
<td><a class="td-name" href="<?=e(forumTopicUrl($topic))?>" target="_blank"><?=e($topic['title'])?></a><div style="font-size:.75rem;color:var(--text-d)"><?=ago($topic['updated_at'])?></div></td>
|
||||
<td><?=e($topic['category_name'])?></td>
|
||||
<td><?=e($topic['username'])?></td>
|
||||
<td><?=$topic['reply_count']?></td>
|
||||
<td>
|
||||
<?php if($topic['is_pinned']): ?><span class="badge badge-gold">Pinned</span><?php endif; ?>
|
||||
<?php if($topic['is_locked']): ?><span class="badge badge-gray">Locked</span><?php endif; ?>
|
||||
</td>
|
||||
<td>
|
||||
<div class="flex-row" style="gap:.3rem">
|
||||
<form method="POST"><?=csrfField()?><input type="hidden" name="_act" value="toggle_pin"><input type="hidden" name="id" value="<?=$topic['id']?>"><button class="btn btn-xs btn-outline"><?=$topic['is_pinned']?'Unpin':'Pin'?></button></form>
|
||||
<form method="POST"><?=csrfField()?><input type="hidden" name="_act" value="toggle_lock"><input type="hidden" name="id" value="<?=$topic['id']?>"><button class="btn btn-xs btn-secondary"><?=$topic['is_locked']?'Unlock':'Lock'?></button></form>
|
||||
<form method="POST"><?=csrfField()?><input type="hidden" name="_act" value="delete_topic"><input type="hidden" name="id" value="<?=$topic['id']?>"><button class="btn btn-xs btn-danger" data-confirm="Delete this topic and replies?">Delete</button></form>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php if(!$topics): ?><tr><td colspan="6" style="text-align:center;color:var(--text-d)">No forum topics yet.</td></tr><?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<?php include __DIR__.'/admin_footer.php'; ?>
|
||||
Reference in New Issue
Block a user