Files
swiftadmin/app/admin/controller/developer/Plugin.php
2024-07-13 12:53:20 +08:00

432 lines
15 KiB
PHP

<?php
declare(strict_types=1);
namespace app\admin\controller\developer;
use app\AdminController;
use app\common\model\system\AdminRules;
use Exception;
use FilesystemIterator;
use Psr\SimpleCache\InvalidArgumentException;
use support\Response;
use system\File;
use system\ZipArchives;
use think\db\exception\DataNotFoundException;
use think\db\exception\DbException;
use think\db\exception\ModelNotFoundException;
use app\admin\controller\system\Plugin as PluginService;
/**
* 插件开发
* <!--Developer-->
* Class Plugin
* @package app\admin\controller\developer
*/
class Plugin extends AdminController
{
// 初始化操作
public function __construct()
{
parent::__construct();
$this->model = new \app\admin\model\developer\Plugin();
}
/**
* 获取资源
* @return Response
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
*/
public function index(): \support\Response
{
if (request()->isAjax()) {
$param = \request()->post();
$param['page'] = (int)input('page');
$param['limit'] = (int)('limit');
$count = $this->model->count();
$limit = empty($param['limit']) ? 10 : $param['limit'];
$page = ($count <= $limit) ? 1 : $param['page'];
$list = $this->model->order("id desc")->limit($limit)->page($page)->select()->toArray();
return $this->success('查询成功', null, $list, $count);
}
return view('/developer/plugin/index');
}
/**
* 上传插件
* @return array
* @throws Exception
*/
public function parseFile(): array
{
try {
$file = \request()->file('file');
if (empty($file)) {
throw new \Exception('插件上传失败');
}
$uploadName = $file->getUploadName();
$filePath = plugin_path() . $uploadName;
if (!$file->move($filePath)) {
throw new \Exception('插件上传失败');
}
$uploadName = pathinfo($uploadName)['filename'];
$uploadName = explode('-', $uploadName);
$fileName = current($uploadName);
} catch (\Throwable $th) {
throw new \Exception($th->getMessage());
}
return [strtolower($fileName), $filePath];
}
/**
* 执行本地安装测试
* @return Response
* @throws InvalidArgumentException
*/
public function install(): Response
{
try {
list($name, $filePath) = $this->parseFile();
if (is_dir(plugin_path($name))) {
throw new \Exception('插件已存在');
}
ZipArchives::unzip($filePath, plugin_path(), '', true);
$pluginClass = get_plugin_instance($name);
$pluginClass->install();
PluginService::pluginMenu($name);
PluginService::executeSql($name);
PluginService::enabled($name);
} catch (\Throwable $th) {
return $this->error($th->getMessage());
}
return $this->success('插件安装成功');
}
/**
* 升级插件
* @return \support\Response
* @throws Exception|\Psr\SimpleCache\InvalidArgumentException
*/
public function upgrade(): \support\Response
{
try {
list($name, $filePath) = $this->parseFile();
$pluginPath = plugin_path($name);
$pluginInfo = get_plugin_config($name, true);
if (!$pluginInfo) {
throw new \Exception('插件不存在');
}
if ($pluginInfo['status']) {
throw new \Exception('插件已启用,请先禁用插件');
}
$formIndex = ZipArchives::unzip($filePath, plugin_path(), 'config.json');
$upgradeInfo = json_decode($formIndex, true);
if (version_compare($upgradeInfo['version'], $pluginInfo['version'], "<=")) {
throw new \Exception('升级版本不能低于已安装版本');
}
$backupDir = root_path() . $name . '_' . $pluginInfo['version'] . '.zip';
ZipArchives::compression($backupDir, $pluginPath, plugin_path());
ZipArchives::unzip($filePath, plugin_path(), '', true);
$pluginClass = get_plugin_instance($name, 'upgrade');
$pluginClass->execute($pluginInfo['version'], $upgradeInfo['version']);
$data = array_merge($upgradeInfo, [
'extends' => $pluginInfo['extends'],
'rewrite' => $pluginInfo['rewrite'],
]);
write_file($pluginPath . 'config.json', json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
PluginService::pluginMenu($name);
PluginService::executeSql($name);
PluginService::enabled($name);
} catch (\Exception $th) {
return $this->error($th->getMessage());
}
return $this->success('插件升级成功', null, $pluginInfo);
}
/**
* 导入本地插件
* @return Response
* @throws DataNotFoundException
* @throws DbException
* @throws InvalidArgumentException
* @throws ModelNotFoundException
*/
public function import(): \support\Response
{
if (request()->isPost()) {
$name = input('dir');
$pluginInfo = get_plugin_config($name, true);
if (empty($pluginInfo)) {
return $this->error('插件信息获取失败');
}
// 是否已经导入
$result = $this->model->where('name', $name)->findOrEmpty();
if (!$result->isEmpty()) {
return $this->error('请勿重复导入插件');
}
try {
$pluginInfo['import'] = time();
$this->model->create($pluginInfo);
} catch (\Throwable $th) {
return $this->error($th->getMessage());
}
return $this->success('导入成功');
}
$dirs = [];
$iterator = new FilesystemIterator(plugin_path(), FilesystemIterator::SKIP_DOTS);
foreach ($iterator as $item) {
if ($item->isDir()) {
// 是否已经导入
$find = $this->model->where('name', $item->getBasename())->find();
if (empty($find)) {
$name = $item->getBasename();
$info = get_plugin_config($name);
$dirs[] = [
'name' => $name,
'title' => $info['title'] ?? '',
];
}
}
}
return view('/developer/plugin/import', ['dirs' => $dirs]);
}
/**
* 插件生成
* @return \support\Response
* @throws \Psr\SimpleCache\InvalidArgumentException
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function build(): \support\Response
{
$id = input('id');
$data = $this->model->where('id', $id)->withoutField('id,status,create_time')->find();
if (empty($data)) {
return $this->error('插件不存在');
}
$name = $data['name'];
$pluginPath = plugin_path($name);
$pluginClass = ucfirst($name);
if ($data['import'] || is_dir($pluginPath)) {
return $this->error('请勿重复生成插件');
}
$pluginZip = __DIR__ . '/stubs/plugin_init.stub';
$sourceFiles = plugin_path('plugin_init');
$replaces = ['controller', 'model', 'validate', 'view'];
$source = ['{name}', '{pluginClass}', '{title}', '{icon}'];
$destVars = [$name, $pluginClass, $data['title'], $data['icon']];
try {
ZipArchives::unzip($pluginZip, plugin_path());
foreach ($replaces as $item) {
$namespace = $sourceFiles . 'app/admin' . DIRECTORY_SEPARATOR . $item;
$tempPath = $namespace . DIRECTORY_SEPARATOR . 'demo';
$tempPath = str_replace('\\', '/', $tempPath);
if (is_dir($tempPath)) {
$isFirst = false;
$targetPath = $namespace . DIRECTORY_SEPARATOR . $name;
rename($tempPath, $targetPath);
$targetFile = $targetPath . DIRECTORY_SEPARATOR . 'Index.php';
if (!is_file($targetFile)) {
$isFirst = true;
$targetFile = $targetPath . DIRECTORY_SEPARATOR . ($item === 'view' ? '/index/index.html' : 'Demo.php');
}
$content = str_replace($source, $destVars, read_file($targetFile));
if (!empty($content)) {
write_file($targetFile, $content);
}
if ($isFirst && $content && $item !== 'view') {
rename($targetFile, $targetPath . DIRECTORY_SEPARATOR . $pluginClass . '.php');
}
}
}
$array = [$sourceFiles . 'app/index/controller/', $sourceFiles];
foreach ($array as $item) {
$file = $item . 'Demo.php';
$content = str_replace($source, $destVars, read_file($file));
write_file($file, $content);
rename($file, $item . $pluginClass . '.php');
}
foreach (['README.md', 'function.php', 'Upgrade.php', 'config.html'] as $item) {
$file = $sourceFiles . $item;
$content = str_replace($source, $destVars, read_file($file));
write_file($file, $content);
}
// 处理静态文件
$staticFile = [
$sourceFiles . 'app/index/view',
$sourceFiles . 'public/static/plugin',
$sourceFiles . 'public/static/system/plugin'
];
foreach ($staticFile as $index => $item) {
$file = $item . '/demo';
if (!$index) {
$elem = $file . DIRECTORY_SEPARATOR . 'index.html';
write_file($elem, str_replace('{pluginClass}', $pluginClass, read_file($elem)));
}
rename($item . '/' . 'demo', $item . '/' . $name);
}
$configPath = $sourceFiles . 'config.json';
$data = $data->toArray();
$data['rewrite'] = [];
$data['extends'] = [
'title' => $data['title'],
];
$data['area'] = ['600px', '650px',];
write_file($configPath, json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));
rename($sourceFiles, $pluginPath);
if ($data['menu']) {
AdminRules::createMenu($this->createPluginMenu($data), $name);
}
// 启用插件
foreach (File::getCopyDirs($name) as $copyDir) {
copydirs($copyDir, root_path() . str_replace($pluginPath, '', $copyDir));
}
AdminRules::enabled($name);
set_plugin_config($name, ['status' => 1]);
} catch (\Throwable $th) {
@recursive_delete($pluginPath);
@recursive_delete($sourceFiles);
return $this->error($th->getMessage());
}
return $this->success('插件初始化成功');
}
/**
* 插件打包
* @return \support\Response
* @throws Exception
*/
public function package(): \support\Response
{
$id = input('id');
$data = $this->model->withoutField('id, import, create_time')->where('id', $id)->find();
if (empty($data)) {
return $this->error('插件不存在,打包失败!');
}
$name = $data['name'];
$pluginClass = ucfirst($name);
$pluginPath = plugin_path($name);
if (!is_dir($pluginPath)) {
return $this->error('插件未生成!');
}
try {
$files = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator(root_path('app'), FilesystemIterator::SKIP_DOTS),
\RecursiveIteratorIterator::CHILD_FIRST
);
foreach ($files as $fileinfo) {
if ($fileinfo->isFile()) {
$filePath = $fileinfo->getPathName();
$content = read_file($filePath);
if (preg_match('/<!--' . $pluginClass . '-->/iU', $content, $match)) {
$match = $match[0];
$filePath = str_replace('\\', '/', $filePath);
copy_file($filePath, str_replace(root_path(), $pluginPath, $filePath));
}
}
}
$statics = [
root_path('public/static/plugin/' . $name),
root_path('public/static/system/plugin/' . $name),
];
foreach ($statics as $item) {
if (is_dir($item)) {
@copydirs($item, str_replace(root_path(), $pluginPath, $item));
}
}
if (!empty($data['menu'])) {
arr2file($pluginPath . '/data/menu.php', AdminRules::export($name));
}
// 加载配置文件
$configPath = $pluginPath . 'config.json';
$configInfo = json_decode(read_file($configPath), true);
$configPack = array_merge($configInfo, $data->toArray());
unset($configPack['path']);
unset($configPack['filePath']);
if (empty($configPack['rewrite'])) {
$configPack['rewrite'] = [];
}
write_file($configPath, json_encode($configPack, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));
$pluginZip = root_path('public/upload') . $name . '-' . $data['version'] . '.zip';
ZipArchives::compression($pluginZip, $pluginPath);
$url = request()->domain() . str_replace(root_path() . 'public', '', $pluginZip);
} catch (\Throwable $th) {
return $this->error($th->getMessage());
}
return $this->success('插件打包成功', $url);
}
/**
* 基础菜单项
* @param $plugin
* @return array
*/
public function createPluginMenu($plugin): array
{
return [
[
'title' => $plugin['title'],
'router' => '/' . $plugin['name'] . '/Index',
'icon' => $plugin['icon'],
'auth' => '1',
'children' => [
['router' => '/' . $plugin['name'] . '/Index/index', 'title' => '查看'],
['router' => '/' . $plugin['name'] . '/Index/add', 'title' => '添加'],
['router' => '/' . $plugin['name'] . '/Index/edit', 'title' => '编辑'],
['router' => '/' . $plugin['name'] . '/Index/del', 'title' => '删除'],
['router' => '/' . $plugin['name'] . '/Index/status', 'title' => '状态']
]
],
];
}
}