working on framework of plugins

This commit is contained in:
printempw 2016-10-17 12:20:55 +08:00
parent 688bf668e8
commit 55414072e7
12 changed files with 647 additions and 12 deletions

View File

@ -9,6 +9,7 @@ use App\Models\Player;
use App\Models\Texture;
use App\Models\UserModel;
use Illuminate\Http\Request;
use App\Services\PluginManager;
use App\Exceptions\PrettyPageException;
class AdminController extends Controller
@ -63,6 +64,47 @@ class AdminController extends Controller
}
}
public function plugins(Request $request, PluginManager $plugins)
{
if ($request->has('action') && $request->has('id')) {
$id = $request->get('id');
if ($plugins->getPlugins()->has($id)) {
switch ($request->get('action')) {
case 'enable':
$plugins->enable($id);
break;
case 'disable':
$plugins->disable($id);
break;
case 'delete':
if ($request->isMethod('post')) {
$plugins->uninstall($id);
return json('插件已被成功删除', 0);
}
break;
default:
# code...
break;
}
}
}
$data = [
'installed' => $plugins->getPlugins(),
'enabled' => $plugins->getEnabledPlugins()
];
// dd($data);
return view('admin.plugins', $data);
}
/**
* Show Manage Page of Users.
*

View File

@ -99,6 +99,7 @@ Route::group(['middleware' => 'admin', 'prefix' => 'admin'], function()
{
Route::get('/', 'AdminController@index');
Route::any('/plugins', 'AdminController@plugins');
Route::any('/customize', 'AdminController@customize');
Route::any('/score', 'AdminController@score');
Route::any('/options', 'AdminController@options');

View File

@ -0,0 +1,37 @@
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class PluginServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
//
}
/**
* Register any application services.
*
* @return void
*/
public function register()
{
$this->app->bind('plugins', 'App\Services\PluginManager');
$bootstrappers = $this->app->make('plugins')->getEnabledBootstrappers();
foreach ($bootstrappers as $file) {
// bootstraper is a closure
$bootstrapper = require $file;
$this->app->call($bootstrapper);
}
}
}

183
app/Services/Plugin.php Normal file
View File

@ -0,0 +1,183 @@
<?php
namespace App\Services;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
/**
* @property string $name
* @property string $description
* @property string $type
* @property array $keywords
* @property string $homepage
* @property string $time
* @property string $license
* @property array $authors
* @property array $support
* @property array $require
* @property array $requireDev
* @property array $autoload
* @property array $autoloadDev
* @property array $conflict
* @property array $replace
* @property array $provide
* @property array $suggest
* @property array $extra
*/
class Plugin implements Arrayable
{
/**
* The directory of this plugin.
*
* @var string
*/
protected $path;
/**
* package.json of the package.
*
* @var array
*/
protected $packageInfo;
/**
* Whether the plugin is installed.
*
* @var bool
*/
protected $installed = true;
/**
* The installed version of the plugin.
*
* @var string
*/
protected $version;
/**
* Whether the plugin is enabled.
*
* @var bool
*/
protected $enabled = false;
/**
* @param $path
* @param array $packageInfo
*/
public function __construct($path, $packageInfo)
{
$this->path = $path;
$this->packageInfo = $packageInfo;
}
/**
* {@inheritdoc}
*/
public function __get($name)
{
return $this->packageInfoAttribute(Str::snake($name, '-'));
}
/**
* {@inheritdoc}
*/
public function __isset($name)
{
return isset($this->{$name}) || $this->packageInfoAttribute(Str::snake($name, '-'));
}
/**
* Dot notation getter for composer.json attributes.
*
* @see https://laravel.com/docs/5.1/helpers#arrays
*
* @param $name
* @return mixed
*/
public function packageInfoAttribute($name)
{
return Arr::get($this->packageInfo, $name);
}
/**
* @param bool $installed
* @return Plugin
*/
public function setInstalled($installed)
{
$this->installed = $installed;
return $this;
}
/**
* @return bool
*/
public function isInstalled()
{
return $this->installed;
}
/**
* @param string $version
* @return Plugin
*/
public function setVersion($version)
{
$this->version = $version;
return $this;
}
/**
* @return string
*/
public function getVersion()
{
return $this->version;
}
/**
* @param bool $enabled
* @return Plugin
*/
public function setEnabled($enabled)
{
$this->enabled = $enabled;
return $this;
}
/**
* @return bool
*/
public function isEnabled()
{
return $this->enabled;
}
/**
* @return string
*/
public function getPath()
{
return $this->path;
}
/**
* Generates an array result for the object.
*
* @return array
*/
public function toArray()
{
return (array) array_merge([
'name' => $this->name,
'version' => $this->getVersion(),
'path' => $this->path
], $this->packageInfo);
}
}

View File

@ -2,10 +2,250 @@
namespace App\Services;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Filesystem\Filesystem;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use App\Events\PluginWasDisabled;
use App\Events\PluginWasEnabled;
use App\Events\PluginWasUninstalled;
class PluginManager
{
public function __construct()
{
protected $option;
protected $app;
/**
* @var Dispatcher
*/
protected $dispatcher;
/**
* @var Filesystem
*/
protected $filesystem;
/**
* @var Collection|null
*/
protected $plugins;
public function __construct(
OptionRepository $option,
Application $app,
Dispatcher $dispatcher,
Filesystem $filesystem
) {
$this->option = $option;
$this->app = $app;
$this->dispatcher = $dispatcher;
$this->filesystem = $filesystem;
}
/**
* @return Collection
*/
public function getPlugins()
{
if (is_null($this->plugins)) {
$plugins = new Collection();
$installed = [];
$resource = opendir(base_path('plugins'));
// traverse plugins dir
while($filename = @readdir($resource)) {
if ($filename == "." || $filename == "..")
continue;
$path = base_path('plugins')."/".$filename;
if (is_dir($path)) {
if (file_exists($path."/package.json")) {
// load packages installed
$installed[$filename] = json_decode($this->filesystem->get($path."/package.json"), true);
}
}
}
closedir($resource);
foreach ($installed as $path => $package) {
// Instantiates an Plugin object using the package path and package.json file.
$plugin = new Plugin($this->getPluginsDir().'/'.$path, $package);
// Per default all plugins are installed if they are registered in composer.
$plugin->setInstalled(true);
$plugin->setVersion(Arr::get($package, 'version'));
$plugin->setEnabled($this->isEnabled($plugin->name));
$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)
{
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;
// $this->migrate($plugin);
// $this->publishAssets($plugin);
$this->setEnabled($enabled);
$plugin->setEnabled(true);
// $this->dispatcher->fire(new PluginWasEnabled($plugin));
}
}
/**
* 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);
// $this->dispatcher->fire(new PluginWasDisabled($plugin));
}
}
/**
* Uninstalls an plugin.
*
* @param string $name
*/
public function uninstall($name)
{
$plugin = $this->getPlugin($name);
$this->disable($name);
// $this->migrateDown($plugin);
// $this->unpublishAssets($plugin);
// $plugin->setInstalled(false);
$this->filesystem->deleteDirectory($plugin->getPath());
// refresh plugin list
$this->plugins = null;
// $this->dispatcher->fire(new PluginWasUninstalled($plugin));
}
/**
* 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;
}
/**
* The id's of the enabled plugins.
*
* @return array
*/
public function getEnabled()
{
return json_decode($this->option->get('plugins_enabled'), true);
}
/**
* 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));
}
/**
* Whether the plugin is enabled.
*
* @param $plugin
* @return bool
*/
public function isEnabled($plugin)
{
return in_array($plugin, $this->getEnabled());
}
/**
* The plugins path.
*
* @return string
*/
protected function getPluginsDir()
{
return $this->app->basePath().'/plugins';
}
}

View File

@ -162,6 +162,7 @@ return [
*/
App\Providers\BootServiceProvider::class,
App\Providers\AppServiceProvider::class,
App\Providers\PluginServiceProvider::class,
App\Providers\EventServiceProvider::class,
App\Providers\RouteServiceProvider::class,
App\Providers\ResponseMacroServiceProvider::class,

View File

@ -17,14 +17,15 @@ $menu['user'] = array(
);
$menu['admin'] = array(
['title' => 'general.dashboard', 'link' => 'admin', 'icon' => 'fa-dashboard'],
['title' => 'general.user-manage', 'link' => 'admin/users', 'icon' => 'fa-users'],
['title' => 'general.player-manage', 'link' => 'admin/players', 'icon' => 'fa-gamepad'],
['title' => 'general.customize', 'link' => 'admin/customize', 'icon' => 'fa-paint-brush'],
['title' => 'general.score-options', 'link' => 'admin/score', 'icon' => 'fa-credit-card'],
['title' => 'general.options', 'link' => 'admin/options', 'icon' => 'fa-cog'],
['title' => 'general.import-v2', 'link' => 'setup/migrations', 'icon' => 'fa-undo'],
['title' => 'general.check-update', 'link' => 'admin/update', 'icon' => 'fa-arrow-up']
['title' => 'general.dashboard', 'link' => 'admin', 'icon' => 'fa-dashboard'],
['title' => 'general.user-manage', 'link' => 'admin/users', 'icon' => 'fa-users'],
['title' => 'general.player-manage', 'link' => 'admin/players', 'icon' => 'fa-gamepad'],
['title' => 'general.plugin-manage', 'link' => 'admin/plugins', 'icon' => 'fa-plug'],
['title' => 'general.customize', 'link' => 'admin/customize', 'icon' => 'fa-paint-brush'],
['title' => 'general.score-options', 'link' => 'admin/score', 'icon' => 'fa-credit-card'],
['title' => 'general.options', 'link' => 'admin/options', 'icon' => 'fa-cog'],
['title' => 'general.import-v2', 'link' => 'setup/migrations', 'icon' => 'fa-undo'],
['title' => 'general.check-update', 'link' => 'admin/update', 'icon' => 'fa-arrow-up']
);
return $menu;

View File

@ -3,7 +3,7 @@
* @Author: printempw
* @Date: 2016-07-29 11:53:11
* @Last Modified by: printempw
* @Last Modified time: 2016-10-17 09:40:16
* @Last Modified time: 2016-10-17 11:11:52
*/
return [
@ -37,5 +37,6 @@ return [
'return_200_when_notfound' => '0',
'cache_expire_time' => '31536000',
'max_upload_file_size' => '1024',
'auto_detect_asset_url' => '1'
'auto_detect_asset_url' => '1',
'plugins_enabled' => ''
];

2
plugins/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*
!.gitignore

View File

@ -16,6 +16,7 @@ my-closet: Closet
player-manage: Player Manage
user-manage: User Manage
generate-config: Generate Config
plugin-manage: Plugin Manage
customize: Customize
options: Options
import-v2: Import Data

View File

@ -16,6 +16,7 @@ my-closet: 我的衣柜
player-manage: 角色管理
user-manage: 用户管理
generate-config: 配置生成
plugin-manage: 插件管理
customize: 个性化
options: 站点配置
import-v2: 导入数据

View File

@ -0,0 +1,125 @@
@extends('admin.master')
@section('title', trans('general.plugin-manage'))
@section('style')
<style>
.btn {
margin-right: 4px;
}
</style>
@endsection
@section('content')
<!-- Content Wrapper. Contains page content -->
<div class="content-wrapper">
<!-- Content Header (Page header) -->
<section class="content-header">
<h1>
{{ trans('general.plugin-manage') }}
<small>Plugin Manage</small>
</h1>
</section>
<!-- Main content -->
<section class="content">
<div class="box">
<div class="box-body table-responsive no-padding">
<table class="table table-hover">
<thead>
<tr>
<th>名称</th>
<th>描述</th>
<th>作者</th>
<th>版本</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
<tbody>
@forelse($installed as $plugin)
<tr id="plugin-{{ $plugin->name }}">
<td>{{ $plugin->title }}</td>
<td id="description">{{ $plugin->description }}</td>
<td id="author">{{ $plugin->author }}</td>
<td id="version">{{ $plugin->version }}</td>
<td id="status">
@if ($plugin->isEnabled())
已启用
@else
已禁用
@endif
</td>
<td>
@if ($plugin->isEnabled())
<a class="btn btn-warning btn-sm" href="?action=disable&id={{ $plugin->name }}">禁用插件</a>
@else
<a class="btn btn-primary btn-sm" href="?action=enable&id={{ $plugin->name }}">启用插件</a>
@endif
<a class="btn btn-default btn-sm" href="?action=config&id={{ $plugin->name }}">插件配置</a>
<a class="btn btn-danger btn-sm" href="javascript:deletePlugin('{{ $plugin->name }}');">删除插件</a>
</td>
</tr>
@empty
<tr>
<td>0</td>
<td>无结果</td>
<td>(´・ω・`)</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
</section><!-- /.content -->
</div><!-- /.content-wrapper -->
@endsection
@section('style')
<style>
@media (max-width: 767px) {
.content-header > h1 > small {
display: none;
}
}
</style>
@endsection
@section('script')
<script type="text/javascript">
$(document).ready(function() {
$('.box-body').css('min-height', $('.content-wrapper').height() - $('.content-header').outerHeight() - 120);
});
function deletePlugin(name) {
swal({
text: '真的要删除这个插件吗?',
type: 'warning',
showCancelButton: true
}).then(function() {
$.ajax({
type: "POST",
url: "?action=delete&id=" + name,
dataType: "json",
success: function(json) {
if (json.errno == 0) {
toastr.success(json.msg);
$('tr[id=plugin-'+name+']').remove();
} else {
toastr.warning(json.msg);
}
},
error: showAjaxError
});
});
}
</script>
@endsection