- [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:
Ty Clifford
2026-06-19 10:49:48 -04:00
parent 2ce3eec65d
commit 25327e3302
21 changed files with 1363 additions and 74 deletions
+1
View File
@@ -25,6 +25,7 @@ $_pendingClaims = (int)qval("SELECT COUNT(*) FROM business_claim_requests WHERE
<?php endif; ?>
</a>
<div class="admin-side-title" style="margin-top:1rem">COMMUNITY</div>
<a class="asl" href="/admin/forum.php">💬 Forum</a>
<a class="asl" href="/admin/events.php">📅 Events</a>
<a class="asl" href="/admin/attractions.php">🗺️ Attractions</a>
<a class="asl" href="/admin/reports.php">🚩 Reports</a>
+40 -7
View File
@@ -1,6 +1,7 @@
<?php
require_once dirname(__DIR__).'/includes/boot.php';
requireAdmin();
$adminTitle = 'Events';
include __DIR__.'/admin_header.php';
if (isPost()) {
csrfCheck();
@@ -12,18 +13,36 @@ if (isPost()) {
if ($act === 'feature') { qrun("UPDATE events SET is_featured=1-is_featured WHERE id=?",[(int)p('id')]); go('/admin/events.php'); }
if ($act === 'add' || $act === 'edit') {
$id = (int)p('id');
$existing = $act === 'edit' ? qone("SELECT image_path FROM events WHERE id=?", [$id]) : null;
$imagePath = (string)($existing['image_path'] ?? '');
if (p('remove_image','0') === '1') $imagePath = '';
$upload = storeSingleUpload(
'event_image',
'events',
imageExts(),
mbToBytes(setting('event_upload_max_mb','5'), 5),
[
'image_only' => true,
'resize_over_bytes' => mbToBytes(setting('forum_image_resize_threshold_mb','1'), 1),
'image_max_width' => (int)setting('forum_image_max_width','1600'),
'image_quality' => (int)setting('forum_image_quality','82'),
]
);
if (!$upload['ok']) { flash($upload['error'], 'error'); go('/admin/events.php'.($id ? '?edit='.$id : '')); }
if (!empty($upload['path'])) $imagePath = (string)$upload['path'];
$f=['title'=>ps('title'),'description'=>ps('description'),'venue'=>ps('venue'),'address'=>ps('address'),
'event_date'=>ps('event_date'),'event_time'=>ps('event_time'),'end_date'=>ps('end_date')?:null,
'end_time'=>ps('end_time'),'cost'=>ps('cost'),'website'=>ps('website'),'contact_name'=>ps('contact_name'),
'contact_email'=>ps('contact_email'),'contact_phone'=>ps('contact_phone'),
'contact_email'=>ps('contact_email'),'contact_phone'=>ps('contact_phone'),'image_path'=>$imagePath,
'status'=>p('status','approved'),'is_featured'=>(int)p('is_featured',0)];
if (!$f['title'] || !$f['event_date']) { flash('Title and date required.','error'); go('/admin/events.php'); }
if ($act === 'add') {
qrun("INSERT INTO events(title,description,venue,address,event_date,event_time,end_date,end_time,cost,website,contact_name,contact_email,contact_phone,status,is_featured,submitted_by)VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",array_merge(array_values($f),[uid()]));
qrun("INSERT INTO events(title,description,venue,address,event_date,event_time,end_date,end_time,cost,website,contact_name,contact_email,contact_phone,image_path,status,is_featured,submitted_by)VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",array_merge(array_values($f),[uid()]));
flash('Event added!','success');
} else {
$id = (int)p('id');
qrun("UPDATE events SET title=?,description=?,venue=?,address=?,event_date=?,event_time=?,end_date=?,end_time=?,cost=?,website=?,contact_name=?,contact_email=?,contact_phone=?,status=?,is_featured=? WHERE id=?",array_merge(array_values($f),[$id]));
qrun("UPDATE events SET title=?,description=?,venue=?,address=?,event_date=?,event_time=?,end_date=?,end_time=?,cost=?,website=?,contact_name=?,contact_email=?,contact_phone=?,image_path=?,status=?,is_featured=? WHERE id=?",array_merge(array_values($f),[$id]));
flash('Event updated!','success');
}
go('/admin/events.php');
@@ -35,12 +54,13 @@ $editId = iget('edit');
$editing = $editId ? qone("SELECT * FROM events WHERE id=?",[$editId]) : null;
$where = match($filter) { 'pending'=>"WHERE status='pending'",'approved'=>"WHERE status='approved'", default=>"" };
$events = qall("SELECT e.*,u.username FROM events e LEFT JOIN users u ON u.id=e.submitted_by $where ORDER BY CASE status WHEN 'pending' THEN 0 ELSE 1 END,event_date DESC");
include __DIR__.'/admin_header.php';
?>
<?php adminTitle('Manage Events') ?>
<div class="ibox" style="border-color:var(--gold-d);margin-bottom:2rem">
<div class="ibox-title"><?=$editing ? '✏️ EDIT EVENT: '.strtoupper(e($editing['title'])) : ' ADD EVENT'?></div>
<form method="POST"><?=csrfField()?><input type="hidden" name="_act" value="<?=$editing?'edit':'add'?>">
<form method="POST" enctype="multipart/form-data"><?=csrfField()?><input type="hidden" name="_act" value="<?=$editing?'edit':'add'?>">
<?php if($editing): ?><input type="hidden" name="id" value="<?=$editing['id']?>"><?php endif; ?>
<div class="fg"><label class="flabel">TITLE *</label><input type="text" name="title" class="finput" value="<?=e($editing['title']??'')?>" required></div>
<div class="fg"><label class="flabel">DESCRIPTION</label><textarea name="description" class="ftextarea" style="min-height:80px"><?=e($editing['description']??'')?></textarea></div>
@@ -60,6 +80,19 @@ $events = qall("SELECT e.*,u.username FROM events e LEFT JOIN users u ON u.id=e.
<div class="fg"><label class="flabel">COST</label><input type="text" name="cost" class="finput" value="<?=e($editing['cost']??'Free')?>"></div>
<div class="fg"><label class="flabel">WEBSITE</label><input type="text" name="website" class="finput" value="<?=e($editing['website']??'')?>"></div>
</div>
<div class="fg">
<label class="flabel">EVENT IMAGE</label>
<?php if(!empty($editing['image_path'])): ?>
<div class="event-admin-image">
<img src="<?=e($editing['image_path'])?>" alt="<?=e($editing['title'] ?? 'Event image')?>">
<label style="display:flex;align-items:center;gap:.45rem;font-size:.85rem;color:var(--text-m);cursor:pointer">
<input type="checkbox" name="remove_image" value="1"> Remove current image
</label>
</div>
<?php endif; ?>
<input type="file" name="event_image" class="finput" accept="image/*">
<div class="fhint">Optional JPG, PNG, GIF, or WebP. Max <?=e(setting('event_upload_max_mb','5'))?> MB.</div>
</div>
<div class="form-row">
<div class="fg"><label class="flabel">CONTACT NAME</label><input type="text" name="contact_name" class="finput" value="<?=e($editing['contact_name']??'')?>"></div>
<div class="fg"><label class="flabel">CONTACT PHONE</label><input type="text" name="contact_phone" class="finput" value="<?=e($editing['contact_phone']??'')?>"></div>
@@ -90,7 +123,7 @@ $events = qall("SELECT e.*,u.username FROM events e LEFT JOIN users u ON u.id=e.
<tbody>
<?php foreach($events as $ev): ?>
<tr>
<td class="td-name" style="max-width:220px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap"><?=e($ev['title'])?></td>
<td class="td-name" style="max-width:220px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap"><?=e($ev['title'])?><?=!empty($ev['image_path'])?' <span class="badge badge-blue">Image</span>':''?></td>
<td style="font-size:.8rem"><?=fdate($ev['event_date'])?></td>
<td style="font-size:.8rem"><?=e($ev['username']??'Admin')?></td>
<td><?php $sc=['approved'=>'badge-green','pending'=>'badge-gold','rejected'=>'badge-red']; ?><span class="badge <?=$sc[$ev['status']]??"badge-gray"?>"><?=strtoupper($ev['status'])?></span></td>
+223
View File
@@ -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'; ?>
+32
View File
@@ -9,6 +9,7 @@ $stats = [
'Pending Events' => qval("SELECT COUNT(*) FROM events WHERE status='pending'"),
'Pending Claims' => qval("SELECT COUNT(*) FROM business_claim_requests WHERE status='pending'"),
'Pending Edits' => qval("SELECT COUNT(*) FROM business_edit_requests WHERE status='pending'"),
'Forum Topics' => qval("SELECT COUNT(*) FROM forum_topics"),
'Open Reports' => qval("SELECT COUNT(*) FROM reports WHERE status='open'"),
'Active Alerts' => qval("SELECT COUNT(*) FROM alerts WHERE is_active=1"),
];
@@ -34,6 +35,15 @@ $pendingEdits = qall("
ORDER BY ber.created_at DESC
LIMIT 10
");
$recentForumTopics = 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 datetime(ft.updated_at) DESC
LIMIT 5
");
?>
<?php adminTitle('Site Overview','Welcome to the Discover Keyser WV administration panel.') ?>
@@ -156,6 +166,28 @@ $pendingEdits = qall("
<?php endif; ?>
</div>
<?php if($recentForumTopics): ?>
<div style="margin-bottom:2rem">
<div class="admin-sec-title">FORUM ACTIVITY</div>
<div class="tbl-wrap">
<table class="dtbl">
<thead><tr><th>TOPIC</th><th>CATEGORY</th><th>BY</th><th>REPLIES</th><th></th></tr></thead>
<tbody>
<?php foreach($recentForumTopics as $topic): ?>
<tr>
<td><a href="<?=e(forumTopicUrl($topic))?>" target="_blank" class="td-name"><?=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><a href="/admin/forum.php#topics" class="btn btn-xs btn-primary">Moderate</a></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
<?php endif; ?>
<div class="g2">
<div>
<div class="admin-sec-title">🏢 RECENT BUSINESSES</div>
+62 -6
View File
@@ -1,9 +1,11 @@
<?php
require_once dirname(__DIR__).'/includes/boot.php';
requireAdmin();
$adminTitle = 'Menus';
include __DIR__.'/admin_header.php';
$bizId = iget('biz_id');
$biz = $bizId ? qone("SELECT * FROM businesses WHERE id=?",[$bizId]) : null;
$menuImagesEnabled = max(0, min(1, (int)setting('menu_item_image_limit','1'))) > 0;
if (isPost()) {
csrfCheck();
@@ -31,15 +33,51 @@ if (isPost()) {
$sid = (int)p('section_id');
$name = ps('item_name');
if ($sid && $name) {
$imagePath = '';
if ($menuImagesEnabled) {
$upload = storeSingleUpload(
'item_image',
'menus',
imageExts(),
mbToBytes(setting('menu_upload_max_mb','5'), 5),
[
'image_only' => true,
'resize_over_bytes' => mbToBytes(setting('forum_image_resize_threshold_mb','1'), 1),
'image_max_width' => (int)setting('forum_image_max_width','1600'),
'image_quality' => (int)setting('forum_image_quality','82'),
]
);
if (!$upload['ok']) { flash($upload['error'], 'error'); go("/admin/menus.php?biz_id=$bizId"); }
$imagePath = (string)($upload['path'] ?? '');
}
$ord = (int)qval("SELECT COALESCE(MAX(sort_order),0)+1 FROM menu_items WHERE section_id=?",[$sid]);
qrun("INSERT INTO menu_items(section_id,name,description,price,is_available,sort_order)VALUES(?,?,?,?,?,?)",[$sid,$name,ps('item_desc'),(float)p('item_price',0),(int)p('is_available',1),$ord]);
qrun("INSERT INTO menu_items(section_id,name,description,price,image_path,is_available,sort_order)VALUES(?,?,?,?,?,?,?)",[$sid,$name,ps('item_desc'),(float)p('item_price',0),$imagePath,(int)p('is_available',1),$ord]);
flash('Item added!','success');
}
go("/admin/menus.php?biz_id=$bizId");
}
if ($act === 'edit_item') {
$iid = (int)p('iid');
qrun("UPDATE menu_items SET name=?,description=?,price=?,is_available=?,sort_order=? WHERE id=?",[ps('item_name'),ps('item_desc'),(float)p('item_price',0),(int)p('is_available',1),(int)p('item_order'),$iid]);
$item = qone("SELECT * FROM menu_items WHERE id=?", [$iid]);
$imagePath = (string)($item['image_path'] ?? '');
if ($menuImagesEnabled && p('remove_item_image','0') === '1') $imagePath = '';
if ($menuImagesEnabled) {
$upload = storeSingleUpload(
'item_image',
'menus',
imageExts(),
mbToBytes(setting('menu_upload_max_mb','5'), 5),
[
'image_only' => true,
'resize_over_bytes' => mbToBytes(setting('forum_image_resize_threshold_mb','1'), 1),
'image_max_width' => (int)setting('forum_image_max_width','1600'),
'image_quality' => (int)setting('forum_image_quality','82'),
]
);
if (!$upload['ok']) { flash($upload['error'], 'error'); go("/admin/menus.php?biz_id=$bizId"); }
if (!empty($upload['path'])) $imagePath = (string)$upload['path'];
}
qrun("UPDATE menu_items SET name=?,description=?,price=?,image_path=?,is_available=?,sort_order=? WHERE id=?",[ps('item_name'),ps('item_desc'),(float)p('item_price',0),$imagePath,(int)p('is_available',1),(int)p('item_order'),$iid]);
flash('Item updated!','success'); go("/admin/menus.php?biz_id=$bizId");
}
if ($act === 'del_item') {
@@ -55,6 +93,7 @@ if ($biz) {
$items = [];
foreach ($sections as $s) $items[$s['id']] = qall("SELECT * FROM menu_items WHERE section_id=? ORDER BY sort_order",[$s['id']]);
}
include __DIR__.'/admin_header.php';
?>
<?php adminTitle('Manage Menus','Edit menus, sections, and items for any business.') ?>
@@ -117,11 +156,12 @@ if ($biz) {
<?php if($items[$sec['id']]): ?>
<div class="tbl-wrap" style="margin-bottom:.85rem">
<table class="dtbl">
<thead><tr><th>ITEM NAME</th><th>DESCRIPTION</th><th>PRICE</th><th>AVAIL.</th><th>ORDER</th><th></th></tr></thead>
<thead><tr><th>ITEM NAME</th><th>IMAGE</th><th>DESCRIPTION</th><th>PRICE</th><th>AVAIL.</th><th>ORDER</th><th></th></tr></thead>
<tbody>
<?php foreach($items[$sec['id']] as $item): ?>
<tr>
<td class="td-name"><?=e($item['name'])?></td>
<td><?php if(!empty($item['image_path'])): ?><img src="<?=e($item['image_path'])?>" alt="<?=e($item['name'])?>" class="admin-menu-thumb"><?php else: ?>—<?php endif; ?></td>
<td style="font-size:.8rem;max-width:180px"><?=e(substr($item['description'],0,80))?></td>
<td style="color:var(--gold);font-family:'Oswald',sans-serif"><?=$item['price']>0?money($item['price']):'—'?></td>
<td><?=$item['is_available']?'<span class="badge badge-green">Yes</span>':'<span class="badge badge-gray">No</span>'?></td>
@@ -132,12 +172,25 @@ if ($biz) {
<form method="POST" style="display:inline"><?=csrfField()?><input type="hidden" name="_act" value="del_item"><input type="hidden" name="iid" value="<?=$item['id']?>"><button class="btn btn-xs btn-danger" data-confirm="Delete item?">🗑️</button></form>
</div>
<div id="eif<?=$item['id']?>" class="hidden" style="background:var(--bg2);border:1px solid var(--bdr);border-radius:4px;padding:.75rem;margin-top:.4rem;min-width:320px">
<form method="POST"><?=csrfField()?><input type="hidden" name="_act" value="edit_item"><input type="hidden" name="iid" value="<?=$item['id']?>">
<form method="POST" enctype="multipart/form-data"><?=csrfField()?><input type="hidden" name="_act" value="edit_item"><input type="hidden" name="iid" value="<?=$item['id']?>">
<div class="form-row">
<div class="fg"><label class="flabel">NAME *</label><input type="text" name="item_name" class="finput" value="<?=e($item['name'])?>" required></div>
<div class="fg"><label class="flabel">PRICE</label><input type="number" name="item_price" class="finput" value="<?=$item['price']?>" step="0.01" min="0"></div>
</div>
<div class="fg"><label class="flabel">DESCRIPTION</label><input type="text" name="item_desc" class="finput" value="<?=e($item['description'])?>"></div>
<?php if($menuImagesEnabled): ?>
<div class="fg">
<label class="flabel">ITEM IMAGE</label>
<?php if(!empty($item['image_path'])): ?>
<div class="menu-admin-image">
<img src="<?=e($item['image_path'])?>" alt="<?=e($item['name'])?>">
<label style="display:flex;align-items:center;gap:.45rem;font-size:.82rem;color:var(--text-m);cursor:pointer"><input type="checkbox" name="remove_item_image" value="1"> Remove current image</label>
</div>
<?php endif; ?>
<input type="file" name="item_image" class="finput" accept="image/*">
<div class="fhint">One image allowed. Max <?=e(setting('menu_upload_max_mb','5'))?> MB.</div>
</div>
<?php endif; ?>
<div class="form-row">
<div class="fg"><label class="flabel">AVAILABLE</label><select name="is_available" class="fselect"><option value="1"<?=$item['is_available']?' selected':''?>>Yes</option><option value="0"<?=!$item['is_available']?' selected':''?>>No</option></select></div>
<div class="fg"><label class="flabel">SORT ORDER</label><input type="number" name="item_order" class="finput" value="<?=$item['sort_order']?>"></div>
@@ -156,12 +209,15 @@ if ($biz) {
<!-- Add item -->
<details><summary style="cursor:pointer;font-size:.82rem;color:var(--gold);font-family:'Oswald',sans-serif;letter-spacing:.08em"> Add Item to "<?=e($sec['name'])?>"</summary>
<div style="padding:.75rem 0">
<form method="POST"><?=csrfField()?><input type="hidden" name="_act" value="add_item"><input type="hidden" name="section_id" value="<?=$sec['id']?>">
<form method="POST" enctype="multipart/form-data"><?=csrfField()?><input type="hidden" name="_act" value="add_item"><input type="hidden" name="section_id" value="<?=$sec['id']?>">
<div class="form-row">
<div class="fg"><label class="flabel">ITEM NAME *</label><input type="text" name="item_name" class="finput" required placeholder="e.g. Spaghetti Carbonara"></div>
<div class="fg"><label class="flabel">PRICE ($)</label><input type="number" name="item_price" class="finput" value="0" step="0.01" min="0"></div>
</div>
<div class="fg"><label class="flabel">DESCRIPTION</label><input type="text" name="item_desc" class="finput" placeholder="Brief description…"></div>
<?php if($menuImagesEnabled): ?>
<div class="fg"><label class="flabel">ITEM IMAGE</label><input type="file" name="item_image" class="finput" accept="image/*"><div class="fhint">Optional. One image allowed, max <?=e(setting('menu_upload_max_mb','5'))?> MB.</div></div>
<?php endif; ?>
<div class="fg"><label class="flabel">AVAILABLE</label><select name="is_available" class="fselect"><option value="1">Yes</option><option value="0">No</option></select></div>
<button type="submit" class="btn btn-primary btn-sm">Add Item</button>
</form>
+59 -1
View File
@@ -1,6 +1,7 @@
<?php
require_once dirname(__DIR__).'/includes/boot.php';
requireAdmin();
$adminTitle = 'Site Settings';
include __DIR__.'/admin_header.php';
if (isPost()) {
csrfCheck();
@@ -13,6 +14,15 @@ if (isPost()) {
setSetting('registration_enabled',p('registration_enabled','0'));
setSetting('events_require_approval',p('events_require_approval','0'));
setSetting('home_random_interval',p('home_random_interval','2hours') === 'day' ? 'day' : '2hours');
if (array_key_exists('event_upload_max_mb', $_POST)) {
setSetting('event_upload_max_mb', (string)max(1, (float)p('event_upload_max_mb','5')));
setSetting('menu_item_image_limit', (string)max(0, min(1, (int)p('menu_item_image_limit','1'))));
setSetting('menu_upload_max_mb', (string)max(1, (float)p('menu_upload_max_mb','5')));
setSetting('avatar_upload_max_mb', (string)max(1, (float)p('avatar_upload_max_mb','3')));
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'))));
}
if (array_key_exists('mailgun_domain', $_POST)) {
setSetting('site_base_url', rtrim(ps('site_base_url'), '/'));
setSetting('mailgun_region', p('mailgun_region','us') === 'eu' ? 'eu' : 'us');
@@ -55,7 +65,15 @@ $mailgunFromName = setting('mailgun_from_name','Discover Keyser WV');
$mailgunFromEmail = setting('mailgun_from_email','');
$mailgunSendMode = setting('mailgun_send_mode','html');
$mailgunHasKey = setting('mailgun_api_key','') !== '';
$eventUploadMax = setting('event_upload_max_mb','5');
$menuImageLimit = setting('menu_item_image_limit','1');
$menuUploadMax = setting('menu_upload_max_mb','5');
$avatarUploadMax = setting('avatar_upload_max_mb','3');
$resizeThreshold = setting('forum_image_resize_threshold_mb','1');
$resizeMaxWidth = setting('forum_image_max_width','1600');
$resizeQuality = setting('forum_image_quality','82');
$me = qone("SELECT * FROM users WHERE id=?",[uid()]);
include __DIR__.'/admin_header.php';
?>
<?php adminTitle('Site Settings') ?>
@@ -119,6 +137,39 @@ $me = qone("SELECT * FROM users WHERE id=?",[uid()]);
</form>
</div>
<div class="ibox" style="border-color:var(--gold-d)">
<div class="ibox-title">🖼️ UPLOAD &amp; IMAGE SETTINGS</div>
<form method="POST"><?=csrfField()?><input type="hidden" name="_act" value="settings">
<input type="hidden" name="site_name" value="<?=e($siteName)?>">
<input type="hidden" name="site_tagline" value="<?=e($siteTagline)?>">
<input type="hidden" name="contact_email" value="<?=e($contactEmail)?>">
<input type="hidden" name="registration_enabled" value="<?=e($regEnabled)?>">
<input type="hidden" name="events_require_approval" value="<?=e($evtApproval)?>">
<input type="hidden" name="home_random_interval" value="<?=e($homeRandomInterval)?>">
<div class="form-row">
<div class="fg"><label class="flabel">EVENT IMAGE MAX MB</label><input type="number" name="event_upload_max_mb" class="finput" min="1" step="0.5" value="<?=e($eventUploadMax)?>"></div>
<div class="fg"><label class="flabel">AVATAR MAX MB</label><input type="number" name="avatar_upload_max_mb" class="finput" min="1" step="0.5" value="<?=e($avatarUploadMax)?>"></div>
</div>
<div class="form-row">
<div class="fg">
<label class="flabel">MENU ITEM IMAGE LIMIT</label>
<select name="menu_item_image_limit" class="fselect">
<option value="1"<?=$menuImageLimit==='1'?' selected':''?>>One image per item</option>
<option value="0"<?=$menuImageLimit==='0'?' selected':''?>>No menu item images</option>
</select>
</div>
<div class="fg"><label class="flabel">MENU IMAGE MAX MB</label><input type="number" name="menu_upload_max_mb" class="finput" min="1" step="0.5" value="<?=e($menuUploadMax)?>"></div>
</div>
<hr class="divider" style="margin:1rem 0">
<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 class="fhint">Used when large uploaded images are re-saved.</div></div>
<button type="submit" class="btn btn-primary">Save Upload Settings</button>
</form>
</div>
<div class="ibox" style="border-color:var(--gold-d)">
<div class="ibox-title">✉️ EMAIL VERIFICATION &amp; MAILGUN</div>
<form method="POST"><?=csrfField()?><input type="hidden" name="_act" value="settings">
@@ -128,6 +179,13 @@ $me = qone("SELECT * FROM users WHERE id=?",[uid()]);
<input type="hidden" name="registration_enabled" value="<?=e($regEnabled)?>">
<input type="hidden" name="events_require_approval" value="<?=e($evtApproval)?>">
<input type="hidden" name="home_random_interval" value="<?=e($homeRandomInterval)?>">
<input type="hidden" name="event_upload_max_mb" value="<?=e($eventUploadMax)?>">
<input type="hidden" name="menu_item_image_limit" value="<?=e($menuImageLimit)?>">
<input type="hidden" name="menu_upload_max_mb" value="<?=e($menuUploadMax)?>">
<input type="hidden" name="avatar_upload_max_mb" value="<?=e($avatarUploadMax)?>">
<input type="hidden" name="forum_image_resize_threshold_mb" value="<?=e($resizeThreshold)?>">
<input type="hidden" name="forum_image_max_width" value="<?=e($resizeMaxWidth)?>">
<input type="hidden" name="forum_image_quality" value="<?=e($resizeQuality)?>">
<div class="fg">
<label class="flabel">SITE BASE URL</label>