Files
mediaplayer/admin/index.php
T
Ty Clifford 6486483d88 -
2026-06-16 16:31:08 -04:00

296 lines
14 KiB
PHP

<?php
require_once __DIR__ . '/auth.php';
require_once __DIR__ . '/../includes/layout.php';
admin_require_auth();
$db = get_db();
// Handle quick-actions
$msg = ''; $msg_type = 'success';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$action = $_POST['action'] ?? '';
if ($action === 'delete' && !empty($_POST['id'])) {
$id = (int)$_POST['id'];
$v = get_video_by_id($id);
if ($v) {
// Remove source files + thumbnail
foreach ($v['sources'] as $s) {
$f = MEDIA_DIR . $s['file_path'];
if (file_exists($f)) unlink($f);
}
if ($v['thumbnail']) {
$t = MEDIA_DIR . $v['thumbnail'];
if (file_exists($t)) unlink($t);
}
$db->prepare("DELETE FROM videos WHERE id=?")->execute([$id]);
$msg = 'Video deleted.';
}
}
if ($action === 'toggle_published' && !empty($_POST['id'])) {
$id = (int)$_POST['id'];
$row = $db->prepare("SELECT published FROM videos WHERE id=?");
$row->execute([$id]);
$cur = $row->fetchColumn();
$db->prepare("UPDATE videos SET published=? WHERE id=?")->execute([$cur ? 0 : 1, $id]);
$msg = 'Video ' . ($cur ? 'unpublished' : 'published') . '.';
}
}
// Search + pagination
$search = trim($_GET['q'] ?? '');
$page = max(1, (int)($_GET['page'] ?? 1));
$per_page = (int)setting('catalogue_per_page', '10');
$where_sql = $search ? "WHERE v.title LIKE :q OR v.description LIKE :q" : '';
$bind = $search ? [':q' => '%' . $search . '%'] : [];
$total_stmt = $db->prepare("SELECT COUNT(*) FROM videos v $where_sql");
$total_stmt->execute($bind);
$total = (int)$total_stmt->fetchColumn();
$total_pages = max(1, (int)ceil($total / $per_page));
$page = min($page, $total_pages);
$offset = ($page - 1) * $per_page;
$list_stmt = $db->prepare("
SELECT v.id, v.title, v.slug, v.thumbnail, v.duration, v.published, v.sort_order,
v.created_at,
COUNT(vs.id) AS source_count
FROM videos v
LEFT JOIN video_sources vs ON vs.video_id = v.id
$where_sql
GROUP BY v.id
ORDER BY v.sort_order ASC, v.id DESC
LIMIT :limit OFFSET :offset
");
foreach ($bind as $k => $val) $list_stmt->bindValue($k, $val);
$list_stmt->bindValue(':limit', $per_page, PDO::PARAM_INT);
$list_stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
$list_stmt->execute();
$videos = $list_stmt->fetchAll();
$qs = fn($p) => '?' . http_build_query(array_filter(['q' => $search, 'page' => $p]));
render_head('Admin — TyClifford.com', '
.admin-layout { display:grid; grid-template-columns:1fr; gap:1.5rem; }
@media(min-width:900px) { .admin-layout { grid-template-columns:220px 1fr; } }
.admin-nav {
background:var(--surface); border:1px solid var(--border);
border-radius:calc(var(--r)+2px); padding:1.2rem;
display:flex; flex-direction:column; gap:.4rem;
align-self:start; position:sticky; top:1rem;
}
.admin-nav-label { font-family:"Share Tech Mono",monospace; font-size:.58rem; letter-spacing:.18em; text-transform:uppercase; color:var(--text-3); margin-bottom:.3rem; padding-left:.5rem; }
.admin-nav a {
display:flex; align-items:center; gap:.6rem;
padding:.55rem .75rem; border-radius:var(--r);
font-size:.82rem; color:var(--text-2); text-decoration:none;
transition:background .15s, color .15s;
}
.admin-nav a:hover { background:rgba(255,255,255,.05); color:var(--text); }
.admin-nav a.active { background:rgba(176,96,255,.12); color:var(--purple); }
.admin-nav a svg { flex-shrink:0; }
.nav-divider { border:none; border-top:1px solid var(--border); margin:.4rem 0; }
.admin-main { display:flex; flex-direction:column; gap:1rem; }
.admin-toolbar { display:flex; align-items:center; gap:.75rem; flex-wrap:wrap; justify-content:space-between; }
.search-form { display:flex; gap:.5rem; flex:1; min-width:0; max-width:360px; }
.search-form input { flex:1; }
.video-table { width:100%; border-collapse:collapse; }
.video-table th, .video-table td {
padding:.65rem .85rem; text-align:left;
border-bottom:1px solid var(--border);
font-size:.82rem;
}
.video-table th {
font-family:"Share Tech Mono",monospace; font-size:.62rem; letter-spacing:.12em;
text-transform:uppercase; color:var(--text-3); background:var(--surface-2);
}
.video-table tr:hover td { background:rgba(255,255,255,.02); }
.video-table .thumb-cell { width:80px; }
.video-table .thumb-cell img { width:72px; height:40px; object-fit:cover; border-radius:4px; display:block; background:var(--surface-2); }
.video-table .thumb-cell .no-thumb-sm { width:72px; height:40px; border-radius:4px; background:var(--surface-2); display:flex; align-items:center; justify-content:center; font-family:"Share Tech Mono",monospace; font-size:.45rem; color:var(--text-3); }
.video-table .title-cell { max-width:250px; }
.video-table .title-cell a { color:var(--text); text-decoration:none; font-weight:600; }
.video-table .title-cell a:hover { color:var(--purple); }
.video-table .title-cell .slug { font-family:"Share Tech Mono",monospace; font-size:.65rem; color:var(--text-3); display:block; margin-top:.15rem; }
.video-table .actions-cell { white-space:nowrap; }
.badge { display:inline-flex; align-items:center; padding:.18em .55em; border-radius:99px; font-family:"Share Tech Mono",monospace; font-size:.6rem; letter-spacing:.08em; }
.badge-green { background:rgba(0,232,122,.12); color:var(--green); border:1px solid rgba(0,232,122,.25); }
.badge-gray { background:rgba(255,255,255,.05); color:var(--text-3); border:1px solid var(--border-2); }
.table-wrap { overflow-x:auto; background:var(--surface); border:1px solid var(--border); border-radius:calc(var(--r)+2px); }
.admin-footer-bar { display:flex; align-items:center; justify-content:space-between; gap:1rem; flex-wrap:wrap; }
.rec-count { font-family:"Share Tech Mono",monospace; font-size:.63rem; color:var(--text-3); letter-spacing:.06em; }
');
?>
<main class="page" role="main">
<header class="topbar">
<div class="topbar-brand">
<div>
<div class="brand-name">Ty Clifford</div>
<div class="brand-sub">admin / dashboard</div>
</div>
</div>
<nav class="topbar-social">
<a class="social-pill" href="../index.php" target="_blank">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 13v6a2 2 0 01-2 2H5a2 2 0 01-2-2V8a2 2 0 012-2h6"/><polyline points="15,3 21,3 21,9"/><line x1="10" y1="14" x2="21" y2="3"/></svg>
View Site
</a>
<a class="social-pill" href="logout.php">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 21H5a2 2 0 01-2-2V5a2 2 0 012-2h4"/><polyline points="16,17 21,12 16,7"/><line x1="21" y1="12" x2="9" y2="12"/></svg>
Logout
</a>
</nav>
</header>
<?php if ($msg): ?>
<div class="notice notice-<?= $msg_type ?>"><?= h($msg) ?></div>
<?php endif; ?>
<div class="admin-layout">
<!-- Sidebar nav -->
<aside class="admin-nav">
<div class="admin-nav-label">Navigation</div>
<a href="index.php" class="active">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="3" width="20" height="14" rx="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg>
Videos
</a>
<a href="add.php">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="16"/><line x1="8" y1="12" x2="16" y2="12"/></svg>
Add Video
</a>
<hr class="nav-divider">
<a href="settings.php">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"/><path d="M19.07 4.93l-1.41 1.41M4.93 4.93l1.41 1.41M19.07 19.07l-1.41-1.41M4.93 19.07l1.41-1.41M12 2v2M12 20v2M2 12h2M20 12h2"/></svg>
Settings
</a>
</aside>
<!-- Main content -->
<div class="admin-main">
<div class="admin-toolbar">
<form class="search-form" method="get" action="index.php">
<input class="form-input" type="search" name="q"
placeholder="Search videos…" value="<?= h($search) ?>">
<button class="btn btn-secondary btn-sm" type="submit">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
Search
</button>
<?php if ($search): ?>
<a href="index.php" class="btn btn-secondary btn-sm">✕ Clear</a>
<?php endif; ?>
</form>
<a href="add.php" class="btn btn-primary btn-sm">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
Add Video
</a>
</div>
<div class="table-wrap">
<table class="video-table">
<thead>
<tr>
<th class="thumb-cell">Thumb</th>
<th class="title-cell">Title / Slug</th>
<th>Duration</th>
<th>Sources</th>
<th>Status</th>
<th>Added</th>
<th class="actions-cell">Actions</th>
</tr>
</thead>
<tbody>
<?php if (empty($videos)): ?>
<tr><td colspan="7" style="text-align:center;padding:3rem;font-family:'Share Tech Mono',monospace;font-size:.7rem;color:var(--text-3);">
<?= $search ? 'No videos match "' . h($search) . '"' : 'No videos yet. <a href="add.php" style="color:var(--purple)">Add one!</a>' ?>
</td></tr>
<?php else: ?>
<?php foreach ($videos as $v): ?>
<tr>
<td class="thumb-cell">
<?php if ($v['thumbnail']): ?>
<img src="<?= h('../' . MEDIA_URL . $v['thumbnail']) ?>" alt="">
<?php else: ?>
<div class="no-thumb-sm">NO IMG</div>
<?php endif; ?>
</td>
<td class="title-cell">
<a href="edit.php?id=<?= $v['id'] ?>"><?= h($v['title']) ?></a>
<span class="slug">/<?= h($v['slug']) ?></span>
</td>
<td style="font-family:'Share Tech Mono',monospace;font-size:.7rem;color:var(--text-3);">
<?= $v['duration'] ? format_duration((int)$v['duration']) : '—' ?>
</td>
<td style="font-family:'Share Tech Mono',monospace;font-size:.7rem;color:var(--text-2);">
<?= $v['source_count'] ?> file<?= $v['source_count'] != 1 ? 's' : '' ?>
</td>
<td>
<span class="badge <?= $v['published'] ? 'badge-green' : 'badge-gray' ?>">
<?= $v['published'] ? 'Published' : 'Draft' ?>
</span>
</td>
<td style="font-family:'Share Tech Mono',monospace;font-size:.65rem;color:var(--text-3);">
<?= substr($v['created_at'], 0, 10) ?>
</td>
<td class="actions-cell">
<div style="display:flex;gap:.4rem;align-items:center;">
<a href="edit.php?id=<?= $v['id'] ?>" class="btn btn-secondary btn-sm" title="Edit">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>
Edit
</a>
<form method="post" style="display:inline" onsubmit="return confirm('Delete this video and all its files?')">
<input type="hidden" name="action" value="delete">
<input type="hidden" name="id" value="<?= $v['id'] ?>">
<button class="btn btn-danger btn-sm" type="submit" title="Delete">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3,6 5,6 21,6"/><path d="M19 6l-1 14H6L5 6"/><path d="M10 11v6M14 11v6"/><path d="M9 6V4h6v2"/></svg>
</button>
</form>
<form method="post" style="display:inline">
<input type="hidden" name="action" value="toggle_published">
<input type="hidden" name="id" value="<?= $v['id'] ?>">
<button class="btn btn-secondary btn-sm" type="submit" title="<?= $v['published'] ? 'Unpublish' : 'Publish' ?>">
<?php if ($v['published']): ?>
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17.94 17.94A10.07 10.07 0 0112 20c-7 0-11-8-11-8a18.45 18.45 0 015.06-5.94"/><path d="M9.9 4.24A9.12 9.12 0 0112 4c7 0 11 8 11 8a18.5 18.5 0 01-2.16 3.19"/><line x1="1" y1="1" x2="23" y2="23"/></svg>
<?php else: ?>
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>
<?php endif; ?>
</button>
</form>
</div>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
<?php if ($total_pages > 1 || $total > 0): ?>
<div class="admin-footer-bar">
<span class="rec-count">
<?= $total ?> video<?= $total != 1 ? 's' : '' ?>
<?= $search ? '· filtered' : '' ?>
· page <?= $page ?> of <?= $total_pages ?>
</span>
<?php
render_pagination($page, $total_pages, 'index.php?' . ($search ? 'q=' . urlencode($search) . '&' : '') . 'page=%d');
?>
</div>
<?php endif; ?>
</div><!-- /admin-main -->
</div><!-- /admin-layout -->
<?php render_footer(); ?>
</main>
</body>
</html>