2d52255b93
Added external_view_count so provider totals from YouTube/Vimeo/etc are added to the site’s total without double-counting local plays. Updated public totals and per-video public view displays to use site views + external views. Changed Upkeeping action to Correct external view counts with a Correct Views button. Expanded provider scraping beyond YouTube with generic page/JSON-LD/oEmbed patterns for readable Vimeo, Dailymotion, and similar pages. Improved Add Video:Title no longer has to be manually entered first. External URL rows now have Fetch Info. Metadata can fill title, description, duration, source label, and external view count. Save also retries metadata extraction server-side. Added authenticated metadata endpoint: [admin/media_info.php](/Users/tyemeclifford/Documents/GH/mediaplayer/admin/media_info.php). Updated docs in [README.md](/Users/tyemeclifford/Documents/GH/mediaplayer/README.md).
393 lines
21 KiB
PHP
393 lines
21 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 local source files + thumbnail
|
|
foreach ($v['sources'] as $s) {
|
|
if (source_is_local($s)) {
|
|
$f = MEDIA_DIR . $s['file_path'];
|
|
if ($s['file_path'] && 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') . '.';
|
|
}
|
|
|
|
if ($action === 'correct_youtube_timestamps') {
|
|
$limit = max(1, min(100, (int)($_POST['timestamp_limit'] ?? 25)));
|
|
$result = media_correct_youtube_timestamps($limit);
|
|
$msg = 'Upkeeping checked ' . $result['checked'] . ' YouTube video' . ($result['checked'] === 1 ? '' : 's') . ': ';
|
|
$msg .= $result['corrected'] . ' corrected';
|
|
if ($result['already_correct']) $msg .= ', ' . $result['already_correct'] . ' already correct';
|
|
if ($result['not_found']) $msg .= ', ' . $result['not_found'] . ' without a readable original date';
|
|
$msg .= '.';
|
|
}
|
|
|
|
if ($action === 'restore_external_view_counts' || $action === 'correct_external_view_counts') {
|
|
$limit = max(1, min(100, (int)($_POST['external_view_limit'] ?? 25)));
|
|
$result = media_correct_external_view_counts($limit);
|
|
$msg = 'Upkeeping checked ' . $result['checked'] . ' external video' . ($result['checked'] === 1 ? '' : 's') . ': ';
|
|
$msg .= $result['corrected'] . ' corrected';
|
|
if ($result['already_current']) $msg .= ', ' . $result['already_current'] . ' already current';
|
|
if ($result['not_found']) $msg .= ', ' . $result['not_found'] . ' without a readable external count';
|
|
$msg .= '.';
|
|
}
|
|
}
|
|
|
|
// 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, v.view_count, v.external_view_count,
|
|
COUNT(vs.id) AS source_count,
|
|
SUM(CASE WHEN vs.source_type='remote' THEN 1 ELSE 0 END) AS remote_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();
|
|
$youtube_timestamp_candidates = media_youtube_timestamp_candidate_count();
|
|
$external_view_candidates = media_external_view_count_candidate_count();
|
|
$admin_total_views = total_video_views(false);
|
|
|
|
$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; }
|
|
.section-heading { font-family:"Share Tech Mono",monospace; font-size:.65rem; letter-spacing:.15em; text-transform:uppercase; color:var(--text-3); margin-bottom:.75rem; }
|
|
.hint { font-size:.72rem; color:var(--text-3); margin-top:.2rem; line-height:1.5; }
|
|
.upkeep-stack { display:flex; flex-direction:column; gap:1rem; }
|
|
.upkeep-row { display:flex; align-items:flex-end; justify-content:space-between; gap:1rem; flex-wrap:wrap; }
|
|
.upkeep-row + .upkeep-row { border-top:1px solid var(--border); padding-top:1rem; }
|
|
.upkeep-copy { max-width:620px; }
|
|
.upkeep-form { display:flex; align-items:flex-end; gap:.65rem; flex-wrap:wrap; }
|
|
.upkeep-form .form-group { min-width:120px; }
|
|
');
|
|
?>
|
|
<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="<?= h(public_absolute_url(public_home_url())) ?>" 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>
|
|
<a href="youtube_import.php">
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22.54 6.42a2.78 2.78 0 00-1.95-2C18.88 4 12 4 12 4s-6.88 0-8.59.46a2.78 2.78 0 00-1.95 2A29 29 0 001 12a29 29 0 00.46 5.58 2.78 2.78 0 001.95 2C5.12 20 12 20 12 20s6.88 0 8.59-.46a2.78 2.78 0 001.95-2A29 29 0 0023 12a29 29 0 00-.46-5.58z"/><polygon points="10,15 15,12 10,9"/></svg>
|
|
YouTube Importer
|
|
</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="card">
|
|
<p class="section-heading">Upkeeping</p>
|
|
<div class="upkeep-stack">
|
|
<div class="upkeep-row">
|
|
<div class="upkeep-copy">
|
|
<h2 class="card-title">Correct YouTube timestamps</h2>
|
|
<p class="hint">
|
|
Fetches original YouTube publish dates and updates the database timestamp shown in the admin list and public catalogue.
|
|
<?= $youtube_timestamp_candidates ?> YouTube-backed video<?= $youtube_timestamp_candidates === 1 ? '' : 's' ?> currently match this upkeep task.
|
|
</p>
|
|
</div>
|
|
<form class="upkeep-form" method="post" action="index.php">
|
|
<input type="hidden" name="action" value="correct_youtube_timestamps">
|
|
<div class="form-group">
|
|
<label class="form-label" for="timestamp_limit">Batch Size</label>
|
|
<input class="form-input" type="number" id="timestamp_limit" name="timestamp_limit" value="25" min="1" max="100">
|
|
</div>
|
|
<button class="btn btn-secondary btn-sm" type="submit">
|
|
Correct Dates
|
|
</button>
|
|
</form>
|
|
</div>
|
|
<div class="upkeep-row">
|
|
<div class="upkeep-copy">
|
|
<h2 class="card-title">Correct external view counts</h2>
|
|
<p class="hint">
|
|
Scrapes readable counts from supported providers and adds them to the media-site totals alongside local plays.
|
|
<?= $external_view_candidates ?> external video<?= $external_view_candidates === 1 ? '' : 's' ?> can be checked.
|
|
Current database total: <?= h(view_count_label((int)$admin_total_views)) ?>.
|
|
</p>
|
|
</div>
|
|
<form class="upkeep-form" method="post" action="index.php">
|
|
<input type="hidden" name="action" value="correct_external_view_counts">
|
|
<div class="form-group">
|
|
<label class="form-label" for="external_view_limit">Batch Size</label>
|
|
<input class="form-input" type="number" id="external_view_limit" name="external_view_limit" value="25" min="1" max="100">
|
|
</div>
|
|
<button class="btn btn-secondary btn-sm" type="submit">
|
|
Correct Views
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<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>
|
|
<a href="youtube_import.php" class="btn btn-secondary btn-sm">
|
|
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22.54 6.42a2.78 2.78 0 00-1.95-2C18.88 4 12 4 12 4s-6.88 0-8.59.46a2.78 2.78 0 00-1.95 2A29 29 0 001 12a29 29 0 00.46 5.58 2.78 2.78 0 001.95 2C5.12 20 12 20 12 20s6.88 0 8.59-.46a2.78 2.78 0 001.95-2A29 29 0 0023 12a29 29 0 00-.46-5.58z"/><polygon points="10,15 15,12 10,9"/></svg>
|
|
Import YouTube
|
|
</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>Views</th>
|
|
<th>Status</th>
|
|
<th>Date</th>
|
|
<th class="actions-cell">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php if (empty($videos)): ?>
|
|
<tr><td colspan="8" 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'] ?> source<?= $v['source_count'] != 1 ? 's' : '' ?>
|
|
<?php if ((int)$v['remote_count'] > 0): ?>
|
|
<span style="color:var(--text-3);"> · <?= (int)$v['remote_count'] ?> URL<?= (int)$v['remote_count'] != 1 ? 's' : '' ?></span>
|
|
<?php endif; ?>
|
|
</td>
|
|
<td style="font-family:'Share Tech Mono',monospace;font-size:.7rem;color:var(--text-2);">
|
|
<?= h(format_count(video_total_views($v))) ?>
|
|
<?php if ((int)$v['external_view_count'] > 0): ?>
|
|
<span style="color:var(--text-3);"> · <?= h(format_count((int)$v['external_view_count'])) ?> external</span>
|
|
<?php endif; ?>
|
|
</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>
|