123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146 |
- <?php
- namespace think\addons;
- use fast\Http;
- use GuzzleHttp\Client;
- use GuzzleHttp\Exception\TransferException;
- use GuzzleHttp\TransferStats;
- use PhpZip\Exception\ZipException;
- use PhpZip\ZipFile;
- use RecursiveDirectoryIterator;
- use RecursiveIteratorIterator;
- use Symfony\Component\VarExporter\VarExporter;
- use think\Cache;
- use think\Db;
- use think\Exception;
- use think\Log;
- /**
- * 插件服务
- * @package think\addons
- */
- class Service
- {
- /**
- * 插件列表
- */
- public static function addons($params = [])
- {
- $params['domain'] = request()->host(true);
- return self::sendRequest('/addon/index', $params, 'GET');
- }
- /**
- * 检测插件是否购买授权
- */
- public static function isBuy($name, $extend = [])
- {
- $params = array_merge(['name' => $name, 'domain' => request()->host(true)], $extend);
- return self::sendRequest('/addon/isbuy', $params, 'POST');
- }
- /**
- * 检测插件是否授权
- *
- * @param string $name 插件名称
- * @param string $domain 验证域名
- */
- public static function isAuthorization($name, $domain = '')
- {
- $config = self::config($name);
- $request = request();
- $domain = self::getRootDomain($domain ? $domain : $request->host(true));
- if (isset($config['domains']) && isset($config['validations']) && isset($config['licensecodes'])) {
- $index = array_search($domain, $config['domains']);
- if ((in_array($domain, $config['domains']) && in_array(md5(md5($domain) . ($config['licensecodes'][$index] ?? '')), $config['validations'])) || $request->isCli()) {
- return true;
- }
- }
- return false;
- }
- /**
- * 远程下载插件
- *
- * @param string $name 插件名称
- * @param array $extend 扩展参数
- * @return string
- */
- public static function download($name, $extend = [])
- {
- $addonsTempDir = self::getAddonsBackupDir();
- $tmpFile = $addonsTempDir . $name . ".zip";
- try {
- $client = self::getClient();
- $response = $client->get('/addon/download', ['query' => array_merge(['name' => $name], $extend)]);
- $body = $response->getBody();
- $content = $body->getContents();
- if (substr($content, 0, 1) === '{') {
- $json = (array)json_decode($content, true);
- //如果传回的是一个下载链接,则再次下载
- if ($json['data'] && isset($json['data']['url'])) {
- $response = $client->get($json['data']['url']);
- $body = $response->getBody();
- $content = $body->getContents();
- } else {
- //下载返回错误,抛出异常
- throw new AddonException($json['msg'], $json['code'], $json['data']);
- }
- }
- } catch (TransferException $e) {
- throw new Exception("Addon package download failed");
- }
- if ($write = fopen($tmpFile, 'w')) {
- fwrite($write, $content);
- fclose($write);
- return $tmpFile;
- }
- throw new Exception("No permission to write temporary files");
- }
- /**
- * 解压插件
- *
- * @param string $name 插件名称
- * @param string $file 文件路径
- * @return string
- * @throws Exception
- */
- public static function unzip($name, $file = '')
- {
- if (!$name) {
- throw new Exception('Invalid parameters');
- }
- $addonsBackupDir = self::getAddonsBackupDir();
- $file = $file ?: $addonsBackupDir . $name . '.zip';
- // 打开插件压缩包
- $zip = new ZipFile();
- try {
- $zip->openFile($file);
- } catch (ZipException $e) {
- $zip->close();
- throw new Exception('Unable to open the zip file');
- }
- $dir = self::getAddonDir($name);
- if (!is_dir($dir)) {
- @mkdir($dir, 0755);
- }
- // 解压插件压缩包
- try {
- $zip->extractTo($dir);
- } catch (ZipException $e) {
- throw new Exception('Unable to extract the file');
- } finally {
- $zip->close();
- }
- return $dir;
- }
- /**
- * 离线安装
- * @param string $file 插件压缩包
- * @param array $extend 扩展参数
- * @param string $force 是否覆盖
- */
- public static function local($file, $extend = [], $force = false)
- {
- $addonsTempDir = self::getAddonsBackupDir();
- if (!$file || !$file instanceof \think\File) {
- throw new Exception('No file upload or server upload limit exceeded');
- }
- $uploadFile = $file->rule('uniqid')->validate(['size' => 102400000, 'ext' => 'zip,fastaddon'])->move($addonsTempDir);
- if (!$uploadFile) {
- // 上传失败获取错误信息
- throw new Exception(__($file->getError()));
- }
- $tmpFile = $addonsTempDir . $uploadFile->getSaveName();
- $info = [];
- $zip = new ZipFile();
- try {
- // 打开插件压缩包
- try {
- $zip->openFile($tmpFile);
- } catch (ZipException $e) {
- @unlink($tmpFile);
- throw new Exception('Unable to open the zip file');
- }
- $config = self::getInfoIni($zip);
- // 判断插件标识
- $name = $config['name'] ?? '';
- if (!$name) {
- throw new Exception('Addon info file data incorrect');
- }
- // 判断插件是否存在
- if (!preg_match("/^[a-zA-Z0-9]+$/", $name)) {
- throw new Exception('Addon name incorrect');
- }
- // 判断新插件是否存在
- $newAddonDir = self::getAddonDir($name);
- if (!$force && is_dir($newAddonDir)) {
- throw new AddonException('Addon already exists', -1, ['name' => $name, 'title' => $config['title']]);
- }
- // 读取旧版本号
- $oldversion = '';
- if (is_dir($newAddonDir)) {
- $oldConfig = parse_ini_file($newAddonDir . 'info.ini');
- $oldversion = $oldConfig['version'] ?? '';
- }
- $extend['oldversion'] = $oldversion;
- $extend['version'] = $config['version'];
- // 追加MD5和Data数据
- $extend['md5'] = md5_file($tmpFile);
- $extend['data'] = $zip->getArchiveComment();
- $extend['unknownsources'] = config('app_debug') && config('fastadmin.unknownsources');
- $extend['faversion'] = config('fastadmin.version');
- $params = array_merge($config, $extend);
- // 压缩包验证、版本依赖判断,应用插件需要授权使用,移除或绕过授权验证,保留追究法律责任的权利
- Service::valid($params);
- if (!$oldversion) {
- // 新装模式
- $info = Service::install($name, $force, $extend, $tmpFile);
- } else {
- // 升级模式
- $info = Service::upgrade($name, $extend, $tmpFile);
- }
- } catch (AddonException $e) {
- throw new AddonException($e->getMessage(), $e->getCode(), $e->getData());
- } catch (Exception $e) {
- throw new Exception(__($e->getMessage()));
- } finally {
- $zip->close();
- unset($uploadFile);
- is_file($tmpFile) && unlink($tmpFile);
- }
- $info['config'] = get_addon_config($name) ? 1 : 0;
- $info['bootstrap'] = is_file(Service::getBootstrapFile($name));
- $info['testdata'] = is_file(Service::getTestdataFile($name));
- return $info;
- }
- /**
- * 验证压缩包、依赖验证
- * @param array $params
- * @return bool
- * @throws Exception
- */
- public static function valid($params = [])
- {
- $json = self::sendRequest('/addon/valid', $params, 'POST');
- if ($json && isset($json['code'])) {
- if ($json['code']) {
- return true;
- } else {
- throw new Exception($json['msg'] ?? "Invalid addon package");
- }
- } else {
- throw new Exception("Unknown data format");
- }
- }
- /**
- * 备份插件
- * @param string $name 插件名称
- * @return bool
- * @throws Exception
- */
- public static function backup($name)
- {
- $addonsBackupDir = self::getAddonsBackupDir();
- $file = $addonsBackupDir . $name . '-backup-' . date("YmdHis") . '.zip';
- $zipFile = new ZipFile();
- try {
- $zipFile
- ->addDirRecursive(self::getAddonDir($name))
- ->saveAsFile($file)
- ->close();
- } catch (ZipException $e) {
- } finally {
- $zipFile->close();
- }
- return true;
- }
- /**
- * 检测插件是否完整
- *
- * @param string $name 插件名称
- * @return boolean
- * @throws Exception
- */
- public static function check($name)
- {
- if (!$name || !is_dir(ADDON_PATH . $name)) {
- throw new Exception('Addon not exists');
- }
- $addonClass = get_addon_class($name);
- if (!$addonClass) {
- throw new Exception("The addon file does not exist");
- }
- $addon = new $addonClass();
- if (!$addon->checkInfo()) {
- throw new Exception("The configuration file content is incorrect");
- }
- return true;
- }
- /**
- * 是否有冲突
- *
- * @param string $name 插件名称
- * @return boolean
- * @throws AddonException
- */
- public static function noconflict($name)
- {
- // 检测冲突文件
- $list = self::getGlobalFiles($name, true);
- if ($list) {
- //发现冲突文件,抛出异常
- throw new AddonException(__("Conflicting file found"), -3, ['conflictlist' => $list]);
- }
- return true;
- }
- /**
- * 导入SQL
- *
- * @param string $name 插件名称
- * @param string $fileName SQL文件名称
- * @return boolean
- */
- public static function importsql($name, $fileName = null)
- {
- $fileName = is_null($fileName) ? 'install.sql' : $fileName;
- $sqlFile = self::getAddonDir($name) . $fileName;
- if (is_file($sqlFile)) {
- $lines = file($sqlFile);
- $templine = '';
- foreach ($lines as $line) {
- if (substr($line, 0, 2) == '--' || $line == '' || substr($line, 0, 2) == '/*') {
- continue;
- }
- $templine .= $line;
- if (substr(trim($line), -1, 1) == ';') {
- $templine = str_ireplace('__PREFIX__', config('database.prefix'), $templine);
- $templine = str_ireplace('INSERT INTO ', 'INSERT IGNORE INTO ', $templine);
- try {
- Db::getPdo()->exec($templine);
- } catch (\PDOException $e) {
- //$e->getMessage();
- }
- $templine = '';
- }
- }
- }
- return true;
- }
- /**
- * 刷新插件缓存文件
- *
- * @return boolean
- * @throws Exception
- */
- public static function refresh()
- {
- //刷新addons.js
- $addons = get_addon_list();
- $bootstrapArr = [];
- foreach ($addons as $name => $addon) {
- $bootstrapFile = self::getBootstrapFile($name);
- if ($addon['state'] && is_file($bootstrapFile)) {
- $bootstrapArr[] = file_get_contents($bootstrapFile);
- }
- }
- $addonsFile = ROOT_PATH . str_replace("/", DS, "public/assets/js/addons.js");
- if ($handle = fopen($addonsFile, 'w')) {
- $tpl = <<<EOD
- define([], function () {
- {__JS__}
- });
- EOD;
- fwrite($handle, str_replace("{__JS__}", implode("\n", $bootstrapArr), $tpl));
- fclose($handle);
- } else {
- throw new Exception(__("Unable to open file '%s' for writing", "addons.js"));
- }
- Cache::rm("addons");
- Cache::rm("hooks");
- $file = self::getExtraAddonsFile();
- $config = get_addon_autoload_config(true);
- if ($config['autoload']) {
- return;
- }
- if (!is_really_writable($file)) {
- throw new Exception(__("Unable to open file '%s' for writing", "addons.php"));
- }
- file_put_contents($file, "<?php\n\n" . "return " . VarExporter::export($config) . ";\n", LOCK_EX);
- return true;
- }
- /**
- * 安装插件
- *
- * @param string $name 插件名称
- * @param boolean $force 是否覆盖
- * @param array $extend 扩展参数
- * @param array $tmpFile 本地文件
- * @return boolean
- * @throws Exception
- * @throws AddonException
- */
- public static function install($name, $force = false, $extend = [], $tmpFile = '')
- {
- if (!$name || (is_dir(ADDON_PATH . $name) && !$force)) {
- throw new Exception('Addon already exists');
- }
- $extend['domain'] = request()->host(true);
- // 远程下载插件
- $tmpFile = $tmpFile ?: Service::download($name, $extend);
- $addonDir = self::getAddonDir($name);
- try {
- // 解压插件压缩包到插件目录
- Service::unzip($name, $tmpFile);
- // 检查插件是否完整
- Service::check($name);
- if (!$force) {
- Service::noconflict($name);
- }
- } catch (AddonException $e) {
- @rmdirs($addonDir);
- throw new AddonException($e->getMessage(), $e->getCode(), $e->getData());
- } catch (Exception $e) {
- @rmdirs($addonDir);
- throw new Exception($e->getMessage());
- } finally {
- // 移除临时文件
- @unlink($tmpFile);
- }
- // 默认启用该插件
- $info = get_addon_info($name);
- Db::startTrans();
- try {
- if (!$info['state']) {
- $info['state'] = 1;
- set_addon_info($name, $info);
- }
- // 执行安装脚本
- $class = get_addon_class($name);
- if (class_exists($class)) {
- $addon = new $class();
- $addon->install();
- }
- Db::commit();
- } catch (Exception $e) {
- @rmdirs($addonDir);
- Db::rollback();
- throw new Exception($e->getMessage());
- }
- // 导入
- Service::importsql($name);
- // 启用插件
- Service::enable($name, true);
- $info['config'] = get_addon_config($name) ? 1 : 0;
- $info['bootstrap'] = is_file(Service::getBootstrapFile($name));
- $info['testdata'] = is_file(Service::getTestdataFile($name));
- return $info;
- }
- /**
- * 卸载插件
- *
- * @param string $name
- * @param boolean $force 是否强制卸载
- * @return boolean
- * @throws Exception
- */
- public static function uninstall($name, $force = false)
- {
- if (!$name || !is_dir(ADDON_PATH . $name)) {
- throw new Exception('Addon not exists');
- }
- if (!$force) {
- Service::noconflict($name);
- }
- // 移除插件全局资源文件
- if ($force) {
- $list = Service::getGlobalFiles($name);
- foreach ($list as $k => $v) {
- @unlink(ROOT_PATH . $v);
- }
- }
- // 执行卸载脚本
- try {
- $class = get_addon_class($name);
- if (class_exists($class)) {
- $addon = new $class();
- $addon->uninstall();
- }
- } catch (Exception $e) {
- throw new Exception($e->getMessage());
- }
- // 移除插件目录
- rmdirs(ADDON_PATH . $name);
- // 刷新
- Service::refresh();
- return true;
- }
- /**
- * 启用
- * @param string $name 插件名称
- * @param boolean $force 是否强制覆盖
- * @return boolean
- */
- public static function enable($name, $force = false)
- {
- if (!$name || !is_dir(ADDON_PATH . $name)) {
- throw new Exception('Addon not exists');
- }
- if (!$force) {
- Service::noconflict($name);
- }
- //备份冲突文件
- if (config('fastadmin.backup_global_files')) {
- $conflictFiles = self::getGlobalFiles($name, true);
- if ($conflictFiles) {
- $zip = new ZipFile();
- try {
- foreach ($conflictFiles as $k => $v) {
- $zip->addFile(ROOT_PATH . $v, $v);
- }
- $addonsBackupDir = self::getAddonsBackupDir();
- $zip->saveAsFile($addonsBackupDir . $name . "-conflict-enable-" . date("YmdHis") . ".zip");
- } catch (Exception $e) {
- } finally {
- $zip->close();
- }
- }
- }
- $addonDir = self::getAddonDir($name);
- $sourceAssetsDir = self::getSourceAssetsDir($name);
- $destAssetsDir = self::getDestAssetsDir($name);
- $files = self::getGlobalFiles($name);
- if ($files) {
- //刷新插件配置缓存
- Service::config($name, ['files' => $files]);
- }
- // 复制文件
- if (is_dir($sourceAssetsDir)) {
- copydirs($sourceAssetsDir, $destAssetsDir);
- }
- // 复制application和public到全局
- foreach (self::getCheckDirs() as $k => $dir) {
- if (is_dir($addonDir . $dir)) {
- copydirs($addonDir . $dir, ROOT_PATH . $dir);
- }
- }
- //插件纯净模式时将插件目录下的application、public和assets删除
- if (config('fastadmin.addon_pure_mode')) {
- // 删除插件目录已复制到全局的文件
- @rmdirs($sourceAssetsDir);
- foreach (self::getCheckDirs() as $k => $dir) {
- @rmdirs($addonDir . $dir);
- }
- }
- //执行启用脚本
- try {
- $class = get_addon_class($name);
- if (class_exists($class)) {
- $addon = new $class();
- if (method_exists($class, "enable")) {
- $addon->enable();
- }
- }
- } catch (Exception $e) {
- throw new Exception($e->getMessage());
- }
- $info = get_addon_info($name);
- $info['state'] = 1;
- unset($info['url']);
- set_addon_info($name, $info);
- // 刷新
- Service::refresh();
- return true;
- }
- /**
- * 禁用
- *
- * @param string $name 插件名称
- * @param boolean $force 是否强制禁用
- * @return boolean
- * @throws Exception
- */
- public static function disable($name, $force = false)
- {
- if (!$name || !is_dir(ADDON_PATH . $name)) {
- throw new Exception('Addon not exists');
- }
- $file = self::getExtraAddonsFile();
- if (!is_really_writable($file)) {
- throw new Exception(__("Unable to open file '%s' for writing", "addons.php"));
- }
- if (!$force) {
- Service::noconflict($name);
- }
- if (config('fastadmin.backup_global_files')) {
- //仅备份修改过的文件
- $conflictFiles = Service::getGlobalFiles($name, true);
- if ($conflictFiles) {
- $zip = new ZipFile();
- try {
- foreach ($conflictFiles as $k => $v) {
- $zip->addFile(ROOT_PATH . $v, $v);
- }
- $addonsBackupDir = self::getAddonsBackupDir();
- $zip->saveAsFile($addonsBackupDir . $name . "-conflict-disable-" . date("YmdHis") . ".zip");
- } catch (Exception $e) {
- } finally {
- $zip->close();
- }
- }
- }
- $config = Service::config($name);
- $addonDir = self::getAddonDir($name);
- //插件资源目录
- $destAssetsDir = self::getDestAssetsDir($name);
- // 移除插件全局文件
- $list = Service::getGlobalFiles($name);
- //插件纯净模式时将原有的文件复制回插件目录
- //当无法获取全局文件列表时也将列表复制回插件目录
- if (config('fastadmin.addon_pure_mode') || !$list) {
- if ($config && isset($config['files']) && is_array($config['files'])) {
- foreach ($config['files'] as $index => $item) {
- //避免切换不同服务器后导致路径不一致
- $item = str_replace(['/', '\\'], DS, $item);
- //插件资源目录,无需重复复制
- if (stripos($item, str_replace(ROOT_PATH, '', $destAssetsDir)) === 0) {
- continue;
- }
- //检查目录是否存在,不存在则创建
- $itemBaseDir = dirname($addonDir . $item);
- if (!is_dir($itemBaseDir)) {
- @mkdir($itemBaseDir, 0755, true);
- }
- if (is_file(ROOT_PATH . $item)) {
- @copy(ROOT_PATH . $item, $addonDir . $item);
- }
- }
- $list = $config['files'];
- }
- //复制插件目录资源
- if (is_dir($destAssetsDir)) {
- @copydirs($destAssetsDir, $addonDir . 'assets' . DS);
- }
- }
- $dirs = [];
- foreach ($list as $k => $v) {
- $file = ROOT_PATH . $v;
- $dirs[] = dirname($file);
- @unlink($file);
- }
- // 移除插件空目录
- $dirs = array_filter(array_unique($dirs));
- foreach ($dirs as $k => $v) {
- remove_empty_folder($v);
- }
- $info = get_addon_info($name);
- $info['state'] = 0;
- unset($info['url']);
- set_addon_info($name, $info);
- // 执行禁用脚本
- try {
- $class = get_addon_class($name);
- if (class_exists($class)) {
- $addon = new $class();
- if (method_exists($class, "disable")) {
- $addon->disable();
- }
- }
- } catch (Exception $e) {
- throw new Exception($e->getMessage());
- }
- // 刷新
- Service::refresh();
- return true;
- }
- /**
- * 升级插件
- *
- * @param string $name 插件名称
- * @param array $extend 扩展参数
- */
- public static function upgrade($name, $extend = [], $tmpFile = false)
- {
- $info = get_addon_info($name);
- if ($info['state']) {
- throw new Exception(__('Please disable addon first'));
- }
- $config = get_addon_config($name);
- if ($config) {
- //备份配置
- }
- // 远程下载插件(如果为本地文件则使用本地文件)
- $tmpFile = $tmpFile ? $tmpFile : Service::download($name, $extend);
- // 备份插件文件
- Service::backup($name);
- $addonDir = self::getAddonDir($name);
- // 删除插件目录下的application和public
- $files = self::getCheckDirs();
- foreach ($files as $index => $file) {
- @rmdirs($addonDir . $file);
- }
- try {
- // 解压插件
- Service::unzip($name, $tmpFile);
- } catch (Exception $e) {
- throw new Exception($e->getMessage());
- } finally {
- // 移除临时文件
- @unlink($tmpFile);
- }
- if ($config) {
- $configFile = ADDON_PATH . $name . DS . 'config.php';
- $bakFile = ADDON_PATH . $name . DS . 'config_tmp.php';
- @copy($configFile, $bakFile);
- $fullConfig = include($bakFile);
- @unlink($bakFile);
- foreach ($fullConfig as $index => &$item) {
- if (isset($config[$item['name']])) {
- $item['value'] = $config[$item['name']];
- }
- }
- // 更新配置
- set_addon_fullconfig($name, $fullConfig);
- }
- // 导入
- Service::importsql($name);
- // 执行升级脚本
- try {
- $addonName = ucfirst($name);
- //创建临时类用于调用升级的方法
- $sourceFile = $addonDir . $addonName . ".php";
- $destFile = $addonDir . $addonName . "Upgrade.php";
- $classContent = str_replace("class {$addonName} extends", "class {$addonName}Upgrade extends", file_get_contents($sourceFile));
- //创建临时的类文件
- file_put_contents($destFile, $classContent);
- $className = "\\addons\\" . $name . "\\" . $addonName . "Upgrade";
- $addon = new $className($name);
- //调用升级的方法
- if (method_exists($addon, "upgrade")) {
- $addon->upgrade();
- }
- //移除临时文件
- @unlink($destFile);
- } catch (Exception $e) {
- throw new Exception($e->getMessage());
- }
- // 刷新
- Service::refresh();
- //必须变更版本号
- $info['version'] = $extend['version'] ?? $info['version'];
- $info['config'] = get_addon_config($name) ? 1 : 0;
- $info['bootstrap'] = is_file(Service::getBootstrapFile($name));
- return $info;
- }
- /**
- * 读取或修改插件配置
- * @param string $name
- * @param array $changed
- * @return array
- */
- public static function config($name, $changed = [])
- {
- $addonDir = self::getAddonDir($name);
- $addonConfigFile = $addonDir . '.addonrc';
- $config = [];
- if (is_file($addonConfigFile)) {
- $config = (array)json_decode(file_get_contents($addonConfigFile), true);
- }
- $config = array_merge($config, $changed);
- if ($changed) {
- file_put_contents($addonConfigFile, json_encode($config, JSON_UNESCAPED_UNICODE));
- }
- return $config;
- }
- /**
- * 获取插件在全局的文件
- *
- * @param string $name 插件名称
- * @param boolean $onlyconflict 是否只返回冲突文件
- * @return array
- */
- public static function getGlobalFiles($name, $onlyconflict = false)
- {
- $list = [];
- $addonDir = self::getAddonDir($name);
- $checkDirList = self::getCheckDirs();
- $checkDirList = array_merge($checkDirList, ['assets']);
- $assetDir = self::getDestAssetsDir($name);
- // 扫描插件目录是否有覆盖的文件
- foreach ($checkDirList as $k => $dirName) {
- //检测目录是否存在
- if (!is_dir($addonDir . $dirName)) {
- continue;
- }
- //匹配出所有的文件
- $files = new RecursiveIteratorIterator(
- new RecursiveDirectoryIterator($addonDir . $dirName, RecursiveDirectoryIterator::SKIP_DOTS), RecursiveIteratorIterator::CHILD_FIRST
- );
- foreach ($files as $fileinfo) {
- if ($fileinfo->isFile()) {
- $filePath = $fileinfo->getPathName();
- //如果名称为assets需要做特殊处理
- if ($dirName === 'assets') {
- $path = str_replace(ROOT_PATH, '', $assetDir) . str_replace($addonDir . $dirName . DS, '', $filePath);
- } else {
- $path = str_replace($addonDir, '', $filePath);
- }
- if ($onlyconflict) {
- $destPath = ROOT_PATH . $path;
- if (is_file($destPath)) {
- if (filesize($filePath) != filesize($destPath) || md5_file($filePath) != md5_file($destPath)) {
- $list[] = $path;
- }
- }
- } else {
- $list[] = $path;
- }
- }
- }
- }
- $list = array_filter(array_unique($list));
- return $list;
- }
- /**
- * 更新本地应用插件授权
- */
- public static function authorization($params = [])
- {
- $addonList = get_addon_list();
- $result = [];
- $domain = request()->host(true);
- $addons = [];
- foreach ($addonList as $name => $item) {
- $config = self::config($name);
- $addons[] = ['name' => $name, 'domains' => $config['domains'] ?? [], 'licensecodes' => $config['licensecodes'] ?? [], 'validations' => $config['validations'] ?? []];
- }
- $params = array_merge($params, [
- 'faversion' => config('fastadmin.version'),
- 'domain' => $domain,
- 'addons' => $addons
- ]);
- $result = self::sendRequest('/addon/authorization', $params, 'POST');
- if (isset($result['code']) && $result['code'] == 1) {
- $json = $result['data']['addons'] ?? [];
- foreach ($addonList as $name => $item) {
- self::config($name, ['domains' => $json[$name]['domains'] ?? [], 'licensecodes' => $json[$name]['licensecodes'] ?? [], 'validations' => $json[$name]['validations'] ?? []]);
- }
- return true;
- } else {
- throw new Exception($result['msg'] ?? __('Network error'));
- }
- }
- /**
- * 验证插件授权,应用插件需要授权使用,移除或绕过授权验证,保留追究法律责任的权利
- * @param $name
- * @return bool
- */
- public static function checkAddonAuthorization($name)
- {
- $request = request();
- $config = self::config($name);
- $domain = self::getRootDomain($request->host(true));
- //应用插件需要授权使用,移除或绕过授权验证,保留追究法律责任的权利
- if (isset($config['domains']) && isset($config['validations']) && isset($config['licensecodes'])) {
- $index = array_search($domain, $config['domains']);
- if ((in_array($domain, $config['domains']) && in_array(md5(md5($domain) . ($config['licensecodes'][$index] ?? '')), $config['validations'])) || $request->isCli()) {
- $request->bind('authorized', $domain ?: 'cli');
- return true;
- } elseif ($config['domains']) {
- foreach ($config['domains'] as $index => $item) {
- if (substr_compare($domain, "." . $item, -strlen("." . $item)) === 0 && in_array(md5(md5($item) . ($config['licensecodes'][$index] ?? '')), $config['validations'])) {
- $request->bind('authorized', $domain);
- return true;
- }
- }
- }
- }
- return false;
- }
- /**
- * 获取顶级域名
- * @param $domain
- * @return string
- */
- public static function getRootDomain($domain)
- {
- $host = strtolower(trim($domain));
- $hostArr = explode('.', $host);
- $hostCount = count($hostArr);
- $cnRegex = '/\w+\.(gov|org|ac|mil|net|edu|com|bj|tj|sh|cq|he|sx|nm|ln|jl|hl|js|zj|ah|fj|jx|sd|ha|hb|hn|gd|gx|hi|sc|gz|yn|xz|sn|gs|qh|nx|xj|tw|hk|mo)\.cn$/i';
- $countryRegex = '/\w+\.(\w{2}|com|net)\.\w{2}$/i';
- if ($hostCount > 2 && (preg_match($cnRegex, $host) || preg_match($countryRegex, $host))) {
- $host = implode('.', array_slice($hostArr, -3, 3, true));
- } else {
- $host = implode('.', array_slice($hostArr, -2, 2, true));
- }
- return $host;
- }
- /**
- * 获取插件行为、路由配置文件
- * @return string
- */
- public static function getExtraAddonsFile()
- {
- return CONF_PATH . 'extra' . DS . 'addons.php';
- }
- /**
- * 获取bootstrap.js路径
- * @return string
- */
- public static function getBootstrapFile($name)
- {
- return ADDON_PATH . $name . DS . 'bootstrap.js';
- }
- /**
- * 获取testdata.sql路径
- * @return string
- */
- public static function getTestdataFile($name)
- {
- return ADDON_PATH . $name . DS . 'testdata.sql';
- }
- /**
- * 获取指定插件的目录
- */
- public static function getAddonDir($name)
- {
- $dir = ADDON_PATH . $name . DS;
- return $dir;
- }
- /**
- * 获取插件备份目录
- */
- public static function getAddonsBackupDir()
- {
- $dir = RUNTIME_PATH . 'addons' . DS;
- if (!is_dir($dir)) {
- @mkdir($dir, 0755, true);
- }
- return $dir;
- }
- /**
- * 获取插件源资源文件夹
- * @param string $name 插件名称
- * @return string
- */
- protected static function getSourceAssetsDir($name)
- {
- return ADDON_PATH . $name . DS . 'assets' . DS;
- }
- /**
- * 获取插件目标资源文件夹
- * @param string $name 插件名称
- * @return string
- */
- protected static function getDestAssetsDir($name)
- {
- $assetsDir = ROOT_PATH . str_replace("/", DS, "public/assets/addons/{$name}/");
- return $assetsDir;
- }
- /**
- * 获取远程服务器
- * @return string
- */
- protected static function getServerUrl()
- {
- return config('fastadmin.api_url');
- }
- /**
- * 获取检测的全局文件夹目录
- * @return array
- */
- protected static function getCheckDirs()
- {
- return [
- 'application',
- 'public'
- ];
- }
- /**
- * 获取请求对象
- * @return Client
- */
- public static function getClient()
- {
- $options = [
- 'base_uri' => self::getServerUrl(),
- 'timeout' => 30,
- 'connect_timeout' => 30,
- 'verify' => false,
- 'http_errors' => false,
- 'headers' => [
- 'X-REQUESTED-WITH' => 'XMLHttpRequest',
- 'Referer' => dirname(request()->root(true)),
- 'User-Agent' => 'FastAddon',
- ]
- ];
- static $client;
- if (empty($client)) {
- $client = new Client($options);
- }
- return $client;
- }
- /**
- * 发送请求
- * @return array
- * @throws Exception
- * @throws \GuzzleHttp\Exception\GuzzleException
- */
- public static function sendRequest($url, $params = [], $method = 'POST')
- {
- $json = [];
- try {
- $client = self::getClient();
- $options = strtoupper($method) == 'POST' ? ['form_params' => $params] : ['query' => $params];
- $response = $client->request($method, $url, $options);
- $body = $response->getBody();
- $content = $body->getContents();
- $json = (array)json_decode($content, true);
- } catch (TransferException $e) {
- throw new Exception(__('Network error'));
- } catch (\Exception $e) {
- throw new Exception(__('Unknown data format'));
- }
- return $json;
- }
- /**
- * 匹配配置文件中info信息
- * @param ZipFile $zip
- * @return array|false
- * @throws Exception
- */
- protected static function getInfoIni($zip)
- {
- $config = [];
- // 读取插件信息
- try {
- $info = $zip->getEntryContents('info.ini');
- $config = parse_ini_string($info);
- } catch (ZipException $e) {
- throw new Exception('Unable to extract the file');
- }
- return $config;
- }
- }
|