Files
AudioCore/core/services/Plugins.php
AudioCore Bot 9deabe1ec9 Release v1.5.1
2026-04-01 14:12:17 +00:00

277 lines
9.6 KiB
PHP

<?php
declare(strict_types=1);
namespace Core\Services;
use Core\Http\Router;
use PDO;
use Throwable;
class Plugins
{
private static string $path = '';
private static array $plugins = [];
public static function init(string $path): void
{
self::$path = rtrim($path, '/');
self::sync();
}
public static function all(): array
{
return self::$plugins;
}
public static function enabled(): array
{
return array_values(array_filter(self::$plugins, static function (array $plugin): bool {
return (bool)($plugin['is_enabled'] ?? false);
}));
}
public static function isEnabled(string $slug): bool
{
foreach (self::$plugins as $plugin) {
if ((string)($plugin['slug'] ?? '') === $slug) {
return (bool)($plugin['is_enabled'] ?? false);
}
}
return false;
}
public static function adminNav(): array
{
$items = [];
foreach (self::enabled() as $plugin) {
$nav = $plugin['admin_nav'] ?? null;
if (!is_array($nav)) {
continue;
}
$items[] = [
'label' => (string)($nav['label'] ?? ''),
'url' => (string)($nav['url'] ?? ''),
'roles' => array_values(array_filter((array)($nav['roles'] ?? []))),
'icon' => (string)($nav['icon'] ?? ''),
'slug' => (string)($plugin['slug'] ?? ''),
];
}
$items = array_values(array_filter($items, static function (array $item): bool {
if ($item['label'] === '' || $item['url'] === '') {
return false;
}
$slug = trim((string)($item['slug'] ?? ''));
if ($slug !== '' && Auth::check() && !Permissions::can(Auth::role(), 'plugin.' . $slug)) {
return false;
}
return true;
}));
$order = [
'artists' => 10,
'releases' => 20,
'store' => 30,
'advanced-reporting' => 40,
'support' => 50,
];
usort($items, static function (array $a, array $b) use ($order): int {
$aSlug = (string)($a['slug'] ?? '');
$bSlug = (string)($b['slug'] ?? '');
$aOrder = $order[$aSlug] ?? 1000;
$bOrder = $order[$bSlug] ?? 1000;
if ($aOrder === $bOrder) {
return strcasecmp((string)($a['label'] ?? ''), (string)($b['label'] ?? ''));
}
return $aOrder <=> $bOrder;
});
return $items;
}
public static function register(Router $router): void
{
foreach (self::enabled() as $plugin) {
$entry = (string)($plugin['entry'] ?? 'plugin.php');
$entryPath = rtrim((string)($plugin['path'] ?? ''), '/') . '/' . $entry;
if (!is_file($entryPath)) {
continue;
}
$handler = require $entryPath;
if (is_callable($handler)) {
$handler($router);
}
}
}
public static function toggle(string $slug, bool $enabled): void
{
$db = Database::get();
if (!$db instanceof PDO) {
return;
}
try {
$stmt = $db->prepare("UPDATE ac_plugins SET is_enabled = :enabled, updated_at = NOW() WHERE slug = :slug");
$stmt->execute([
':enabled' => $enabled ? 1 : 0,
':slug' => $slug,
]);
} catch (Throwable $e) {
}
self::sync();
if (!$enabled) {
$plugin = null;
foreach (self::$plugins as $item) {
if (($item['slug'] ?? '') === $slug) {
$plugin = $item;
break;
}
}
if ($plugin && !empty($plugin['pages'])) {
self::removeNavLinks($db, (array)$plugin['pages']);
}
}
}
public static function sync(): void
{
$filesystem = self::scanFilesystem();
$db = Database::get();
$dbRows = [];
if ($db instanceof PDO) {
try {
$db->exec("
CREATE TABLE IF NOT EXISTS ac_plugins (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
slug VARCHAR(120) NOT NULL UNIQUE,
name VARCHAR(200) NOT NULL,
version VARCHAR(50) NOT NULL DEFAULT '0.0.0',
is_enabled TINYINT(1) NOT NULL DEFAULT 0,
installed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
");
$stmt = $db->query("SELECT slug, is_enabled FROM ac_plugins");
$dbRows = $stmt ? $stmt->fetchAll(PDO::FETCH_ASSOC) : [];
} catch (Throwable $e) {
$dbRows = [];
}
}
$dbMap = [];
foreach ($dbRows as $row) {
$dbMap[(string)$row['slug']] = (int)$row['is_enabled'];
}
foreach ($filesystem as $slug => $plugin) {
$plugin['is_enabled'] = (bool)($dbMap[$slug] ?? ($plugin['default_enabled'] ?? false));
$filesystem[$slug] = $plugin;
if ($db instanceof PDO && !isset($dbMap[$slug])) {
try {
$stmt = $db->prepare("
INSERT INTO ac_plugins (slug, name, version, is_enabled)
VALUES (:slug, :name, :version, :enabled)
");
$stmt->execute([
':slug' => $slug,
':name' => (string)($plugin['name'] ?? $slug),
':version' => (string)($plugin['version'] ?? '0.0.0'),
':enabled' => $plugin['is_enabled'] ? 1 : 0,
]);
} catch (Throwable $e) {
}
}
if ($db instanceof PDO && $plugin['is_enabled']) {
$thisPages = $plugin['pages'] ?? [];
if (is_array($thisPages) && $thisPages) {
self::ensurePages($db, $thisPages);
}
}
}
self::$plugins = array_values($filesystem);
}
private static function scanFilesystem(): array
{
if (self::$path === '' || !is_dir(self::$path)) {
return [];
}
$plugins = [];
foreach (glob(self::$path . '/*/plugin.json') as $manifestPath) {
$dir = dirname($manifestPath);
$slug = basename($dir);
$raw = file_get_contents($manifestPath);
$decoded = json_decode($raw ?: '', true);
if (!is_array($decoded)) {
$decoded = [];
}
$plugins[$slug] = [
'slug' => $slug,
'name' => (string)($decoded['name'] ?? $slug),
'version' => (string)($decoded['version'] ?? '0.0.0'),
'description' => (string)($decoded['description'] ?? ''),
'author' => (string)($decoded['author'] ?? ''),
'admin_nav' => is_array($decoded['admin_nav'] ?? null) ? $decoded['admin_nav'] : null,
'pages' => is_array($decoded['pages'] ?? null) ? $decoded['pages'] : [],
'entry' => (string)($decoded['entry'] ?? 'plugin.php'),
'default_enabled' => (bool)($decoded['default_enabled'] ?? false),
'path' => $dir,
];
}
return $plugins;
}
private static function ensurePages(PDO $db, array $pages): void
{
foreach ($pages as $page) {
if (!is_array($page)) {
continue;
}
$slug = trim((string)($page['slug'] ?? ''));
$title = trim((string)($page['title'] ?? ''));
$content = (string)($page['content_html'] ?? '');
if ($slug === '' || $title === '') {
continue;
}
try {
$stmt = $db->prepare("SELECT id FROM ac_pages WHERE slug = :slug LIMIT 1");
$stmt->execute([':slug' => $slug]);
if ($stmt->fetch()) {
continue;
}
$insert = $db->prepare("
INSERT INTO ac_pages (title, slug, content_html, is_published, is_home, is_blog_index)
VALUES (:title, :slug, :content_html, 1, 0, 0)
");
$insert->execute([
':title' => $title,
':slug' => $slug,
':content_html' => $content,
]);
} catch (Throwable $e) {
}
}
}
private static function removeNavLinks(PDO $db, array $pages): void
{
foreach ($pages as $page) {
if (!is_array($page)) {
continue;
}
$slug = trim((string)($page['slug'] ?? ''));
if ($slug === '') {
continue;
}
$url = '/' . ltrim($slug, '/');
try {
$stmt = $db->prepare("DELETE FROM ac_nav_links WHERE url = :url");
$stmt->execute([':url' => $url]);
} catch (Throwable $e) {
}
}
}
}