2026-03-04 20:46:11 +00:00
|
|
|
<?php
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
|
|
namespace Plugins\Artists;
|
|
|
|
|
|
|
|
|
|
use Core\Http\Response;
|
|
|
|
|
use Core\Services\Auth;
|
|
|
|
|
use Core\Views\View;
|
|
|
|
|
use Core\Services\Database;
|
|
|
|
|
use PDO;
|
|
|
|
|
use Throwable;
|
|
|
|
|
|
|
|
|
|
class ArtistsController
|
|
|
|
|
{
|
|
|
|
|
private View $view;
|
|
|
|
|
|
|
|
|
|
public function __construct()
|
|
|
|
|
{
|
|
|
|
|
$this->view = new View(__DIR__ . '/views');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function index(): Response
|
|
|
|
|
{
|
|
|
|
|
$db = Database::get();
|
|
|
|
|
$page = null;
|
|
|
|
|
$artists = [];
|
|
|
|
|
if ($db instanceof PDO) {
|
|
|
|
|
try {
|
|
|
|
|
$stmt = $db->prepare("SELECT title, content_html FROM ac_pages WHERE slug = 'artists' AND is_published = 1 LIMIT 1");
|
|
|
|
|
$stmt->execute();
|
|
|
|
|
$page = $stmt->fetch(PDO::FETCH_ASSOC) ?: null;
|
|
|
|
|
$listStmt = $db->query("
|
|
|
|
|
SELECT id, name, slug, country, avatar_url
|
|
|
|
|
FROM ac_artists
|
|
|
|
|
WHERE is_active = 1
|
|
|
|
|
ORDER BY created_at DESC
|
|
|
|
|
");
|
|
|
|
|
$artists = $listStmt ? $listStmt->fetchAll(PDO::FETCH_ASSOC) : [];
|
|
|
|
|
} catch (Throwable $e) {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return new Response($this->view->render('site/index.php', [
|
|
|
|
|
'title' => (string)($page['title'] ?? 'Artists'),
|
|
|
|
|
'content_html' => (string)($page['content_html'] ?? ''),
|
|
|
|
|
'artists' => $artists,
|
|
|
|
|
]));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function show(): Response
|
|
|
|
|
{
|
|
|
|
|
$slug = trim((string)($_GET['slug'] ?? ''));
|
|
|
|
|
$artist = null;
|
|
|
|
|
$artistReleases = [];
|
|
|
|
|
if ($slug !== '') {
|
|
|
|
|
$db = Database::get();
|
|
|
|
|
if ($db instanceof PDO) {
|
|
|
|
|
$stmt = $db->prepare("SELECT * FROM ac_artists WHERE slug = :slug AND is_active = 1 LIMIT 1");
|
|
|
|
|
$stmt->execute([':slug' => $slug]);
|
|
|
|
|
$artist = $stmt->fetch(PDO::FETCH_ASSOC) ?: null;
|
|
|
|
|
if ($artist) {
|
|
|
|
|
try {
|
|
|
|
|
$artistIdReady = false;
|
|
|
|
|
$probe = $db->query("SHOW COLUMNS FROM ac_releases LIKE 'artist_id'");
|
|
|
|
|
if ($probe && $probe->fetch(PDO::FETCH_ASSOC)) {
|
|
|
|
|
$artistIdReady = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($artistIdReady) {
|
|
|
|
|
$relStmt = $db->prepare("
|
|
|
|
|
SELECT id, title, slug, release_date, cover_url, artist_name
|
|
|
|
|
FROM ac_releases
|
|
|
|
|
WHERE is_published = 1
|
2026-03-05 14:30:52 +00:00
|
|
|
AND (release_date IS NULL OR release_date <= :today)
|
2026-03-04 20:46:11 +00:00
|
|
|
AND (artist_id = :artist_id OR artist_name = :artist_name)
|
|
|
|
|
ORDER BY release_date DESC, created_at DESC
|
|
|
|
|
LIMIT 2
|
|
|
|
|
");
|
|
|
|
|
$relStmt->execute([
|
2026-03-05 14:30:52 +00:00
|
|
|
':today' => date('Y-m-d'),
|
2026-03-04 20:46:11 +00:00
|
|
|
':artist_id' => (int)($artist['id'] ?? 0),
|
|
|
|
|
':artist_name' => (string)($artist['name'] ?? ''),
|
|
|
|
|
]);
|
|
|
|
|
} else {
|
|
|
|
|
$relStmt = $db->prepare("
|
|
|
|
|
SELECT id, title, slug, release_date, cover_url, artist_name
|
|
|
|
|
FROM ac_releases
|
|
|
|
|
WHERE is_published = 1
|
2026-03-05 14:30:52 +00:00
|
|
|
AND (release_date IS NULL OR release_date <= :today)
|
2026-03-04 20:46:11 +00:00
|
|
|
AND artist_name = :artist_name
|
|
|
|
|
ORDER BY release_date DESC, created_at DESC
|
|
|
|
|
LIMIT 2
|
|
|
|
|
");
|
|
|
|
|
$relStmt->execute([
|
2026-03-05 14:30:52 +00:00
|
|
|
':today' => date('Y-m-d'),
|
2026-03-04 20:46:11 +00:00
|
|
|
':artist_name' => (string)($artist['name'] ?? ''),
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$artistReleases = $relStmt->fetchAll(PDO::FETCH_ASSOC) ?: [];
|
|
|
|
|
} catch (Throwable $e) {
|
|
|
|
|
$artistReleases = [];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return new Response($this->view->render('site/show.php', [
|
|
|
|
|
'title' => $artist ? (string)$artist['name'] : 'Artist Profile',
|
|
|
|
|
'artist' => $artist,
|
|
|
|
|
'artist_releases' => $artistReleases,
|
|
|
|
|
]));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function adminIndex(): Response
|
|
|
|
|
{
|
|
|
|
|
if (!Auth::check()) {
|
|
|
|
|
return new Response('', 302, ['Location' => '/admin/login']);
|
|
|
|
|
}
|
|
|
|
|
if (!Auth::hasRole(['admin', 'manager'])) {
|
|
|
|
|
return new Response('', 302, ['Location' => '/admin']);
|
|
|
|
|
}
|
|
|
|
|
$tableReady = $this->artistsTableReady();
|
|
|
|
|
$socialReady = $this->socialColumnReady();
|
|
|
|
|
$artists = [];
|
|
|
|
|
$pageId = 0;
|
|
|
|
|
$pagePublished = 0;
|
|
|
|
|
if ($tableReady) {
|
|
|
|
|
$db = Database::get();
|
|
|
|
|
if ($db instanceof PDO) {
|
|
|
|
|
$stmt = $db->query("SELECT id, name, slug, country, avatar_url, is_active FROM ac_artists ORDER BY created_at DESC");
|
|
|
|
|
$artists = $stmt ? $stmt->fetchAll(PDO::FETCH_ASSOC) : [];
|
|
|
|
|
$pageStmt = $db->prepare("SELECT id, is_published FROM ac_pages WHERE slug = 'artists' LIMIT 1");
|
|
|
|
|
$pageStmt->execute();
|
|
|
|
|
$pageRow = $pageStmt->fetch(PDO::FETCH_ASSOC);
|
|
|
|
|
if ($pageRow) {
|
|
|
|
|
$pageId = (int)($pageRow['id'] ?? 0);
|
|
|
|
|
$pagePublished = (int)($pageRow['is_published'] ?? 0);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return new Response($this->view->render('admin/index.php', [
|
|
|
|
|
'title' => 'Artists',
|
|
|
|
|
'table_ready' => $tableReady,
|
|
|
|
|
'social_ready' => $socialReady,
|
|
|
|
|
'artists' => $artists,
|
|
|
|
|
'page_id' => $pageId,
|
|
|
|
|
'page_published' => $pagePublished,
|
|
|
|
|
]));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function adminNew(): Response
|
|
|
|
|
{
|
|
|
|
|
return $this->adminEdit(0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function adminEdit(int $id = 0): Response
|
|
|
|
|
{
|
|
|
|
|
if (!Auth::check()) {
|
|
|
|
|
return new Response('', 302, ['Location' => '/admin/login']);
|
|
|
|
|
}
|
|
|
|
|
if (!Auth::hasRole(['admin', 'manager'])) {
|
|
|
|
|
return new Response('', 302, ['Location' => '/admin']);
|
|
|
|
|
}
|
|
|
|
|
$artist = [
|
|
|
|
|
'id' => 0,
|
|
|
|
|
'name' => '',
|
|
|
|
|
'slug' => '',
|
|
|
|
|
'bio' => '',
|
|
|
|
|
'credits' => '',
|
|
|
|
|
'country' => '',
|
|
|
|
|
'avatar_url' => '',
|
|
|
|
|
'social_links' => '',
|
|
|
|
|
'is_active' => 1,
|
|
|
|
|
];
|
|
|
|
|
if ($id > 0) {
|
|
|
|
|
$db = Database::get();
|
|
|
|
|
if ($db instanceof PDO) {
|
|
|
|
|
$stmt = $db->prepare("SELECT * FROM ac_artists WHERE id = :id LIMIT 1");
|
|
|
|
|
$stmt->execute([':id' => $id]);
|
|
|
|
|
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
|
|
|
if ($row) {
|
|
|
|
|
$artist = array_merge($artist, $row);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!empty($_GET['avatar_url']) && $artist['avatar_url'] === '') {
|
|
|
|
|
$artist['avatar_url'] = (string)$_GET['avatar_url'];
|
|
|
|
|
}
|
|
|
|
|
return new Response($this->view->render('admin/edit.php', [
|
|
|
|
|
'title' => $id > 0 ? 'Edit Artist' : 'New Artist',
|
|
|
|
|
'artist' => $artist,
|
|
|
|
|
'error' => '',
|
|
|
|
|
]));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function adminSave(): Response
|
|
|
|
|
{
|
|
|
|
|
if (!Auth::check()) {
|
|
|
|
|
return new Response('', 302, ['Location' => '/admin/login']);
|
|
|
|
|
}
|
|
|
|
|
if (!Auth::hasRole(['admin', 'manager'])) {
|
|
|
|
|
return new Response('', 302, ['Location' => '/admin']);
|
|
|
|
|
}
|
|
|
|
|
$id = (int)($_POST['id'] ?? 0);
|
|
|
|
|
$name = trim((string)($_POST['name'] ?? ''));
|
|
|
|
|
$slug = trim((string)($_POST['slug'] ?? ''));
|
|
|
|
|
$bio = trim((string)($_POST['bio'] ?? ''));
|
|
|
|
|
$credits = trim((string)($_POST['credits'] ?? ''));
|
|
|
|
|
$country = trim((string)($_POST['country'] ?? ''));
|
|
|
|
|
$avatarUrl = trim((string)($_POST['avatar_url'] ?? ''));
|
|
|
|
|
$socialLinks = [
|
|
|
|
|
'website' => $this->normalizeUrl(trim((string)($_POST['social_website'] ?? ''))),
|
|
|
|
|
'instagram' => $this->normalizeUrl(trim((string)($_POST['social_instagram'] ?? ''))),
|
|
|
|
|
'soundcloud' => $this->normalizeUrl(trim((string)($_POST['social_soundcloud'] ?? ''))),
|
|
|
|
|
'spotify' => $this->normalizeUrl(trim((string)($_POST['social_spotify'] ?? ''))),
|
|
|
|
|
'youtube' => $this->normalizeUrl(trim((string)($_POST['social_youtube'] ?? ''))),
|
|
|
|
|
'tiktok' => $this->normalizeUrl(trim((string)($_POST['social_tiktok'] ?? ''))),
|
|
|
|
|
'bandcamp' => $this->normalizeUrl(trim((string)($_POST['social_bandcamp'] ?? ''))),
|
|
|
|
|
'beatport' => $this->normalizeUrl(trim((string)($_POST['social_beatport'] ?? ''))),
|
|
|
|
|
'facebook' => $this->normalizeUrl(trim((string)($_POST['social_facebook'] ?? ''))),
|
|
|
|
|
'x' => $this->normalizeUrl(trim((string)($_POST['social_x'] ?? ''))),
|
|
|
|
|
];
|
|
|
|
|
$socialLinks = array_filter($socialLinks, static fn($value) => $value !== '');
|
|
|
|
|
$socialJson = $socialLinks ? json_encode($socialLinks, JSON_UNESCAPED_SLASHES) : null;
|
|
|
|
|
$socialReady = $this->socialColumnReady();
|
|
|
|
|
$creditsReady = $this->creditsColumnReady();
|
|
|
|
|
$isActive = isset($_POST['is_active']) ? 1 : 0;
|
|
|
|
|
|
|
|
|
|
if ($name === '') {
|
|
|
|
|
return $this->saveError($id, 'Name is required.');
|
|
|
|
|
}
|
|
|
|
|
$slug = $slug !== '' ? $this->slugify($slug) : $this->slugify($name);
|
|
|
|
|
|
|
|
|
|
$db = Database::get();
|
|
|
|
|
if (!$db instanceof PDO) {
|
|
|
|
|
return $this->saveError($id, 'Database unavailable.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$dupStmt = $id > 0
|
|
|
|
|
? $db->prepare("SELECT id FROM ac_artists WHERE slug = :slug AND id != :id LIMIT 1")
|
|
|
|
|
: $db->prepare("SELECT id FROM ac_artists WHERE slug = :slug LIMIT 1");
|
|
|
|
|
$params = $id > 0 ? [':slug' => $slug, ':id' => $id] : [':slug' => $slug];
|
|
|
|
|
$dupStmt->execute($params);
|
|
|
|
|
if ($dupStmt->fetch()) {
|
|
|
|
|
return $this->saveError($id, 'Slug already exists.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
if ($id > 0) {
|
|
|
|
|
$stmt = $db->prepare("
|
|
|
|
|
UPDATE ac_artists
|
|
|
|
|
SET name = :name, slug = :slug, bio = :bio" . ($creditsReady ? ", credits = :credits" : "") . ", country = :country,
|
|
|
|
|
avatar_url = :avatar_url" . ($socialReady ? ", social_links = :social_links" : "") . ",
|
|
|
|
|
is_active = :is_active
|
|
|
|
|
WHERE id = :id
|
|
|
|
|
");
|
|
|
|
|
$params = [
|
|
|
|
|
':name' => $name,
|
|
|
|
|
':slug' => $slug,
|
|
|
|
|
':bio' => $bio !== '' ? $bio : null,
|
|
|
|
|
':country' => $country !== '' ? $country : null,
|
|
|
|
|
':avatar_url' => $avatarUrl !== '' ? $avatarUrl : null,
|
|
|
|
|
':is_active' => $isActive,
|
|
|
|
|
':id' => $id,
|
|
|
|
|
];
|
|
|
|
|
if ($creditsReady) {
|
|
|
|
|
$params[':credits'] = $credits !== '' ? $credits : null;
|
|
|
|
|
}
|
|
|
|
|
if ($socialReady) {
|
|
|
|
|
$params[':social_links'] = $socialJson;
|
|
|
|
|
}
|
|
|
|
|
$stmt->execute($params);
|
|
|
|
|
} else {
|
|
|
|
|
$stmt = $db->prepare("
|
|
|
|
|
INSERT INTO ac_artists (name, slug, bio" . ($creditsReady ? ", credits" : "") . ", country, avatar_url" . ($socialReady ? ", social_links" : "") . ", is_active)
|
|
|
|
|
VALUES (:name, :slug, :bio" . ($creditsReady ? ", :credits" : "") . ", :country, :avatar_url" . ($socialReady ? ", :social_links" : "") . ", :is_active)
|
|
|
|
|
");
|
|
|
|
|
$params = [
|
|
|
|
|
':name' => $name,
|
|
|
|
|
':slug' => $slug,
|
|
|
|
|
':bio' => $bio !== '' ? $bio : null,
|
|
|
|
|
':country' => $country !== '' ? $country : null,
|
|
|
|
|
':avatar_url' => $avatarUrl !== '' ? $avatarUrl : null,
|
|
|
|
|
':is_active' => $isActive,
|
|
|
|
|
];
|
|
|
|
|
if ($creditsReady) {
|
|
|
|
|
$params[':credits'] = $credits !== '' ? $credits : null;
|
|
|
|
|
}
|
|
|
|
|
if ($socialReady) {
|
|
|
|
|
$params[':social_links'] = $socialJson;
|
|
|
|
|
}
|
|
|
|
|
$stmt->execute($params);
|
|
|
|
|
}
|
|
|
|
|
} catch (Throwable $e) {
|
|
|
|
|
error_log('AC artists save error: ' . $e->getMessage());
|
|
|
|
|
return $this->saveError($id, 'Unable to save artist. Check table columns and input.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return new Response('', 302, ['Location' => '/admin/artists']);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function adminDelete(): Response
|
|
|
|
|
{
|
|
|
|
|
if (!Auth::check()) {
|
|
|
|
|
return new Response('', 302, ['Location' => '/admin/login']);
|
|
|
|
|
}
|
|
|
|
|
if (!Auth::hasRole(['admin', 'manager'])) {
|
|
|
|
|
return new Response('', 302, ['Location' => '/admin']);
|
|
|
|
|
}
|
|
|
|
|
$id = (int)($_POST['id'] ?? 0);
|
|
|
|
|
$db = Database::get();
|
|
|
|
|
if ($db instanceof PDO && $id > 0) {
|
|
|
|
|
$stmt = $db->prepare("DELETE FROM ac_artists WHERE id = :id");
|
|
|
|
|
$stmt->execute([':id' => $id]);
|
|
|
|
|
}
|
|
|
|
|
return new Response('', 302, ['Location' => '/admin/artists']);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function adminUpload(): Response
|
|
|
|
|
{
|
|
|
|
|
if (!Auth::check()) {
|
|
|
|
|
return new Response('', 302, ['Location' => '/admin/login']);
|
|
|
|
|
}
|
|
|
|
|
if (!Auth::hasRole(['admin', 'manager'])) {
|
|
|
|
|
return new Response('', 302, ['Location' => '/admin']);
|
|
|
|
|
}
|
|
|
|
|
$file = $_FILES['artist_avatar'] ?? null;
|
|
|
|
|
$artistId = (int)($_POST['artist_id'] ?? 0);
|
|
|
|
|
if (!$file || !isset($file['tmp_name'])) {
|
|
|
|
|
return $this->uploadRedirect($artistId);
|
|
|
|
|
}
|
|
|
|
|
if ($file['error'] !== UPLOAD_ERR_OK) {
|
|
|
|
|
return $this->uploadRedirect($artistId, $this->uploadErrorMessage((int)$file['error']));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$tmp = (string)$file['tmp_name'];
|
|
|
|
|
if ($tmp === '' || !is_uploaded_file($tmp)) {
|
|
|
|
|
return $this->uploadRedirect($artistId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$info = getimagesize($tmp);
|
|
|
|
|
if ($info === false) {
|
|
|
|
|
return $this->uploadRedirect($artistId, 'Avatar must be an image.');
|
|
|
|
|
}
|
|
|
|
|
$ext = image_type_to_extension($info[2], false);
|
|
|
|
|
$allowed = ['jpg', 'jpeg', 'png', 'webp'];
|
|
|
|
|
if (!in_array($ext, $allowed, true)) {
|
|
|
|
|
return $this->uploadRedirect($artistId, 'Avatar must be JPG, PNG, or WEBP.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$uploadDir = __DIR__ . '/../../uploads/media';
|
|
|
|
|
if (!is_dir($uploadDir) && !mkdir($uploadDir, 0755, true)) {
|
|
|
|
|
return $this->uploadRedirect($artistId, 'Upload directory is not writable.');
|
|
|
|
|
}
|
|
|
|
|
if (!is_writable($uploadDir)) {
|
|
|
|
|
return $this->uploadRedirect($artistId, 'Upload directory is not writable.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$baseName = preg_replace('~[^a-z0-9]+~', '-', strtolower((string)$file['name'])) ?? 'avatar';
|
|
|
|
|
$baseName = trim($baseName, '-');
|
|
|
|
|
$fileName = ($baseName !== '' ? $baseName : 'avatar') . '-' . date('YmdHis') . '.' . $ext;
|
|
|
|
|
$dest = $uploadDir . '/' . $fileName;
|
|
|
|
|
if (!move_uploaded_file($tmp, $dest)) {
|
|
|
|
|
return $this->uploadRedirect($artistId, 'Upload failed.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$fileUrl = '/uploads/media/' . $fileName;
|
|
|
|
|
$fileType = (string)($file['type'] ?? '');
|
|
|
|
|
$fileSize = (int)($file['size'] ?? 0);
|
|
|
|
|
|
|
|
|
|
$db = Database::get();
|
|
|
|
|
if ($db instanceof PDO) {
|
|
|
|
|
try {
|
|
|
|
|
$stmt = $db->prepare("
|
|
|
|
|
INSERT INTO ac_media (file_name, file_url, file_type, file_size, folder_id)
|
|
|
|
|
VALUES (:name, :url, :type, :size, NULL)
|
|
|
|
|
");
|
|
|
|
|
$stmt->execute([
|
|
|
|
|
':name' => (string)$file['name'],
|
|
|
|
|
':url' => $fileUrl,
|
|
|
|
|
':type' => $fileType,
|
|
|
|
|
':size' => $fileSize,
|
|
|
|
|
]);
|
|
|
|
|
} catch (Throwable $e) {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($artistId > 0 && $db instanceof PDO) {
|
|
|
|
|
$stmt = $db->prepare("UPDATE ac_artists SET avatar_url = :url WHERE id = :id");
|
|
|
|
|
$stmt->execute([':url' => $fileUrl, ':id' => $artistId]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $this->uploadRedirect($artistId, '', $fileUrl);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function adminInstall(): Response
|
|
|
|
|
{
|
|
|
|
|
if (!Auth::check()) {
|
|
|
|
|
return new Response('', 302, ['Location' => '/admin/login']);
|
|
|
|
|
}
|
|
|
|
|
if (!Auth::hasRole(['admin', 'manager'])) {
|
|
|
|
|
return new Response('', 302, ['Location' => '/admin']);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$db = Database::get();
|
|
|
|
|
if ($db instanceof PDO) {
|
|
|
|
|
try {
|
|
|
|
|
$db->exec("
|
|
|
|
|
CREATE TABLE IF NOT EXISTS ac_artists (
|
|
|
|
|
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
|
|
|
|
name VARCHAR(200) NOT NULL,
|
|
|
|
|
slug VARCHAR(200) NOT NULL UNIQUE,
|
|
|
|
|
bio MEDIUMTEXT NULL,
|
|
|
|
|
credits MEDIUMTEXT NULL,
|
|
|
|
|
country VARCHAR(120) NULL,
|
|
|
|
|
avatar_url VARCHAR(255) NULL,
|
|
|
|
|
social_links TEXT NULL,
|
|
|
|
|
is_active TINYINT(1) NOT NULL DEFAULT 1,
|
|
|
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
|
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
|
|
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
|
|
|
");
|
|
|
|
|
$db->exec("ALTER TABLE ac_artists ADD COLUMN credits MEDIUMTEXT NULL");
|
|
|
|
|
$db->exec("ALTER TABLE ac_artists ADD COLUMN social_links TEXT NULL");
|
|
|
|
|
} catch (Throwable $e) {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return new Response('', 302, ['Location' => '/admin/artists']);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function artistsTableReady(): bool
|
|
|
|
|
{
|
|
|
|
|
$db = Database::get();
|
|
|
|
|
if (!$db instanceof PDO) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
$stmt = $db->query("SELECT 1 FROM ac_artists LIMIT 1");
|
|
|
|
|
return $stmt !== false;
|
|
|
|
|
} catch (Throwable $e) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function socialColumnReady(): bool
|
|
|
|
|
{
|
|
|
|
|
$db = Database::get();
|
|
|
|
|
if (!$db instanceof PDO) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
$stmt = $db->query("SHOW COLUMNS FROM ac_artists LIKE 'social_links'");
|
|
|
|
|
$row = $stmt ? $stmt->fetch(PDO::FETCH_ASSOC) : null;
|
|
|
|
|
return (bool)$row;
|
|
|
|
|
} catch (Throwable $e) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function creditsColumnReady(): bool
|
|
|
|
|
{
|
|
|
|
|
$db = Database::get();
|
|
|
|
|
if (!$db instanceof PDO) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
$stmt = $db->query("SHOW COLUMNS FROM ac_artists LIKE 'credits'");
|
|
|
|
|
$row = $stmt ? $stmt->fetch(PDO::FETCH_ASSOC) : null;
|
|
|
|
|
return (bool)$row;
|
|
|
|
|
} catch (Throwable $e) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function saveError(int $id, string $message): Response
|
|
|
|
|
{
|
|
|
|
|
return new Response($this->view->render('admin/edit.php', [
|
|
|
|
|
'title' => $id > 0 ? 'Edit Artist' : 'New Artist',
|
|
|
|
|
'artist' => [
|
|
|
|
|
'id' => $id,
|
|
|
|
|
'name' => (string)($_POST['name'] ?? ''),
|
|
|
|
|
'slug' => (string)($_POST['slug'] ?? ''),
|
|
|
|
|
'bio' => (string)($_POST['bio'] ?? ''),
|
|
|
|
|
'credits' => (string)($_POST['credits'] ?? ''),
|
|
|
|
|
'country' => (string)($_POST['country'] ?? ''),
|
|
|
|
|
'avatar_url' => (string)($_POST['avatar_url'] ?? ''),
|
|
|
|
|
'social_links' => '',
|
|
|
|
|
'is_active' => isset($_POST['is_active']) ? 1 : 0,
|
|
|
|
|
],
|
|
|
|
|
'error' => $message,
|
|
|
|
|
]));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function slugify(string $value): string
|
|
|
|
|
{
|
|
|
|
|
$value = strtolower(trim($value));
|
|
|
|
|
$value = preg_replace('~[^a-z0-9]+~', '-', $value) ?? $value;
|
|
|
|
|
$value = trim($value, '-');
|
|
|
|
|
return $value !== '' ? $value : 'artist';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function uploadRedirect(int $artistId, string $error = '', string $url = ''): Response
|
|
|
|
|
{
|
|
|
|
|
if ($artistId > 0) {
|
|
|
|
|
$target = '/admin/artists/edit?id=' . $artistId;
|
|
|
|
|
} else {
|
|
|
|
|
$target = '/admin/artists/new';
|
|
|
|
|
}
|
|
|
|
|
if ($url !== '') {
|
|
|
|
|
$target .= '&avatar_url=' . rawurlencode($url);
|
|
|
|
|
}
|
|
|
|
|
if ($error !== '') {
|
|
|
|
|
$target .= '&upload_error=' . rawurlencode($error);
|
|
|
|
|
}
|
|
|
|
|
return new Response('', 302, ['Location' => $target]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function uploadErrorMessage(int $code): string
|
|
|
|
|
{
|
|
|
|
|
$max = (string)ini_get('upload_max_filesize');
|
|
|
|
|
$map = [
|
|
|
|
|
UPLOAD_ERR_INI_SIZE => "File exceeds upload_max_filesize ({$max}).",
|
|
|
|
|
UPLOAD_ERR_FORM_SIZE => 'File exceeds form limit.',
|
|
|
|
|
UPLOAD_ERR_PARTIAL => 'File was only partially uploaded.',
|
|
|
|
|
UPLOAD_ERR_NO_TMP_DIR => 'Missing temp upload directory.',
|
|
|
|
|
UPLOAD_ERR_CANT_WRITE => 'Failed to write file to disk.',
|
|
|
|
|
UPLOAD_ERR_EXTENSION => 'Upload stopped by a PHP extension.',
|
|
|
|
|
UPLOAD_ERR_NO_FILE => 'No file uploaded.',
|
|
|
|
|
];
|
|
|
|
|
return $map[$code] ?? 'Upload failed.';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function normalizeUrl(string $value): string
|
|
|
|
|
{
|
|
|
|
|
if ($value === '') {
|
|
|
|
|
return '';
|
|
|
|
|
}
|
|
|
|
|
if (preg_match('~^(https?://)~i', $value)) {
|
|
|
|
|
return $value;
|
|
|
|
|
}
|
|
|
|
|
return 'https://' . $value;
|
|
|
|
|
}
|
|
|
|
|
}
|