123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443 |
- <?php
- // +----------------------------------------------------------------------
- // | Yzncms [ 御宅男工作室 ]
- // +----------------------------------------------------------------------
- // | Copyright (c) 2018 http://yzncms.com All rights reserved.
- // +----------------------------------------------------------------------
- // | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
- // +----------------------------------------------------------------------
- // | Author: 御宅男 <530765310@qq.com>
- // +----------------------------------------------------------------------
- // +----------------------------------------------------------------------
- // | 插件服务类
- // +----------------------------------------------------------------------
- namespace think;
- use app\admin\model\Addons as AddonsModel;
- use app\common\model\Cache as CacheModel;
- use RecursiveDirectoryIterator;
- use RecursiveIteratorIterator;
- use think\Exception;
- use think\facade\Cache;
- use think\facade\Db;
- use util\File;
- use util\Sql;
- use ZipArchive;
- class AddonService
- {
- /**
- * 安装插件.
- *
- * @param string $name 插件名称
- * @param bool $force 是否覆盖
- * @param array $extend 扩展参数
- *
- * @throws Exception
- *
- * @return bool
- */
- public static function install($name, $force = false, $extend = [])
- {
- try {
- // 检查插件是否完整
- self::check($name);
- if (!$force) {
- self::noconflict($name);
- }
- } catch (Exception $e) {
- throw new Exception($e->getMessage());
- }
- /*if (!$name || (is_dir(ADDON_PATH . $name) && !$force)) {
- throw new Exception('插件已存在!');
- }*/
- foreach (self::getCheckDirs() as $k => $dir) {
- if (is_dir(ADDON_PATH . $name . DS . $dir)) {
- File::copy_dir(ADDON_PATH . $name . DS . $dir, app()->getRootPath() . $dir);
- }
- }
- //前台模板
- $installdir = ADDON_PATH . "{$name}" . DS . "install" . DS;
- if (is_dir($installdir . "template" . DS)) {
- //拷贝模板到前台模板目录中去
- File::copy_dir($installdir . "template" . DS, TEMPLATE_PATH . 'default' . DS);
- }
- //静态资源文件
- if (file_exists(ADDON_PATH . $name . DS . "install" . DS . "public" . DS)) {
- //拷贝模板到前台模板目录中去
- File::copy_dir(ADDON_PATH . $name . DS . "install" . DS . "public" . DS, app()->getRootPath() . 'public' . DS . 'static' . DS . 'addons' . DS . strtolower($name) . '/');
- }
- try {
- // 默认启用该插件
- $info = get_addon_info($name);
- if (!$info['status']) {
- $info['status'] = 1;
- set_addon_info($name, $info);
- }
- // 执行安装脚本
- $class = get_addon_class($name);
- if (class_exists($class)) {
- $addon = new $class();
- $addon->install();
- //缓存
- if (isset($addon->cache) && is_array($addon->cache)) {
- self::installAddonCache($addon->cache, $name);
- }
- }
- self::runSQL($name);
- AddonsModel::create($info);
- } catch (Exception $e) {
- throw new Exception($e->getMessage());
- }
- // 刷新
- self::refresh();
- return true;
- }
- /**
- * 卸载插件.
- *
- * @param string $name
- * @param bool $force 是否强制卸载
- *
- * @throws Exception
- *
- * @return bool
- */
- public static function uninstall($name, $force = false)
- {
- if (!$name || !is_dir(ADDON_PATH . $name)) {
- throw new Exception('插件不存在!');
- }
- // 移除插件全局资源文件
- if ($force) {
- $list = self::getGlobalFiles($name);
- foreach ($list as $k => $v) {
- @unlink(app()->getRootPath() . $v);
- }
- }
- //删除模块前台模板
- if (is_dir(TEMPLATE_PATH . 'default' . DS . $name . DS)) {
- File::del_dir(TEMPLATE_PATH . 'default' . DS . $name . DS);
- }
- //静态资源移除
- if (is_dir(app()->getRootPath() . 'public' . DS . 'static' . DS . 'addons' . DS . strtolower($name) . DS)) {
- File::del_dir(app()->getRootPath() . 'public' . DS . 'static' . DS . 'addons' . DS . strtolower($name) . DS);
- }
- // 执行卸载脚本
- try {
- // 默认禁用该插件
- $info = get_addon_info($name);
- if ($info['status']) {
- $info['status'] = 0;
- set_addon_info($name, $info);
- }
- $class = get_addon_class($name);
- if (class_exists($class)) {
- $addon = new $class();
- $addon->uninstall();
- //缓存
- if (isset($addon->cache) && is_array($addon->cache)) {
- CacheModel::where(['module' => $name, 'system' => 0])->delete();
- }
- };
- self::runSQL($name, 'uninstall');
- AddonsModel::where('name', $name)->delete();
- } catch (Exception $e) {
- throw new Exception($e->getMessage());
- }
- // 刷新
- self::refresh();
- return true;
- }
- /**
- * 启用.
- *
- * @param string $name 插件名称
- * @param bool $force 是否强制覆盖
- *
- * @return bool
- */
- public static function enable($name, $force = false)
- {
- if (!$name || !is_dir(ADDON_PATH . $name)) {
- throw new Exception('插件不存在!');
- }
- $info = get_addon_info($name);
- $info['status'] = 1;
- unset($info['url']);
- set_addon_info($name, $info);
- //执行启用脚本
- try {
- AddonsModel::update(['status' => 1], ['name' => $name]);
- $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());
- }
- // 刷新
- self::refresh();
- return true;
- }
- /**
- * 禁用.
- *
- * @param string $name 插件名称
- * @param bool $force 是否强制禁用
- *
- * @throws Exception
- *
- * @return bool
- */
- public static function disable($name, $force = false)
- {
- if (!$name || !is_dir(ADDON_PATH . $name)) {
- throw new Exception('插件不存在!');
- }
- $info = get_addon_info($name);
- $info['status'] = 0;
- unset($info['url']);
- set_addon_info($name, $info);
- // 执行禁用脚本
- try {
- AddonsModel::update(['status' => 0], ['name' => $name]);
- $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());
- }
- // 刷新
- self::refresh();
- return true;
- }
- /**
- * 刷新插件缓存文件.
- *
- * @throws Exception
- *
- * @return bool
- */
- public static function refresh()
- {
- $file = app()->getRootPath() . 'config' . DS . 'addons.php';
- $config = get_addon_autoload_config(true);
- if ($config['autoload']) {
- return;
- }
- if (!\util\File::is_really_writable($file)) {
- throw new Exception('addons.php文件没有写入权限');
- }
- if ($handle = fopen($file, 'w')) {
- fwrite($handle, "<?php\n\n" . 'return ' . var_export($config, true) . ';');
- fclose($handle);
- } else {
- throw new Exception('文件没有写入权限');
- }
- return true;
- }
- /**
- * 解压插件.
- *
- * @param string $name 插件名称
- *
- * @throws Exception
- *
- * @return string
- */
- public static function unzip($name)
- {
- $file = app()->getRootPath() . 'runtime' . DS . 'addons' . DS . $name . '.zip';
- $dir = ADDON_PATH . $name . DS;
- if (class_exists('ZipArchive')) {
- $zip = new ZipArchive();
- if ($zip->open($file) !== true) {
- throw new Exception('Unable to open the zip file');
- }
- if (!$zip->extractTo($dir)) {
- $zip->close();
- throw new Exception('Unable to extract the file');
- }
- $zip->close();
- return $dir;
- }
- throw new Exception('无法执行解压操作,请确保ZipArchive安装正确');
- }
- /**
- * 注册插件缓存
- * @return boolean
- */
- public static function installAddonCache(array $cache, $name)
- {
- $data = array();
- foreach ($cache as $key => $rs) {
- $add = array(
- 'key' => $key,
- 'name' => $rs['name'],
- 'module' => isset($rs['module']) ? $rs['module'] : $name,
- 'model' => $rs['model'],
- 'action' => $rs['action'],
- //'param' => isset($rs['param']) ? $rs['param'] : '',
- 'system' => 0,
- );
- CacheModel::create($add);
- }
- return true;
- }
- /**
- * 执行安装数据库脚本
- * @param type $name 模块名(目录名)
- * @return boolean
- */
- public static function runSQL($name = '', $Dir = 'install')
- {
- $sql_file = ADDON_PATH . "{$name}" . DS . "{$Dir}" . DS . "{$Dir}.sql";
- if (file_exists($sql_file)) {
- $sql_statement = Sql::getSqlFromFile($sql_file);
- if (!empty($sql_statement)) {
- foreach ($sql_statement as $value) {
- try {
- Db::execute($value);
- } catch (\Exception $e) {
- throw new Exception('导入SQL失败,请检查{$name}.sql的语句是否正确');
- }
- }
- }
- }
- return true;
- }
- /**
- * 是否有冲突
- *
- * @param string $name 插件名称
- * @return boolean
- * @throws AddonException
- */
- public static function noconflict($name)
- {
- // 检测冲突文件
- $list = self::getGlobalFiles($name, true);
- if ($list) {
- //发现冲突文件,抛出异常
- throw new Exception("发现冲突文件");
- }
- return true;
- }
- /**
- * 获取插件在全局的文件
- *
- * @param string $name 插件名称
- * @return array
- */
- public static function getGlobalFiles($name, $onlyconflict = false)
- {
- $list = [];
- $addonDir = ADDON_PATH . $name . DS;
- // 扫描插件目录是否有覆盖的文件
- foreach (self::getCheckDirs() as $k => $dir) {
- $checkDir = app()->getRootPath() . DS . $dir . DS;
- if (!is_dir($checkDir)) {
- continue;
- }
- //检测到存在插件外目录
- if (is_dir($addonDir . $dir)) {
- //匹配出所有的文件
- $files = new RecursiveIteratorIterator(
- new RecursiveDirectoryIterator($addonDir . $dir, RecursiveDirectoryIterator::SKIP_DOTS), RecursiveIteratorIterator::CHILD_FIRST
- );
- foreach ($files as $fileinfo) {
- if ($fileinfo->isFile()) {
- $filePath = $fileinfo->getPathName();
- $path = str_replace($addonDir, '', $filePath);
- if ($onlyconflict) {
- $destPath = app()->getRootPath() . $path;
- if (is_file($destPath)) {
- if (filesize($filePath) != filesize($destPath) || md5_file($filePath) != md5_file($destPath)) {
- $list[] = $path;
- }
- }
- } else {
- $list[] = $path;
- }
- }
- }
- }
- }
- return $list;
- }
- /**
- * 获取检测的全局文件夹目录
- * @return array
- */
- protected static function getCheckDirs()
- {
- return [
- 'app',
- 'public',
- ];
- }
- /**
- * 检测插件是否完整.
- *
- * @param string $name 插件名称
- *
- * @throws Exception
- *
- * @return bool
- */
- public static function check($name)
- {
- if (!$name || !is_dir(ADDON_PATH . $name)) {
- throw new Exception('插件不存在!');
- }
- $addonClass = get_addon_class($name);
- if (!$addonClass) {
- throw new Exception('插件主启动程序不存在');
- }
- $addon = new $addonClass();
- if (!$addon->checkInfo()) {
- throw new Exception('配置文件不完整');
- }
- return true;
- }
- }
|