<?php
/**
 * 日志统计
 * @作者 苏相锟 <admin@phpok.com>
 * @主页 https://www.phpok.com
 * @版本 5.x
 * @授权 GNU Lesser General Public License https://www.phpok.com/lgpl.html 
 * @时间 2022年2月15日
**/

/**
 * 安全限制，防止直接访问
**/
if(!defined("PHPOK_SET")){
	exit("<h1>Access Denied</h1>");
}

class stat_model_base extends phpok_model
{
	private $folder = 'log-stat';
	private $ip2city = false;
	private $method = 'local';
	public function __construct()
	{
		parent::model();
		$this->folder = $this->dir_data.'log-stat/';
	}

	public function data($date='')
	{
		if(!$date){
			$date = date("Ymd",$this->time);
		}
		$chkdate = date("Ymd",$this->time);
		if($date == $chkdate){
			return $this->_data2file($date);
		}
		return $this->_data2sql($date);
	}

	public function get_list($condition='',$offset=0,$psize=0)
	{
		$sql = "SELECT * FROM ".$this->db->prefix."stat ";
		if($condition){
			$sql .= " WHERE ".$condition." ";
		}
		$sql .= " ORDER BY dateinfo ASC ";
		if($psize){
			$sql .= " LIMIT ".$offset.",".$psize;
		}
		return $this->db->get_all($sql);
	}

	private function _data2file($date)
	{
		$file = $this->folder.$date.'.php';
		if(!file_exists($file)){
			return false;
		}
		$pv = $uv = $ip = 0; //总PV数
		$pvlist = $iplist = $uvlist = array();
		$iplist_sub = $uvlist_sub = array();
		$list = array();
		$handle = fopen($file,'rb');
		if(!$handle){
			return false;
		}
		while (!feof($handle)) {
			$line = fgets($handle);
			$line = trim($line);
			if(!$line){
				continue;
			}
			if($line == "<?php die('forbidden'); ?>"){
				continue;
			}
			$tmp = $this->_line2array($date,$line);
			$pv++;
			if(!in_array($tmp['ip'],$iplist)){
				$ip++;
				$iplist[] = $tmp['ip'];
			}
			if(!in_array($tmp['session_id'],$uvlist)){
				$uv++;
				$uvlist[] = $tmp['session_id'];
			}
			$id = $this->_id($tmp);
			if(!$pvlist || !$pvlist[$id]){
				$pvlist[$id] = $tmp;
				$pvlist[$id]['pv'] = $pvlist[$id]['uv'] = $pvlist[$id]['ip'] = 0;
				$iplist_sub[$id] = array();
				$uvlist_sub[$id] = array();
			}
			$pvlist[$id]['pv'] = $pvlist[$id]['pv']+1;
			if(!isset($iplist_sub[$id])){
				$iplist_sub[$id]  = array();
			}
			if(!isset($uvlist_sub[$id])){
				$uvlist_sub[$id]  = array();
			}
			if(!in_array($tmp['ip'],$iplist_sub[$id])){
				$pvlist[$id]['ip'] = $pvlist[$id]['ip']+1;
				$iplist_sub[$id][] = $tmp['ip'];
			}
			if(!in_array($tmp['session_id'],$uvlist_sub[$id])){
				$pvlist[$id]['uv'] = $pvlist[$id]['uv']+1;
				$uvlist_sub[$id][] = $tmp['session_id'];
				if(!$pvlist[$id]['firstlogin']){
					$pvlist[$id]['firstlogin'] = $tmp['dateline'];
					$pvlist[$id]['lastlogin'] = $tmp['dateline'];
				}else{
					if($pvlist[$id]['firstlogin'] > $tmp['dateline']){
						$pvlist[$id]['firstlogin'] = $tmp['dateline'];
					}
					if($pvlist[$id]['lastlogin'] < $tmp['dateline']){
						$pvlist[$id]['lastlogin'] = $tmp['dateline'];
					}
				}
			}
		}
		fclose($handle);
		$data = array();
		$data['dateinfo'] = $date;
		$data['pv'] = $pv;
		$data['uv'] = $uv;
		$data['ip'] = $ip;
		$data['list'] = $pvlist;
		return $data;
	}

	private function _line2array($date,$line)
	{
		$info = array();
		$info['e_year'] = substr($date,0,4);
		$info['e_month'] = substr($date,4,2);
		$info['e_day'] = substr($date,6,2);
		$tmp = explode(" --- ",$line);
		$info['e_hour'] = date("H",strtotime($tmp[0]));
		$info['e_browser'] = trim($tmp[11]);
		$info['e_version'] = trim($tmp[13]);
		$info['e_country'] = trim($tmp[4]);
		$info['e_province'] = trim($tmp[5]);
		$info['e_city'] = trim($tmp[6]);
		$info['e_net'] = trim($tmp[7]);
		$info['e_device'] = trim($tmp[14]);
		$info['e_pixel'] = trim($tmp[9]).'x'.trim($tmp[10]);
		$info['e_os'] = trim($tmp[18]);
		$info['ip'] = trim($tmp[1]);
		$info['session_id'] = trim($tmp[2]);
		$info['user_id'] = trim($tmp[3]);
		$info['dateline'] = strtotime($tmp[0]);//转时间
		return $info;
	}

	private function _data2sql($date)
	{
		$sql = "SELECT * FROM ".$this->db->prefix."stat WHERE dateinfo='".$date."'";
		$rs = $this->db->get_one($sql);
		if(!$rs){
			$data = $this->_data2file($date);
			$this->_save($data);
			return $data;
		}
		$id = $rs['id'];
		$data = $rs;
		$sql = "SELECT * FROM ".$this->db->prefix."stat_info WHERE stat_id='".$id."'";
		$tmplist = $this->db->get_all($sql);
		if(!$tmplist){
			return false;
		}
		$list = array();
		foreach($tmplist as $key=>$value){
			$tmp = $value;
			unset($tmp['id'],$tmp['stat_id']);
			$list[] = $tmp;
		}
		$data['list'] = $list;
		return $data;
	}

	private function _id($rs)
	{
		$id  = $rs['e_year'].'-'.$rs['e_month'].'-'.$rs['e_day'].'-'.$rs['e_hour'];
		$id .= '-'.$rs['e_browser'].'-'.$rs['e_version'].'-'.$rs['e_country'];
		$id .= '-'.$rs['e_province'].'-'.$rs['e_city'].'-'.$rs['e_net'];
		$id .= '-'.$rs['e_device'].'-'.$rs['e_pixel'].'-'.$rs['e_os'];
		return 'd'.md5($id);
	}

	/**
	 * 保存统计好的日志
	**/
	private function _save($data)
	{
		if(!$data || !is_array($data) || !$data['list']){
			return false;
		}
		$info = $data['list'];
		unset($data['list']);
		$sql = "SELECT * FROM ".$this->db->prefix."stat WHERE dateinfo='".$data['dateinfo']."'";
		$chk = $this->db->get_one($sql);
		if($chk){
			return false;
		}
		$id = $this->db->insert(array("dateinfo"=>$data['dateinfo'],'pv'=>$data['pv'],'ip'=>$data['ip'],'uv'=>$data['uv']),'stat');
		if(!$id){
			return false;
		}
		$ids = array('stat_id','e_year','e_month','e_day','e_hour','e_browser','e_version','e_country','e_province','e_city','e_net');
		$ids[] = 'e_device';
		$ids[] = 'e_pixel';
		$ids[] = 'e_os';
		$ids[] = 'pv';
		$ids[] = 'uv';
		$ids[] = 'ip';
		$ids[] = 'firstlogin';
		$ids[] = 'lastlogin';
		
		
		$tmplist = array_chunk($info,200);
		foreach($tmplist as $k=>$v){
			if(!$v || !is_array($v)){
				continue;
			}
			$sql = "REPLACE INTO ".$this->db->prefix."stat_info(`".implode("`,`",$ids)."`) VALUES";
			$list = array();
			foreach($v as $key=>$value){
				$tmp = array();
				$tmp['stat_id'] = $id;
				$tmp['e_year'] = $value['e_year'];
				$tmp['e_month'] = $value['e_month'];
				$tmp['e_day'] = $value['e_day'];
				$tmp['e_hour'] = $value['e_hour'];
				$tmp['e_browser'] = $value['e_browser'];
				$tmp['e_version'] = $value['e_version'];
				$tmp['e_country'] = $value['e_country'];
				$tmp['e_province'] = $value['e_province'];
				$tmp['e_city'] = $value['e_city'];
				$tmp['e_net'] = $value['e_net'];
				$tmp['e_device'] = $value['e_device'];
				$tmp['e_pixel'] = $value['e_pixel'];
				$tmp['e_os'] = $value['e_os'];
				$tmp['pv'] = $value['pv'];
				$tmp['uv'] = $value['uv'];
				$tmp['ip'] = $value['ip'];
				$tmp['firstlogin'] = $value['firstlogin'];
				$tmp['lastlogin'] = $value['lastlogin'];
				$tmpsql = "('".implode("','",$tmp)."')";
				$list[] = $tmpsql;
			}
			$sql .= implode(",",$list);
			$this->db->query($sql); 
		}
		return true;
	}

	private function _save2info($rs)
	{
		$sql  = " SELECT * FROM ".$this->db->prefix."stat_info WHERE stat_id='".$rs['stat_id']."' ";
		$sql .= " AND e_year='".$rs['e_year']."' ";
		$sql .= " AND e_month='".$rs['e_month']."' ";
		$sql .= " AND e_day='".$rs['e_day']."' ";
		$sql .= " AND e_hour='".$rs['e_hour']."' ";
		$sql .= " AND e_browser='".$rs['e_browser']."' ";
		$sql .= " AND e_version='".$rs['e_version']."' ";
		$sql .= " AND e_country='".$rs['e_country']."' ";
		$sql .= " AND e_province='".$rs['e_province']."' ";
		$sql .= " AND e_city='".$rs['e_city']."' ";
		$sql .= " AND e_net='".$rs['e_net']."' ";
		$sql .= " AND e_device='".$rs['e_device']."' ";
		$sql .= " AND e_pixel='".$rs['e_pixel']."' ";
		$sql .= " AND e_os='".$rs['e_os']."' ";
		$chk = $this->db->get_one($sql);
		if($chk){
			$sql = "UPDATE ".$this->db->prefix."stat_info SET pv=pv+".$rs['pv'].",uv=uv+".$rs['uv'].",ip=ip+".$rs['ip']." WHERE id='".$chk['id']."'";
			return $this->db->query($sql);
		}
		return $this->db->insert($rs,'stat_info');
	}

	public function folder($folder='')
	{
		if($folder){
			$folder = $this->dir_data.$folder.'/';
			if(!file_exists($folder)){
				$this->lib('file')->make($folder);
			}
			$this->folder = $folder;
		}
		return $this->folder;
	}

	public function ip2city($status='')
	{
		if($status){
			$this->ip2city = true;
		}else{
			if(is_bool($status)){
				$this->ip2city = false;
			}
		}
		return $this->ip2city;
	}

	public function method($type='')
	{
		if($type){
			$this->method = $type;
		}
		return $this->method;
	}

	public function add($e)
	{
		$ip = $this->lib('common')->ip();
		$data = array();
		$data['datetime'] = date("Y-m-d H:i:s",$this->time);
		$data['ip'] = $ip;
		$data['session_id'] = $this->session->sessid();
		$data['user_id'] = $this->session->val('user_id') ? $this->session->val('user_id') : '*';
		$data['country'] = $data['province'] = $data['city'] = $data['net'] = '*';
		if($this->ip2city){
			$pcan = $this->_pcan($ip);
			if($pcan){
				$data['country'] = $pcan['country'];
				$data['province'] = $pcan['province'];
				$data['city'] = $pcan['city'];
				$data['net'] = $pcan['net'];
			}
		}
		
		$data['referer'] = $this->lib('server')->referer();
		$data['width'] = $e['width'];
		$data['height'] = $e['height'];
		$agent = $this->lib('server')->agent();
		$b = $this->_browser($agent,$this->config['is_mobile']);
		if($b){
			$data['title'] = $b['title'];
			$data['browser'] = $b['browser'];
			$data['version'] = $b['version'];
		}else{
			$data['title'] = $data['browser'] = $data['version'] = "*";
		}
		$data['mobile'] = $this->config['is_mobile'] ? 'Mobile' : "PC";
		$language = $_SERVER['HTTP_ACCEPT_LANGUAGE'] ? $_SERVER['HTTP_ACCEPT_LANGUAGE'] : '*';
		if($language != '*'){
			$tmp = explode(",",$language);
			$language = $tmp[0];
		}
		$data['language'] = $language;
		$data['url'] = $e['url'];
		$data['agent'] = $agent;
		$data['os'] = $this->_os($agent);//操作系统
		$day = date("Ymd",$this->time);
		if(!file_exists($this->folder)){
			$this->lib('file')->make($this->folder);
		}
		$content = implode(" --- ",$data);
		$file = $this->folder.$day.'.php';
		if(!file_exists($file)){
			$this->lib('file')->vi($content."\n",$file);
		}else{
			$this->lib('file')->vi($content."\n",$file,'','ab');
		}
		return true;
	}

	/**
	 * 内网判断，返回 true 表示内容，返回 false 表示非内网
	**/
	private function _intranet($ip)
	{
		$tmparray = explode(".",$ip);
		//用于到本地主机的环回地址。
		if($tmparray[0] == '10' || $tmparray[0] == '127'){
			return true;
		}
		if($tmparray[0] == '192' && $tmparray[1] == '168'){
			return true;
		}
		//用于专用网络中的本地通信
		if($tmparray[0] == '172' && (intval($tmparray[1])>=16 && intval($tmparray[2])<=31)){
			return true;
		}
		//用于在电信级NAT环境中服务提供商与其用户通信。
		if($tmparray[0] == '100' && (intval($tmparray[1])>=64 && intval($tmparray[2])<=127)){
			return true;
		}
		return false;
	}

	/**
	 * 主要 ipv4 获取到的地址信息
	**/
	private function _pcan($ip)
	{
		//最常见的本地IP 
		if($ip == '::1' || $ip == '127.0.0.1'){
			return false;
		}
		//检测内网
		if($this->_intranet($ip)){
			return false;
		}
		$tmpfile = md5($ip);
		$file = $this->dir_cache.$tmpfile.'.php';
		if(file_exists($file)){
			$tmp = $this->lib('file')->cat($file);
			if($tmp == 'error'){
				return false;
			}
			$list = explode(",",$tmp);
			$data = array();
			$data['country'] = $list[0];
			$data['province'] = $list[1];
			$data['city'] = $list[2];
			$data['net'] = $list[3];
			return $data;
		}
		//使用OK方法获取远程IP数据
		if($this->method == 'phpok'){
			$config = $this->model('config')->get_all();
			if(!$config){
				return false;
			}
			if(!$config['ok_status'] || !$config['ok_appid'] || !$config['ok_appkey'] || !$config['ok_domain'] || !$config['ok_list']){
				return false;
			}
			$this->lib('phpok')->app_id($config['ok_appid']);
			$this->lib('phpok')->app_key($config['ok_appkey']);
			$this->lib('phpok')->server_url('https://'.$config['ok_domain'].'/apix-24920');
			$info = $this->lib('phpok')->content(array('ip'=>$ip));
			if(!$info || !$info['status']){
				$this->lib('file')->vi('error',$file);
				return false;
			}
			$data = $info['info'];
			$this->lib('file')->vi(implode(",",$data),$file);
			return $data;
		}
		
		//本地执行，仅限本地安装了 ip2region 类时有效
		//检测 ip2region 存在与否 $vfile[] = $this->dir_extension.$class.'/index.php';
		$libfile = $this->dir_extension.'ip2region/index.php';
		if(!file_exists($libfile)){
			$this->lib('file')->vi('error',$file);
			return false;
		}
		$data = $this->lib('ip2region')->ip2city($ip);
		return $data;
	}

	private function _os($agent)
	{
		if(stripos($agent,'Android')!==false){
			preg_match('/android\s([\d\.]+)/i', $agent, $match);
			return 'Android '.$match[1];
		}else if(stripos($agent,'BlackBerry')!==false || stripos($agent,'PlayBook')){
			preg_match('/BlackBerry\s([0-9_\.]+)/i', $agent, $match);
			return 'BlackBerry '.$match[1];
		}else if(stripos($agent,'(iPhone')!==false){
			preg_match('/OS\s([0-9_\.]+)/i', $agent, $match);
            $os_ver = str_replace('_','.',$match[1]);
            return 'iPhone '.$os_ver;
		}else if(stripos($agent,'iPad')!==false){
			preg_match('/OS\s([0-9_\.]+)/i', $agent, $match);
            $os_ver = str_replace('_','.',$match[1]);
            return 'iPad '.$os_ver;
		}else if(stripos($agent,'CrOS')!==false){
			return 'Chrome OS';
		}else if(stripos($agent,'Mac')!==false){
			preg_match('/Mac OS X\s([0-9_\.]+)/i', $agent, $match);
            $os_ver = str_replace('_','.',$match[1]);
            if($os_ver){
	            return 'Mac OS X'.$os_ver;
            }
            return 'Mac OS';
		}else if(stripos($agent,'Windows')!==false){
			$info = "Windows";
			$os_ver = '';
			if (preg_match('/nt 6.0/i', $agent)) {
                $os_ver = 'Vista';
            }elseif(preg_match('/nt 11/i', $agent)) {
                $os_ver = '11';
            }elseif(preg_match('/nt 10/i', $agent)) {
                $os_ver = '10';
            }elseif(preg_match('/nt 6.3/i', $agent)) {
                $os_ver = '8.1';
            }elseif(preg_match('/nt 6.2/i', $agent)) {
                $os_ver = '8.0';
            }elseif(preg_match('/nt 6.1/i', $agent)) {
                $os_ver = '7';
            }elseif(preg_match('/nt 5.1/i', $agent)) {
                $os_ver = 'XP';
            }elseif(preg_match('/nt 5/i', $agent)) {
                $os_ver = '2000';
            }elseif(preg_match('/nt 98/i', $agent)) {
                $os_ver = '98';
            }elseif(preg_match('/nt/i', $agent)) {
                $os_ver = 'NT';
            }
            if($os_ver){
	            $info .= ' '.$os_ver;
            }
            return $info;
		}else if(stripos($agent,'Linux')!==false){
			return "Linux";
		}
		return false;
	}

	private function _browser($agent,$ismobile=false)
	{
		$b = "";   //浏览器
		$v = ""; //浏览器的版本

		//IE 版本判断（基本上已经无效）
		preg_match('/MSIE ([0-9].[0-9]{1,2})/isU',$agent,$matches);
		if($matches){
			$version = $matches[1];
			$name = "IE浏览器";
			$tmp = array('title'=>'Internet Explorer','version'=>$version,'browser'=>'IE');
			return $tmp;
		}

		//其他浏览器的判断
		$list = array();
		$list['OPR'] = "欧朋";
		$list['Opera'] = "欧朋";
		$list['Edg'] = "Edge";
		$list['Firefox'] = "火狐";
		$list['UCBrowser'] = "UC";
		$list['Maxthon'] = "傲游";
		$list['TaoBrowser'] = "淘宝";
		if($is_mobile){
			$list['baiduboxapp'] = "百度";
			$list['MiuiBrowser'] = "小米";
			$list['MQQBrowser'] = "QQ";
		}else{
			$list['QQBrowser'] = "QQ";
			$list['QQDownload'] = "QQ";
		}
		$keys = array_keys($list);
		$str = '/([a-zA-Z]+)[\/|\s]+([0-9]+\.[0-9]+)/isU';
		preg_match_all($str,$agent,$matches);
		if($matches){
			$browser = end($matches[1]);
			$version = end($matches[2]);
			$name = $list[$browser] ? $list[$browser].'浏览器' : '';
			if($name){
				$tmp = array('title'=>$name,'browser'=>$browser,'version'=>$version);
				return $tmp;
			}
		}
		//安卓/iPhone/iPad/BlackBerry
		$list = array();
		$list['iPhone'] = "iPhone";
		$list['iPad'] = "iPad";
		$list['BlackBerry'] ='黑莓';
		$list['Android'] = "安卓";
		foreach($list as $key=>$value){
			if(strpos($agent,$key) !== false){
				$browser = $key;
				$str = '/Version\/([0-9]+\.[0-9]+)/isU';
				preg_match($str,$agent,$matches);
				$version = $matches[1];
				$name = $list[$browser] ? $list[$browser].'浏览器' : '';
				if($name){
					$tmp = array('title'=>$name,'browser'=>$browser,'version'=>$version);
					return $tmp;
				}
			}
		}
		preg_match('/Chrome\/([0-9]+.[0-9]+)/isU',$agent,$matches);
		if($matches){
			$browser = 'Chrome';
			$version = $matches[1];
			$name = "谷歌浏览器";
			$tmp = array('title'=>$name,'browser'=>$browser,'version'=>$browser);
			return $tmp;
		}
		return false;
	}
}
