> */ private array $actions = []; /** @var array> */ private array $filters = []; /** @var array> */ private array $providers = []; public function path(?string $plugin = null): string { return config('plugins.path').($plugin ? '/'.$plugin : ''); } public function exists(string $plugin): bool { return is_dir($this->path($plugin)) && File::exists($this->path($plugin).'/plugin.json'); } public function manifest(string $plugin): array { $file = $this->path($plugin).'/plugin.json'; if (! File::exists($file)) { return []; } return json_decode(File::get($file), true) ?? []; } public function all(): array { $plugins = []; foreach (File::directories(config('plugins.path')) as $dir) { $basename = basename($dir); $manifest = $this->manifest($basename); if (! $manifest) { continue; } [$vendor, $name] = array_pad(explode('.', $basename), 2, $basename); $plugins[$basename] = [ 'key' => $basename, 'vendor' => $vendor, 'name' => $name, 'title' => $manifest['title'] ?? $name, 'version' => $manifest['version'] ?? '0.0.0', 'description' => $manifest['description'] ?? '', 'author' => $manifest['author'] ?? '', 'enabled' => $this->isEnabled($basename), ]; } return $plugins; } public function isEnabled(string $plugin): bool { if (\Illuminate\Support\Facades\Schema::hasTable('plugin_records')) { [$vendor, $name] = array_pad(explode('.', $plugin), 2, $plugin); $record = PluginRecord::query()->where('vendor', $vendor)->where('name', $name)->first(); if ($record) { return (bool) $record->enabled; } } return in_array($plugin, config('plugins.enabled', []), true); } public function enable(string $plugin): void { [$vendor, $name] = array_pad(explode('.', $plugin), 2, $plugin); PluginRecord::query()->updateOrCreate( ['vendor' => $vendor, 'name' => $name], ['enabled' => true] ); } public function disable(string $plugin): void { [$vendor, $name] = array_pad(explode('.', $plugin), 2, $plugin); PluginRecord::query()->updateOrCreate( ['vendor' => $vendor, 'name' => $name], ['enabled' => false] ); } public function uninstall(string $plugin): void { [$vendor, $name] = array_pad(explode('.', $plugin), 2, $plugin); PluginRecord::query()->where('vendor', $vendor)->where('name', $name)->delete(); } /** * 解析插件提供者类:plugins/{vendor}.{name}/src/PluginServiceProvider.php * * @return class-string<\App\Blog\Support\PluginServiceProvider>|null */ public function resolveProvider(string $plugin): ?string { $class = $this->providers[$plugin] ?? null; if ($class) { return $class; } // 类名兜底:先找 src/ServiceProvider.php,再找 src/PluginServiceProvider.php $manifest = $this->manifest($plugin); $class = $manifest['provider'] ?? null; if (! $class) { $candidates = ['/src/ServiceProvider.php', '/src/PluginServiceProvider.php']; foreach ($candidates as $candidate) { if (File::exists($this->path($plugin).$candidate)) { [$vendor, $name] = array_pad(explode('.', $plugin), 2, $plugin); $class = 'Plugins\\'.Str::studly($vendor).'\\'.Str::studly($name).'\\'.basename($candidate, '.php'); break; } } } return $this->providers[$plugin] = $class; } public function registerProvider(string $plugin, string $class): void { $this->providers[$plugin] = $class; } /* ---------------- Hooks ---------------- */ public function addAction(string $hook, callable $callback, int $priority = 10): void { $this->actions[$hook][$priority][] = $callback; ksort($this->actions[$hook]); } public function addFilter(string $hook, callable $callback, int $priority = 10): void { $this->filters[$hook][$priority][] = $callback; ksort($this->filters[$hook]); } public function doAction(string $hook, mixed ...$args): void { foreach ($this->actions[$hook] ?? [] as $callbacks) { foreach ($callbacks as $callback) { $callback(...$args); } } } public function applyFilters(string $hook, mixed $value, mixed ...$args): mixed { foreach ($this->filters[$hook] ?? [] as $callbacks) { foreach ($callbacks as $callback) { $value = $callback($value, ...$args); } } return $value; } }