* Date: 2018-4-3
*/
namespace app\admin\logic;
use think\Model;
use think\Db;
class UpgradeLogic extends Model
{
public $root_path;
public $data_path;
public $version_txt_path;
public $curent_version;
public $service_url;
public $upgrade_url;
public $service_ey;
/**
* 析构函数
*/
function __construct() {
$this->service_ey = config('service_ey');
$this->root_path = ROOT_PATH; //
$this->data_path = DATA_PATH; //
$this->version_txt_path = $this->data_path.'conf'.DS.'version.txt'; // 版本文件路径
$this->curent_version = getCmsVersion();
// api_Service_checkVersion
$upgrade_dev = config('global.upgrade_dev');
/*安全补丁*/
$security_patch = tpSetting('upgrade.upgrade_security_patch'); // 是否开启
$version_security = getVersion('version_security'); // 补丁版本号
/* END */
$tmp_str = 'L2luZGV4LnBocD9tPWFwaSZjPVNlcnZpY2UmYT1jaGVja1ZlcnNpb24=';
$this->service_url = base64_decode($this->service_ey).base64_decode($tmp_str);
$this->upgrade_url = $this->service_url . '&domain='.request()->host(true).'&v=' . $this->curent_version . '&dev=' . $upgrade_dev . '&security_patch=' . $security_patch . '&version_security=' . $version_security;
}
/**
* 检查是否有更新包
* @return type 提示语
*/
public function checkVersion() {
//error_reporting(0);//关闭所有错误报告
$allow_url_fopen = ini_get('allow_url_fopen');
if (!$allow_url_fopen) {
return ['code' => 1, 'msg' => "请联系空间商(设置 php.ini 中参数 allow_url_fopen = 1)"];
}
$url = $this->upgrade_url;
$context = stream_context_set_default(array('http' => array('timeout' => 5,'method'=>'GET')));
$serviceVersionList = @file_get_contents($url,false,$context);
if (false === $serviceVersionList) {
$serviceVersionList = httpRequest($url);
}
$serviceVersionList = json_decode($serviceVersionList,true);
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); // 升级提示需要覆盖哪些文件
tpCache('system', ['system_upgrade_filelist'=>base64_encode($lastupgrade['upgrade'])]);
/*升级公告*/
if (!empty($lastupgrade['notice'])) {
$lastupgrade['notice'] = htmlspecialchars_decode($lastupgrade['notice']) . '
';
}
/*--end*/
return ['code' => 2, 'msg' => $lastupgrade];
}
return ['code' => 1, 'msg' => '已是最新版'];
}
/**
* 检查是否有安全补丁包
* @return type 提示语
*/
public function checkSecurityVersion() {
// error_reporting(0);//关闭所有错误报告
$allow_url_fopen = ini_get('allow_url_fopen');
if (empty($allow_url_fopen)) {
return ['code' => 1, 'msg' => "请联系空间商(设置 php.ini 中参数 allow_url_fopen = 1)"];
}
$context = stream_context_set_default(array('http' => array('timeout' => 5, 'method' => 'GET')));
$serviceVersionList = @file_get_contents($this->upgrade_url, false, $context);
if (false === $serviceVersionList) {
$serviceVersionList = httpRequest($this->upgrade_url);
}
$serviceVersionList = json_decode($serviceVersionList, true);
if(!empty($serviceVersionList)) {
/* 插件过期则执行 */
if (isset($serviceVersionList['maturity']) && 1 == $serviceVersionList['maturity']) {
$WeappUrl = weapp_url('Security/Security/index');
$msg = ' [安全补丁升级] ';
$remind = str_replace("[安全补丁升级]", $msg, $serviceVersionList['remind']);
return ['code' => 0, 'msg' => $remind];
}
/* END */
$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); // 升级提示需要覆盖哪些文件
tpCache('system', ['system_upgrade_filelist' => base64_encode($lastupgrade['upgrade'])]);
/*升级公告*/
if (!empty($lastupgrade['notice'])) {
$lastupgrade['notice'] = htmlspecialchars_decode($lastupgrade['notice']) . '
';
}
/*--end*/
return ['code' => 2, 'msg' => $lastupgrade];
}
return ['code' => 1, 'msg' => '已是最新补丁版'];
}
/**
* 查询安全补丁升级插件订单
*/
public function checkSecurityOrder() {
$context = stream_context_set_default(array('http' => array('timeout' => 5, 'method' => 'GET')));
$SecurityOrder = @file_get_contents($this->upgrade_url . '&get_order=1', false, $context);
if (false === $SecurityOrder) {
$SecurityOrder = httpRequest($this->upgrade_url);
}
$SecurityOrder = json_decode($SecurityOrder, true);
if (!empty($SecurityOrder)) return $SecurityOrder;
}
/**
* 一键更新
*/
public function OneKeyUpgrade(){
error_reporting(0);//关闭所有错误报告
$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扩展"];
}
$serviceVersionList = @file_get_contents($this->upgrade_url);
if (false === $serviceVersionList) {
$serviceVersionList = httpRequest($this->upgrade_url);
}
if (false === $serviceVersionList) {
return ['code' => 0, 'msg' => "无法连接远程升级服务器!"];
} else {
$serviceVersionList = json_decode($serviceVersionList,true);
if (empty($serviceVersionList)) {
return ['code' => 0, 'msg' => "当前没有可升级的版本!"];
}
}
clearstatcache(); // 清除文件夹权限缓存
/*$quanxuan = substr(base_convert(@fileperms($this->data_path),10,8),-4);
if(!in_array($quanxuan,array('0777','0755','0666','0662','0622','0222')))
return "网站根目录不可写,无法升级.";*/
if (!is_writeable($this->version_txt_path)) {
return ['code' => 0, 'msg' => '文件'.$this->version_txt_path.' 不可写,不能升级!!!'];
}
/*最新更新版本信息*/
$lastServiceVersion = $serviceVersionList[count($serviceVersionList) - 1];
/*--end*/
/*批量下载更新包*/
$upgradeArr = array(); // 更新的文件列表
$sqlfileArr = array(); // 更新SQL文件列表
$folderName = $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 = 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.'data'.DS.'conf'.DS.'version.txt')) {
return ['code' => 0, 'msg' => "缺少version.txt文件,请联系客服"];
}
if (file_exists($this->data_path.'backup'.DS.$folderName.DS.'www'.DS.'application'.DS.'database.php')) {
return ['code' => 0, 'msg' => "不得修改数据库配置文件,请联系客服"];
}
/*更新的文件列表*/
$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, $val['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.$this->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' => -2, 'msg' => "数据库执行中途失败,请查看官方解决教程,否则将影响后续的版本升级!"];
}
}
}
/*--end*/
// 递归复制文件夹
$copy_data = $this->recurse_copy($this->data_path.'backup'.DS.$folderName.DS.'www', rtrim($this->root_path, DS), $folderName);
/*覆盖自定义后台入口文件*/
$login_php = 'login.php';
$rootLoginFile = $this->data_path.'backup'.DS.$folderName.DS.'www'.DS.$login_php;
if (file_exists($rootLoginFile)) {
$adminbasefile = preg_replace('/^(.*)\/([^\/]+)$/i', '$2', request()->baseFile());
if ($login_php != $adminbasefile && is_writable($this->root_path.$adminbasefile)) {
if (!@copy($rootLoginFile, $this->root_path.$adminbasefile)) {
return ['code' => 0, 'msg' => "更新入口文件失败,请第一时间请求技术支持,否则将影响部分功能的使用!"];
}
@unlink($this->root_path.$login_php);
}
}
/*--end*/
/*多语言*/
if (is_language()) {
$langRow = \think\Db::name('language')->order('id asc')
->select();
foreach ($langRow as $key => $val) {
tpCache('system',['system_version'=>$serviceVersion['key_num']], $val['mark']); // 记录版本号
}
} else { // 单语言
tpCache('system',['system_version'=>$serviceVersion['key_num']]); // 记录版本号
}
/*--end*/
// 清空缓存
delFile(rtrim(RUNTIME_PATH, '/'));
tpCache('global');
// 清空检测标记
$s_key = 'aXNzZXRfYXV0aG9y';
$s_key = base64_decode($s_key);
session($s_key, null);
/*删除下载的升级包*/
$ziplist = glob($this->data_path.'backup'.DS.'*.zip');
@array_map('unlink', $ziplist);
/*--end*/
// 推送回服务器 记录升级成功
$this->UpgradeLog($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;
}
private function sql_split($sql, $tablepre) {
if ($tablepre != "ey_")
$sql = str_replace("`ey_", '`'.$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)
{
$downFileName = explode('/', $fileUrl);
$downFileName = end($downFileName);
$saveDir = $this->data_path.'backup'.DS.$downFileName; // 保存目录
tp_mkdir(dirname($saveDir));
$content = @file_get_contents($fileUrl, 0, null, 0, 1);
if (false === $content) {
$fileUrl = str_replace('http://service', 'https://service', $fileUrl);
$content = @file_get_contents($fileUrl, 0, null, 0, 1);
if (false === $content) {
$content = httpRequest($fileUrl);
}
}
if(!$content){
return ['code' => 0, 'msg' => '官方升级包不存在']; // 文件存在直接退出
}
if (!stristr($fileUrl, 'https://service')) {
$ch = curl_init($fileUrl);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_BINARYTRANSFER,1);
$file = curl_exec ($ch);
} else {
$file = httpRequest($fileUrl);
}
if (preg_match('#__HALT_COMPILER()#i', $file)) {
return ['code' => 0, 'msg' => '下载包损坏,请联系官方客服!'];
}
curl_close ($ch);
$fp = fopen($saveDir,'w');
fwrite($fp, $file);
fclose($fp);
if(!eyPreventShell($saveDir) || !file_exists($saveDir) || $md5File != md5_file($saveDir))
{
return ['code' => 0, 'msg' => '下载保存升级包失败,请检查所有目录的权限以及用户组不能为root'];
}
return ['code' => 1, 'msg' => '下载成功'];
}
// 升级记录 log 日志
private function UpgradeLog($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'];
$vaules = array(
'domain'=>$_SERVER['HTTP_HOST'], //用户域名
'key_num'=>$this->curent_version, // 用户版本号
'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_Service_upgradeLog
$tmp_str = 'L2luZGV4LnBocD9tPWFwaSZjPVNlcnZpY2UmYT11cGdyYWRlTG9nJg==';
$url = base64_decode($this->service_ey).base64_decode($tmp_str).http_build_query($vaules);
httpRequest($url);
}
}
?>