* Date: 2018-4-3
*/
namespace app\admin\logic;
use think\Model;
use think\Db;
/**
* 逻辑定义
* Class CatsLogic
* @package admin\Logic
*/
class ShopLogic extends Model
{
private $request = null;
private $data_path;
private $version_txt_path;
private $version;
private $service_url;
private $upgrade_url;
private $service_ey;
private $planPath_pc;
private $planPath_m;
/**
* 析构函数
*/
function __construct() {
$this->request = request();
$this->service_ey = config('service_ey');
$this->data_path = DATA_PATH; //
$this->version_txt_path = $this->data_path.'conf'.DS.'version_themeshop.txt'; // 版本文件路径
$this->version = getVersion('version_themeshop');
// api_Service_checkVersion
$tmp_str = 'L2luZGV4LnBocD9tPWFwaSZjPVVwZ3JhZGUmYT1jaGVja1RoZW1lVmVyc2lvbg==';
$this->service_url = base64_decode($this->service_ey).base64_decode($tmp_str);
$this->upgrade_url = $this->service_url . '&domain='.request()->host(true).'&v=' . $this->version.'&type=theme_shop&cms_version='.getVersion().'&ip='.serverIP();
$this->planPath_pc = 'template/'.TPL_THEME.'pc/';
$this->planPath_m = 'template/'.TPL_THEME.'mobile/';
}
// 过期订单预处理
public function OverdueOrderHandle()
{
$ShopOrder = Db::name('shop_order')->where('order_status', 4)->column('order_id');
if (!empty($ShopOrder)) {
// 删除条件
$where['order_id'] = ['IN', $ShopOrder];
// 删除订单主表
Db::name('shop_order')->where($where)->delete();
// 删除订单副表
Db::name('shop_order_details')->where($where)->delete();
// 删除订单操作记录表
Db::name('shop_order_log')->where($where)->delete();
}
}
/**
* 检测并第一次从官方同步订单中心的前台模板
*/
public function syn_theme_shop()
{
error_reporting(0);//关闭所有错误报告
$web_users_tpl_theme = tpCache('web.web_users_tpl_theme');
empty($web_users_tpl_theme) && $web_users_tpl_theme = 'users';
$shop_tpl_list = glob("./{$this->planPath_pc}{$web_users_tpl_theme}/shop_*");
if (empty($shop_tpl_list)) {
return $this->OneKeyUpgrade();
} else {
return true;
}
}
/**
* 检测目录权限
*/
public function checkAuthority($filelist = '')
{
/*------------------检测目录读写权限----------------------*/
$filelist = htmlspecialchars_decode($filelist);
$filelist = explode('
', $filelist);
$dirs = array();
$i = -1;
foreach($filelist as $filename)
{
if (stristr($filename, $this->planPath_pc) && !file_exists($this->planPath_pc)) {
continue;
} else if (stristr($filename, $this->planPath_m) && !file_exists($this->planPath_m)) {
continue;
}
$tfilename = $filename;
$curdir = $this->GetDirName($tfilename);
if (empty($curdir)) {
continue;
}
if( !isset($dirs[$curdir]) )
{
$dirs[$curdir] = $this->TestIsFileDir($curdir);
}
if($dirs[$curdir]['isdir'] == FALSE)
{
continue;
}
else {
$dirs[$curdir] = $this->TestIsFileDir($curdir);
}
$i++;
}
$is_pass = true;
$msg = '检测通过';
if($i > -1)
{
$n = 0;
$dirinfos = '';
foreach($dirs as $curdir)
{
$dirinfos .= $curdir['name']." 状态:";
if ($curdir['writeable']) {
$dirinfos .= "[√正常]";
} else {
$is_pass = false;
$n++;
$dirinfos .= "[×不可写]";
}
$dirinfos .= "
";
}
$title = "本次升级需要在下面文件夹写入更新文件,已检测站点有 {$n} 处没有写入权限:
";
$title .= "问题分析(如有问题,请咨询技术支持):
";
$title .= "1、检查站点目录的用户组与所有者,禁止是 root ;
";
$title .= "2、检查站点目录的读写权限,一般权限值是 0755 ;
";
$title .= "涉及更新目录列表如下:
";
$msg = $title . $dirinfos;
}
/*------------------end----------------------*/
if (true === $is_pass) {
return ['code'=>1, 'msg'=>$msg];
} else {
return ['code'=>0, 'msg'=>$msg, 'data'=>['code'=>1]];
}
}
/**
* 检查是否有更新包
* @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;
$serviceVersionList = @httpRequest($url);
if (false === $serviceVersionList) {
$context = stream_context_set_default(array('http' => array('timeout' => 3,'method'=>'GET')));
$serviceVersionList = @file_get_contents($url,false,$context);
}
$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);
foreach ($upgradeArr as $key => $val) {
if (stristr($val, $this->planPath_pc) && !file_exists($this->planPath_pc)) {
unset($upgradeArr[$key]);
} else if (stristr($val, $this->planPath_m) && !file_exists($this->planPath_m)) {
unset($upgradeArr[$key]);
}
}
$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 OneKeyUpgrade() {
$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 = @httpRequest($this->upgrade_url);
if (false === $serviceVersionList) {
$serviceVersionList = @file_get_contents($this->upgrade_url);
}
$serviceVersionList = json_decode($serviceVersionList,true);
if (empty($serviceVersionList)) {
if ('v1.0.1' > $this->version) {
return ['code' => 0, 'msg' => "请求服务器失败,请检查是否网络故障!"];
} else {
return ['code' => 0, 'msg' => "没找到升级信息"];
}
} else if (isset($serviceVersionList['code']) && empty($serviceVersionList['code'])) {
$icon = !empty($serviceVersionList['icon']) ? $serviceVersionList['icon'] : 2;
return ['code' => 0, 'msg' => $serviceVersionList['msg'], 'icon'=>$icon];
}
clearstatcache(); // 清除文件夹权限缓存
if (!is_writeable($this->version_txt_path)) {
return ['code' => 0, 'msg' => '文件'.$this->version_txt_path.' 不可写,不能升级!!!'];
}
/*最新更新版本信息*/
$lastServiceVersion = $serviceVersionList[count($serviceVersionList) - 1];
/*--end*/
/*批量下载更新包*/
$upgradeArr = array(); // 更新的文件列表
$folderName = 'shop-'.$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 = 'shop-'.str_replace(".zip", "", $lastDownFileName); // 文件夹
/*--end*/
/*解压之前,删除已重复的文件夹*/
delFile($this->data_path.'backup'.DS.'theme'.DS.$folderName);
/*--end*/
}
/*--end*/
$downFileName = explode('/', $val['down_url']);
$downFileName = 'shop-'.end($downFileName);
/*解压文件*/
$zip = new \ZipArchive();//新建一个ZipArchive的对象
if ($zip->open($this->data_path.'backup'.DS.'theme'.DS.$downFileName) != true) {
return ['code' => 0, 'msg' => "升级包读取失败!"];
}
$zip->extractTo($this->data_path.'backup'.DS.'theme'.DS.$folderName.DS);//假设解压缩到在当前路径下backup文件夹内
$zip->close();//关闭处理的zip文件
/*--end*/
if (!file_exists($this->data_path.'backup'.DS.'theme'.DS.$folderName.DS.'data'.DS.'conf'.DS.'version_themeshop.txt')) {
return ['code' => 0, 'msg' => "缺少version_themeshop.txt文件,请联系客服"];
}
/*更新的文件列表*/
$upgrade = !empty($val['upgrade']) ? $val['upgrade'] : array();
$upgradeArr = array_merge($upgradeArr, $upgrade);
/*--end*/
}
/*--end*/
/*将多个更新包重新组建一个新的完全更新包*/
$upgradeArr = array_unique($upgradeArr); // 移除文件列表里重复的文件
$serviceVersion = $lastServiceVersion;
$serviceVersion['upgrade'] = $upgradeArr;
/*--end*/
/*升级之前,备份涉及的源文件*/
$upgrade = $serviceVersion['upgrade'];
if (!empty($upgrade) && is_array($upgrade)) {
foreach ($upgrade as $key => $val) {
$source_file = ROOT_PATH.$val;
if (file_exists($source_file)) {
$destination_file = $this->data_path.'backup'.DS.'theme'.DS.$folderName.'_www'.DS.$val;
tp_mkdir(dirname($destination_file));
$copy_bool = @copy($source_file, $destination_file);
if (false == $copy_bool) {
return ['code' => 0, 'msg' => "更新前备份文件失败,请检查所有目录是否有读写权限"];
}
}
}
}
/*--end*/
// 递归复制文件夹
$copy_data = $this->recurse_copy($this->data_path.'backup'.DS.'theme'.DS.$folderName, rtrim(ROOT_PATH, DS), $folderName);
/*删除下载的升级包*/
$ziplist = glob($this->data_path.'backup'.DS.'theme'.DS.'shop-*.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);
/*pc和mobile目录存在的情况下,才拷贝会员模板到相应的pc或mobile里*/
$dst_tmp = str_replace('\\', '/', $dst);
$dst_tmp = rtrim($dst_tmp, '/').'/';
if (stristr($dst_tmp, $this->planPath_pc) && file_exists($this->planPath_pc)) {
tp_mkdir($dst);
} else if (stristr($dst_tmp, $this->planPath_m) && file_exists($this->planPath_m)) {
tp_mkdir($dst);
}
/*--end*/
while (false !== $file = readdir($dir)) {
if (($file != '.') && ($file != '..')) {
if (is_dir($src . '/' . $file)) {
$needle = '/template/'.TPL_THEME;
$needle = rtrim($needle, '/');
$dstfile = $dst . '/' . $file;
if (!stristr($dstfile, $needle)) {
$dstfile = str_replace('/template', $needle, $dstfile);
}
$this->recurse_copy($src . '/' . $file, $dstfile, $folderName);
}
else {
if (file_exists($src . DIRECTORY_SEPARATOR . $file)) {
/*pc和mobile目录存在的情况下,才拷贝会员模板到相应的pc或mobile里*/
$rs = true;
$src_tmp = str_replace('\\', '/', $src . DIRECTORY_SEPARATOR . $file);
if (stristr($src_tmp, $this->planPath_pc) && !file_exists($this->planPath_pc)) {
continue;
} else if (stristr($src_tmp, $this->planPath_m) && !file_exists($this->planPath_m)) {
continue;
}
/*--end*/
$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/theme/{$folderName}]中的取出全部文件覆盖到根目录,完成手工升级。";
}
$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;
}
/**
* @param type $fileUrl 下载文件地址
* @param type $md5File 文件MD5 加密值 用于对比下载是否完整
* @return string 错误或成功提示
*/
private function downloadFile($fileUrl,$md5File)
{
$downFileName = explode('/', $fileUrl);
$downFileName = 'shop-'.end($downFileName);
$saveDir = $this->data_path.'backup'.DS.'theme'.DS.$downFileName; // 保存目录
tp_mkdir(dirname($saveDir));
$content = @httpRequest($fileUrl);
if (false === $content) {
$content = @file_get_contents($fileUrl, 0, null, 0, 1);
}
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);
curl_close ($ch);
} else {
$file = httpRequest($fileUrl);
}
if (preg_match('#__HALT_COMPILER()#i', $file)) {
return ['code' => 0, 'msg' => '下载包损坏,请联系官方客服!'];
}
$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'];
$values = array(
'type' => 'theme_shop',
'domain'=>request()->host(), //用户域名
'key_num'=>$this->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 = 'L2luZGV4LnBocD9tPWFwaSZjPVVwZ3JhZGUmYT11cGdyYWRlTG9nJg==';
$url = base64_decode($this->service_ey).base64_decode($tmp_str).http_build_query($values);
@httpRequest($url);
}
/**
* 获取文件的目录路径
* @param string $filename 文件路径+文件名
* @return string
*/
private function GetDirName($filename)
{
$dirname = preg_replace("#[\\\\\/]{1,}#", '/', $filename);
$dirname = preg_replace("#([^\/]*)$#", '', $dirname);
return $dirname;
}
/**
* 测试目录路径是否有读写权限
* @param string $dirname 文件目录路径
* @return array
*/
private function TestIsFileDir($dirname)
{
$dirs = array('name'=>'', 'isdir'=>FALSE, 'writeable'=>FALSE);
$dirs['name'] = $dirname;
tp_mkdir($dirname);
if(is_dir($dirname))
{
$dirs['isdir'] = TRUE;
$dirs['writeable'] = $this->TestWriteAble($dirname);
}
return $dirs;
}
/**
* 测试目录路径是否有写入权限
* @param string $d 目录路劲
* @return boolean
*/
private function TestWriteAble($d)
{
$tfile = '_eyout.txt';
$fp = @fopen($d.$tfile,'w');
if(!$fp) {
return false;
}
else {
fclose($fp);
$rs = @unlink($d.$tfile);
return true;
}
}
/**
* 列出营销功能里已使用的模块
* @return [type] [description]
*/
public function marketLogic()
{
// 列出已使用的功能模块
$func = [];
// 整点秒杀功能是否被用
$sharpCount = Db::name('sharp_goods')->where(['sharp_goods_id'=>['gt',0]])->count();
if (!empty($sharpCount)) {
$func[] = 'sharp';
}
// 优惠券功能是否被用
$couponCount = Db::name('shop_coupon')->where(['coupon_id'=>['gt',0]])->count();
if (!empty($couponCount)) {
$func[] = 'coupon';
}
// 返回值
return $func;
}
/**
* 列出功能地图里已使用的模块
* @return [type] [description]
*/
public function useFuncLogic()
{
// 列出已使用的功能模块
$func = [];
// 积分兑换功能是否被用 - 暂时不禁用了,先留着用
$memgiftCount = Db::name('memgift')->where(['gift_id'=>['gt',0]])->count();
if (true || !empty($memgiftCount)) {
$func[] = 'memgift';
}
// 返回值
return $func;
}
}