<?php
class Model {
	protected $tablePrefix = ''; // 数据表前缀
	protected $tableName = ''; // 数据表名(不包含表前缀)
	protected $defaultTableName = ''; //默认数据表名(不含前缀)
	protected $moduleName = ''; // 当前模块名称
	protected $dbConfigDir = ''; // 数据库配置目录,仅在模块使用独立数据库配置时使用
	protected $cacheFields = array(); // 字段信息
	protected $options = array(); // 查询表达式参数
	public $data = array(); // 数据信息
	public $error = ''; // 最近错误信息
	public $totle = 0; //总数
	public $startNo = 1; //用于显示序号 例如:第2页开始就需要增加前面的总数
	public $validate = array(); // 验证定义
	public $page = 1;
	public $pageSize = 20;
	/**
	 * 架构函数
	 * 取得DB类的实例对象 字段检查
	 *
	 * @param  $tableName
	 * @access public
	 */
	public function __construct($init = '') {
		// 模型初始化
		if (method_exists($this, '_initialize')) $this -> _initialize($init);
		// 设置表前缀
		$this -> tablePrefix = C('DB_PREFIX', 1);
		if (empty($this -> moduleName)) {
			$className = get_class($this);
			if ($className == 'Model') $className = $init;
			$className = substr($className, 0, -5);
			$this -> moduleName = $className?$className:'Model';
		}
		if (empty($this -> tableName)) {
			if (empty($this -> defaultTableName)) $this -> defaultTableName = parse_name($this -> moduleName);
			$this -> tableName = $this -> defaultTableName;
		}
	}
	/**
	 * 设置当前操作数据表名,一般不在查询中使用
	 *
	 * @param  $tableName 设置为空时,恢复默认值
	 * @access public
	 */
	public function setTable($tableName = '') {
		$this -> tableName = $tableName?$tableName:$this -> defaultTableName;
		return $this;
	}
	/**
	 * 返回DB对象
	 *
	 * @access public
	 * @param void $
	 * @return object
	 */
	public function db() {
		return Db :: getInitial($this -> dbConfigDir);
	}
	/**
	 * 切换数据库配置目录
	 *
	 * @access public
	 * @param void $dbConfigDir 仅在模块使用独立数据库配置时使用
	 * @return object
	 */
	public function setDbConfigDir($dbConfigDir = '') {
		$this -> dbConfigDir = $dbConfigDir;
		$this -> tablePrefix = C('DB_PREFIX', 1, $dbConfigDir);
		return $this;
	}
	/**
	 * 设置数据对象的值
	 *
	 * @access public
	 * @param string $name 名称
	 * @param mixed $value 值
	 * @return void
	 */
	public function __set($name, $value) {
		// 设置数据对象属性
		$this -> data[$name] = $value;
	}

	/**
	 * 获取数据对象的值
	 *
	 * @access public
	 * @param string $name 名称
	 * @return mixed
	 */
	public function __get($name) {
		return isset($this -> data[$name])?$this -> data[$name]:null;
	}

	/**
	 * 检测数据对象的值
	 *
	 * @access public
	 * @param string $name 名称
	 * @return boolean
	 */
	public function __isset($name) {
		return isset($this -> data[$name]);
	}

	/**
	 * 销毁数据对象的值
	 *
	 * @access public
	 * @param string $name 名称
	 * @return void
	 */
	public function __unset($name) {
		unset($this -> data[$name]);
	}

	/**
	 * 利用__call方法实现一些特殊的Model方法
	 *
	 * @access public
	 * @param string $method 方法名称
	 * @param array $args 调用参数
	 * @return mixed
	 */
	public function __call($method, $args) {
		$method = strtolower($method);
		if (in_array($method, array('field', 'table', 'where', 'order', 'limit', 'page', 'having', 'group', 'lock', 'distinct'), true)) {
			// 连贯操作的实现
			$this -> options[$method] = $args[0];
			return $this;
		} elseif (in_array($method, array('count', 'sum', 'min', 'max', 'avg'), true)) {
			// 统计查询的实现
			$field = isset($args[0])?$args[0]:'*';
			$r = $this -> field(strtoupper($method) . '(' . $field . ') AS tp') -> find();
			return $r['tp'];
		} else {
			show_error(__CLASS__ . ':' . $method . L('METHOD_NOT_EXIST'));
		}
	}
	/**
	 * 查询SQL组装 join
	 *
	 * @access public
	 * @param string $table
	 * @param string $on
	 * @param string $type
	 * @return Object
	 */
	public function join($table, $on, $type = 'left') {
		if ($table && $on) {
			$table = $this -> getTableName($table);
			$this -> options['join'][] = array($table, $on, $type);
		}
		return $this;
	}

	/**
	 * 查询SQL组装 union
	 *
	 * @access public
	 * @param mixed $union
	 * @param boolean $all
	 * @return Object
	 */
	public function union($union, $all = false) {
		if (empty($union)) return $this;
		if ($all) {
			$this -> options['union']['_all'] = true;
		}
		// 转换union表达式
		if (is_string($union)) {
			$options = $union;
		} elseif (is_array($union)) {
			if (isset($union[0])) {
				$this -> options['union'] = array_merge($this -> options['union'], $union);
				return $this;
			} else {
				$options = $union;
			}
		} else {
			show_error(L('DATA_TYPE_INVALID'));
		}
		$this -> options['union'][] = $options;
		return $this;
	}
	/**
	 * SQL查询
	 *
	 * @access public
	 * @param mixed $sql SQL指令
	 * @return mixed
	 */
	public function query($sql) {
		if ($sql) {
			if (strpos($sql, '__TABLE__')) $sql = str_replace('__TABLE__', $this -> getTableName(), $sql);
			return $this -> db() -> query($sql);
		}
		return false;
	}
	/**
	 * 新增数据
	 *
	 * @access public
	 * @param boolean $replace 是否replace
	 * @return mixed
	 */
	public function add($replace = false) {
		if (empty($this -> data)) return false;
		if (!empty($this -> options['table'])) {
			$backTableName = $this -> tableName;
			$this -> setTable($this -> options['table']);
		}
		// 分析表达式
		$options = $this -> parseOptions();
		// 写入数据到数据库
		$result = $this -> db() -> insert($this -> data, $options, $replace);
		if (false !== $result) {
			$insertId = $this -> getInsertId();
			if ($insertId) {
				$this -> data[$this -> getPk()] = $insertId;
				$result = $insertId; // 自增主键返回插入ID
			}
		}
		if (isset($backTableName)) $this -> setTable($backTableName);
		return $result;
	}
	/**
	 * 更新数据
	 *
	 * @access public
	 * @return boolean
	 */
	public function update() {
		if (empty($this -> data)) return false;
		if (!empty($this -> options['table'])) {
			$backTableName = $this -> tableName;
			$this -> setTable($this -> options['table']);
		}
		// 分析表达式
		$options = $this -> parseOptions();
		if (!isset($options['where'])) {
			// 如果存在主键数据 则自动作为更新条件
			$pk = $this -> getPk();
			if (isset($this -> data[$pk])) {
				$pkValue = $this -> data[$pk];
				$options['where'] = $pk . '=' . $pkValue;
				unset($this -> data[$pk]);
			} else {
				// 如果没有任何更新条件则不执行
				$this -> error = L('OPERATION_WRONG');
				return false;
			}
		}
		$result = $this -> db() -> update($this -> data, $options);
		if (false !== $result && isset($pkValue)) $this -> data[$pk] = $pkValue;
		if (isset($backTableName)) $this -> setTable($backTableName);
		return $result;
	}
	/**
	 * 创建数据
	 *
	 * @access public
	 * @param mixed $data
	 * @return mixed
	 */
	public function create($data = '') {
		// 如果没有传值默认取POST数据
		if (empty($data)) $data = $_POST;
		elseif (is_object($data)) $data = get_object_vars($data);
		elseif (!is_array($data)) {
			$this -> error = L('DATA_TYPE_INVALID');
			return false;
		}
		// 表单令牌验证
		if (!$this -> autoCheckToken($data)) {
			$this -> error = L('TOKEN_ERROR');
			return false;
		}
		// 表单数据验证
		if (!$this -> validation($data)) return false;
		// 验证完成生成数据对象
		$vo = array();
		$dbFieldTypeCheck = C('DB_FIELDTYPE_CHECK', 1);
		foreach ($this -> getFields() as $name => $field) {
			$val = isset($data[$name])?$data[$name]:null;
			if ($dbFieldTypeCheck && is_scalar($val) && substr($val, 0, strlen($name)) != $name) {
				// 字段类型检查
				if (false !== strpos($field['type'], 'int'))
					$val = intval($val);
				elseif (false !== strpos($field['type'], 'float') || false !== strpos($field['type'], 'double'))
					$val = floatval($val);
			}
			// 保证赋值有效
			if (!is_null($val)) $vo[$name] = (MAGIC_QUOTES_GPC && is_string($val))?stripslashes($val):$val;
		}
		$this -> data = $vo;
		return $this;
	}
	/**
	 * 分析表达式
	 *
	 * @access private
	 * @param mix $options
	 * @return array
	 */
	private function parseOptions($options = array()) {
		if (empty($options)) $options = array();
		elseif (is_numeric($options) || is_string($options)) {
			$options = array('where' => $this -> getPk() . '=' . $options);
		}
		$options = array_merge($this -> options, $options);
		// 查询过后清空sql表达式组装 避免影响下次查询
		$this -> options = array();
		// 自动获取表名
		$options['table'] = isset($options['table'])?$this -> getTableName($options['table']):$this -> getTableName();
		return $options;
	}
	/**
	 * 查询数据集
	 *
	 * @access public
	 * @param string $condition
	 * @return mixed
	 */
	public function select($condition = '') {
		// 分析表达式
		$options = $this -> parseOptions($condition);
		if (isset($options['page'])) {
			$listRows = isset($options['limit'])?intval($options['limit']):0;
			if ($listRows) $this -> pageSize = $listRows;
			$field = isset($options['field'])?$options['field']:'*'; //备份字段
			$options['limit'] = 1;
			$options['field'] = 'COUNT(*) AS cnt';
			$t = $this -> db() -> select($options);
			$this -> totle = $t['cnt'];
			// 根据页数计算limit
			if ($listRows < 1) $listRows = 20; //该设置必须与pages方法的默认值一致，否则导致分页出错！
			$pages = ceil($this -> totle / $listRows);
			$page = max(min($pages, intval($options['page'])), 1);
			if (defined('IN_CREATEHTML') && $pages > 1 && $page == 1) {
				Cache :: write('createHtmlPages', array('pages' => $pages));
			}
			$offset = $listRows * ($page-1);
			$this -> startNo = $offset + 1;
			$options['limit'] = $offset . ', ' . $listRows;
			$options['field'] = $field; // 还原字段
		}
		$r = $this -> db() -> select($options);
		if (false === $r) return false;
		if (empty($r)) return null; // 查询结果为空
		return $r;
	}
	/**
	 * 删除数据
	 *
	 * @access public
	 * @param string $ / num $condition
	 * @return mixed
	 */
	public function delete($condition = '') {
		if (empty($condition) && empty($this -> options)) {
			// 如果删除条件为空 则删除当前数据对象所对应的记录
			if (!empty($this -> data) && isset($this -> data[$this -> getPk()]))
				return $this -> delete($this -> data[$this -> getPk()]);
			else
				return false;
		}
		// 分析表达式
		$options = $this -> parseOptions($condition);
		return $this -> db() -> delete($options);
	}
	/**
	 * 查询数据
	 *
	 * @access public
	 * @param string $ or num $condition 表达式参数
	 * @return mixed
	 */
	public function find($condition = '') {
		// 分析表达式
		$options = $this -> parseOptions($condition);
		// 总是查找一条记录
		$options['limit'] = 1;
		$result = $this -> db() -> select($options);
		if (false === $result) return false;
		if (empty($result)) return null; // 查询结果为空
		return $result;
	}
	// 自动表单令牌验证
	public function autoCheckToken($data) {
		$name = C('TOKEN_NAME', 1);
		if (isset($data[$name])) {
			isset($_SESSION) or session_start();
			$index = $name . substr($data[$name], 0, 4);
			$index = strtoupper($index);
			if (isset($_SESSION[$index])) {
				// 当前需要令牌验证
				if (empty($data[$name]) || $_SESSION[$index] != $data[$name]) return false; // 非法提交
				// 验证完成销毁session
				unset($_SESSION[$index]);
				return true;
			}
			return false;
		}
		return true;
	}
	/**
	 * 获取字段信息并缓存
	 *
	 * @access public
	 * @return void
	 */
	protected function getFields() {
		$tableName = $this -> getTableName(empty($this -> options['table'])?'':$this -> options['table']);
		if (!isset($this -> cacheFields[$tableName])) {
			$this -> cacheFields[$tableName] = Cache :: read($tableName, '', 0, 'Fields');
			if (false === $this -> cacheFields[$tableName]) {
				// 缓存不存在则查询数据表信息
				$this -> cacheFields[$tableName] = $this -> db() -> getFields($tableName);
				// 缓存数据表
				if (C('DB_FIELDS_CACHE', 1)) Cache :: write($tableName, $this -> cacheFields[$tableName], '', 'Fields');
			}
		}
		return $this -> cacheFields[$tableName];
	}
	/**
	 * 得到完整的数据表名
	 *
	 * @access public
	 * @return string
	 */
	public function getTableName($tableName = '') {
		if (empty($tableName)) $tableName = $this -> tableName;
		if (is_array($tableName)) {
			$array = array();
			foreach($tableName as $tmp) {
				$array[] = substr($tmp, 0, 1) == '@'?substr($tmp, 1):$this -> tablePrefix . $tmp;
			}
			return $array;
		}
		return substr($tableName, 0, 1) == '@'?substr($tmp, 1):$this -> tablePrefix . $tableName;
	}
	/**
	 * 删除
	 *
	 * @access public
	 * @return boolean
	 */
	public function del($selids = 0) {
		$selids or $selids = R('selids');
		if (!empty($selids)) {
			if (is_array($selids)) $selids = implode(', ', $selids);
			$pk = $this -> getPk();
			$where = strstr($selids, ',')?"$pk IN ($selids)":"$pk=$selids";
			if (!$this -> where($where) -> delete()) {
				$this -> error = L('OPERATE_FAIL');
				$rs = false;
			} else $rs = true;
		} else {
			$this -> error = L('NOPARAM');
			$rs = false;
		}
		return $rs;
	}
	/**
	 * 获取主键名称
	 *
	 * @access public
	 * @return string
	 */
	public function getPk() {
		$fields = $this -> getFields();
		if (is_array($fields)) foreach($fields as $field) {
			if ($field['primary']) return $field['name'];
		}
		return 'id';
	}
	/**
	 * 返回最后插入的ID
	 *
	 * @access public
	 * @return string
	 */
	public function getInsertId() {
		return $this -> db() -> insertId();
	}
	/**
	 * 返回影响的记录数
	 *
	 * @access public
	 * @return num
	 */
	public function getAffectedRows() {
		return $this -> db() -> affectedRows();
	}
	/**
	 * 返回最后执行的sql语句
	 *
	 * @access public
	 * @return string
	 */
	public function getLastSql() {
		return $this -> db() -> getLastSql();
	}
	/**
	 * 表单验证
	 *
	 * @access protected
	 * @param array $data 创建数据
	 * @param string $type 创建类型
	 * @return boolean
	 */
	protected function validation($data) {
		// 属性验证
		if (!empty($this -> validate)) {
			$this -> error = '';
			// 如果设置了验证规则,则进行数据验证
			foreach($this -> validate as $val) {
				// $val 格式 array(field,message,rule)
				// 判断是否需要执行验证
				if (substr($val[2], 0, 1) === '/' && substr($val[2], -1) === '/') {
					if (!$this -> regex($data[$val[0]], $val[2])) $this -> error = $val[1];
				} elseif (method_exists($this, $val[2])) {
					if (!call_user_func(array(&$this, $val[2]), $data[$val[0]])) $this -> error = $val[1];
				} elseif (function_exists($val[2])) {
					if (!call_user_func($val[2], $data[$val[0]])) $this -> error = $val[1];
				} elseif (strstr($val[0], ',')) {
					$val[0] = str_replace(' ', '', $val[0]);
					$fields = explode(',', $val[0]);
					if (!$this -> comput($fields[0], $fields[1], $val[2], $data)) $this -> error = $val[1];
				} elseif (!$this -> regex($data[$val[0]], $val[2])) $this -> error = $val[1];
				if ($this -> error) return false;
			}
		}
		return true;
	}
	/**
	 * 分析表达式
	 *
	 * @access private
	 * @param array $options 表达式参数
	 * @return array
	 */
	private function comput($field1, $field2, $logic = '=', $data) {
		if (!in_array($logic, array('=', '!=', '>', '>=', '<', '<='))) $logic = '=';
		if (isset($data[$field1])) $field1 = $data[$field1];
		if (isset($data[$field2])) $field2 = $data[$field2];
		if ($logic == '=') return $field1 == $field2;
		if ($logic == '!=') return $field1 != $field2;
		if ($logic == '>') return $field1 > $field2;
		if ($logic == '>=') return $field1 > $field2;
		if ($logic == '<') return $field1 < $field2;
		if ($logic == '<=') return $field1 <= $field2;
	}
	/**
	 * 使用正则验证数据
	 *
	 * @access public
	 * @param string $value 要验证的数据
	 * @param string $rule 验证规则
	 * @return boolean
	 */
	public function regex($value, $rule) {
		$validate = array('require' => '/.+/',
			'email' => '/^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/',
			'url' => '/^http:\/\/[A-Za-z0-9]+\.[A-Za-z0-9]+[\/=\?%\-&_~`@[\]\':+!]*([^<>\"\"])*$/',
			'currency' => '/^\d+(\.\d+)?$/',
			'number' => '/^\d+$/',
			'zip' => '/^[1-9]\d{5}$/',
			'integer' => '/^[-\+]?\d+$/',
			'double' => '/^[-\+]?\d+(\.\d+)?$/',
			'english' => '/^[A-Za-z]+$/',
			);
		// 检查是否有内置的正则表达式
		if (isset($validate[strtolower($rule)])) $rule = $validate[strtolower($rule)];
		return preg_match($rule, $value) === 1;
	}
	/**
	 * 获取字段数值,必须是唯一字段
	 *
	 * @access public
	 * @param string $field
	 * @param string $tableName
	 * @return intval
	 */
	public function getId($field = 'id', $tableName = '') {
		if ($tableName) $this -> table($tableName);
		$data = $this -> field($field) -> order($field) -> select();
		$cnt = count($data);
		if (!$cnt) return 1;
		for($i = 0; $i < $cnt; $i++) {
			$tmp = $i + 1;
			if ($tmp < $data[$i][$field]) return $tmp;
		}
		return $cnt + 1;
	}
	/**
	 * 分页
	 *
	 * @access public
	 * @param string $url 传入的地址(一般用于静态网页)
	 * @param string $firstUrl 首页地址(一般用于静态网页
	 * @param string $max 同一批次最大分页数
	 * @return str
	 */
	public function pages($url = '', $firstUrl = '', $max = 10) {
		return Func :: pages($this -> page, $this -> totle, $url, $firstUrl, $this -> pageSize, $max);
	}
	/**
	 * 列表
	 *
	 * @access public
	 * @param mix $page 当前分页数字,值为'',表示不使用分页
	 * @param intval $pageSize 分页大小
	 * @param mix $where 条件
	 * @param mix $order 排序,字符串或数组
	 * @return array
	 */
	public function getList($page = '', $pageSize = 0, $where = array(), $order = '') {
		if (!empty($where)) $this -> where($where);
		$pageSize = intval($pageSize);
		if ($pageSize) $this -> limit($pageSize);
		if ($order == '') $order = $this -> getPk() . ' DESC';
		elseif ($order == 'none') $order = '';
		if ($order) $this -> order($order);
		if (is_numeric($page)) {
			$page = max($page, 1);
			$this -> page = $page;
			$this -> page($page);
		}
		return $this -> select();
	}
	/**
	 * 设置字段值
	 *
	 * @access public
	 * @param array $data
	 * @param mix $field
	 * @param mix $selids
	 * @return boolean
	 */
	public function setFields($data, $field = '', $selids = 0) {
		$selids or $selids = R('selids');
		if (!(empty($selids) || empty($data))) {
			$field or $field = $this -> getPk();
			$where = strstr($selids, ',')?"$field IN ($selids)":"$field=$selids";
			if (is_array($data) && $this -> create($data) -> where($where) -> update()) $r = true;
			else {
				$this -> error = L('OPERATE_FAIL');
				$r = false;
			}
		} else {
			$this -> error = L('NOPARAM');
			$r = false;
		}
		return $r;
	}
	public function param($index, $param = '', $default = '') {
		if (isset($param[$index])) {
			$r = is_numeric($param[$index])?$param[$index] + 0:$param[$index];
		} else $r = $default;
		return $r;
	}
}