- Descriptions now render Markdown or sanitized HTML via [includes/db.php](/Users/tyemeclifford/Documents/GH/mediaplayer/includes/db.php).

Public player descriptions render formatted HTML on the homepage.
Catalogue cards, embed meta tags, and social metadata use safe 
plain-text excerpts.
Add/edit admin pages now use a rich description editor that preserves 
pasted HTML styling, with a textarea fallback for no-JS.
External/YouTube-imported descriptions are sanitized before storage.
README now documents Markdown/HTML description support.
This commit is contained in:
Ty Clifford
2026-07-01 15:41:01 -04:00
parent e9369e242c
commit 6a1c545f35
11 changed files with 787 additions and 17 deletions
+106
View File
@@ -27,6 +27,10 @@ function cli_main(array $argv): int {
$command = 'help';
}
if (cli_command_requires_database_write($command)) {
cli_assert_database_writable();
}
return match ($command) {
'help' => cli_help(),
'site:status', 'status' => cli_site_status(),
@@ -51,6 +55,7 @@ function cli_main(array $argv): int {
'maintenance:external-views' => cli_maintenance_external_views($parsed),
'maintenance:all' => cli_maintenance_all($parsed),
'db:path' => cli_db_path(),
'db:doctor' => cli_db_doctor(),
'db:backup' => cli_db_backup($parsed),
'db:vacuum' => cli_db_vacuum(),
default => cli_unknown($command),
@@ -157,6 +162,7 @@ Maintenance:
Database:
db:path
db:doctor
db:backup [--output=/path/to/media.db]
db:vacuum
@@ -164,6 +170,46 @@ HELP;
return 0;
}
function cli_command_requires_database_write(string $command): bool {
return in_array($command, [
'settings:set',
'admin:password',
'password:set',
'video:add',
'video:update',
'video:publish',
'video:unpublish',
'video:toggle',
'video:delete',
'source:add',
'source:update',
'source:delete',
'maintenance:catalogue-order',
'maintenance:youtube-timestamps',
'maintenance:external-views',
'maintenance:all',
'db:vacuum',
], true);
}
function cli_assert_database_writable(): void {
$paths = cli_database_writable_checks();
$problems = [];
foreach ($paths as $check) {
if (!$check['ok']) $problems[] = $check['message'];
}
if ($problems) {
$details = implode(PHP_EOL . ' - ', $problems);
throw new RuntimeException(
"Database is not writable by the current CLI user (" . cli_current_user_label() . ")." .
PHP_EOL . " - " . $details .
PHP_EOL . "Run db:doctor for details, then make the data directory and media.db writable by this user."
);
}
}
function cli_unknown(string $command): int {
fwrite(STDERR, "Unknown command: {$command}" . PHP_EOL . PHP_EOL);
cli_help();
@@ -768,6 +814,66 @@ function cli_db_path(): int {
return 0;
}
function cli_db_doctor(): int {
$rows = [
['Current user', cli_current_user_label()],
['Database path', DB_PATH],
['Data directory', dirname(DB_PATH)],
['Database exists', cli_yes_no(file_exists(DB_PATH))],
];
foreach (cli_database_writable_checks() as $check) {
$rows[] = [$check['label'], $check['ok'] ? 'ok' : $check['message']];
}
cli_table(['check', 'result'], $rows);
return 0;
}
function cli_database_writable_checks(): array {
$directory = dirname(DB_PATH);
$checks = [];
$checks[] = [
'label' => 'data directory',
'ok' => is_dir($directory) && is_writable($directory),
'message' => is_dir($directory)
? "data directory is not writable: {$directory}"
: "data directory does not exist: {$directory}",
];
if (file_exists(DB_PATH)) {
$checks[] = [
'label' => 'media.db',
'ok' => is_writable(DB_PATH),
'message' => "database file is not writable: " . DB_PATH,
];
}
foreach ([DB_PATH . '-wal', DB_PATH . '-shm'] as $path) {
if (!file_exists($path)) continue;
$checks[] = [
'label' => basename($path),
'ok' => is_writable($path),
'message' => "SQLite sidecar file is not writable: {$path}",
];
}
return $checks;
}
function cli_current_user_label(): string {
if (function_exists('posix_geteuid') && function_exists('posix_getpwuid')) {
$uid = posix_geteuid();
$info = posix_getpwuid($uid);
$name = is_array($info) && isset($info['name']) ? (string)$info['name'] : '';
return $name !== '' ? "{$name} (uid {$uid})" : "uid {$uid}";
}
$user = get_current_user();
return $user !== '' ? $user : 'unknown';
}
function cli_db_backup(array $parsed): int {
cli_db()->exec('PRAGMA wal_checkpoint(FULL)');