- Rebuilt the CLI importer to use the official Facebook Graph API instead of HTML scraping.
Main changes: Replaced [scripts/facebook-events-import.php](/Users/tyemeclifford/Documents/GH/MyKeyser/scripts/facebook-events-import.php) with a Graph API client. It now supports --page-id, --pages-file, --event-id, --events-file, and optional area discovery via --discover-pages --center=LAT,LNG --query=.... Uses FACEBOOK_GRAPH_ACCESS_TOKEN or --access-token. Pulls Graph fields for title, description, start/end time, place/location, ticket URI, and cover.source for the main photo. Keeps the existing staged review/import workflow in /admin/event_imports.php. - Implemented event detail pages and clickable event cards. What changed: Added [event.php](/Users/tyemeclifford/Documents/GH/MyKeyser/event.php) for individual event pages. Made event cards clickable from [events.php](/Users/tyemeclifford/Documents/GH/MyKeyser/events.php), [index.php](/Users/tyemeclifford/Documents/GH/MyKeyser/index.php), and legacy [2index.php](/Users/tyemeclifford/Documents/GH/MyKeyser/2index.php). Added large event photo display plus lightbox enlargement in [assets/css/style.css](/Users/tyemeclifford/Documents/GH/MyKeyser/assets/css/style.css) and [assets/js/app.js](/Users/tyemeclifford/Documents/GH/MyKeyser/assets/js/app.js). Added eventUrl() and externalHref() helpers in [includes/functions.php](/Users/tyemeclifford/Documents/GH/MyKeyser/includes/functions.php). Added copy-event-link support on event detail pages. Updated README to list the new route.
This commit is contained in:
@@ -7,6 +7,7 @@ include dirname(__DIR__).'/includes/header.php';
|
||||
// Pending owner edit count for badge
|
||||
$_pendingEdits = (int)qval("SELECT COUNT(*) FROM business_edit_requests WHERE status='pending'");
|
||||
$_pendingClaims = (int)qval("SELECT COUNT(*) FROM business_claim_requests WHERE status='pending'");
|
||||
$_pendingEventImports = (int)qval("SELECT COUNT(*) FROM event_imports WHERE status='staged'");
|
||||
?>
|
||||
<div class="admin-wrap">
|
||||
<aside class="admin-side">
|
||||
@@ -27,6 +28,12 @@ $_pendingClaims = (int)qval("SELECT COUNT(*) FROM business_claim_requests WHERE
|
||||
<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/event_imports.php" style="display:flex;align-items:center;justify-content:space-between">
|
||||
<span>Event Imports</span>
|
||||
<?php if ($_pendingEventImports): ?>
|
||||
<span style="background:var(--gold);color:var(--bg0);font-family:'Oswald',sans-serif;font-size:.65rem;padding:.1rem .42rem;border-radius:10px;font-weight:600"><?=$_pendingEventImports?></span>
|
||||
<?php endif; ?>
|
||||
</a>
|
||||
<a class="asl" href="/admin/attractions.php">🗺️ Attractions</a>
|
||||
<a class="asl" href="/admin/reports.php">🚩 Reports</a>
|
||||
<div class="admin-side-title" style="margin-top:1rem">USERS</div>
|
||||
|
||||
@@ -0,0 +1,246 @@
|
||||
<?php
|
||||
require_once dirname(__DIR__).'/includes/boot.php';
|
||||
require_once dirname(__DIR__).'/includes/facebook_event_importer.php';
|
||||
requireAdmin();
|
||||
$adminTitle = 'Event Imports';
|
||||
|
||||
function eventImportRowsFromPost(): array {
|
||||
$rows = p('rows', []);
|
||||
return is_array($rows) ? $rows : [];
|
||||
}
|
||||
|
||||
function eventImportSelectedIds(): array {
|
||||
$ids = p('ids', []);
|
||||
if (!is_array($ids)) $ids = [];
|
||||
return array_values(array_unique(array_filter(array_map('intval', $ids))));
|
||||
}
|
||||
|
||||
function eventImportSavePostedRows(array $onlyIds = []): void {
|
||||
$wanted = array_fill_keys($onlyIds, true);
|
||||
foreach (eventImportRowsFromPost() as $id => $row) {
|
||||
$id = (int)$id;
|
||||
if ($id <= 0) continue;
|
||||
if ($onlyIds && !isset($wanted[$id])) continue;
|
||||
if (!is_array($row)) continue;
|
||||
facebookEventSaveCandidateFields($id, $row);
|
||||
}
|
||||
}
|
||||
|
||||
$filter = gs('filter') ?: p('filter', 'staged');
|
||||
if (!in_array($filter, ['staged', 'imported', 'skipped', 'all'], true)) $filter = 'staged';
|
||||
|
||||
if (isPost()) {
|
||||
csrfCheck();
|
||||
$eventStatus = in_array(p('event_status', 'pending'), ['pending', 'approved'], true) ? p('event_status', 'pending') : 'pending';
|
||||
$allowDuplicate = p('allow_duplicates', '0') === '1';
|
||||
|
||||
if (($saveId = (int)p('save_id', 0)) > 0) {
|
||||
eventImportSavePostedRows([$saveId]);
|
||||
flash('Import candidate saved.', 'success');
|
||||
go('/admin/event_imports.php?filter='.$filter);
|
||||
}
|
||||
|
||||
if (($importId = (int)p('import_id', 0)) > 0) {
|
||||
eventImportSavePostedRows([$importId]);
|
||||
$result = facebookEventImportCandidate($importId, uid(), $eventStatus, $allowDuplicate);
|
||||
flash($result['ok'] ? 'Event imported.'.(!empty($result['warning']) ? ' '.$result['warning'] : '') : $result['error'], $result['ok'] ? 'success' : 'error');
|
||||
go('/admin/event_imports.php?filter='.$filter);
|
||||
}
|
||||
|
||||
if (($skipId = (int)p('skip_id', 0)) > 0) {
|
||||
qrun("UPDATE event_imports SET status='skipped',updated_at=datetime('now') WHERE id=? AND status!='imported'", [$skipId]);
|
||||
flash('Import candidate skipped.', 'success');
|
||||
go('/admin/event_imports.php?filter='.$filter);
|
||||
}
|
||||
|
||||
if (($restoreId = (int)p('restore_id', 0)) > 0) {
|
||||
qrun("UPDATE event_imports SET status='staged',updated_at=datetime('now') WHERE id=? AND status='skipped'", [$restoreId]);
|
||||
flash('Import candidate restored.', 'success');
|
||||
go('/admin/event_imports.php?filter=staged');
|
||||
}
|
||||
|
||||
if (($deleteId = (int)p('delete_id', 0)) > 0) {
|
||||
qrun("DELETE FROM event_imports WHERE id=? AND status!='imported'", [$deleteId]);
|
||||
flash('Import candidate deleted.', 'success');
|
||||
go('/admin/event_imports.php?filter='.$filter);
|
||||
}
|
||||
|
||||
$act = p('_act', '');
|
||||
if ($act === 'save_all') {
|
||||
eventImportSavePostedRows();
|
||||
flash('Import candidates saved.', 'success');
|
||||
go('/admin/event_imports.php?filter='.$filter);
|
||||
}
|
||||
|
||||
if ($act === 'bulk_import') {
|
||||
$ids = eventImportSelectedIds();
|
||||
eventImportSavePostedRows($ids);
|
||||
$ok = 0; $errors = [];
|
||||
foreach ($ids as $id) {
|
||||
$result = facebookEventImportCandidate($id, uid(), $eventStatus, $allowDuplicate);
|
||||
if ($result['ok']) $ok++;
|
||||
else $errors[] = '#'.$id.': '.$result['error'];
|
||||
}
|
||||
if ($ok) flash($ok.' event'.($ok === 1 ? '' : 's').' imported.', 'success');
|
||||
if ($errors) flash(implode(' ', $errors), 'error');
|
||||
if (!$ids) flash('Select at least one staged import first.', 'warning');
|
||||
go('/admin/event_imports.php?filter='.$filter);
|
||||
}
|
||||
|
||||
if ($act === 'bulk_skip') {
|
||||
$ids = eventImportSelectedIds();
|
||||
foreach ($ids as $id) qrun("UPDATE event_imports SET status='skipped',updated_at=datetime('now') WHERE id=? AND status!='imported'", [$id]);
|
||||
flash($ids ? count($ids).' import candidate'.(count($ids) === 1 ? '' : 's').' skipped.' : 'Select at least one staged import first.', $ids ? 'success' : 'warning');
|
||||
go('/admin/event_imports.php?filter='.$filter);
|
||||
}
|
||||
|
||||
if ($act === 'bulk_delete') {
|
||||
$ids = eventImportSelectedIds();
|
||||
foreach ($ids as $id) qrun("DELETE FROM event_imports WHERE id=? AND status!='imported'", [$id]);
|
||||
flash($ids ? count($ids).' import candidate'.(count($ids) === 1 ? '' : 's').' deleted.' : 'Select at least one staged import first.', $ids ? 'success' : 'warning');
|
||||
go('/admin/event_imports.php?filter='.$filter);
|
||||
}
|
||||
}
|
||||
|
||||
$where = match ($filter) {
|
||||
'staged' => "WHERE status='staged'",
|
||||
'imported' => "WHERE status='imported'",
|
||||
'skipped' => "WHERE status='skipped'",
|
||||
default => '',
|
||||
};
|
||||
$imports = qall("SELECT * FROM event_imports $where ORDER BY CASE status WHEN 'staged' THEN 0 WHEN 'skipped' THEN 1 ELSE 2 END, created_at DESC LIMIT 200");
|
||||
$counts = [
|
||||
'staged' => (int)qval("SELECT COUNT(*) FROM event_imports WHERE status='staged'"),
|
||||
'imported' => (int)qval("SELECT COUNT(*) FROM event_imports WHERE status='imported'"),
|
||||
'skipped' => (int)qval("SELECT COUNT(*) FROM event_imports WHERE status='skipped'"),
|
||||
'all' => (int)qval("SELECT COUNT(*) FROM event_imports"),
|
||||
];
|
||||
include __DIR__.'/admin_header.php';
|
||||
?>
|
||||
<?php adminTitle('Facebook Event Imports', 'Review staged Facebook event candidates, correct any fields, then import selected rows into the normal My Keyser events workflow.') ?>
|
||||
|
||||
<div class="ibox" style="border-color:var(--gold-d)">
|
||||
<div class="ibox-title">CLI STAGING</div>
|
||||
<p style="color:var(--text-m);font-size:.9rem;line-height:1.65;margin-bottom:.8rem">
|
||||
Stage Facebook event candidates through the official Graph API, then return here to verify titles, dates, locations, duplicates, and photos before publishing or sending them to pending review.
|
||||
</p>
|
||||
<code style="display:block;white-space:normal;background:var(--bg2);border:1px solid var(--bdr);border-radius:var(--r);padding:.8rem;color:var(--text-m)">FACEBOOK_GRAPH_ACCESS_TOKEN=... php scripts/facebook-events-import.php --area="Keyser, WV" --page-id=123456789 --limit=20</code>
|
||||
</div>
|
||||
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:.5rem;margin-bottom:1rem">
|
||||
<div class="flex-row" style="gap:.4rem">
|
||||
<?php foreach(['staged'=>'Staged','imported'=>'Imported','skipped'=>'Skipped','all'=>'All'] as $key => $label): ?>
|
||||
<a href="/admin/event_imports.php?filter=<?=$key?>" class="btn btn-xs<?=$filter===$key?' btn-primary':' btn-outline'?>"><?=$label?> (<?=$counts[$key]?>)</a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<a href="/admin/events.php" class="btn btn-xs btn-outline">Manage Events</a>
|
||||
</div>
|
||||
|
||||
<?php if (!$imports): ?>
|
||||
<div class="empty"><div class="empty-ico">+</div><h3>No event imports here</h3><p style="margin-top:.5rem">Run the Graph API CLI importer to stage Facebook event candidates for review.</p></div>
|
||||
<?php else: ?>
|
||||
<form method="POST">
|
||||
<?=csrfField()?>
|
||||
<input type="hidden" name="filter" value="<?=e($filter)?>">
|
||||
<div class="ibox" style="position:sticky;top:76px;z-index:2">
|
||||
<div class="flex-row" style="justify-content:space-between;align-items:flex-end">
|
||||
<div class="flex-row">
|
||||
<div class="fg" style="margin-bottom:0;min-width:180px">
|
||||
<label class="flabel">IMPORT AS</label>
|
||||
<select name="event_status" class="fselect">
|
||||
<option value="pending">Pending review</option>
|
||||
<option value="approved">Approved public event</option>
|
||||
</select>
|
||||
</div>
|
||||
<label style="display:flex;align-items:center;gap:.45rem;color:var(--text-m);font-size:.86rem;margin-top:1.25rem">
|
||||
<input type="checkbox" name="allow_duplicates" value="1"> Allow duplicate title/date
|
||||
</label>
|
||||
</div>
|
||||
<div class="flex-row" style="gap:.4rem">
|
||||
<button type="submit" name="_act" value="save_all" class="btn btn-xs btn-outline">Save Edits</button>
|
||||
<button type="submit" name="_act" value="bulk_import" class="btn btn-xs btn-success">Import Selected</button>
|
||||
<button type="submit" name="_act" value="bulk_skip" class="btn btn-xs btn-secondary">Skip Selected</button>
|
||||
<button type="submit" name="_act" value="bulk_delete" class="btn btn-xs btn-danger" data-confirm="Delete selected import candidates?">Delete Selected</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display:grid;gap:1rem">
|
||||
<?php foreach ($imports as $imp): ?>
|
||||
<?php
|
||||
$id = (int)$imp['id'];
|
||||
$issues = facebookEventCandidateIssues($imp);
|
||||
$duplicateId = facebookEventDuplicateId($imp);
|
||||
$statusClass = ['staged' => 'badge-gold', 'imported' => 'badge-green', 'skipped' => 'badge-gray'][$imp['status']] ?? 'badge-gray';
|
||||
$preview = $imp['image_path'] ?: $imp['image_url'];
|
||||
$canEdit = $imp['status'] !== 'imported';
|
||||
?>
|
||||
<div class="ibox" style="margin-bottom:0">
|
||||
<div class="event-import-grid">
|
||||
<div>
|
||||
<?php if ($imp['status'] === 'staged'): ?>
|
||||
<label style="display:flex;align-items:center;gap:.45rem;color:var(--text-m);font-size:.86rem;margin-bottom:.55rem">
|
||||
<input type="checkbox" name="ids[]" value="<?=$id?>"> Select
|
||||
</label>
|
||||
<?php endif; ?>
|
||||
<div class="event-import-preview">
|
||||
<?php if ($preview): ?>
|
||||
<img src="<?=e($preview)?>" alt="<?=e($imp['title'] ?: 'Event image')?>">
|
||||
<?php else: ?>
|
||||
No image
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="flex-row" style="gap:.35rem;margin-top:.65rem">
|
||||
<span class="badge <?=$statusClass?>"><?=strtoupper(e($imp['status']))?></span>
|
||||
<?php if (!$issues && !$duplicateId && $imp['status'] === 'staged'): ?><span class="badge badge-green">READY</span><?php endif; ?>
|
||||
<?php if ($issues): ?><span class="badge badge-red">NEEDS FIX</span><?php endif; ?>
|
||||
<?php if ($duplicateId && $imp['status'] === 'staged'): ?><span class="badge badge-gold">DUPLICATE?</span><?php endif; ?>
|
||||
</div>
|
||||
<?php if ($imp['source_url']): ?><p style="font-size:.78rem;margin-top:.6rem"><a href="<?=e($imp['source_url'])?>" target="_blank">View source</a></p><?php endif; ?>
|
||||
<?php if ($imp['imported_event_id']): ?><p style="font-size:.78rem;margin-top:.25rem"><a href="/admin/events.php?edit=<?=(int)$imp['imported_event_id']?>">Edit imported event #<?=(int)$imp['imported_event_id']?></a></p><?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div style="min-width:0">
|
||||
<div class="form-row">
|
||||
<div class="fg"><label class="flabel">TITLE *</label><input type="text" name="rows[<?=$id?>][title]" class="finput" value="<?=e($imp['title'])?>" <?=$canEdit?'':'disabled'?>></div>
|
||||
<div class="fg"><label class="flabel">SOURCE AREA</label><input type="text" class="finput" value="<?=e($imp['source_area'])?>" disabled></div>
|
||||
</div>
|
||||
<div class="fg"><label class="flabel">DESCRIPTION</label><textarea name="rows[<?=$id?>][description]" class="ftextarea" style="min-height:76px" <?=$canEdit?'':'disabled'?>><?=e($imp['description'])?></textarea></div>
|
||||
<div class="form-row">
|
||||
<div class="fg"><label class="flabel">VENUE</label><input type="text" name="rows[<?=$id?>][venue]" class="finput" value="<?=e($imp['venue'])?>" <?=$canEdit?'':'disabled'?>></div>
|
||||
<div class="fg"><label class="flabel">ADDRESS</label><input type="text" name="rows[<?=$id?>][address]" class="finput" value="<?=e($imp['address'])?>" <?=$canEdit?'':'disabled'?>></div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="fg"><label class="flabel">START DATE *</label><input type="date" name="rows[<?=$id?>][event_date]" class="finput" value="<?=e($imp['event_date'])?>" <?=$canEdit?'':'disabled'?>></div>
|
||||
<div class="fg"><label class="flabel">START TIME</label><input type="text" name="rows[<?=$id?>][event_time]" class="finput" value="<?=e($imp['event_time'])?>" placeholder="e.g. 7:00 PM" <?=$canEdit?'':'disabled'?>></div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="fg"><label class="flabel">END DATE</label><input type="date" name="rows[<?=$id?>][end_date]" class="finput" value="<?=e($imp['end_date'])?>" <?=$canEdit?'':'disabled'?>></div>
|
||||
<div class="fg"><label class="flabel">END TIME</label><input type="text" name="rows[<?=$id?>][end_time]" class="finput" value="<?=e($imp['end_time'])?>" <?=$canEdit?'':'disabled'?>></div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="fg"><label class="flabel">COST</label><input type="text" name="rows[<?=$id?>][cost]" class="finput" value="<?=e($imp['cost'] ?: 'Free')?>" <?=$canEdit?'':'disabled'?>></div>
|
||||
<div class="fg"><label class="flabel">WEBSITE</label><input type="text" name="rows[<?=$id?>][website]" class="finput" value="<?=e($imp['website'])?>" <?=$canEdit?'':'disabled'?>></div>
|
||||
</div>
|
||||
<input type="hidden" name="rows[<?=$id?>][image_path]" value="<?=e($imp['image_path'])?>">
|
||||
<?php if ($issues || $duplicateId || $imp['status_note']): ?>
|
||||
<div style="background:rgba(201,168,76,.08);border:1px solid rgba(201,168,76,.2);border-radius:var(--r);padding:.75rem;margin:.4rem 0 1rem;color:var(--text-m);font-size:.84rem;line-height:1.55">
|
||||
<?php if ($issues): ?><div><strong>Needs review:</strong> <?=e(implode(', ', $issues))?></div><?php endif; ?>
|
||||
<?php if ($duplicateId && $imp['status'] === 'staged'): ?><div><strong>Possible duplicate:</strong> existing event #<?=$duplicateId?> has the same title and start date.</div><?php endif; ?>
|
||||
<?php if ($imp['status_note']): ?><div><strong>Note:</strong> <?=e($imp['status_note'])?></div><?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div class="flex-row" style="gap:.4rem">
|
||||
<?php if ($canEdit): ?><button type="submit" name="save_id" value="<?=$id?>" class="btn btn-xs btn-outline">Save</button><?php endif; ?>
|
||||
<?php if ($imp['status'] === 'staged'): ?><button type="submit" name="import_id" value="<?=$id?>" class="btn btn-xs btn-success">Import</button><button type="submit" name="skip_id" value="<?=$id?>" class="btn btn-xs btn-secondary">Skip</button><?php endif; ?>
|
||||
<?php if ($imp['status'] === 'skipped'): ?><button type="submit" name="restore_id" value="<?=$id?>" class="btn btn-xs btn-primary">Restore</button><?php endif; ?>
|
||||
<?php if ($imp['status'] !== 'imported'): ?><button type="submit" name="delete_id" value="<?=$id?>" class="btn btn-xs btn-danger" data-confirm="Delete this import candidate?">Delete</button><?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
<?php include __DIR__.'/admin_footer.php'; ?>
|
||||
Reference in New Issue
Block a user