TaoLer/app/admin/controller/addon/Addons.php
2023-03-16 22:42:05 +08:00

649 lines
24 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace app\admin\controller\addon;
use app\common\controller\AdminController;
use app\common\lib\SqlFile;
use app\common\lib\Zip;
use think\Exception;
use think\facade\View;
use think\facade\Request;
use think\facade\Config;
use app\admin\model\AuthRule;
use app\admin\model\Addons as AddonsModel;
use think\response\Json;
use Symfony\Component\VarExporter\VarExporter;
use taoler\com\Files;
use app\common\lib\facade\HttpHelper;
use app\common\lib\FileHelper;
class Addons extends AdminController
{
/**
* 浏览插件
*
*/
public function index()
{
// if(Request::isAjax()) {
// $data = Request::param();
// if(!isset($data['type'])) $data['type'] = 'onlineAddons';
// if(!isset($data['selector'])) $data['selector'] = 'all';
// return $this->getList($data);
// }
return View::fetch();
}
/**
* 插件动态列表
* @param $data
* @return Json
*/
public function list()
{
$param = Request::param();
$data = ['page' => $param['page'] ?? 1, 'limit' => $param['limit'] ?? 10, 'type' => $param['type'] ?? 'all'];
$res = [];
//本地插件列表
$localAddons = Files::getDirName('../addons/');
if($data['type'] == 'installed') {
$count = count($localAddons); // 安装总数
// 已安装
if ($count) {
$res = ['code' => 0, 'msg' => 'ok', 'count' => $count];
// 数组分组
$arr = array_chunk($localAddons, $data['limit']);
// 选中的页码数组
$arrAddon = $arr[$data['page'] - 1];
// $data数据
foreach ($arrAddon as $k => $v) {
$info_file = '../addons/' . $v . '/info.ini';
$info = parse_ini_file($info_file);
$info['show'] = $info['status'] ? '启用' : '禁用';
$info['install'] = $info['status'] ? '是' : '否';
$res['data'][] = $info;
}
return json($res);
}
return json(['code' => -1, 'msg' => '没有安装任何插件']);
}
// 在线插件
$response = HttpHelper::withHost()->get('/v1/addonlist', $data);
$addons = $response->toJson();
if($response->ok()) {
$res = ['code' => 0, 'msg' => 'ok', 'count' => $addons->count];
// $data数据 与本地文件对比
foreach($addons->data as $v){
if(in_array($v->name, $localAddons)) {
$info = get_addons_info($v->name);
//已安装
$v->isInstall = 1;
//判断是否有新版本
if($v->version > $info['version']) $v->have_newversion = 1;
$v->price = $v->price ? $v->price : '免费';
}
$res['data'][] = $v;
};
return json($res);
}
return json(['code' => -1, 'msg' => '未获取到服务器信息']);
}
/**
* 插件动态列表
* @param $data
* @return Json
*/
public function getList()
{
$data = Request::param();
if(!isset($data['type'])) $data['type'] = 'onlineAddons';
if(!isset($data['selector'])) $data['selector'] = 'all';
$res = [];
//本地插件列表
$addonsList = Files::getDirName('../addons/');
$response = HttpHelper::withHost()->get('/v1/addons');
$addons = $response->toJson();
switch($data['type']){
//已安装
case 'installed':
if($addonsList){
$res = ['code'=>0,'msg'=>'','count'=>5];
$res['col'] = [
['type' => 'numbers'],
['field' => 'name','title'=> '插件', 'width'=> 120],
['field'=> 'title','title'=> '标题', 'width'=> 100],
['field'=> 'version','title'=> '版本', 'templet' => '<div>{{d.version}}</div>', 'width'=> 60],
['field' => 'author','title'=> '作者', 'width'=> 80],
['field' => 'description','title'=> '简介', 'minWidth'=> 200],
['field' => 'install','title'=> '安装', 'width'=> 100],
['field' => 'ctime','title'=> '到期时间', 'width'=> 100],
['field' => 'status','title'=> '状态', 'width'=> 95, 'templet' => '#buttonStatus'],
['title' => '操作', 'width'=> 150, 'align'=>'center', 'toolbar'=> '#addons-installed-tool']
];
// $data数据
foreach($addonsList as $v){
$info_file = '../addons/'.$v.'/info.ini';
$info = parse_ini_file($info_file);
$info['show'] = $info['status'] ? '启用' : '禁用';
$info['install'] = $info['status'] ? '是' : '否';
$res['data'][] = $info;
}
} else {
$res = ['code'=>-1,'msg'=>'没有安装任何插件'];
}
break;
//在线全部
case 'onlineAddons':
if($response->ok()) {
$res['code'] = 0;
$res['msg'] = '';
$res['count'] = count($addons->data);
$res['col'] = [
['type' => 'numbers'],
['field' => 'title','title'=> '插件', 'width'=> 200],
['field' => 'description','title'=> '简介', 'minWidth'=> 200],
['field' => 'author','title'=> '作者', 'width'=> 100],
['field' => 'price','title'=> '价格(元)','width'=> 85],
['field' => 'downloads','title'=> '下载', 'width'=> 70],
['field' => 'version','title'=> '版本', 'templet' => '<div>{{d.version}} {{# if(d.have_newversion == 1){ }}<span class="layui-badge-dot"></span>{{# } }}</div>','width'=> 75],
['field' => 'status','title'=> '在线', 'width'=> 70],
['title' => '操作', 'width'=> 150, 'align'=>'center', 'toolbar'=> '#addons-tool']
];
// $data数据 与本地文件对比
foreach($addons->data as $v){
switch ($data['selector']) {
case 'free':
if($v->price == 0) {
if(in_array($v->name,$addonsList)) {
$info = get_addons_info($v->name);
//已安装
$v->isInstall = 1;
//判断是否有新版本
if($v->version > $info['version']) $v->have_newversion = 1;
$v->price = $v->price ? $v->price : '免费';
}
$res['data'][] = $v;
}
break;
case 'pay':
if($v->price > 0) {
if(in_array($v->name,$addonsList)) {
$info = get_addons_info($v->name);
//已安装
$v->isInstall = 1;
//判断是否有新版本
if($v->version > $info['version']) $v->have_newversion = 1;
$v->price = $v->price ? $v->price : '免费';
}
$res['data'][] = $v;
}
break;
case 'all':
if(in_array($v->name,$addonsList)) {
$info = get_addons_info($v->name);
//已安装
$v->isInstall = 1;
//判断是否有新版本
if($v->version > $info['version']) $v->have_newversion = 1;
$v->price = $v->price ? $v->price : '免费';
}
$res['data'][] = $v;
break;
}
};
} else {
$res = ['code' => -1, 'msg' => '未获取到服务器信息'];
}
break;
}
return json($res);
}
/**
* 安装&升级,
* @param array $data
* @param bool $type true执行sql,false升级不执行sql
* @return Json
*/
public function install(array $data = [], bool $type = true)
{
$data = Request::only(['name','version','uid','token']) ?? $data;
// 接口
$response = HttpHelper::withHost()->post('/v1/getaddons',$data)->toJson();
if($response->code < 0) return json($response);
//版本判断,是否能够安装?
$addInstalledVersion = get_addons_info($data['name']);
if(!empty($addInstalledVersion)){
$verRes = version_compare($data['version'],$addInstalledVersion['version'],'>');
if(!$verRes){
return json(['code'=>-1,'msg'=>'不能降级,请选择正确版本']);
}
//$tpl_ver_res = version_compare($addInstalledVersion['template_version'], config('taoler.template_version'),'<');
}
$file_url = $response->addons_src;
//判断远程文件是否可用存在
$header = get_headers($file_url, true);
if(!isset($header[0]) && (strpos($header[0], '200') || strpos($header[0], '304'))){
return json(['code'=>-1,'msg'=>'获取远程文件失败']);
}
//把远程文件放入本地
//拼接路径
$addons_dir = FileHelper::getDirPath(root_path() . 'runtime' . DS . 'addons');
if(!is_dir($addons_dir)) Files::mkdirs($addons_dir);
$package_file = $addons_dir . $data['name'] . '.zip'; //升级的压缩包文件路径
$cpfile = copy($file_url, $package_file);
if(!$cpfile) return json(['code'=>-1,'msg'=>'下载升级文件失败']);
$uzip = new Zip();
$zipDir = strstr($package_file, '.zip',true); //返回文件名后缀前的字符串
$zipPath = FileHelper::getDirPath($zipDir); //转换为带/的路径 压缩文件解压到的路径
$unzip_res = $uzip->unzip($package_file, $zipPath, true);
if(!$unzip_res) return json(['code'=>-1,'msg'=>'解压失败']);
unlink($package_file);
//升级插件
//升级前的写入文件权限检查
$allUpdateFiles = Files::getAllFile($zipPath);
if (empty($allUpdateFiles)) return json(['code' => -1, 'msg' => '无可更新文件。']);
$checkString = '';
foreach ($allUpdateFiles as $updateFile) {
$coverFile = ltrim(str_replace($zipPath, '', $updateFile), DIRECTORY_SEPARATOR);
$dirPath = dirname('../'.$coverFile);
if (file_exists('../'.$coverFile)) {
if (!is_writable('../'.$coverFile)) $checkString .= $coverFile . '&nbsp;[<span class="text-red">' . '无写入权限' . '</span>]<br>';
} else {
if (!is_dir($dirPath)) @mkdir($dirPath, 0777, true);
if (!is_writable($dirPath)) $checkString .= $dirPath . '&nbsp;[<span class="text-red">' . '无写入权限' . '</span>]<br>';
}
}
if (!empty($checkString)) return json(['code' => -1, 'msg' => $checkString]);
try {
// 拷贝文件
FileHelper::copyDir(root_path() . 'runtime' . DS . 'addons' . DS . $data['name'] . DS, root_path());
// $type判断是安装还是升级
if($type) {
// 执行数据库
$sqlInstallFile = root_path(). 'addons' . DS . $data['name'] . DS . 'install.sql';
if(file_exists($sqlInstallFile)) {
SqlFile::dbExecute($sqlInstallFile);
}
}
//安装菜单
//$menu = get_addons_menu($data['name']);
$menuFile = app()->getRootPath() . 'addons' . DS . $data['name'] . DS . 'menu.php';
if(file_exists($menuFile)){
$menu = include $menuFile;
} else {
$menu = [];
}
if(!empty($menu)){
if(isset($menu['is_nav']) && $menu['is_nav'] == 1){
$pid = 0;
}else{
$pid = AuthRule::where('name','addons')->value('id');
}
$menu_arr[] = $menu['menu'];
$this->addAddonMenu($menu_arr, (int)$pid,1);
}
$class = get_addons_instance($data['name']);
//执行插件安装
$class->install();
set_addons_info($data['name'],['status' => 1,'install' => 1]);
} catch (\Exception $e) {
return json(['code'=>-1,'msg'=> $e->getMessage()]);
}
Files::delDirAndFile('../runtime/addons/'.$data['name'] . DS);
$msg = $type ? '插件安装成功!' : '插件升级成功!';
return json(['code' => 0, 'msg' => $msg]);
}
/**
* 卸载插件
* @param string $name
* @return Json
* @throws \Exception
*/
public function uninstall(string $name = '')
{
$name = input('name') ?? $name;
// 执行插件卸载
$class = get_addons_instance($name);
$class->uninstall();
// 卸载菜单
$menu = get_addons_menu($name);
if(!empty($menu)){
$menu_arr[] = $menu['menu'];
$this->delAddonMenu($menu_arr);
}
try {
//卸载插件数据库
$sqlUninstallFile = root_path().'addons/'.$name.'/uninstall.sql';
if(file_exists($sqlUninstallFile)) {
SqlFile::dbExecute($sqlUninstallFile);
}
// 插件addons下目录
$addonsDir = root_path() . 'addons' . DS . $name . DS;
// 插件管理后台目录
$admin_controller = app_path() . 'controller' . DS . $name . DS;
$admin_model = app_path() . 'model' . DS . $name . DS;
$admin_view = app_path() . 'view' . DS . $name . DS;
$admin_validate = app_path() . 'validate' . DS . $name . DS;
// 插件静态资源目录
$addon_public = public_path() . 'addons' . DS . $name . DS;
if(file_exists($addonsDir)) Files::delDir($addonsDir);
if(file_exists($admin_controller)) Files::delDir($admin_controller);
if(file_exists($admin_model)) Files::delDir($admin_model);
if(file_exists($admin_view)) Files::delDir($admin_view);
if(file_exists($admin_validate)) Files::delDir($admin_validate);
if(file_exists($addon_public)) Files::delDir($addon_public);
} catch (\Exception $e) {
return json(['code' => -1, 'msg' => $e->getMessage()]);
}
return json(['code' => 0, 'msg' => '插件卸载成功']);
}
/**
* 升级插件
* @return Json
* @throws \Exception
*/
public function upgrade()
{
$data = Request::only(['name','version','uid','token']);
// 获取配置信息
$config = get_addons_config($data['name']);
// 卸载插件
$class = get_addons_instance($data['name']);
$class->uninstall();
//$this->uninstall($data['name']);
// 卸载菜单
$menu = get_addons_menu($data['name']);
if(!empty($menu)){
$menu_arr[] = $menu['menu'];
$this->delAddonMenu($menu_arr);
}
try {
// 升级安装第二个type参数false为升级true安装
$res = $this->install($data,false);
// 升级sql
$sqlUpdateFile = root_path().'addons/'.$data['name'].'/update.sql';
if(file_exists($sqlUpdateFile)) {
SqlFile::dbExecute($sqlUpdateFile);
}
// 恢复配置
if(!empty($config)) {
set_addons_config($data['name'], $config);
}
// 写入版本号
set_addons_info($data['name'],['version' =>$data['version']]);
} catch (\Exception $e) {
return json(['code' => -1, 'msg' => $e->getMessage()]);
}
return $res;
}
/**
* 启用禁用插件
* @return Json
* @throws Exception
*/
public function check(){
$name = input('name');
$info = get_addons_info($name);
try{
$arr = ['status' => $info['status'] ? 0 :1];
set_addons_info($name,$arr);
$class = get_addons_instance($name);
if($arr['status']) {
$res = ['code'=>0,'msg'=>'启用成功'];
} else {
$res = ['code'=>0,'msg'=>'已被禁用'];
}
$info['status']==1 ?$class->enabled():$class->disabled();
} catch (\Exception $e) {
throw new Exception($e->getMessage());
}
return json($res);
}
/**
* 配置插件
* @param $name
* @return string|Json
* @throws \Exception
*/
public function config($name)
{
$name = input('name');
$config = get_addons_config($name);
if(empty($config)) return json(['code'=>-1,'msg'=>'无配置项!无需操作']);
if(Request::isAjax()){
$params = Request::param('params/a',[],'trim');
if ($params) {
foreach ($config as $k => &$v) {
if (isset($params[$k])) {
if ($v['type'] == 'array') {
$arr = [];
$params[$k] = is_array($params[$k]) ? $params[$k] :[];
foreach ($params[$k]['key'] as $kk=>$vv){
$arr[$vv] = $params[$k]['value'][$kk];
}
$params[$k] = $arr;
$value = $params[$k];
$v['content'] = $value;
$v['value'] = $value;
} else {
$value = $params[$k];
}
$v['value'] = $value;
}
}
unset($v);
set_addons_config($name,$config);
}
return json(['code'=>0,'msg'=>'配置成功!']);
}
//模板引擎初始化
$view = ['formData'=>$config,'title'=>'title'];
View::assign($view);
$configFile = root_path() . 'addons' . DS . $name . DS . 'config.html';
$viewFile = is_file($configFile) ? $configFile : '';
return View::fetch($viewFile);
}
/**
* 添加菜单
* @param array $menu
* @param int $pid
* @param int $type
* @return void
* @throws \Exception
*/
public function addAddonMenu(array $menu,int $pid = 0, int $type = 1)
{
foreach ($menu as $v){
$hasChild = isset($v['sublist']) && $v['sublist'] ? true : false;
try {
$v['pid'] = $pid;
$v['name'] = trim($v['name'],'/');
$v['type'] = $type;
$menu = AuthRule::withTrashed()->where('name',$v['name'])->find();
if($menu){
$menu->restore();
} else {
$menu = AuthRule::create($v);
}
if ($hasChild) {
$this->addAddonMenu($v['sublist'], $menu->id,$type);
}
} catch (\Exception $e) {
throw new \Exception($e->getMessage());
}
}
}
/**
* 循环删除菜单
* @param array $menu
* @param string $module
* @return void
* @throws \Exception
*/
public function delAddonMenu(array $menu,string $module = 'addon')
{
foreach ($menu as $k=>$v){
$hasChild = isset($v['sublist']) && $v['sublist'] ? true : false;
try {
$v['name'] = trim($v['name'],'/');
$menu_rule = AuthRule::withTrashed()->where('name',$v['name'])->find();
if(!is_null($menu_rule)){
$menu_rule->delete(true);
if ($hasChild) {
$this->delAddonMenu($v['sublist']);
}
}
} catch (\Exception $e) {
throw new \Exception($e->getMessage());
}
}
}
/**
* 用户登录
* @return mixed|Json
*/
public function userLogin()
{
$response = HttpHelper::withHost()->post('/v1/user/login', Request::param())->toJson();
return json($response);
}
/**
* 订单
* @return string|Json
*/
public function pay()
{
$data = Request::only(['id','name','version','uid','price']);
// $url = $this->getSystem()['api_url'].'/v1/createOrder';
// $order = Api::urlPost($url,$data);
$response = HttpHelper::withHost()->post('/v1/createOrder', $data);
if ($response->ok()) {
// $orderData = json_decode(json_encode($response->toJson()->data),TRUE);
View::assign('orderData',$response->toArray()['data']);
return View::fetch();
} else {
return json($response->toJson());
}
}
/**
* 支付查询
* @return Json
*/
public function isPay()
{
$param = Request::only(['name','userinfo']);
$data = [
'name'=>$param['name'],
'uid'=> $param['userinfo']['uid'],
];
$response = HttpHelper::withHost()->post('/v1/ispay', $data)->toJson();
return json($response);
}
/**
* 显示创建资源表单页.
*
* @return \think\Response
*/
public function add()
{
//添加版本
if(Request::isAjax()){
$data = Request::param();
$result = AddonsModel::create($data);
if($result){
$res = ['code'=>0,'msg'=>'添加成功'];
}else{
$res = ['code'=>-1,'msg'=>'添加失败'];
}
return json($res);
}
return View::fetch();
}
/**
* 上传插件文件zip
* @return Json
*/
public function uploadZip()
{
$id = Request::param();
$file = request()->file('file');
try {
validate(['file'=>'filesize:2048|fileExt:zip,rar,7z'])
->check(array($file));
$saveName = \think\facade\Filesystem::disk('public')->putFile('addons',$file);
} catch (\think\exception\ValidateException $e) {
return json(['code' => -1,'msg' => $e->getMessage()]);
}
$upload = Config::get('filesystem.disks.public.url');
if($saveName){
$name_path =str_replace('\\',"/",$upload.'/'.$saveName);
$res = ['code'=>0,'msg'=>'插件上传成功','src'=>$name_path];
} else {
$res = ['code'=>-1,'msg'=>'上传错误'];
}
return json($res);
}
/**
* 上传接口
*
* @return void
*/
public function uploads()
{
$type = Request::param('type');
return $this->uploadFiles($type);
}
}