* Date: 2018-4-3 */ namespace app\admin\logic; use think\Model; use think\Db; class WeappLogic extends Model { public $root_path; public $weapp_path; public $data_path; // public $config_path; // public $curent_version; public $upgrade_url; public $service_ey; // public $code; public $cms_version; public $upgrade_postdata = []; /** * 析构函数 */ function __construct() { // $this->code = input('param.code/s', ''); // $this->curent_version = getWeappVersion($this->code); $this->cms_version = getCmsVersion(); $upgradeLogic = new \app\admin\logic\UpgradeLogic; $this->service_ey = $upgradeLogic->getServiceUrl(true); $this->root_path = ROOT_PATH; // $this->weapp_path = WEAPP_DIR_NAME.DS; // $this->data_path = DATA_PATH; // // $this->config_path = $this->weapp_path.$this->code.DS.'config.php'; // 版本配置文件路径 // api_Weapp_checkVersion $this->upgrade_url = $this->service_ey.'/index.php?m=api&c=Weapp&a=checkVersion'; $this->upgrade_postdata = [ 'domain' => request()->host(true), 'cms_version' => $this->cms_version, 'ip' => serverIP(), ]; } /** * 更新插件到数据库 * @param $weapp_list array 本地插件数组 */ public function insertWeapp() { $row = Db::name('weapp')->field('*')->getAllWithIndex('code'); // 数据库 $new_arr = array(); // 本地 $addData = array(); // 数据存储变量 $updateData = array(); // 数据存储变量 $weapp_list = $this->scanWeapp(); // 本地对比数据库 foreach($weapp_list as $k=>$v){ $code = isset($v['code']) ? $v['code'] : 'error_'.date('Ymd'); /*初步过滤不规范插件*/ if ($k != $code) { continue; } /*--end*/ $new_arr[] = $code; // 对比数据库 本地有 数据库没有 $data = array( 'code' => $code, 'name' => isset($v['name']) ? $v['name'] : '配置信息不完善', 'config' => empty($v) ? '' : json_encode($v), 'position' => isset($v['position']) ? $v['position'] : 'default', 'sort_order' => 100, ); if(empty($row[$code])){ // 新增插件 $data['add_time'] = getTime(); $addData[] = $data; } else { // 更新插件 if ($row[$code]['config'] != json_encode($v)) { $data['id'] = $row[$code]['id']; $data['update_time'] = getTime(); $updateData[] = $data; } } } if (!empty($addData)) { model('weapp')->saveAll($addData); } if (!empty($updateData)) { model('weapp')->saveAll($updateData); } //数据库有 本地没有 foreach($row as $k => $v){ if (!in_array($v['code'], $new_arr) && isset($v['is_buy']) && $v['is_buy'] < 1) {//is_buy 0->本地安装,1-线上购买 Db::name('weapp')->where($v)->cache(true, null, 'weapp')->delete(); } } \think\Cache::clear('weapp'); } /** * 插件目录扫描 * @return array 返回目录数组 */ private function scanWeapp(){ $dir = WEAPP_DIR_NAME; $weapp_list = $this->dirscan($dir); foreach($weapp_list as $k=>$v){ if (!is_dir(WEAPP_DIR_NAME.DS.$v) || !file_exists(WEAPP_DIR_NAME.DS.$v.'/config.php')) { unset($weapp_list[$k]); } else { $weapp_list[$v] = include(WEAPP_DIR_NAME.DS.$v.'/config.php'); unset($weapp_list[$k]); } } return $weapp_list; } /** * 获取插件目录列表 * @param $dir * @return array */ private function dirscan($dir){ $dirArray = array(); if (false != ($handle = opendir($dir))) { $i = 0; while ( false !== ($file = readdir ($handle)) ) { //去掉"“.”、“..”以及带“.xxx”后缀的文件 if ($file != "." && $file != ".." && !strpos($file,".")) { $dirArray[$i] = $file; $i++; } } //关闭句柄 closedir($handle); } return $dirArray; } /** * 插件基类构造方法 * sm:module 插件模块 * sc:controller 插件控制器 * sa:action 插件操作 */ public function checkInstall() { $msg = true; if(!array_key_exists("sm", request()->param())){ $msg = '无效插件URL!'; } else { $module = request()->param('sm'); $module = $module ?: request()->param('sc'); $row = Db::name('Weapp')->field('code, name, status') ->where(array('code'=>$module)) ->find(); if (empty($row)) { $msg = "插件【{$row['name']}】不存在"; } else { if ($row['status'] == -1) { $msg = "请先启用插件【{$row['name']}】"; } else if (intval($row['status']) == 0) { $msg = "请先安装插件【{$row['name']}】"; } } } return $msg; } /** * 检查是否有更新包 * @return type 提示语 */ public function checkVersion($code, $serviceVersionList = false) { error_reporting(0);//关闭所有错误报告 $lastupgrade = array(); if (false === $serviceVersionList) { $this->upgrade_postdata['code'] = $code; $this->upgrade_postdata['v'] = getWeappVersion($code); $upgradeLogic = new \app\admin\logic\UpgradeLogic; $upgradeLogic->GetKeyData($this->upgrade_postdata); $serviceVersionList = @httpRequest($this->upgrade_url, 'POST', $this->upgrade_postdata, [], 5); if (false === $serviceVersionList) { $url = $this->upgrade_url.'&'.http_build_query($this->upgrade_postdata); $context = stream_context_set_default(array('http' => array('timeout' => 5,'method'=>'GET'))); $serviceVersionList = @file_get_contents($url, false, $context); } if (false === $serviceVersionList) { return ['code' => 0, 'msg' => "无法连接远程升级服务器!"]; } else { $serviceVersionList = json_decode($serviceVersionList,true); if (isset($serviceVersionList['code']) && empty($serviceVersionList['code'])) { $msg = empty($serviceVersionList['msg']) ? 'API请求超时' : $serviceVersionList['msg']; return ['code' => 0, 'msg' => "{$msg}"]; } } } if(!empty($serviceVersionList)) { $upgradeArr = array(); $introStr = ''; $upgradeStr = ''; foreach ($serviceVersionList as $key => $val) { $upgrade = !empty($val['upgrade']) ? $val['upgrade'] : array(); $upgradeArr = array_merge($upgradeArr, $upgrade); $introStr .= '
'.filter_line_return($val['intro'], '
'); } $upgradeArr = array_unique($upgradeArr); $upgradeStr = implode('
', $upgradeArr); // 升级提示需要覆盖哪些文件 $introArr = explode('
', $introStr); $introStr = '更新日志:'; foreach ($introArr as $key => $val) { if (empty($val)) { continue; } $introStr .= "
{$key}、".$val; } $lastupgrade = $serviceVersionList[count($serviceVersionList) - 1]; if (!empty($lastupgrade['upgrade_title'])) { $introStr .= '
'.$lastupgrade['upgrade_title']; } $lastupgrade['intro'] = htmlspecialchars_decode($introStr); $lastupgrade['upgrade'] = htmlspecialchars_decode($upgradeStr); // 升级提示需要覆盖哪些文件 /*升级公告*/ if (!empty($lastupgrade['notice'])) { $lastupgrade['notice'] = htmlspecialchars_decode($lastupgrade['notice']) . '
'; } /*--end*/ return ['code' => 2, 'msg' => $lastupgrade]; } return ['code' => 1, 'msg' => '已是最新版']; } /** * 批量检查是否有更新包 * @return type 提示语 */ public function checkBatchVersion($upgradeArr) { $result = array(); if (is_array($upgradeArr) && !empty($upgradeArr)) { foreach ($upgradeArr as $key => $upgrade) { if ($key == 'Sample') { tpCache('system', ['system_usecodelist'=>$upgradeArr['Sample']]); } else { $result[$key] = $this->checkVersion($key, $upgrade); } } } return $result; } /** * 一键更新 */ public function OneKeyUpgrade($code){ error_reporting(0);//关闭所有错误报告 if (empty($code)) { return ['code' => 0, 'msg' => "URL传参错误,缺少插件标识参数值!"]; } $allow_url_fopen = ini_get('allow_url_fopen'); if (!$allow_url_fopen) { return ['code' => 0, 'msg' => "请联系空间商,设置 php.ini 中参数 allow_url_fopen = 1"]; } if (!extension_loaded('zip')) { return ['code' => 0, 'msg' => "请联系空间商,开启 php.ini 中的php-zip扩展"]; } $curent_version = getWeappVersion($code); $this->upgrade_postdata['code'] = $code; $this->upgrade_postdata['dev'] = config('global.upgrade_dev'); $this->upgrade_postdata['v'] = $curent_version; $upgradeLogic = new \app\admin\logic\UpgradeLogic; $upgradeLogic->GetKeyData($this->upgrade_postdata); $url = $this->service_ey."/index.php?m=api&c=Weapp&a=upgradeVersion"; $serviceVersionList = @httpRequest($url, 'POST', $this->upgrade_postdata, [], 5); if (false === $serviceVersionList) { $url = $url.'&'.http_build_query($this->upgrade_postdata); $context = stream_context_set_default(array('http' => array('timeout' => 5,'method'=>'GET'))); $serviceVersionList = @file_get_contents($url, false, $context); } if (false === $serviceVersionList) { return ['code' => 0, 'msg' => "无法连接远程升级服务器!"]; } else { $serviceVersionList = json_decode($serviceVersionList,true); if (isset($serviceVersionList['code']) && empty($serviceVersionList['code'])) { $msg = empty($serviceVersionList['msg']) ? 'API请求超时' : $serviceVersionList['msg']; return ['code' => 0, 'msg' => $msg]; } } if (empty($serviceVersionList)) { return ['code' => 0, 'msg' => "当前没有可升级的版本!"]; } clearstatcache(); // 清除文件夹权限缓存 $config_path = $this->weapp_path.$code.DS.'config.php'; // 版本配置文件路径 if(!is_writeable($config_path)) { return ['code' => 0, 'msg' => '文件'.$config_path.' 不可写,不能升级!!!']; } /*最新更新版本信息*/ $lastServiceVersion = $serviceVersionList[count($serviceVersionList) - 1]; /*--end*/ /*批量下载更新包*/ $upgradeArr = array(); // 更新的文件列表 $sqlfileArr = array(); // 更新SQL文件列表 $folderName = $code.'-'.$lastServiceVersion['key_num']; foreach ($serviceVersionList as $key => $val) { // 下载更新包 $result = $this->downloadFile($val['down_url'], $val['file_md5']); if (!isset($result['code']) || $result['code'] != 1) { return $result; } /*第一个循环执行的业务*/ if ($key == 0) { /*解压到最后一个更新包的文件夹*/ $lastDownFileName = explode('/', $lastServiceVersion['down_url']); $lastDownFileName = end($lastDownFileName); $folderName = $code.'-'.str_replace(".zip", "", $lastDownFileName); // 文件夹 /*--end*/ /*解压之前,删除已重复的文件夹*/ delFile($this->data_path.'backup'.DS.$folderName); /*--end*/ } /*--end*/ $downFileName = explode('/', $val['down_url']); $downFileName = end($downFileName); /*解压文件*/ $zip = new \ZipArchive();//新建一个ZipArchive的对象 if($zip->open($this->data_path.'backup'.DS.$downFileName) != true) { return ['code' => 0, 'msg' => "升级包读取失败!"]; } $zip->extractTo($this->data_path.'backup'.DS.$folderName.DS);//假设解压缩到在当前路径下backup文件夹内 $zip->close();//关闭处理的zip文件 /*--end*/ if(!file_exists($this->data_path.'backup'.DS.$folderName.DS.'www'.DS.'weapp'.DS.$code.DS.'config.php')) { return ['code' => 0, 'msg' => $code."插件目录缺少config.php文件,请联系客服"]; } /*更新的文件列表*/ $upgrade = !empty($val['upgrade']) ? $val['upgrade'] : array(); $upgradeArr = array_merge($upgradeArr, $upgrade); /*--end*/ /*更新的SQL文件列表*/ $sql_file = !empty($val['sql_file']) ? $val['sql_file'] : array(); $sqlfileArr = array_merge($sqlfileArr, $sql_file); /*--end*/ } /*--end*/ /*将多个更新包重新组建一个新的完全更新包*/ $upgradeArr = array_unique($upgradeArr); // 移除文件列表里重复的文件 $sqlfileArr = array_unique($sqlfileArr); // 移除文件列表里重复的文件 $serviceVersion = $lastServiceVersion; $serviceVersion['upgrade'] = $upgradeArr; $serviceVersion['sql_file'] = $sqlfileArr; /*--end*/ /*升级之前,备份涉及的源文件*/ $upgrade = $serviceVersion['upgrade']; if (!empty($upgrade) && is_array($upgrade)) { foreach ($upgrade as $key => $val) { $source_file = $this->root_path.$val; if (file_exists($source_file)) { $destination_file = $this->data_path.'backup'.DS.$code.'-'.$curent_version.'_www'.DS.$val; tp_mkdir(dirname($destination_file)); $copy_bool = @copy($source_file, $destination_file); if (false == $copy_bool) { return ['code' => 0, 'msg' => "更新前备份文件失败,请检查所有目录是否有读写权限"]; } } } } /*--end*/ /*升级的 sql文件*/ if(!empty($serviceVersion['sql_file'])) { foreach($serviceVersion['sql_file'] as $key => $val) { //读取数据文件 $sqlpath = $this->data_path.'backup'.DS.$folderName.DS.'sql'.DS.trim($val); $execute_sql = file_get_contents($sqlpath); $sqlFormat = $this->sql_split($execute_sql, PREFIX); /** * 执行SQL语句 */ try { $counts = count($sqlFormat); for ($i = 0; $i < $counts; $i++) { $sql = trim($sqlFormat[$i]); if (stristr($sql, 'CREATE TABLE')) { Db::execute($sql); } else { if(trim($sql) == '') continue; Db::execute($sql); } } } catch (\Exception $e) { return ['code' => 0, 'msg' => "数据库执行中途失败,请第一时间请求技术支持,否则将影响该插件后续的版本升级!"]; } } } /*--end*/ // 递归复制文件夹 $copy_data = $this->recurse_copy($this->data_path.'backup'.DS.$folderName.DS.'www', rtrim($this->root_path, DS), $folderName); // 清空缓存 delFile(RUNTIME_PATH.'cache'); delFile(RUNTIME_PATH.'temp'); tpCache('global'); /*删除下载的升级包*/ $ziplist = glob($this->data_path.'backup'.DS.'*.zip'); @array_map('unlink', $ziplist); /*--end*/ // 推送回服务器 记录升级成功 $this->UpgradeLog($code, $serviceVersion['key_num']); return ['code' => $copy_data['code'], 'msg' => "升级成功{$copy_data['msg']}"]; } /** * 自定义函数递归的复制带有多级子目录的目录 * 递归复制文件夹 * * @param string $src 原目录 * @param string $dst 复制到的目录 * @param string $folderName 存放升级包目录名称 * @return string */ //参数说明: //自定义函数递归的复制带有多级子目录的目录 private function recurse_copy($src, $dst, $folderName) { static $badcp = 0; // 累计覆盖失败的文件总数 static $n = 0; // 累计执行覆盖的文件总数 static $total = 0; // 累计更新的文件总数 $dir = opendir($src); tp_mkdir($dst); while (false !== $file = readdir($dir)) { if (($file != '.') && ($file != '..')) { if (is_dir($src . '/' . $file)) { $this->recurse_copy($src . '/' . $file, $dst . '/' . $file, $folderName); } else { if (file_exists($src . DIRECTORY_SEPARATOR . $file)) { $rs = @copy($src . DIRECTORY_SEPARATOR . $file, $dst . DIRECTORY_SEPARATOR . $file); if($rs) { $n++; @unlink($src . DIRECTORY_SEPARATOR . $file); } else { $n++; $badcp++; } } else { $n++; } $total++; } } } closedir($dir); $code = 1; $msg = '!'; if($badcp > 0) { $code = 2; $msg = ",其中失败 {$badcp} 个文件,
请从升级包目录[data/backup/{$folderName}/www]中的取出全部文件覆盖到根目录,完成手工升级。"; } $this->copy_speed($n, $total); return ['code'=>$code, 'msg'=>$msg]; } /** * 复制文件进度 */ private function copy_speed($n, $total) { $data = false; if ($n < $total) { $this->copy_speed($n, $total); } else { $data = true; } return $data; } public function sql_split($sql, $tablepre) { $sql = str_replace("`#@__", '`'.$tablepre, $sql); $sql = preg_replace("/TYPE=(InnoDB|MyISAM|MEMORY)( DEFAULT CHARSET=[^; ]+)?/", "ENGINE=\\1 DEFAULT CHARSET=utf8", $sql); $sql = str_replace("\r", "\n", $sql); $ret = array(); $num = 0; $queriesarray = explode(";\n", trim($sql)); unset($sql); foreach ($queriesarray as $query) { $ret[$num] = ''; $queries = explode("\n", trim($query)); $queries = array_filter($queries); foreach ($queries as $query) { $str1 = substr($query, 0, 1); if ($str1 != '#' && $str1 != '-') $ret[$num] .= $query; } $num++; } return $ret; } /** * @param type $fileUrl 下载文件地址 * @param type $md5File 文件MD5 加密值 用于对比下载是否完整 * @return string 错误或成功提示 */ private function downloadFile($fileUrl,$md5File) { $upgradeLogic = new \app\admin\logic\UpgradeLogic; return $upgradeLogic->downloadFile($fileUrl, $md5File, 'weapp'); } // 升级记录 log 日志 private function UpgradeLog($code, $to_key_num){ $serial_number = DEFAULT_SERIALNUMBER; $constsant_path = APP_PATH.MODULE_NAME.'/conf/constant.php'; if (file_exists($constsant_path)) { require_once($constsant_path); defined('SERIALNUMBER') && $serial_number = SERIALNUMBER; } $mysqlinfo = \think\Db::query("SELECT VERSION() as version"); $mysql_version = $mysqlinfo[0]['version']; $values = array( 'domain'=>request()->host(), //用户域名 'code' => $code, // 插件标识 'key_num'=>getWeappVersion($code), // 用户版本号 'to_key_num'=>$to_key_num, // 用户要升级的版本号 'add_time'=>time(), // 升级时间 'serial_number'=>$serial_number, 'ip' => GetHostByName($_SERVER['SERVER_NAME']), 'phpv' => phpversion(), 'mysql_version' => $mysql_version, 'web_server' => $_SERVER['SERVER_SOFTWARE'], ); // api_Weapp_upgradeLog $upgradeLogic = new \app\admin\logic\UpgradeLogic; $upgradeLogic->GetKeyData($values); $url = $this->service_ey.'/index.php?m=api&c=Weapp&a=upgradeLog'; httpRequest($url, 'POST', $values, [], 5); } /** * 兼容早期版本,1.4.5或以下版本 * @param array &$assign_data [description] * @return [type] [description] */ public function installpwd_145(&$assign_data = []) { $main_lang = get_main_lang(); /*插件安装密码设置*/ $weapp_installpwd = tpCache('weapp.weapp_installpwd', [], $main_lang); if (!empty($weapp_installpwd)) { $weapp_installpwd = 1; } else { $weapp_installpwd = 0; } $assign_data['weapp_installpwd'] = $weapp_installpwd; /*--end*/ $admin_info = session('admin_info'); /*是否创始人*/ $isFounder = 0; if (empty($admin_info['parent_id']) && -1 == $admin_info['role_id']) { $isFounder = 1; } $assign_data['isFounder'] = $isFounder; /*--end*/ /*登录第一次输入插件安装密码之后,在退出之前安装所有插件都不再输入安装密码*/ $is_weapp_installpwd = 0; $weapp_installpwd = tpCache('weapp.weapp_installpwd', [], $main_lang); $firstInstallpwd = empty($admin_info['weapp_info']['firstInstallpwd']) ? '' : $admin_info['weapp_info']['firstInstallpwd']; if (!empty($firstInstallpwd) && $firstInstallpwd == $weapp_installpwd) { $is_weapp_installpwd = 1; } $assign_data['is_weapp_installpwd'] = $is_weapp_installpwd; /*--end*/ } /** * 加密函数 * * @access public * @param string $string 字符串 * @param string $operation 操作 * @return string */ public function mchStrCode($string, $operation = 'ENCODE', $auth_code = '') { if (empty($auth_code)) { if (function_exists('get_auth_code')) { $auth_code = get_auth_code(); } else { $auth_code = tpCache('system.system_auth_code'); if (empty($auth_code)) { $auth_code = \think\Config::get('AUTH_CODE'); /*多语言*/ if (is_language()) { $langRow = \think\Db::name('language')->order('id asc')->select(); foreach ($langRow as $key => $val) { tpCache('system', ['system_auth_code'=>$auth_code], $val['mark']); } } else { // 单语言 tpCache('system', ['system_auth_code'=>$auth_code]); } /*--end*/ } } } if (function_exists('mchStrCode')) { return mchStrCode($string, $operation, $auth_code); } $key_length = 4; $expiry = 0; $key = md5($auth_code); $fixedkey = md5($key); $egiskeys = md5(substr($fixedkey, 16, 16)); $runtokey = $key_length ? ($operation == 'ENCODE' ? substr(md5(microtime(true)), -$key_length) : substr($string, 0, $key_length)) : ''; $keys = md5(substr($runtokey, 0, 16) . substr($fixedkey, 0, 16) . substr($runtokey, 16) . substr($fixedkey, 16)); $string = $operation == 'ENCODE' ? sprintf('%010d', $expiry ? $expiry + time() : 0) . substr(md5($string . $egiskeys), 0, 16) . $string : base64_decode(substr($string, $key_length)); $i = 0; $result = ''; $string_length = strlen($string); for ($i = 0; $i < $string_length; $i++) { $result .= chr(ord($string[$i]) ^ ord($keys[$i % 32])); } if ($operation == 'ENCODE') { return $runtokey . str_replace('=', '', base64_encode($result)); } else { $str1 = substr($result, 0, 10); if(version_compare(PHP_VERSION,'8.0.0','>')) { $str1 = !empty($str1) ? $str1 : 0; } if (($str1 == 0 || intval($str1) - time() > 0) && substr($result, 10, 16) == substr(md5(substr($result, 26) . $egiskeys), 0, 16)) { return substr($result, 26); } else { return ''; } } } } ?>