Add admin input directory editor
This commit is contained in:
parent
7e96be1f54
commit
be890808c3
2 changed files with 66 additions and 20 deletions
72
new.php
72
new.php
|
|
@ -4,6 +4,27 @@ foreach (["videos_dir", "thumbs_dir", "uploads_dir"] as $d) if (!is_dir($config[
|
|||
function h($s){ return htmlspecialchars((string)$s, ENT_QUOTES, "UTF-8"); }
|
||||
function slugify($s){ $s = strtolower(trim($s)); $s = preg_replace('/[^a-z0-9]+/', '-', $s); return trim($s, '-') ?: 'movie'; }
|
||||
function rrmdir($dir){ if(!is_dir($dir)) return; foreach(scandir($dir) as $f){ if($f==='.'||$f==='..') continue; $p="$dir/$f"; is_dir($p)?rrmdir($p):unlink($p);} rmdir($dir); }
|
||||
function input_dirs($base){ $dirs = glob($base.'/*', GLOB_ONLYDIR) ?: []; usort($dirs, fn($a,$b)=>filemtime($b)<=>filemtime($a)); return $dirs; }
|
||||
function safe_input_dir($base, $name){ $name = basename((string)$name); $path = realpath($base . '/' . $name); $root = realpath($base); if (!$path || !$root || !str_starts_with($path, $root . DIRECTORY_SEPARATOR) || !is_dir($path)) throw new RuntimeException('Invalid input directory.'); return $path; }
|
||||
function read_data($dir){
|
||||
$data = ['title'=>'','date'=>'','place'=>'','description'=>'']; $file = $dir . '/data.txt';
|
||||
if (!is_file($file)) return $data;
|
||||
foreach (file($file, FILE_IGNORE_NEW_LINES) as $line) {
|
||||
if (!str_contains($line, ':')) continue;
|
||||
[$k,$v] = array_map('trim', explode(':', $line, 2));
|
||||
$k = strtolower($k); if ($k === 'location') $k = 'place';
|
||||
if (array_key_exists($k, $data)) $data[$k] = $v;
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
function write_data($dir, $post){
|
||||
$lines = [];
|
||||
foreach (['title'=>'Title','date'=>'Date','place'=>'Place','description'=>'Description'] as $k=>$label) {
|
||||
$v = trim($post[$k] ?? ''); if ($v !== '') $lines[] = "$label: $v";
|
||||
}
|
||||
file_put_contents($dir . '/data.txt', implode("\n", $lines) . "\n");
|
||||
}
|
||||
function list_files($dir){ $files = array_values(array_filter(scandir($dir), fn($f)=>$f!=='.' && $f!=='..' && is_file($dir.'/'.$f) && $f !== 'data.txt')); natcasesort($files); return $files; }
|
||||
function save_uploads($field, $dest, $allowed){
|
||||
if (empty($_FILES[$field])) return [];
|
||||
$files = $_FILES[$field]; $saved = [];
|
||||
|
|
@ -14,34 +35,59 @@ function save_uploads($field, $dest, $allowed){
|
|||
$ext = strtolower(pathinfo($files['name'][$i], PATHINFO_EXTENSION));
|
||||
if (!in_array($ext, $allowed, true)) throw new RuntimeException("File type not allowed: ".$files['name'][$i]);
|
||||
$base = preg_replace('/[^A-Za-z0-9._-]+/', '_', basename($files['name'][$i]));
|
||||
$target = $dest . '/' . sprintf('%03d_', count($saved)+1) . $base;
|
||||
$target = $dest . '/' . sprintf('%03d_', count(glob($dest.'/*') ?: [])+count($saved)+1) . $base;
|
||||
if (!move_uploaded_file($files['tmp_name'][$i], $target)) throw new RuntimeException("Cannot save upload");
|
||||
$saved[] = basename($target);
|
||||
}
|
||||
return $saved;
|
||||
}
|
||||
$msg = $err = null;
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
try {
|
||||
$allowedMedia = ['jpg','jpeg','png','webp','gif','mp4','mov','m4v','avi','mkv','webm'];
|
||||
$allowedAudio = ['mp3','wav','m4a','aac','ogg','flac'];
|
||||
try {
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$action = $_POST['action'] ?? 'create';
|
||||
if ($action === 'create') {
|
||||
$title = trim($_POST['title'] ?? '');
|
||||
if ($title === '') throw new RuntimeException('Title is required.');
|
||||
$slug = date('Ymd_His') . '_' . slugify($title);
|
||||
$dir = $config['uploads_dir'] . '/' . $slug;
|
||||
if (!mkdir($dir, 0775, true)) throw new RuntimeException('Cannot create input directory.');
|
||||
$media = save_uploads('media', $dir, ['jpg','jpeg','png','webp','gif','mp4','mov','m4v','avi','mkv','webm']);
|
||||
$media = save_uploads('media', $dir, $allowedMedia);
|
||||
if (!$media) { rrmdir($dir); throw new RuntimeException('Upload at least one image or video.'); }
|
||||
save_uploads('audio', $dir, ['mp3','wav','m4a','aac','ogg','flac']);
|
||||
$lines = [];
|
||||
foreach (['title'=>'Title','date'=>'Date','place'=>'Place','description'=>'Description'] as $k=>$label) {
|
||||
$v = trim($_POST[$k] ?? ''); if ($v !== '') $lines[] = "$label: $v";
|
||||
}
|
||||
file_put_contents($dir . '/data.txt', implode("\n", $lines) . "\n");
|
||||
save_uploads('audio', $dir, $allowedAudio);
|
||||
write_data($dir, $_POST);
|
||||
$msg = "Created movmaker input directory: in-dir/$slug";
|
||||
} catch (Throwable $e) { $err = $e->getMessage(); }
|
||||
}
|
||||
$_GET['edit'] = $slug;
|
||||
} elseif ($action === 'update') {
|
||||
$dir = safe_input_dir($config['uploads_dir'], $_POST['dir'] ?? '');
|
||||
write_data($dir, $_POST);
|
||||
save_uploads('media', $dir, $allowedMedia);
|
||||
save_uploads('audio', $dir, $allowedAudio);
|
||||
$msg = 'Updated input directory: in-dir/' . basename($dir);
|
||||
$_GET['edit'] = basename($dir);
|
||||
} elseif ($action === 'delete_file') {
|
||||
$dir = safe_input_dir($config['uploads_dir'], $_POST['dir'] ?? '');
|
||||
$file = basename((string)($_POST['file'] ?? ''));
|
||||
if ($file === '' || $file === 'data.txt' || !is_file($dir.'/'.$file)) throw new RuntimeException('Invalid file.');
|
||||
unlink($dir.'/'.$file);
|
||||
$msg = 'Removed file: ' . $file;
|
||||
$_GET['edit'] = basename($dir);
|
||||
} elseif ($action === 'delete_dir') {
|
||||
$dir = safe_input_dir($config['uploads_dir'], $_POST['dir'] ?? '');
|
||||
$name = basename($dir); rrmdir($dir); $msg = 'Deleted input directory: in-dir/' . $name;
|
||||
}
|
||||
}
|
||||
} catch (Throwable $e) { $err = $e->getMessage(); }
|
||||
$dirs = input_dirs($config['uploads_dir']);
|
||||
$editName = $_GET['edit'] ?? '';
|
||||
$editDir = null; $editData = ['title'=>'','date'=>'','place'=>'','description'=>'']; $editFiles = [];
|
||||
if ($editName !== '') { try { $editDir = safe_input_dir($config['uploads_dir'], $editName); $editData = read_data($editDir); $editFiles = list_files($editDir); } catch (Throwable $e) { $err = $e->getMessage(); } }
|
||||
?>
|
||||
<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Admin - <?=h($config['site_name'])?></title><link rel="stylesheet" href="style.css"></head><body>
|
||||
<header><h1>MVLog Admin</h1><nav><a href="index.php">Videos</a></nav></header><main>
|
||||
<?php if($msg): ?><div class="ok"><?=h($msg)?></div><?php endif; ?><?php if($err): ?><div class="err"><?=h($err)?></div><?php endif; ?>
|
||||
<section><h2>Add new movie input</h2><form method="post" enctype="multipart/form-data"><label>Title*<input name="title" required></label><label>Date<input name="date" placeholder="2026-05-25"></label><label>Place<input name="place"></label><label>Description<textarea name="description"></textarea></label><label>Images / videos*<input type="file" name="media[]" multiple required accept="image/*,video/*"></label><label>Audio (optional)<input type="file" name="audio[]" multiple accept="audio/*"></label><button type="submit">Create input-dir</button></form></section>
|
||||
<section><h2>Existing input dirs</h2><?php if(!$dirs): ?><p>No input directories yet.</p><?php endif; ?><div class="admin-list"><?php foreach($dirs as $d): $data=read_data($d); ?><div class="admin-item"><div><strong><?=h($data['title'] ?: basename($d))?></strong><br><span><?=h(basename($d))?> · <?=h(count(list_files($d)))?> files</span></div><a class="button" href="?edit=<?=rawurlencode(basename($d))?>">Edit</a></div><?php endforeach; ?></div></section>
|
||||
<?php if($editDir): ?><section><h2>Edit input dir</h2><p><code>in-dir/<?=h(basename($editDir))?></code></p><form method="post" enctype="multipart/form-data"><input type="hidden" name="action" value="update"><input type="hidden" name="dir" value="<?=h(basename($editDir))?>"><label>Title<input name="title" value="<?=h($editData['title'])?>"></label><label>Date<input name="date" value="<?=h($editData['date'])?>"></label><label>Place<input name="place" value="<?=h($editData['place'])?>"></label><label>Description<textarea name="description"><?=h($editData['description'])?></textarea></label><label>Add images / videos<input type="file" name="media[]" multiple accept="image/*,video/*"></label><label>Add audio<input type="file" name="audio[]" multiple accept="audio/*"></label><button type="submit">Save changes</button></form><h3>Files</h3><div class="file-list"><?php foreach($editFiles as $f): ?><form method="post" class="file-row"><input type="hidden" name="action" value="delete_file"><input type="hidden" name="dir" value="<?=h(basename($editDir))?>"><input type="hidden" name="file" value="<?=h($f)?>"><span><?=h($f)?></span><button type="submit" onclick="return confirm('Remove this file?')">Remove</button></form><?php endforeach; ?></div><form method="post" onsubmit="return confirm('Delete this whole input directory?')"><input type="hidden" name="action" value="delete_dir"><input type="hidden" name="dir" value="<?=h(basename($editDir))?>"><button type="submit">Delete input dir</button></form></section><?php endif; ?>
|
||||
<section><h2>Add new movie input</h2><form method="post" enctype="multipart/form-data"><input type="hidden" name="action" value="create"><label>Title*<input name="title" required></label><label>Date<input name="date" placeholder="2026-05-25"></label><label>Place<input name="place"></label><label>Description<textarea name="description"></textarea></label><label>Images / videos*<input type="file" name="media[]" multiple required accept="image/*,video/*"></label><label>Audio (optional)<input type="file" name="audio[]" multiple accept="audio/*"></label><button type="submit">Create input-dir</button></form></section>
|
||||
</main><footer>Input dirs are saved under <code>in-dir/</code>.</footer></body></html>
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
@font-face{font-family:BebasNeue;src:url('assets/fonts/BebasNeue-Regular.ttf')}@font-face{font-family:IBMPlexCaption;src:url('assets/fonts/IBMPlexSans-SemiBold.ttf')}@font-face{font-family:NotoStamp;src:url('assets/fonts/NotoSans-Bold.ttf')}:root{font-family:IBMPlexCaption,system-ui,sans-serif;color:#F3F4F6;background:#111315}body{margin:0;background:#111315 url(assets/img/background.jpg) center center/cover fixed no-repeat;min-height:100vh}body::before{content:'';position:fixed;inset:0;background:rgba(17,19,21,.78);z-index:-1}header{background:#1B1E22;color:#F3F4F6;padding:1.2rem 2rem;text-align:center}header h1{font-family:BebasNeue,system-ui,sans-serif;font-size:3rem;letter-spacing:.06em;margin:0}a{color:#3BA7A0}.button,button{background:#C46A3A;color:#111315;border:0;border-radius:.5rem;padding:.7rem 1rem;text-decoration:none;cursor:pointer}nav a{margin-left:1rem;color:#F3F4F6}main{max-width:1100px;margin:2rem auto;padding:0 1rem}.video-list{display:flex;flex-direction:column;gap:1rem}.video-row,form{background:rgba(27,30,34,.92);backdrop-filter:blur(2px);border-radius:.8rem;padding:1rem;box-shadow:0 2px 16px #0006}.video-row{display:grid;grid-template-columns:minmax(280px,42%) 1fr;gap:1rem;align-items:start}.video-row video{width:100%;aspect-ratio:16/9;background:#111315;border-radius:.5rem}.video-info h2{font-size:1.6rem;margin:.1rem 0 .5rem}.meta{display:flex;gap:.5rem;flex-wrap:wrap;color:#A0A4AB;margin:.25rem 0}.meta span{background:#26383A;border-radius:999px;padding:.25rem .55rem}.description{font-size:1rem;line-height:1.45;color:#F3F4F6;margin:.9rem 0}.details{color:#A0A4AB;font-size:.9rem}label{display:block;margin:.8rem 0;font-weight:600}input,textarea{display:block;width:100%;box-sizing:border-box;margin-top:.3rem;padding:.65rem;border:1px solid #3BA7A0;background:#111315;color:#F3F4F6;border-radius:.45rem}textarea{min-height:6rem}.ok,.err{padding:1rem;border-radius:.5rem;margin-bottom:1rem}.ok{background:#1f3b32;color:#F3F4F6}.err{background:#4a241f;color:#F3F4F6}.pages a{display:inline-block;margin:.2rem;padding:.45rem .7rem;background:#1B1E22;border-radius:.4rem;text-decoration:none}.pages .active{background:#C46A3A;color:#111315}footer{font-family:NotoStamp,system-ui,sans-serif;text-align:center;color:#A0A4AB;padding:2rem;font-size:.8rem}@media(max-width:760px){.video-row{grid-template-columns:1fr}header h1{font-size:2.4rem}}
|
||||
@font-face{font-family:BebasNeue;src:url('assets/fonts/BebasNeue-Regular.ttf')}@font-face{font-family:IBMPlexCaption;src:url('assets/fonts/IBMPlexSans-SemiBold.ttf')}@font-face{font-family:NotoStamp;src:url('assets/fonts/NotoSans-Bold.ttf')}:root{font-family:IBMPlexCaption,system-ui,sans-serif;color:#F3F4F6;background:#111315}body{margin:0;background:#111315 url(assets/img/background.jpg) center center/cover fixed no-repeat;min-height:100vh}body::before{content:'';position:fixed;inset:0;background:rgba(17,19,21,.78);z-index:-1}header{background:#1B1E22;color:#F3F4F6;padding:1.2rem 2rem;text-align:center}header h1{font-family:BebasNeue,system-ui,sans-serif;font-size:3rem;letter-spacing:.06em;margin:0}a{color:#3BA7A0}.button,button{background:#C46A3A;color:#111315;border:0;border-radius:.5rem;padding:.7rem 1rem;text-decoration:none;cursor:pointer}nav a{margin-left:1rem;color:#F3F4F6}main{max-width:1100px;margin:2rem auto;padding:0 1rem}.video-list{display:flex;flex-direction:column;gap:1rem}.video-row,form{background:rgba(27,30,34,.92);backdrop-filter:blur(2px);border-radius:.8rem;padding:1rem;box-shadow:0 2px 16px #0006}.video-row{display:grid;grid-template-columns:minmax(280px,42%) 1fr;gap:1rem;align-items:start}.video-row video{width:100%;aspect-ratio:16/9;background:#111315;border-radius:.5rem}.video-info h2{font-size:1.6rem;margin:.1rem 0 .5rem}.meta{display:flex;gap:.5rem;flex-wrap:wrap;color:#A0A4AB;margin:.25rem 0}.meta span{background:#26383A;border-radius:999px;padding:.25rem .55rem}.description{font-size:1rem;line-height:1.45;color:#F3F4F6;margin:.9rem 0}.details{color:#A0A4AB;font-size:.9rem}label{display:block;margin:.8rem 0;font-weight:600}input,textarea{display:block;width:100%;box-sizing:border-box;margin-top:.3rem;padding:.65rem;border:1px solid #3BA7A0;background:#111315;color:#F3F4F6;border-radius:.45rem}textarea{min-height:6rem}.ok,.err{padding:1rem;border-radius:.5rem;margin-bottom:1rem}.ok{background:#1f3b32;color:#F3F4F6}.err{background:#4a241f;color:#F3F4F6}.pages a{display:inline-block;margin:.2rem;padding:.45rem .7rem;background:#1B1E22;border-radius:.4rem;text-decoration:none}.pages .active{background:#C46A3A;color:#111315}footer{font-family:NotoStamp,system-ui,sans-serif;text-align:center;color:#A0A4AB;padding:2rem;font-size:.8rem}.admin-list{display:flex;flex-direction:column;gap:.7rem}.admin-item,.file-row{display:flex;justify-content:space-between;align-items:center;gap:1rem;background:rgba(27,30,34,.92);padding:.8rem;border-radius:.6rem}.admin-item span,.file-row span{color:#A0A4AB}.file-list{display:flex;flex-direction:column;gap:.5rem;margin:1rem 0}.file-row{margin:0}.file-row button{padding:.45rem .7rem}@media(max-width:760px){.video-row{grid-template-columns:1fr}header h1{font-size:2.4rem}}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue