* @return mixed */ public function index($group = 'local', $type = '') { // 配置分组信息 $list_group = ['local' => '本地模块']; $tab_list = []; foreach ($list_group as $key => $value) { $tab_list[$key]['title'] = $value; $tab_list[$key]['url'] = url('index', ['group' => $key]); } // 监听tab钩子 Hook::listen('module_index_tab_list', $tab_list); switch ($group) { case 'local': // 查询条件 $keyword = $this->request->get('keyword', ''); if (input('?param.status') && input('param.status') != '_all') { $status = input('param.status'); } else { $status = ''; } $ModuleModel = new ModuleModel(); $result = $ModuleModel->getAll($keyword, $status); if ($result['modules'] === false) { $this->error($ModuleModel->getError()); } $type_show = Cache::get('module_type_show'); $type_show = $type != '' ? $type : ($type_show == false ? 'block' : $type_show); Cache::set('module_type_show', $type_show); $type = $type_show == 'block' ? 'list' : 'block'; $this->assign('page_title', '模块管理'); $this->assign('modules', $result['modules']); $this->assign('total', $result['total']); $this->assign('tab_nav', ['tab_list' => $tab_list, 'curr_tab' => $group]); $this->assign('type', $type); return $this->fetch(); break; case 'online': return '

正在建设中...

'; break; default: $this->error('非法操作'); } } /** * 安装模块 * @param string $name 模块标识 * @param int $confirm 是否确认 * @author 蔡伟明 <314013107@qq.com> * @throws \think\Exception * @throws \think\exception\PDOException */ public function install($name = '', $confirm = 0) { // 设置最大执行时间和内存大小 ini_set('max_execution_time', '0'); ini_set('memory_limit', '1024M'); if ($name == '') $this->error('模块不存在!'); if ($name == 'admin' || $name == 'user') $this->error('禁止操作系统核心模块!'); // 模块配置信息 $module_info = ModuleModel::getInfoFromFile($name); if ($confirm == 0) { $need_module = []; $need_plugin = []; $table_check = []; // 检查模块依赖 if (isset($module_info['need_module']) && !empty($module_info['need_module'])) { $need_module = $this->checkDependence('module', $module_info['need_module']); } // 检查插件依赖 if (isset($module_info['need_plugin']) && !empty($module_info['need_plugin'])) { $need_plugin = $this->checkDependence('plugin', $module_info['need_plugin']); } // 检查数据表 if (isset($module_info['tables']) && !empty($module_info['tables'])) { foreach ($module_info['tables'] as $table) { if (Db::query("SHOW TABLES LIKE '".config('database.prefix')."{$table}'")) { $table_check[] = [ 'table' => config('database.prefix')."{$table}", 'result' => '存在同名' ]; } else { $table_check[] = [ 'table' => config('database.prefix')."{$table}", 'result' => '' ]; } } } $this->assign('need_module', $need_module); $this->assign('need_plugin', $need_plugin); $this->assign('table_check', $table_check); $this->assign('name', $name); $this->assign('page_title', '安装模块:'. $name); return $this->fetch(); } // 执行安装文件 $install_file = realpath(Env::get('app_path').$name.'/install.php'); if (file_exists($install_file)) { @include($install_file); } // 执行安装模块sql文件 $sql_file = realpath(Env::get('app_path').$name.'/sql/install.sql'); if (file_exists($sql_file)) { if (isset($module_info['database_prefix']) && !empty($module_info['database_prefix'])) { $sql_statement = Sql::getSqlFromFile($sql_file, false, [$module_info['database_prefix'] => config('database.prefix')]); } else { $sql_statement = Sql::getSqlFromFile($sql_file); } if (!empty($sql_statement)) { foreach ($sql_statement as $value) { try{ Db::execute($value); }catch(\Exception $e){ $this->error('导入SQL失败,请检查install.sql的语句是否正确'); } } } } // 添加菜单 $menus = ModuleModel::getMenusFromFile($name); if (is_array($menus) && !empty($menus)) { if (false === $this->addMenus($menus, $name)) { $this->error('菜单添加失败,请重新安装'); } } // 检查是否有模块设置信息 if (isset($module_info['config']) && !empty($module_info['config'])) { $module_info['config'] = json_encode(parse_config($module_info['config'])); } // 检查是否有模块授权配置 if (isset($module_info['access']) && !empty($module_info['access'])) { $module_info['access'] = json_encode($module_info['access']); } // 检查是否有行为规则 if (isset($module_info['action']) && !empty($module_info['action'])) { $ActionModel = new ActionModel; if (!$ActionModel->saveAll($module_info['action'])) { MenuModel::where('module', $name)->delete(); $this->error('行为添加失败,请重新安装'); } } // 将模块信息写入数据库 $ModuleModel = new ModuleModel($module_info); $allowField = ['name','title','icon','description','author','author_url','config','access','version','identifier','status']; if ($ModuleModel->allowField($allowField)->save()) { // 复制静态资源目录 File::copy_dir(Env::get('app_path'). $name. '/public', Env::get('root_path'). 'public'); // 删除静态资源目录 File::del_dir(Env::get('app_path'). $name. '/public'); cache('modules', null); cache('module_all', null); // 记录行为 action_log('module_install', 'admin_module', 0, UID, $module_info['title']); $this->success('模块安装成功', 'index'); } else { MenuModel::where('module', $name)->delete(); $this->error('模块安装失败'); } } /** * 卸载模块 * @param string $name 模块名 * @param int $confirm 是否确认 * @author 蔡伟明 <314013107@qq.com> * @return mixed * @throws \think\Exception * @throws \think\exception\PDOException */ public function uninstall($name = '', $confirm = 0) { if ($name == '') $this->error('模块不存在!'); if ($name == 'admin') $this->error('禁止操作系统模块!'); // 模块配置信息 $module_info = ModuleModel::getInfoFromFile($name); if ($confirm == 0) { $this->assign('name', $name); $this->assign('page_title', '卸载模块:'. $name); return $this->fetch(); } // 执行卸载文件 $uninstall_file = realpath(Env::get('app_path').$name.'/uninstall.php'); if (file_exists($uninstall_file)) { @include($uninstall_file); } // 执行卸载模块sql文件 $clear = $this->request->get('clear'); if ($clear == 1) { $sql_file = realpath(Env::get('app_path').$name.'/sql/uninstall.sql'); if (file_exists($sql_file)) { if (isset($module_info['database_prefix']) && !empty($module_info['database_prefix'])) { $sql_statement = Sql::getSqlFromFile($sql_file, false, [$module_info['database_prefix'] => config('database.prefix')]); } else { $sql_statement = Sql::getSqlFromFile($sql_file); } if (!empty($sql_statement)) { foreach ($sql_statement as $sql) { try{ Db::execute($sql); }catch(\Exception $e){ $this->error('卸载失败,请检查uninstall.sql的语句是否正确'); } } } } } // 删除菜单 if (false === MenuModel::where('module', $name)->delete()) { $this->error('菜单删除失败,请重新卸载'); } // 删除授权信息 if (false === Db::name('admin_access')->where('module', $name)->delete()) { $this->error('删除授权信息失败,请重新卸载'); } // 删除行为规则 if (false === Db::name('admin_action')->where('module', $name)->delete()) { $this->error('删除行为信息失败,请重新卸载'); } // 删除模块信息 if (ModuleModel::where('name', $name)->delete()) { // 复制静态资源目录 File::copy_dir(Env::get('root_path'). 'public/static/'. $name, Env::get('app_path').$name.'/public/static/'. $name); // 删除静态资源目录 File::del_dir(Env::get('root_path'). 'public/static/'. $name); cache('modules', null); cache('module_all', null); // 记录行为 action_log('module_uninstall', 'admin_module', 0, UID, $module_info['title']); $this->success('模块卸载成功', 'index'); } else { $this->error('模块卸载失败'); } } /** * 更新模块配置 * @param string $name 模块名 * @author 蔡伟明 <314013107@qq.com> */ public function update($name = '') { $name == '' && $this->error('缺少模块名!'); $Module = ModuleModel::get(['name' => $name]); !$Module && $this->error('模块不存在,或未安装'); // 模块配置信息 $module_info = ModuleModel::getInfoFromFile($name); unset($module_info['name']); // 检查是否有模块设置信息 if (isset($module_info['config']) && !empty($module_info['config'])) { $module_info['config'] = json_encode(parse_config($module_info['config'])); } else { $module_info['config'] = ''; } // 检查是否有模块授权配置 if (isset($module_info['access']) && !empty($module_info['access'])) { $module_info['access'] = json_encode($module_info['access']); } else { $module_info['access'] = ''; } // 更新模块信息 if (false !== $Module->save($module_info)) { $this->success('模块配置更新成功'); } else { $this->error('模块配置更新失败,请重试'); } } /** * 导出模块 * @param string $name 模块名 * @author 蔡伟明 <314013107@qq.com> * @throws \think\db\exception\DataNotFoundException * @throws \think\db\exception\ModelNotFoundException * @throws \think\exception\DbException */ public function export($name = '') { if ($name == '') $this->error('缺少模块名'); $export_data = $this->request->get('export_data', ''); if ($export_data == '') { $this->assign('page_title', '导出模块:'. $name); return $this->fetch(); } // 模块导出目录 $module_dir = Env::get('root_path'). 'export/module/'. $name; // 删除旧的导出数据 if (is_dir($module_dir)) { File::del_dir($module_dir); } // 复制模块目录到导出目录 File::copy_dir(Env::get('app_path'). $name, $module_dir); // 复制静态资源目录 File::copy_dir(Env::get('root_path'). 'public/static/'. $name, $module_dir.'/public/static/'. $name); // 模块本地配置信息 $module_info = ModuleModel::getInfoFromFile($name); // 检查是否有模块设置信息 if (isset($module_info['config'])) { $db_config = ModuleModel::where('name', $name)->value('config'); $db_config = json_decode($db_config, true); // 获取最新的模块设置信息 $module_info['config'] = set_config_value($module_info['config'], $db_config); } // 检查是否有模块行为信息 $action = Db::name('admin_action')->where('module', $name)->field('module,name,title,remark,rule,log,status')->select(); if ($action) { $module_info['action'] = $action; } // 表前缀 $module_info['database_prefix'] = config('database.prefix'); // 生成配置文件 if (false === $this->buildInfoFile($module_info, $name)) { $this->error('模块配置文件创建失败,请重新导出'); } // 获取模型菜单并导出 $fields = 'id,pid,title,icon,url_type,url_value,url_target,online_hide,sort,status'; $menus = MenuModel::getMenusByGroup($name, $fields); if (false === $this->buildMenuFile($menus, $name)) { $this->error('模型菜单文件创建失败,请重新导出'); } // 导出数据库表 if (isset($module_info['tables']) && !empty($module_info['tables'])) { if (!is_dir($module_dir. '/sql')) { mkdir($module_dir. '/sql', 644, true); } if (!Database::export($module_info['tables'], $module_dir. '/sql/install.sql', config('database.prefix'), $export_data)) { $this->error('数据库文件创建失败,请重新导出'); } if (!Database::exportUninstall($module_info['tables'], $module_dir. '/sql/uninstall.sql', config('database.prefix'))) { $this->error('数据库文件创建失败,请重新导出'); } } // 记录行为 action_log('module_export', 'admin_module', 0, UID, $module_info['title']); // 打包下载 $archive = new PHPZip; return $archive->ZipAndDownload($module_dir, $name); } /** * 创建模块菜单文件 * @param array $menus 菜单 * @param string $name 模块名 * @author 蔡伟明 <314013107@qq.com> * @return int */ private function buildMenuFile($menus = [], $name = '') { $menus = Tree::toLayer($menus); // 美化数组格式 $menus = var_export($menus, true); $menus = preg_replace("/(\d+|'id'|'pid') =>(.*)/", '', $menus); $menus = preg_replace("/'child' => (.*)(\r\n|\r|\n)\s*array/", "'child' => $1array", $menus); $menus = str_replace(['array (', ')'], ['[', ']'], $menus); $menus = preg_replace("/(\s*?\r?\n\s*?)+/", "\n", $menus); $content = << * @return int */ private function buildInfoFile($info = [], $name = '') { // 美化数组格式 $info = var_export($info, true); $info = preg_replace("/'(.*)' => (.*)(\r\n|\r|\n)\s*array/", "'$1' => array", $info); $info = preg_replace("/(\d+) => (\s*)(\r\n|\r|\n)\s*array/", "array", $info); $info = preg_replace("/(\d+ => )/", "", $info); $info = preg_replace("/array \((\r\n|\r|\n)\s*\)/", "[)", $info); $info = preg_replace("/array \(/", "[", $info); $info = preg_replace("/\)/", "]", $info); $content = << * @throws \think\db\exception\DataNotFoundException * @throws \think\db\exception\ModelNotFoundException * @throws \think\exception\DbException */ public function setStatus($type = '', $record = []) { $ids = input('param.ids'); empty($ids) && $this->error('缺少主键'); $module = ModuleModel::where('id', $ids)->find(); $module['system_module'] == 1 && $this->error('禁止操作系统内置模块'); $status = $type == 'enable' ? 1 : 0; // 将模块对应的菜单禁用或启用 $map = [ 'pid' => 0, 'module' => $module['name'] ]; MenuModel::where($map)->setField('status', $status); if (false !== ModuleModel::where('id', $ids)->setField('status', $status)) { // 记录日志 call_user_func_array('action_log', ['module_'.$type, 'admin_module', 0, UID, $module['title']]); $this->success('操作成功'); } else { $this->error('操作失败'); } } /** * 禁用模块 * @param array $record 行为日志内容 * @author 蔡伟明 <314013107@qq.com> * @throws \think\db\exception\DataNotFoundException * @throws \think\db\exception\ModelNotFoundException * @throws \think\exception\DbException */ public function disable($record = []) { $this->setStatus('disable'); } /** * 启用模块 * @param array $record 行为日志内容 * @author 蔡伟明 <314013107@qq.com> * @throws \think\db\exception\DataNotFoundException * @throws \think\db\exception\ModelNotFoundException * @throws \think\exception\DbException */ public function enable($record = []) { $this->setStatus('enable'); } /** * 添加模型菜单 * @param array $menus 菜单 * @param string $module 模型名称 * @param int $pid 父级ID * @author 蔡伟明 <314013107@qq.com> * @return bool */ private function addMenus($menus = [], $module = '', $pid = 0) { foreach ($menus as $menu) { $data = [ 'pid' => $pid, 'module' => $module, 'title' => $menu['title'], 'icon' => isset($menu['icon']) ? $menu['icon'] : 'fa fa-fw fa-puzzle-piece', 'url_type' => isset($menu['url_type']) ? $menu['url_type'] : 'module_admin', 'url_value' => isset($menu['url_value']) ? $menu['url_value'] : '', 'url_target' => isset($menu['url_target']) ? $menu['url_target'] : '_self', 'online_hide' => isset($menu['online_hide']) ? $menu['online_hide'] : 0, 'status' => isset($menu['status']) ? $menu['status'] : 1 ]; $result = MenuModel::create($data); if (!$result) return false; if (isset($menu['child'])) { $this->addMenus($menu['child'], $module, $result['id']); } } return true; } /** * 检查依赖 * @param string $type 类型:module/plugin * @param array $data 检查数据 * @author 蔡伟明 <314013107@qq.com> * @return array */ private function checkDependence($type = '', $data = []) { $need = []; foreach ($data as $key => $value) { if (!isset($value[3])) { $value[3] = '='; } // 当前版本 if ($type == 'module') { $curr_version = ModuleModel::where('identifier', $value[1])->value('version'); } else { $curr_version = PluginModel::where('identifier', $value[1])->value('version'); } // 比对版本 $result = version_compare($curr_version, $value[2], $value[3]); $need[$key] = [ $type => $value[0], 'identifier' => $value[1], 'version' => $curr_version ? $curr_version : '未安装', 'version_need' => $value[3].$value[2], 'result' => $result ? '' : '' ]; } return $need; } }