<?php
/***
*
*@package binzcms
*@version $id:ModAdminDataClass.php, v 1.0
*@author 斌子
*@Thu Jan 15 01:11:03 CST 2009
*@copyright (c) 2009-2010 binzcms
*
*/
if(!defined("BINZCMS")) {
	die("hacking");
}
/**
 *
 * ModAdminDataClass
 * 数据库管理类
 * 
 */
class ModAdminDataClass extends BaseClass {

	private $dump_sql;
	private $max_size  = 0;
	private $is_short  = false;
	private $offset    = 300;
	private $sql_num   = 0;
	/**
	 * __construct
	 *
	 * 构造函数
	 * @access  public（权限）
	 */
	public function __construct($act) {
		parent::__construct($act);
	}
	/**
	 * mod_run_data_back
	 *
	 * 备份数据库操作
	 * @access  public（权限）
	 * @param   string	$back_path		备份路径
	 * @return  bool
	 */
	public function mod_run_data_back($back_path) {
		@set_time_limit(480);

		$run_log = $back_path. '/run.log';
		$sql_file_name	= trim($_REQUEST['sql_file_name']) != '' ? str_replace("0xa", '',trim($_REQUEST['sql_file_name'])) : 'binzcms'.date("YmdHis").'.sql';
		$pos = strpos($sql_file_name, '.sql');
		if ($pos !== false) {
			$sql_file_name = substr($sql_file_name, 0, $pos);
		}

		$max_size	= empty($_REQUEST['vol_size']) ? 0 : intval($_REQUEST['vol_size']);
		$vol		= empty($_REQUEST['vol']) ? 1 : intval($_REQUEST['vol']);

		/* 变量验证 */
		$allow_max_size = intval(@ini_get('upload_max_filesize')); 	//单位M
		if ($allow_max_size > 0 && $this->max_size > ($allow_max_size*1024)) {
			$max_size = $allow_max_size*1024; 						//单位K
		}

		if ($max_size > 0){
			$this->max_size = $max_size*1024;
		}

		/* 获取要备份数据列表 */
		$type = empty($_REQUEST['type']) ? '' : trim($_REQUEST['type']);
		$tables = array();
		switch ($type) {
			case 'full':
				$temp = $this->binz_db->set_select_sql("SHOW TABLES");
				foreach ($temp AS $table) {
					$tables[$table[0]] = -1;
				}
				$this->put_tables_list($run_log, $tables);
				break;

			case 'stand':
				$temp = $this->binz_db->set_select_sql("SHOW TABLES LIKE '#binz#%'");
				foreach ($temp AS $table) {
					$tables[$table[0]] = -1;
				}
				$this->put_tables_list($run_log, $tables);

				break;
		}

		/* 开始备份 */
		$tables = $this->dump_table($run_log, $vol);
		if($tables === false) {
			die('dataup error');
			exit();
		}
		if (empty($tables)) {
			/* 备份结束 */
			if ($vol > 1) {
				/* 有多个文件 */
				if (!@file_put_contents($back_path . '/' . $sql_file_name . '_' . $vol . '.sql',$this->dump_sql)){
					return array(0,array('name'=>$sql_file_name . '_' . $vol . '.sql'));
				}
				$list = array();
				for ($i = 1; $i <= $vol; $i++){
					$list[] = $sql_file_name . '_' . $i . '.sql';
				}
				return array(1,$list);
			} else {
				/* 只有一个文件 */
				if (!@file_put_contents($back_path . '/' . $sql_file_name . '.sql',$this->dump_sql)) {
					return array(0,array('name'=>$sql_file_name . '_' . $vol . '.sql'));
				};
				return array(1,array('name' => $sql_file_name . '.sql'));

			}
		} else {
			/* 下一个页面处理 */
			if (!@file_put_contents($back_path . '/' . $sql_file_name . '_' . $vol . '.sql',$this->dump_sql)) {
				return array(0,array('name'=>$sql_file_name . '_' . $vol . '.sql'));
			}

			return array(2,array('file_name'=>$sql_file_name . '_' . $vol . '.sql','name'=>'index.php?ctl=data&act=run_data_back&sql_file_name=' . $sql_file_name . '&vol_size=' . $max_size . '&vol=' . ($vol+1)));
		}
	}
	/**
	 * mod_data_revert_show
	 *
	 * 数据库文件检索
	 * @access  public（权限）
	 * @param   string	$path		备份路径
	 * @return  array
	 */
	public function mod_data_revert_show($path) {
		$array	= array();

		/* 获取文件列表 */
		$real_list = array();
		$folder = opendir($path);
		while ($file = readdir($folder)) {
			if (strpos($file,'.sql') !== false) {
				$real_list[] = $file;
			}
		}
		natsort($real_list);
		$match = array();
		foreach ($real_list AS $file) {
			if (preg_match('/_([0-9])+\.sql$/', $file, $match)) {
				if ($match[1] == 1) {
					$mark = 1;
				} else {
					$mark = 2;
				}
			} else {
				$mark = 0;
			}
			$file_size = filesize($path . $file);
			$info = $this->get_head($path . $file);
			$array[] = array('name' => $file, 'add_time' => $info['date'], 'vol' => $info['vol'], 'file_size' => $this->num_bitunit($file_size), 'mark' => $mark);
		}
		return $array;
	}
	/**
	 * mod_run_data_import
	 *
	 * 数据库还原
	 * @access  public（权限）
	 * 
	 * @return  bool
	 */
	public function mod_run_data_import($path) {
		@set_time_limit(480);
		$file_name	= empty($_GET['file_name']) ? '': trim($_GET['file_name']);
		$array		= array();

		if (preg_match('/_[0-9]+\.sql$/', $file_name)) {
			/* 多卷处理 */

			$short_name = substr($file_name, 0, strrpos($file_name, '_'));

			/* 获取文件列表 */
			$real_file = array();
			$folder = opendir($path);
			while ($file = readdir($folder)) {
				if (is_file($path . $file) && preg_match('/_[0-9]+\.sql$/', $file)) {
					$real_file[] = $file;
				}
			}
			/* 所有相同分卷数据列表 */
			$post_list = array();
			foreach ($real_file AS $file) {
				$tmp_name = substr($file, 0, strrpos($file, '_'));
				if ($tmp_name == $short_name) {
					$post_list[] = $file;
				}
			}

			natsort($post_list);

			/* 开始恢复数据 */
			foreach ($post_list AS $file) {
				$info = $this->get_head($path . $file);
				if (!$this->sql_import($path . $file)) {
					return false;
				}
			}
			return true;
		} else {
			/* 单卷 */
			$info = $this->get_head($path . $file_name);
			if ($this->sql_import($path . $file_name)) {
				return true;
			} else {
				return false;
			}
		}
	}
	/**
	 * mod_sql_del
	 *
	 * 函数简介
	 * @access  public（权限）
	 * 
	 * @return  bool
	 */
	public function mod_sql_del($path) {
		if (isset($_POST['file'])) {
			$m_file = array(); //多卷文件
			$s_file = array(); //单卷文件

			foreach ($_POST['file'] AS $file) {
				if (preg_match('/_[0-9]+\.sql$/', $file)) {
					$m_file[] = substr($file, 0, strrpos($file, '_'));
				} else {
					$s_file[] = $file;
				}
			}

			if ($m_file) {
				$m_file = array_unique ($m_file);

				/* 获取文件列表 */
				$real_file = array();

				$folder = opendir($path);
				while ($file = readdir($folder)) {
					if ( preg_match('/_[0-9]+\.sql$/', $file) && is_file($path . $file)) {
						$real_file[] = $file;
					}
				}

				foreach ($real_file AS $file) {
					$short_file = substr($file, 0, strrpos($file, '_'));
					if (in_array($short_file, $m_file)) {
						@unlink($path . $file);
					}
				}
			}

			if ($s_file) {
				foreach ($s_file AS $file) {
					@unlink($path . $file);
				}
			}
		}
		return true;
	}
	/**
	 * mod_data_optimize_show
	 *
	 * 得到数据表碎片数和数据表信息
	 * @access  public（权限）
	 * @return  array	数据表碎片数和数据表信息
	 */
	public function mod_data_optimize_show() {
		$data_sql	= '';
		$row		= array();
		$chip_num	= 0;
		$data_list	= array();
		$type		= '';
		$charset	= '';

		$data_sql	= "SHOW TABLE STATUS LIKE '#binz#%'";
		$result		= $this->binz_db->sql_query($data_sql);

		while ($row = $this->binz_db->sql_fetch_array($result)) {
			$res	= $this->binz_db->sql_fetch_array($this->binz_db->one_query("CHECK TABLE ".$row['Name']));
			$chip_num	= $chip_num + $row['Data_free'];

			$type	= version_compare($this->binz_db->sql_version(),'4.1','>=') ? $row['Engine'] : $row['Type'];
			$charset= version_compare($this->binz_db->sql_version(),'4.1','>=') ? $row['Collation'] : 'N/A';

			$data_list[]	= array('table'		=> $row['Name'],
			'row_num'	=> $row['Rows'],
			'row_size'	=> sprintf(" %.2f KB", $row['Data_length'] / 1024),
			'chip_num'	=> $row['Data_free'],
			'state'		=> $res['Msg_text'],
			'type'		=> $type,
			'charset'	=> $charset);
		}
		return array('data_list'=>$data_list,'all_chip_count'=>$chip_num);
	}
	/**
	 * mod_run_data_optimize
	 *
	 * 数据表优化操作
	 * @access  public（权限）
	 * @return  bool
	 */
	public function mod_run_data_optimize() {
		$table_array	= array();
		$row			= array();

		$table_array	= $this->binz_db->set_select_sql("SHOW TABLES LIKE '#binz#%'");
		foreach ($table_array as $v) {
			$row	= $this->binz_db->sql_fetch_array($this->binz_db->sql_query("OPTIMIZE TABLE $v[0]"));
			/* 如果出错，进行修复 */
			if ($row['Msg_type'] =='error' and strpos($row['Msg_text'], 'repair') !== false) {
				$this->binz_db->sql_query("REPAIR TABLE $v[0]");
			}
		}
		return true;
	}
	/**
     *  获取指定表的定义
     *
     * @access  public
     * @param   string      $table      数据表名
     * @param   boolen      $add_drop   是否加入drop table
     *
     * @return  string      $sql
     */
	private function get_table_df($table, $add_drop = false) {
		if ($add_drop) {
			$table_df = "DROP TABLE IF EXISTS `$table`;\r\n";
		} else {
			$table_df = '';
		}

		$tmp_arr = $this->binz_db->sql_fetch_array($this->binz_db->sql_query("SHOW CREATE TABLE `$table`"));
		$tmp_sql = $tmp_arr['Create Table'];
		$tmp_sql = substr($tmp_sql, 0, strrpos($tmp_sql, ")") + 1); //去除行尾定义。

		$table_df .= $tmp_sql . " ENGINE=InnoDB DEFAULT CHARSET=utf8;\r\n";

		return $table_df;
	}

	/**
     *  获取指定表的数据定义
     *
     * @access  public
     * @param   string      $table      表名
     * @param   int         $pos        备份开始位置
     *
     * @return  int         $post_pos   记录位置
     */
	private function get_table_data($table, $pos) {
		$post_pos = $pos;

		/* 获取数据表记录总数 */
		$total_1	= $this->binz_db->sql_fetch_array($this->binz_db->sql_query("SELECT COUNT(*) FROM $table"));
		$total		= $total_1[0];

		if ($total == 0 || $pos >= $total) {
			/* 无须处理 */
			return -1;
		}

		/* 确定循环次数 */
		$cycle_time = ceil(($total-$pos) / $this->offset); //每次取offset条数。需要取的次数

		/* 循环查数据表 */
		for($i = 0; $i<$cycle_time; $i++) {
			/* 获取数据库数据 */
			$data = $this->binz_db->set_select_sql("SELECT * FROM $table LIMIT " . ($this->offset * $i + $pos) . ', ' . $this->offset);
			$data_count = count($data);

			for($k=0;$k<count($data[0]);$k++) {
				unset($data[0][$k]);
			}
			unset($k);

			$fields = array_keys($data[0]);
			$start_sql = "INSERT INTO `$table` ( `" . implode("`, `", $fields) . "` ) VALUES ";

			/* 循环将数据写入 */
			for($j=0; $j< $data_count; $j++) {
				for($k=0;$k<count($data[$j]);$k++) {
					unset($data[$j][$k]);
				}
				unset($k);

				$record = array_map("mysql_real_escape_string", $data[$j]);//过滤非法字符
				/* 检查是否能写入，能则写入 */
				if ($this->is_short) {
					if ($post_pos == $total-1) {
						$tmp_dump_sql = " ( '" . implode("', '" , $record) . "' );\r\n";
					} else {
						$tmp_dump_sql = " ( '" . implode("', '" , $record) . "' ),\r\n";
					}

					if ($post_pos == $pos) {
						/* 第一次插入数据 */
						$tmp_dump_sql = $start_sql . "\r\n" . $tmp_dump_sql;
					}
				} else {
					$tmp_dump_sql = $start_sql . " ('" . implode("', '" , $record) . "');\r\n";
				}

				if (strlen($this->dump_sql) + strlen($tmp_dump_sql) > $this->max_size - 32) {
					if ($this->sql_num == 0) {
						$this->dump_sql .= $tmp_dump_sql; //当是第一条记录时强制写入
						$this->sql_num++;
						$post_pos++;
						if ($post_pos == $total) {
							/* 所有数据已经写完 */
							return -1;
						}
					}

					return $post_pos;
				} else {
					$this->dump_sql .= $tmp_dump_sql;
					$this->sql_num++; //记录sql条数
					$post_pos++;
				}
			}
		}

		/* 所有数据已经写完 */
		return -1;
	}
	/**
     *  备份一个数据表
     *
     * @access  public
     * @param   string      $path       保存路径表名的文件
     * @param   int         $vol        卷标
     *
     * @return  array       $tables     未备份完的表列表
     */
	private function dump_table($path, $vol)
	{
		$tables = $this->get_tables_list($path);
		if ($tables === false) {
			return false;
		}

		if (empty($tables)) {
			return $tables;
		}

		$this->dump_sql = $this->make_head($vol);
		foreach($tables as $table => $pos) {

			if ($pos == -1) {
				/* 获取表定义，如果没有超过限制则保存 */
				$table_df = $this->get_table_df($table, true);
				if (strlen($this->dump_sql) + strlen($table_df) > $this->max_size - 32) {
					if ($this->sql_num == 0) {
						/* 第一条记录，强制写入 */
						$this->dump_sql .= $table_df;
						$this->sql_num +=2;
						$tables[$table] = 0;
					}
					/* 已经达到上限 */

					break;
				} else {
					$this->dump_sql .= $table_df;
					$this->sql_num +=2;
					$pos = 0;
				}
			}

			/* 尽可能多获取数据表数据 */
			$post_pos = $this->get_table_data($table, $pos);

			if ($post_pos == -1) {
				/* 该表已经完成，清除该表 */
				unset($tables[$table]);
			} else {
				/* 该表未完成。说明将要到达上限,记录备份数据位置 */
				$tables[$table] = $post_pos;
				break;
			}
		}
		$this->dump_sql .= '-- END Binzcms SQL Dump';
		$this->put_tables_list($path, $tables);

		return $tables;
	}

	/**
     *  生成备份文件头部
     *
     * @access  public
     * @param   int     文件卷数
     *
     * @return  string  $str    备份文件头部
     */
	private function make_head($vol) {
		/* 系统信息 */
		$sys_info['os']         = PHP_OS;
		$sys_info['php_ver']    = PHP_VERSION;
		$sys_info['mysql_ver']  = $this->binz_db->sql_version();
		$sys_info['date']       = date('Y-m-d H:i:s');

		$head = "-- binzcms v1.0 SQL Dump\r\n".
		"-- " . $sys_info['web_server'] . "\r\n".
		"-- \r\n".
		"-- DATE : ".$sys_info["date"]."\r\n".
		"-- MYSQL SERVER VERSION : ".$sys_info['mysql_ver']."\r\n".
		"-- PHP VERSION : ".$sys_info['php_ver']."\r\n".
		"-- Vol : " . $vol . "\r\n";

		return $head;
	}

	/**
     *  获取备份文件信息
     *
     * @access  public
     * @param   string      $path       备份文件路径
     *
     * @return  array       $arr        信息数组
     */
	private function get_head($path) {
		/* 获取sql文件头部信息 */
		$sql_info = array('date'=>'', 'mysql_ver'=> '', 'php_ver'=>0, 'vol'=>0);
		$fp = fopen($path,'rb');
		$str = fread($fp, 250);
		fclose($fp);
		$arr = explode("\n", $str);

		foreach ($arr AS $val) {
			$pos = strpos($val, ':');
			if ($pos > 0) {
				$type = trim(substr($val, 0, $pos), "-\n\r\t ");
				$value = trim(substr($val, $pos+1), "/\n\r\t ");
				if ($type == 'DATE') {
					$sql_info['date'] = $value;
				} elseif ($type == 'MYSQL SERVER VERSION') {
					$sql_info['mysql_ver'] = $value;
				} elseif ($type == 'PHP VERSION') {
					$sql_info['php_ver'] = $value;
				} elseif ($type == 'Vol') {
					$sql_info['vol'] = $value;
				}
			}
		}

		return $sql_info;
	}

	/**
     *  将文件中数据表列表取出
     *
     * @access  public
     * @param   string      $path    文件路径
     *
     * @return  array       $arr    数据表列表
     */
	private function get_tables_list($path) {
		if (!file_exists($path)) {
			$this->error_msg = $path . ' is not exists';

			return false;
		}

		$arr = array();
		$str = @file_get_contents($path);

		if (!empty($str)) {
			$tmp_arr = explode("\n", $str);
			foreach ($tmp_arr as $val) {
				$val = trim ($val, "\r;");
				if (!empty($val)) {
					list($table, $count) = explode(':',$val);
					$arr[$table] = $count;
				}
			}
		}

		return $arr;
	}

	/**
     *  将数据表数组写入指定文件
     *
     * @access  public
     * @param   string      $path    文件路径
     * @param   array       $arr    要写入的数据
     *
     * @return  boolen
     */
	private function put_tables_list($path, $arr) {
		if (is_array($arr)) {
			$str = '';
			foreach($arr as $key => $val) {
				$str .= $key . ':' . $val . ";\r\n";
			}

			if (@file_put_contents($path, $str)) {
				return true;
			} else {
				$this->error_msg = 'Can not write ' . $path;

				return false;
			}
		} else {
			$this->error_msg = 'It need a array';

			return false;
		}
	}
	/**
	 * sql_import
	 *
	 * 数据还原操作
	 * @access  public（权限）
	 * @param   string	$sql_file		数据库还原文件
	 * @return  bool
	 */
	private function sql_import($sql_file)
	{
		$sql_str = '';
		$lines		= file($sql_file);
		foreach ($lines as $v) {
			if(substr($v, 0, 2) != '--') {
				$sql_str	.= $v;
			}
		}
		$sql_str	= str_replace("\r", '', $sql_str);

		$ret = explode(";\n", $sql_str);
		$ret_count = count($ret);

		/* 执行sql语句 */
		for($i = 0; $i < $ret_count; $i++) {
			$ret[$i] = trim($ret[$i], " \r\n;"); //剔除多余信息
			if (!empty($ret[$i])) {
				if ((strpos($ret[$i], 'CREATE TABLE') !== false) && (strpos($ret[$i], 'DEFAULT CHARSET=utf8')=== false)) {
					/* 建表时缺 DEFAULT CHARSET=utf8 */
					$ret[$i] = $ret[$i] . ' DEFAULT CHARSET=utf8';
				}
				$this->binz_db->one_query($ret[$i]);
			}
		}
		return true;
	}

	/**
	 * num_bitunit
	 *
	 * 将字节转化为可阅读格式
	 * @access  public（权限）
	 * @param   integer $num	字节数
	 * @return  string	具体大小
	 */
	private function num_bitunit($num) {
		$bitunit = array(' B',' KB',' MB',' GB');
		for ($key = 0, $count = count($bitunit); $key < $count; $key++) {
			if ($num >= pow(2, 10 * $key) - 1) {// 1024B 会显示为 1KB
				$num_bitunit_str = (ceil($num / pow(2, 10 * $key) * 100) / 100) . " $bitunit[$key]";
			}
		}

		return $num_bitunit_str;
	}
	/**
	 * mod_query_sql
	 *
	 * SQL语句执行
	 * @access  public（权限）
	 *
	 * @return  array
	 */
	public function mod_query_sql() {
		$state	= false;
		$message= '';
		$sql 	= stripcslashes($_POST['sql']);
		/* 解析查询项 */
		$sql = str_replace("\r", '', $sql);
		$query_items = explode(";\n", $sql);
		foreach ($query_items as $key=>$value) {
			if (empty($value)) {
				unset($query_items[$key]);
			}
		}
		/* 如果是多条语句，拆开来执行 */
		if (count($query_items) > 1) {
			foreach ($query_items as $key=>$value) {
				if ($this->binz_db->one_query($value)) {
					$state	= 1;
				} else {
					$state	= 0;
					$message= $this->binz_db->out_mysql_error();
					return array('state'=>$state,'message'=>$message,'sql'=>$sql);
				}
			}
			return array('state'=>$state,'message'=>$message,'sql'=>$sql); //退出函数
		}
		/* 单独一条sql语句处理 */
		if (preg_match("/^(?:UPDATE|DELETE|TRUNCATE|ALTER|DROP|FLUSH|INSERT|REPLACE|SET|CREATE)\\s+/i", $sql)) {
			if ($this->binz_db->one_query($sql)) {
				$state	= 1;
			} else {
				$state	= 0;
				$message= $this->binz_db->out_mysql_error();
			}
		} else {
			$query	= $this->binz_db->sql_query($sql);
			if($query !== false) {
				$data	= array();
				while ($row = $this->binz_db->sql_fetch_assoc($query)) {
					$data[] = $row;
				}
			} else {
				$data	= false;
			}
			if ($data === false) {
				$state	= 0;
				$message= $this->binz_db->out_mysql_error();
			} else {
				$state	= 2;
				$message= $this->query_sql_table($data);

			}
		}
		return array('state'=>$state,'message'=>$message,'sql'=>$sql);
	}
	/**
	 * query_sql_table
	 *
	 * 将sql的信息拼接为table
	 * @access  private（权限）
	 *
	 * @return  string
	 */
	private function query_sql_table($data) {
		$result	= '';
		if (is_array($data) && isset($data[0]) === true)
		{
			$result = "<table> \n <tr>";
			$keys = array_keys($data[0]);
			for ($i = 0, $num = count($keys); $i < $num; $i++)
			{
				$result .= "<th>" . $keys[$i] . "</th>\n";
			}
			$result .= "</tr> \n";
			foreach ($data AS $data1)
			{
				$result .= "<tr>\n";
				foreach ($data1 AS $value)
				{
					$result .= "<td>" . $value . "</td>";
				}
				$result .= "</tr>\n";
			}
			$result .= "</table>\n";
		}
		else
		{
			$result ="<center><h3>NO SQL INFO</h3></center>";
		}
		return $result;
	}
	/**
	 * __destruct
	 *
	 * 析构函数
	 * @access  public（权限）
	 */
	public function __destruct() {

	}
}
?>