2016-08-24 22:43:04 +08:00
|
|
|
<?php
|
|
|
|
|
|
|
|
namespace App\Services;
|
|
|
|
|
2017-01-14 21:30:05 +08:00
|
|
|
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;
|
2016-10-24 22:32:07 +08:00
|
|
|
use Illuminate\Support\Collection;
|
2016-10-17 12:20:55 +08:00
|
|
|
use App\Events\PluginWasUninstalled;
|
2016-10-24 22:32:07 +08:00
|
|
|
use Illuminate\Filesystem\Filesystem;
|
2017-07-30 16:11:14 +08:00
|
|
|
use App\Exceptions\PrettyPageException;
|
2016-10-24 22:32:07 +08:00
|
|
|
use Illuminate\Contracts\Events\Dispatcher;
|
|
|
|
use App\Services\Repositories\OptionRepository;
|
|
|
|
use Illuminate\Contracts\Foundation\Application;
|
2016-10-17 12:20:55 +08:00
|
|
|
|
2016-08-24 22:43:04 +08:00
|
|
|
class PluginManager
|
|
|
|
{
|
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
|
|
|
/**
|
|
|
|
* @var OptionRepository
|
|
|
|
*/
|
|
|
|
protected $option;
|
|
|
|
|
2016-10-17 12:20:55 +08:00
|
|
|
/**
|
|
|
|
* @var Dispatcher
|
|
|
|
*/
|
|
|
|
protected $dispatcher;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var Filesystem
|
|
|
|
*/
|
|
|
|
protected $filesystem;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var Collection|null
|
|
|
|
*/
|
|
|
|
protected $plugins;
|
|
|
|
|
|
|
|
public function __construct(
|
|
|
|
Application $app,
|
2016-10-24 22:32:07 +08:00
|
|
|
OptionRepository $option,
|
2016-10-17 12:20:55 +08:00
|
|
|
Dispatcher $dispatcher,
|
|
|
|
Filesystem $filesystem
|
|
|
|
) {
|
2016-10-24 22:32:07 +08:00
|
|
|
$this->app = $app;
|
|
|
|
$this->option = $option;
|
2016-10-17 12:20:55 +08:00
|
|
|
$this->dispatcher = $dispatcher;
|
|
|
|
$this->filesystem = $filesystem;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return Collection
|
|
|
|
*/
|
|
|
|
public function getPlugins()
|
|
|
|
{
|
|
|
|
if (is_null($this->plugins)) {
|
|
|
|
$plugins = new Collection();
|
|
|
|
|
|
|
|
$installed = [];
|
|
|
|
|
2018-06-28 12:07:20 +08:00
|
|
|
try {
|
|
|
|
$resource = opendir($this->getPluginsDir());
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
throw new PrettyPageException(trans('errors.plugins.directory', ['msg' => $e->getMessage()]), 500);
|
|
|
|
}
|
|
|
|
|
2016-10-17 12:20:55 +08:00
|
|
|
// traverse plugins dir
|
|
|
|
while($filename = @readdir($resource)) {
|
2018-06-28 12:07:20 +08:00
|
|
|
if ($filename == '.' || $filename == '..')
|
2016-10-17 12:20:55 +08:00
|
|
|
continue;
|
|
|
|
|
2018-06-28 12:07:20 +08:00
|
|
|
$path = $this->getPluginsDir().DIRECTORY_SEPARATOR.$filename;
|
2016-10-17 12:20:55 +08:00
|
|
|
|
|
|
|
if (is_dir($path)) {
|
2018-06-28 12:07:20 +08:00
|
|
|
$packageJsonPath = $path.DIRECTORY_SEPARATOR.'package.json';
|
|
|
|
|
|
|
|
if (file_exists($packageJsonPath)) {
|
2016-10-17 12:20:55 +08:00
|
|
|
// load packages installed
|
2018-06-28 12:07:20 +08:00
|
|
|
$installed[$filename] = json_decode($this->filesystem->get($packageJsonPath), true);
|
2016-10-17 12:20:55 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
closedir($resource);
|
|
|
|
|
2018-06-28 12:07:20 +08:00
|
|
|
foreach ($installed as $dirname => $package) {
|
2016-10-17 12:20:55 +08:00
|
|
|
|
|
|
|
// Instantiates an Plugin object using the package path and package.json file.
|
2018-06-28 12:07:20 +08:00
|
|
|
$plugin = new Plugin($this->getPluginsDir().DIRECTORY_SEPARATOR.$dirname, $package);
|
2016-10-17 12:20:55 +08:00
|
|
|
|
|
|
|
// Per default all plugins are installed if they are registered in composer.
|
2018-06-28 12:07:20 +08:00
|
|
|
$plugin->setDirname($dirname);
|
2016-10-17 12:20:55 +08:00
|
|
|
$plugin->setInstalled(true);
|
2016-10-24 22:32:07 +08:00
|
|
|
$plugin->setNameSpace(Arr::get($package, 'namespace'));
|
2016-10-17 12:20:55 +08:00
|
|
|
$plugin->setVersion(Arr::get($package, 'version'));
|
|
|
|
$plugin->setEnabled($this->isEnabled($plugin->name));
|
|
|
|
|
2017-07-30 16:11:14 +08:00
|
|
|
if ($plugins->has($plugin->name)) {
|
|
|
|
throw new PrettyPageException(trans('errors.plugins.duplicate', [
|
|
|
|
'dir1' => $plugin->getDirname(),
|
|
|
|
'dir2' => $plugins->get($plugin->name)->getDirname()
|
|
|
|
]), 5);
|
|
|
|
}
|
|
|
|
|
2016-10-17 12:20:55 +08:00
|
|
|
$plugins->put($plugin->name, $plugin);
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->plugins = $plugins->sortBy(function ($plugin, $name) {
|
|
|
|
return $plugin->name;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this->plugins;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Loads an Plugin with all information.
|
|
|
|
*
|
|
|
|
* @param string $name
|
|
|
|
* @return Plugin|null
|
|
|
|
*/
|
|
|
|
public function getPlugin($name)
|
2016-08-24 22:43:04 +08:00
|
|
|
{
|
2016-10-17 12:20:55 +08:00
|
|
|
return $this->getPlugins()->get($name);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Enables the plugin.
|
|
|
|
*
|
|
|
|
* @param string $name
|
|
|
|
*/
|
|
|
|
public function enable($name)
|
|
|
|
{
|
|
|
|
if (! $this->isEnabled($name)) {
|
|
|
|
$plugin = $this->getPlugin($name);
|
|
|
|
|
|
|
|
$enabled = $this->getEnabled();
|
|
|
|
|
|
|
|
$enabled[] = $name;
|
2016-08-24 22:43:04 +08:00
|
|
|
|
2016-10-17 12:20:55 +08:00
|
|
|
$this->setEnabled($enabled);
|
|
|
|
|
|
|
|
$plugin->setEnabled(true);
|
|
|
|
|
2017-01-14 21:30:05 +08:00
|
|
|
$this->dispatcher->fire(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
|
|
|
|
|
|
|
/**
|
|
|
|
* Disables an plugin.
|
|
|
|
*
|
|
|
|
* @param string $name
|
|
|
|
*/
|
|
|
|
public function disable($name)
|
|
|
|
{
|
|
|
|
$enabled = $this->getEnabled();
|
|
|
|
|
|
|
|
if (($k = array_search($name, $enabled)) !== false) {
|
|
|
|
unset($enabled[$k]);
|
|
|
|
|
|
|
|
$plugin = $this->getPlugin($name);
|
|
|
|
|
|
|
|
$this->setEnabled($enabled);
|
|
|
|
|
|
|
|
$plugin->setEnabled(false);
|
|
|
|
|
2017-01-14 21:30:05 +08:00
|
|
|
$this->dispatcher->fire(new Events\PluginWasDisabled($plugin));
|
2016-10-17 12:20:55 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Uninstalls an plugin.
|
|
|
|
*
|
|
|
|
* @param string $name
|
|
|
|
*/
|
|
|
|
public function uninstall($name)
|
|
|
|
{
|
|
|
|
$plugin = $this->getPlugin($name);
|
|
|
|
|
|
|
|
$this->disable($name);
|
|
|
|
|
2017-11-16 10:09:58 +08:00
|
|
|
// fire event before deleting plugin files
|
2017-01-17 21:41:20 +08:00
|
|
|
$this->dispatcher->fire(new Events\PluginWasDeleted($plugin));
|
|
|
|
|
2016-10-17 12:20:55 +08:00
|
|
|
$this->filesystem->deleteDirectory($plugin->getPath());
|
|
|
|
|
|
|
|
// refresh plugin list
|
|
|
|
$this->plugins = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get only enabled plugins.
|
|
|
|
*
|
|
|
|
* @return Collection
|
|
|
|
*/
|
|
|
|
public function getEnabledPlugins()
|
|
|
|
{
|
|
|
|
return $this->getPlugins()->only($this->getEnabled());
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Loads all bootstrap.php files of the enabled plugins.
|
|
|
|
*
|
|
|
|
* @return Collection
|
|
|
|
*/
|
|
|
|
public function getEnabledBootstrappers()
|
|
|
|
{
|
|
|
|
$bootstrappers = new Collection;
|
|
|
|
|
|
|
|
foreach ($this->getEnabledPlugins() as $plugin) {
|
|
|
|
if ($this->filesystem->exists($file = $plugin->getPath().'/bootstrap.php')) {
|
|
|
|
$bootstrappers->push($file);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $bootstrappers;
|
|
|
|
}
|
|
|
|
|
2018-06-28 23:32:27 +08:00
|
|
|
/**
|
|
|
|
* Loads composer autoloader for the enabled plugins if exists.
|
|
|
|
*
|
|
|
|
* @return Collection
|
|
|
|
*/
|
|
|
|
public function getEnabledComposerAutoloaders()
|
|
|
|
{
|
|
|
|
$autoloaders = new Collection;
|
|
|
|
|
|
|
|
foreach ($this->getEnabledPlugins() as $plugin) {
|
|
|
|
if ($this->filesystem->exists($file = $plugin->getPath().'/vendor/autoload.php')) {
|
|
|
|
$autoloaders->push($file);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $autoloaders;
|
|
|
|
}
|
|
|
|
|
2016-10-17 12:20:55 +08:00
|
|
|
/**
|
|
|
|
* The id's of the enabled plugins.
|
|
|
|
*
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function getEnabled()
|
|
|
|
{
|
2016-10-23 13:04:59 +08:00
|
|
|
return (array) json_decode($this->option->get('plugins_enabled'), true);
|
2016-10-17 12:20:55 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Persist the currently enabled plugins.
|
|
|
|
*
|
|
|
|
* @param array $enabled
|
|
|
|
*/
|
|
|
|
protected function setEnabled(array $enabled)
|
|
|
|
{
|
|
|
|
$enabled = array_values(array_unique($enabled));
|
|
|
|
|
|
|
|
$this->option->set('plugins_enabled', json_encode($enabled));
|
2016-12-17 17:07:41 +08:00
|
|
|
|
|
|
|
// ensure to save options
|
|
|
|
$this->option->save();
|
2016-10-17 12:20:55 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Whether the plugin is enabled.
|
|
|
|
*
|
2018-06-29 15:11:42 +08:00
|
|
|
* @param string $pluginName
|
2016-10-17 12:20:55 +08:00
|
|
|
* @return bool
|
|
|
|
*/
|
2018-06-29 15:11:42 +08:00
|
|
|
public function isEnabled($pluginName)
|
2016-10-17 12:20:55 +08:00
|
|
|
{
|
2018-06-29 15:11:42 +08:00
|
|
|
return in_array($pluginName, $this->getEnabled());
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the unsatisfied requirements of plugin.
|
|
|
|
*
|
2018-06-29 18:14:55 +08:00
|
|
|
* @param string|Plugin $plugin
|
2018-06-29 15:11:42 +08:00
|
|
|
* @return array
|
|
|
|
*/
|
2018-06-29 18:14:55 +08:00
|
|
|
public function getUnsatisfiedRequirements($plugin)
|
2018-06-29 15:11:42 +08:00
|
|
|
{
|
2018-06-29 18:14:55 +08:00
|
|
|
if (! $plugin instanceof Plugin) {
|
|
|
|
$plugin = $this->getPlugin($plugin);
|
|
|
|
}
|
2018-06-29 15:11:42 +08:00
|
|
|
|
|
|
|
if (! $plugin) {
|
|
|
|
throw new \InvalidArgumentException('Plugin with given name does not exist.');
|
|
|
|
}
|
|
|
|
|
|
|
|
$requirements = $plugin->getRequirements();
|
|
|
|
|
|
|
|
$unsatisfied = [];
|
|
|
|
|
|
|
|
foreach ($requirements as $name => $versionConstraint) {
|
|
|
|
// Version requirement for the main application
|
|
|
|
if ($name == 'blessing-skin-server') {
|
|
|
|
if (! Semver::satisfies(config('app.version'), $versionConstraint)) {
|
|
|
|
$unsatisfied['blessing-skin-server'] = [
|
|
|
|
'version' => config('app.version'),
|
|
|
|
'constraint' => $versionConstraint
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
$requiredPlugin = $this->getPlugin($name);
|
|
|
|
|
|
|
|
if (!$requiredPlugin || !$requiredPlugin->isEnabled()) {
|
|
|
|
$unsatisfied[$name] = [
|
|
|
|
'version' => null,
|
|
|
|
'constraint' => $versionConstraint
|
|
|
|
];
|
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (! Semver::satisfies($requiredPlugin->getVersion(), $versionConstraint)) {
|
|
|
|
$unsatisfied[$name] = [
|
|
|
|
'version' => $requiredPlugin->getVersion(),
|
|
|
|
'constraint' => $versionConstraint
|
|
|
|
];
|
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $unsatisfied;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Whether the plugin's requirements are satisfied.
|
|
|
|
*
|
2018-06-29 18:14:55 +08:00
|
|
|
* @param string|Plugin $plugin
|
2018-06-29 15:11:42 +08:00
|
|
|
* @return bool
|
|
|
|
*/
|
2018-06-29 18:14:55 +08:00
|
|
|
public function isRequirementsSatisfied($plugin)
|
2018-06-29 15:11:42 +08:00
|
|
|
{
|
2018-06-29 18:14:55 +08:00
|
|
|
return empty($this->getUnsatisfiedRequirements($plugin));
|
2016-10-17 12:20:55 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The plugins path.
|
|
|
|
*
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
protected function getPluginsDir()
|
|
|
|
{
|
2018-06-28 12:07:20 +08:00
|
|
|
return config('plugins.directory') ?: base_path('plugins');
|
2016-10-17 12:20:55 +08:00
|
|
|
}
|
|
|
|
|
2016-08-24 22:43:04 +08:00
|
|
|
}
|