query("SHOW COLUMNS FROM ac_releases LIKE 'artist_id'");
$artistJoinReady = (bool)($probe && $probe->fetch(\PDO::FETCH_ASSOC));
} catch (\Throwable $e) {
$artistJoinReady = false;
}
if ($artistJoinReady) {
$stmt = $db->prepare("
SELECT r.title, r.slug, r.release_date, r.cover_url, COALESCE(r.artist_name, a.name) AS artist_name
FROM ac_releases r
LEFT JOIN ac_artists a ON a.id = r.artist_id
WHERE r.is_published = 1
AND (r.release_date IS NULL OR r.release_date <= CURDATE())
ORDER BY r.release_date DESC, r.created_at DESC
LIMIT :limit
");
} else {
$stmt = $db->prepare("
SELECT title, slug, release_date, cover_url, artist_name
FROM ac_releases
WHERE is_published = 1
AND (release_date IS NULL OR release_date <= CURDATE())
ORDER BY release_date DESC, created_at DESC
LIMIT :limit
");
}
$stmt->bindValue(':limit', $limit, \PDO::PARAM_INT);
$stmt->execute();
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC) ?: [];
} catch (\Throwable $e) {
return '';
}
if (!$rows) {
return '
No releases published yet.
';
}
$cards = '';
foreach ($rows as $row) {
$title = htmlspecialchars((string)($row['title'] ?? ''), ENT_QUOTES, 'UTF-8');
$slug = rawurlencode((string)($row['slug'] ?? ''));
$artist = htmlspecialchars(trim((string)($row['artist_name'] ?? '')), ENT_QUOTES, 'UTF-8');
$date = htmlspecialchars((string)($row['release_date'] ?? ''), ENT_QUOTES, 'UTF-8');
$cover = trim((string)($row['cover_url'] ?? ''));
$coverHtml = $cover !== ''
? '
'
: 'AC
';
$cards .= ''
. '' . $coverHtml . '
'
. ''
. '';
}
return ''
. 'Latest Releases
'
. '' . $cards . '
'
. '';
});
Shortcodes::register('latest-releases', static function (array $attrs = []): string {
return Shortcodes::render('[releases limit="' . max(1, min(20, (int)($attrs['limit'] ?? 8))) . '"]');
});
return function (Router $router): void {
$controller = new ReleasesController();
$router->get('/releases', [$controller, 'index']);
$router->get('/release', [$controller, 'show']);
$router->get('/admin/releases', [$controller, 'adminIndex']);
$router->post('/admin/releases/install', [$controller, 'adminInstall']);
$router->get('/admin/releases/new', [$controller, 'adminNew']);
$router->get('/admin/releases/edit', function () use ($controller): Core\Http\Response {
$id = isset($_GET['id']) ? (int)$_GET['id'] : 0;
return $controller->adminEdit($id);
});
$router->get('/admin/releases/tracks', function () use ($controller): Core\Http\Response {
$releaseId = isset($_GET['release_id']) ? (int)$_GET['release_id'] : 0;
return $controller->adminTracks($releaseId);
});
$router->get('/admin/releases/tracks/new', function () use ($controller): Core\Http\Response {
$releaseId = isset($_GET['release_id']) ? (int)$_GET['release_id'] : 0;
return $controller->adminTrackEdit(0, $releaseId);
});
$router->get('/admin/releases/tracks/edit', function () use ($controller): Core\Http\Response {
$id = isset($_GET['id']) ? (int)$_GET['id'] : 0;
$releaseId = isset($_GET['release_id']) ? (int)$_GET['release_id'] : 0;
return $controller->adminTrackEdit($id, $releaseId);
});
$router->post('/admin/releases/save', [$controller, 'adminSave']);
$router->post('/admin/releases/delete', [$controller, 'adminDelete']);
$router->post('/admin/releases/upload', [$controller, 'adminUpload']);
$router->post('/admin/releases/tracks/save', [$controller, 'adminTrackSave']);
$router->post('/admin/releases/tracks/delete', [$controller, 'adminTrackDelete']);
$router->post('/admin/releases/tracks/upload', [$controller, 'adminTrackUpload']);
$router->post('/admin/releases/tracks/sample/generate', [$controller, 'adminTrackGenerateSample']);
$router->get('/admin/releases/tracks/source', [$controller, 'adminTrackSource']);
};