blessing-skin-server/app/Services/PluginManager.php

345 lines
9.8 KiB
PHP
Raw Normal View History

2016-08-24 22:43:04 +08:00
<?php
namespace App\Services;
2018-11-21 23:32:32 +08:00
use Storage;
2019-08-11 18:00:00 +08:00
use Exception;
use App\Events;
2018-06-29 15:11:42 +08:00
use Composer\Semver\Semver;
2016-10-17 12:20:55 +08:00
use Illuminate\Support\Arr;
2019-08-11 18:00:00 +08:00
use Illuminate\Support\Str;
2018-11-21 23:32:32 +08:00
use Composer\Semver\Comparator;
2016-10-24 22:32:07 +08:00
use Illuminate\Support\Collection;
use Illuminate\Filesystem\Filesystem;
use App\Exceptions\PrettyPageException;
2016-10-24 22:32:07 +08:00
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Contracts\Foundation\Application;
2016-10-17 12:20:55 +08:00
2016-08-24 22:43:04 +08:00
class PluginManager
{
2019-08-11 18:00:00 +08:00
/**
* @var bool
*/
protected $booted = false;
2016-10-24 22:32:07 +08:00
/**
* @var Application
*/
2016-10-17 12:20:55 +08:00
protected $app;
2016-10-24 22:32:07 +08:00
/**
2019-03-23 15:44:16 +08:00
* @var Option
2016-10-24 22:32:07 +08:00
*/
protected $option;
2016-10-17 12:20:55 +08:00
/**
* @var Dispatcher
*/
protected $dispatcher;
/**
* @var Filesystem
*/
protected $filesystem;
/**
* @var Collection|null
*/
protected $plugins;
/**
* @var Collection
*/
protected $enabled;
2016-10-17 12:20:55 +08:00
public function __construct(
Application $app,
2019-03-23 15:44:16 +08:00
Option $option,
2016-10-17 12:20:55 +08:00
Dispatcher $dispatcher,
Filesystem $filesystem
) {
$this->app = $app;
$this->option = $option;
2016-10-17 12:20:55 +08:00
$this->dispatcher = $dispatcher;
$this->filesystem = $filesystem;
2019-08-12 17:37:52 +08:00
$this->enabled = collect();
2016-10-17 12:20:55 +08:00
}
2019-08-11 18:00:00 +08:00
/**
2019-08-13 18:42:17 +08:00
* Get all installed plugins.
*
* @return Collection
2019-08-11 18:00:00 +08:00
*/
2019-08-13 18:42:17 +08:00
public function all()
2019-08-11 18:00:00 +08:00
{
2019-08-13 18:42:17 +08:00
if (filled($this->plugins)) {
return $this->plugins;
2019-08-11 18:00:00 +08:00
}
2019-08-13 18:42:17 +08:00
$this->enabled = collect(json_decode($this->option->get('plugins_enabled', '[]'), true))
->mapWithKeys(function ($item) {
return [$item['name'] => ['version' => $item['version']]];
});
2019-08-11 18:00:00 +08:00
$plugins = collect();
collect($this->filesystem->directories($this->getPluginsDir()))
->filter(function ($directory) {
return $this->filesystem->exists($directory.DIRECTORY_SEPARATOR.'package.json');
})
->each(function ($directory) use (&$plugins) {
$manifest = json_decode(
$this->filesystem->get($directory.DIRECTORY_SEPARATOR.'package.json'),
true
);
$name = $manifest['name'];
if ($plugins->has($name)) {
throw new PrettyPageException(trans('errors.plugins.duplicate', [
'dir1' => $plugins->get($name)->getPath(),
'dir2' => $directory,
]), 5);
}
2019-08-12 10:52:40 +08:00
$plugin = new Plugin($directory, $manifest);
2019-08-12 17:37:52 +08:00
$plugins->put($name, $plugin);
if ($this->getUnsatisfied($plugin)->isNotEmpty()) {
$this->disable($plugin);
}
2019-08-13 18:42:17 +08:00
if ($this->enabled->has($name)) {
2019-08-12 10:52:40 +08:00
$plugin->setEnabled(true);
2019-08-12 15:21:50 +08:00
if (Comparator::notEqualTo(
$manifest['version'],
2019-08-13 18:42:17 +08:00
$this->enabled->get($name)['version']
2019-08-12 15:21:50 +08:00
)) {
2019-08-15 16:54:12 +08:00
$this->enabled->put($name, $manifest['version']);
2019-08-12 15:21:50 +08:00
$this->dispatcher->dispatch(new Events\PluginVersionChanged($plugin));
}
2019-08-12 10:52:40 +08:00
}
2019-08-11 18:00:00 +08:00
});
2019-08-13 18:42:17 +08:00
$this->plugins = $plugins;
return $plugins;
}
/**
* Boot all enabled plugins.
*/
public function boot()
{
if ($this->booted) {
return;
}
2019-08-15 16:54:12 +08:00
$enabled = $this->getEnabledPlugins();
2019-08-12 14:35:36 +08:00
2019-08-12 10:52:40 +08:00
$this->registerAutoload(
2019-08-12 14:35:36 +08:00
$enabled->mapWithKeys(function ($plugin) {
2019-08-12 10:52:40 +08:00
return [$plugin->namespace => $plugin->getPath().'/src'];
})
);
2019-08-12 14:35:36 +08:00
$this->loadVendor($enabled);
$this->loadViewsAndTranslations($enabled);
$this->loadBootstrapper($enabled);
2019-08-12 15:59:01 +08:00
$this->registerLifecycleHooks();
2019-08-11 18:00:00 +08:00
$this->booted = true;
}
/**
2019-08-12 14:35:36 +08:00
* Register classes autoloading.
*
2019-08-11 18:00:00 +08:00
* @param Collection $paths
*/
protected function registerAutoload($paths)
{
spl_autoload_register(function ($class) use ($paths) {
$paths->each(function ($path, $namespace) use ($class) {
if ($namespace != '' && mb_strpos($class, $namespace) === 0) {
// Parse real file path
$path = $path.Str::replaceFirst($namespace, '', $class).'.php';
$path = str_replace('\\', '/', $path);
if ($this->filesystem->exists($path)) {
$this->filesystem->getRequire($path);
}
}
});
});
}
2019-08-12 14:35:36 +08:00
/**
* Load Composer dumped autoload file.
*
* @param Collection $enabled
*/
protected function loadVendor($enabled)
{
$enabled->each(function ($plugin) {
$path = $plugin->getPath().'/vendor/autoload.php';
if ($this->filesystem->exists($path)) {
$this->filesystem->getRequire($path);
}
});
}
/**
* Load views and translations.
*
* @param Collection $enabled
*/
protected function loadViewsAndTranslations($enabled)
{
$translations = $this->app->make('translation.loader');
$view = $this->app->make('view');
$enabled->each(function ($plugin) use (&$translations, &$view) {
$namespace = $plugin->namespace;
$path = $plugin->getPath();
$translations->addNamespace($namespace, $path.'/lang');
$view->addNamespace($namespace, $path.'/views');
});
}
/**
* Load plugin's bootstrapper.
*
* @param Collection $enabled
*/
protected function loadBootstrapper($enabled)
{
$enabled->each(function ($plugin) {
$path = $plugin->getPath().'/bootstrap.php';
if ($this->filesystem->exists($path)) {
$this->app->call($this->filesystem->getRequire($path));
}
});
}
2019-08-12 15:59:01 +08:00
protected function registerLifecycleHooks()
{
$this->dispatcher->listen([
Events\PluginWasEnabled::class,
Events\PluginWasDisabled::class,
Events\PluginWasDeleted::class,
], function ($event) {
$plugin = $event->plugin;
$path = $plugin->getPath().'/callbacks.php';
if ($this->filesystem->exists($path)) {
$callbacks = $this->filesystem->getRequire($path);
$callback = Arr::get($callbacks, get_class($event));
if ($callback) {
return $this->app->call($callback, [$plugin]);
}
}
});
}
2019-08-13 18:42:17 +08:00
/**
* @return Plugin|null
*/
public function get(string $name)
{
return $this->all()->get($name);
}
2019-08-15 16:54:12 +08:00
public function enable($plugin)
2016-10-17 12:20:55 +08:00
{
2019-08-15 16:54:12 +08:00
$plugin = is_string($plugin) ? $this->get($plugin) : $plugin;
if ($plugin && ! $plugin->isEnabled()) {
$this->enabled->put($plugin->name, ['version' => $plugin->version]);
$this->saveEnabled();
2016-10-17 12:20:55 +08:00
$plugin->setEnabled(true);
2019-02-27 23:44:50 +08:00
$this->dispatcher->dispatch(new Events\PluginWasEnabled($plugin));
2016-10-17 12:20:55 +08:00
}
2016-08-24 22:43:04 +08:00
}
2016-10-17 12:20:55 +08:00
2019-08-15 16:54:12 +08:00
public function disable($plugin)
2016-10-17 12:20:55 +08:00
{
2019-08-15 16:54:12 +08:00
$plugin = is_string($plugin) ? $this->get($plugin) : $plugin;
if ($plugin && $plugin->isEnabled()) {
$this->enabled->pull($plugin->name);
$this->saveEnabled();
2016-10-17 12:20:55 +08:00
$plugin->setEnabled(false);
2019-02-27 23:44:50 +08:00
$this->dispatcher->dispatch(new Events\PluginWasDisabled($plugin));
2016-10-17 12:20:55 +08:00
}
}
2019-08-15 16:54:12 +08:00
public function delete($plugin)
2016-10-17 12:20:55 +08:00
{
2019-08-15 16:54:12 +08:00
$plugin = is_string($plugin) ? $this->get($plugin) : $plugin;
if ($plugin) {
$this->disable($plugin);
2016-10-17 12:20:55 +08:00
2019-08-15 16:54:12 +08:00
// dispatch event before deleting plugin files
$this->dispatcher->dispatch(new Events\PluginWasDeleted($plugin));
2016-10-17 12:20:55 +08:00
2019-08-15 16:54:12 +08:00
$this->filesystem->deleteDirectory($plugin->getPath());
2017-01-17 21:41:20 +08:00
2019-08-15 16:54:12 +08:00
$this->plugins->pull($plugin->name);
}
2016-10-17 12:20:55 +08:00
}
/**
* @return Collection
*/
public function getEnabledPlugins()
{
2019-08-13 23:06:28 +08:00
return $this->all()->filter(function ($plugin) {
return $plugin->isEnabled();
2018-11-21 23:32:32 +08:00
});
}
2016-10-17 12:20:55 +08:00
/**
* Persist the currently enabled plugins.
*/
protected function saveEnabled()
2016-10-17 12:20:55 +08:00
{
2019-08-13 18:42:17 +08:00
$this->option->set('plugins_enabled', $this->enabled->map(function ($info, $name) {
2019-08-13 23:06:28 +08:00
return array_merge(compact('name'), $info);
})->values()->toJson());
2018-06-29 15:11:42 +08:00
}
2019-08-12 17:37:52 +08:00
/**
* @param Plugin $plugin
* @return Collection
*/
public function getUnsatisfied(Plugin $plugin)
{
return collect(Arr::get($plugin->getManifest(), 'require', []))
->mapWithKeys(function ($constraint, $name) {
if ($name == 'blessing-skin-server') {
$version = config('app.version');
return (! Semver::satisfies($version, $constraint))
? [$name => compact('version', 'constraint')]
: [];
} elseif ($name == 'php') {
$version = PHP_VERSION;
return (! Semver::satisfies($version, $constraint))
? [$name => compact('version', 'constraint')]
: [];
2019-08-13 18:42:17 +08:00
} elseif (! $this->enabled->has($name)) {
2019-08-12 17:37:52 +08:00
return [$name => ['version' => null, 'constraint' => $constraint]];
} else {
2019-08-13 18:42:17 +08:00
$version = $this->enabled->get($name)['version'];
2019-08-12 17:37:52 +08:00
return (! Semver::satisfies($version, $constraint))
? [$name => compact('version', 'constraint')]
: [];
}
});
}
2016-10-17 12:20:55 +08:00
/**
* The plugins path.
*
* @return string
*/
2018-08-19 11:39:14 +08:00
public function getPluginsDir()
2016-10-17 12:20:55 +08:00
{
return config('plugins.directory') ? realpath(config('plugins.directory')) : base_path('plugins');
2016-10-17 12:20:55 +08:00
}
2016-08-24 22:43:04 +08:00
}