* 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('//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' => '状态'] ] ], ]; } }