Release v1.5.1
This commit is contained in:
@@ -1,258 +1,276 @@
|
||||
<?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'] ?? ''),
|
||||
];
|
||||
}
|
||||
return 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;
|
||||
}));
|
||||
}
|
||||
|
||||
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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
<?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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user