commit b2b4edaee0175205a76f7e081023d493e3349f37 Author: thinkphp Date: Wed Mar 13 14:15:52 2013 +0800 测试 diff --git a/Think/App.php b/Think/App.php new file mode 100644 index 00000000..144efd9a --- /dev/null +++ b/Think/App.php @@ -0,0 +1,280 @@ + +// +---------------------------------------------------------------------- +// $Id$ + +namespace Think; + +/** + * ThinkApp 应用管理 + * @author liu21st + */ +class App { + + static private $config = []; + + /** + * 执行应用程序 + * @access public + * @return void + */ + static public function run() { + // 监听app_init + Tag::listen('app_init'); + // 加载全局公共文件和配置 + + // 检测项目(或模块)配置文件 + if(is_file(APP_PATH.'config'.EXT)) { + $config = Config::set(include APP_PATH.'config'.EXT); + } + // 加载别名文件 + if(is_file(APP_PATH.'alias'.EXT)) { + Loader::import(include APP_PATH.'alias'.EXT); + } + // 加载公共文件 + if(is_file(APP_PATH.'common'.EXT)) { + include APP_PATH.'common'.EXT; + } + + if(is_file(APP_PATH.'tags'.EXT)) { + // 行为扩展文件 + Tag::import(include APP_PATH.'tags'.EXT); + } + // 应用URL调度 + self::dispatch($config); + + // 执行操作 + $instance = Loader::controll(CONTROLL_NAME); + if(!$instance) { + // 是否定义empty控制器 + $instance = Loader::controll('empty'); + if(!$instance){ + _404('controll not exists :'.CONTROLL_NAME); + } + } + + // 获取当前操作名 + $action = ACTION_NAME.$config['action_suffix']; + try{ + // 操作方法开始监听 + $call = array($instance,$action); + Tag::listen('action_begin',$call); + if(!preg_match('/^[A-Za-z](\w)*$/',$action)){ + // 非法操作 + throw new \ReflectionException(); + } + //执行当前操作 + $method = new \ReflectionMethod($instance, $action); + if($method->isPublic()) { + // URL参数绑定检测 + if($config['url_params_bind'] && $method->getNumberOfParameters()>0){ + switch($_SERVER['REQUEST_METHOD']) { + case 'POST': + $vars = array_merge($_GET,$_POST); + break; + case 'PUT': + parse_str(file_get_contents('php://input'), $vars); + break; + default: + $vars = $_GET; + } + $params = $method->getParameters(); + foreach ($params as $param){ + $name = $param->getName(); + if(isset($vars[$name])) { + $args[] = $vars[$name]; + }elseif($param->isDefaultValueAvailable()){ + $args[] = $param->getDefaultValue(); + }else{ + _404('_PARAM_ERROR_:'.$name); + } + } + $method->invokeArgs($instance,$args); + }else{ + $method->invoke($instance); + } + // 操作方法执行完成监听 + Tag::listen('action_end',$call); + }else{ + // 操作方法不是Public 抛出异常 + throw new ReflectionException(); + } + } catch (ReflectionException $e) { + // 操作不存在 + if(method_exists($instance,'_empty')) { + $method = new ReflectionMethod($instance,'_empty'); + $method->invokeArgs($instance,array($action,'')); + }else{ + _404('action not exists :'.ACTION_NAME); + } + } + // 监听app_end + Tag::listen('app_end'); + return ; + } + + /** + * URL调度 + * @access public + * @return void + */ + static public function dispatch($config) { + $var_module = $config['var_module']; + $var_controll = $config['var_controll']; + $var_action = $config['var_action']; + $var_pathinfo = $config['var_pathinfo']; + if(!empty($_GET[$var_pathinfo])) { // 判断URL里面是否有兼容模式参数 + $_SERVER['PATH_INFO'] = $_GET[$var_pathinfo]; + unset($_GET[$var_pathinfo]); + }elseif(IS_CLI){ // CLI模式下 index.php module/controll/action/params/... + $_SERVER['PATH_INFO'] = isset($_SERVER['argv'][1])?$_SERVER['argv'][1]:''; + } + + // 开启子域名部署 支持二级和三级域名 + if($config['app_domain_deploy']) { + $rules = $config['app_domain_rules']; + if(isset($rules[$_SERVER['HTTP_HOST']])) { // 完整域名或者IP配置 + $rule = $rules[$_SERVER['HTTP_HOST']]; + }else{// 子域名配置 + $domain = array_slice(explode('.',$_SERVER['HTTP_HOST']),0,-2); + if(!empty($domain)) { + $subDomain = implode('.',$domain); + $domain2 = array_pop($domain); // 二级域名 + if($domain) { // 存在三级域名 + $domain3 = array_pop($domain); + } + if($subDomain && isset($rules[$subDomain])) { // 子域名配置 + $rule = $rules[$subDomain]; + }elseif(isset($rules['*.'.$domain2]) && !empty($domain3)){ // 泛三级域名 + $rule = $rules['*.'.$domain2]; + $panDomain = $domain3; + }elseif(isset($rules['*']) && !empty($domain2)){ // 泛二级域名 + if('www' != $domain2 && !in_array($domain2,$config['app_doamin_deny'])) { + $rule = $rules['*']; + $panDomain = $domain2; + } + } + } + } + if(!empty($rule)) { + // 子域名部署规则 '子域名'=>array('模块名'[,'var1=a&var2=b&var3=*']); + $_GET[$var_module] = $rule[0]; + if(isset($rule[1])) { // 传入参数 + parse_str($rule[1],$parms); + if(isset($panDomain)) { + $pos = array_search('*',$parms); + if(false !== $pos) { + // 泛域名作为参数 + $parms[$pos] = $panDomain; + } + } + $_GET = array_merge($_GET,$parms); + } + } + } + + // 分析PATHINFO信息 + if(empty($_SERVER['PATH_INFO']) && $_SERVER['SCRIPT_NAME'] != $_SERVER['PHP_SELF']) { + $types = explode(',',$config['pathinfo_fetch']); + foreach ($types as $type){ + if(0===strpos($type,':')) {// 支持函数判断 + $_SERVER['PATH_INFO'] = call_user_func(substr($type,1)); + break; + }elseif(!empty($_SERVER[$type])) { + $_SERVER['PATH_INFO'] = (0 === strpos($_SERVER[$type],$_SERVER['SCRIPT_NAME']))? + substr($_SERVER[$type], strlen($_SERVER['SCRIPT_NAME'])) : $_SERVER[$type]; + break; + } + } + } + // 定位模块 + if(!empty($_SERVER['PATH_INFO'])) { + // 监听path_info + Tag::listen('path_info'); + $part = pathinfo($_SERVER['PATH_INFO']); + define('__EXT__', isset($part['extension'])?strtolower($part['extension']):''); + $_SERVER['PATH_INFO'] = preg_replace('/\.('.trim($config['url_html_suffix'],'.').')$/i', '',$_SERVER['PATH_INFO']); + $paths = explode($config['pathinfo_depr'],trim($_SERVER['PATH_INFO'],'/')); + if($config['require_module'] && !isset($_GET[$var_module])) { + $_GET[$var_module] = array_shift($paths); + } + }elseif(isset($_GET[$var_module]) && !$config['require_module']) { + unset($_GET[$var_module]); + } + // 获取模块名称 + define('MODULE_NAME',strtolower(isset($_GET[$var_module])?$_GET[$var_module]:$config['default_module'])); + + // 加载模块的公共文件和配置 + if(is_dir(APP_PATH.MODULE_NAME)) { + define('MODULE_PATH',APP_PATH.MODULE_NAME.'/'); + Tag::listen('app_begin'); + + // 检测项目(或模块)配置文件 + if(is_file(MODULE_PATH.'config'.EXT)) { + $config = Config::set(include MODULE_PATH.'config'.EXT); + } + if(Config::get('app_debug')) { + // 读取调试模式的应用状态 + $status = Config::get('app_status'); + // 加载对应的项目配置文件 + if(is_file(MODULE_PATH.$status.EXT)) + $config = Config::set(include MODULE_PATH.$status.EXT); + } + // 加载别名文件 + if(is_file(MODULE_PATH.'alias'.EXT)) { + Loader::import(include MODULE_PATH.'alias'.EXT); + } + // 加载公共文件 + if(is_file(MODULE_PATH.'common'.EXT)) { + include MODULE_PATH.'common'.EXT; + } + if(is_file(MODULE_PATH.'tags'.EXT)) { + // 行为扩展文件 + Tag::import(include MODULE_PATH.'tags'.EXT); + } + $var_controll = $config['var_controll']; + $var_action = $config['var_action']; + }else{ + _404('module not exists :'.MODULE_NAME); + } + + if(!empty($_SERVER['PATH_INFO'])) { + Tag::listen('path_info'); + $url = trim(substr_replace($_SERVER['PATH_INFO'],'',0,strlen($_GET[$var_module])+1),'/'); + // 模块路由检测 + if($config['url_route'] && !Route::check($url,$config['url_route_rules'])){ + $paths = explode($config['pathinfo_depr'],$url); + if($config['require_controll'] && !isset($_GET[$var_controll])) { + $_GET[$var_controll] = array_shift($paths); + } + if(!isset($_GET[$var_action])) { + $_GET[$var_action] = array_shift($paths); + } + // 解析剩余的URL参数 + $var = []; + preg_replace('@(\w+)\/([^\/]+)@e', '$var[\'\\1\']=strip_tags(\'\\2\');', implode('/',$paths)); + $_GET = array_merge($var,$_GET); + } + }elseif(isset($_GET[$var_controll]) && !$config['require_controll']) { + unset($_GET[$var_controll]); + } + + // 获取控制器名 + define('CONTROLL_NAME', strtolower(isset($_GET[$var_controll])?$_GET[$var_controll]:$config['default_controll'])); + + // 获取操作名 + define('ACTION_NAME', strtolower(isset($_GET[$var_action])?$_GET[$var_action]:$config['default_action'])); + + unset($_GET[$var_action],$_GET[$var_controll],$_GET[$var_module]); + //保证$_REQUEST正常取值 + $_REQUEST = array_merge($_POST,$_GET); + } + +} \ No newline at end of file diff --git a/Think/Auto.php b/Think/Auto.php new file mode 100644 index 00000000..ea78b77b --- /dev/null +++ b/Think/Auto.php @@ -0,0 +1,58 @@ + +// +---------------------------------------------------------------------- +// $Id$ + +class ThinkAuto { + + protected $auto = array(); + + public function rule($rule){ + $this->auto = $rule; + return $this; + } + + /** + * 自动表单处理 + * @access public + * @param array $data 创建数据 + * @return mixed + */ + public function operate($data) { + // 自动填充 + if($this->auto) { + foreach ($this->auto as $auto){ + // 填充因子定义格式 + // array('field','填充内容','附加规则',[额外参数]) + switch(trim($auto[2])) { + case 'callback': // 使用回调方法 + $args = isset($auto[3])?(array)$auto[3]:array(); + if(isset($data[$auto[0]])) { + array_unshift($args,$data[$auto[0]]); + } + $data[$auto[0]] = call_user_func_array($auto[1], $args); + break; + case 'field': // 用其它字段的值进行填充 + $data[$auto[0]] = $data[$auto[1]]; + break; + case 'ignore': // 为空忽略 + if(''===$data[$auto[0]]) + unset($data[$auto[0]]); + break; + case 'string': + default: // 默认作为字符串填充 + $data[$auto[0]] = $auto[1]; + } + if(false === $data[$auto[0]] ) unset($data[$auto[0]]); + } + } + return $data; + } +} \ No newline at end of file diff --git a/Think/Behavior/ContentReplace.php b/Think/Behavior/ContentReplace.php new file mode 100644 index 00000000..307e5b87 --- /dev/null +++ b/Think/Behavior/ContentReplace.php @@ -0,0 +1,63 @@ + +// +---------------------------------------------------------------------- +namespace Think\Behavior; +use Think\Config; +/** + * 系统行为扩展:模板内容输出替换 + * @category Think + * @package Think + * @subpackage Behavior + * @author liu21st + */ +class ContentReplace { + + // 行为扩展的执行入口必须是run + public function run(&$content){ + $content = $this->templateContentReplace($content); + } + + /** + * 模板内容替换 + * @access protected + * @param string $content 模板内容 + * @return string + */ + protected function templateContentReplace($content) { + if(IS_CGI) { + //CGI/FASTCGI模式下 + $_temp = explode('.php',$_SERVER['PHP_SELF']); + $script_name = rtrim(str_replace($_SERVER['HTTP_HOST'],'',$_temp[0].'.php'),'/'); + }else { + $script_name = rtrim($_SERVER['SCRIPT_NAME'],'/'); + } + define('ROOT_URL', rtrim(dirname(str_replace("\\","\/",$script_name)),'/')); + define('MODULE_URL', ROOT_URL.(Config::get('require_module')?'/'.MODULE_NAME:'')); + define('CONTROLL_URL', MODULE_URL.(Config::get('require_controll')?'/'.CONTROLL_NAME:'')); + define('ACTION_URL', CONTROLL_URL.'/'.ACTION_NAME); + + // 系统默认的特殊变量替换 + $replace = array( + '__ROOT__' => ROOT_URL, // 当前网站地址 + '__APP__' => MODULE_URL, // 当前项目地址 + '__CONTROLL__' => CONTROLL_URL, // 当前操作地址 + '__URL__' => CONTROLL_URL, + '__ACTION__' => ACTION_URL, // 当前操作地址 + '__SELF__' => $_SERVER['PHP_SELF'], // 当前页面地址 + '__PUBLIC__' => ROOT_URL.'/Public',// 站点公共目录 + ); + // 允许用户自定义模板的字符串替换 + if(is_array(Config::get('tmpl_parse_string')) ) + $replace = array_merge($replace,Config::get('tmpl_parse_string')); + $content = str_replace(array_keys($replace),array_values($replace),$content); + return $content; + } + +} \ No newline at end of file diff --git a/Think/Behavior/LocationTemplate.php b/Think/Behavior/LocationTemplate.php new file mode 100644 index 00000000..70635bc3 --- /dev/null +++ b/Think/Behavior/LocationTemplate.php @@ -0,0 +1,50 @@ + +// +---------------------------------------------------------------------- + +namespace Think\Behavior; + +/** + * 系统行为扩展:定位模板文件 + * @category Think + * @package Think + * @subpackage Behavior + * @author liu21st + */ +class LocationTemplate { + // 行为扩展的执行入口必须是run + public function run(&$templateFile){ + // 自动定位模板文件 + if(!is_file($templateFile)) + $templateFile = $this->parseTemplateFile($templateFile); + } + + /** + * 自动定位模板文件 + * @access private + * @param string $templateFile 文件名 + * @return string + */ + private function parseTemplateFile($template) { + $template = str_replace(':','/',$template); + if(''==$template) { + // 如果模板文件名为空 按照默认规则定位 + $template = CONTROLL_NAME.'/'.ACTION_NAME; + }elseif(false === strpos($template,'/')){ + $template = CONTROLL_NAME.'/'.$template; + }elseif(false === strpos($template,'.')) { + $template = $template; + } + $templateFile = MODULE_PATH.'view/'.$template.'.html'; + if(!is_file($templateFile)) + throw_exception(L('_TEMPLATE_NOT_EXIST_').'['.$templateFile.']'); + return $templateFile; + } +} \ No newline at end of file diff --git a/Think/Behavior/ShowPageTrace.php b/Think/Behavior/ShowPageTrace.php new file mode 100644 index 00000000..aedcba94 --- /dev/null +++ b/Think/Behavior/ShowPageTrace.php @@ -0,0 +1,89 @@ + +// +---------------------------------------------------------------------- + +namespace Think\Behavior; +use Think\Config; +use Think\Log; +use Think\Debug; +/** + * 系统行为扩展:页面Trace显示输出 + * @category Think + * @package Think + * @subpackage Behavior + * @author liu21st + */ +class ShowPageTrace { + + // 行为扩展的执行入口必须是run + public function run(&$params){ + if(!IS_AJAX && Config::get('show_page_trace')) { + echo $this->showTrace(); + } + } + + /** + * 显示页面Trace信息 + * @access private + */ + private function showTrace() { + // 系统默认显示信息 + $files = get_included_files(); + $info = array(); + foreach ($files as $key=>$file){ + $info[] = $file.' ( '.number_format(filesize($file)/1024,2).' KB )'; + } + $trace = array(); + Debug::remark('START',$GLOBALS['startTime']); + $base = array( + '请求信息' => date('Y-m-d H:i:s',$_SERVER['REQUEST_TIME']).' '.$_SERVER['SERVER_PROTOCOL'].' '.$_SERVER['REQUEST_METHOD'].' : '.$_SERVER['PHP_SELF'], + '运行时间' => Debug::getUseTime('START','END',6).'s', + '内存开销' => MEMORY_LIMIT_ON?G('START','END','m').'b':'不支持', + '查询信息' => N('db_query').' queries '.N('db_write').' writes ', + '文件加载' => count($files), + '缓存信息' => N('cache_read').' gets '.N('cache_write').' writes ', + '配置加载' => count(Config::get()), + ); + // 读取项目定义的Trace文件 + $traceFile = MODULE_PATH.'trace.php'; + if(is_file($traceFile)) { + $base = array_merge($base,include $traceFile); + } + $debug = Log::getLog(); + $tabs = Config::get('trace_page_tabs'); + foreach ($tabs as $name=>$title){ + switch(strtoupper($name)) { + case 'BASE':// 基本信息 + $trace[$title] = $base; + break; + case 'FILE': // 文件信息 + $trace[$title] = $info; + break; + default:// 调试信息 + $name = strtoupper($name); + if(strpos($name,'|')) {// 多组信息 + $array = explode('|',$name); + $result = array(); + foreach($array as $name){ + $result += isset($debug[$name])?$debug[$name]:array(); + } + $trace[$title] = $result; + }else{ + $trace[$title] = isset($debug[$name])?$debug[$name]:''; + } + } + } + unset($files,$info,$base,$debug); + // 调用Trace页面模板 + ob_start(); + include Config::has('tmpl_trace_file')?Config::get('tmpl_trace_file'):THINK_PATH.'tpl/page_trace.tpl'; + return ob_get_clean(); + } +} \ No newline at end of file diff --git a/Think/Behavior/read_html_cache.php b/Think/Behavior/read_html_cache.php new file mode 100644 index 00000000..2a9f1072 --- /dev/null +++ b/Think/Behavior/read_html_cache.php @@ -0,0 +1,122 @@ + +// +---------------------------------------------------------------------- + +/** + * 系统行为扩展:静态缓存读取 + * @category Think + * @package Think + * @subpackage Behavior + * @author liu21st + */ +class ReadHtmlCacheBehavior { + protected $options = array( + 'HTML_CACHE_ON' => false, + 'HTML_CACHE_TIME' => 60, + 'HTML_CACHE_RULES' => array(), + 'HTML_FILE_SUFFIX' => '.html', + ); + + // 行为扩展的执行入口必须是run + public function run(&$params){ + // 开启静态缓存 + if(C('HTML_CACHE_ON')) { + $cacheTime = $this->requireHtmlCache(); + if( false !== $cacheTime && $this->checkHTMLCache(HTML_FILE_NAME,$cacheTime)) { //静态页面有效 + // 读取静态页面输出 + readfile(HTML_FILE_NAME); + exit(); + } + } + } + + // 判断是否需要静态缓存 + static private function requireHtmlCache() { + // 分析当前的静态规则 + $htmls = C('HTML_CACHE_RULES'); // 读取静态规则 + if(!empty($htmls)) { + $htmls = array_change_key_case($htmls); + // 静态规则文件定义格式 actionName=>array('静态规则','缓存时间','附加规则') + // 'read'=>array('{id},{name}',60,'md5') 必须保证静态规则的唯一性 和 可判断性 + // 检测静态规则 + $moduleName = strtolower(MODULE_NAME); + $actionName = strtolower(ACTION_NAME); + if(isset($htmls[$moduleName.':'.$actionName])) { + $html = $htmls[$moduleName.':'.$actionName]; // 某个模块的操作的静态规则 + }elseif(isset($htmls[$moduleName.':'])){// 某个模块的静态规则 + $html = $htmls[$moduleName.':']; + }elseif(isset($htmls[$actionName])){ + $html = $htmls[$actionName]; // 所有操作的静态规则 + }elseif(isset($htmls['*'])){ + $html = $htmls['*']; // 全局静态规则 + }elseif(isset($htmls['empty:index']) && !class_exists(MODULE_NAME.'Action')){ + $html = $htmls['empty:index']; // 空模块静态规则 + }elseif(isset($htmls[$moduleName.':_empty']) && $this->isEmptyAction(MODULE_NAME,ACTION_NAME)){ + $html = $htmls[$moduleName.':_empty']; // 空操作静态规则 + } + if(!empty($html)) { + // 解读静态规则 + $rule = $html[0]; + // 以$_开头的系统变量 + $rule = preg_replace('/{\$(_\w+)\.(\w+)\|(\w+)}/e',"\\3(\$\\1['\\2'])",$rule); + $rule = preg_replace('/{\$(_\w+)\.(\w+)}/e',"\$\\1['\\2']",$rule); + // {ID|FUN} GET变量的简写 + $rule = preg_replace('/{(\w+)\|(\w+)}/e',"\\2(\$_GET['\\1'])",$rule); + $rule = preg_replace('/{(\w+)}/e',"\$_GET['\\1']",$rule); + // 特殊系统变量 + $rule = str_ireplace( + array('{:app}','{:module}','{:action}','{:group}'), + array(APP_NAME,MODULE_NAME,ACTION_NAME,defined('GROUP_NAME')?GROUP_NAME:''), + $rule); + // {|FUN} 单独使用函数 + $rule = preg_replace('/{|(\w+)}/e',"\\1()",$rule); + if(!empty($html[2])) $rule = $html[2]($rule); // 应用附加函数 + $cacheTime = isset($html[1])?$html[1]:C('HTML_CACHE_TIME'); // 缓存有效期 + // 当前缓存文件 + define('HTML_FILE_NAME',HTML_PATH . $rule.C('HTML_FILE_SUFFIX')); + return $cacheTime; + } + } + // 无需缓存 + return false; + } + + /** + * 检查静态HTML文件是否有效 + * 如果无效需要重新更新 + * @access public + * @param string $cacheFile 静态文件名 + * @param integer $cacheTime 缓存有效期 + * @return boolen + */ + static public function checkHTMLCache($cacheFile='',$cacheTime='') { + if(!is_file($cacheFile)){ + return false; + }elseif (filemtime(C('TEMPLATE_NAME')) > filemtime($cacheFile)) { + // 模板文件如果更新静态文件需要更新 + return false; + }elseif(!is_numeric($cacheTime) && function_exists($cacheTime)){ + return $cacheTime($cacheFile); + }elseif ($cacheTime != 0 && NOW_TIME > filemtime($cacheFile)+$cacheTime) { + // 文件是否在有效期 + return false; + } + //静态文件有效 + return true; + } + + //检测是否是空操作 + static private function isEmptyAction($module,$action) { + $className = $module.'Action'; + $class = new $className; + return !method_exists($class,$action); + } + +} \ No newline at end of file diff --git a/Think/Behavior/token_build.php b/Think/Behavior/token_build.php new file mode 100644 index 00000000..cda80e4e --- /dev/null +++ b/Think/Behavior/token_build.php @@ -0,0 +1,61 @@ + +// +---------------------------------------------------------------------- + +defined('THINK_PATH') or exit(); +/** + * 系统行为扩展:表单令牌生成 + * @category Think + * @package Think + * @subpackage Behavior + * @author liu21st + */ +class TokenBuildBehavior extends Behavior { + // 行为参数定义 + protected $options = array( + 'TOKEN_ON' => false, // 开启令牌验证 + 'TOKEN_NAME' => '__hash__', // 令牌验证的表单隐藏字段名称 + 'TOKEN_TYPE' => 'md5', // 令牌验证哈希规则 + 'TOKEN_RESET' => true, // 令牌错误后是否重置 + ); + + public function run(&$content){ + if(C('TOKEN_ON')) { + if(strpos($content,'{__TOKEN__}')) { + // 指定表单令牌隐藏域位置 + $content = str_replace('{__TOKEN__}',$this->buildToken(),$content); + }elseif(preg_match('/<\/form(\s*)>/is',$content,$match)) { + // 智能生成表单令牌隐藏域 + $content = str_replace($match[0],$this->buildToken().$match[0],$content); + } + }else{ + $content = str_replace('{__TOKEN__}','',$content); + } + } + + // 创建表单令牌 + private function buildToken() { + $tokenName = C('TOKEN_NAME'); + $tokenType = C('TOKEN_TYPE'); + if(!isset($_SESSION[$tokenName])) { + $_SESSION[$tokenName] = array(); + } + // 标识当前页面唯一性 + $tokenKey = md5($_SERVER['REQUEST_URI']); + if(isset($_SESSION[$tokenName][$tokenKey])) {// 相同页面不重复生成session + $tokenValue = $_SESSION[$tokenName][$tokenKey]; + }else{ + $tokenValue = $tokenType(microtime(TRUE)); + $_SESSION[$tokenName][$tokenKey] = $tokenValue; + } + $token = ''; + return $token; + } +} \ No newline at end of file diff --git a/Think/Cache.php b/Think/Cache.php new file mode 100644 index 00000000..c220947a --- /dev/null +++ b/Think/Cache.php @@ -0,0 +1,42 @@ + +// +---------------------------------------------------------------------- +namespace Think; +class Cache { + + /** + * 操作句柄 + * @var object + * @access protected + */ + static protected $handler = null; + + /** + * 连接缓存 + * @access public + * @param array $options 配置数组 + * @return object + */ + static public function connect($options=[]) { + $type = !empty($options['type'])?$options['type']:'File'; + $class = 'Think\\Cache\\Driver\\'.ucwords($type); + if(class_exists($class)) { + self::$handler = new $class($options); + return self::$handler; + }else{ + Error::halt('_CACHE_TYPE_INVALID_:'.$type); + } + } + + public static function __callStatic($method, $params){ + return call_user_func_array(array(self::$handler, $method), $params); + } + +} \ No newline at end of file diff --git a/Think/Cache/Driver/Apc.php b/Think/Cache/Driver/Apc.php new file mode 100644 index 00000000..e40e23f2 --- /dev/null +++ b/Think/Cache/Driver/Apc.php @@ -0,0 +1,101 @@ + +// +---------------------------------------------------------------------- + +namespace Think\Cache\Driver; + +/** + * Apc缓存驱动 + * @author liu21st + */ +class Apc { + + protected $options = array( + 'expire' => 0, + 'prefix' => '', + 'length' => 0, + ); + + /** + * 架构函数 + * @param array $options 缓存参数 + * @access public + */ + public function __construct($options=array()) { + if(!function_exists('apc_cache_info')) { + throw New Exception('_NOT_SUPPERT_:Apc'); + } + if(!empty($options)) { + $this->options = array_merge($this->options,$options); + } + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @return mixed + */ + public function get($name) { + return apc_fetch($this->options['prefix'].$name); + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer $expire 有效时间(秒) + * @return boolen + */ + public function set($name, $value, $expire = null) { + if(is_null($expire)) { + $expire = $this->options['expire']; + } + $name = $this->options['prefix'].$name; + if($result = apc_store($name, $value, $expire)) { + if($this->options['length']>0) { + // 记录缓存队列 + $queue = apc_fetch('__info__'); + if(!$queue) { + $queue = array(); + } + if(false===array_search($name, $queue)) array_push($queue,$name); + if(count($queue) > $this->options['length']) { + // 出列 + $key = array_shift($queue); + // 删除缓存 + apc_delete($key); + } + apc_store('__info__', $queue); + } + } + return $result; + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return boolen + */ + public function rm($name) { + return apc_delete($this->options['prefix'].$name); + } + + /** + * 清除缓存 + * @access public + * @return boolen + */ + public function clear() { + return apc_clear_cache(); + } +} \ No newline at end of file diff --git a/Think/Cache/Driver/Db.php b/Think/Cache/Driver/Db.php new file mode 100644 index 00000000..ab4c0d37 --- /dev/null +++ b/Think/Cache/Driver/Db.php @@ -0,0 +1,144 @@ + +// +---------------------------------------------------------------------- +namespace Think\Cache\Driver; +/** + * 数据库方式缓存驱动 + * CREATE TABLE think_cache ( + * cachekey varchar(255) NOT NULL, + * expire int(11) NOT NULL, + * data blob, + * datacrc int(32), + * UNIQUE KEY `cachekey` (`cachekey`) + * ); + * @author liu21st + */ +class Db { + + protected $handler = null; + protected $options = array( + 'db' => '', + 'table' => '', + 'prefix' => '', + 'expire' => 0, + 'length' => 0, + ); + + /** + * 架构函数 + * @param array $options 缓存参数 + * @access public + */ + public function __construct($options=array()) { + if(!empty($options)) { + $this->options = array_merge($this->options,$options); + } + $this->handler = ThinkDB::getInstance(); + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @return mixed + */ + public function get($name) { + $name = $this->options['prefix'].addslashes($name); + $result = $this->handler->query('SELECT `data`,`datacrc` FROM `'.$this->options['table'].'` WHERE `cachekey`=\''.$name.'\' AND (`expire` =0 OR `expire`>'.time().') LIMIT 0,1'); + if(false !== $result ) { + $result = $result[0]; + $content = $result['data']; + if(function_exists('gzcompress')) { + //启用数据压缩 + $content = gzuncompress($content); + } + $content = unserialize($content); + return $content; + } + else { + return false; + } + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer $expire 有效时间(秒) + * @return boolen + */ + public function set($name, $value,$expire=null) { + $data = serialize($value); + $name = $this->options['prefix'].addslashes($name); + if(function_exists('gzcompress')) { + //数据压缩 + $data = gzcompress($data,3); + } + if(is_null($expire)) { + $expire = $this->options['expire']; + } + $expire = ($expire==0)?0: (time()+$expire) ;//缓存有效期为0表示永久缓存 + $result = $this->handler->query('select `cachekey` from `'.$this->options['table'].'` where `cachekey`=\''.$name.'\' limit 0,1'); + if(!empty($result) ) { + //更新记录 + $result = $this->handler->execute('UPDATE '.$this->options['table'].' SET data=\''.$data.'\' ,expire='.$expire.' WHERE `cachekey`=\''.$name.'\''); + }else { + //新增记录 + $result = $this->handler->execute('INSERT INTO '.$this->options['table'].' (`cachekey`,`data`,`expire`) VALUES (\''.$name.'\',\''.$data.'\','.$expire.')'); + } + if($result) { + if($this->options['length']>0) { + // 记录缓存队列 + $result = $this->handler->query('SELECT `data`,`datacrc` FROM `'.$this->options['table'].'` WHERE `cachekey`=\'__info__\' AND `expire` =0 LIMIT 0,1'); + $queue = xcache_get('__info__'); + if(!$result) { + $this->handler->execute('INSERT INTO '.$this->options['table'].' (`cachekey`,`data`,`expire`) VALUES (\'__info__\',\'\',0)'); + $queue = array(); + }else{ + $queue = unserialize($result[0]['data']); + } + if(false===array_search($name, $queue)) array_push($queue,$name); + if(count($queue) > $this->options['length']) { + // 出列 + $key = array_shift($queue); + // 删除缓存 + $this->handler->execute('DELETE FROM `'.$this->options['table'].'` WHERE `cachekey`=\''.$key.'\''); + } + $this->handler->execute('UPDATE '.$this->options['table'].' SET data=\''.serialize($queue).'\' ,expire=0 WHERE `cachekey`=\'__info__\'') + xcache_set('__info__', $queue); + } + return true; + }else { + return false; + } + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return boolen + */ + public function rm($name) { + $name = $this->options['prefix'].addslashes($name); + return $this->handler->execute('DELETE FROM `'.$this->options['table'].'` WHERE `cachekey`=\''.$name.'\''); + } + + /** + * 清除缓存 + * @access public + * @return boolen + */ + public function clear() { + return $this->handler->execute('TRUNCATE TABLE `'.$this->options['table'].'`'); + } + +} \ No newline at end of file diff --git a/Think/Cache/Driver/Eaccelerator.php b/Think/Cache/Driver/Eaccelerator.php new file mode 100644 index 00000000..2ce26162 --- /dev/null +++ b/Think/Cache/Driver/Eaccelerator.php @@ -0,0 +1,99 @@ + +// +---------------------------------------------------------------------- +namespace Think\Cache\Driver; +/** + * Eaccelerator缓存驱动 + * @author liu21st + */ +class Eaccelerator { + + protected $options = array( + 'prefix' => '', + 'expire' => 0, + 'length' => 0, + ); + + /** + * 架构函数 + * @param array $options 缓存参数 + * @access public + */ + public function __construct($options=array()) { + if(!empty($options)) { + $this->options = array_merge($this->options,$options); + } + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @return mixed + */ + public function get($name) { + return eaccelerator_get($this->options['prefix'].$name); + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer $expire 有效时间(秒) + * @return boolen + */ + public function set($name, $value, $expire = null) { + if(is_null($expire)) { + $expire = $this->options['expire']; + } + $name = $this->options['prefix'].$name; + eaccelerator_lock($name); + if(eaccelerator_put($name, $value, $expire)) { + if($this->options['length']>0) { + // 记录缓存队列 + $queue = eaccelerator_get('__info__'); + if(!$queue) { + $queue = array(); + } + if(false===array_search($name, $queue)) array_push($queue,$name); + if(count($queue) > $this->options['length']) { + // 出列 + $key = array_shift($queue); + // 删除缓存 + eaccelerator_rm($key); + } + eaccelerator_put('__info__', $queue); + } + return true; + } + return false; + } + + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return boolen + */ + public function rm($name) { + return eaccelerator_rm($this->options['prefix'].$name); + } + + /** + * 清除缓存 + * @access public + * @return boolen + */ + public function clear() { + return ; + } +} \ No newline at end of file diff --git a/Think/Cache/Driver/File.php b/Think/Cache/Driver/File.php new file mode 100644 index 00000000..e0dee86a --- /dev/null +++ b/Think/Cache/Driver/File.php @@ -0,0 +1,182 @@ + +// +---------------------------------------------------------------------- +namespace Think\Cache\Driver; +/** + * 文件类型缓存类 + * @author liu21st + */ +class File { + + protected $options = array( + 'expire' => 0, + 'cache_subdir' => false, + 'path_level' => 1, + 'prefix' => '', + 'length' => 0, + 'temp' => '', + 'data_compress' => false, + ); + + /** + * 架构函数 + * @access public + */ + public function __construct($options=array()) { + if(!empty($options)) { + $this->options = array_merge($this->options,$options); + } + if(substr($this->options['temp'], -1) != '/') $this->options['temp'] .= '/'; + $this->init(); + } + + /** + * 初始化检查 + * @access private + * @return boolen + */ + private function init() { + // 创建项目缓存目录 + if (!is_dir($this->options['temp'])) { + if (! mkdir($this->options['temp'],0755)) + return false; + } + } + + /** + * 取得变量的存储文件名 + * @access private + * @param string $name 缓存变量名 + * @return string + */ + private function filename($name) { + $name = md5($name); + if($this->options['cache_subdir']) { + // 使用子目录 + $dir = ''; + $len = $this->options['path_level']; + for($i=0;$i<$len;$i++) { + $dir .= $name{$i}.'/'; + } + if(!is_dir($this->options['temp'].$dir)) { + mkdir($this->options['temp'].$dir,0755,true); + } + $filename = $dir.$this->options['prefix'].$name.'.php'; + }else{ + $filename = $this->options['prefix'].$name.'.php'; + } + return $this->options['temp'].$filename; + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @return mixed + */ + public function get($name) { + $filename = $this->filename($name); + if (!is_file($filename)) { + return false; + } + $content = file_get_contents($filename); + if( false !== $content) { + $expire = (int)substr($content,8, 12); + if($expire != 0 && time() > filemtime($filename) + $expire) { + //缓存过期删除缓存文件 + unlink($filename); + return false; + } + $content = substr($content,20, -3); + if($this->options['data_compress'] && function_exists('gzcompress')) { + //启用数据压缩 + $content = gzuncompress($content); + } + $content = unserialize($content); + return $content; + } + else { + return false; + } + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param int $expire 有效时间 0为永久 + * @return boolen + */ + public function set($name,$value,$expire=null) { + if(is_null($expire)) { + $expire = $this->options['expire']; + } + $filename = $this->filename($name); + $data = serialize($value); + if($this->options['data_compress'] && function_exists('gzcompress')) { + //数据压缩 + $data = gzcompress($data,3); + } + $data = ""; + $result = file_put_contents($filename,$data); + if($result) { + if($this->options['length']>0) { + // 记录缓存队列 + $queue_file = dirname($filename).'/__info__.php'; + $queue = unserialize(file_get_contents($queue_file)); + if(!$queue) { + $queue = array(); + } + if(false===array_search($name, $queue)) array_push($queue,$name); + if(count($queue) > $this->options['length']) { + // 出列 + $key = array_shift($queue); + // 删除缓存 + unlink($this->filename($key)); + } + file_put_contents($queue_file, serialize($queue)); + } + clearstatcache(); + return true; + }else { + return false; + } + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return boolen + */ + public function rm($name) { + return unlink($this->filename($name)); + } + + /** + * 清除缓存 + * @access public + * @param string $name 缓存变量名 + * @return boolen + */ + public function clear() { + $path = $this->options['temp']; + if ( $dir = opendir( $path ) ) { + while ( $file = readdir( $dir ) ) { + $check = is_dir( $file ); + if ( !$check ) + unlink( $path . $file ); + } + closedir( $dir ); + return true; + } + } +} \ No newline at end of file diff --git a/Think/Cache/Driver/Memcache.php b/Think/Cache/Driver/Memcache.php new file mode 100644 index 00000000..df05bd91 --- /dev/null +++ b/Think/Cache/Driver/Memcache.php @@ -0,0 +1,111 @@ + +// +---------------------------------------------------------------------- +namespace Think\Cache\Driver; +/** + * Memcache缓存驱动 + * @author liu21st + */ +class Memcache { + protected $handler = null; + protected $options = array( + 'host' => '127.0.0.1', + 'port' => 11211, + 'expire' => 0, + 'timeout' => false, + 'persistent' => false, + 'length' => 0, + ); + + /** + * 架构函数 + * @param array $options 缓存参数 + * @access public + */ + public function __construct($options=array()) { + if ( !extension_loaded('memcache') ) { + throw new Exception('_NOT_SUPPERT_:memcache'); + } + if(!empty($options)) { + $this->options = array_merge($this->options,$options); + } + $func = $this->options['persistent'] ? 'pconnect' : 'connect'; + $this->handler = new Memcache; + $options['timeout'] === false ? + $this->handler->$func($options['host'], $options['port']) : + $this->handler->$func($options['host'], $options['port'], $options['timeout']); + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @return mixed + */ + public function get($name) { + return $this->handler->get($this->options['prefix'].$name); + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer $expire 有效时间(秒) + * @return boolen + */ + public function set($name, $value, $expire = null) { + if(is_null($expire)) { + $expire = $this->options['expire']; + } + $name = $this->options['prefix'].$name; + if($this->handler->set($name, $value, 0, $expire)) { + if($this->options['length']>0) { + // 记录缓存队列 + $queue = $this->handler->get('__info__'); + if(!$queue) { + $queue = array(); + } + if(false===array_search($name, $queue)) array_push($queue,$name); + if(count($queue) > $this->options['length']) { + // 出列 + $key = array_shift($queue); + // 删除缓存 + $this->handler->delete($key); + } + $this->handler->set('__info__', $queue); + } + return true; + } + return false; + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return boolen + */ + public function rm($name, $ttl = false) { + $name = $this->options['prefix'].$name; + return $ttl === false ? + $this->handler->delete($name) : + $this->handler->delete($name, $ttl); + } + + /** + * 清除缓存 + * @access public + * @return boolen + */ + public function clear() { + return $this->handler->flush(); + } +} \ No newline at end of file diff --git a/Think/Cache/Driver/Redis.php b/Think/Cache/Driver/Redis.php new file mode 100644 index 00000000..217e3ce0 --- /dev/null +++ b/Think/Cache/Driver/Redis.php @@ -0,0 +1,114 @@ + +// +---------------------------------------------------------------------- +namespace Think\Cache\Driver; +/** + * Redis缓存驱动 + * 要求安装phpredis扩展:https://github.com/nicolasff/phpredis + * @author 尘缘 <130775@qq.com> + */ +class Redis { + protected $handler = null; + protected $options = array( + 'host' => '127.0.0.1', + 'port' => 6379, + 'timeout' => false, + 'expire' => 0, + 'persistent' => false, + 'length' => 0, + ); + + /** + * 架构函数 + * @param array $options 缓存参数 + * @access public + */ + public function __construct($options=array()) { + if ( !extension_loaded('redis') ) { + throw new Exception('_NOT_SUPPERT_:redis'); + } + if(!empty($options)) { + $this->options = array_merge($this->options,$options); + } + $func = $options['persistent'] ? 'pconnect' : 'connect'; + $this->handler = new Redis; + $options['timeout'] === false ? + $this->handler->$func($options['host'], $options['port']) : + $this->handler->$func($options['host'], $options['port'], $options['timeout']); + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @return mixed + */ + public function get($name) { + return $this->handler->get($this->options['prefix'].$name); + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer $expire 有效时间(秒) + * @return boolen + */ + public function set($name, $value, $expire = null) { + if(is_null($expire)) { + $expire = $this->options['expire']; + } + $name = $this->options['prefix'].$name; + if(is_int($expire)) { + $result = $this->handler->setex($name, $expire, $value); + }else{ + $result = $this->handler->set($name, $value); + } + if($result && $this->options['length']>0) { + if($this->options['length']>0) { + // 记录缓存队列 + $queue = $this->handler->get('__info__'); + if(!$queue) { + $queue = array(); + } + if(false===array_search($name, $queue)) array_push($queue,$name); + if(count($queue) > $this->options['length']) { + // 出列 + $key = array_shift($queue); + // 删除缓存 + $this->handler->delete($key); + } + $this->handler->set('__info__', $queue); + } + } + return $result; + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return boolen + */ + public function rm($name) { + return $this->handler->delete($this->options['prefix'].$name); + } + + /** + * 清除缓存 + * @access public + * @return boolen + */ + public function clear() { + return $this->handler->flushDB(); + } + +} diff --git a/Think/Cache/Driver/Secache.php b/Think/Cache/Driver/Secache.php new file mode 100644 index 00000000..85b08346 --- /dev/null +++ b/Think/Cache/Driver/Secache.php @@ -0,0 +1,720 @@ + +// +---------------------------------------------------------------------- +namespace Think\Cache\Driver; +/** + * Secache缓存驱动 + * @author liu21st + */ +class Secache { + + protected $handler = null; + protected $options = array( + 'project' => '', + 'temp' => '', + 'expire' => 0, + 'prefix' => '', + 'length' => 0, + ); + + /** + * 架构函数 + * @param array $options 缓存参数 + * @access public + */ + public function __construct($options=array()) { + if(!empty($options)) { + $this->options = array_merge($this->options,$options); + } + if(substr($this->options['temp'], -1) != '/') $this->options['temp'] .= '/'; + $this->handler = new secache; + $this->handler->workat($this->options['temp'].$this->options['project']); + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @return mixed + */ + public function get($name) { + $name = $this->options['prefix'].$name; + $key = md5($name); + $this->handler->fetch($key,$return); + return $return; + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer $expire 有效时间(秒) + * @return boolen + */ + public function set($name, $value) { + $name = $this->options['prefix'].$name; + $key = md5($name); + if($result = $this->handler->store($key, $value)) { + if($this->options['length']>0) { + // 记录缓存队列 + $queue = $this->handler->fetch(md5('__info__')); + if(!$queue) { + $queue = array(); + } + if(false===array_search($key, $queue)) array_push($queue,$key); + if(count($queue) > $this->options['length']) { + // 出列 + $key = array_shift($queue); + // 删除缓存 + $this->handler->delete($key); + } + $this->handler->store(md5('__info__'), $queue); + } + } + return $result; + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return boolen + */ + public function rm($name) { + $name = $this->options['prefix'].$name; + $key = md5($name); + return $this->handler->delete($key); + } + + /** + * 清除缓存 + * @access public + * @return boolen + */ + public function clear() { + return $this->handler->_format(true); + } + +} + +if(!defined('SECACHE_SIZE')){ + define('SECACHE_SIZE','15M'); +} +class secache{ + + var $idx_node_size = 40; + var $data_base_pos = 262588; //40+20+24*16+16*16*16*16*4; + var $schema_item_size = 24; + var $header_padding = 20; //保留空间 放置php标记防止下载 + var $info_size = 20; //保留空间 4+16 maxsize|ver + + //40起 添加20字节保留区域 + var $idx_seq_pos = 40; //id 计数器节点地址 + var $dfile_cur_pos = 44; //id 计数器节点地址 + var $idx_free_pos = 48; //id 空闲链表入口地址 + + var $idx_base_pos = 444; //40+20+24*16 + var $min_size = 10240; //10M最小值 + var $schema_struct = array('size','free','lru_head','lru_tail','hits','miss'); + var $ver = '$Rev: 3 $'; + var $name = '系统默认缓存(文件型)'; + + function workat($file){ + + $this->_file = $file.'.php'; + $this->_bsize_list = array( + 512=>10, + 3<<10=>10, + 8<<10=>10, + 20<<10=>4, + 30<<10=>2, + 50<<10=>2, + 80<<10=>2, + 96<<10=>2, + 128<<10=>2, + 224<<10=>2, + 256<<10=>2, + 512<<10=>1, + 1024<<10=>1, + ); + + $this->_node_struct = array( + 'next'=>array(0,'V'), + 'prev'=>array(4,'V'), + 'data'=>array(8,'V'), + 'size'=>array(12,'V'), + 'lru_right'=>array(16,'V'), + 'lru_left'=>array(20,'V'), + 'key'=>array(24,'H*'), + ); + + if(!file_exists($this->_file)){ + $this->create(); + }else{ + $this->_rs = fopen($this->_file,'rb+') or $this->trigger_error('Can\'t open the cachefile: '.realpath($this->_file),E_USER_ERROR); + $this->_seek($this->header_padding); + $info = unpack('V1max_size/a*ver',fread($this->_rs,$this->info_size)); + if($info['ver']!=$this->ver){ + $this->_format(true); + }else{ + $this->max_size = $info['max_size']; + } + } + + $this->idx_node_base = $this->data_base_pos+$this->max_size; + $this->_block_size_list = array_keys($this->_bsize_list); + sort($this->_block_size_list); + return true; + } + + function create(){ + $this->_rs = fopen($this->_file,'wb+') or $this->trigger_error('Can\'t open the cachefile: '.realpath($this->_file),E_USER_ERROR);; + fseek($this->_rs,0); + fputs($this->_rs,'<'.'?php exit()?'.'>'); + return $this->_format(); + } + + function _puts($offset,$data){ + if($offset < $this->max_size*1.5){ + $this->_seek($offset); + return fputs($this->_rs,$data); + }else{ + $this->trigger_error('Offset over quota:'.$offset,E_USER_ERROR); + } + } + + function _seek($offset){ + return fseek($this->_rs,$offset); + } + + function clear(){ + return $this->_format(true); + } + + function fetch($key,&$return){ + + if($this->lock(false)){ + $locked = true; + } + + if($this->search($key,$offset)){ + $info = $this->_get_node($offset); + $schema_id = $this->_get_size_schema_id($info['size']); + if($schema_id===false){ + if($locked) $this->unlock(); + return false; + } + + $this->_seek($info['data']); + $data = fread($this->_rs,$info['size']); + $return = unserialize($data); + + if($return===false){ + if($locked) $this->unlock(); + return false; + } + + if($locked){ + $this->_lru_push($schema_id,$info['offset']); + $this->_set_schema($schema_id,'hits',$this->_get_schema($schema_id,'hits')+1); + return $this->unlock(); + }else{ + return true; + } + }else{ + if($locked) $this->unlock(); + return false; + } + } + + /** + * lock + * 如果flock不管用,请继承本类,并重载此方法 + * + * @param mixed $is_block 是否阻塞 + * @access public + * @return void + */ + function lock($is_block,$whatever=false){ + return flock($this->_rs, $is_block?LOCK_EX:LOCK_EX+LOCK_NB); + } + + /** + * unlock + * 如果flock不管用,请继承本类,并重载此方法 + * + * @access public + * @return void + */ + function unlock(){ + return flock($this->_rs, LOCK_UN); + } + + function delete($key,$pos=false){ + if($pos || $this->search($key,$pos)){ + if($info = $this->_get_node($pos)){ + //删除data区域 + if($info['prev']){ + $this->_set_node($info['prev'],'next',$info['next']); + $this->_set_node($info['next'],'prev',$info['prev']); + }else{ //改入口位置 + $this->_set_node($info['next'],'prev',0); + $this->_set_node_root($key,$info['next']); + } + $this->_free_dspace($info['size'],$info['data']); + $this->_lru_delete($info); + $this->_free_node($pos); + return $info['prev']; + } + } + return false; + } + + function store($key,$value){ + + if($this->lock(true)){ + //save data + $data = serialize($value); + $size = strlen($data); + + //get list_idx + $has_key = $this->search($key,$list_idx_offset); + $schema_id = $this->_get_size_schema_id($size); + if($schema_id===false){ + $this->unlock(); + return false; + } + if($has_key){ + $hdseq = $list_idx_offset; + + $info = $this->_get_node($hdseq); + if($schema_id == $this->_get_size_schema_id($info['size'])){ + $dataoffset = $info['data']; + }else{ + //破掉原有lru + $this->_lru_delete($info); + if(!($dataoffset = $this->_dalloc($schema_id))){ + $this->unlock(); + return false; + } + $this->_free_dspace($info['size'],$info['data']); + $this->_set_node($hdseq,'lru_left',0); + $this->_set_node($hdseq,'lru_right',0); + } + + $this->_set_node($hdseq,'size',$size); + $this->_set_node($hdseq,'data',$dataoffset); + }else{ + + if(!($dataoffset = $this->_dalloc($schema_id))){ + $this->unlock(); + return false; + } + $hdseq = $this->_alloc_idx(array( + 'next'=>0, + 'prev'=>$list_idx_offset, + 'data'=>$dataoffset, + 'size'=>$size, + 'lru_right'=>0, + 'lru_left'=>0, + 'key'=>$key, + )); + + if($list_idx_offset>0){ + $this->_set_node($list_idx_offset,'next',$hdseq); + }else{ + $this->_set_node_root($key,$hdseq); + } + } + + if($dataoffset>$this->max_size){ + $this->trigger_error('alloc datasize:'.$dataoffset,E_USER_WARNING); + return false; + } + $this->_puts($dataoffset,$data); + + $this->_set_schema($schema_id,'miss',$this->_get_schema($schema_id,'miss')+1); + + $this->_lru_push($schema_id,$hdseq); + $this->unlock(); + return true; + }else{ + $this->trigger_error("Couldn't lock the file !",E_USER_WARNING); + return false; + } + + } + + /** + * search + * 查找指定的key + * 如果找到节点则$pos=节点本身 返回true + * 否则 $pos=树的末端 返回false + * + * @param mixed $key + * @access public + * @return void + */ + function search($key,&$pos){ + return $this->_get_pos_by_key($this->_get_node_root($key),$key,$pos); + } + + function _get_size_schema_id($size){ + foreach($this->_block_size_list as $k=>$block_size){ + if($size <= $block_size){ + return $k; + } + } + return false; + } + + function _parse_str_size($str_size,$default){ + if(preg_match('/^([0-9]+)\s*([gmk]|)$/i',$str_size,$match)){ + switch(strtolower($match[2])){ + case 'g': + if($match[1]>1){ + $this->trigger_error('Max cache size 1G',E_USER_ERROR); + } + $size = $match[1]<<30; + break; + case 'm': + $size = $match[1]<<20; + break; + case 'k': + $size = $match[1]<<10; + break; + default: + $size = $match[1]; + } + if($size<=0){ + $this->trigger_error('Error cache size '.$this->max_size,E_USER_ERROR); + return false; + }elseif($size<10485760){ + return 10485760; + }else{ + return $size; + } + }else{ + return $default; + } + } + + + function _format($truncate=false){ + if($this->lock(true,true)){ + + if($truncate){ + $this->_seek(0); + ftruncate($this->_rs,$this->idx_node_base); + } + + $this->max_size = $this->_parse_str_size(SECACHE_SIZE,15728640); //default:15m + $this->_puts($this->header_padding,pack('V1a*',$this->max_size,$this->ver)); + + ksort($this->_bsize_list); + $ds_offset = $this->data_base_pos; + $i=0; + foreach($this->_bsize_list as $size=>$count){ + + //将预分配的空间注册到free链表里 + $count *= min(3,floor($this->max_size/10485760)); + $next_free_node = 0; + for($j=0;$j<$count;$j++){ + $this->_puts($ds_offset,pack('V',$next_free_node)); + $next_free_node = $ds_offset; + $ds_offset+=intval($size); + } + + $code = pack(str_repeat('V1',count($this->schema_struct)),$size,$next_free_node,0,0,0,0); + + $this->_puts(60+$i*$this->schema_item_size,$code); + $i++; + } + $this->_set_dcur_pos($ds_offset); + + $this->_puts($this->idx_base_pos,str_repeat("\0",262144)); + $this->_puts($this->idx_seq_pos,pack('V',1)); + $this->unlock(); + return true; + }else{ + $this->trigger_error("Couldn't lock the file !",E_USER_ERROR); + return false; + } + } + + function _get_node_root($key){ + $this->_seek(hexdec(substr($key,0,4))*4+$this->idx_base_pos); + $a= fread($this->_rs,4); + list(,$offset) = unpack('V',$a); + return $offset; + } + + function _set_node_root($key,$value){ + return $this->_puts(hexdec(substr($key,0,4))*4+$this->idx_base_pos,pack('V',$value)); + } + + function _set_node($pos,$key,$value){ + + if(!$pos){ + return false; + } + + if(isset($this->_node_struct[$key])){ + return $this->_puts($pos*$this->idx_node_size+$this->idx_node_base+$this->_node_struct[$key][0],pack($this->_node_struct[$key][1],$value)); + }else{ + return false; + } + } + + function _get_pos_by_key($offset,$key,&$pos){ + if(!$offset){ + $pos = 0; + return false; + } + + $info = $this->_get_node($offset); + + if($info['key']==$key){ + $pos = $info['offset']; + return true; + }elseif($info['next'] && $info['next']!=$offset){ + return $this->_get_pos_by_key($info['next'],$key,$pos); + }else{ + $pos = $offset; + return false; + } + } + + function _lru_delete($info){ + + if($info['lru_right']){ + $this->_set_node($info['lru_right'],'lru_left',$info['lru_left']); + }else{ + $this->_set_schema($this->_get_size_schema_id($info['size']),'lru_tail',$info['lru_left']); + } + + if($info['lru_left']){ + $this->_set_node($info['lru_left'],'lru_right',$info['lru_right']); + }else{ + $this->_set_schema($this->_get_size_schema_id($info['size']),'lru_head',$info['lru_right']); + } + + return true; + } + + function _lru_push($schema_id,$offset){ + $lru_head = $this->_get_schema($schema_id,'lru_head'); + $lru_tail = $this->_get_schema($schema_id,'lru_tail'); + + if((!$offset) || ($lru_head==$offset))return; + + $info = $this->_get_node($offset); + + $this->_set_node($info['lru_right'],'lru_left',$info['lru_left']); + $this->_set_node($info['lru_left'],'lru_right',$info['lru_right']); + + $this->_set_node($offset,'lru_right',$lru_head); + $this->_set_node($offset,'lru_left',0); + + $this->_set_node($lru_head,'lru_left',$offset); + $this->_set_schema($schema_id,'lru_head',$offset); + + if($lru_tail==0){ + $this->_set_schema($schema_id,'lru_tail',$offset); + }elseif($lru_tail==$offset && $info['lru_left']){ + $this->_set_schema($schema_id,'lru_tail',$info['lru_left']); + } + return true; + } + + function _get_node($offset){ + $this->_seek($offset*$this->idx_node_size + $this->idx_node_base); + $info = unpack('V1next/V1prev/V1data/V1size/V1lru_right/V1lru_left/H*key',fread($this->_rs,$this->idx_node_size)); + $info['offset'] = $offset; + return $info; + } + + function _lru_pop($schema_id){ + if($node = $this->_get_schema($schema_id,'lru_tail')){ + $info = $this->_get_node($node); + if(!$info['data']){ + return false; + } + $this->delete($info['key'],$info['offset']); + if(!$this->_get_schema($schema_id,'free')){ + $this->trigger_error('pop lru,But nothing free...',E_USER_ERROR); + } + return $info; + }else{ + return false; + } + } + + function _dalloc($schema_id,$lru_freed=false){ + + if($free = $this->_get_schema($schema_id,'free')){ //如果lru里有链表 + $this->_seek($free); + list(,$next) = unpack('V',fread($this->_rs,4)); + $this->_set_schema($schema_id,'free',$next); + return $free; + }elseif($lru_freed){ + $this->trigger_error('Bat lru poped freesize',E_USER_ERROR); + return false; + }else{ + $ds_offset = $this->_get_dcur_pos(); + $size = $this->_get_schema($schema_id,'size'); + + if($size+$ds_offset > $this->max_size){ + if($info = $this->_lru_pop($schema_id)){ + return $this->_dalloc($schema_id,$info); + }else{ + $this->trigger_error('Can\'t alloc dataspace',E_USER_ERROR); + return false; + } + }else{ + $this->_set_dcur_pos($ds_offset+$size); + return $ds_offset; + } + } + } + + function _get_dcur_pos(){ + $this->_seek($this->dfile_cur_pos); + list(,$ds_offset) = unpack('V',fread($this->_rs,4)); + return $ds_offset; + } + function _set_dcur_pos($pos){ + return $this->_puts($this->dfile_cur_pos,pack('V',$pos)); + } + + function _free_dspace($size,$pos){ + + if($pos>$this->max_size){ + $this->trigger_error('free dspace over quota:'.$pos,E_USER_ERROR); + return false; + } + + $schema_id = $this->_get_size_schema_id($size); + if($free = $this->_get_schema($schema_id,'free')){ + $this->_puts($free,pack('V1',$pos)); + }else{ + $this->_set_schema($schema_id,'free',$pos); + } + $this->_puts($pos,pack('V1',0)); + } + + function _dfollow($pos,&$c){ + $c++; + $this->_seek($pos); + list(,$next) = unpack('V1',fread($this->_rs,4)); + if($next){ + return $this->_dfollow($next,$c); + }else{ + return $pos; + } + } + + function _free_node($pos){ + $this->_seek($this->idx_free_pos); + list(,$prev_free_node) = unpack('V',fread($this->_rs,4)); + $this->_puts($pos*$this->idx_node_size+$this->idx_node_base,pack('V',$prev_free_node).str_repeat("\0",$this->idx_node_size-4)); + return $this->_puts($this->idx_free_pos,pack('V',$pos)); + } + + function _alloc_idx($data){ + $this->_seek($this->idx_free_pos); + list(,$list_pos) = unpack('V',fread($this->_rs,4)); + if($list_pos){ + + $this->_seek($list_pos*$this->idx_node_size+$this->idx_node_base); + list(,$prev_free_node) = unpack('V',fread($this->_rs,4)); + $this->_puts($this->idx_free_pos,pack('V',$prev_free_node)); + + }else{ + $this->_seek($this->idx_seq_pos); + list(,$list_pos) = unpack('V',fread($this->_rs,4)); + $this->_puts($this->idx_seq_pos,pack('V',$list_pos+1)); + } + return $this->_create_node($list_pos,$data); + } + + function _create_node($pos,$data){ + $this->_puts($pos*$this->idx_node_size + $this->idx_node_base + ,pack('V1V1V1V1V1V1H*',$data['next'],$data['prev'],$data['data'],$data['size'],$data['lru_right'],$data['lru_left'],$data['key'])); + return $pos; + } + + function _set_schema($schema_id,$key,$value){ + $info = array_flip($this->schema_struct); + return $this->_puts(60+$schema_id*$this->schema_item_size + $info[$key]*4,pack('V',$value)); + } + + function _get_schema($id,$key){ + $info = array_flip($this->schema_struct); + + $this->_seek(60+$id*$this->schema_item_size); + unpack('V1'.implode('/V1',$this->schema_struct),fread($this->_rs,$this->schema_item_size)); + + $this->_seek(60+$id*$this->schema_item_size + $info[$key]*4); + list(,$value) =unpack('V',fread($this->_rs,4)); + return $value; + } + + function _all_schemas(){ + $schema = array(); + for($i=0;$i<16;$i++){ + $this->_seek(60+$i*$this->schema_item_size); + $info = unpack('V1'.implode('/V1',$this->schema_struct),fread($this->_rs,$this->schema_item_size)); + if($info['size']){ + $info['id'] = $i; + $schema[$i] = $info; + }else{ + return $schema; + } + } + } + + function schemaStatus(){ + $return = array(); + foreach($this->_all_schemas() as $k=>$schemaItem){ + if($schemaItem['free']){ + $this->_dfollow($schemaItem['free'],$schemaItem['freecount']); + } + $return[] = $schemaItem; + } + return $return; + } + + function status(&$curBytes,&$totalBytes){ + $totalBytes = $curBytes = 0; + $hits = $miss = 0; + + $schemaStatus = $this->schemaStatus(); + $totalBytes = $this->max_size; + $freeBytes = $this->max_size - $this->_get_dcur_pos(); + + foreach($schemaStatus as $schema){ + $freeBytes+=$schema['freecount']*$schema['size']; + $miss += $schema['miss']; + $hits += $schema['hits']; + } + $curBytes = $totalBytes-$freeBytes; + + $return[] = array('name'=>'缓存命中','value'=>$hits); + $return[] = array('name'=>'缓存未命中','value'=>$miss); + return $return; + } + + function trigger_error($errstr,$errno){ + trigger_error($errstr,$errno); + } + +} \ No newline at end of file diff --git a/Think/Cache/Driver/Simple.php b/Think/Cache/Driver/Simple.php new file mode 100644 index 00000000..09f348d6 --- /dev/null +++ b/Think/Cache/Driver/Simple.php @@ -0,0 +1,97 @@ + +// +---------------------------------------------------------------------- +namespace Think\Cache\Driver; +/** + * 文件类型缓存类 + * @author liu21st + */ +class Simple { + + protected $options = array( + 'prefix' => '', + 'temp' => '', + ); + + /** + * 架构函数 + * @access public + */ + public function __construct($options=array()) { + if(!empty($options)) { + $this->options = array_merge($this->options,$options); + } + if(substr($this->options['temp'], -1) != '/') $this->options['temp'] .= '/'; + } + + /** + * 取得变量的存储文件名 + * @access private + * @param string $name 缓存变量名 + * @return string + */ + private function filename($name) { + return $this->options['temp'].$this->options['prefix'].md5($name).'.php'; + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @return mixed + */ + public function get($name) { + $filename = $this->filename($name); + if (is_file($filename)) { + return include $filename; + }else{ + return false; + } + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param int $expire 有效时间 0为永久 + * @return boolen + */ + public function set($name,$value,$expire=null) { + $filename = $this->filename($name); + // 缓存数据 + $dir = dirname($filename); + // 目录不存在则创建 + //if (!is_dir($dir)) + // mkdir($dir,0755,true); + return file_put_contents($filename, ("")); + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return boolen + */ + public function rm($name) { + return unlink($this->filename($name)); + } + + /** + * 清除缓存 + * @access public + * @param string $name 缓存变量名 + * @return boolen + */ + public function clear() { + $filename = $this->filename('*'); + array_map("unlink", glob($filename)); + } +} \ No newline at end of file diff --git a/Think/Cache/Driver/Sqlite.php b/Think/Cache/Driver/Sqlite.php new file mode 100644 index 00000000..893fa357 --- /dev/null +++ b/Think/Cache/Driver/Sqlite.php @@ -0,0 +1,117 @@ + +// +---------------------------------------------------------------------- +namespace Think\Cache\Driver; +/** + * Sqlite缓存驱动 + * @author liu21st + */ +class Sqlite { + + protected $options = array( + 'db' => ':memory:', + 'table' => 'sharedmemory', + 'prefix' => '', + 'expire' => 0, + 'length' => 0, + 'persistent' => false, + ); + + /** + * 架构函数 + * @param array $options 缓存参数 + * @access public + */ + public function __construct($options=array()) { + if ( !extension_loaded('sqlite') ) { + throw new Exception('_NOT_SUPPERT_:sqlite'); + } + if(!empty($options)) { + $this->options = array_merge($this->options,$options); + } + $func = $this->options['persistent'] ? 'sqlite_popen' : 'sqlite_open'; + $this->handler = $func($this->options['db']); + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @return mixed + */ + public function get($name) { + $name = $this->options['prefix'].sqlite_escape_string($name); + $sql = 'SELECT value FROM '.$this->options['table'].' WHERE var=\''.$name.'\' AND (expire=0 OR expire >'.time().') LIMIT 1'; + $result = sqlite_query($this->handler, $sql); + if (sqlite_num_rows($result)) { + $content = sqlite_fetch_single($result); + if(function_exists('gzcompress')) { + //启用数据压缩 + $content = gzuncompress($content); + } + return unserialize($content); + } + return false; + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer $expire 有效时间(秒) + * @return boolen + */ + public function set($name, $value,$expire=null) { + $name = $this->options['prefix'].sqlite_escape_string($name); + $value = sqlite_escape_string(serialize($value)); + if(is_null($expire)) { + $expire = $this->options['expire']; + } + $expire = ($expire==0)?0: (time()+$expire) ;//缓存有效期为0表示永久缓存 + if(function_exists('gzcompress')) { + //数据压缩 + $value = gzcompress($value,3); + } + $sql = 'REPLACE INTO '.$this->options['table'].' (var, value,expire) VALUES (\''.$name.'\', \''.$value.'\', \''.$expire.'\')'; + if(sqlite_query($this->handler, $sql)){ + if($this->options['length']>0) { + // 记录缓存队列 + $this->queue($name); + } + return true; + } + return false; + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return boolen + */ + public function rm($name) { + $name = $this->options['prefix'].sqlite_escape_string($name); + $sql = 'DELETE FROM '.$this->options['table'].' WHERE var=\''.$name.'\''; + sqlite_query($this->handler, $sql); + return true; + } + + /** + * 清除缓存 + * @access public + * @return boolen + */ + public function clear() { + $sql = 'DELETE FROM '.$this->options['table']; + sqlite_query($this->handler, $sql); + return ; + } +} \ No newline at end of file diff --git a/Think/Cache/Driver/Wincache.php b/Think/Cache/Driver/Wincache.php new file mode 100644 index 00000000..cbc35948 --- /dev/null +++ b/Think/Cache/Driver/Wincache.php @@ -0,0 +1,101 @@ + +// +---------------------------------------------------------------------- +namespace Think\Cache\Driver; +/** + * Wincache缓存驱动 + * @author liu21st + */ +class Wincache { + + protected $options = array( + 'prefix' => '', + 'expire' => 0, + 'length' => 0, + ); + + /** + * 架构函数 + * @param array $options 缓存参数 + * @access public + */ + public function __construct($options=array()) { + if ( !function_exists('wincache_ucache_info') ) { + throw new Exception('_NOT_SUPPERT_:WinCache'); + } + if(!empty($options)) { + $this->options = array_merge($this->options,$options); + } + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @return mixed + */ + public function get($name) { + $name = $this->options['prefix'].$name; + return wincache_ucache_exists($name)? wincache_ucache_get($name) : false; + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer $expire 有效时间(秒) + * @return boolen + */ + public function set($name, $value,$expire=null) { + if(is_null($expire)) { + $expire = $this->options['expire']; + } + $name = $this->options['prefix'].$name; + if(wincache_ucache_set($name, $value, $expire)) { + if($this->options['length']>0) { + // 记录缓存队列 + $queue = wincache_ucache_get('__info__'); + if(!$queue) { + $queue = array(); + } + if(false===array_search($name, $queue)) array_push($queue,$name); + if(count($queue) > $this->options['length']) { + // 出列 + $key = array_shift($queue); + // 删除缓存 + wincache_ucache_delete($key); + } + wincache_ucache_set('__info__', $queue); + } + return true; + } + return false; + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return boolen + */ + public function rm($name) { + return wincache_ucache_delete($this->options['prefix'].$name); + } + + /** + * 清除缓存 + * @access public + * @return boolen + */ + public function clear() { + return ; + } +} \ No newline at end of file diff --git a/Think/Cache/Driver/Xcache.php b/Think/Cache/Driver/Xcache.php new file mode 100644 index 00000000..2d65c209 --- /dev/null +++ b/Think/Cache/Driver/Xcache.php @@ -0,0 +1,104 @@ + +// +---------------------------------------------------------------------- +namespace Think\Cache\Driver; +/** + * Xcache缓存驱动 + * @author liu21st + */ +class Xcache { + + protected $options = array( + 'prefix' => '', + 'expire' => 0, + 'length' => 0, + ); + + /** + * 架构函数 + * @param array $options 缓存参数 + * @access public + */ + public function __construct($options=array()) { + if ( !function_exists('xcache_info') ) { + throw new Exception('_NOT_SUPPERT_:Xcache'); + } + if(!empty($options)) { + $this->options = array_merge($this->options,$options); + } + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @return mixed + */ + public function get($name) { + $name = $this->options['prefix'].$name; + if (xcache_isset($name)) { + return xcache_get($name); + } + return false; + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer $expire 有效时间(秒) + * @return boolen + */ + public function set($name, $value,$expire=null) { + if(is_null($expire)) { + $expire = $this->options['expire'] ; + } + $name = $this->options['prefix'].$name; + if(xcache_set($name, $value, $expire)) { + if($this->options['length']>0) { + // 记录缓存队列 + $queue = xcache_get('__info__'); + if(!$queue) { + $queue = array(); + } + if(false===array_search($name, $queue)) array_push($queue,$name); + if(count($queue) > $this->options['length']) { + // 出列 + $key = array_shift($queue); + // 删除缓存 + xcache_unset($key); + } + xcache_set('__info__', $queue); + } + return true; + } + return false; + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return boolen + */ + public function rm($name) { + return xcache_unset($this->options['prefix'].$name); + } + + /** + * 清除缓存 + * @access public + * @return boolen + */ + public function clear() { + return ; + } +} \ No newline at end of file diff --git a/Think/Config.php b/Think/Config.php new file mode 100644 index 00000000..8f3b3ded --- /dev/null +++ b/Think/Config.php @@ -0,0 +1,80 @@ + +// +---------------------------------------------------------------------- +namespace Think; +class Config { + static private $_config = []; // 配置参数 + static private $_range = '_sys_'; // 参数作用域 + + // 设定配置参数的作用域 + static public function range($range){ + self::$_range = $range; + } + + // 加载配置文件 + static public function load($file,$range=''){ + return self::set(include $file,'',$range); + } + + // 检测配置是否存在 + static public function has($name,$range=''){ + $range = $range?$range:self::$_range; + $name = strtolower($name); + // 优先执行设置获取或赋值 + if (!strpos($name, '.')) { + return isset(self::$_config[$range][$name]); + } + // 二维数组设置和获取支持 + $name = explode('.', $name); + return isset(self::$_config[$range][$name[0]][$name[1]]); + } + + // 获取配置参数 为空则获取所有配置 + static public function get($name=null,$range='') { + $range = $range?$range:self::$_range; + // 无参数时获取所有 + if (empty($name)) { + return self::$_config[$range]; + } + $name = strtolower($name); + // 优先执行设置获取或赋值 + if (!strpos($name, '.')) { + return isset(self::$_config[$range][$name]) ? self::$_config[$range][$name] : null; + } + // 二维数组设置和获取支持 + $name = explode('.', $name); + return isset(self::$_config[$range][$name[0]][$name[1]]) ? self::$_config[$range][$name[0]][$name[1]] : null; + } + + // 设置配置参数 name为数组则为批量设置 + static public function set($name, $value=null,$range='') { + $range = $range?$range:self::$_range; + if(!isset(self::$_config[$range])) { + self::$_config[$range] = []; + } + if (is_string($name)) { + $name = strtolower($name); + if (!strpos($name, '.')) { + self::$_config[$range][$name] = $value; + return; + } + // 二维数组设置和获取支持 + $name = explode('.', $name); + self::$_config[$range][$name[0]][$name[1]] = $value; + return; + } + // 批量设置 + if (is_array($name)){ + self::$_config[$range] = array_merge(self::$_config[$range], array_change_key_case($name)); + return self::$_config[$range]; + } + return null; // 避免非法参数 + } +} \ No newline at end of file diff --git a/Think/Controll.php b/Think/Controll.php new file mode 100644 index 00000000..d3322485 --- /dev/null +++ b/Think/Controll.php @@ -0,0 +1,151 @@ + +// +---------------------------------------------------------------------- +// $Id$ +namespace Think; +class Controll { + protected $view = null; + + public function __construct(){ + + $config['template_options'] = array( + 'tpl_path' => MODULE_PATH.'view/', + 'cache_path' => MODULE_PATH.'cache/', + 'cache_type' => '', + 'taglib_pre_load' => 'html', + 'taglib_build_in' => 'cx,attr', + 'cache_options' => array( + 'temp' => APP_PATH.'runtime/temp/', + ), + ); + $config['http_content_type'] = 'text/html'; + $config['http_cache_control'] = 'private'; + $config['http_charset'] = 'utf-8'; + $this->view = new View($config); + $this->view->engine('think',$config['template_options']); + //控制器初始化 + if(method_exists($this,'_initialize')) + $this->_initialize(); + } + + public function display($template=''){ + $this->view->display($template); + } + + public function assign($name,$value=''){ + $this->view->assign($name,$value); + } + + public function __set($name,$value){ + return $this->assign($name,$value); + } + + /** + * Ajax方式返回数据到客户端 + * @access protected + * @param mixed $data 要返回的数据 + * @param String $type AJAX返回数据格式 + * @return void + */ + protected function ajaxReturn($data,$type='') { + if(empty($type)) $type = C('DEFAULT_AJAX_RETURN'); + switch (strtoupper($type)){ + case 'JSON' : + // 返回JSON数据格式到客户端 包含状态信息 + header('Content-Type:application/json; charset=utf-8'); + exit(json_encode($data)); + case 'XML' : + // 返回xml格式数据 + header('Content-Type:text/xml; charset=utf-8'); + exit(xml_encode($data)); + case 'JSONP': + // 返回JSON数据格式到客户端 包含状态信息 + header('Content-Type:application/json; charset=utf-8'); + $handler = isset($_GET[C('VAR_JSONP_HANDLER')]) ? $_GET[C('VAR_JSONP_HANDLER')] : C('DEFAULT_JSONP_HANDLER'); + exit($handler.'('.json_encode($data).');'); + case 'EVAL' : + // 返回可执行的js脚本 + header('Content-Type:text/html; charset=utf-8'); + exit($data); + default : + // 用于扩展其他返回格式数据 + Tag::listen('ajax_return',$data); + } + } + + /** + * 操作错误跳转的快捷方法 + * @access protected + * @param string $message 错误信息 + * @param string $jumpUrl 页面跳转地址 + * @param mixed $ajax 是否为Ajax方式 当数字时指定跳转时间 + * @return void + */ + protected function error($message,$jumpUrl='',$ajax=false) { + $this->dispatchJump($message,0,$jumpUrl,$ajax); + } + + /** + * 操作成功跳转的快捷方法 + * @access protected + * @param string $message 提示信息 + * @param string $jumpUrl 页面跳转地址 + * @param mixed $ajax 是否为Ajax方式 当数字时指定跳转时间 + * @return void + */ + protected function success($message,$jumpUrl='',$ajax=false) { + $this->dispatchJump($message,1,$jumpUrl,$ajax); + } + + /** + * 默认跳转操作 支持错误导向和正确跳转 + * 调用模板显示 默认为public目录下面的success页面 + * 提示页面为可配置 支持模板标签 + * @param string $message 提示信息 + * @param Boolean $status 状态 + * @param string $jumpUrl 页面跳转地址 + * @param mixed $ajax 是否为Ajax方式 当数字时指定跳转时间 + * @access private + * @return void + */ + private function dispatchJump($message,$status=1,$jumpUrl='',$ajax=false) { + if(true === $ajax || IS_AJAX) {// AJAX提交 + $data = is_array($ajax)?$ajax:array(); + $data['info'] = $message; + $data['status'] = $status; + $data['url'] = $jumpUrl; + $this->ajaxReturn($data); + } + if(is_int($ajax)) $this->view->assign('waitSecond',$ajax); + if(!empty($jumpUrl)) $this->view->assign('jumpUrl',$jumpUrl); + // 提示标题 + $this->view->assign('msgTitle',$status? L('_OPERATION_SUCCESS_') : L('_OPERATION_FAIL_')); + $this->view->assign('status',$status); // 状态 + //保证输出不受静态缓存影响 + C('HTML_CACHE_ON',false); + if($status) { //发送成功信息 + $this->view->assign('message',$message);// 提示信息 + // 成功操作后默认停留1秒 + $this->view->assign('waitSecond','1'); + // 默认操作成功自动返回操作前页面 + if(!$jumpUrl) $this->view->assign("jumpUrl",$_SERVER["HTTP_REFERER"]); + $this->display(C('TMPL_ACTION_SUCCESS')); + }else{ + $this->view->assign('error',$message);// 提示信息 + //发生错误时候默认停留3秒 + $this->view->assign('waitSecond','3'); + // 默认发生错误的话自动返回上页 + if(!$jumpUrl) $this->view->assign('jumpUrl',"javascript:history.back(-1);"); + $this->display(C('TMPL_ACTION_ERROR')); + // 中止执行 避免出错后继续执行 + exit ; + } + } +} \ No newline at end of file diff --git a/Think/Cookie.php b/Think/Cookie.php new file mode 100644 index 00000000..1520e7f4 --- /dev/null +++ b/Think/Cookie.php @@ -0,0 +1,130 @@ + +// +---------------------------------------------------------------------- +// $Id$ +namespace Think; +class Cookie { + + static protected $config = [ + 'prefix' => '', // cookie 名称前缀 + 'expire' => 0, // cookie 保存时间 + 'path' => '/', // cookie 保存路径 + 'domain' => '', // cookie 有效域名 + ]; + + /** + * Cookie初始化 + * @param array $config + * @return void + */ + static public function init($config=[]){ + self::$config = array_merge(self::$config, array_change_key_case($config)); + } + + /** + * 设置或者获取cookie作用域(前缀) + * @param string $prefix + * @return string|void + */ + static public function prefix($prefix=''){ + if(empty($prefix)) { + return self::$config['prefix']; + }else{ + self::$config['prefix'] = $prefix; + } + } + + /** + * Cookie 设置、获取、删除 + * @param string $name cookie名称 + * @param mixed $value cookie值 + * @param mixed $options cookie参数 + * @return mixed + */ + static public function set($name, $value='', $option=null) { + // 参数设置(会覆盖黙认设置) + if (!is_null($option)) { + if (is_numeric($option)) + $option = array('expire' => $option); + elseif (is_string($option)) + parse_str($option, $option); + $config = array_merge(self::$config, array_change_key_case($option)); + }else{ + $config = self::$config; + } + $name = $config['prefix'] . $name; + // 设置cookie + if(is_array($value)){ + $value = 'think:'.json_encode(array_map('urlencode',$value)); + } + $expire = !empty($config['expire']) ? time() + intval($config['expire']) : 0; + setcookie($name, $value, $expire, $config['path'], $config['domain']); + $_COOKIE[$name] = $value; + } + + /** + * Cookie获取 + * @param string $name cookie名称 + * @param string $prefix cookie前缀 + * @return mixed + */ + static public function get($name, $prefix='') { + $prefix = $prefix?$prefix:self::$config['prefix']; + $name = $prefix . $name; + if(isset($_COOKIE[$name])){ + $value = $_COOKIE[$name]; + if(0===strpos($value,'think:')){ + $value = substr($value,6); + return array_map('urldecode',json_decode($value,true)); + }else{ + return $value; + } + }else{ + return null; + } + } + + /** + * Cookie删除 + * @param string $name cookie名称 + * @param string $prefix cookie前缀 + * @return mixed + */ + static public function delete($name, $prefix='') { + $prefix = $prefix?$prefix:self::$config['prefix']; + $name = $prefix . $name; + setcookie($name, '', time() - 3600, self::$config['path'], self::$config['domain']); + unset($_COOKIE[$name]); // 删除指定cookie + } + + /** + * Cookie清空 + * @param string $prefix cookie前缀 + * @return mixed + */ + static public function clear($prefix='') { + // 清除指定前缀的所有cookie + if (empty($_COOKIE)) + return; + // 要删除的cookie前缀,不指定则删除config设置的指定前缀 + $prefix = $prefix ? $prefix: self::$config['prefix']; + if ($prefix) {// 如果前缀为空字符串将不作处理直接返回 + foreach ($_COOKIE as $key => $val) { + if (0 === strpos($key, $prefix)) { + setcookie($key, '', time() - 3600, self::$config['path'], self::$config['domain']); + unset($_COOKIE[$key]); + } + } + }else{ + unset($_COOKIE); + } + return; + } +} \ No newline at end of file diff --git a/Think/Crypt.php b/Think/Crypt.php new file mode 100644 index 00000000..e6bfba93 --- /dev/null +++ b/Think/Crypt.php @@ -0,0 +1,74 @@ + +// +---------------------------------------------------------------------- +// $Id$ +namespace Think; +class Crypt { + + /** + * 加密字符串 + * @access static + * @param string $str 字符串 + * @param string $key 加密key + * @return string + */ + static public function encrypt($data,$key,$expire=0){ + $key = md5($key); + $data = base64_encode($data); + $x=0; + $len = strlen($data); + $l = strlen($key); + $char = ''; + for ($i=0;$i< $len;$i++) { + if ($x== $l) $x=0; + $char .=substr($key,$x,1); + $x++; + } + $str = sprintf('%010d', $expire ? $expire + time():0); + for ($i=0;$i< $len;$i++) { + $str .=chr(ord(substr($data,$i,1))+(ord(substr($char,$i,1)))%256); + } + return str_replace('=', '',base64_encode($str)); + } + + /** + * 解密字符串 + * @access static + * @param string $str 字符串 + * @param string $key 加密key + * @return string + */ + static public function decrypt($data,$key){ + $key = md5($key); + $x=0; + $data = base64_decode($data); + $expire = substr($data,0,10); + $data = substr($data,10); + if($expire>0 && $expire +// +---------------------------------------------------------------------- +// $Id$ +namespace Think; + +/** + * ThinkPHP 数据库中间层实现类 + * @category Think + * @package Think + * @subpackage Core + * @author liu21st + */ +class Db { + // 数据库类型 + protected $dbType = null; + // 是否自动释放查询结果 + protected $autoFree = false; + // 当前操作所属的模型名 + protected $model = '_think_'; + // 是否使用永久连接 + protected $pconnect = false; + // 当前SQL指令 + protected $queryStr = ''; + protected $modelSql = []; + // 最后插入ID + protected $lastInsID = null; + // 返回或者影响记录数 + protected $numRows = 0; + // 返回字段数 + protected $numCols = 0; + // 事务指令数 + protected $transTimes = 0; + // 错误信息 + protected $error = ''; + // 数据库连接ID 支持多个连接 + protected $linkID = []; + // 当前连接ID + protected $_linkID = null; + // 当前查询ID + protected $queryID = null; + // 是否已经连接数据库 + protected $connected = false; + // 数据库连接参数配置 + protected $config = ''; + // 数据库表达式 + protected $comparison = ['eq'=>'=','neq'=>'<>','gt'=>'>','egt'=>'>=','lt'=>'<','elt'=>'<=','notlike'=>'NOT LIKE','like'=>'LIKE','in'=>'IN','notin'=>'NOT IN']; + // 查询表达式 + protected $selectSql = 'SELECT%DISTINCT% %FIELD% FROM %TABLE%%JOIN%%WHERE%%GROUP%%HAVING%%ORDER%%LIMIT% %UNION%%COMMENT%'; + protected $conf = []; + + /** + * 取得数据库类实例 + * @static + * @access public + * @return mixed 返回数据库驱动类 + */ + public static function getInstance($config) { + static $_instance = []; + $md5 = md5(serialize($config)); + if(!isset($_instance[$md5])) { + $Db = new Db(); + $_instance[$md5] = $Db->factory($config); + } + return $_instance[$md5]; + } + + /** + * 加载数据库 支持配置文件或者 DSN + * @access public + * @param mixed $db_config 数据库配置信息 + * @return string + */ + public function factory($db_config='') { + $this->conf = Config::get(); + // 读取数据库配置 + $db_config = $this->parseConfig($db_config); + if(empty($db_config['dbms'])) + throw_exception(Lang::get('_NO_DB_CONFIG_')); + // 数据库类型 + $db_type = strtolower($db_config['dbms']); + $class = '\Think\Db\Driver\\'. ucwords($db_type); + // 检查驱动类 + if(class_exists($class)) { + $db = new $class($db_config); + // 获取当前的数据库类型 + if( 'pdo' != $db_type ) + $db->dbType = strtoupper($db_type); + else + $db->dbType = $this->_getDsnType($db_config['dsn']); + }else { + // 类没有定义 + throw_exception(Lang::get('_NO_DB_DRIVER_').': ' . $class); + } + return $db; + } + + /** + * 根据DSN获取数据库类型 返回大写 + * @access protected + * @param string $dsn dsn字符串 + * @return string + */ + protected function _getDsnType($dsn) { + $match = explode(':',$dsn); + $dbType = strtoupper(trim($match[0])); + return $dbType; + } + + /** + * 分析数据库配置信息,支持数组和DSN + * @access private + * @param mixed $db_config 数据库配置信息 + * @return string + */ + private function parseConfig($db_config='') { + if ( !empty($db_config) && is_string($db_config)) { + // 如果DSN字符串则进行解析 + $db_config = $this->parseDSN($db_config); + }elseif(is_array($db_config)) { // 数组配置 + $db_config = array_change_key_case($db_config); + $db_config = [ + 'dbms' => $db_config['db_type'], + 'username' => $db_config['db_user'], + 'password' => $db_config['db_pwd'], + 'hostname' => $db_config['db_host'], + 'hostport' => $db_config['db_port'], + 'database' => $db_config['db_name'], + 'dsn' => $db_config['db_dsn'], + 'params' => $db_config['db_params'], + ]; + }elseif(empty($db_config)) { + // 如果配置为空,读取配置文件设置 + if( isset($this->conf['db_dsn']) && $this->conf['db_dsn'] && 'pdo' != strtolower($this->conf['db_type']) ) { // 如果设置了DB_DSN 则优先 + $db_config = $this->parseDSN($this->conf['db_dsn']); + }else{ + $db_config = array ( + 'dbms' => $this->conf['db_type'], + 'username' => $this->conf['db_user'], + 'password' => $this->conf['db_pwd'], + 'hostname' => $this->conf['db_host'], + 'hostport' => $this->conf['db_port'], + 'database' => $this->conf['db_name'], + 'dsn' => isset($this->conf['db_dsn'])?$this->conf['db_dsn']:'', + 'params' => isset($this->conf['db_params'])?$this->conf['db_params']:'', + ); + } + } + return $db_config; + } + + /** + * 初始化数据库连接 + * @access protected + * @param boolean $master 主服务器 + * @return void + */ + protected function initConnect($master=true) { + if(empty($this->conf)) { + $this->conf = Config::get(); + } + + if(1 == $this->conf['db_deploy_type']) + // 采用分布式数据库 + $this->_linkID = $this->multiConnect($master); + else + // 默认单数据库 + if ( !$this->connected ) $this->_linkID = $this->connect(); + } + + /** + * 连接分布式服务器 + * @access protected + * @param boolean $master 主服务器 + * @return void + */ + protected function multiConnect($master=false) { + static $_config = []; + if(empty($_config)) { + // 缓存分布式数据库配置解析 + foreach ($this->config as $key=>$val){ + $_config[$key] = explode(',',$val); + } + } + // 数据库读写是否分离 + if($this->conf['db_rw_separate']){ + // 主从式采用读写分离 + if($master) + // 主服务器写入 + $r = floor(mt_rand(0,$this->conf['db_master_num']-1)); + else{ + if(is_numeric($this->conf['db_slave_no'])) {// 指定服务器读 + $r = $this->conf['db_slave_no']; + }else{ + // 读操作连接从服务器 + $r = floor(mt_rand($this->conf['db_master_num'],count($_config['hostname'])-1)); // 每次随机连接的数据库 + } + } + }else{ + // 读写操作不区分服务器 + $r = floor(mt_rand(0,count($_config['hostname'])-1)); // 每次随机连接的数据库 + } + $db_config = [ + 'username' => isset($_config['username'][$r])?$_config['username'][$r]:$_config['username'][0], + 'password' => isset($_config['password'][$r])?$_config['password'][$r]:$_config['password'][0], + 'hostname' => isset($_config['hostname'][$r])?$_config['hostname'][$r]:$_config['hostname'][0], + 'hostport' => isset($_config['hostport'][$r])?$_config['hostport'][$r]:$_config['hostport'][0], + 'database' => isset($_config['database'][$r])?$_config['database'][$r]:$_config['database'][0], + 'dsn' => isset($_config['dsn'][$r])?$_config['dsn'][$r]:$_config['dsn'][0], + 'params' => isset($_config['params'][$r])?$_config['params'][$r]:$_config['params'][0], + ]; + return $this->connect($db_config,$r); + } + + /** + * DSN解析 + * 格式: mysql://username:passwd@localhost:3306/DbName + * @static + * @access public + * @param string $dsnStr + * @return array + */ + public function parseDSN($dsnStr) { + if( empty($dsnStr) ){return false;} + $info = parse_url($dsnStr); + if($info['scheme']){ + $dsn = [ + 'dbms' => $info['scheme'], + 'username' => isset($info['user']) ? $info['user'] : '', + 'password' => isset($info['pass']) ? $info['pass'] : '', + 'hostname' => isset($info['host']) ? $info['host'] : '', + 'hostport' => isset($info['port']) ? $info['port'] : '', + 'database' => isset($info['path']) ? substr($info['path'],1) : '' + ]; + }else { + preg_match('/^(.*?)\:\/\/(.*?)\:(.*?)\@(.*?)\:([0-9]{1, 6})\/(.*?)$/',trim($dsnStr),$matches); + $dsn = [ + 'dbms' => $matches[1], + 'username' => $matches[2], + 'password' => $matches[3], + 'hostname' => $matches[4], + 'hostport' => $matches[5], + 'database' => $matches[6] + ]; + } + $dsn['dsn'] = ''; // 兼容配置信息数组 + return $dsn; + } + + /** + * 数据库调试 记录当前SQL + * @access protected + */ + protected function debug() { + $this->modelSql[$this->model] = $this->queryStr; + $this->model = '_think_'; + // 记录操作结束时间 + Debug::remark('queryEndTime','time'); + Log::record($this->queryStr.' [ RunTime:'.Debug::getUseTime('queryStartTime','queryEndTime').'s ]','SQL'); + } + + /** + * 设置锁机制 + * @access protected + * @return string + */ + protected function parseLock($lock=false) { + if(!$lock) return ''; + if('ORACLE' == $this->dbType) { + return ' FOR UPDATE NOWAIT '; + } + return ' FOR UPDATE '; + } + + /** + * set分析 + * @access protected + * @param array $data + * @return string + */ + protected function parseSet($data) { + foreach ($data as $key=>$val){ + $value = $this->parseValue($val); + if(is_scalar($value)) // 过滤非标量数据 + $set[] = $this->parseKey($key).'='.$value; + } + return ' SET '.implode(',',$set); + } + + /** + * 字段名分析 + * @access protected + * @param string $key + * @return string + */ + protected function parseKey(&$key) { + return $key; + } + + /** + * value分析 + * @access protected + * @param mixed $value + * @return string + */ + protected function parseValue($value) { + if(is_string($value)) { + $value = '\''.$this->escapeString($value).'\''; + }elseif(isset($value[0]) && is_string($value[0]) && strtolower($value[0]) == 'exp'){ + $value = $this->escapeString($value[1]); + }elseif(is_array($value)) { + $value = array_map(array($this, 'parseValue'),$value); + }elseif(is_bool($value)){ + $value = $value ? '1' : '0'; + }elseif(is_null($value)){ + $value = 'null'; + } + return $value; + } + + /** + * field分析 + * @access protected + * @param mixed $fields + * @return string + */ + protected function parseField($fields) { + if(is_string($fields) && strpos($fields,',')) { + $fields = explode(',',$fields); + } + if(is_array($fields)) { + // 完善数组方式传字段名的支持 + // 支持 'field1'=>'field2' 这样的字段别名定义 + $array = []; + foreach ($fields as $key=>$field){ + if(!is_numeric($key)) + $array[] = $this->parseKey($key).' AS '.$this->parseKey($field); + else + $array[] = $this->parseKey($field); + } + $fieldsStr = implode(',', $array); + }elseif(is_string($fields) && !empty($fields)) { + $fieldsStr = $this->parseKey($fields); + }else{ + $fieldsStr = '*'; + } + //TODO 如果是查询全部字段,并且是join的方式,那么就把要查的表加个别名,以免字段被覆盖 + return $fieldsStr; + } + + /** + * table分析 + * @access protected + * @param mixed $table + * @return string + */ + protected function parseTable($tables) { + if(is_array($tables)) {// 支持别名定义 + $array = []; + foreach ($tables as $table=>$alias){ + if(!is_numeric($table)) + $array[] = $this->parseKey($table).' '.$this->parseKey($alias); + else + $array[] = $this->parseKey($table); + } + $tables = $array; + }elseif(is_string($tables)){ + $tables = explode(',',$tables); + array_walk($tables, array(&$this, 'parseKey')); + } + return implode(',',$tables); + } + + /** + * where分析 + * @access protected + * @param mixed $where + * @return string + */ + protected function parseWhere($where) { + $whereStr = ''; + if(is_string($where)) { + // 直接使用字符串条件 + $whereStr = $where; + }else{ // 使用数组表达式 + $operate = isset($where['_logic'])?strtoupper($where['_logic']):''; + if(in_array($operate,['AND','OR','XOR'])){ + // 定义逻辑运算规则 例如 OR XOR AND NOT + $operate = ' '.$operate.' '; + unset($where['_logic']); + }else{ + // 默认进行 AND 运算 + $operate = ' AND '; + } + foreach ($where as $key=>$val){ + $whereStr .= '( '; + if(0===strpos($key,'_')) { + // 解析特殊条件表达式 + $whereStr .= $this->parseThinkWhere($key,$val); + }else{ + // 查询字段的安全过滤 + if(!preg_match('/^[A-Z_\|\&\-.a-z0-9\(\)\,]+$/',trim($key))){ + throw_exception(L('_EXPRESS_ERROR_').':'.$key); + } + // 多条件支持 + $multi = is_array($val) && isset($val['_multi']); + $key = trim($key); + if(strpos($key,'|')) { // 支持 name|title|nickname 方式定义查询字段 + $array = explode('|',$key); + $str = []; + foreach ($array as $m=>$k){ + $v = $multi?$val[$m]:$val; + $str[] = '('.$this->parseWhereItem($this->parseKey($k),$v).')'; + } + $whereStr .= implode(' OR ',$str); + }elseif(strpos($key,'&')){ + $array = explode('&',$key); + $str = []; + foreach ($array as $m=>$k){ + $v = $multi?$val[$m]:$val; + $str[] = '('.$this->parseWhereItem($this->parseKey($k),$v).')'; + } + $whereStr .= implode(' AND ',$str); + }else{ + $whereStr .= $this->parseWhereItem($this->parseKey($key),$val); + } + } + $whereStr .= ' )'.$operate; + } + $whereStr = substr($whereStr,0,-strlen($operate)); + } + return empty($whereStr)?'':' WHERE '.$whereStr; + } + + // where子单元分析 + protected function parseWhereItem($key,$val) { + $whereStr = ''; + if(is_array($val)) { + if(is_string($val[0])) { + if(preg_match('/^(EQ|NEQ|GT|EGT|LT|ELT)$/i',$val[0])) { // 比较运算 + $whereStr .= $key.' '.$this->comparison[strtolower($val[0])].' '.$this->parseValue($val[1]); + }elseif(preg_match('/^(NOTLIKE|LIKE)$/i',$val[0])){// 模糊查找 + if(is_array($val[1])) { + $likeLogic = isset($val[2])?strtoupper($val[2]):'OR'; + if(in_array($likeLogic,['AND','OR','XOR'])){ + $likeStr = $this->comparison[strtolower($val[0])]; + $like = []; + foreach ($val[1] as $item){ + $like[] = $key.' '.$likeStr.' '.$this->parseValue($item); + } + $whereStr .= '('.implode(' '.$likeLogic.' ',$like).')'; + } + }else{ + $whereStr .= $key.' '.$this->comparison[strtolower($val[0])].' '.$this->parseValue($val[1]); + } + }elseif('exp'==strtolower($val[0])){ // 使用表达式 + $whereStr .= ' ('.$key.' '.$val[1].') '; + }elseif(preg_match('/IN/i',$val[0])){ // IN 运算 + if(isset($val[2]) && 'exp'==$val[2]) { + $whereStr .= $key.' '.strtoupper($val[0]).' '.$val[1]; + }else{ + if(is_string($val[1])) { + $val[1] = explode(',',$val[1]); + } + $zone = implode(',',$this->parseValue($val[1])); + $whereStr .= $key.' '.strtoupper($val[0]).' ('.$zone.')'; + } + }elseif(preg_match('/BETWEEN/i',$val[0])){ // BETWEEN运算 + $data = is_string($val[1])? explode(',',$val[1]):$val[1]; + $whereStr .= ' ('.$key.' '.strtoupper($val[0]).' '.$this->parseValue($data[0]).' AND '.$this->parseValue($data[1]).' )'; + }else{ + throw_exception(L('_EXPRESS_ERROR_').':'.$val[0]); + } + }else { + $count = count($val); + $rule = isset($val[$count-1])?strtoupper($val[$count-1]):''; + if(in_array($rule,['AND','OR','XOR'])) { + $count = $count -1; + }else{ + $rule = 'AND'; + } + for($i=0;$i<$count;$i++) { + $data = is_array($val[$i])?$val[$i][1]:$val[$i]; + if('exp'==strtolower($val[$i][0])) { + $whereStr .= '('.$key.' '.$data.') '.$rule.' '; + }else{ + $op = is_array($val[$i])?$this->comparison[strtolower($val[$i][0])]:'='; + $whereStr .= '('.$key.' '.$op.' '.$this->parseValue($data).') '.$rule.' '; + } + } + $whereStr = substr($whereStr,0,-4); + } + }else { + //对字符串类型字段采用模糊匹配 + if($this->conf['db_like_fields'] && preg_match('/('.$this->conf['db_like_fields'].')/i',$key)) { + $val = '%'.$val.'%'; + $whereStr .= $key.' LIKE '.$this->parseValue($val); + }else { + $whereStr .= $key.' = '.$this->parseValue($val); + } + } + return $whereStr; + } + + /** + * 特殊条件分析 + * @access protected + * @param string $key + * @param mixed $val + * @return string + */ + protected function parseThinkWhere($key,$val) { + $whereStr = ''; + switch($key) { + case '_string': + // 字符串模式查询条件 + $whereStr = $val; + break; + case '_complex': + // 复合查询条件 + $whereStr = substr($this->parseWhere($val),6); + break; + case '_query': + // 字符串模式查询条件 + parse_str($val,$where); + if(isset($where['_logic'])) { + $op = ' '.strtoupper($where['_logic']).' '; + unset($where['_logic']); + }else{ + $op = ' AND '; + } + $array = []; + foreach ($where as $field=>$data) + $array[] = $this->parseKey($field).' = '.$this->parseValue($data); + $whereStr = implode($op,$array); + break; + } + return $whereStr; + } + + /** + * limit分析 + * @access protected + * @param mixed $lmit + * @return string + */ + protected function parseLimit($limit) { + return !empty($limit)? ' LIMIT '.$limit.' ':''; + } + + /** + * join分析 + * @access protected + * @param mixed $join + * @return string + */ + protected function parseJoin($join) { + $joinStr = ''; + if(!empty($join)) { + if(is_array($join)) { + foreach ($join as $key=>$_join){ + if(false !== stripos($_join,'JOIN')) + $joinStr .= ' '.$_join; + else + $joinStr .= ' LEFT JOIN ' .$_join; + } + }else{ + $joinStr .= ' LEFT JOIN ' .$join; + } + } + //将__TABLE_NAME__这样的字符串替换成正规的表名,并且带上前缀和后缀 + $joinStr = preg_replace("/__([A-Z_-]+)__/esU",Config::get('db_prefix').".strtolower('$1')",$joinStr); + return $joinStr; + } + + /** + * order分析 + * @access protected + * @param mixed $order + * @return string + */ + protected function parseOrder($order) { + if(is_array($order)) { + $array = []; + foreach ($order as $key=>$val){ + if(is_numeric($key)) { + $array[] = $this->parseKey($val); + }else{ + $array[] = $this->parseKey($key).' '.$val; + } + } + $order = implode(',',$array); + } + return !empty($order)? ' ORDER BY '.$order:''; + } + + /** + * group分析 + * @access protected + * @param mixed $group + * @return string + */ + protected function parseGroup($group) { + return !empty($group)? ' GROUP BY '.$group:''; + } + + /** + * having分析 + * @access protected + * @param string $having + * @return string + */ + protected function parseHaving($having) { + return !empty($having)? ' HAVING '.$having:''; + } + + /** + * comment分析 + * @access protected + * @param string $comment + * @return string + */ + protected function parseComment($comment) { + return !empty($comment)? ' /* '.$comment.' */':''; + } + + /** + * distinct分析 + * @access protected + * @param mixed $distinct + * @return string + */ + protected function parseDistinct($distinct) { + return !empty($distinct)? ' DISTINCT ' :''; + } + + /** + * union分析 + * @access protected + * @param mixed $union + * @return string + */ + protected function parseUnion($union) { + if(empty($union)) return ''; + if(isset($union['_all'])) { + $str = 'UNION ALL '; + unset($union['_all']); + }else{ + $str = 'UNION '; + } + foreach ($union as $u){ + $sql[] = $str.(is_array($u)?$this->buildSelectSql($u):$u); + } + return implode(' ',$sql); + } + + /** + * 插入记录 + * @access public + * @param mixed $data 数据 + * @param array $options 参数表达式 + * @param boolean $replace 是否replace + * @return false | integer + */ + public function insert($data,$options=[],$replace=false) { + $values = $fields = []; + $this->model = $options['model']; + foreach ($data as $key=>$val){ + $value = $this->parseValue($val); + if(is_scalar($value)) { // 过滤非标量数据 + $values[] = $value; + $fields[] = $this->parseKey($key); + } + } + $sql = ($replace?'REPLACE':'INSERT').' INTO '.$this->parseTable($options['table']).' ('.implode(',', $fields).') VALUES ('.implode(',', $values).')'; + $sql .= $this->parseLock(isset($options['lock'])?$options['lock']:false); + $sql .= $this->parseComment(!empty($options['comment'])?$options['comment']:''); + return $this->execute($sql); + } + + /** + * 通过Select方式插入记录 + * @access public + * @param string $fields 要插入的数据表字段名 + * @param string $table 要插入的数据表名 + * @param array $option 查询数据参数 + * @return false | integer + */ + public function selectInsert($fields,$table,$options=[]) { + $this->model = $options['model']; + if(is_string($fields)) $fields = explode(',',$fields); + array_walk($fields, [$this, 'parseKey']); + $sql = 'INSERT INTO '.$this->parseTable($table).' ('.implode(',', $fields).') '; + $sql .= $this->buildSelectSql($options); + return $this->execute($sql); + } + + /** + * 更新记录 + * @access public + * @param mixed $data 数据 + * @param array $options 表达式 + * @return false | integer + */ + public function update($data,$options) { + $this->model = $options['model']; + $sql = 'UPDATE ' + .$this->parseTable($options['table']) + .$this->parseSet($data) + .$this->parseWhere(!empty($options['where'])?$options['where']:'') + .$this->parseOrder(!empty($options['order'])?$options['order']:'') + .$this->parseLimit(!empty($options['limit'])?$options['limit']:'') + .$this->parseLock(isset($options['lock'])?$options['lock']:false) + .$this->parseComment(!empty($options['comment'])?$options['comment']:''); + return $this->execute($sql); + } + + /** + * 删除记录 + * @access public + * @param array $options 表达式 + * @return false | integer + */ + public function delete($options=[]) { + $this->model = $options['model']; + $sql = 'DELETE FROM ' + .$this->parseTable($options['table']) + .$this->parseWhere(!empty($options['where'])?$options['where']:'') + .$this->parseOrder(!empty($options['order'])?$options['order']:'') + .$this->parseLimit(!empty($options['limit'])?$options['limit']:'') + .$this->parseLock(isset($options['lock'])?$options['lock']:false) + .$this->parseComment(!empty($options['comment'])?$options['comment']:''); + return $this->execute($sql); + } + + /** + * 查找记录 + * @access public + * @param array $options 表达式 + * @return mixed + */ + public function select($options=[]) { + $this->model = $options['model']; + $sql = $this->buildSelectSql($options); + $cache = isset($options['cache'])?$options['cache']:false; + if($cache) { // 查询缓存检测 + $key = is_string($cache['key'])?$cache['key']:md5($sql); + $value = S($key,'',$cache); + if(false !== $value) { + return $value; + } + } + $result = $this->query($sql); + if($cache && false !== $result ) { // 查询缓存写入 + S($key,$result,$cache); + } + return $result; + } + + /** + * 生成查询SQL + * @access public + * @param array $options 表达式 + * @return string + */ + public function buildSelectSql($options=[]) { + if(isset($options['page'])) { + // 根据页数计算limit + if(strpos($options['page'],',')) { + list($page,$listRows) = explode(',',$options['page']); + }else{ + $page = $options['page']; + } + $page = $page?$page:1; + $listRows= isset($listRows)?$listRows:(is_numeric($options['limit'])?$options['limit']:20); + $offset = $listRows*((int)$page-1); + $options['limit'] = $offset.','.$listRows; + } + $sql = $this->parseSql($this->selectSql,$options); + $sql .= $this->parseLock(isset($options['lock'])?$options['lock']:false); + return $sql; + } + + /** + * 替换SQL语句中表达式 + * @access public + * @param array $options 表达式 + * @return string + */ + public function parseSql($sql,$options=[]){ + $sql = str_replace( + ['%TABLE%','%DISTINCT%','%FIELD%','%JOIN%','%WHERE%','%GROUP%','%HAVING%','%ORDER%','%LIMIT%','%UNION%','%COMMENT%'], + [ + $this->parseTable($options['table']), + $this->parseDistinct(isset($options['distinct'])?$options['distinct']:false), + $this->parseField(!empty($options['field'])?$options['field']:'*'), + $this->parseJoin(!empty($options['join'])?$options['join']:''), + $this->parseWhere(!empty($options['where'])?$options['where']:''), + $this->parseGroup(!empty($options['group'])?$options['group']:''), + $this->parseHaving(!empty($options['having'])?$options['having']:''), + $this->parseOrder(!empty($options['order'])?$options['order']:''), + $this->parseLimit(!empty($options['limit'])?$options['limit']:''), + $this->parseUnion(!empty($options['union'])?$options['union']:''), + $this->parseComment(!empty($options['comment'])?$options['comment']:'') + ],$sql); + return $sql; + } + + /** + * 获取最近一次查询的sql语句 + * @param string $model 模型名 + * @access public + * @return string + */ + public function getLastSql($model='') { + return $model?$this->modelSql[$model]:$this->queryStr; + } + + /** + * 获取最近插入的ID + * @access public + * @return string + */ + public function getLastInsID() { + return $this->lastInsID; + } + + /** + * 获取最近的错误信息 + * @access public + * @return string + */ + public function getError() { + return $this->error; + } + + /** + * SQL指令安全过滤 + * @access public + * @param string $str SQL字符串 + * @return string + */ + public function escapeString($str) { + return addslashes($str); + } + + /** + * 设置当前操作模型 + * @access public + * @param string $model 模型名 + * @return void + */ + public function setModel($model){ + $this->model = $model; + } + + /** + * 析构方法 + * @access public + */ + public function __destruct() { + // 释放查询 + if ($this->queryID){ + $this->free(); + } + // 关闭连接 + $this->close(); + } + + // 关闭数据库 由驱动类定义 + public function close(){} +} \ No newline at end of file diff --git a/Think/Db/Driver/Ibase.php b/Think/Db/Driver/Ibase.php new file mode 100644 index 00000000..2c0acf14 --- /dev/null +++ b/Think/Db/Driver/Ibase.php @@ -0,0 +1,339 @@ + +// +---------------------------------------------------------------------- + +namespace Think\Db\Driver; +use Think\Db; +/** + * Firebird数据库驱动 + * @category Extend + * @package Extend + * @subpackage Driver.Db + * @author 剑雷 + */ +class Ibase extends Db{ + + protected $selectSql = 'SELECT %LIMIT% %DISTINCT% %FIELD% FROM %TABLE%%JOIN%%WHERE%%GROUP%%HAVING%%ORDER%'; + /** + * 架构函数 读取数据库配置信息 + * @access public + * @param array $config 数据库配置数组 + */ + public function __construct($config='') { + if ( !extension_loaded('interbase') ) { + throw_exception(L('_NOT_SUPPERT_').':Interbase or Firebird'); + } + if(!empty($config)) { + $this->config = $config; + if(empty($this->config['params'])) { + $this->config['params'] = array(); + } + } + } + + /** + * 连接数据库方法 + * @access public + * @throws ThinkExecption + */ + public function connect($config='',$linkNum=0) { + if ( !isset($this->linkID[$linkNum]) ) { + if(empty($config)) $config = $this->config; + $pconnect = !empty($config['params']['persist'])? $config['params']['persist']:$this->pconnect; + $conn = $pconnect ? 'ibase_pconnect':'ibase_connect'; + // 处理不带端口号的socket连接情况 + $host = $config['hostname'].($config['hostport']?"/{$config['hostport']}":''); + $this->linkID[$linkNum] = $conn($host.':'.$config['database'], $config['username'], $config['password'],C('DB_CHARSET'),0,3); + if ( !$this->linkID[$linkNum]) { + throw_exception(ibase_errmsg()); + } + // 标记连接成功 + $this->connected = true; + // 注销数据库连接配置信息 + if(1 != Config::get('DB_DEPLOY_TYPE')) unset($this->config); + } + return $this->linkID[$linkNum]; + } + + /** + * 释放查询结果 + * @access public + */ + public function free() { + ibase_free_result($this->queryID); + $this->queryID = null; + } + + /** + * 执行查询 返回数据集 + * @access public + * @param string $str sql指令 + * @return mixed + */ + public function query($str) { + $this->initConnect(false); + if ( !$this->_linkID ) return false; + $this->queryStr = $str; + //释放前次的查询结果 + if ( $this->queryID ) $this->free(); + N('db_query',1); + // 记录开始执行时间 + G('queryStartTime'); + $this->queryID = ibase_query($this->_linkID, $str); + $this->debug(); + if ( false === $this->queryID ) { + $this->error(); + return false; + } else { + return $this->getAll(); + } + } + + /** + * 执行语句 + * @access public + * @param string $str sql指令 + * @return integer + */ + public function execute($str) { + $this->initConnect(true); + if ( !$this->_linkID ) return false; + $this->queryStr = $str; + //释放前次的查询结果 + if ( $this->queryID ) $this->free(); + N('db_write',1); + // 记录开始执行时间 + G('queryStartTime'); + $result = ibase_query($this->_linkID, $str) ; + $this->debug(); + if ( false === $result) { + $this->error(); + return false; + } else { + $this->numRows = ibase_affected_rows($this->_linkID); + $this->lastInsID =0; + return $this->numRows; + } + } + + public function startTrans() { + $this->initConnect(true); + if ( !$this->_linkID ) return false; + //数据rollback 支持 + if ($this->transTimes == 0) { + ibase_trans( IBASE_DEFAULT, $this->_linkID); + } + $this->transTimes++; + return ; + } + + /** + * 用于非自动提交状态下面的查询提交 + * @access public + * @return boolen + */ + public function commit() { + if ($this->transTimes > 0) { + $result = ibase_commit($this->_linkID); + $this->transTimes = 0; + if(!$result){ + $this->error(); + return false; + } + } + return true; + } + + /** + * 事务回滚 + * @access public + * @return boolen + */ + public function rollback() { + if ($this->transTimes > 0) { + $result =ibase_rollback($this->_linkID); + $this->transTimes = 0; + if(!$result){ + $this->error(); + return false; + } + } + return true; + } + + /** + * BLOB字段解密函数 Firebird特有 + * @access public + * @param $blob 待解密的BLOB + * @return 二进制数据 + */ + public function BlobDecode($blob) { + $maxblobsize = 262144; + $blob_data = ibase_blob_info($this->_linkID, $blob ); + $blobid = ibase_blob_open($this->_linkID, $blob ); + if( $blob_data[0] > $maxblobsize ) { + $realblob = ibase_blob_get($blobid, $maxblobsize); + while($string = ibase_blob_get($blobid, 8192)){ + $realblob .= $string; + } + } else { + $realblob = ibase_blob_get($blobid, $blob_data[0]); + } + ibase_blob_close( $blobid ); + return( $realblob ); + } + + /** + * 获得所有的查询数据 + * @access private + * @return array + */ + private function getAll() { + //返回数据集 + $result = array(); + while ( $row = ibase_fetch_assoc($this->queryID)) { + $result[] = $row; + } + //剑雷 2007.12.30 自动解密BLOB字段 + //取BLOB字段清单 + $bloblist = array(); + $fieldCount = ibase_num_fields($this->queryID); + for ($i = 0; $i < $fieldCount; $i++) { + $col_info = ibase_field_info($this->queryID, $i); + if ($col_info['type']=='BLOB') { + $bloblist[]=trim($col_info['name']); + } + } + //如果有BLOB字段,就进行解密处理 + if (!empty($bloblist)) { + $i=0; + foreach ($result as $row) { + foreach($bloblist as $field) { + if (!empty($row[$field])) $result[$i][$field]=$this->BlobDecode($row[$field]); + } + $i++; + } + } + return $result; + } + + /** + * 取得数据表的字段信息 + * @access public + */ + public function getFields($tableName) { + $result = $this->query('SELECT RDB$FIELD_NAME AS FIELD, RDB$DEFAULT_VALUE AS DEFAULT1, RDB$NULL_FLAG AS NULL1 FROM RDB$RELATION_FIELDS WHERE RDB$RELATION_NAME=UPPER(\''.$tableName.'\') ORDER By RDB$FIELD_POSITION'); + $info = array(); + if($result) { + foreach ($result as $key => $val) { + $info[trim($val['FIELD'])] = array( + 'name' => trim($val['FIELD']), + 'type' => '', + 'notnull' => (bool) ($val['NULL1'] ==1), // 1表示不为Null + 'default' => $val['DEFAULT1'], + 'primary' => false, + 'autoinc' => false, + ); + } + } + //剑雷 取表字段类型 + $sql='select first 1 * from '. $tableName; + $rs_temp = ibase_query ($this->_linkID, $sql); + $fieldCount = ibase_num_fields($rs_temp); + + for ($i = 0; $i < $fieldCount; $i++) + { + $col_info = ibase_field_info($rs_temp, $i); + $info[trim($col_info['name'])]['type']=$col_info['type']; + } + ibase_free_result ($rs_temp); + + //剑雷 取表的主键 + $sql='select b.rdb$field_name as FIELD_NAME from rdb$relation_constraints a join rdb$index_segments b +on a.rdb$index_name=b.rdb$index_name +where a.rdb$constraint_type=\'PRIMARY KEY\' and a.rdb$relation_name=UPPER(\''.$tableName.'\')'; + $rs_temp = ibase_query ($this->_linkID, $sql); + while ($row=ibase_fetch_object($rs_temp)) { + $info[trim($row->FIELD_NAME)]['primary']=True; + } + ibase_free_result ($rs_temp); + + return $info; + } + + /** + * 取得数据库的表信息 + * @access public + */ + public function getTables($dbName='') { + $sql='SELECT DISTINCT RDB$RELATION_NAME FROM RDB$RELATION_FIELDS WHERE RDB$SYSTEM_FLAG=0'; + $result = $this->query($sql); + $info = array(); + foreach ($result as $key => $val) { + $info[$key] = trim(current($val)); + } + return $info; + } + + /** + * 关闭数据库 + * @access public + */ + public function close() { + if ($this->_linkID){ + ibase_close($this->_linkID); + } + $this->_linkID = null; + } + + /** + * 数据库错误信息 + * 并显示当前的SQL语句 + * @access public + * @return string + */ + public function error() { + $this->error = ibase_errmsg(); + if('' != $this->queryStr){ + $this->error .= "\n [ SQL语句 ] : ".$this->queryStr; + } + ThinkLog::record($this->error,'ERR'); + return $this->error; + } + + /** + * SQL指令安全过滤 + * @access public + * @param string $str SQL指令 + * @return string + */ + public function escapeString($str) { + return str_replace("'", "''", $str); + } + + /** + * limit + * @access public + * @param $limit limit表达式 + * @return string + */ + public function parseLimit($limit) { + $limitStr = ''; + if(!empty($limit)) { + $limit = explode(',',$limit); + if(count($limit)>1) { + $limitStr = ' FIRST '.($limit[1]-$limit[0]).' SKIP '.$limit[0].' '; + }else{ + $limitStr = ' FIRST '.$limit[0].' '; + } + } + return $limitStr; + } +} \ No newline at end of file diff --git a/Think/Db/Driver/Mongo.php b/Think/Db/Driver/Mongo.php new file mode 100644 index 00000000..b0d9ea31 --- /dev/null +++ b/Think/Db/Driver/Mongo.php @@ -0,0 +1,752 @@ + +// +---------------------------------------------------------------------- + +namespace Think\Db\Driver; +use Think\Db; +/** + * Mongo数据库驱动 必须配合MongoModel使用 + * @category Extend + * @package Extend + * @subpackage Driver.Db + * @author liu21st + */ +class Mongo extends Db{ + + protected $_mongo = null; // MongoDb Object + protected $_collection = null; // MongoCollection Object + protected $_dbName = ''; // dbName + protected $_collectionName = ''; // collectionName + protected $_cursor = null; // MongoCursor Object + protected $comparison = array('neq'=>'ne','ne'=>'ne','gt'=>'gt','egt'=>'gte','gte'=>'gte','lt'=>'lt','elt'=>'lte','lte'=>'lte','in'=>'in','not in'=>'nin','nin'=>'nin'); + + /** + * 架构函数 读取数据库配置信息 + * @access public + * @param array $config 数据库配置数组 + */ + public function __construct($config=''){ + if ( !class_exists('mongo') ) { + throw_exception(L('_NOT_SUPPERT_').':mongo'); + } + if(!empty($config)) { + $this->config = $config; + if(empty($this->config['params'])) { + $this->config['params'] = array(); + } + } + } + + /** + * 连接数据库方法 + * @access public + */ + public function connect($config='',$linkNum=0) { + if ( !isset($this->linkID[$linkNum]) ) { + if(empty($config)) $config = $this->config; + $host = 'mongodb://'.($config['username']?"{$config['username']}":'').($config['password']?":{$config['password']}@":'').$config['hostname'].($config['hostport']?":{$config['hostport']}":'').'/'.($config['database']?"{$config['database']}":''); + try{ + $this->linkID[$linkNum] = new mongo( $host,$config['params']); + }catch (MongoConnectionException $e){ + throw_exception($e->getmessage()); + } + // 标记连接成功 + $this->connected = true; + // 注销数据库连接配置信息 + if(1 != C('DB_DEPLOY_TYPE')) unset($this->config); + } + return $this->linkID[$linkNum]; + } + + /** + * 切换当前操作的Db和Collection + * @access public + * @param string $collection collection + * @param string $db db + * @param boolean $master 是否主服务器 + * @return void + */ + public function switchCollection($collection,$db='',$master=true){ + // 当前没有连接 则首先进行数据库连接 + if ( !$this->_linkID ) $this->initConnect($master); + try{ + if(!empty($db)) { // 传人Db则切换数据库 + // 当前MongoDb对象 + $this->_dbName = $db; + $this->_mongo = $this->_linkID->selectDb($db); + } + // 当前MongoCollection对象 + if(C('DB_SQL_LOG')) { + $this->queryStr = $this->_dbName.'.getCollection('.$collection.')'; + } + if($this->_collectionName != $collection) { + N('db_read',1); + // 记录开始执行时间 + G('queryStartTime'); + $this->_collection = $this->_mongo->selectCollection($collection); + $this->debug(); + $this->_collectionName = $collection; // 记录当前Collection名称 + } + }catch (MongoException $e){ + throw_exception($e->getMessage()); + } + } + + /** + * 释放查询结果 + * @access public + */ + public function free() { + $this->_cursor = null; + } + + /** + * 执行命令 + * @access public + * @param array $command 指令 + * @return array + */ + public function command($command=array()) { + N('db_write',1); + $this->queryStr = 'command:'.json_encode($command); + // 记录开始执行时间 + G('queryStartTime'); + $result = $this->_mongo->command($command); + $this->debug(); + if(!$result['ok']) { + throw_exception($result['errmsg']); + } + return $result; + } + + /** + * 执行语句 + * @access public + * @param string $code sql指令 + * @param array $args 参数 + * @return mixed + */ + public function execute($code,$args=array()) { + N('db_write',1); + $this->queryStr = 'execute:'.$code; + // 记录开始执行时间 + G('queryStartTime'); + $result = $this->_mongo->execute($code,$args); + $this->debug(); + if($result['ok']) { + return $result['retval']; + }else{ + throw_exception($result['errmsg']); + } + } + + /** + * 关闭数据库 + * @access public + */ + public function close() { + if($this->_linkID) { + $this->_linkID->close(); + $this->_linkID = null; + $this->_mongo = null; + $this->_collection = null; + $this->_cursor = null; + } + } + + /** + * 数据库错误信息 + * @access public + * @return string + */ + public function error() { + $this->error = $this->_mongo->lastError(); + ThinkLog::record($this->error,'ERR'); + return $this->error; + } + + /** + * 插入记录 + * @access public + * @param mixed $data 数据 + * @param array $options 参数表达式 + * @param boolean $replace 是否replace + * @return false | integer + */ + public function insert($data,$options=array(),$replace=false) { + if(isset($options['table'])) { + $this->switchCollection($options['table']); + } + $this->model = $options['model']; + N('db_write',1); + if(C('DB_SQL_LOG')) { + $this->queryStr = $this->_dbName.'.'.$this->_collectionName.'.insert('; + $this->queryStr .= $data?json_encode($data):'{}'; + $this->queryStr .= ')'; + } + try{ + // 记录开始执行时间 + G('queryStartTime'); + $result = $replace? $this->_collection->save($data,true): $this->_collection->insert($data,true); + $this->debug(); + if($result) { + $_id = $data['_id']; + if(is_object($_id)) { + $_id = $_id->__toString(); + } + $this->lastInsID = $_id; + } + return $result; + } catch (MongoCursorException $e) { + throw_exception($e->getMessage()); + } + } + + /** + * 插入多条记录 + * @access public + * @param array $dataList 数据 + * @param array $options 参数表达式 + * @return bool + */ + public function insertAll($dataList,$options=array()) { + if(isset($options['table'])) { + $this->switchCollection($options['table']); + } + $this->model = $options['model']; + N('db_write',1); + try{ + // 记录开始执行时间 + G('queryStartTime'); + $result = $this->_collection->batchInsert($dataList); + $this->debug(); + return $result; + } catch (MongoCursorException $e) { + throw_exception($e->getMessage()); + } + } + + /** + * 生成下一条记录ID 用于自增非MongoId主键 + * @access public + * @param string $pk 主键名 + * @return integer + */ + public function mongo_next_id($pk) { + N('db_read',1); + if(C('DB_SQL_LOG')) { + $this->queryStr = $this->_dbName.'.'.$this->_collectionName.'.find({},{'.$pk.':1}).sort({'.$pk.':-1}).limit(1)'; + } + try{ + // 记录开始执行时间 + G('queryStartTime'); + $result = $this->_collection->find(array(),array($pk=>1))->sort(array($pk=>-1))->limit(1); + $this->debug(); + } catch (MongoCursorException $e) { + throw_exception($e->getMessage()); + } + $data = $result->getNext(); + return isset($data[$pk])?$data[$pk]+1:1; + } + + /** + * 更新记录 + * @access public + * @param mixed $data 数据 + * @param array $options 表达式 + * @return bool + */ + public function update($data,$options) { + if(isset($options['table'])) { + $this->switchCollection($options['table']); + } + $this->model = $options['model']; + N('db_write',1); + $query = $this->parseWhere($options['where']); + $set = $this->parseSet($data); + if(C('DB_SQL_LOG')) { + $this->queryStr = $this->_dbName.'.'.$this->_collectionName.'.update('; + $this->queryStr .= $query?json_encode($query):'{}'; + $this->queryStr .= ','.json_encode($set).')'; + } + try{ + // 记录开始执行时间 + G('queryStartTime'); + $result = $this->_collection->update($query,$set,array("multiple" => true)); + $this->debug(); + return $result; + } catch (MongoCursorException $e) { + throw_exception($e->getMessage()); + } + } + + /** + * 删除记录 + * @access public + * @param array $options 表达式 + * @return false | integer + */ + public function delete($options=array()) { + if(isset($options['table'])) { + $this->switchCollection($options['table']); + } + $query = $this->parseWhere($options['where']); + $this->model = $options['model']; + N('db_write',1); + if(C('DB_SQL_LOG')) { + $this->queryStr = $this->_dbName.'.'.$this->_collectionName.'.remove('.json_encode($query).')'; + } + try{ + // 记录开始执行时间 + G('queryStartTime'); + $result = $this->_collection->remove($query); + $this->debug(); + return $result; + } catch (MongoCursorException $e) { + throw_exception($e->getMessage()); + } + } + + /** + * 清空记录 + * @access public + * @param array $options 表达式 + * @return false | integer + */ + public function clear($options=array()){ + if(isset($options['table'])) { + $this->switchCollection($options['table']); + } + $this->model = $options['model']; + N('db_write',1); + if(C('DB_SQL_LOG')) { + $this->queryStr = $this->_dbName.'.'.$this->_collectionName.'.remove({})'; + } + try{ + // 记录开始执行时间 + G('queryStartTime'); + $result = $this->_collection->drop(); + $this->debug(); + return $result; + } catch (MongoCursorException $e) { + throw_exception($e->getMessage()); + } + } + + /** + * 查找记录 + * @access public + * @param array $options 表达式 + * @return iterator + */ + public function select($options=array()) { + if(isset($options['table'])) { + $this->switchCollection($options['table'],'',false); + } + $cache = isset($options['cache'])?$options['cache']:false; + if($cache) { // 查询缓存检测 + $key = is_string($cache['key'])?$cache['key']:md5(serialize($options)); + $value = S($key,'','',$cache['type']); + if(false !== $value) { + return $value; + } + } + $this->model = $options['model']; + N('db_query',1); + $query = $this->parseWhere($options['where']); + $field = $this->parseField($options['field']); + try{ + if(C('DB_SQL_LOG')) { + $this->queryStr = $this->_dbName.'.'.$this->_collectionName.'.find('; + $this->queryStr .= $query? json_encode($query):'{}'; + $this->queryStr .= $field? ','.json_encode($field):''; + $this->queryStr .= ')'; + } + // 记录开始执行时间 + G('queryStartTime'); + $_cursor = $this->_collection->find($query,$field); + if($options['order']) { + $order = $this->parseOrder($options['order']); + if(C('DB_SQL_LOG')) { + $this->queryStr .= '.sort('.json_encode($order).')'; + } + $_cursor = $_cursor->sort($order); + } + if(isset($options['page'])) { // 根据页数计算limit + if(strpos($options['page'],',')) { + list($page,$length) = explode(',',$options['page']); + }else{ + $page = $options['page']; + } + $page = $page?$page:1; + $length = isset($length)?$length:(is_numeric($options['limit'])?$options['limit']:20); + $offset = $length*((int)$page-1); + $options['limit'] = $offset.','.$length; + } + if(isset($options['limit'])) { + list($offset,$length) = $this->parseLimit($options['limit']); + if(!empty($offset)) { + if(C('DB_SQL_LOG')) { + $this->queryStr .= '.skip('.intval($offset).')'; + } + $_cursor = $_cursor->skip(intval($offset)); + } + if(C('DB_SQL_LOG')) { + $this->queryStr .= '.limit('.intval($length).')'; + } + $_cursor = $_cursor->limit(intval($length)); + } + $this->debug(); + $this->_cursor = $_cursor; + $resultSet = iterator_to_array($_cursor); + if($cache && $resultSet ) { // 查询缓存写入 + S($key,$resultSet,$cache['expire'],$cache['type']); + } + return $resultSet; + } catch (MongoCursorException $e) { + throw_exception($e->getMessage()); + } + } + + /** + * 查找某个记录 + * @access public + * @param array $options 表达式 + * @return array + */ + public function find($options=array()){ + if(isset($options['table'])) { + $this->switchCollection($options['table'],'',false); + } + $cache = isset($options['cache'])?$options['cache']:false; + if($cache) { // 查询缓存检测 + $key = is_string($cache['key'])?$cache['key']:md5(serialize($options)); + $value = S($key,'','',$cache['type']); + if(false !== $value) { + return $value; + } + } + $this->model = $options['model']; + N('db_query',1); + $query = $this->parseWhere($options['where']); + $fields = $this->parseField($options['field']); + if(C('DB_SQL_LOG')) { + $this->queryStr = $this->_dbName.'.'.$this->_collectionName.'.findOne('; + $this->queryStr .= $query?json_encode($query):'{}'; + $this->queryStr .= $fields?','.json_encode($fields):''; + $this->queryStr .= ')'; + } + try{ + // 记录开始执行时间 + G('queryStartTime'); + $result = $this->_collection->findOne($query,$fields); + $this->debug(); + if($cache && $result ) { // 查询缓存写入 + S($key,$result,$cache['expire'],$cache['type']); + } + return $result; + } catch (MongoCursorException $e) { + throw_exception($e->getMessage()); + } + } + + /** + * 统计记录数 + * @access public + * @param array $options 表达式 + * @return iterator + */ + public function count($options=array()){ + if(isset($options['table'])) { + $this->switchCollection($options['table'],'',false); + } + $this->model = $options['model']; + N('db_query',1); + $query = $this->parseWhere($options['where']); + if(C('DB_SQL_LOG')) { + $this->queryStr = $this->_dbName.'.'.$this->_collectionName; + $this->queryStr .= $query?'.find('.json_encode($query).')':''; + $this->queryStr .= '.count()'; + } + try{ + // 记录开始执行时间 + G('queryStartTime'); + $count = $this->_collection->count($query); + $this->debug(); + return $count; + } catch (MongoCursorException $e) { + throw_exception($e->getMessage()); + } + } + + public function group($keys,$initial,$reduce,$options=array()){ + $this->_collection->group($keys,$initial,$reduce,$options); + } + + /** + * 取得数据表的字段信息 + * @access public + * @return array + */ + public function getFields($collection=''){ + if(!empty($collection) && $collection != $this->_collectionName) { + $this->switchCollection($collection,'',false); + } + N('db_query',1); + if(C('DB_SQL_LOG')) { + $this->queryStr = $this->_dbName.'.'.$this->_collectionName.'.findOne()'; + } + try{ + // 记录开始执行时间 + G('queryStartTime'); + $result = $this->_collection->findOne(); + $this->debug(); + } catch (MongoCursorException $e) { + throw_exception($e->getMessage()); + } + if($result) { // 存在数据则分析字段 + $info = array(); + foreach ($result as $key=>$val){ + $info[$key] = array( + 'name'=>$key, + 'type'=>getType($val), + ); + } + return $info; + } + // 暂时没有数据 返回false + return false; + } + + /** + * 取得当前数据库的collection信息 + * @access public + */ + public function getTables(){ + if(C('DB_SQL_LOG')) { + $this->queryStr = $this->_dbName.'.getCollenctionNames()'; + } + N('db_query',1); + // 记录开始执行时间 + G('queryStartTime'); + $list = $this->_mongo->listCollections(); + $this->debug(); + $info = array(); + foreach ($list as $collection){ + $info[] = $collection->getName(); + } + return $info; + } + + /** + * set分析 + * @access protected + * @param array $data + * @return string + */ + protected function parseSet($data) { + $result = array(); + foreach ($data as $key=>$val){ + if(is_array($val)) { + switch($val[0]) { + case 'inc': + $result['$inc'][$key] = (int)$val[1]; + break; + case 'set': + case 'unset': + case 'push': + case 'pushall': + case 'addtoset': + case 'pop': + case 'pull': + case 'pullall': + $result['$'.$val[0]][$key] = $val[1]; + break; + default: + $result['$set'][$key] = $val; + } + }else{ + $result['$set'][$key] = $val; + } + } + return $result; + } + + /** + * order分析 + * @access protected + * @param mixed $order + * @return array + */ + protected function parseOrder($order) { + if(is_string($order)) { + $array = explode(',',$order); + $order = array(); + foreach ($array as $key=>$val){ + $arr = explode(' ',trim($val)); + if(isset($arr[1])) { + $arr[1] = $arr[1]=='asc'?1:-1; + }else{ + $arr[1] = 1; + } + $order[$arr[0]] = $arr[1]; + } + } + return $order; + } + + /** + * limit分析 + * @access protected + * @param mixed $limit + * @return array + */ + protected function parseLimit($limit) { + if(strpos($limit,',')) { + $array = explode(',',$limit); + }else{ + $array = array(0,$limit); + } + return $array; + } + + /** + * field分析 + * @access protected + * @param mixed $fields + * @return array + */ + public function parseField($fields){ + if(empty($fields)) { + $fields = array(); + } + if(is_string($fields)) { + $fields = explode(',',$fields); + } + return $fields; + } + + /** + * where分析 + * @access protected + * @param mixed $where + * @return array + */ + public function parseWhere($where){ + $query = array(); + foreach ($where as $key=>$val){ + if('_id' != $key && 0===strpos($key,'_')) { + // 解析特殊条件表达式 + $query = $this->parseThinkWhere($key,$val); + }else{ + // 查询字段的安全过滤 + if(!preg_match('/^[A-Z_\|\&\-.a-z0-9]+$/',trim($key))){ + throw_exception(L('_ERROR_QUERY_').':'.$key); + } + $key = trim($key); + if(strpos($key,'|')) { + $array = explode('|',$key); + $str = array(); + foreach ($array as $k){ + $str[] = $this->parseWhereItem($k,$val); + } + $query['$or'] = $str; + }elseif(strpos($key,'&')){ + $array = explode('&',$key); + $str = array(); + foreach ($array as $k){ + $str[] = $this->parseWhereItem($k,$val); + } + $query = array_merge($query,$str); + }else{ + $str = $this->parseWhereItem($key,$val); + $query = array_merge($query,$str); + } + } + } + return $query; + } + + /** + * 特殊条件分析 + * @access protected + * @param string $key + * @param mixed $val + * @return string + */ + protected function parseThinkWhere($key,$val) { + $query = array(); + switch($key) { + case '_query': // 字符串模式查询条件 + parse_str($val,$query); + if(isset($query['_logic']) && strtolower($query['_logic']) == 'or' ) { + unset($query['_logic']); + $query['$or'] = $query; + } + break; + case '_string':// MongoCode查询 + $query['$where'] = new MongoCode($val); + break; + } + return $query; + } + + /** + * where子单元分析 + * @access protected + * @param string $key + * @param mixed $val + * @return array + */ + protected function parseWhereItem($key,$val) { + $query = array(); + if(is_array($val)) { + if(is_string($val[0])) { + $con = strtolower($val[0]); + if(in_array($con,array('neq','ne','gt','egt','gte','lt','lte','elt'))) { // 比较运算 + $k = '$'.$this->comparison[$con]; + $query[$key] = array($k=>$val[1]); + }elseif('like'== $con){ // 模糊查询 采用正则方式 + $query[$key] = new MongoRegex("/".$val[1]."/"); + }elseif('mod'==$con){ // mod 查询 + $query[$key] = array('$mod'=>$val[1]); + }elseif('regex'==$con){ // 正则查询 + $query[$key] = new MongoRegex($val[1]); + }elseif(in_array($con,array('in','nin','not in'))){ // IN NIN 运算 + $data = is_string($val[1])? explode(',',$val[1]):$val[1]; + $k = '$'.$this->comparison[$con]; + $query[$key] = array($k=>$data); + }elseif('all'==$con){ // 满足所有指定条件 + $data = is_string($val[1])? explode(',',$val[1]):$val[1]; + $query[$key] = array('$all'=>$data); + }elseif('between'==$con){ // BETWEEN运算 + $data = is_string($val[1])? explode(',',$val[1]):$val[1]; + $query[$key] = array('$gte'=>$data[0],'$lte'=>$data[1]); + }elseif('not between'==$con){ + $data = is_string($val[1])? explode(',',$val[1]):$val[1]; + $query[$key] = array('$lt'=>$data[0],'$gt'=>$data[1]); + }elseif('exp'==$con){ // 表达式查询 + $query['$where'] = new MongoCode($val[1]); + }elseif('exists'==$con){ // 字段是否存在 + $query[$key] =array('$exists'=>(bool)$val[1]); + }elseif('size'==$con){ // 限制属性大小 + $query[$key] =array('$size'=>intval($val[1])); + }elseif('type'==$con){ // 限制字段类型 1 浮点型 2 字符型 3 对象或者MongoDBRef 5 MongoBinData 7 MongoId 8 布尔型 9 MongoDate 10 NULL 15 MongoCode 16 32位整型 17 MongoTimestamp 18 MongoInt64 如果是数组的话判断元素的类型 + $query[$key] =array('$type'=>intval($val[1])); + }else{ + $query[$key] = $val; + } + return $query; + } + } + $query[$key] = $val; + return $query; + } +} \ No newline at end of file diff --git a/Think/Db/Driver/Mssql.php b/Think/Db/Driver/Mssql.php new file mode 100644 index 00000000..851e8a3d --- /dev/null +++ b/Think/Db/Driver/Mssql.php @@ -0,0 +1,334 @@ + +// +---------------------------------------------------------------------- +namespace Think\Db\Driver; +use Think\Db; +/** + * MSsql数据库驱动 要求sqlserver2005 + * @category Extend + * @package Extend + * @subpackage Driver.Db + * @author liu21st + */ +class Mssql extends Db{ + protected $selectSql = 'SELECT T1.* FROM (SELECT thinkphp.*, ROW_NUMBER() OVER (%ORDER%) AS ROW_NUMBER FROM (SELECT %DISTINCT% %FIELD% FROM %TABLE%%JOIN%%WHERE%%GROUP%%HAVING%) AS thinkphp) AS T1 %LIMIT%%COMMENT%'; + /** + * 架构函数 读取数据库配置信息 + * @access public + * @param array $config 数据库配置数组 + */ + public function __construct($config=''){ + if ( !function_exists('mssql_connect') ) { + throw_exception(L('_NOT_SUPPERT_').':mssql'); + } + if(!empty($config)) { + $this->config = $config; + if(empty($this->config['params'])) { + $this->config['params'] = array(); + } + } + } + + /** + * 连接数据库方法 + * @access public + */ + public function connect($config='',$linkNum=0) { + if ( !isset($this->linkID[$linkNum]) ) { + if(empty($config)) $config = $this->config; + $pconnect = !empty($config['params']['persist'])? $config['params']['persist']:$this->pconnect; + $conn = $pconnect ? 'mssql_pconnect':'mssql_connect'; + // 处理不带端口号的socket连接情况 + $sepr = IS_WIN ? ',' : ':'; + $host = $config['hostname'].($config['hostport']?$sepr."{$config['hostport']}":''); + $this->linkID[$linkNum] = $conn( $host, $config['username'], $config['password']); + if ( !$this->linkID[$linkNum] ) throw_exception("Couldn't connect to SQL Server on $host"); + if ( !empty($config['database']) && !mssql_select_db($config['database'], $this->linkID[$linkNum]) ) { + throw_exception("Couldn't open database '".$config['database']); + } + // 标记连接成功 + $this->connected = true; + //注销数据库安全信息 + if(1 != C('DB_DEPLOY_TYPE')) unset($this->config); + } + return $this->linkID[$linkNum]; + } + + /** + * 释放查询结果 + * @access public + */ + public function free() { + mssql_free_result($this->queryID); + $this->queryID = null; + } + + /** + * 执行查询 返回数据集 + * @access public + * @param string $str sql指令 + * @return mixed + */ + public function query($str) { + $this->initConnect(false); + if ( !$this->_linkID ) return false; + $this->queryStr = $str; + //释放前次的查询结果 + if ( $this->queryID ) $this->free(); + N('db_query',1); + // 记录开始执行时间 + G('queryStartTime'); + $this->queryID = mssql_query($str, $this->_linkID); + $this->debug(); + if ( false === $this->queryID ) { + $this->error(); + return false; + } else { + $this->numRows = mssql_num_rows($this->queryID); + return $this->getAll(); + } + } + + /** + * 执行语句 + * @access public + * @param string $str sql指令 + * @return integer + */ + public function execute($str) { + $this->initConnect(true); + if ( !$this->_linkID ) return false; + $this->queryStr = $str; + //释放前次的查询结果 + if ( $this->queryID ) $this->free(); + N('db_write',1); + // 记录开始执行时间 + G('queryStartTime'); + $result = mssql_query($str, $this->_linkID); + $this->debug(); + if ( false === $result ) { + $this->error(); + return false; + } else { + $this->numRows = mssql_rows_affected($this->_linkID); + $this->lastInsID = $this->mssql_insert_id(); + return $this->numRows; + } + } + + /** + * 用于获取最后插入的ID + * @access public + * @return integer + */ + public function mssql_insert_id() { + $query = "SELECT @@IDENTITY as last_insert_id"; + $result = mssql_query($query, $this->_linkID); + list($last_insert_id) = mssql_fetch_row($result); + mssql_free_result($result); + return $last_insert_id; + } + + /** + * 启动事务 + * @access public + * @return void + */ + public function startTrans() { + $this->initConnect(true); + if ( !$this->_linkID ) return false; + //数据rollback 支持 + if ($this->transTimes == 0) { + mssql_query('BEGIN TRAN', $this->_linkID); + } + $this->transTimes++; + return ; + } + + /** + * 用于非自动提交状态下面的查询提交 + * @access public + * @return boolen + */ + public function commit() { + if ($this->transTimes > 0) { + $result = mssql_query('COMMIT TRAN', $this->_linkID); + $this->transTimes = 0; + if(!$result){ + $this->error(); + return false; + } + } + return true; + } + + /** + * 事务回滚 + * @access public + * @return boolen + */ + public function rollback() { + if ($this->transTimes > 0) { + $result = mssql_query('ROLLBACK TRAN', $this->_linkID); + $this->transTimes = 0; + if(!$result){ + $this->error(); + return false; + } + } + return true; + } + + /** + * 获得所有的查询数据 + * @access private + * @return array + */ + private function getAll() { + //返回数据集 + $result = array(); + if($this->numRows >0) { + while($row = mssql_fetch_assoc($this->queryID)) + $result[] = $row; + } + return $result; + } + + /** + * 取得数据表的字段信息 + * @access public + * @return array + */ + public function getFields($tableName) { + $result = $this->query("SELECT column_name, data_type, column_default, is_nullable + FROM information_schema.tables AS t + JOIN information_schema.columns AS c + ON t.table_catalog = c.table_catalog + AND t.table_schema = c.table_schema + AND t.table_name = c.table_name + WHERE t.table_name = '$tableName'"); + $info = array(); + if($result) { + foreach ($result as $key => $val) { + $info[$val['column_name']] = array( + 'name' => $val['column_name'], + 'type' => $val['data_type'], + 'notnull' => (bool) ($val['is_nullable'] === ''), // not null is empty, null is yes + 'default' => $val['column_default'], + 'primary' => false, + 'autoinc' => false, + ); + } + } + return $info; + } + + /** + * 取得数据表的字段信息 + * @access public + * @return array + */ + public function getTables($dbName='') { + $result = $this->query("SELECT TABLE_NAME + FROM INFORMATION_SCHEMA.TABLES + WHERE TABLE_TYPE = 'BASE TABLE' + "); + $info = array(); + foreach ($result as $key => $val) { + $info[$key] = current($val); + } + return $info; + } + + /** + * order分析 + * @access protected + * @param mixed $order + * @return string + */ + protected function parseOrder($order) { + return !empty($order)? ' ORDER BY '.$order:' ORDER BY rand()'; + } + + /** + * limit + * @access public + * @return string + */ + public function parseLimit($limit) { + if(empty($limit)) return ''; + $limit = explode(',',$limit); + if(count($limit)>1) + $limitStr = '(T1.ROW_NUMBER BETWEEN '.$limit[0].' + 1 AND '.$limit[0].' + '.$limit[1].')'; + else + $limitStr = '(T1.ROW_NUMBER BETWEEN 1 AND '.$limit[0].")"; + return 'WHERE '.$limitStr; + } + + /** + * 更新记录 + * @access public + * @param mixed $data 数据 + * @param array $options 表达式 + * @return false | integer + */ + public function update($data,$options) { + $this->model = $options['model']; + $sql = 'UPDATE ' + .$this->parseTable($options['table']) + .$this->parseSet($data) + .$this->parseWhere(!empty($options['where'])?$options['where']:'') + .$this->parseLock(isset($options['lock'])?$options['lock']:false) + .$this->parseComment(!empty($options['comment'])?$options['comment']:''); + return $this->execute($sql); + } + + /** + * 删除记录 + * @access public + * @param array $options 表达式 + * @return false | integer + */ + public function delete($options=array()) { + $this->model = $options['model']; + $sql = 'DELETE FROM ' + .$this->parseTable($options['table']) + .$this->parseWhere(!empty($options['where'])?$options['where']:'') + .$this->parseLock(isset($options['lock'])?$options['lock']:false) + .$this->parseComment(!empty($options['comment'])?$options['comment']:''); + return $this->execute($sql); + } + + /** + * 关闭数据库 + * @access public + */ + public function close() { + if ($this->_linkID){ + mssql_close($this->_linkID); + } + $this->_linkID = null; + } + + /** + * 数据库错误信息 + * 并显示当前的SQL语句 + * @access public + * @return string + */ + public function error() { + $this->error = mssql_get_last_message(); + if('' != $this->queryStr){ + $this->error .= "\n [ SQL语句 ] : ".$this->queryStr; + } + ThinkLog::record($this->error,'ERR'); + return $this->error; + } +} \ No newline at end of file diff --git a/Think/Db/Driver/Mysql.php b/Think/Db/Driver/Mysql.php new file mode 100644 index 00000000..147c3c57 --- /dev/null +++ b/Think/Db/Driver/Mysql.php @@ -0,0 +1,350 @@ + +// +---------------------------------------------------------------------- + +namespace Think\Db\Driver; +use Think\Db; + +/** + * Mysql数据库驱动类 + * @category Think + * @package Think + * @subpackage Driver.Db + * @author liu21st + */ +class Mysql extends Db{ + + /** + * 架构函数 读取数据库配置信息 + * @access public + * @param array $config 数据库配置数组 + */ + public function __construct($config=''){ + if ( !extension_loaded('mysql') ) { + throw_exception(L('_NOT_SUPPERT_').':mysql'); + } + if(!empty($config)) { + $this->config = $config; + if(empty($this->config['params'])) { + $this->config['params'] = ''; + } + } + } + + /** + * 连接数据库方法 + * @access public + * @throws ThinkExecption + */ + public function connect($config='',$linkNum=0,$force=false) { + if ( !isset($this->linkID[$linkNum]) ) { + if(empty($config)) $config = $this->config; + // 处理不带端口号的socket连接情况 + $host = $config['hostname'].($config['hostport']?":{$config['hostport']}":''); + // 是否长连接 + $pconnect = !empty($config['params']['persist'])? $config['params']['persist']:$this->pconnect; + if($pconnect) { + $this->linkID[$linkNum] = mysql_pconnect( $host, $config['username'], $config['password'],131072); + }else{ + $this->linkID[$linkNum] = mysql_connect( $host, $config['username'], $config['password'],true,131072); + } + if ( !$this->linkID[$linkNum] || (!empty($config['database']) && !mysql_select_db($config['database'], $this->linkID[$linkNum])) ) { + throw_exception(mysql_error()); + } + $dbVersion = mysql_get_server_info($this->linkID[$linkNum]); + //使用UTF8存取数据库 + mysql_query("SET NAMES '".$this->conf['db_charset']."'", $this->linkID[$linkNum]); + //设置 sql_model + if($dbVersion >'5.0.1'){ + mysql_query("SET sql_mode=''",$this->linkID[$linkNum]); + } + // 标记连接成功 + $this->connected = true; + // 注销数据库连接配置信息 + if(1 != $this->conf['db_deploy_type']) unset($this->config); + } + return $this->linkID[$linkNum]; + } + + /** + * 释放查询结果 + * @access public + */ + public function free() { + mysql_free_result($this->queryID); + $this->queryID = null; + } + + /** + * 执行查询 返回数据集 + * @access public + * @param string $str sql指令 + * @return mixed + */ + public function query($str) { + if(0===stripos($str, 'call')){ // 存储过程查询支持 + $this->close(); + } + $this->initConnect(false); + if ( !$this->_linkID ) return false; + $this->queryStr = $str; + //释放前次的查询结果 + if ( $this->queryID ) { $this->free(); } + N('db_query',1); + // 记录开始执行时间 + \Think\Debug::remark('queryStartTime','time'); + $this->queryID = mysql_query($str, $this->_linkID); + $this->debug(); + if ( false === $this->queryID ) { + $this->error(); + return false; + } else { + $this->numRows = mysql_num_rows($this->queryID); + return $this->getAll(); + } + } + + /** + * 执行语句 + * @access public + * @param string $str sql指令 + * @return integer|false + */ + public function execute($str) { + $this->initConnect(true); + if ( !$this->_linkID ) return false; + $this->queryStr = $str; + //释放前次的查询结果 + if ( $this->queryID ) { $this->free(); } + N('db_write',1); + // 记录开始执行时间 + \Think\Debug::remark('queryStartTime','time'); + $result = mysql_query($str, $this->_linkID) ; + $this->debug(); + if ( false === $result) { + $this->error(); + return false; + } else { + $this->numRows = mysql_affected_rows($this->_linkID); + $this->lastInsID = mysql_insert_id($this->_linkID); + return $this->numRows; + } + } + + /** + * 启动事务 + * @access public + * @return void + */ + public function startTrans() { + $this->initConnect(true); + if ( !$this->_linkID ) return false; + //数据rollback 支持 + if ($this->transTimes == 0) { + mysql_query('START TRANSACTION', $this->_linkID); + } + $this->transTimes++; + return ; + } + + /** + * 用于非自动提交状态下面的查询提交 + * @access public + * @return boolen + */ + public function commit() { + if ($this->transTimes > 0) { + $result = mysql_query('COMMIT', $this->_linkID); + $this->transTimes = 0; + if(!$result){ + $this->error(); + return false; + } + } + return true; + } + + /** + * 事务回滚 + * @access public + * @return boolen + */ + public function rollback() { + if ($this->transTimes > 0) { + $result = mysql_query('ROLLBACK', $this->_linkID); + $this->transTimes = 0; + if(!$result){ + $this->error(); + return false; + } + } + return true; + } + + /** + * 获得所有的查询数据 + * @access private + * @return array + */ + private function getAll() { + //返回数据集 + $result = array(); + if($this->numRows >0) { + while($row = mysql_fetch_assoc($this->queryID)){ + $result[] = $row; + } + mysql_data_seek($this->queryID,0); + } + return $result; + } + + /** + * 取得数据表的字段信息 + * @access public + * @return array + */ + public function getFields($tableName) { + $result = $this->query('SHOW COLUMNS FROM '.$this->parseKey($tableName)); + $info = array(); + if($result) { + foreach ($result as $key => $val) { + $info[$val['Field']] = array( + 'name' => $val['Field'], + 'type' => $val['Type'], + 'notnull' => (bool) ($val['Null'] === ''), // not null is empty, null is yes + 'default' => $val['Default'], + 'primary' => (strtolower($val['Key']) == 'pri'), + 'autoinc' => (strtolower($val['Extra']) == 'auto_increment'), + ); + } + } + return $info; + } + + /** + * 取得数据库的表信息 + * @access public + * @return array + */ + public function getTables($dbName='') { + if(!empty($dbName)) { + $sql = 'SHOW TABLES FROM '.$dbName; + }else{ + $sql = 'SHOW TABLES '; + } + $result = $this->query($sql); + $info = array(); + foreach ($result as $key => $val) { + $info[$key] = current($val); + } + return $info; + } + + /** + * 替换记录 + * @access public + * @param mixed $data 数据 + * @param array $options 参数表达式 + * @return false | integer + */ + public function replace($data,$options=array()) { + foreach ($data as $key=>$val){ + $value = $this->parseValue($val); + if(is_scalar($value)) { // 过滤非标量数据 + $values[] = $value; + $fields[] = $this->parseKey($key); + } + } + $sql = 'REPLACE INTO '.$this->parseTable($options['table']).' ('.implode(',', $fields).') VALUES ('.implode(',', $values).')'; + return $this->execute($sql); + } + + /** + * 插入记录 + * @access public + * @param mixed $datas 数据 + * @param array $options 参数表达式 + * @param boolean $replace 是否replace + * @return false | integer + */ + public function insertAll($datas,$options=array(),$replace=false) { + if(!is_array($datas[0])) return false; + $fields = array_keys($datas[0]); + array_walk($fields, array($this, 'parseKey')); + $values = array(); + foreach ($datas as $data){ + $value = array(); + foreach ($data as $key=>$val){ + $val = $this->parseValue($val); + if(is_scalar($val)) { // 过滤非标量数据 + $value[] = $val; + } + } + $values[] = '('.implode(',', $value).')'; + } + $sql = ($replace?'REPLACE':'INSERT').' INTO '.$this->parseTable($options['table']).' ('.implode(',', $fields).') VALUES '.implode(',',$values); + return $this->execute($sql); + } + + /** + * 关闭数据库 + * @access public + * @return void + */ + public function close() { + if ($this->_linkID){ + mysql_close($this->_linkID); + } + $this->_linkID = null; + } + + /** + * 数据库错误信息 + * 并显示当前的SQL语句 + * @access public + * @return string + */ + public function error() { + $this->error = mysql_error($this->_linkID); + if('' != $this->queryStr){ + $this->error .= "\n [ SQL语句 ] : ".$this->queryStr; + } + \Think\Log::record($this->error,'ERR'); + return $this->error; + } + + /** + * SQL指令安全过滤 + * @access public + * @param string $str SQL字符串 + * @return string + */ + public function escapeString($str) { + if($this->_linkID) { + return mysql_real_escape_string($str,$this->_linkID); + }else{ + return mysql_escape_string($str); + } + } + + /** + * 字段和表名处理添加` + * @access protected + * @param string $key + * @return string + */ + protected function parseKey(&$key) { + $key = trim($key); + if(!preg_match('/[,\'\"\*\(\)`.\s]/',$key)) { + $key = '`'.$key.'`'; + } + return $key; + } +} \ No newline at end of file diff --git a/Think/Db/Driver/Mysqli.php b/Think/Db/Driver/Mysqli.php new file mode 100644 index 00000000..407b2dff --- /dev/null +++ b/Think/Db/Driver/Mysqli.php @@ -0,0 +1,345 @@ + +// +---------------------------------------------------------------------- +namespace Think\Db\Driver; +use Think\Db; +/** + * Mysqli数据库驱动类 + * @category Think + * @package Think + * @subpackage Driver.Db + * @author liu21st + */ +class Mysqli extends Db{ + + /** + * 架构函数 读取数据库配置信息 + * @access public + * @param array $config 数据库配置数组 + */ + public function __construct($config=''){ + if ( !extension_loaded('mysqli') ) { + throw_exception(L('_NOT_SUPPERT_').':mysqli'); + } + if(!empty($config)) { + $this->config = $config; + if(empty($this->config['params'])) { + $this->config['params'] = ''; + } + } + } + + /** + * 连接数据库方法 + * @access public + * @throws ThinkExecption + */ + public function connect($config='',$linkNum=0) { + if ( !isset($this->linkID[$linkNum]) ) { + if(empty($config)) $config = $this->config; + $this->linkID[$linkNum] = new mysqli($config['hostname'],$config['username'],$config['password'],$config['database'],$config['hostport']?intval($config['hostport']):3306); + if (mysqli_connect_errno()) throw_exception(mysqli_connect_error()); + $dbVersion = $this->linkID[$linkNum]->server_version; + + // 设置数据库编码 + $this->linkID[$linkNum]->query("SET NAMES '".C('DB_CHARSET')."'"); + //设置 sql_model + if($dbVersion >'5.0.1'){ + $this->linkID[$linkNum]->query("SET sql_mode=''"); + } + // 标记连接成功 + $this->connected = true; + //注销数据库安全信息 + if(1 != C('DB_DEPLOY_TYPE')) unset($this->config); + } + return $this->linkID[$linkNum]; + } + + /** + * 释放查询结果 + * @access public + */ + public function free() { + $this->queryID->free_result(); + $this->queryID = null; + } + + /** + * 执行查询 返回数据集 + * @access public + * @param string $str sql指令 + * @return mixed + */ + public function query($str) { + $this->initConnect(false); + if ( !$this->_linkID ) return false; + $this->queryStr = $str; + //释放前次的查询结果 + if ( $this->queryID ) $this->free(); + N('db_query',1); + // 记录开始执行时间 + G('queryStartTime'); + $this->queryID = $this->_linkID->query($str); + // 对存储过程改进 + if( $this->_linkID->more_results() ){ + while (($res = $this->_linkID->next_result()) != NULL) { + $res->free_result(); + } + } + $this->debug(); + if ( false === $this->queryID ) { + $this->error(); + return false; + } else { + $this->numRows = $this->queryID->num_rows; + $this->numCols = $this->queryID->field_count; + return $this->getAll(); + } + } + + /** + * 执行语句 + * @access public + * @param string $str sql指令 + * @return integer + */ + public function execute($str) { + $this->initConnect(true); + if ( !$this->_linkID ) return false; + $this->queryStr = $str; + //释放前次的查询结果 + if ( $this->queryID ) $this->free(); + N('db_write',1); + // 记录开始执行时间 + G('queryStartTime'); + $result = $this->_linkID->query($str); + $this->debug(); + if ( false === $result ) { + $this->error(); + return false; + } else { + $this->numRows = $this->_linkID->affected_rows; + $this->lastInsID = $this->_linkID->insert_id; + return $this->numRows; + } + } + + /** + * 启动事务 + * @access public + * @return void + */ + public function startTrans() { + $this->initConnect(true); + //数据rollback 支持 + if ($this->transTimes == 0) { + $this->_linkID->autocommit(false); + } + $this->transTimes++; + return ; + } + + /** + * 用于非自动提交状态下面的查询提交 + * @access public + * @return boolen + */ + public function commit() { + if ($this->transTimes > 0) { + $result = $this->_linkID->commit(); + $this->_linkID->autocommit( true); + $this->transTimes = 0; + if(!$result){ + $this->error(); + return false; + } + } + return true; + } + + /** + * 事务回滚 + * @access public + * @return boolen + */ + public function rollback() { + if ($this->transTimes > 0) { + $result = $this->_linkID->rollback(); + $this->transTimes = 0; + if(!$result){ + $this->error(); + return false; + } + } + return true; + } + + /** + * 获得所有的查询数据 + * @access private + * @param string $sql sql语句 + * @return array + */ + private function getAll() { + //返回数据集 + $result = array(); + if($this->numRows>0) { + //返回数据集 + for($i=0;$i<$this->numRows ;$i++ ){ + $result[$i] = $this->queryID->fetch_assoc(); + } + $this->queryID->data_seek(0); + } + return $result; + } + + /** + * 取得数据表的字段信息 + * @access public + * @return array + */ + public function getFields($tableName) { + $result = $this->query('SHOW COLUMNS FROM '.$this->parseKey($tableName)); + $info = array(); + if($result) { + foreach ($result as $key => $val) { + $info[$val['Field']] = array( + 'name' => $val['Field'], + 'type' => $val['Type'], + 'notnull' => (bool) ($val['Null'] === ''), // not null is empty, null is yes + 'default' => $val['Default'], + 'primary' => (strtolower($val['Key']) == 'pri'), + 'autoinc' => (strtolower($val['Extra']) == 'auto_increment'), + ); + } + } + return $info; + } + + /** + * 取得数据表的字段信息 + * @access public + * @return array + */ + public function getTables($dbName='') { + $sql = !empty($dbName)?'SHOW TABLES FROM '.$dbName:'SHOW TABLES '; + $result = $this->query($sql); + $info = array(); + if($result) { + foreach ($result as $key => $val) { + $info[$key] = current($val); + } + } + return $info; + } + + /** + * 替换记录 + * @access public + * @param mixed $data 数据 + * @param array $options 参数表达式 + * @return false | integer + */ + public function replace($data,$options=array()) { + foreach ($data as $key=>$val){ + $value = $this->parseValue($val); + if(is_scalar($value)) { // 过滤非标量数据 + $values[] = $value; + $fields[] = $this->parseKey($key); + } + } + $sql = 'REPLACE INTO '.$this->parseTable($options['table']).' ('.implode(',', $fields).') VALUES ('.implode(',', $values).')'; + return $this->execute($sql); + } + + /** + * 插入记录 + * @access public + * @param mixed $datas 数据 + * @param array $options 参数表达式 + * @param boolean $replace 是否replace + * @return false | integer + */ + public function insertAll($datas,$options=array(),$replace=false) { + if(!is_array($datas[0])) return false; + $fields = array_keys($datas[0]); + array_walk($fields, array($this, 'parseKey')); + $values = array(); + foreach ($datas as $data){ + $value = array(); + foreach ($data as $key=>$val){ + $val = $this->parseValue($val); + if(is_scalar($val)) { // 过滤非标量数据 + $value[] = $val; + } + } + $values[] = '('.implode(',', $value).')'; + } + $sql = ($replace?'REPLACE':'INSERT').' INTO '.$this->parseTable($options['table']).' ('.implode(',', $fields).') VALUES '.implode(',',$values); + return $this->execute($sql); + } + + /** + * 关闭数据库 + * @access public + * @return volid + */ + public function close() { + if ($this->_linkID){ + $this->_linkID->close(); + } + $this->_linkID = null; + } + + /** + * 数据库错误信息 + * 并显示当前的SQL语句 + * @static + * @access public + * @return string + */ + public function error() { + $this->error = $this->_linkID->error; + if('' != $this->queryStr){ + $this->error .= "\n [ SQL语句 ] : ".$this->queryStr; + } + ThinkLog::record($this->error,'ERR'); + return $this->error; + } + + /** + * SQL指令安全过滤 + * @static + * @access public + * @param string $str SQL指令 + * @return string + */ + public function escapeString($str) { + if($this->_linkID) { + return $this->_linkID->real_escape_string($str); + }else{ + return addslashes($str); + } + } + + /** + * 字段和表名处理添加` + * @access protected + * @param string $key + * @return string + */ + protected function parseKey(&$key) { + $key = trim($key); + if(!preg_match('/[,\'\"\*\(\)`.\s]/',$key)) { + $key = '`'.$key.'`'; + } + return $key; + } +} \ No newline at end of file diff --git a/Think/Db/Driver/Oracle.php b/Think/Db/Driver/Oracle.php new file mode 100644 index 00000000..1aee5e93 --- /dev/null +++ b/Think/Db/Driver/Oracle.php @@ -0,0 +1,337 @@ + +// +---------------------------------------------------------------------- +namespace Think\Db\Driver; +use Think\Db; +/** + * Oracle数据库驱动 + * @category Extend + * @package Extend + * @subpackage Driver.Db + * @author ZhangXuehun + */ +class Oracle extends Db{ + + private $mode = OCI_COMMIT_ON_SUCCESS; + private $table = ''; + protected $selectSql = 'SELECT * FROM (SELECT thinkphp.*, rownum AS numrow FROM (SELECT %DISTINCT% %FIELD% FROM %TABLE%%JOIN%%WHERE%%GROUP%%HAVING%%ORDER%) thinkphp ) %LIMIT%%COMMENT%'; + + /** + * 架构函数 读取数据库配置信息 + * @access public + * @param array $config 数据库配置数组 + */ + public function __construct($config=''){ + putenv("NLS_LANG=AMERICAN_AMERICA.UTF8"); + if ( !extension_loaded('oci8') ) { + throw_exception(L('_NOT_SUPPERT_').'oracle'); + } + if(!empty($config)) { + $this->config = $config; + if(empty($this->config['params'])) { + $this->config['params'] = array(); + } + } + } + + /** + * 连接数据库方法 + * @access public + */ + public function connect($config='',$linkNum=0) { + if ( !isset($this->linkID[$linkNum]) ) { + if(empty($config)) $config = $this->config; + $pconnect = !empty($config['params']['persist'])? $config['params']['persist']:$this->pconnect; + $conn = $pconnect ? 'oci_pconnect':'oci_new_connect'; + $this->linkID[$linkNum] = $conn($config['username'], $config['password'],$config['database']);//modify by wyfeng at 2008.12.19 + + if (!$this->linkID[$linkNum]){ + $this->error(false); + } + // 标记连接成功 + $this->connected = true; + //注销数据库安全信息 + if(1 != C('DB_DEPLOY_TYPE')) unset($this->config); + } + return $this->linkID[$linkNum]; + } + + /** + * 释放查询结果 + * @access public + */ + public function free() { + oci_free_statement($this->queryID); + $this->queryID = null; + } + + /** + * 执行查询 返回数据集 + * @access public + * @param string $str sql指令 + * @return mixed + */ + public function query($str) { + $this->initConnect(false); + if ( !$this->_linkID ) return false; + $this->queryStr = $str; + //更改事务模式 + $this->mode = OCI_COMMIT_ON_SUCCESS; + //释放前次的查询结果 + if ( $this->queryID ) $this->free(); + N('db_query',1); + // 记录开始执行时间 + G('queryStartTime'); + $this->queryID = oci_parse($this->_linkID,$str); + $this->debug(); + if (false === oci_execute($this->queryID, $this->mode)) { + $this->error(); + return false; + } else { + return $this->getAll(); + } + } + + /** + * 执行语句 + * @access public + * @param string $str sql指令 + * @return integer + */ + public function execute($str) { + $this->initConnect(true); + if ( !$this->_linkID ) return false; + $this->queryStr = $str; + // 判断新增操作 + $flag = false; + if(preg_match("/^\s*(INSERT\s+INTO)\s+(\w+)\s+/i", $this->queryStr, $match)) { + $this->table = C("DB_SEQUENCE_PREFIX") .str_ireplace(C("DB_PREFIX"), "", $match[2]); + $flag = (boolean)$this->query("SELECT * FROM user_sequences WHERE sequence_name='" . strtoupper($this->table) . "'"); + }//modify by wyfeng at 2009.08.28 + + //更改事务模式 + $this->mode = OCI_COMMIT_ON_SUCCESS; + //释放前次的查询结果 + if ( $this->queryID ) $this->free(); + N('db_write',1); + // 记录开始执行时间 + G('queryStartTime'); + $stmt = oci_parse($this->_linkID,$str); + $this->debug(); + if (false === oci_execute($stmt)) { + $this->error(); + return false; + } else { + $this->numRows = oci_num_rows($stmt); + $this->lastInsID = $flag?$this->insertLastId():0;//modify by wyfeng at 2009.08.28 + return $this->numRows; + } + } + + /** + * 启动事务 + * @access public + * @return void + */ + public function startTrans() { + $this->initConnect(true); + if ( !$this->_linkID ) return false; + //数据rollback 支持 + if ($this->transTimes == 0) { + $this->mode = OCI_DEFAULT; + } + $this->transTimes++; + return ; + } + + /** + * 用于非自动提交状态下面的查询提交 + * @access public + * @return boolen + */ + public function commit(){ + if ($this->transTimes > 0) { + $result = oci_commit($this->_linkID); + if(!$result){ + $this->error(); + return false; + } + $this->transTimes = 0; + } + return true; + } + + /** + * 事务回滚 + * @access public + * @return boolen + */ + public function rollback(){ + if ($this->transTimes > 0) { + $result = oci_rollback($this->_linkID); + if(!$result){ + $this->error(); + return false; + } + $this->transTimes = 0; + } + return true; + } + + /** + * 获得所有的查询数据 + * @access private + * @return array + */ + private function getAll() { + //返回数据集 + $result = array(); + $this->numRows = oci_fetch_all($this->queryID, $result, 0, -1, OCI_FETCHSTATEMENT_BY_ROW); + //add by wyfeng at 2008-12-23 强制将字段名转换为小写,以配合Model类函数如count等 + if(C("DB_CASE_LOWER")) { + foreach($result as $k=>$v) { + $result[$k] = array_change_key_case($result[$k], CASE_LOWER); + } + } + return $result; + } + + + /** + * 取得数据表的字段信息 + * @access public + */ + public function getFields($tableName) { + $result = $this->query("select a.column_name,data_type,decode(nullable,'Y',0,1) notnull,data_default,decode(a.column_name,b.column_name,1,0) pk " + ."from user_tab_columns a,(select column_name from user_constraints c,user_cons_columns col " + ."where c.constraint_name=col.constraint_name and c.constraint_type='P'and c.table_name='".strtoupper($tableName) + ."') b where table_name='".strtoupper($tableName)."' and a.column_name=b.column_name(+)"); + $info = array(); + if($result) { + foreach ($result as $key => $val) { + $info[strtolower($val['column_name'])] = array( + 'name' => strtolower($val['column_name']), + 'type' => strtolower($val['data_type']), + 'notnull' => $val['notnull'], + 'default' => $val['data_default'], + 'primary' => $val['pk'], + 'autoinc' => $val['pk'], + ); + } + } + return $info; + } + + /** + * 取得数据库的表信息(暂时实现取得用户表信息) + * @access public + */ + public function getTables($dbName='') { + $result = $this->query("select table_name from user_tables"); + $info = array(); + foreach ($result as $key => $val) { + $info[$key] = current($val); + } + return $info; + } + + /** + * 关闭数据库 + * @access public + */ + public function close() { + if($this->_linkID){ + oci_close($this->_linkID); + } + $this->_linkID = null; + } + + /** + * 数据库错误信息 + * 并显示当前的SQL语句 + * @access public + * @return string + */ + public function error($result = true) { + if($result){ + $error = oci_error($this->queryID); + }elseif(!$this->_linkID){ + $error = oci_error(); + }else{ + $error = oci_error($this->_linkID); + } + if('' != $this->queryStr){ + $error['message'] .= "\n [ SQL语句 ] : ".$this->queryStr; + } + $result? ThinkLog::record($error['message'],'ERR'):throw_exception($error['message'],'',$error['code']); + $this->error = $error['message']; + return $this->error; + } + + /** + * SQL指令安全过滤 + * @access public + * @param string $str SQL指令 + * @return string + */ + public function escapeString($str) { + return str_ireplace("'", "''", $str); + } + + /** + * 获取最后插入id ,仅适用于采用序列+触发器结合生成ID的方式 + * 在config.php中指定 + 'DB_TRIGGER_PREFIX' => 'tr_', + 'DB_SEQUENCE_PREFIX' => 'ts_', + * eg:表 tb_user + 相对tb_user的序列为: + -- Create sequence + create sequence TS_USER + minvalue 1 + maxvalue 999999999999999999999999999 + start with 1 + increment by 1 + nocache; + 相对tb_user,ts_user的触发器为: + create or replace trigger TR_USER + before insert on "TB_USER" + for each row + begin + select "TS_USER".nextval into :NEW.ID from dual; + end; + * @access public + * @return integer + */ + public function insertLastId() { + if(empty($this->table)) { + return 0; + } + $sequenceName = $this->table; + $vo = $this->query("SELECT {$sequenceName}.currval currval FROM dual"); + return $vo?$vo[0]["currval"]:0; + } + + /** + * limit + * @access public + * @return string + */ + public function parseLimit($limit) { + $limitStr = ''; + if(!empty($limit)) { + $limit = explode(',',$limit); + if(count($limit)>1) + $limitStr = "(numrow>" . $limit[0] . ") AND (numrow<=" . ($limit[0]+$limit[1]) . ")"; + else + $limitStr = "(numrow>0 AND numrow<=".$limit[0].")"; + } + return $limitStr?' WHERE '.$limitStr:''; + } +} \ No newline at end of file diff --git a/Think/Db/Driver/Pdo.php b/Think/Db/Driver/Pdo.php new file mode 100644 index 00000000..ddc8fea0 --- /dev/null +++ b/Think/Db/Driver/Pdo.php @@ -0,0 +1,447 @@ + +// +---------------------------------------------------------------------- +namespace Think\Db\Driver; +use Think\Db; +/** + * PDO数据库驱动 + * @category Extend + * @package Extend + * @subpackage Driver.Db + * @author liu21st + */ +class Pdo extends Db{ + + protected $PDOStatement = null; + private $table = ''; + + /** + * 架构函数 读取数据库配置信息 + * @access public + * @param array $config 数据库配置数组 + */ + public function __construct($config=''){ + if ( !class_exists('PDO') ) { + throw_exception(L('_NOT_SUPPERT_').':PDO'); + } + if(!empty($config)) { + $this->config = $config; + if(empty($this->config['params'])) { + $this->config['params'] = array(); + } + } + + } + + /** + * 连接数据库方法 + * @access public + */ + public function connect($config='',$linkNum=0) { + if ( !isset($this->linkID[$linkNum]) ) { + if(empty($config)) $config = $this->config; + if($this->pconnect) { + $config['params'][PDO::ATTR_PERSISTENT] = true; + } + //$config['params'][PDO::ATTR_CASE] = C("DB_CASE_LOWER")?PDO::CASE_LOWER:PDO::CASE_UPPER; + try{ + $this->linkID[$linkNum] = new PDO( $config['dsn'], $config['username'], $config['password'],$config['params']); + }catch (PDOException $e) { + throw_exception($e->getMessage()); + } + // 因为PDO的连接切换可能导致数据库类型不同,因此重新获取下当前的数据库类型 + $this->dbType = $this->_getDsnType($config['dsn']); + if(in_array($this->dbType,array('MSSQL','ORACLE','IBASE','OCI'))) { + // 由于PDO对于以上的数据库支持不够完美,所以屏蔽了 如果仍然希望使用PDO 可以注释下面一行代码 + throw_exception('由于目前PDO暂时不能完美支持'.$this->dbType.' 请使用官方的'.$this->dbType.'驱动'); + } + $this->linkID[$linkNum]->exec('SET NAMES '.C('DB_CHARSET')); + // 标记连接成功 + $this->connected = true; + // 注销数据库连接配置信息 + if(1 != C('DB_DEPLOY_TYPE')) unset($this->config); + } + return $this->linkID[$linkNum]; + } + + /** + * 释放查询结果 + * @access public + */ + public function free() { + $this->PDOStatement = null; + } + + /** + * 执行查询 返回数据集 + * @access public + * @param string $str sql指令 + * @return mixed + */ + public function query($str) { + $this->initConnect(false); + if ( !$this->_linkID ) return false; + $this->queryStr = $str; + //释放前次的查询结果 + if ( !empty($this->PDOStatement) ) $this->free(); + N('db_query',1); + // 记录开始执行时间 + G('queryStartTime'); + $this->PDOStatement = $this->_linkID->prepare($str); + if(false === $this->PDOStatement) + throw_exception($this->error()); + $result = $this->PDOStatement->execute(); + $this->debug(); + if ( false === $result ) { + $this->error(); + return false; + } else { + return $this->getAll(); + } + } + + /** + * 执行语句 + * @access public + * @param string $str sql指令 + * @return integer + */ + public function execute($str) { + $this->initConnect(true); + if ( !$this->_linkID ) return false; + $this->queryStr = $str; + $flag = false; + if($this->dbType == 'OCI') + { + if(preg_match("/^\s*(INSERT\s+INTO)\s+(\w+)\s+/i", $this->queryStr, $match)) { + $this->table = C("DB_SEQUENCE_PREFIX").str_ireplace(C("DB_PREFIX"), "", $match[2]); + $flag = (boolean)$this->query("SELECT * FROM user_sequences WHERE sequence_name='" . strtoupper($this->table) . "'"); + } + }//modify by wyfeng at 2009.08.28 + //释放前次的查询结果 + if ( !empty($this->PDOStatement) ) $this->free(); + N('db_write',1); + // 记录开始执行时间 + G('queryStartTime'); + $this->PDOStatement = $this->_linkID->prepare($str); + if(false === $this->PDOStatement) { + throw_exception($this->error()); + } + $result = $this->PDOStatement->execute(); + $this->debug(); + if ( false === $result) { + $this->error(); + return false; + } else { + $this->numRows = $this->PDOStatement->rowCount(); + if($flag || preg_match("/^\s*(INSERT\s+INTO|REPLACE\s+INTO)\s+/i", $str)) { + $this->lastInsID = $this->getLastInsertId(); + } + return $this->numRows; + } + } + + /** + * 启动事务 + * @access public + * @return void + */ + public function startTrans() { + $this->initConnect(true); + if ( !$this->_linkID ) return false; + //数据rollback 支持 + if ($this->transTimes == 0) { + $this->_linkID->beginTransaction(); + } + $this->transTimes++; + return ; + } + + /** + * 用于非自动提交状态下面的查询提交 + * @access public + * @return boolen + */ + public function commit() { + if ($this->transTimes > 0) { + $result = $this->_linkID->commit(); + $this->transTimes = 0; + if(!$result){ + $this->error(); + return false; + } + } + return true; + } + + /** + * 事务回滚 + * @access public + * @return boolen + */ + public function rollback() { + if ($this->transTimes > 0) { + $result = $this->_linkID->rollback(); + $this->transTimes = 0; + if(!$result){ + $this->error(); + return false; + } + } + return true; + } + + /** + * 获得所有的查询数据 + * @access private + * @return array + */ + private function getAll() { + //返回数据集 + $result = $this->PDOStatement->fetchAll(PDO::FETCH_ASSOC); + $this->numRows = count( $result ); + return $result; + } + + /** + * 取得数据表的字段信息 + * @access public + */ + public function getFields($tableName) { + $this->initConnect(true); + if(C('DB_DESCRIBE_TABLE_SQL')) { + // 定义特殊的字段查询SQL + $sql = str_replace('%table%',$tableName,C('DB_DESCRIBE_TABLE_SQL')); + }else{ + switch($this->dbType) { + case 'MSSQL': + case 'SQLSRV': + $sql = "SELECT column_name as 'Name', data_type as 'Type', column_default as 'Default', is_nullable as 'Null' + FROM information_schema.tables AS t + JOIN information_schema.columns AS c + ON t.table_catalog = c.table_catalog + AND t.table_schema = c.table_schema + AND t.table_name = c.table_name + WHERE t.table_name = '$tableName'"; + break; + case 'SQLITE': + $sql = 'PRAGMA table_info ('.$tableName.') '; + break; + case 'ORACLE': + case 'OCI': + $sql = "SELECT a.column_name \"Name\",data_type \"Type\",decode(nullable,'Y',0,1) notnull,data_default \"Default\",decode(a.column_name,b.column_name,1,0) \"pk\" " + ."FROM user_tab_columns a,(SELECT column_name FROM user_constraints c,user_cons_columns col " + ."WHERE c.constraint_name=col.constraint_name AND c.constraint_type='P' and c.table_name='".strtoupper($tableName) + ."') b where table_name='".strtoupper($tableName)."' and a.column_name=b.column_name(+)"; + break; + case 'PGSQL': + $sql = 'select fields_name as "Name",fields_type as "Type",fields_not_null as "Null",fields_key_name as "Key",fields_default as "Default",fields_default as "Extra" from table_msg('.$tableName.');'; + break; + case 'IBASE': + break; + case 'MYSQL': + default: + $sql = 'DESCRIBE '.$tableName;//备注: 驱动类不只针对mysql,不能加`` + } + } + $result = $this->query($sql); + $info = array(); + if($result) { + foreach ($result as $key => $val) { + $val = array_change_key_case($val); + $val['name'] = isset($val['name'])?$val['name']:""; + $val['type'] = isset($val['type'])?$val['type']:""; + $name = isset($val['field'])?$val['field']:$val['name']; + $info[$name] = array( + 'name' => $name , + 'type' => $val['type'], + 'notnull' => (bool)(((isset($val['null'])) && ($val['null'] === '')) || ((isset($val['notnull'])) && ($val['notnull'] === ''))), // not null is empty, null is yes + 'default' => isset($val['default'])? $val['default'] :(isset($val['dflt_value'])?$val['dflt_value']:""), + 'primary' => isset($val['dey'])?strtolower($val['dey']) == 'pri':(isset($val['pk'])?$val['pk']:false), + 'autoinc' => isset($val['extra'])?strtolower($val['extra']) == 'auto_increment':(isset($val['key'])?$val['key']:false), + ); + } + } + return $info; + } + + /** + * 取得数据库的表信息 + * @access public + */ + public function getTables($dbName='') { + if(C('DB_FETCH_TABLES_SQL')) { + // 定义特殊的表查询SQL + $sql = str_replace('%db%',$dnName,C('DB_FETCH_TABLES_SQL')); + }else{ + switch($this->dbType) { + case 'ORACLE': + case 'OCI': + $sql = 'SELECT table_name FROM user_tables'; + break; + case 'MSSQL': + case 'SQLSRV': + $sql = "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE'"; + break; + case 'PGSQL': + $sql = "select tablename as Tables_in_test from pg_tables where schemaname ='public'"; + break; + case 'IBASE': + // 暂时不支持 + throw_exception(L('_NOT_SUPPORT_DB_').':IBASE'); + break; + case 'SQLITE': + $sql = "SELECT name FROM sqlite_master WHERE type='table' " + . "UNION ALL SELECT name FROM sqlite_temp_master " + . "WHERE type='table' ORDER BY name"; + break; + case 'MYSQL': + default: + if(!empty($dbName)) { + $sql = 'SHOW TABLES FROM '.$dbName; + }else{ + $sql = 'SHOW TABLES '; + } + } + } + $result = $this->query($sql); + $info = array(); + foreach ($result as $key => $val) { + $info[$key] = current($val); + } + return $info; + } + + /** + * limit分析 + * @access protected + * @param mixed $lmit + * @return string + */ + protected function parseLimit($limit) { + $limitStr = ''; + if(!empty($limit)) { + switch($this->dbType){ + case 'PGSQL': + case 'SQLITE': + $limit = explode(',',$limit); + if(count($limit)>1) { + $limitStr .= ' LIMIT '.$limit[1].' OFFSET '.$limit[0].' '; + }else{ + $limitStr .= ' LIMIT '.$limit[0].' '; + } + break; + case 'MSSQL': + case 'SQLSRV': + break; + case 'IBASE': + // 暂时不支持 + break; + case 'ORACLE': + case 'OCI': + break; + case 'MYSQL': + default: + $limitStr .= ' LIMIT '.$limit.' '; + } + } + return $limitStr; + } + + /** + * 字段和表名处理 + * @access protected + * @param string $key + * @return string + */ + protected function parseKey(&$key) { + if($this->dbType=='MYSQL'){ + $key = trim($key); + if(!preg_match('/[,\'\"\*\(\)`.\s]/',$key)) { + $key = '`'.$key.'`'; + } + return $key; + }else{ + return parent::parseKey($key); + } + + } + + /** + * 关闭数据库 + * @access public + */ + public function close() { + $this->_linkID = null; + } + + /** + * 数据库错误信息 + * 并显示当前的SQL语句 + * @access public + * @return string + */ + public function error() { + if($this->PDOStatement) { + $error = $this->PDOStatement->errorInfo(); + $this->error = $error[2]; + }else{ + $this->error = ''; + } + if('' != $this->queryStr){ + $this->error .= "\n [ SQL语句 ] : ".$this->queryStr; + } + ThinkLog::record($this->error,'ERR'); + return $this->error; + } + + /** + * SQL指令安全过滤 + * @access public + * @param string $str SQL指令 + * @return string + */ + public function escapeString($str) { + switch($this->dbType) { + case 'PGSQL': + case 'MSSQL': + case 'SQLSRV': + case 'MYSQL': + return addslashes($str); + case 'IBASE': + case 'SQLITE': + case 'ORACLE': + case 'OCI': + return str_ireplace("'", "''", $str); + } + } + + /** + * 获取最后插入id + * @access public + * @return integer + */ + public function getLastInsertId() { + switch($this->dbType) { + case 'PGSQL': + case 'SQLITE': + case 'MSSQL': + case 'SQLSRV': + case 'IBASE': + case 'MYSQL': + return $this->_linkID->lastInsertId(); + case 'ORACLE': + case 'OCI': + $sequenceName = $this->table; + $vo = $this->query("SELECT {$sequenceName}.currval currval FROM dual"); + return $vo?$vo[0]["currval"]:0; + } + } +} \ No newline at end of file diff --git a/Think/Db/Driver/Pgsql.php b/Think/Db/Driver/Pgsql.php new file mode 100644 index 00000000..05ce7a4d --- /dev/null +++ b/Think/Db/Driver/Pgsql.php @@ -0,0 +1,300 @@ + +// +---------------------------------------------------------------------- +namespace Think\Db\Driver; +use Think\Db; +/** + * Pgsql数据库驱动 + * @category Extend + * @package Extend + * @subpackage Driver.Db + * @author liu21st + */ +class Pgsql extends Db{ + + /** + * 架构函数 读取数据库配置信息 + * @access public + * @param array $config 数据库配置数组 + */ + public function __construct($config='') { + if ( !extension_loaded('pgsql') ) { + throw_exception(L('_NOT_SUPPERT_').':pgsql'); + } + if(!empty($config)) { + $this->config = $config; + if(empty($this->config['params'])) { + $this->config['params'] = array(); + } + } + } + + /** + * 连接数据库方法 + * @access public + */ + public function connect($config='',$linkNum=0) { + if ( !isset($this->linkID[$linkNum]) ) { + if(empty($config)) $config = $this->config; + $pconnect = !empty($config['params']['persist'])? $config['params']['persist']:$this->pconnect; + $conn = $pconnect ? 'pg_pconnect':'pg_connect'; + $this->linkID[$linkNum] = $conn('host='.$config['hostname'].' port='.$config['hostport'].' dbname='.$config['database'].' user='.$config['username'].' password='.$config['password']); + if (0 !== pg_connection_status($this->linkID[$linkNum])){ + throw_exception($this->error(false)); + } + //设置编码 + pg_set_client_encoding($this->linkID[$linkNum], C('DB_CHARSET')); + //$pgInfo = pg_version($this->linkID[$linkNum]); + //$dbVersion = $pgInfo['server']; + // 标记连接成功 + $this->connected = true; + //注销数据库安全信息 + if(1 != C('DB_DEPLOY_TYPE')) unset($this->config); + } + return $this->linkID[$linkNum]; + } + + /** + * 释放查询结果 + * @access public + */ + public function free() { + pg_free_result($this->queryID); + $this->queryID = null; + } + + /** + * 执行查询 返回数据集 + * @access public + * @param string $str sql指令 + * @return mixed + */ + public function query($str) { + $this->initConnect(false); + if ( !$this->_linkID ) return false; + $this->queryStr = $str; + //释放前次的查询结果 + if ( $this->queryID ) $this->free(); + N('db_query',1); + // 记录开始执行时间 + G('queryStartTime'); + $this->queryID = pg_query($this->_linkID,$str); + $this->debug(); + if ( false === $this->queryID ) { + $this->error(); + return false; + } else { + $this->numRows = pg_num_rows($this->queryID); + return $this->getAll(); + } + } + + /** + * 执行语句 + * @access public + * @param string $str sql指令 + * @return integer + */ + public function execute($str) { + $this->initConnect(true); + if ( !$this->_linkID ) return false; + $this->queryStr = $str; + //释放前次的查询结果 + if ( $this->queryID ) $this->free(); + N('db_write',1); + // 记录开始执行时间 + G('queryStartTime'); + $result = pg_query($this->_linkID,$str); + $this->debug(); + if ( false === $result ) { + $this->error(); + return false; + } else { + $this->numRows = pg_affected_rows($result); + $this->lastInsID = $this->last_insert_id(); + return $this->numRows; + } + } + + /** + * 用于获取最后插入的ID + * @access public + * @return integer + */ + public function last_insert_id() { + $query = "SELECT LASTVAL() AS insert_id"; + $result = pg_query($this->_linkID,$query); + list($last_insert_id) = pg_fetch_array($result,null,PGSQL_ASSOC); + pg_free_result($result); + return $last_insert_id; + } + + /** + * 启动事务 + * @access public + * @return void + */ + public function startTrans() { + $this->initConnect(true); + if ( !$this->_linkID ) return false; + //数据rollback 支持 + if ($this->transTimes == 0) { + pg_exec($this->_linkID,'begin;'); + } + $this->transTimes++; + return ; + } + + /** + * 用于非自动提交状态下面的查询提交 + * @access public + * @return boolen + */ + public function commit() { + if ($this->transTimes > 0) { + $result = pg_exec($this->_linkID,'end;'); + if(!$result){ + $this->error(); + return false; + } + $this->transTimes = 0; + } + return true; + } + + /** + * 事务回滚 + * @access public + * @return boolen + */ + public function rollback() { + if ($this->transTimes > 0) { + $result = pg_exec($this->_linkID,'abort;'); + if(!$result){ + $this->error(); + return false; + } + $this->transTimes = 0; + } + return true; + } + + /** + * 获得所有的查询数据 + * @access private + * @return array + */ + private function getAll() { + //返回数据集 + $result = pg_fetch_all($this->queryID); + pg_result_seek($this->queryID,0); + return $result; + } + + /** + * 取得数据表的字段信息 + * @access public + */ + public function getFields($tableName) { + $result = $this->query("select a.attname as \"Field\", + t.typname as \"Type\", + a.attnotnull as \"Null\", + i.indisprimary as \"Key\", + d.adsrc as \"Default\" + from pg_class c + inner join pg_attribute a on a.attrelid = c.oid + inner join pg_type t on a.atttypid = t.oid + left join pg_attrdef d on a.attrelid=d.adrelid and d.adnum=a.attnum + left join pg_index i on a.attnum=ANY(i.indkey) and c.oid = i.indrelid + where (c.relname='{$tableName}' or c.relname = lower('{$tableName}')) AND a.attnum > 0 + order by a.attnum asc;"); + $info = array(); + if($result) { + foreach ($result as $key => $val) { + $info[$val['Field']] = array( + 'name' => $val['Field'], + 'type' => $val['Type'], + 'notnull' => (bool) ($val['Null'] == 't'?1:0), // 't' is 'not null' + 'default' => $val['Default'], + 'primary' => (strtolower($val['Key']) == 't'), + 'autoinc' => (strtolower($val['Default']) == "nextval('{$tableName}_id_seq'::regclass)"), + ); + } + } + return $info; + } + + /** + * 取得数据库的表信息 + * @access public + */ + public function getTables($dbName='') { + $result = $this->query("select tablename as Tables_in_test from pg_tables where schemaname ='public'"); + $info = array(); + foreach ($result as $key => $val) { + $info[$key] = current($val); + } + return $info; + } + + /** + * 关闭数据库 + * @access public + */ + public function close() { + if($this->_linkID){ + pg_close($this->_linkID); + } + $this->_linkID = null; + } + + /** + * 数据库错误信息 + * 并显示当前的SQL语句 + * @access public + * @return string + */ + public function error($result = true) { + $this->error = $result?pg_result_error($this->queryID): pg_last_error($this->_linkID); + if('' != $this->queryStr){ + $this->error .= "\n [ SQL语句 ] : ".$this->queryStr; + } + ThinkLog::record($this->error,'ERR'); + return $this->error; + } + + /** + * SQL指令安全过滤 + * @access public + * @param string $str SQL指令 + * @return string + */ + public function escapeString($str) { + return pg_escape_string($str); + } + + /** + * limit + * @access public + * @return string + */ + public function parseLimit($limit) { + $limitStr = ''; + if(!empty($limit)) { + $limit = explode(',',$limit); + if(count($limit)>1) { + $limitStr .= ' LIMIT '.$limit[1].' OFFSET '.$limit[0].' '; + }else{ + $limitStr .= ' LIMIT '.$limit[0].' '; + } + } + return $limitStr; + } +} \ No newline at end of file diff --git a/Think/Db/Driver/Sqlite.php b/Think/Db/Driver/Sqlite.php new file mode 100644 index 00000000..839ec22b --- /dev/null +++ b/Think/Db/Driver/Sqlite.php @@ -0,0 +1,284 @@ + +// +---------------------------------------------------------------------- +namespace Think\Db\Driver; +use Think\Db; +/** + * Sqlite数据库驱动 + * @category Extend + * @package Extend + * @subpackage Driver.Db + * @author liu21st + */ +class Sqlite extends Db { + + /** + * 架构函数 读取数据库配置信息 + * @access public + * @param array $config 数据库配置数组 + */ + public function __construct($config='') { + if ( !extension_loaded('sqlite') ) { + throw_exception(L('_NOT_SUPPERT_').':sqlite'); + } + if(!empty($config)) { + if(!isset($config['mode'])) { + $config['mode'] = 0666; + } + $this->config = $config; + if(empty($this->config['params'])) { + $this->config['params'] = array(); + } + } + } + + /** + * 连接数据库方法 + * @access public + */ + public function connect($config='',$linkNum=0) { + if ( !isset($this->linkID[$linkNum]) ) { + if(empty($config)) $config = $this->config; + $pconnect = !empty($config['params']['persist'])? $config['params']['persist']:$this->pconnect; + $conn = $pconnect ? 'sqlite_popen':'sqlite_open'; + $this->linkID[$linkNum] = $conn($config['database'],$config['mode']); + if ( !$this->linkID[$linkNum]) { + throw_exception(sqlite_error_string()); + } + // 标记连接成功 + $this->connected = true; + //注销数据库安全信息 + if(1 != C('DB_DEPLOY_TYPE')) unset($this->config); + } + return $this->linkID[$linkNum]; + } + + /** + * 释放查询结果 + * @access public + */ + public function free() { + $this->queryID = null; + } + + /** + * 执行查询 返回数据集 + * @access public + * @param string $str sql指令 + * @return mixed + */ + public function query($str) { + $this->initConnect(false); + if ( !$this->_linkID ) return false; + $this->queryStr = $str; + //释放前次的查询结果 + if ( $this->queryID ) $this->free(); + N('db_query',1); + // 记录开始执行时间 + G('queryStartTime'); + $this->queryID = sqlite_query($this->_linkID,$str); + $this->debug(); + if ( false === $this->queryID ) { + $this->error(); + return false; + } else { + $this->numRows = sqlite_num_rows($this->queryID); + return $this->getAll(); + } + } + + /** + * 执行语句 + * @access public + * @param string $str sql指令 + * @return integer + */ + public function execute($str) { + $this->initConnect(true); + if ( !$this->_linkID ) return false; + $this->queryStr = $str; + //释放前次的查询结果 + if ( $this->queryID ) $this->free(); + N('db_write',1); + // 记录开始执行时间 + G('queryStartTime'); + $result = sqlite_exec($this->_linkID,$str); + $this->debug(); + if ( false === $result ) { + $this->error(); + return false; + } else { + $this->numRows = sqlite_changes($this->_linkID); + $this->lastInsID = sqlite_last_insert_rowid($this->_linkID); + return $this->numRows; + } + } + + /** + * 启动事务 + * @access public + * @return void + */ + public function startTrans() { + $this->initConnect(true); + if ( !$this->_linkID ) return false; + //数据rollback 支持 + if ($this->transTimes == 0) { + sqlite_query($this->_linkID,'BEGIN TRANSACTION'); + } + $this->transTimes++; + return ; + } + + /** + * 用于非自动提交状态下面的查询提交 + * @access public + * @return boolen + */ + public function commit() { + if ($this->transTimes > 0) { + $result = sqlite_query($this->_linkID,'COMMIT TRANSACTION'); + if(!$result){ + $this->error(); + return false; + } + $this->transTimes = 0; + } + return true; + } + + /** + * 事务回滚 + * @access public + * @return boolen + */ + public function rollback() { + if ($this->transTimes > 0) { + $result = sqlite_query($this->_linkID,'ROLLBACK TRANSACTION'); + if(!$result){ + $this->error(); + return false; + } + $this->transTimes = 0; + } + return true; + } + + /** + * 获得所有的查询数据 + * @access private + * @return array + */ + private function getAll() { + //返回数据集 + $result = array(); + if($this->numRows >0) { + for($i=0;$i<$this->numRows ;$i++ ){ + // 返回数组集 + $result[$i] = sqlite_fetch_array($this->queryID,SQLITE_ASSOC); + } + sqlite_seek($this->queryID,0); + } + return $result; + } + + /** + * 取得数据表的字段信息 + * @access public + * @return array + */ + public function getFields($tableName) { + $result = $this->query('PRAGMA table_info( '.$tableName.' )'); + $info = array(); + if($result){ + foreach ($result as $key => $val) { + $info[$val['Field']] = array( + 'name' => $val['Field'], + 'type' => $val['Type'], + 'notnull' => (bool) ($val['Null'] === ''), // not null is empty, null is yes + 'default' => $val['Default'], + 'primary' => (strtolower($val['Key']) == 'pri'), + 'autoinc' => (strtolower($val['Extra']) == 'auto_increment'), + ); + } + } + return $info; + } + + /** + * 取得数据库的表信息 + * @access public + * @return array + */ + public function getTables($dbName='') { + $result = $this->query("SELECT name FROM sqlite_master WHERE type='table' " + . "UNION ALL SELECT name FROM sqlite_temp_master " + . "WHERE type='table' ORDER BY name"); + $info = array(); + foreach ($result as $key => $val) { + $info[$key] = current($val); + } + return $info; + } + + /** + * 关闭数据库 + * @access public + */ + public function close() { + if ($this->_linkID){ + sqlite_close($this->_linkID); + } + $this->_linkID = null; + } + + /** + * 数据库错误信息 + * 并显示当前的SQL语句 + * @access public + * @return string + */ + public function error() { + $this->error = sqlite_error_string(sqlite_last_error($this->_linkID)); + if('' != $this->queryStr){ + $this->error .= "\n [ SQL语句 ] : ".$this->queryStr; + } + ThinkLog::record($this->error,'ERR'); + return $this->error; + } + + /** + * SQL指令安全过滤 + * @access public + * @param string $str SQL指令 + * @return string + */ + public function escapeString($str) { + return sqlite_escape_string($str); + } + + /** + * limit + * @access public + * @return string + */ + public function parseLimit($limit) { + $limitStr = ''; + if(!empty($limit)) { + $limit = explode(',',$limit); + if(count($limit)>1) { + $limitStr .= ' LIMIT '.$limit[1].' OFFSET '.$limit[0].' '; + }else{ + $limitStr .= ' LIMIT '.$limit[0].' '; + } + } + return $limitStr; + } +} \ No newline at end of file diff --git a/Think/Db/Driver/Sqlsrv.php b/Think/Db/Driver/Sqlsrv.php new file mode 100644 index 00000000..296fd22f --- /dev/null +++ b/Think/Db/Driver/Sqlsrv.php @@ -0,0 +1,331 @@ + +// +---------------------------------------------------------------------- + +namespace Think\Db\Driver; +use Think\Db; +/** + * Sqlsrv数据库驱动 + * @category Extend + * @package Extend + * @subpackage Driver.Db + * @author liu21st + */ +class Sqlsrv extends Db{ + protected $selectSql = 'SELECT T1.* FROM (SELECT thinkphp.*, ROW_NUMBER() OVER (%ORDER%) AS ROW_NUMBER FROM (SELECT %DISTINCT% %FIELD% FROM %TABLE%%JOIN%%WHERE%%GROUP%%HAVING%) AS thinkphp) AS T1 %LIMIT%%COMMENT%'; + /** + * 架构函数 读取数据库配置信息 + * @access public + * @param array $config 数据库配置数组 + */ + public function __construct($config='') { + if ( !function_exists('sqlsrv_connect') ) { + throw_exception(L('_NOT_SUPPERT_').':sqlsrv'); + } + if(!empty($config)) { + $this->config = $config; + } + } + + /** + * 连接数据库方法 + * @access public + */ + public function connect($config='',$linkNum=0) { + if ( !isset($this->linkID[$linkNum]) ) { + if(empty($config)) $config = $this->config; + $host = $config['hostname'].($config['hostport']?",{$config['hostport']}":''); + $connectInfo = array('Database'=>$config['database'],'UID'=>$config['username'],'PWD'=>$config['password'],'CharacterSet' => C('DEFAULT_CHARSET')); + $this->linkID[$linkNum] = sqlsrv_connect( $host, $connectInfo); + if ( !$this->linkID[$linkNum] ) $this->error(false); + // 标记连接成功 + $this->connected = true; + //注销数据库安全信息 + if(1 != C('DB_DEPLOY_TYPE')) unset($this->config); + } + return $this->linkID[$linkNum]; + } + + /** + * 释放查询结果 + * @access public + */ + public function free() { + sqlsrv_free_stmt($this->queryID); + $this->queryID = null; + } + + /** + * 执行查询 返回数据集 + * @access public + * @param string $str sql指令 + * @return mixed + */ + public function query($str) { + $this->initConnect(false); + if ( !$this->_linkID ) return false; + $this->queryStr = $str; + //释放前次的查询结果 + if ( $this->queryID ) $this->free(); + N('db_query',1); + // 记录开始执行时间 + G('queryStartTime'); + $this->queryID = sqlsrv_query($this->_linkID,$str,array(), array( "Scrollable" => SQLSRV_CURSOR_KEYSET)); + $this->debug(); + if ( false === $this->queryID ) { + $this->error(); + return false; + } else { + $this->numRows = sqlsrv_num_rows($this->queryID); + return $this->getAll(); + } + } + + /** + * 执行语句 + * @access public + * @param string $str sql指令 + * @return integer + */ + public function execute($str) { + $this->initConnect(true); + if ( !$this->_linkID ) return false; + $this->queryStr = $str; + //释放前次的查询结果 + if ( $this->queryID ) $this->free(); + N('db_write',1); + // 记录开始执行时间 + G('queryStartTime'); + $this->queryID= sqlsrv_query($this->_linkID,$str); + $this->debug(); + if ( false === $this->queryID ) { + $this->error(); + return false; + } else { + $this->numRows = sqlsrv_rows_affected($this->queryID); + $this->lastInsID = $this->mssql_insert_id(); + return $this->numRows; + } + } + + /** + * 用于获取最后插入的ID + * @access public + * @return integer + */ + public function mssql_insert_id() { + $query = "SELECT @@IDENTITY as last_insert_id"; + $result = sqlsrv_query($this->_linkID,$query); + list($last_insert_id) = sqlsrv_fetch_array($result); + sqlsrv_free_stmt($result); + return $last_insert_id; + } + + /** + * 启动事务 + * @access public + * @return void + */ + public function startTrans() { + $this->initConnect(true); + if ( !$this->_linkID ) return false; + //数据rollback 支持 + if ($this->transTimes == 0) { + sqlsrv_begin_transaction($this->_linkID); + } + $this->transTimes++; + return ; + } + + /** + * 用于非自动提交状态下面的查询提交 + * @access public + * @return boolen + */ + public function commit() { + if ($this->transTimes > 0) { + $result = sqlsrv_commit($this->_linkID); + $this->transTimes = 0; + if(!$result){ + $this->error(); + return false; + } + } + return true; + } + + /** + * 事务回滚 + * @access public + * @return boolen + */ + public function rollback() { + if ($this->transTimes > 0) { + $result = sqlsrv_rollback($this->_linkID); + $this->transTimes = 0; + if(!$result){ + $this->error(); + return false; + } + } + return true; + } + + /** + * 获得所有的查询数据 + * @access private + * @return array + */ + private function getAll() { + //返回数据集 + $result = array(); + if($this->numRows >0) { + while($row = sqlsrv_fetch_array($this->queryID,SQLSRV_FETCH_ASSOC)) + $result[] = $row; + } + return $result; + } + + /** + * 取得数据表的字段信息 + * @access public + * @return array + */ + public function getFields($tableName) { + $result = $this->query("SELECT column_name, data_type, column_default, is_nullable + FROM information_schema.tables AS t + JOIN information_schema.columns AS c + ON t.table_catalog = c.table_catalog + AND t.table_schema = c.table_schema + AND t.table_name = c.table_name + WHERE t.table_name = '$tableName'"); + $info = array(); + if($result) { + foreach ($result as $key => $val) { + $info[$val['column_name']] = array( + 'name' => $val['column_name'], + 'type' => $val['data_type'], + 'notnull' => (bool) ($val['is_nullable'] === ''), // not null is empty, null is yes + 'default' => $val['column_default'], + 'primary' => false, + 'autoinc' => false, + ); + } + } + return $info; + } + + /** + * 取得数据表的字段信息 + * @access public + * @return array + */ + public function getTables($dbName='') { + $result = $this->query("SELECT TABLE_NAME + FROM INFORMATION_SCHEMA.TABLES + WHERE TABLE_TYPE = 'BASE TABLE' + "); + $info = array(); + foreach ($result as $key => $val) { + $info[$key] = current($val); + } + return $info; + } + + /** + * order分析 + * @access protected + * @param mixed $order + * @return string + */ + protected function parseOrder($order) { + return !empty($order)? ' ORDER BY '.$order:' ORDER BY rand()'; + } + + /** + * limit + * @access public + * @param mixed $limit + * @return string + */ + public function parseLimit($limit) { + if(empty($limit)) return ''; + $limit = explode(',',$limit); + if(count($limit)>1) + $limitStr = '(T1.ROW_NUMBER BETWEEN '.$limit[0].' + 1 AND '.$limit[0].' + '.$limit[1].')'; + else + $limitStr = '(T1.ROW_NUMBER BETWEEN 1 AND '.$limit[0].")"; + return 'WHERE '.$limitStr; + } + + /** + * 更新记录 + * @access public + * @param mixed $data 数据 + * @param array $options 表达式 + * @return false | integer + */ + public function update($data,$options) { + $this->model = $options['model']; + $sql = 'UPDATE ' + .$this->parseTable($options['table']) + .$this->parseSet($data) + .$this->parseWhere(!empty($options['where'])?$options['where']:'') + .$this->parseLock(isset($options['lock'])?$options['lock']:false) + .$this->parseComment(!empty($options['comment'])?$options['comment']:''); + return $this->execute($sql); + } + + /** + * 删除记录 + * @access public + * @param array $options 表达式 + * @return false | integer + */ + public function delete($options=array()) { + $this->model = $options['model']; + $sql = 'DELETE FROM ' + .$this->parseTable($options['table']) + .$this->parseWhere(!empty($options['where'])?$options['where']:'') + .$this->parseLock(isset($options['lock'])?$options['lock']:false) + .$this->parseComment(!empty($options['comment'])?$options['comment']:''); + return $this->execute($sql); + } + + /** + * 关闭数据库 + * @access public + */ + public function close() { + if ($this->_linkID){ + sqlsrv_close($this->_linkID); + } + $this->_linkID = null; + } + + /** + * 数据库错误信息 + * 并显示当前的SQL语句 + * @access public + * @return string + */ + public function error($result = true) { + $errors = sqlsrv_errors(); + $this->error = ''; + foreach( $errors as $error ) { + $this->error .= $error['message']; + } + if('' != $this->queryStr){ + $this->error .= "\n [ SQL语句 ] : ".$this->queryStr; + } + $result? ThinkLog::record($error['message'],'ERR'):throw_exception($this->error); + return $this->error; + } +} \ No newline at end of file diff --git a/Think/Debug.php b/Think/Debug.php new file mode 100644 index 00000000..7f2c3f3f --- /dev/null +++ b/Think/Debug.php @@ -0,0 +1,111 @@ + +// +---------------------------------------------------------------------- +// $Id$ +namespace Think; +class Debug { + + static protected $_info = []; + static protected $_mem = []; + + /** + * 记录时间(微秒)和内存使用情况 + * @param string $name 标记位置 + * @param mixed $value 标记值 留空则取当前 time 表示仅记录时间 否则同时记录时间和内存 + * @return mixed + */ + static public function remark($name,$value='') { + // 记录时间和内存使用 + self::$_info[$name] = is_float($value)?$value:microtime(TRUE); + if('time' != $value && function_exists('memory_get_usage')) { + self::$_mem['mem'][$name] = is_float($value)?$value:memory_get_usage(); + self::$_mem['peak'][$name] = function_exists('memory_get_peak_usage')?memory_get_peak_usage(): self::$_mem['mem'][$name]; + } + } + + /** + * 统计某个区间的时间(微秒)使用情况 + * @param string $start 开始标签 + * @param string $end 结束标签 + * @param integer|string $dec 小数位或者m + * @return mixed + */ + static public function getUseTime($start,$end,$dec=6) { + if(!isset(self::$_info[$end])) self::$_info[$end] = microtime(TRUE); + return number_format((self::$_info[$end]-self::$_info[$start]),$dec); + } + + /** + * 记录内存使用情况 + * @param string $start 开始标签 + * @param string $end 结束标签 + * @param integer|string $dec 小数位或者m + * @return mixed + */ + static public function getUseMem($start,$end,$dec=2) { + if(!isset(self::$_mem['mem'][$end])) + self::$_mem['mem'][$end] = memory_get_usage(); + $size = self::$_mem['mem'][$end]-self::$_mem['mem'][$start]; + $a = array('B', 'KB', 'MB', 'GB', 'TB'); + $pos = 0; + while ($size >= 1024) { + $size /= 1024; + $pos++; + } + return round($size,$dec)." ".$a[$pos]; + } + + /** + * 统计内存峰值情况 + * @param string $start 开始标签 + * @param string $end 结束标签 + * @param integer|string $dec 小数位或者m + * @return mixed + */ + static public function getMemPeak($start,$end,$dec=2) { + if(!isset(self::$_mem['peak'][$end])) self::$_mem['peak'][$end] = function_exists('memory_get_peak_usage')?memory_get_peak_usage():memory_get_usage(); + $size = self::$_mem['peak'][$end]-self::$_mem['peak'][$start]; + $a = array('B', 'KB', 'MB', 'GB', 'TB'); + $pos = 0; + while ($size >= 1024) { + $size /= 1024; + $pos++; + } + return round($size,$dec)." ".$a[$pos]; + } + + /** + * 浏览器友好的变量输出 + * @param mixed $var 变量 + * @param boolean $echo 是否输出 默认为True 如果为false 则返回输出字符串 + * @param string $label 标签 默认为空 + * @return void|string + */ + static public function dump($var, $echo=true, $label=null) { + $label = ($label === null) ? '' : rtrim($label) . ':'; + ob_start(); + var_dump($var); + $output = ob_get_clean(); + $output = preg_replace('/\]\=\>\n(\s+)/m', '] => ', $output); + if(IS_CLI) { + $output = PHP_EOL . $label. $output . PHP_EOL; + }else{ + if (!extension_loaded('xdebug')) { + $output = htmlspecialchars($output, ENT_QUOTES); + } + $output = '
' . $label . $output . '
'; + } + if ($echo) { + echo($output); + return null; + }else + return $output; + } +} \ No newline at end of file diff --git a/Think/Error.php b/Think/Error.php new file mode 100644 index 00000000..85a635e6 --- /dev/null +++ b/Think/Error.php @@ -0,0 +1,130 @@ + +// +---------------------------------------------------------------------- +// $Id$ +namespace Think; +class Error { + + /** + * 自定义异常处理 + * @access public + * @param mixed $e 异常对象 + */ + static public function appException($e) { + $error = array(); + $trace = $e->getTrace(); + $error['message'] = $e->getMessage(); + $error['file'] = $e->getFile(); + $error['class'] = isset($trace[0]['class'])?$trace[0]['class']:''; + $error['function'] = isset($trace[0]['function'])?$trace[0]['function']:''; + $error['line'] = $e->getLine(); + $error['trace'] = ''; + $time = date('y-m-d H:i:m'); + foreach ($trace as $t) { + $error['trace'] .= '[' . $time . '] ' . $t['file'] . ' (' . $t['line'] . ') '; + $error['trace'] .= $t['class'] . $t['type'] . $t['function'] . '('; + $error['trace'] .= implode(', ', $t['args']); + $error['trace'] .=')
'; + } + self::halt($error); + } + + /** + * 自定义错误处理 + * @access public + * @param int $errno 错误类型 + * @param string $errstr 错误信息 + * @param string $errfile 错误文件 + * @param int $errline 错误行数 + * @return void + */ + static public function appError($errno, $errstr, $errfile, $errline) { + switch ($errno) { + case E_ERROR: + case E_PARSE: + case E_CORE_ERROR: + case E_COMPILE_ERROR: + case E_USER_ERROR: + ob_end_clean(); + $errorStr = "$errstr ".$errfile." 第 $errline 行."; + Log::write("[$errno] ".$errorStr,'ERROR'); + self::halt($errorStr); + break; + case E_STRICT: + case E_USER_WARNING: + case E_USER_NOTICE: + default: + $errorStr = "[$errno] $errstr ".$errfile." 第 $errline 行."; + Log::record($errorStr,'NOTIC'); + break; + } + } + + /** + * 应用关闭处理 + * @param mixed $error 错误 + * @return void + */ + static public function appShutdown(){ + // 记录日志 + Log::save(); + if ($e = error_get_last()) { + self::appError($e['type'],$e['message'],$e['file'],$e['line']); + } + } + + /** + * 错误输出 + * @param mixed $error 错误 + * @return void + */ + static public function halt($error) { + if(IS_CLI) { + exit($error); + } + $e = array(); + if (Config::get('app_debug')) { + //调试模式下输出错误信息 + if (!is_array($error)) { + $trace = debug_backtrace(); + $e['message'] = $error; + $e['file'] = $trace[0]['file']; + $e['class'] = isset($trace[0]['class'])?$trace[0]['class']:''; + $e['function'] = isset($trace[0]['function'])?$trace[0]['function']:''; + $e['line'] = $trace[0]['line']; + $traceInfo = ''; + $time = date('y-m-d H:i:m'); + foreach ($trace as $t) { + $traceInfo .= '[' . $time . '] ' . $t['file'] . ' (' . $t['line'] . ') '; + $traceInfo .= $t['class'] . $t['type'] . $t['function'] . '('; + $traceInfo .= implode(', ', $t['args']); + $traceInfo .=')
'; + } + $e['trace'] = $traceInfo; + } else { + $e = $error; + } + } else { + //否则定向到错误页面 + $error_page = Config::get('error_page'); + if (!empty($error_page)) { + redirect($error_page); + } else { + if (Config::get('show_error_msg')) + $e['message'] = is_array($error) ? $error['message'] : $error; + else + $e['message'] = C('error_message'); + } + } + // 包含异常页面模板 + include Config::get('exception_tmpl'); + exit; + } +} \ No newline at end of file diff --git a/Think/Exception.php b/Think/Exception.php new file mode 100644 index 00000000..1f68d62b --- /dev/null +++ b/Think/Exception.php @@ -0,0 +1,80 @@ + +// +---------------------------------------------------------------------- +// $Id$ +namespace Think; +class Exception extends \Exception { + + /** + * 异常类型 + * @var string + * @access private + */ + private $type; + + // 是否存在多余调试信息 + private $extra; + + /** + * 架构函数 + * @access public + * @param string $message 异常信息 + */ + public function __construct($message,$code=0,$extra=false) { + parent::__construct($message,$code); + $this->type = get_class($this); + $this->extra = $extra; + } + + /** + * 异常输出 所有异常处理类均通过__toString方法输出错误 + * 每次异常都会写入系统日志 + * 该方法可以被子类重载 + * @access public + * @return array + */ + public function __toString() { + $trace = $this->getTrace(); + if($this->extra) + // 通过throw_exception抛出的异常要去掉多余的调试信息 + array_shift($trace); + $this->class = isset($trace[0]['class'])?$trace[0]['class']:''; + $this->function = isset($trace[0]['function'])?$trace[0]['function']:''; + $this->file = $trace[0]['file']; + $this->line = $trace[0]['line']; + $file = file($this->file); + $traceInfo = ''; + $time = date('y-m-d H:i:m'); + foreach($trace as $t) { + $traceInfo .= '['.$time.'] '.$t['file'].' ('.$t['line'].') '; + $traceInfo .= $t['class'].$t['type'].$t['function'].'('; + $traceInfo .= implode(', ', $t['args']); + $traceInfo .=")\n"; + } + $error['message'] = $this->message; + $error['type'] = $this->type; + $error['detail'] = ($this->line-2).': '.$file[$this->line-3]; + $error['detail'] .= ($this->line-1).': '.$file[$this->line-2]; + $error['detail'] .= ''.($this->line).': '.$file[$this->line-1].''; + $error['detail'] .= ($this->line+1).': '.$file[$this->line]; + $error['detail'] .= ($this->line+2).': '.$file[$this->line+1]; + $error['class'] = $this->class; + $error['function'] = $this->function; + $error['file'] = $this->file; + $error['line'] = $this->line; + $error['trace'] = $traceInfo; + + // 记录 Exception 日志 + if(C('LOG_EXCEPTION_RECORD')) { + ThinkLog::Write('('.$this->type.') '.$this->message); + } + return $error ; + } +} \ No newline at end of file diff --git a/Think/Image.php b/Think/Image.php new file mode 100644 index 00000000..6f682281 --- /dev/null +++ b/Think/Image.php @@ -0,0 +1,70 @@ + +// +---------------------------------------------------------------------- +// | ThinkImage.class.php 2013-03-05 +// +---------------------------------------------------------------------- + +namespace Think; + +/* 缩略图相关常量定义 */ +define('THINKIMAGE_THUMB_SCALING', 1); //常量,标识缩略图等比例缩放类型 +define('THINKIMAGE_THUMB_FILLED', 2); //常量,标识缩略图缩放后填充类型 +define('THINKIMAGE_THUMB_CENTER', 3); //常量,标识缩略图居中裁剪类型 +define('THINKIMAGE_THUMB_NORTHWEST', 4); //常量,标识缩略图左上角裁剪类型 +define('THINKIMAGE_THUMB_SOUTHEAST', 5); //常量,标识缩略图右下角裁剪类型 +define('THINKIMAGE_THUMB_FIXED', 6); //常量,标识缩略图固定尺寸缩放类型 + +/* 水印相关常量定义 */ +define('THINKIMAGE_WATER_NORTHWEST', 1); //常量,标识左上角水印 +define('THINKIMAGE_WATER_NORTH', 2); //常量,标识上居中水印 +define('THINKIMAGE_WATER_NORTHEAST', 3); //常量,标识右上角水印 +define('THINKIMAGE_WATER_WEST', 4); //常量,标识左居中水印 +define('THINKIMAGE_WATER_CENTER', 5); //常量,标识居中水印 +define('THINKIMAGE_WATER_EAST', 6); //常量,标识右居中水印 +define('THINKIMAGE_WATER_SOUTHWEST', 7); //常量,标识左下角水印 +define('THINKIMAGE_WATER_SOUTH', 8); //常量,标识下居中水印 +define('THINKIMAGE_WATER_SOUTHEAST', 9); //常量,标识右下角水印 + +/** + * 图片处理驱动类,可配置图片处理库 + * 目前支持GD库和imagick + * @author 麦当苗儿 + */ +class Image { + /** + * 图片资源 + * @var resource + */ + private static $im; + + /** + * 构造方法,用于实例化一个图片处理对象 + * @param string $type 要使用的类库,默认使用GD库 + */ + public function init($type = 'Gd', $imgname = null){ + /* 引入处理库,实例化图片处理对象 */ + $class = '\Think\Image\Driver\\'.ucwords($type); + if(class_exists($class)) { + self::$im = new $class($imgname); + return self::$im; + }else{ + throw new Exception('不支持的图片处理库类型'); + } + } + + // 调用驱动类的方法 + public static function __callStatic($method, $params){ + if(empty(self::$im)) { + self::init(); + } + return call_user_func_array(array(self::$im, $method), $params); + } + +} \ No newline at end of file diff --git a/Think/Image/Driver/Gd.php b/Think/Image/Driver/Gd.php new file mode 100644 index 00000000..8bee6062 --- /dev/null +++ b/Think/Image/Driver/Gd.php @@ -0,0 +1,550 @@ + +// +---------------------------------------------------------------------- +// | ImageGd.class.php 2013-03-05 +// +---------------------------------------------------------------------- + +namespace Think\Image\Driver; +class Gd{ + /** + * 图像资源对象 + * @var resource + */ + private $im; + private $gif; + /** + * 图像信息,包括width,height,type,mime,size + * @var array + */ + private $info; + + /** + * 构造方法,可用于打开一张图像 + * @param string $imgname 图像路径 + */ + public function __construct($imgname = null) { + $imgname && $this->open($imgname); + } + + /** + * 打开一张图像 + * @param string $imgname 图像路径 + */ + public function open($imgname){ + //检测图像文件 + if(!is_file($imgname)) throw new Exception('不存在的图像文件'); + + //获取图像信息 + $info = getimagesize($imgname); + + //检测图像合法性 + if(false === $info || (IMAGETYPE_GIF === $info[2] && empty($info['bits']))){ + throw new Exception('非法图像文件'); + } + + //设置图像信息 + $this->info = array( + 'width' => $info[0], + 'height' => $info[1], + 'type' => image_type_to_extension($info[2], false), + 'mime' => $info['mime'], + ); + + //销毁已存在的图像 + empty($this->im) || imagedestroy($this->im); + + //打开图像 + if('gif' == $this->info['type']){ + $class = '\Think\Image\Driver\Gif'; + $this->gif = new $class($imgname); + $this->im = imagecreatefromstring($this->gif->image()); + } else { + $fun = "imagecreatefrom{$this->info['type']}"; + $this->im = $fun($imgname); + } + return $this; + } + + /** + * 保存图像 + * @param string $imgname 图像保存名称 + * @param string $type 图像类型 + * @param boolean $interlace 是否对JPEG类型图像设置隔行扫描 + */ + public function save($imgname, $type = null, $interlace = true){ + if(empty($this->im)) throw new \Exception('没有可以被保存的图像资源'); + + //自动获取图像类型 + if(is_null($type)){ + $type = $this->info['type']; + } else { + $type = strtolower($type); + } + + //JPEG图像设置隔行扫描 + if('jpeg' == $type || 'jpg' == $type){ + $type = 'jpeg'; + imageinterlace($this->im, $interlace); + } + + //保存图像 + if('gif' == $type && !empty($this->gif)){ + $this->gif->save($imgname); + } else { + $fun = "image{$type}"; + $fun($this->im, $imgname); + } + return $this; + } + + /** + * 返回图像宽度 + * @return integer 图像宽度 + */ + public function width(){ + if(empty($this->im)) throw new Exception('没有指定图像资源'); + return $this->info['width']; + } + + /** + * 返回图像高度 + * @return integer 图像高度 + */ + public function height(){ + if(empty($this->im)) throw new Exception('没有指定图像资源'); + return $this->info['height']; + } + + /** + * 返回图像类型 + * @return string 图像类型 + */ + public function type(){ + if(empty($this->im)) throw new Exception('没有指定图像资源'); + return $this->info['type']; + } + + /** + * 返回图像MIME类型 + * @return string 图像MIME类型 + */ + public function mime(){ + if(empty($this->im)) throw new Exception('没有指定图像资源'); + return $this->info['mime']; + } + + /** + * 返回图像尺寸数组 0 - 图像宽度,1 - 图像高度 + * @return array 图像尺寸 + */ + public function size(){ + if(empty($this->im)) throw new Exception('没有指定图像资源'); + return array($this->info['width'], $this->info['height']); + } + + /** + * 裁剪图像 + * @param integer $w 裁剪区域宽度 + * @param integer $h 裁剪区域高度 + * @param integer $x 裁剪区域x坐标 + * @param integer $y 裁剪区域y坐标 + * @param integer $width 图像保存宽度 + * @param integer $height 图像保存高度 + */ + public function crop($w, $h, $x = 0, $y = 0, $width = null, $height = null){ + if(empty($this->im)) throw new Exception('没有可以被裁剪的图像资源'); + + //设置保存尺寸 + empty($width) && $width = $w; + empty($height) && $height = $h; + + do { + //创建新图像 + $img = imagecreatetruecolor($width, $height); + // 调整默认颜色 + $color = imagecolorallocate($img, 255, 255, 255); + imagefill($img, 0, 0, $color); + + //裁剪 + imagecopyresampled($img, $this->im, 0, 0, $x, $y, $width, $height, $w, $h); + imagedestroy($this->im); //销毁原图 + + //设置新图像 + $this->im = $img; + } while(!empty($this->gif) && $this->gifNext()); + + $this->info['width'] = $width; + $this->info['height'] = $height; + return $this; + } + + /** + * 生成缩略图 + * @param integer $width 缩略图最大宽度 + * @param integer $height 缩略图最大高度 + * @param integer $type 缩略图裁剪类型 + */ + public function thumb($width, $height, $type = THINKIMAGE_THUMB_SCALE){ + if(empty($this->im)) throw new Exception('没有可以被缩略的图像资源'); + + //原图宽度和高度 + $w = $this->info['width']; + $h = $this->info['height']; + + /* 计算缩略图生成的必要参数 */ + switch ($type) { + /* 等比例缩放 */ + case THINKIMAGE_THUMB_SCALING: + //原图尺寸小于缩略图尺寸则不进行缩略 + if($w < $width && $h < $height) return; + + //计算缩放比例 + $scale = min($width/$w, $height/$h); + + //设置缩略图的坐标及宽度和高度 + $x = $y = 0; + $width = $w * $scale; + $height = $h * $scale; + break; + + /* 居中裁剪 */ + case THINKIMAGE_THUMB_CENTER: + //计算缩放比例 + $scale = max($width/$w, $height/$h); + + //设置缩略图的坐标及宽度和高度 + $w = $width/$scale; + $h = $height/$scale; + $x = ($this->info['width'] - $w)/2; + $y = ($this->info['height'] - $h)/2; + break; + + /* 左上角裁剪 */ + case THINKIMAGE_THUMB_NORTHWEST: + //计算缩放比例 + $scale = max($width/$w, $height/$h); + + //设置缩略图的坐标及宽度和高度 + $x = $y = 0; + $w = $width/$scale; + $h = $height/$scale; + break; + + /* 右下角裁剪 */ + case THINKIMAGE_THUMB_SOUTHEAST: + //计算缩放比例 + $scale = max($width/$w, $height/$h); + + //设置缩略图的坐标及宽度和高度 + $w = $width/$scale; + $h = $height/$scale; + $x = $this->info['width'] - $w; + $y = $this->info['height'] - $h; + break; + + /* 填充 */ + case THINKIMAGE_THUMB_FILLED: + //计算缩放比例 + if($w < $width && $h < $height){ + $scale = 1; + } else { + $scale = min($width/$w, $height/$h); + } + + //设置缩略图的坐标及宽度和高度 + $neww = $w * $scale; + $newh = $h * $scale; + $posx = ($width - $w * $scale)/2; + $posy = ($height - $h * $scale)/2; + + do{ + //创建新图像 + $img = imagecreatetruecolor($width, $height); + // 调整默认颜色 + $color = imagecolorallocate($img, 255, 255, 255); + imagefill($img, 0, 0, $color); + + //裁剪 + imagecopyresampled($img, $this->im, $posx, $posy, $x, $y, $neww, $newh, $w, $h); + imagedestroy($this->im); //销毁原图 + $this->im = $img; + } while(!empty($this->gif) && $this->gifNext()); + + $this->info['width'] = $width; + $this->info['height'] = $height; + return; + + /* 固定 */ + case THINKIMAGE_THUMB_FIXED: + $x = $y = 0; + break; + + default: + throw new Exception('不支持的缩略图裁剪类型'); + } + + /* 裁剪图像 */ + $this->crop($w, $h, $x, $y, $width, $height); + return $this; + } + + /** + * 添加水印 + * @param string $source 水印图片路径 + * @param integer $locate 水印位置 + * @param integer $alpha 水印透明度 + */ + public function water($source, $locate = THINKIMAGE_WATER_SOUTHEAST){ + //资源检测 + if(empty($this->im)) throw new Exception('没有可以被添加水印的图像资源'); + if(!is_file($source)) throw new Exception('水印图像不存在'); + + //获取水印图像信息 + $info = getimagesize($source); + if(false === $info || (IMAGETYPE_GIF === $info[2] && empty($info['bits']))){ + throw new Exception('非法水印文件'); + } + + //创建水印图像资源 + $fun = 'imagecreatefrom' . image_type_to_extension($info[2], false); + $water = $fun($source); + + //设定水印图像的混色模式 + imagealphablending($water, true); + + /* 设定水印位置 */ + switch ($locate) { + /* 右下角水印 */ + case THINKIMAGE_WATER_SOUTHEAST: + $x = $this->info['width'] - $info[0]; + $y = $this->info['height'] - $info[1]; + break; + + /* 左下角水印 */ + case THINKIMAGE_WATER_SOUTHWEST: + $x = 0; + $y = $this->info['height'] - $info[1]; + break; + + /* 左上角水印 */ + case THINKIMAGE_WATER_NORTHWEST: + $x = $y = 0; + break; + + /* 右上角水印 */ + case THINKIMAGE_WATER_NORTHEAST: + $x = $this->info['width'] - $info[0]; + $y = 0; + break; + + /* 居中水印 */ + case THINKIMAGE_WATER_CENTER: + $x = ($this->info['width'] - $info[0])/2; + $y = ($this->info['height'] - $info[1])/2; + break; + + /* 下居中水印 */ + case THINKIMAGE_WATER_SOUTH: + $x = ($this->info['width'] - $info[0])/2; + $y = $this->info['height'] - $info[1]; + break; + + /* 右居中水印 */ + case THINKIMAGE_WATER_EAST: + $x = $this->info['width'] - $info[0]; + $y = ($this->info['height'] - $info[1])/2; + break; + + /* 上居中水印 */ + case THINKIMAGE_WATER_NORTH: + $x = ($this->info['width'] - $info[0])/2; + $y = 0; + break; + + /* 左居中水印 */ + case THINKIMAGE_WATER_WEST: + $x = 0; + $y = ($this->info['height'] - $info[1])/2; + break; + + default: + /* 自定义水印坐标 */ + if(is_array($locate)){ + list($x, $y) = $locate; + } else { + throw new Exception('不支持的水印位置类型'); + } + } + + do{ + //添加水印 + $src = imagecreatetruecolor($info[0], $info[1]); + // 调整默认颜色 + $color = imagecolorallocate($src, 255, 255, 255); + imagefill($src, 0, 0, $color); + + imagecopy($src, $this->im, 0, 0, $x, $y, $info[0], $info[1]); + imagecopy($src, $water, 0, 0, 0, 0, $info[0], $info[1]); + imagecopymerge($this->im, $src, $x, $y, 0, 0, $info[0], $info[1], 100); + + //销毁零时图片资源 + imagedestroy($src); + } while(!empty($this->gif) && $this->gifNext()); + + //销毁水印资源 + imagedestroy($water); + return $this; + } + + /** + * 图像添加文字 + * @param string $text 添加的文字 + * @param string $font 字体路径 + * @param integer $size 字号 + * @param string $color 文字颜色 + * @param integer $locate 文字写入位置 + * @param integer $offset 文字相对当前位置的偏移量 + * @param integer $angle 文字倾斜角度 + */ + public function text($text, $font, $size, $color = '#00000000', + $locate = THINKIMAGE_WATER_SOUTHEAST, $offset = 0, $angle = 0){ + //资源检测 + if(empty($this->im)) throw new Exception('没有可以被写入文字的图像资源'); + if(!is_file($font)) throw new Exception("不存在的字体文件:{$font}"); + + //获取文字信息 + $info = imagettfbbox($size, $angle, $font, $text); + $minx = min($info[0], $info[2], $info[4], $info[6]); + $maxx = max($info[0], $info[2], $info[4], $info[6]); + $miny = min($info[1], $info[3], $info[5], $info[7]); + $maxy = max($info[1], $info[3], $info[5], $info[7]); + + /* 计算文字初始坐标和尺寸 */ + $x = $minx; + $y = abs($miny); + $w = $maxx - $minx; + $h = $maxy - $miny; + + /* 设定文字位置 */ + switch ($locate) { + /* 右下角文字 */ + case THINKIMAGE_WATER_SOUTHEAST: + $x += $this->info['width'] - $w; + $y += $this->info['height'] - $h; + break; + + /* 左下角文字 */ + case THINKIMAGE_WATER_SOUTHWEST: + $y += $this->info['height'] - $h; + break; + + /* 左上角文字 */ + case THINKIMAGE_WATER_NORTHWEST: + // 起始坐标即为左上角坐标,无需调整 + break; + + /* 右上角文字 */ + case THINKIMAGE_WATER_NORTHEAST: + $x += $this->info['width'] - $w; + break; + + /* 居中文字 */ + case THINKIMAGE_WATER_CENTER: + $x += ($this->info['width'] - $w)/2; + $y += ($this->info['height'] - $h)/2; + break; + + /* 下居中文字 */ + case THINKIMAGE_WATER_SOUTH: + $x += ($this->info['width'] - $w)/2; + $y += $this->info['height'] - $h; + break; + + /* 右居中文字 */ + case THINKIMAGE_WATER_EAST: + $x += $this->info['width'] - $w; + $y += ($this->info['height'] - $h)/2; + break; + + /* 上居中文字 */ + case THINKIMAGE_WATER_NORTH: + $x += ($this->info['width'] - $w)/2; + break; + + /* 左居中文字 */ + case THINKIMAGE_WATER_WEST: + $y += ($this->info['height'] - $h)/2; + break; + + default: + /* 自定义文字坐标 */ + if(is_array($locate)){ + list($posx, $posy) = $locate; + $x += $posx; + $y += $posy; + } else { + throw new Exception('不支持的文字位置类型'); + } + } + + /* 设置偏移量 */ + if(is_array($offset)){ + $offset = array_map('intval', $offset); + list($ox, $oy) = $offset; + } else{ + $offset = intval($offset); + $ox = $oy = $offset; + } + + /* 设置颜色 */ + if(is_string($color) && 0 === strpos($color, '#')){ + $color = str_split(substr($color, 1), 2); + $color = array_map('hexdec', $color); + if(empty($color[3]) || $color[3] > 127){ + $color[3] = 0; + } + } elseif (!is_array($color)) { + throw new Exception('错误的颜色值'); + } + + do{ + /* 写入文字 */ + $col = imagecolorallocatealpha($this->im, $color[0], $color[1], $color[2], $color[3]); + imagettftext($this->im, $size, $angle, $x + $ox, $y + $oy, $col, $font, $text); + } while(!empty($this->gif) && $this->gifNext()); + return $this; + } + + /* 切换到GIF的下一帧并保存当前帧,内部使用 */ + private function gifNext(){ + ob_start(); + ob_implicit_flush(0); + imagegif($this->im); + $img = ob_get_clean(); + + $this->gif->image($img); + $next = $this->gif->nextImage(); + + if($next){ + $this->im = imagecreatefromstring($next); + return $next; + } else { + $this->im = imagecreatefromstring($this->gif->image()); + return false; + } + } + + /** + * 析构方法,用于销毁图像资源 + */ + public function __destruct() { + empty($this->im) || imagedestroy($this->im); + } +} \ No newline at end of file diff --git a/Think/Image/Driver/Gif.php b/Think/Image/Driver/Gif.php new file mode 100644 index 00000000..4d5d42a5 --- /dev/null +++ b/Think/Image/Driver/Gif.php @@ -0,0 +1,570 @@ + +// +---------------------------------------------------------------------- +// | GIF.class.php 2013-03-09 +// +---------------------------------------------------------------------- +namespace Think\Image\Driver; +class Gif{ + /** + * GIF帧列表 + * @var array + */ + private $frames = array(); + + /** + * 每帧等待时间列表 + * @var array + */ + private $delays = array(); + + /** + * 构造方法,用于解码GIF图片 + * @param string $src GIF图片数据 + * @param string $mod 图片数据类型 + */ + public function __construct($src = null, $mod = 'url') { + if(!is_null($src)){ + if('url' == $mod && is_file($src)){ + $src = file_get_contents($src); + } + + /* 解码GIF图片 */ + try{ + $de = new GIFDecoder($src); + $this->frames = $de->GIFGetFrames(); + $this->delays = $de->GIFGetDelays(); + } catch(Exception $e){ + throw new Exception("解码GIF图片出错"); + } + } + } + + /** + * 设置或获取当前帧的数据 + * @param string $stream 二进制数据流 + * @return boolean 获取到的数据 + */ + public function image($stream = null){ + if(is_null($stream)){ + $current = current($this->frames); + return false === $current ? reset($this->frames) : $current; + } else { + $this->frames[key($this->frames)] = $stream; + } + } + + /** + * 将当前帧移动到下一帧 + * @return string 当前帧数据 + */ + public function nextImage(){ + return next($this->frames); + } + + /** + * 编码并保存当前GIF图片 + * @param string $gifname 图片名称 + */ + public function save($gifname){ + $gif = new GIFEncoder($this->frames, $this->delays, 0, 2, 0, 0, 0, 'bin'); + file_put_contents($gifname, $gif->GetAnimation()); + } + +} + + +/* +::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:: +:: GIFEncoder Version 2.0 by László Zsidi, http://gifs.hu +:: +:: This class is a rewritten 'GifMerge.class.php' version. +:: +:: Modification: +:: - Simplified and easy code, +:: - Ultra fast encoding, +:: - Built-in errors, +:: - Stable working +:: +:: +:: Updated at 2007. 02. 13. '00.05.AM' +:: +:: +:: +:: Try on-line GIFBuilder Form demo based on GIFEncoder. +:: +:: http://gifs.hu/phpclasses/demos/GifBuilder/ +:: +::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +*/ + +Class GIFEncoder { + var $GIF = "GIF89a"; /* GIF header 6 bytes */ + var $VER = "GIFEncoder V2.05"; /* Encoder version */ + + var $BUF = Array ( ); + var $LOP = 0; + var $DIS = 2; + var $COL = -1; + var $IMG = -1; + + var $ERR = Array ( + 'ERR00'=>"Does not supported function for only one image!", + 'ERR01'=>"Source is not a GIF image!", + 'ERR02'=>"Unintelligible flag ", + 'ERR03'=>"Does not make animation from animated GIF source", + ); + + /* + ::::::::::::::::::::::::::::::::::::::::::::::::::: + :: + :: GIFEncoder... + :: + */ + function GIFEncoder ( + $GIF_src, $GIF_dly, $GIF_lop, $GIF_dis, + $GIF_red, $GIF_grn, $GIF_blu, $GIF_mod + ) { + if ( ! is_array ( $GIF_src ) && ! is_array ( $GIF_tim ) ) { + printf ( "%s: %s", $this->VER, $this->ERR [ 'ERR00' ] ); + exit ( 0 ); + } + $this->LOP = ( $GIF_lop > -1 ) ? $GIF_lop : 0; + $this->DIS = ( $GIF_dis > -1 ) ? ( ( $GIF_dis < 3 ) ? $GIF_dis : 3 ) : 2; + $this->COL = ( $GIF_red > -1 && $GIF_grn > -1 && $GIF_blu > -1 ) ? + ( $GIF_red | ( $GIF_grn << 8 ) | ( $GIF_blu << 16 ) ) : -1; + + for ( $i = 0; $i < count ( $GIF_src ); $i++ ) { + if ( strToLower ( $GIF_mod ) == "url" ) { + $this->BUF [ ] = fread ( fopen ( $GIF_src [ $i ], "rb" ), filesize ( $GIF_src [ $i ] ) ); + } + else if ( strToLower ( $GIF_mod ) == "bin" ) { + $this->BUF [ ] = $GIF_src [ $i ]; + } + else { + printf ( "%s: %s ( %s )!", $this->VER, $this->ERR [ 'ERR02' ], $GIF_mod ); + exit ( 0 ); + } + if ( substr ( $this->BUF [ $i ], 0, 6 ) != "GIF87a" && substr ( $this->BUF [ $i ], 0, 6 ) != "GIF89a" ) { + printf ( "%s: %d %s", $this->VER, $i, $this->ERR [ 'ERR01' ] ); + exit ( 0 ); + } + for ( $j = ( 13 + 3 * ( 2 << ( ord ( $this->BUF [ $i ] { 10 } ) & 0x07 ) ) ), $k = TRUE; $k; $j++ ) { + switch ( $this->BUF [ $i ] { $j } ) { + case "!": + if ( ( substr ( $this->BUF [ $i ], ( $j + 3 ), 8 ) ) == "NETSCAPE" ) { + printf ( "%s: %s ( %s source )!", $this->VER, $this->ERR [ 'ERR03' ], ( $i + 1 ) ); + exit ( 0 ); + } + break; + case ";": + $k = FALSE; + break; + } + } + } + GIFEncoder::GIFAddHeader ( ); + for ( $i = 0; $i < count ( $this->BUF ); $i++ ) { + GIFEncoder::GIFAddFrames ( $i, $GIF_dly [ $i ] ); + } + GIFEncoder::GIFAddFooter ( ); + } + /* + ::::::::::::::::::::::::::::::::::::::::::::::::::: + :: + :: GIFAddHeader... + :: + */ + function GIFAddHeader ( ) { + $cmap = 0; + + if ( ord ( $this->BUF [ 0 ] { 10 } ) & 0x80 ) { + $cmap = 3 * ( 2 << ( ord ( $this->BUF [ 0 ] { 10 } ) & 0x07 ) ); + + $this->GIF .= substr ( $this->BUF [ 0 ], 6, 7 ); + $this->GIF .= substr ( $this->BUF [ 0 ], 13, $cmap ); + $this->GIF .= "!\377\13NETSCAPE2.0\3\1" . GIFEncoder::GIFWord ( $this->LOP ) . "\0"; + } + } + /* + ::::::::::::::::::::::::::::::::::::::::::::::::::: + :: + :: GIFAddFrames... + :: + */ + function GIFAddFrames ( $i, $d ) { + + $Locals_str = 13 + 3 * ( 2 << ( ord ( $this->BUF [ $i ] { 10 } ) & 0x07 ) ); + + $Locals_end = strlen ( $this->BUF [ $i ] ) - $Locals_str - 1; + $Locals_tmp = substr ( $this->BUF [ $i ], $Locals_str, $Locals_end ); + + $Global_len = 2 << ( ord ( $this->BUF [ 0 ] { 10 } ) & 0x07 ); + $Locals_len = 2 << ( ord ( $this->BUF [ $i ] { 10 } ) & 0x07 ); + + $Global_rgb = substr ( $this->BUF [ 0 ], 13, + 3 * ( 2 << ( ord ( $this->BUF [ 0 ] { 10 } ) & 0x07 ) ) ); + $Locals_rgb = substr ( $this->BUF [ $i ], 13, + 3 * ( 2 << ( ord ( $this->BUF [ $i ] { 10 } ) & 0x07 ) ) ); + + $Locals_ext = "!\xF9\x04" . chr ( ( $this->DIS << 2 ) + 0 ) . + chr ( ( $d >> 0 ) & 0xFF ) . chr ( ( $d >> 8 ) & 0xFF ) . "\x0\x0"; + + if ( $this->COL > -1 && ord ( $this->BUF [ $i ] { 10 } ) & 0x80 ) { + for ( $j = 0; $j < ( 2 << ( ord ( $this->BUF [ $i ] { 10 } ) & 0x07 ) ); $j++ ) { + if ( + ord ( $Locals_rgb { 3 * $j + 0 } ) == ( ( $this->COL >> 16 ) & 0xFF ) && + ord ( $Locals_rgb { 3 * $j + 1 } ) == ( ( $this->COL >> 8 ) & 0xFF ) && + ord ( $Locals_rgb { 3 * $j + 2 } ) == ( ( $this->COL >> 0 ) & 0xFF ) + ) { + $Locals_ext = "!\xF9\x04" . chr ( ( $this->DIS << 2 ) + 1 ) . + chr ( ( $d >> 0 ) & 0xFF ) . chr ( ( $d >> 8 ) & 0xFF ) . chr ( $j ) . "\x0"; + break; + } + } + } + switch ( $Locals_tmp { 0 } ) { + case "!": + $Locals_img = substr ( $Locals_tmp, 8, 10 ); + $Locals_tmp = substr ( $Locals_tmp, 18, strlen ( $Locals_tmp ) - 18 ); + break; + case ",": + $Locals_img = substr ( $Locals_tmp, 0, 10 ); + $Locals_tmp = substr ( $Locals_tmp, 10, strlen ( $Locals_tmp ) - 10 ); + break; + } + if ( ord ( $this->BUF [ $i ] { 10 } ) & 0x80 && $this->IMG > -1 ) { + if ( $Global_len == $Locals_len ) { + if ( GIFEncoder::GIFBlockCompare ( $Global_rgb, $Locals_rgb, $Global_len ) ) { + $this->GIF .= ( $Locals_ext . $Locals_img . $Locals_tmp ); + } + else { + $byte = ord ( $Locals_img { 9 } ); + $byte |= 0x80; + $byte &= 0xF8; + $byte |= ( ord ( $this->BUF [ 0 ] { 10 } ) & 0x07 ); + $Locals_img { 9 } = chr ( $byte ); + $this->GIF .= ( $Locals_ext . $Locals_img . $Locals_rgb . $Locals_tmp ); + } + } + else { + $byte = ord ( $Locals_img { 9 } ); + $byte |= 0x80; + $byte &= 0xF8; + $byte |= ( ord ( $this->BUF [ $i ] { 10 } ) & 0x07 ); + $Locals_img { 9 } = chr ( $byte ); + $this->GIF .= ( $Locals_ext . $Locals_img . $Locals_rgb . $Locals_tmp ); + } + } + else { + $this->GIF .= ( $Locals_ext . $Locals_img . $Locals_tmp ); + } + $this->IMG = 1; + } + /* + ::::::::::::::::::::::::::::::::::::::::::::::::::: + :: + :: GIFAddFooter... + :: + */ + function GIFAddFooter ( ) { + $this->GIF .= ";"; + } + /* + ::::::::::::::::::::::::::::::::::::::::::::::::::: + :: + :: GIFBlockCompare... + :: + */ + function GIFBlockCompare ( $GlobalBlock, $LocalBlock, $Len ) { + + for ( $i = 0; $i < $Len; $i++ ) { + if ( + $GlobalBlock { 3 * $i + 0 } != $LocalBlock { 3 * $i + 0 } || + $GlobalBlock { 3 * $i + 1 } != $LocalBlock { 3 * $i + 1 } || + $GlobalBlock { 3 * $i + 2 } != $LocalBlock { 3 * $i + 2 } + ) { + return ( 0 ); + } + } + + return ( 1 ); + } + /* + ::::::::::::::::::::::::::::::::::::::::::::::::::: + :: + :: GIFWord... + :: + */ + function GIFWord ( $int ) { + + return ( chr ( $int & 0xFF ) . chr ( ( $int >> 8 ) & 0xFF ) ); + } + /* + ::::::::::::::::::::::::::::::::::::::::::::::::::: + :: + :: GetAnimation... + :: + */ + function GetAnimation ( ) { + return ( $this->GIF ); + } +} + + +/* +::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:: +:: GIFDecoder Version 2.0 by László Zsidi, http://gifs.hu +:: +:: Created at 2007. 02. 01. '07.47.AM' +:: +:: +:: +:: +:: Try on-line GIFBuilder Form demo based on GIFDecoder. +:: +:: http://gifs.hu/phpclasses/demos/GifBuilder/ +:: +::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +*/ + +Class GIFDecoder { + var $GIF_buffer = Array ( ); + var $GIF_arrays = Array ( ); + var $GIF_delays = Array ( ); + var $GIF_stream = ""; + var $GIF_string = ""; + var $GIF_bfseek = 0; + + var $GIF_screen = Array ( ); + var $GIF_global = Array ( ); + var $GIF_sorted; + var $GIF_colorS; + var $GIF_colorC; + var $GIF_colorF; + + /* + ::::::::::::::::::::::::::::::::::::::::::::::::::: + :: + :: GIFDecoder ( $GIF_pointer ) + :: + */ + function GIFDecoder ( $GIF_pointer ) { + $this->GIF_stream = $GIF_pointer; + + GIFDecoder::GIFGetByte ( 6 ); // GIF89a + GIFDecoder::GIFGetByte ( 7 ); // Logical Screen Descriptor + + $this->GIF_screen = $this->GIF_buffer; + $this->GIF_colorF = $this->GIF_buffer [ 4 ] & 0x80 ? 1 : 0; + $this->GIF_sorted = $this->GIF_buffer [ 4 ] & 0x08 ? 1 : 0; + $this->GIF_colorC = $this->GIF_buffer [ 4 ] & 0x07; + $this->GIF_colorS = 2 << $this->GIF_colorC; + + if ( $this->GIF_colorF == 1 ) { + GIFDecoder::GIFGetByte ( 3 * $this->GIF_colorS ); + $this->GIF_global = $this->GIF_buffer; + } + /* + * + * 05.06.2007. + * Made a little modification + * + * + - for ( $cycle = 1; $cycle; ) { + + if ( GIFDecoder::GIFGetByte ( 1 ) ) { + - switch ( $this->GIF_buffer [ 0 ] ) { + - case 0x21: + - GIFDecoder::GIFReadExtensions ( ); + - break; + - case 0x2C: + - GIFDecoder::GIFReadDescriptor ( ); + - break; + - case 0x3B: + - $cycle = 0; + - break; + - } + - } + + else { + + $cycle = 0; + + } + - } + */ + for ( $cycle = 1; $cycle; ) { + if ( GIFDecoder::GIFGetByte ( 1 ) ) { + switch ( $this->GIF_buffer [ 0 ] ) { + case 0x21: + GIFDecoder::GIFReadExtensions ( ); + break; + case 0x2C: + GIFDecoder::GIFReadDescriptor ( ); + break; + case 0x3B: + $cycle = 0; + break; + } + } + else { + $cycle = 0; + } + } + } + /* + ::::::::::::::::::::::::::::::::::::::::::::::::::: + :: + :: GIFReadExtension ( ) + :: + */ + function GIFReadExtensions ( ) { + GIFDecoder::GIFGetByte ( 1 ); + for ( ; ; ) { + GIFDecoder::GIFGetByte ( 1 ); + if ( ( $u = $this->GIF_buffer [ 0 ] ) == 0x00 ) { + break; + } + GIFDecoder::GIFGetByte ( $u ); + /* + * 07.05.2007. + * Implemented a new line for a new function + * to determine the originaly delays between + * frames. + * + */ + if ( $u == 4 ) { + $this->GIF_delays [ ] = ( $this->GIF_buffer [ 1 ] | $this->GIF_buffer [ 2 ] << 8 ); + } + } + } + /* + ::::::::::::::::::::::::::::::::::::::::::::::::::: + :: + :: GIFReadExtension ( ) + :: + */ + function GIFReadDescriptor ( ) { + $GIF_screen = Array ( ); + + GIFDecoder::GIFGetByte ( 9 ); + $GIF_screen = $this->GIF_buffer; + $GIF_colorF = $this->GIF_buffer [ 8 ] & 0x80 ? 1 : 0; + if ( $GIF_colorF ) { + $GIF_code = $this->GIF_buffer [ 8 ] & 0x07; + $GIF_sort = $this->GIF_buffer [ 8 ] & 0x20 ? 1 : 0; + } + else { + $GIF_code = $this->GIF_colorC; + $GIF_sort = $this->GIF_sorted; + } + $GIF_size = 2 << $GIF_code; + $this->GIF_screen [ 4 ] &= 0x70; + $this->GIF_screen [ 4 ] |= 0x80; + $this->GIF_screen [ 4 ] |= $GIF_code; + if ( $GIF_sort ) { + $this->GIF_screen [ 4 ] |= 0x08; + } + $this->GIF_string = "GIF87a"; + GIFDecoder::GIFPutByte ( $this->GIF_screen ); + if ( $GIF_colorF == 1 ) { + GIFDecoder::GIFGetByte ( 3 * $GIF_size ); + GIFDecoder::GIFPutByte ( $this->GIF_buffer ); + } + else { + GIFDecoder::GIFPutByte ( $this->GIF_global ); + } + $this->GIF_string .= chr ( 0x2C ); + $GIF_screen [ 8 ] &= 0x40; + GIFDecoder::GIFPutByte ( $GIF_screen ); + GIFDecoder::GIFGetByte ( 1 ); + GIFDecoder::GIFPutByte ( $this->GIF_buffer ); + for ( ; ; ) { + GIFDecoder::GIFGetByte ( 1 ); + GIFDecoder::GIFPutByte ( $this->GIF_buffer ); + if ( ( $u = $this->GIF_buffer [ 0 ] ) == 0x00 ) { + break; + } + GIFDecoder::GIFGetByte ( $u ); + GIFDecoder::GIFPutByte ( $this->GIF_buffer ); + } + $this->GIF_string .= chr ( 0x3B ); + /* + Add frames into $GIF_stream array... + */ + $this->GIF_arrays [ ] = $this->GIF_string; + } + /* + ::::::::::::::::::::::::::::::::::::::::::::::::::: + :: + :: GIFGetByte ( $len ) + :: + */ + + /* + * + * 05.06.2007. + * Made a little modification + * + * + - function GIFGetByte ( $len ) { + - $this->GIF_buffer = Array ( ); + - + - for ( $i = 0; $i < $len; $i++ ) { + + if ( $this->GIF_bfseek > strlen ( $this->GIF_stream ) ) { + + return 0; + + } + - $this->GIF_buffer [ ] = ord ( $this->GIF_stream { $this->GIF_bfseek++ } ); + - } + + return 1; + - } + */ + function GIFGetByte ( $len ) { + $this->GIF_buffer = Array ( ); + + for ( $i = 0; $i < $len; $i++ ) { + if ( $this->GIF_bfseek > strlen ( $this->GIF_stream ) ) { + return 0; + } + $this->GIF_buffer [ ] = ord ( $this->GIF_stream { $this->GIF_bfseek++ } ); + } + return 1; + } + /* + ::::::::::::::::::::::::::::::::::::::::::::::::::: + :: + :: GIFPutByte ( $bytes ) + :: + */ + function GIFPutByte ( $bytes ) { + for ( $i = 0; $i < count ( $bytes ); $i++ ) { + $this->GIF_string .= chr ( $bytes [ $i ] ); + } + } + /* + ::::::::::::::::::::::::::::::::::::::::::::::::::: + :: + :: PUBLIC FUNCTIONS + :: + :: + :: GIFGetFrames ( ) + :: + */ + function GIFGetFrames ( ) { + return ( $this->GIF_arrays ); + } + /* + ::::::::::::::::::::::::::::::::::::::::::::::::::: + :: + :: GIFGetDelays ( ) + :: + */ + function GIFGetDelays ( ) { + return ( $this->GIF_delays ); + } +} diff --git a/Think/Image/Driver/Imagick.php b/Think/Image/Driver/Imagick.php new file mode 100644 index 00000000..528b2dd9 --- /dev/null +++ b/Think/Image/Driver/Imagick.php @@ -0,0 +1,591 @@ + +// +---------------------------------------------------------------------- +// | ImageImagick.class.php 2013-03-06 +// +---------------------------------------------------------------------- +namespace Think\Image\Driver; +class Imagick{ + /** + * 图像资源对象 + * @var resource + */ + private $im; + + /** + * 图像信息,包括width,height,type,mime,size + * @var array + */ + private $info; + + /** + * 构造方法,可用于打开一张图像 + * @param string $imgname 图像路径 + */ + public function __construct($imgname = null) { + if ( !extension_loaded('Imagick') ) { + throw_exception(__('_NOT_SUPPERT_').':Imagick'); + } + $imgname && $this->open($imgname); + } + + /** + * 打开一张图像 + * @param string $imgname 图像路径 + */ + public function open($imgname){ + //检测图像文件 + if(!is_file($imgname)) throw new Exception('不存在的图像文件'); + + //销毁已存在的图像 + empty($this->im) || $this->im->destroy(); + + //载入图像 + $this->im = new \Imagick(realpath($imgname)); + + //设置图像信息 + $this->info = array( + 'width' => $this->im->getImageWidth(), + 'height' => $this->im->getImageHeight(), + 'type' => strtolower($this->im->getImageFormat()), + 'mime' => $this->im->getImageMimeType(), + ); + } + + /** + * 保存图像 + * @param string $imgname 图像保存名称 + * @param string $type 图像类型 + * @param boolean $interlace 是否对JPEG类型图像设置隔行扫描 + */ + public function save($imgname, $type = null, $interlace = true){ + if(empty($this->im)) throw new Exception('没有可以被保存的图像资源'); + + //设置图片类型 + if(is_null($type)){ + $type = $this->info['type']; + } else { + $type = strtolower($type); + $this->im->setImageFormat($type); + } + + //JPEG图像设置隔行扫描 + if('jpeg' == $type || 'jpg' == $type){ + $this->im->setImageInterlaceScheme(1); + } + + //去除图像配置信息 + $this->im->stripImage(); + + //保存图像 + $imgname = realpath(dirname($imgname)) . '/' . basename($imgname); //强制绝对路径 + if ('gif' == $type) { + $this->im->writeImages($imgname, true); + } else { + $this->im->writeImage($imgname); + } + } + + /** + * 返回图像宽度 + * @return integer 图像宽度 + */ + public function width(){ + if(empty($this->im)) throw new Exception('没有指定图像资源'); + return $this->info['width']; + } + + /** + * 返回图像高度 + * @return integer 图像高度 + */ + public function height(){ + if(empty($this->im)) throw new Exception('没有指定图像资源'); + return $this->info['height']; + } + + /** + * 返回图像类型 + * @return string 图像类型 + */ + public function type(){ + if(empty($this->im)) throw new Exception('没有指定图像资源'); + return $this->info['type']; + } + + /** + * 返回图像MIME类型 + * @return string 图像MIME类型 + */ + public function mime(){ + if(empty($this->im)) throw new Exception('没有指定图像资源'); + return $this->info['mime']; + } + + /** + * 返回图像尺寸数组 0 - 图像宽度,1 - 图像高度 + * @return array 图像尺寸 + */ + public function size(){ + if(empty($this->im)) throw new Exception('没有指定图像资源'); + return array($this->info['width'], $this->info['height']); + } + + /** + * 裁剪图像 + * @param integer $w 裁剪区域宽度 + * @param integer $h 裁剪区域高度 + * @param integer $x 裁剪区域x坐标 + * @param integer $y 裁剪区域y坐标 + * @param integer $width 图像保存宽度 + * @param integer $height 图像保存高度 + */ + public function crop($w, $h, $x = 0, $y = 0, $width = null, $height = null){ + if(empty($this->im)) throw new Exception('没有可以被裁剪的图像资源'); + + //设置保存尺寸 + empty($width) && $width = $w; + empty($height) && $height = $h; + + //裁剪图片 + if('gif' == $this->info['type']){ + $img = $this->im->coalesceImages(); + $this->im->destroy(); //销毁原图 + + //循环裁剪每一帧 + do { + $this->_crop($w, $h, $x, $y, $width, $height, $img); + } while ($img->nextImage()); + + //压缩图片 + $this->im = $img->deconstructImages(); + $img->destroy(); //销毁零时图片 + } else { + $this->_crop($w, $h, $x, $y, $width, $height); + } + } + + /* 裁剪图片,内部调用 */ + private function _crop($w, $h, $x, $y, $width, $height, $img = null){ + is_null($img) && $img = $this->im; + + //裁剪 + $info = $this->info; + if($x != 0 || $y != 0 || $w != $info['width'] || $h != $info['height']){ + $img->cropImage($w, $h, $x, $y); + $img->setImagePage($w, $h, 0, 0); //调整画布和图片一致 + } + + //调整大小 + if($w != $width || $h != $height){ + $img->sampleImage($width, $height); + } + + //设置缓存尺寸 + $this->info['width'] = $w; + $this->info['height'] = $h; + } + + /** + * 生成缩略图 + * @param integer $width 缩略图最大宽度 + * @param integer $height 缩略图最大高度 + * @param integer $type 缩略图裁剪类型 + */ + public function thumb($width, $height, $type = THINKIMAGE_THUMB_SCALE){ + if(empty($this->im)) throw new Exception('没有可以被缩略的图像资源'); + + //原图宽度和高度 + $w = $this->info['width']; + $h = $this->info['height']; + + /* 计算缩略图生成的必要参数 */ + switch ($type) { + /* 等比例缩放 */ + case THINKIMAGE_THUMB_SCALING: + //原图尺寸小于缩略图尺寸则不进行缩略 + if($w < $width && $h < $height) return; + + //计算缩放比例 + $scale = min($width/$w, $height/$h); + + //设置缩略图的坐标及宽度和高度 + $x = $y = 0; + $width = $w * $scale; + $height = $h * $scale; + break; + + /* 居中裁剪 */ + case THINKIMAGE_THUMB_CENTER: + //计算缩放比例 + $scale = max($width/$w, $height/$h); + + //设置缩略图的坐标及宽度和高度 + $w = $width/$scale; + $h = $height/$scale; + $x = ($this->info['width'] - $w)/2; + $y = ($this->info['height'] - $h)/2; + break; + + /* 左上角裁剪 */ + case THINKIMAGE_THUMB_NORTHWEST: + //计算缩放比例 + $scale = max($width/$w, $height/$h); + + //设置缩略图的坐标及宽度和高度 + $x = $y = 0; + $w = $width/$scale; + $h = $height/$scale; + break; + + /* 右下角裁剪 */ + case THINKIMAGE_THUMB_SOUTHEAST: + //计算缩放比例 + $scale = max($width/$w, $height/$h); + + //设置缩略图的坐标及宽度和高度 + $w = $width/$scale; + $h = $height/$scale; + $x = $this->info['width'] - $w; + $y = $this->info['height'] - $h; + break; + + /* 填充 */ + case THINKIMAGE_THUMB_FILLED: + //计算缩放比例 + if($w < $width && $h < $height){ + $scale = 1; + } else { + $scale = min($width/$w, $height/$h); + } + + //设置缩略图的坐标及宽度和高度 + $neww = $w * $scale; + $newh = $h * $scale; + $posx = ($width - $w * $scale)/2; + $posy = ($height - $h * $scale)/2; + + //创建一张新图像 + $newimg = new Imagick(); + $newimg->newImage($width, $height, 'white', $this->info['type']); + + + if('gif' == $this->info['type']){ + $imgs = $this->im->coalesceImages(); + $img = new Imagick(); + $this->im->destroy(); //销毁原图 + + //循环填充每一帧 + do { + //填充图像 + $image = $this->_fill($newimg, $posx, $posy, $neww, $newh, $imgs); + + $img->addImage($image); + $img->setImageDelay($imgs->getImageDelay()); + $img->setImagePage($width, $height, 0, 0); + + $image->destroy(); //销毁零时图片 + + } while ($imgs->nextImage()); + + //压缩图片 + $this->im->destroy(); + $this->im = $img->deconstructImages(); + $imgs->destroy(); //销毁零时图片 + $img->destroy(); //销毁零时图片 + + } else { + //填充图像 + $img = $this->_fill($newimg, $posx, $posy, $neww, $newh); + //销毁原图 + $this->im->destroy(); + $this->im = $img; + } + + //设置新图像属性 + $this->info['width'] = $width; + $this->info['height'] = $height; + return; + + /* 固定 */ + case THINKIMAGE_THUMB_FIXED: + $x = $y = 0; + break; + + default: + throw new Exception('不支持的缩略图裁剪类型'); + } + + /* 裁剪图像 */ + $this->crop($w, $h, $x, $y, $width, $height); + } + + /* 填充指定图像,内部使用 */ + private function _fill($newimg, $posx, $posy, $neww, $newh, $img = null){ + is_null($img) && $img = $this->im; + + /* 将指定图片绘入空白图片 */ + $draw = new ImagickDraw(); + $draw->composite($img->getImageCompose(), $posx, $posy, $neww, $newh, $img); + $image = $newimg->clone(); + $image->drawImage($draw); + $draw->destroy(); + + return $image; + } + + /** + * 添加水印 + * @param string $source 水印图片路径 + * @param integer $locate 水印位置 + * @param integer $alpha 水印透明度 + */ + public function water($source, $locate = THINKIMAGE_WATER_SOUTHEAST){ + //资源检测 + if(empty($this->im)) throw new Exception('没有可以被添加水印的图像资源'); + if(!is_file($source)) throw new Exception('水印图像不存在'); + + //创建水印图像资源 + $water = new Imagick(realpath($source)); + $info = array($water->getImageWidth(), $water->getImageHeight()); + + /* 设定水印位置 */ + switch ($locate) { + /* 右下角水印 */ + case THINKIMAGE_WATER_SOUTHEAST: + $x = $this->info['width'] - $info[0]; + $y = $this->info['height'] - $info[1]; + break; + + /* 左下角水印 */ + case THINKIMAGE_WATER_SOUTHWEST: + $x = 0; + $y = $this->info['height'] - $info[1]; + break; + + /* 左上角水印 */ + case THINKIMAGE_WATER_NORTHWEST: + $x = $y = 0; + break; + + /* 右上角水印 */ + case THINKIMAGE_WATER_NORTHEAST: + $x = $this->info['width'] - $info[0]; + $y = 0; + break; + + /* 居中水印 */ + case THINKIMAGE_WATER_CENTER: + $x = ($this->info['width'] - $info[0])/2; + $y = ($this->info['height'] - $info[1])/2; + break; + + /* 下居中水印 */ + case THINKIMAGE_WATER_SOUTH: + $x = ($this->info['width'] - $info[0])/2; + $y = $this->info['height'] - $info[1]; + break; + + /* 右居中水印 */ + case THINKIMAGE_WATER_EAST: + $x = $this->info['width'] - $info[0]; + $y = ($this->info['height'] - $info[1])/2; + break; + + /* 上居中水印 */ + case THINKIMAGE_WATER_NORTH: + $x = ($this->info['width'] - $info[0])/2; + $y = 0; + break; + + /* 左居中水印 */ + case THINKIMAGE_WATER_WEST: + $x = 0; + $y = ($this->info['height'] - $info[1])/2; + break; + + default: + /* 自定义水印坐标 */ + if(is_array($locate)){ + list($x, $y) = $locate; + } else { + throw new Exception('不支持的水印位置类型'); + } + } + + //创建绘图资源 + $draw = new ImagickDraw(); + $draw->composite($water->getImageCompose(), $x, $y, $info[0], $info[1], $water); + + if('gif' == $this->info['type']){ + $img = $this->im->coalesceImages(); + $this->im->destroy(); //销毁原图 + + do{ + //添加水印 + $img->drawImage($draw); + } while ($img->nextImage()); + + //压缩图片 + $this->im = $img->deconstructImages(); + $img->destroy(); //销毁零时图片 + + } else { + //添加水印 + $this->im->drawImage($draw); + } + + //销毁水印资源 + $draw->destroy(); + $water->destroy(); + } + + /** + * 图像添加文字 + * @param string $text 添加的文字 + * @param string $font 字体路径 + * @param integer $size 字号 + * @param string $color 文字颜色 + * @param integer $locate 文字写入位置 + * @param integer $offset 文字相对当前位置的偏移量 + * @param integer $angle 文字倾斜角度 + */ + public function text($text, $font, $size, $color = '#00000000', + $locate = THINKIMAGE_WATER_SOUTHEAST, $offset = 0, $angle = 0){ + //资源检测 + if(empty($this->im)) throw new Exception('没有可以被写入文字的图像资源'); + if(!is_file($font)) throw new Exception("不存在的字体文件:{$font}"); + + //获取颜色和透明度 + if(is_array($color)){ + $color = array_map('dechex', $color); + foreach ($color as &$value) { + $value = str_pad($value, 2, '0', STR_PAD_LEFT); + } + $color = '#' . implode('', $color); + } elseif(!is_string($color) || 0 !== strpos($color, '#')) { + throw new Exception('错误的颜色值'); + } + $col = substr($color, 0, 7); + $alp = strlen($color) == 9 ? substr($color, -2) : 0; + + + //获取文字信息 + $draw = new ImagickDraw(); + $draw->setFont(realpath($font)); + $draw->setFontSize($size); + $draw->setFillColor($col); + $draw->setFillAlpha(1-hexdec($alp)/127); + $draw->setTextAntialias(true); + $draw->setStrokeAntialias(true); + + $metrics = $this->im->queryFontMetrics($draw, $text); + + /* 计算文字初始坐标和尺寸 */ + $x = 0; + $y = $metrics['ascender']; + $w = $metrics['textWidth']; + $h = $metrics['textHeight']; + + /* 设定文字位置 */ + switch ($locate) { + /* 右下角文字 */ + case THINKIMAGE_WATER_SOUTHEAST: + $x += $this->info['width'] - $w; + $y += $this->info['height'] - $h; + break; + + /* 左下角文字 */ + case THINKIMAGE_WATER_SOUTHWEST: + $y += $this->info['height'] - $h; + break; + + /* 左上角文字 */ + case THINKIMAGE_WATER_NORTHWEST: + // 起始坐标即为左上角坐标,无需调整 + break; + + /* 右上角文字 */ + case THINKIMAGE_WATER_NORTHEAST: + $x += $this->info['width'] - $w; + break; + + /* 居中文字 */ + case THINKIMAGE_WATER_CENTER: + $x += ($this->info['width'] - $w)/2; + $y += ($this->info['height'] - $h)/2; + break; + + /* 下居中文字 */ + case THINKIMAGE_WATER_SOUTH: + $x += ($this->info['width'] - $w)/2; + $y += $this->info['height'] - $h; + break; + + /* 右居中文字 */ + case THINKIMAGE_WATER_EAST: + $x += $this->info['width'] - $w; + $y += ($this->info['height'] - $h)/2; + break; + + /* 上居中文字 */ + case THINKIMAGE_WATER_NORTH: + $x += ($this->info['width'] - $w)/2; + break; + + /* 左居中文字 */ + case THINKIMAGE_WATER_WEST: + $y += ($this->info['height'] - $h)/2; + break; + + default: + /* 自定义文字坐标 */ + if(is_array($locate)){ + list($posx, $posy) = $locate; + $x += $posx; + $y += $posy; + } else { + throw new Exception('不支持的文字位置类型'); + } + } + + /* 设置偏移量 */ + if(is_array($offset)){ + $offset = array_map('intval', $offset); + list($ox, $oy) = $offset; + } else{ + $offset = intval($offset); + $ox = $oy = $offset; + } + + /* 写入文字 */ + if('gif' == $this->info['type']){ + $img = $this->im->coalesceImages(); + $this->im->destroy(); //销毁原图 + do{ + $img->annotateImage($draw, $x + $ox, $y + $oy, $angle, $text); + } while ($img->nextImage()); + + //压缩图片 + $this->im = $img->deconstructImages(); + $img->destroy(); //销毁零时图片 + + } else { + $this->im->annotateImage($draw, $x + $ox, $y + $oy, $angle, $text); + } + $draw->destroy(); + } + + /** + * 析构方法,用于销毁图像资源 + */ + public function __destruct() { + empty($this->im) || $this->im->destroy(); + } +} \ No newline at end of file diff --git a/Think/Input.php b/Think/Input.php new file mode 100644 index 00000000..b380514d --- /dev/null +++ b/Think/Input.php @@ -0,0 +1,384 @@ + +// +---------------------------------------------------------------------- +// $Id$ +namespace Think; +class Input { + static private $filter = null; // 输入过滤 + static private $_input = array('get','post','request','env','server','cookie','session','globals','files','call'); + + //html标签设置 + public static $htmlTags = array( + 'allow' => 'table|td|th|tr|i|b|u|strong|img|p|br|div|strong|em|ul|ol|li|dl|dd|dt|a', + 'ban' => 'html|head|meta|link|base|basefont|body|bgsound|title|style|script|form|iframe|frame|frameset|applet|id|ilayer|layer|name|script|style|xml', + ); + + /** + +---------------------------------------------------------- + * 魔术方法 有不存在的操作的时候执行 + +---------------------------------------------------------- + * @access public + +---------------------------------------------------------- + * @param string $type 输入数据类型 + * @param array $args 参数 array(key,filter,default) + +---------------------------------------------------------- + * @return mixed + +---------------------------------------------------------- + */ + static public function __callStatic($type,$args=array()) { + $type = strtolower(trim($type)); + if(in_array($type,self::$_input,true)) { + switch($type) { + case 'get': $input =& $_GET;break; + case 'post': $input =& $_POST;break; + case 'put' : parse_str(file_get_contents('php://input'), $input);break; + case 'param' : + switch($_SERVER['REQUEST_METHOD']) { + case 'POST': + $input = $_POST; + break; + case 'PUT': + parse_str(file_get_contents('php://input'), $input); + break; + default: + $input = $_GET; + } + break; + case 'request': $input =& $_REQUEST;break; + case 'env': $input =& $_ENV;break; + case 'server': $input =& $_SERVER;break; + case 'cookie': $input =& $_COOKIE;break; + case 'session': $input =& $_SESSION;break; + case 'globals': $input =& $GLOBALS;break; + case 'files': $input =& $_FILES;break; + default:return NULL; + } + + if(0==count($args)) { + // 返回全部数据 + return $input; + }elseif(array_key_exists($args[0],$input)) { + $filters = isset($args[1])?$args[1]:self::$filter; + $filters = explode(',',$filters); + $data = $input[$args[0]]; + foreach($filters as $filter){ + if(is_callable($filter)) { + $data = is_array($data)?array_map($filter,$data):$filter($data); // 参数过滤 + } + } + }else{ + // 不存在指定输入 + $data = isset($args[2])?$args[2]:NULL; + } + return $data; + } + } + + /** + +---------------------------------------------------------- + * 设置数据过滤方法 + +---------------------------------------------------------- + * @access public + +---------------------------------------------------------- + * @param mixed $filter 过滤方法 + +---------------------------------------------------------- + * @return void + +---------------------------------------------------------- + */ + static public function filter($filter) { + self::$filter = $filter; + } + + /** + +---------------------------------------------------------- + * 字符MagicQuote转义过滤 + +---------------------------------------------------------- + * @access public + +---------------------------------------------------------- + * @return void + +---------------------------------------------------------- + */ + static public function noGPC() { + if ( get_magic_quotes_gpc() ) { + $_POST = stripslashes_deep($_POST); + $_GET = stripslashes_deep($_GET); + $_COOKIE = stripslashes_deep($_COOKIE); + $_REQUEST= stripslashes_deep($_REQUEST); + } + } + + /** + +---------------------------------------------------------- + * 处理字符串,以便可以正常进行搜索 + +---------------------------------------------------------- + * @access public + +---------------------------------------------------------- + * @param string $string 要处理的字符串 + +---------------------------------------------------------- + * @return string + +---------------------------------------------------------- + */ + static public function forSearch($string) { + return str_replace( array('%','_'), array('\%','\_'), $string ); + } + + /** + +---------------------------------------------------------- + * @access public + +---------------------------------------------------------- + * @param string $string 要处理的字符串 + +---------------------------------------------------------- + * @return string + +---------------------------------------------------------- + */ + static public function forShow($string) { + return self::nl2Br( self::hsc($string) ); + } + + /** + +---------------------------------------------------------- + * 处理纯文本数据,以便在textarea标签中显示 + +---------------------------------------------------------- + * @access public + +---------------------------------------------------------- + * @param string $string 要处理的字符串 + +---------------------------------------------------------- + * @return string + +---------------------------------------------------------- + */ + static public function forTarea($string) { + return str_ireplace(array(''), array('<textarea>','</textarea>'), $string); + } + + /** + +---------------------------------------------------------- + * 将数据中的单引号和双引号进行转义 + +---------------------------------------------------------- + * @access public + +---------------------------------------------------------- + * @param string $text 要处理的字符串 + +---------------------------------------------------------- + * @return string + +---------------------------------------------------------- + */ + static public function forTag($string) { + return str_replace(array('"',"'"), array('"','''), $string); + } + + /** + +---------------------------------------------------------- + * 把换行转换为
标签 + +---------------------------------------------------------- + * @access public + +---------------------------------------------------------- + * @param string $string 要处理的字符串 + +---------------------------------------------------------- + * @return string + +---------------------------------------------------------- + */ + static public function nl2Br($string) { + return nl2Br($string); + } + + /** + +---------------------------------------------------------- + * 如果 magic_quotes_gpc 为关闭状态,这个函数可以转义字符串 + +---------------------------------------------------------- + * @access public + +---------------------------------------------------------- + * @param string $string 要处理的字符串 + +---------------------------------------------------------- + * @return string + +---------------------------------------------------------- + */ + static public function addSlashes($string) { + if (!get_magic_quotes_gpc()) { + $string = addslashes($string); + } + return $string; + } + + /** + +---------------------------------------------------------- + * 从$_POST,$_GET,$_COOKIE,$_REQUEST等数组中获得数据 + +---------------------------------------------------------- + * @access public + +---------------------------------------------------------- + * @param string $string 要处理的字符串 + +---------------------------------------------------------- + * @return string + +---------------------------------------------------------- + */ + static public function getVar($string) { + return self::stripSlashes($string); + } + + /** + +---------------------------------------------------------- + * 如果 magic_quotes_gpc 为开启状态,这个函数可以反转义字符串 + +---------------------------------------------------------- + * @access public + +---------------------------------------------------------- + * @param string $string 要处理的字符串 + +---------------------------------------------------------- + * @return string + +---------------------------------------------------------- + */ + static public function stripSlashes($string) { + if (get_magic_quotes_gpc()) { + $string = stripslashes($string); + } + return $string; + } + + /** + +---------------------------------------------------------- + * 用于在textbox表单中显示html代码 + +---------------------------------------------------------- + * @access public + +---------------------------------------------------------- + * @param string $string 要处理的字符串 + +---------------------------------------------------------- + * @return string + +---------------------------------------------------------- + */ + static function hsc($string) { + return preg_replace(array("/&/i", "/ /i"), array('&', '&nbsp;'), htmlspecialchars($string, ENT_QUOTES)); + } + + /** + +---------------------------------------------------------- + * 是hsc()方法的逆操作 + +---------------------------------------------------------- + * @access public + +---------------------------------------------------------- + * @param string $text 要处理的字符串 + +---------------------------------------------------------- + * @return string + +---------------------------------------------------------- + */ + static function undoHsc($text) { + return preg_replace(array("/>/i", "/</i", "/"/i", "/'/i", '/&nbsp;/i'), array(">", "<", "\"", "'", " "), $text); + } + + /** + +---------------------------------------------------------- + * 输出安全的html,用于过滤危险代码 + +---------------------------------------------------------- + * @access public + +---------------------------------------------------------- + * @param string $text 要处理的字符串 + * @param mixed $allowTags 允许的标签列表,如 table|td|th|td + +---------------------------------------------------------- + * @return string + +---------------------------------------------------------- + */ + static public function safeHtml($text, $allowTags = null) { + $text = trim($text); + //完全过滤注释 + $text = preg_replace('//','',$text); + //完全过滤动态代码 + $text = preg_replace('/<\?|\?'.'>/','',$text); + //完全过滤js + $text = preg_replace('//','',$text); + + $text = str_replace('[','[',$text); + $text = str_replace(']',']',$text); + $text = str_replace('|','|',$text); + //过滤换行符 + $text = preg_replace('/\r?\n/','',$text); + //br + $text = preg_replace('//i','[br]',$text); + $text = preg_replace('/(\[br\]\s*){10,}/i','[br]',$text); + //过滤危险的属性,如:过滤on事件lang js + while(preg_match('/(<[^><]+)(lang|on|action|background|codebase|dynsrc|lowsrc)[^><]+/i',$text,$mat)){ + $text=str_replace($mat[0],$mat[1],$text); + } + while(preg_match('/(<[^><]+)(window\.|javascript:|js:|about:|file:|document\.|vbs:|cookie)([^><]*)/i',$text,$mat)){ + $text=str_replace($mat[0],$mat[1].$mat[3],$text); + } + if( empty($allowTags) ) { $allowTags = self::$htmlTags['allow']; } + //允许的HTML标签 + $text = preg_replace('/<('.$allowTags.')( [^><\[\]]*)>/i','[\1\2]',$text); + //过滤多余html + if ( empty($banTag) ) { $banTag = self::$htmlTags['ban']; } + $text = preg_replace('/<\/?('.$banTag.')[^><]*>/i','',$text); + //过滤合法的html标签 + while(preg_match('/<([a-z]+)[^><\[\]]*>[^><]*<\/\1>/i',$text,$mat)){ + $text=str_replace($mat[0],str_replace('>',']',str_replace('<','[',$mat[0])),$text); + } + //转换引号 + while(preg_match('/(\[[^\[\]]*=\s*)(\"|\')([^\2=\[\]]+)\2([^\[\]]*\])/i',$text,$mat)){ + $text=str_replace($mat[0],$mat[1].'|'.$mat[3].'|'.$mat[4],$text); + } + //空属性转换 + $text = str_replace('\'\'','||',$text); + $text = str_replace('""','||',$text); + //过滤错误的单个引号 + while(preg_match('/\[[^\[\]]*(\"|\')[^\[\]]*\]/i',$text,$mat)){ + $text=str_replace($mat[0],str_replace($mat[1],'',$mat[0]),$text); + } + //转换其它所有不合法的 < > + $text = str_replace('<','<',$text); + $text = str_replace('>','>',$text); + $text = str_replace('"','"',$text); + //反转换 + $text = str_replace('[','<',$text); + $text = str_replace(']','>',$text); + $text = str_replace('|','"',$text); + //过滤多余空格 + $text = str_replace(' ',' ',$text); + return $text; + } + + /** + +---------------------------------------------------------- + * 删除html标签,得到纯文本。可以处理嵌套的标签 + +---------------------------------------------------------- + * @access public + +---------------------------------------------------------- + * @param string $string 要处理的html + +---------------------------------------------------------- + * @return string + +---------------------------------------------------------- + */ + static public function deleteHtmlTags($string) { + while(strstr($string, '>')) { + $currentBeg = strpos($string, '<'); + $currentEnd = strpos($string, '>'); + $tmpStringBeg = @substr($string, 0, $currentBeg); + $tmpStringEnd = @substr($string, $currentEnd + 1, strlen($string)); + $string = $tmpStringBeg.$tmpStringEnd; + } + return $string; + } + + /** + +---------------------------------------------------------- + * 处理文本中的换行 + +---------------------------------------------------------- + * @access public + +---------------------------------------------------------- + * @param string $string 要处理的字符串 + * @param mixed $br 对换行的处理, + * false:去除换行;true:保留原样;string:替换成string + +---------------------------------------------------------- + * @return string + +---------------------------------------------------------- + */ + static public function nl2($string, $br = '
') { + if ($br == false) { + $string = preg_replace("/(\015\012)|(\015)|(\012)/", '', $string); + } elseif ($br != true){ + $string = preg_replace("/(\015\012)|(\015)|(\012)/", $br, $string); + } + return $string; + } +} \ No newline at end of file diff --git a/Think/Lang.php b/Think/Lang.php new file mode 100644 index 00000000..ff96a372 --- /dev/null +++ b/Think/Lang.php @@ -0,0 +1,53 @@ + +// +---------------------------------------------------------------------- +// $Id$ +namespace Think; +class Lang { + static private $_lang = []; // 语言参数 + static private $_range = '_sys_'; // 作用域 + + // 设定语言参数的作用域 + static public function range($range){ + self::$_range = $range; + } + + /** + * 设置语言定义(不区分大小写) + * @param string|array $name 语言变量 + * @param string $value 语言值 + * @param string $range 作用域 + * @return mixed + */ + static public function set($name, $value=null,$range='') { + $range = $range?$range:self::$_range; + // 批量定义 + if (is_array($name)){ + return self::$_lang[$range] = array_merge(self::$_lang[$range], array_change_key_case($name)); + }else{ + return self::$_lang[$range][strtolower($name)] = $value; + } + } + + /** + * 获取语言定义(不区分大小写) + * @param string|null $name 语言变量 + * @param string $range 作用域 + * @return mixed + */ + static public function get($name=null, $range='') { + $range = $range?$range:self::$_range; + // 空参数返回所有定义 + if (empty($name)) + return self::$_lang[$range]; + $name = strtolower($name); + return isset(self::$_lang[$range][$name]) ? self::$_lang[$range][$name] : $name; + } +} \ No newline at end of file diff --git a/Think/Loader.php b/Think/Loader.php new file mode 100644 index 00000000..984cdc36 --- /dev/null +++ b/Think/Loader.php @@ -0,0 +1,244 @@ + +// +---------------------------------------------------------------------- +// $Id$ +namespace Think; +use Think\Config; + +class Loader { + // 类名映射 + static protected $map = []; + // 命名空间 + static protected $namespace = [ + 'Think' => CORE_PATH, + 'Vendor' => VENDOR_PATH, + ]; + + static public function autoload($class){ + // 检查是否定义classmap + if(isset(self::$map[$class])) { + include self::$map[$class]; + return ; + }else{ // 命名空间自动加载 + $find = false; + foreach (self::$namespace as $name=>$path){ + if(0 === stripos($class,$name)) { + $find = true; + break; + } + } + $path = $find?dirname($path).'/':APP_PATH; + $filename = $path.str_replace('\\','/',$class).'.php'; + if(is_file($filename)) { + include $filename; + return ; + } + } + // 扫描模块目录 + /* + //if(defined(\MODULE_PATH)) { + $dir = glob(MODULE_PATH.'*'); + foreach ($dir as $path){ + if(false === strpos($path,'.') && $pos = strripos($class,basename($path))) { + $name = parse_name(substr($class,0,$pos)); + if(is_file($path.'/'.$name.EXT)) { + include $path.'/'.$name.EXT; + return ; + } + } + } + //}*/ + } + + // 注册classmap + static public function addMap($class,$map){ + self::$map[$class] = $map; + } + + // 注册命名空间 + static public function addNamespace($namespace,$path){ + self::$namespace[$namespace] = $path; + } + + // 加载classmap + static public function loadMap($map){ + self::$map = array_merge(self::$map,$map); + } + + static public function register($autoload=''){ + spl_autoload_register($autoload?$autoload:['Think\Loader','autoload']); + } + + /** + * 导入所需的类库 同java的Import 本函数有缓存功能 + * @param string $class 类库命名空间字符串 + * @param string $baseUrl 起始路径 + * @param string $ext 导入的文件扩展名 + * @return boolean + */ + static public function import($class, $baseUrl = '', $ext= EXT ) { + static $_file = []; + $class = str_replace(array('.', '#'), array('/', '.'), $class); + if (isset($_file[$class . $baseUrl])) + return true; + else + $_file[$class . $baseUrl] = true; + $class_strut = explode('/', $class); + if (empty($baseUrl)) { + if ('@' == $class_strut[0] || MODULE_NAME == $class_strut[0]) { + //加载当前项目应用类库 + $class = substr_replace($class, '', 0, strlen($class_strut[0]) + 1); + $baseUrl = MODULE_PATH; + }elseif (in_array(strtolower($class_strut[0]), array('org','com'))) { + // org 第三方公共类库 com 企业公共类库 + $baseUrl = LIB_PATH; + }elseif('vendor' == strtolower($class_strut[0])){ + $baseUrl = VENDOR_PATH; + }else { // 加载其他项目应用类库 + $class = substr_replace($class, '', 0, strlen($class_strut[0]) + 1); + $baseUrl = APP_PATH . $class_strut[0] .'/'; + } + } + if (substr($baseUrl, -1) != '/') + $baseUrl .= '/'; + // 如果类不存在 则导入类库文件 + $filename = $baseUrl . $class . $ext; + if(is_file($filename)) { + include $filename; + return true; + } + return false; + } + + /** + * M函数用于实例化一个没有模型文件的Model + * @param string $name Model名称 支持指定基础模型 例如 MongoModel:User + * @param string $tablePrefix 表前缀 + * @param mixed $connection 数据库连接信息 + * @return Model + */ + static public function table($name='', $tablePrefix='',$connection='') { + static $_model = []; + if(strpos($name,':')) { + list($class,$name) = explode(':',$name); + }else{ + $class = 'Think\Model'; + } + $guid = $tablePrefix . $name . '_' . $class; + if (!isset($_model[$guid])) + $_model[$guid] = new $class($name,$tablePrefix,$connection); + return $_model[$guid]; + } + + /** + * D函数用于实例化Model + * @param string $name Model名称 + * @param string $layer 业务层名称 + * @return Think\Model + */ + static public function model($name='',$layer='Model') { + if(empty($name)) return new Model; + static $_model = []; + if(isset($_model[$name.$layer])) return $_model[$name.$layer]; + if(strpos($name,'/')) { + list($module,$name) = explode('/',$name); + }else{ + $module = MODULE_NAME; + } + $class = $module.'\\'.$layer.'\\'.parse_name($name,1).$layer; + if(class_exists($class)) { + $model = new $class($name); + }else { + $model = new Model($name); + } + $_model[$name.$layer] = $model; + return $model; + } + + /** + * A函数用于实例化控制器 格式:[分组/]模块 + * @param string $name 资源地址 + * @param string $layer 控制层名称 + * @return Action|false + */ + static public function controll($name,$layer='') { + $layer = $layer?$layer:'Controll'; + static $_instance = []; + if(isset($_instance[$name.$layer])) return $_instance[$name.$layer]; + if(strpos($name,'/')) { + list($module,$name) = explode('/',$name); + }else{ + $module = MODULE_NAME; + } + $class = $module.'\\'.$layer.'\\'.parse_name($name,1).$layer; + if(class_exists($class)) { + $action = new $class(); + $_instance[$name.$layer] = $action; + return $action; + }else{ + return false; + } + } + + /** + * 实例化数据库 + * @param string $name 资源地址 + * @param string $layer 控制层名称 + * @return Action|false + */ + static public function db($config) { + return Db::getInstance($config); + } + + /** + * 远程调用模块的操作方法 参数格式 [模块/控制器/]操作 + * @param string $url 调用地址 + * @param string|array $vars 调用参数 支持字符串和数组 + * @param string $layer 要调用的控制层名称 + * @return mixed + */ + static public function action($url,$vars=[],$layer='') { + $info = pathinfo($url); + $action = $info['basename']; + $module = '.' != $info['dirname']?$info['dirname']:CONTROLL_NAME; + $class = self::controll($module,$layer); + if($class){ + if(is_string($vars)) { + parse_str($vars,$vars); + } + return call_user_func_array(array(&$class,$action.Config::get('action_suffix')),$vars); + }else{ + return false; + } + } + + /** + * 取得对象实例 支持调用类的静态方法 + * @param string $class 对象类名 + * @param string $method 类的静态方法名 + * @return object + */ + static public function instance($class,$method='') { + $identify = $class.$method; + if(!isset(self::$_instance[$identify])) { + if(class_exists($class)){ + $o = new $class(); + if(!empty($method) && method_exists($o,$method)) + self::$_instance[$identify] = call_user_func_array(array(&$o, $method)); + else + self::$_instance[$identify] = $o; + } + else + Error::halt(Lang::get('_CLASS_NOT_EXIST_').':'.$class); + } + return self::$_instance[$identify]; + } + +} \ No newline at end of file diff --git a/Think/Log.php b/Think/Log.php new file mode 100644 index 00000000..22965dde --- /dev/null +++ b/Think/Log.php @@ -0,0 +1,38 @@ + +// +---------------------------------------------------------------------- + +namespace Think; +class Log { + + static protected $handler = null; + + // 日志初始化 + static public function init($config=[]){ + if(!empty($config['type'])) { // 读取log驱动 + $class = 'Think\\Log\\Driver\\'. ucwords(strtolower($config['type'])); + // 检查驱动类 + if(class_exists($class)) { + unset($config['type']); + self::$handler = new $class($config); + return self::$handler; + }else { + // 类没有定义 + throw_exception(Lang::get('_CLASS_NOT_EXIST_').': ' . $class); + } + } + } + + // 调用驱动类的方法 + public static function __callStatic($method, $params){ + return call_user_func_array(array(self::$handler, $method), $params); + } + +} \ No newline at end of file diff --git a/Think/Log/Driver/File.php b/Think/Log/Driver/File.php new file mode 100644 index 00000000..1b6b4e7e --- /dev/null +++ b/Think/Log/Driver/File.php @@ -0,0 +1,106 @@ + +// +---------------------------------------------------------------------- +// $Id$ +namespace Think\Log\Driver; + +class File { + + // 日志记录方式 + const SYSTEM = 0; + const MAIL = 1; + const FILE = 3; + const SAPI = 4; + + // 日志信息 + protected $log = array(); + protected $config = array( + 'log_time_format' => '[ c ]', + 'log_file_size' => 2097152, + 'log_allow_level' => array('ERR','NOTIC','DEBUG','SQL','INFO'), + ); + + public function __construct($config=array()){ + $this->config = array_merge($this->config,$config); + } + + /** + * 记录日志 并且会过滤未经设置的级别 + * @access public + * @param string $message 日志信息 + * @param string $level 日志级别 + * @param boolean $record 是否强制记录 + * @return void + */ + public function record($message,$level='INFO',$record=false) { + if($record || false !== array_search($level,$this->config['log_allow_level'])) { + $this->log[$level][] = "{$level}: {$message}\r\n"; + } + } + + /** + * 获取内存中的日志信息 + * @access public + * @param string $level 日志级别 + * @return array + */ + public function getLog($level=''){ + return $level?$this->log[$level]:$this->log; + } + + /** + * 日志保存 + * @access public + * @param string $destination 写入目标 + * @param string $level 保存的日志级别 + * @return void + */ + public function save($destination='',$level='') { + $log = $level?$this->log[$level]:$this->log; + if(empty($log)) return ; + if(empty($destination)) + $destination = $this->config['log_path'].date('y_m_d').'.log'; + + //检测日志文件大小,超过配置大小则备份日志文件重新生成 + if(is_file($destination) && floor($this->config['log_file_size']) <= filesize($destination) ) + rename($destination,dirname($destination).'/'.time().'-'.basename($destination)); + + $message = date($this->config['log_time_format']).' '.$_SERVER['REMOTE_ADDR'].' '.$_SERVER['REQUEST_URI']."\r\n"; + if($level) { + $message .= implode('',$log)."\r\n"; + $this->log[$level] = array(); + }else{ + foreach($log as $info){ + $message .= implode('',$info)."\r\n"; + } + $this->log = array(); + } + error_log($message, 3,$destination); + //clearstatcache(); + } + + /** + * 日志直接写入 + * @access public + * @param string $log 日志信息 + * @param string $level 日志级别 + * @param string $destination 写入目标 + * @return void + */ + public function write($log,$level,$destination='') { + $now = date($this->config['log_time_format']); + if(empty($destination)) + $destination = $this->config['log_path'].date('y_m_d').'.log'; + //检测日志文件大小,超过配置大小则备份日志文件重新生成 + if(is_file($destination) && floor($this->config['log_file_size']) <= filesize($destination) ) + rename($destination,dirname($destination).'/'.time().'-'.basename($destination)); + error_log("{$now} {$level}: {$log}\r\n", 3,$destination); + } +} \ No newline at end of file diff --git a/Think/Model.php b/Think/Model.php new file mode 100644 index 00000000..5dc1410b --- /dev/null +++ b/Think/Model.php @@ -0,0 +1,1167 @@ + +// +---------------------------------------------------------------------- +// $Id$ + +namespace Think; +class Model { + // 操作状态 + const MODEL_INSERT = 1; // 插入模型数据 + const MODEL_UPDATE = 2; // 更新模型数据 + const MODEL_BOTH = 3; // 包含上面两种方式 + + // 当前使用的扩展模型 + private $_extModel = null; + // 当前数据库操作对象 + protected $db = null; + // 主键名称 + protected $pk = 'id'; + // 数据表前缀 + protected $tablePrefix = ''; + // 模型名称 + protected $name = ''; + // 数据库名称 + protected $dbName = ''; + //数据库配置 + protected $connection = ''; + // 数据表名(不包含表前缀) + protected $tableName = ''; + // 实际数据表名(包含表前缀) + protected $trueTableName = ''; + // 最近错误信息 + protected $error = ''; + // 字段信息 + protected $fields = []; + // 数据信息 + protected $data = []; + // 查询表达式参数 + protected $options = []; + protected $_validate = []; // 自动验证定义 + protected $_auto = []; // 自动完成定义 + protected $_map = []; // 字段映射定义 + protected $_scope = []; // 命名范围定义 + // 是否自动检测数据表字段信息 + protected $autoCheckFields = false; + // 是否批处理验证 + protected $patchValidate = false; + // 链操作方法列表 + protected $methods = ['table','order','alias','having','group','lock','distinct','auto','filter','validate']; + // 配置参数 + protected $config = []; + + /** + * 架构函数 + * 取得DB类的实例对象 字段检查 + * @access public + * @param string $name 模型名称 + * @param string $tablePrefix 表前缀 + * @param mixed $connection 数据库连接信息 + */ + public function __construct($name='',$tablePrefix='',$connection='') { + // 模型初始化 + $this->_initialize(); + // 读取配置参数 + $this->config = Config::get(); + + // 获取模型名称 + if(!empty($name)) { + if(strpos($name,'.')) { // 支持 数据库名.模型名的 定义 + list($this->dbName,$this->name) = explode('.',$name); + }else{ + $this->name = $name; + } + }elseif(empty($this->name)){ + $this->name = $this->getModelName(); + } + // 设置表前缀 + if(is_null($tablePrefix)) {// 前缀为Null表示没有前缀 + $this->tablePrefix = ''; + }elseif('' != $tablePrefix) { + $this->tablePrefix = $tablePrefix; + }else{ + $this->tablePrefix = $this->tablePrefix?$this->tablePrefix:$this->config['db_prefix']; + } + + // 数据库初始化操作 + // 获取数据库操作对象 + // 当前模型有独立的数据库连接信息 + $this->db(0,empty($this->connection)?$connection:$this->connection); + } + + /** + * 设置数据对象的值 + * @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) { + if(in_array(strtolower($method),$this->methods,true)) { + // 连贯操作的实现 + $this->options[strtolower($method)] = $args[0]; + return $this; + }elseif(in_array(strtolower($method),array('count','sum','min','max','avg'),true)){ + // 统计查询的实现 + $field = isset($args[0])?$args[0]:'*'; + return $this->getField(strtoupper($method).'('.$field.') AS tp_'.$method); + }elseif(strtolower(substr($method,0,5))=='getby') { + // 根据某个字段获取记录 + $field = parse_name(substr($method,5)); + $where[$field] = $args[0]; + return $this->where($where)->find(); + }elseif(strtolower(substr($method,0,10))=='getfieldby') { + // 根据某个字段获取记录的某个值 + $name = parse_name(substr($method,10)); + $where[$name] =$args[0]; + return $this->where($where)->getField($args[1]); + }elseif(isset($this->_scope[$method])){// 命名范围的单独调用支持 + return $this->scope($method,$args[0]); + }else{ + throw exception(__CLASS__.':'.$method.L('_METHOD_NOT_EXIST_')); + return; + } + } + // 回调方法 初始化模型 + protected function _initialize() {} + + /** + * 对保存到数据库的数据进行处理 + * @access protected + * @param mixed $data 要操作的数据 + * @return boolean + */ + protected function _facade($data) { + // 检查非数据字段 + if(!empty($this->fields)) { + foreach ($data as $key=>$val){ + if(!in_array($key,$this->fields,true)){ + unset($data[$key]); + }elseif(is_scalar($val)) { + // 字段类型检查 + $this->_parseType($data,$key); + } + } + } + // 安全过滤 + if(!empty($this->options['filter'])) { + $data = array_map($this->options['filter'],$data); + unset($this->options['filter']); + } + $this->_before_write($data); + return $data; + } + + // 写入数据前的回调方法 包括新增和更新 + protected function _before_write(&$data) {} + + /** + * 新增数据 + * @access public + * @param mixed $data 数据 + * @param boolean $replace 是否replace + * @return mixed + */ + public function add($data='',$replace=false) { + if(empty($data)) { + // 没有传递数据,获取当前数据对象的值 + if(!empty($this->data)) { + $data = $this->data; + // 重置数据 + $this->data = []; + }else{ + $this->error = L('_DATA_TYPE_INVALID_'); + return false; + } + } + // 分析表达式 + $options = $this->_parseOptions(); + // 数据处理 + $data = $this->_facade($data); + if(false === $this->_before_insert($data,$options)) { + return false; + } + // 写入数据到数据库 + $result = $this->db->insert($data,$options,$replace); + if(false !== $result ) { + $insertId = $this->getLastInsID(); + if($insertId) { + // 自增主键返回插入ID + $data[$this->getPk()] = $insertId; + $this->_after_insert($data,$options); + return $insertId; + } + $this->_after_insert($data,$options); + } + return $result; + } + // 插入数据前的回调方法 + protected function _before_insert(&$data,$options) {} + // 插入成功后的回调方法 + protected function _after_insert($data,$options) {} + + public function addAll($dataList,$replace=false){ + if(empty($dataList)) { + $this->error = L('_DATA_TYPE_INVALID_'); + return false; + } + // 分析表达式 + $options = $this->_parseOptions(); + // 数据处理 + foreach ($dataList as $key=>$data){ + $dataList[$key] = $this->_facade($data); + } + // 写入数据到数据库 + $result = $this->db->insertAll($dataList,$options,$replace); + if(false !== $result ) { + $insertId = $this->getLastInsID(); + if($insertId) { + return $insertId; + } + } + return $result; + } + + /** + * 通过Select方式添加记录 + * @access public + * @param string $fields 要插入的数据表字段名 + * @param string $table 要插入的数据表名 + * @param array $options 表达式 + * @return boolean + */ + public function selectAdd($fields='',$table='',$options=[]) { + // 分析表达式 + $options = $this->_parseOptions($options); + // 写入数据到数据库 + if(false === $result = $this->db->selectInsert($fields?$fields:$options['field'],$table?$table:$this->getTableName(),$options)){ + // 数据库插入操作失败 + $this->error = L('_OPERATION_WRONG_'); + return false; + }else { + // 插入成功 + return $result; + } + } + + /** + * 保存数据 + * @access public + * @param mixed $data 数据 + * @return boolean + */ + public function save($data='') { + if(empty($data)) { + // 没有传递数据,获取当前数据对象的值 + if(!empty($this->data)) { + $data = $this->data; + // 重置数据 + $this->data = []; + }else{ + $this->error = L('_DATA_TYPE_INVALID_'); + return false; + } + } + // 数据处理 + $data = $this->_facade($data); + // 分析表达式 + $options = $this->_parseOptions(); + if(false === $this->_before_update($data,$options)) { + return false; + } + if(!isset($options['where']) ) { + // 如果存在主键数据 则自动作为更新条件 + if(isset($data[$this->getPk()])) { + $pk = $this->getPk(); + $where[$pk] = $data[$pk]; + $options['where'] = $where; + $pkValue = $data[$pk]; + unset($data[$pk]); + }else{ + // 如果没有任何更新条件则不执行 + $this->error = Lang::get('_OPERATION_WRONG_'); + return false; + } + } + $result = $this->db->update($data,$options); + if(false !== $result) { + if(isset($pkValue)) $data[$pk] = $pkValue; + $this->_after_update($data,$options); + } + return $result; + } + // 更新数据前的回调方法 + protected function _before_update(&$data,$options) {} + // 更新成功后的回调方法 + protected function _after_update($data,$options) {} + + /** + * 删除数据 + * @access public + * @param mixed $options 表达式 + * @return mixed + */ + public function delete($options=[]) { + if(empty($options) && empty($this->options['where'])) { + // 如果删除条件为空 则删除当前数据对象所对应的记录 + if(!empty($this->data) && isset($this->data[$this->getPk()])) + return $this->delete($this->data[$this->getPk()]); + else + return false; + } + if(is_numeric($options) || is_string($options)) { + // 根据主键删除记录 + $pk = $this->getPk(); + if(strpos($options,',')) { + $where[$pk] = array('IN', $options); + }else{ + $where[$pk] = $options; + } + $pkValue = $where[$pk]; + $options = []; + $options['where'] = $where; + } + // 分析表达式 + $options = $this->_parseOptions($options); + $result= $this->db->delete($options); + if(false !== $result) { + $data = []; + if(isset($pkValue)) $data[$pk] = $pkValue; + $this->_after_delete($data,$options); + } + // 返回删除记录个数 + return $result; + } + // 删除成功后的回调方法 + protected function _after_delete($data,$options) {} + + /** + * 查询数据集 + * @access public + * @param mixed $options 表达式参数 + * @return mixed + */ + public function select($options=[]) { + if(is_string($options) || is_numeric($options)) { + // 根据主键查询 + $pk = $this->getPk(); + if(strpos($options,',')) { + $where[$pk] = array('IN',$options); + }else{ + $where[$pk] = $options; + } + $options = []; + $options['where'] = $where; + }elseif(false === $options){ // 用于子查询 不查询只返回SQL + $options = []; + // 分析表达式 + $options = $this->_parseOptions($options); + return '( '.$this->db->buildSelectSql($options).' )'; + } + // 分析表达式 + $options = $this->_parseOptions($options); + $resultSet = $this->db->select($options); + if(false === $resultSet) { + return false; + } + if(empty($resultSet)) { // 查询结果为空 + return null; + } + $this->_after_select($resultSet,$options); + return $resultSet; + } + // 查询成功后的回调方法 + protected function _after_select(&$resultSet,$options) {} + + /** + * 生成查询SQL 可用于子查询 + * @access public + * @param array $options 表达式参数 + * @return string + */ + public function buildSql($options=[]) { + // 分析表达式 + $options = $this->_parseOptions($options); + return '( '.$this->db->buildSelectSql($options).' )'; + } + + /** + * 分析表达式 + * @access protected + * @param array $options 表达式参数 + * @return array + */ + protected function _parseOptions($options=[]) { + if(is_array($options)) + $options = array_merge($this->options,$options); + // 查询过后清空sql表达式组装 避免影响下次查询 + $this->options = []; + + if(!empty($options['alias'])) { + $options['table'] .= ' '.$options['alias']; + } + // 记录操作的模型名称 + $options['model'] = $this->name; + + if(isset($options['table'])) {// 动态指定表名 + $fields = $this->db->getFields($this->options['table']); + $fields = $fields?array_keys($fields):false; + }else{ + $options['table'] = $this->getTableName(); + $fields = $this->getDbFields(); + } + // 字段类型验证 + if(isset($options['where']) && is_array($options['where']) && !empty($fields)) { + // 对数组查询条件进行字段类型检查 + foreach ($options['where'] as $key=>$val){ + $key = trim($key); + if(in_array($key,$fields,true)){ + if(is_scalar($val)) { + $this->_parseType($options['where'],$key); + } + }elseif('_' != substr($key,0,1) && false === strpos($key,'.') && false === strpos($key,'(') && false === strpos($key,'|') && false === strpos($key,'&')){ + unset($options['where'][$key]); + } + } + } + + + // 表达式过滤 + $this->_options_filter($options); + return $options; + } + // 表达式过滤回调方法 + protected function _options_filter(&$options) {} + + /** + * 数据类型检测 + * @access protected + * @param mixed $data 数据 + * @param string $key 字段名 + * @return void + */ + protected function _parseType(&$data,$key) { + $fieldType = strtolower($this->fields['_type'][$key]); + if(false === strpos($fieldType,'bigint') && false !== strpos($fieldType,'int')) { + $data[$key] = intval($data[$key]); + }elseif(false !== strpos($fieldType,'float') || false !== strpos($fieldType,'double')){ + $data[$key] = floatval($data[$key]); + }elseif(false !== strpos($fieldType,'bool')){ + $data[$key] = (bool)$data[$key]; + } + } + + /** + * 查询数据 + * @access public + * @param mixed $options 表达式参数 + * @return mixed + */ + public function find($options=[]) { + if(is_numeric($options) || is_string($options)) { + $where[$this->getPk()] = $options; + $options = []; + $options['where'] = $where; + } + // 总是查找一条记录 + $options['limit'] = 1; + // 分析表达式 + $options = $this->_parseOptions($options); + $resultSet = $this->db->select($options); + if(false === $resultSet) { + return false; + } + if(empty($resultSet)) {// 查询结果为空 + return null; + } + $this->data = $resultSet[0]; + $this->_after_find($this->data,$options); + return $this->data; + } + // 查询成功的回调方法 + protected function _after_find(&$result,$options) {} + + /** + * 处理字段映射 + * @access public + * @param array $data 当前数据 + * @param integer $type 类型 0 写入 1 读取 + * @return array + */ + public function parseFieldsMap($data,$type=1) { + // 检查字段映射 + if(!empty($this->_map)) { + foreach ($this->_map as $key=>$val){ + if($type==1) { // 读取 + if(isset($data[$val])) { + $data[$key] = $data[$val]; + unset($data[$val]); + } + }else{ + if(isset($data[$key])) { + $data[$val] = $data[$key]; + unset($data[$key]); + } + } + } + } + return $data; + } + + /** + * 设置记录的某个字段值 + * 支持使用数据库字段和方法 + * @access public + * @param string|array $field 字段名 + * @param string $value 字段值 + * @return boolean + */ + public function setField($field,$value='') { + if(is_array($field)) { + $data = $field; + }else{ + $data[$field] = $value; + } + return $this->save($data); + } + + /** + * 字段值增长 + * @access public + * @param string $field 字段名 + * @param integer $step 增长值 + * @return boolean + */ + public function setInc($field,$step=1) { + return $this->setField($field,array('exp',$field.'+'.$step)); + } + + /** + * 字段值减少 + * @access public + * @param string $field 字段名 + * @param integer $step 减少值 + * @return boolean + */ + public function setDec($field,$step=1) { + return $this->setField($field,array('exp',$field.'-'.$step)); + } + + /** + * 获取一条记录的某个字段值 + * @access public + * @param string $field 字段名 + * @param string $spea 字段数据间隔符号 NULL返回数组 + * @return mixed + */ + public function getField($field,$sepa=null) { + $options['field'] = $field; + $options = $this->_parseOptions($options); + $field = trim($field); + if(strpos($field,',')) { // 多字段 + if(!isset($options['limit'])){ + $options['limit'] = is_numeric($sepa)?$sepa:''; + } + $resultSet = $this->db->select($options); + if(!empty($resultSet)) { + $_field = explode(',', $field); + $field = array_keys($resultSet[0]); + $key = array_shift($field); + $key2 = array_shift($field); + $cols = []; + $count = count($_field); + foreach ($resultSet as $result){ + $name = $result[$key]; + if(2==$count) { + $cols[$name] = $result[$key2]; + }else{ + $cols[$name] = is_string($sepa)?implode($sepa,$result):$result; + } + } + return $cols; + } + }else{ // 查找一条记录 + // 返回数据个数 + if(true !== $sepa) {// 当sepa指定为true的时候 返回所有数据 + $options['limit'] = is_numeric($sepa)?$sepa:1; + } + $result = $this->db->select($options); + if(!empty($result)) { + if(true !== $sepa && 1==$options['limit']) return reset($result[0]); + foreach ($result as $val){ + $array[] = $val[$field]; + } + return $array; + } + } + return null; + } + + /** + * 创建数据对象 但不保存到数据库 + * @access public + * @param mixed $data 创建数据 + * @param string $type 状态 + * @return mixed + */ + public function create($data='',$type='') { + // 如果没有传值默认取POST数据 + if(empty($data)) { + $data = $_POST; + }elseif(is_object($data)){ + $data = get_object_vars($data); + } + // 验证数据 + if(empty($data) || !is_array($data)) { + $this->error = L('_DATA_TYPE_INVALID_'); + return false; + } + + // 检查字段映射 + $data = $this->parseFieldsMap($data,0); + + // 状态 + $type = $type?$type:(!empty($data[$this->getPk()])?self::MODEL_UPDATE:self::MODEL_INSERT); + + // 检测提交字段的合法性 + if(isset($this->options['field'])) { // $this->field('field1,field2...')->create() + $fields = $this->options['field']; + unset($this->options['field']); + }elseif($type == self::MODEL_INSERT && isset($this->insertFields)) { + $fields = $this->insertFields; + }elseif($type == self::MODEL_UPDATE && isset($this->updateFields)) { + $fields = $this->updateFields; + } + if(isset($fields)) { + if(is_string($fields)) { + $fields = explode(',',$fields); + } + foreach ($data as $key=>$val){ + if(!in_array($key,$fields)) { + unset($data[$key]); + } + } + } + + // 验证完成生成数据对象 + if($this->autoCheckFields) { // 开启字段检测 则过滤非法字段数据 + $fields = $this->getDbFields(); + foreach ($data as $key=>$val){ + if(!in_array($key,$fields)) { + unset($data[$key]); + }elseif(MAGIC_QUOTES_GPC && is_string($val)){ + $data[$key] = stripslashes($val); + } + } + } + + // 赋值当前数据对象 + $this->data = $data; + // 返回创建的数据以供其他调用 + return $data; + } + + /** + * SQL查询 + * @access public + * @param string $sql SQL指令 + * @param mixed $parse 是否需要解析SQL + * @return mixed + */ + public function query($sql,$parse=false) { + if(!is_bool($parse) && !is_array($parse)) { + $parse = func_get_args(); + array_shift($parse); + } + $sql = $this->parseSql($sql,$parse); + return $this->db->query($sql); + } + + /** + * 执行SQL语句 + * @access public + * @param string $sql SQL指令 + * @param mixed $parse 是否需要解析SQL + * @return false | integer + */ + public function execute($sql,$parse=false) { + if(!is_bool($parse) && !is_array($parse)) { + $parse = func_get_args(); + array_shift($parse); + } + $sql = $this->parseSql($sql,$parse); + return $this->db->execute($sql); + } + + /** + * 解析SQL语句 + * @access public + * @param string $sql SQL指令 + * @param boolean $parse 是否需要解析SQL + * @return string + */ + protected function parseSql($sql,$parse) { + // 分析表达式 + if(true === $parse) { + $options = $this->_parseOptions(); + $sql = $this->db->parseSql($sql,$options); + }elseif(is_array($parse)){ // SQL预处理 + $sql = vsprintf($sql,$parse); + }else{ + $sql = strtr($sql,array('__TABLE__'=>$this->getTableName(),'__PREFIX__'=>$this->config['DB_PREFIX'])); + } + $this->db->setModel($this->name); + return $sql; + } + + /** + * 切换当前的数据库连接 + * @access public + * @param integer $linkNum 连接序号 + * @param mixed $config 数据库连接信息 + * @param array $params 模型参数 + * @return Model + */ + public function db($linkNum='',$config='',$params=[]){ + if(''===$linkNum && $this->db) { + return $this->db; + } + static $_linkNum = []; + static $_db = []; + if(!isset($_db[$linkNum]) || (isset($_db[$linkNum]) && $config && $_linkNum[$linkNum]!=$config) ) { + // 创建一个新的实例 + if(!empty($config) && is_string($config) && false === strpos($config,'/')) { // 支持读取配置参数 + $config = Config::get($config); + } + $_db[$linkNum] = Db::getInstance($config); + }elseif(NULL === $config){ + $_db[$linkNum]->close(); // 关闭数据库连接 + unset($_db[$linkNum]); + return ; + } + if(!empty($params)) { + if(is_string($params)) parse_str($params,$params); + foreach ($params as $name=>$value){ + $this->setProperty($name,$value); + } + } + // 记录连接信息 + $_linkNum[$linkNum] = $config; + // 切换数据库连接 + $this->db = $_db[$linkNum]; + $this->_after_db(); + return $this; + } + // 数据库切换后回调方法 + protected function _after_db() {} + + /** + * 得到当前的数据对象名称 + * @access public + * @return string + */ + public function getModelName() { + if(empty($this->name)) + $this->name = substr(get_class($this),0,-5); + return $this->name; + } + + /** + * 得到完整的数据表名 + * @access public + * @return string + */ + public function getTableName() { + if(empty($this->trueTableName)) { + $tableName = !empty($this->tablePrefix) ? $this->tablePrefix : ''; + if(!empty($this->tableName)) { + $tableName .= $this->tableName; + }else{ + $tableName .= parse_name($this->name); + } + $this->trueTableName = strtolower($tableName); + } + return (!empty($this->dbName)?$this->dbName.'.':'').$this->trueTableName; + } + + /** + * 启动事务 + * @access public + * @return void + */ + public function startTrans() { + $this->commit(); + $this->db->startTrans(); + return ; + } + + /** + * 提交事务 + * @access public + * @return boolean + */ + public function commit() { + return $this->db->commit(); + } + + /** + * 事务回滚 + * @access public + * @return boolean + */ + public function rollback() { + return $this->db->rollback(); + } + + /** + * 返回模型的错误信息 + * @access public + * @return string + */ + public function getError(){ + return $this->error; + } + + /** + * 返回数据库的错误信息 + * @access public + * @return string + */ + public function getDbError() { + return $this->db->getError(); + } + + /** + * 返回最后插入的ID + * @access public + * @return string + */ + public function getLastInsID() { + return $this->db->getLastInsID(); + } + + /** + * 返回最后执行的sql语句 + * @access public + * @return string + */ + public function getLastSql() { + return $this->db->getLastSql($this->name); + } + // 鉴于getLastSql比较常用 增加_sql 别名 + public function _sql(){ + return $this->getLastSql(); + } + + /** + * 获取主键名称 + * @access public + * @return string + */ + public function getPk() { + return isset($this->fields['_pk'])?$this->fields['_pk']:$this->pk; + } + + /** + * 获取数据表字段信息 + * @access public + * @return array + */ + public function getDbFields(){ + if($this->fields) { + $fields = $this->fields; + unset($fields['_pk'],$fields['_type']); + return array_keys($fields); + }else{ + $fields = Cache::get(md5($this->getTableName())); + if(!$fields) { + $fields = $this->db->getFields($this->getTableName()); + foreach ($fields as $key=>$val){ + // 记录字段类型 + $type[$key] = $val['type']; + if($val['primary']) { + $fields['_pk'] = $key; + } + } + // 记录字段类型信息 + $fields['_type'] = $type; + Cache::set(md5($this->trueTableName),$fields); + } + if($fields) { + $this->fields = $fields; + // 永久缓存 + unset($fields['_pk'],$fields['_type']); + return array_keys($fields); + } + return false; + } + } + + /** + * 设置数据对象值 + * @access public + * @param mixed $data 数据 + * @return Model + */ + public function data($data=''){ + if('' === $data && !empty($this->data)) { + return $this->data; + } + if(is_object($data)){ + $data = get_object_vars($data); + }elseif(is_string($data)){ + parse_str($data,$data); + }elseif(!is_array($data)){ + throw_exception(L('_DATA_TYPE_INVALID_')); + } + $this->data = $data; + return $this; + } + + /** + * 查询SQL组装 join + * @access public + * @param mixed $join + * @return Model + */ + public function join($join) { + if(is_array($join)) { + $this->options['join'] = $join; + }elseif(!empty($join)) { + $this->options['join'][] = $join; + } + return $this; + } + + /** + * 查询SQL组装 union + * @access public + * @param mixed $union + * @param boolean $all + * @return Model + */ + public function union($union,$all=false) { + if(empty($union)) return $this; + if($all) { + $this->options['union']['_all'] = true; + } + if(is_object($union)) { + $union = get_object_vars($union); + } + // 转换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{ + throw_exception(L('_DATA_TYPE_INVALID_')); + } + $this->options['union'][] = $options; + return $this; + } + + /** + * 查询缓存 + * @access public + * @param mixed $key + * @param integer $expire + * @param string $type + * @return Model + */ + public function cache($key=true,$expire=null,$type=''){ + if(false !== $key) + $this->options['cache'] = array('key'=>$key,'expire'=>$expire,'type'=>$type); + return $this; + } + + /** + * 指定查询字段 支持字段排除 + * @access public + * @param mixed $field + * @param boolean $except 是否排除 + * @return Model + */ + public function field($field,$except=false){ + if(true === $field) {// 获取全部字段 + $fields = $this->getDbFields(); + $field = $fields?$fields:'*'; + }elseif($except) {// 字段排除 + if(is_string($field)) { + $field = explode(',',$field); + } + $fields = $this->getDbFields(); + $field = $fields?array_diff($fields,$field):$field; + } + $this->options['field'] = $field; + return $this; + } + + /** + * 调用命名范围 + * @access public + * @param mixed $scope 命名范围名称 支持多个 和直接定义 + * @param array $args 参数 + * @return Model + */ + public function scope($scope='',$args=NULL){ + if('' === $scope) { + if(isset($this->_scope['default'])) { + // 默认的命名范围 + $options = $this->_scope['default']; + }else{ + return $this; + } + }elseif(is_string($scope)){ // 支持多个命名范围调用 用逗号分割 + $scopes = explode(',',$scope); + $options = []; + foreach ($scopes as $name){ + if(!isset($this->_scope[$name])) continue; + $options = array_merge($options,$this->_scope[$name]); + } + if(!empty($args) && is_array($args)) { + $options = array_merge($options,$args); + } + }elseif(is_array($scope)){ // 直接传入命名范围定义 + $options = $scope; + } + + if(is_array($options) && !empty($options)){ + $this->options = array_merge($this->options,array_change_key_case($options)); + } + return $this; + } + + /** + * 指定查询条件 支持安全过滤 + * @access public + * @param mixed $where 条件表达式 + * @param mixed $parse 预处理参数 + * @return Model + */ + public function where($where,$parse=null){ + if(!is_null($parse) && is_string($where)) { + if(!is_array($parse)) { + $parse = func_get_args(); + array_shift($parse); + } + $parse = array_map(array($this->db,'escapeString'),$parse); + $where = vsprintf($where,$parse); + }elseif(is_object($where)){ + $where = get_object_vars($where); + } + if(is_string($where) && '' != $where){ + $map = []; + $map['_string'] = $where; + $where = $map; + } + if(isset($this->options['where'])){ + $this->options['where'] = array_merge($this->options['where'],$where); + }else{ + $this->options['where'] = $where; + } + + return $this; + } + + /** + * 指定查询数量 + * @access public + * @param mixed $offset 起始位置 + * @param mixed $length 查询数量 + * @return Model + */ + public function limit($offset,$length=null){ + $this->options['limit'] = is_null($length)?$offset:$offset.','.$length; + return $this; + } + + /** + * 指定分页 + * @access public + * @param mixed $page 页数 + * @param mixed $listRows 每页数量 + * @return Model + */ + public function page($page,$listRows=null){ + $this->options['page'] = is_null($listRows)?$page:$page.','.$listRows; + return $this; + } + + /** + * 查询注释 + * @access public + * @param string $comment 注释 + * @return Model + */ + public function comment($comment){ + $this->options['comment'] = $comment; + return $this; + } + + /** + * 设置模型的属性值 + * @access public + * @param string $name 名称 + * @param mixed $value 值 + * @return Model + */ + public function setProperty($name,$value) { + if(property_exists($this,$name)) + $this->$name = $value; + return $this; + } + +} \ No newline at end of file diff --git a/Think/Rest.php b/Think/Rest.php new file mode 100644 index 00000000..594ba5ab --- /dev/null +++ b/Think/Rest.php @@ -0,0 +1,215 @@ + +// +---------------------------------------------------------------------- + +namespace Think; +abstract class Rest { + + protected $_method = ''; // 当前请求类型 + protected $_type = ''; // 当前资源类型 + // 输出类型 + protected $rest_method_list = 'get,post,put,delete'; + protected $rest_default_method = 'get'; + protected $rest_type_list = 'html,xml,json,rss'; + protected $rest_default_type = 'html'; + protected $rest_output_type = [ // REST允许输出的资源类型列表 + 'xml' => 'application/xml', + 'json' => 'application/json', + 'html' => 'text/html', + ]; + + /** + * 架构函数 取得模板对象实例 + * @access public + */ + public function __construct() { + // 资源类型检测 + if(!defined('__EXT__') || ''==__EXT__) { // 自动检测资源类型 + $this->_type = $this->getAcceptType(); + }elseif(false === stripos($this->rest_type_list,__EXT__)) { + // 资源类型非法 则用默认资源类型访问 + $this->_type = $this->rest_default_type; + }else{ + $this->_type = __EXT__; + } + // 请求方式检测 + $method = strtolower($_SERVER['REQUEST_METHOD']); + if(false === stripos($this->rest_method_list,$method)) { + // 请求方式非法 则用默认请求方法 + $method = $this->rest_default_method; + } + $this->_method = $method; + } + + /** + * REST 调用 + * @access public + * @param string $method 方法名 + * @param array $args 参数 + * @return mixed + */ + public function _empty($method,$args) { + if(method_exists($this,$method.'_'.$this->_method.'_'.$this->_type)) { // RESTFul方法支持 + $fun = $method.'_'.$this->_method.'_'.$this->_type; + }elseif($this->_method == $this->rest_default_method && method_exists($this,$method.'_'.$this->_type) ){ + $fun = $method.'_'.$this->_type; + }elseif($this->_type == $this->rest_default_type && method_exists($this,$method.'_'.$this->_method) ){ + $fun = $method.'_'.$this->_method; + } + if(isset($fun)) { + $this->$fun(); + }else{ + // 抛出异常 + throw_exception(L('_ERROR_ACTION_:').ACTION_NAME); + } + } + + /** + * 设置页面输出的CONTENT_TYPE和编码 + * @access public + * @param string $type content_type 类型对应的扩展名 + * @param string $charset 页面输出编码 + * @return void + */ + public function setContentType($type, $charset=''){ + if(headers_sent()) return; + if(empty($charset)) $charset = ThinkConfig::get('default_charset'); + $type = strtolower($type); + if(isset($this->rest_output_type[$type])) //过滤content_type + header('Content-Type: '.$this->rest_output_type[$type].'; charset='.$charset); + } + + /** + * 输出返回数据 + * @access protected + * @param mixed $data 要返回的数据 + * @param String $type 返回类型 JSON XML + * @param integer $code HTTP状态 + * @return void + */ + protected function response($data,$type='',$code=200) { + $this->sendHttpStatus($code); + exit($this->encodeData($data,strtolower($type))); + } + + /** + * 编码数据 + * @access protected + * @param mixed $data 要返回的数据 + * @param String $type 返回类型 JSON XML + * @return void + */ + protected function encodeData($data,$type='') { + if(empty($data)) return ''; + if('json' == $type) { + // 返回JSON数据格式到客户端 包含状态信息 + $data = json_encode($data); + }elseif('xml' == $type){ + // 返回xml格式数据 + $data = xml_encode($data); + }elseif('php'==$type){ + $data = serialize($data); + }// 默认直接输出 + $this->setContentType($type); + header('Content-Length: ' . strlen($data)); + return $data; + } + + // 发送Http状态信息 + protected function sendHttpStatus($status) { + static $_status = [ + // Informational 1xx + 100 => 'Continue', + 101 => 'Switching Protocols', + // Success 2xx + 200 => 'OK', + 201 => 'Created', + 202 => 'Accepted', + 203 => 'Non-Authoritative Information', + 204 => 'No Content', + 205 => 'Reset Content', + 206 => 'Partial Content', + // Redirection 3xx + 300 => 'Multiple Choices', + 301 => 'Moved Permanently', + 302 => 'Moved Temporarily ', // 1.1 + 303 => 'See Other', + 304 => 'Not Modified', + 305 => 'Use Proxy', + // 306 is deprecated but reserved + 307 => 'Temporary Redirect', + // Client Error 4xx + 400 => 'Bad Request', + 401 => 'Unauthorized', + 402 => 'Payment Required', + 403 => 'Forbidden', + 404 => 'Not Found', + 405 => 'Method Not Allowed', + 406 => 'Not Acceptable', + 407 => 'Proxy Authentication Required', + 408 => 'Request Timeout', + 409 => 'Conflict', + 410 => 'Gone', + 411 => 'Length Required', + 412 => 'Precondition Failed', + 413 => 'Request Entity Too Large', + 414 => 'Request-URI Too Long', + 415 => 'Unsupported Media Type', + 416 => 'Requested Range Not Satisfiable', + 417 => 'Expectation Failed', + // Server Error 5xx + 500 => 'Internal Server Error', + 501 => 'Not Implemented', + 502 => 'Bad Gateway', + 503 => 'Service Unavailable', + 504 => 'Gateway Timeout', + 505 => 'HTTP Version Not Supported', + 509 => 'Bandwidth Limit Exceeded' + ]; + if(isset($_status[$code])) { + header('HTTP/1.1 '.$code.' '.$_status[$code]); + // 确保FastCGI模式下正常 + header('Status:'.$code.' '.$_status[$code]); + } + } + + /** + * 获取当前请求的Accept头信息 + * @return string + */ + protected function getAcceptType(){ + $type = [ + 'html' => 'text/html,application/xhtml+xml,*/*', + 'xml' => 'application/xml,text/xml,application/x-xml', + 'json' => 'application/json,text/x-json,application/jsonrequest,text/json', + 'js' => 'text/javascript,application/javascript,application/x-javascript', + 'css' => 'text/css', + 'rss' => 'application/rss+xml', + 'yaml' => 'application/x-yaml,text/yaml', + 'atom' => 'application/atom+xml', + 'pdf' => 'application/pdf', + 'text' => 'text/plain', + 'png' => 'image/png', + 'jpg' => 'image/jpg,image/jpeg,image/pjpeg', + 'gif' => 'image/gif', + 'csv' => 'text/csv' + ]; + + foreach($type as $key=>$val){ + $array = explode(',',$val); + foreach($array as $k=>$v){ + if(stristr($_SERVER['HTTP_ACCEPT'], $v)) { + return $key; + } + } + } + return false; + } +} \ No newline at end of file diff --git a/Think/Route.php b/Think/Route.php new file mode 100644 index 00000000..0ada3048 --- /dev/null +++ b/Think/Route.php @@ -0,0 +1,190 @@ + +// +---------------------------------------------------------------------- +// $Id$ +namespace Think; +class Route { + + static public function check($regx,$rules) { + // 优先检测是否存在PATH_INFO + if(empty($regx)) return true; + // 路由处理 + if(!empty($rules)) { + // 分隔符替换 确保路由定义使用统一的分隔符 + $regx = str_replace(Config::get('pathinfo_depr'),'/',$regx); + foreach ($rules as $rule=>$route){ + if(0===strpos($rule,'/') && preg_match($rule,$regx,$matches)) { // 正则路由 + return self::parseRegex($matches,$route,$regx); + }else{ // 规则路由 + $len1 = substr_count($regx,'/'); + $len2 = substr_count($rule,'/'); + if($len1>=$len2) { + if('$' == substr($rule,-1,1)) {// 完整匹配 + if($len1 != $len2) { + continue; + }else{ + $rule = substr($rule,0,-1); + } + } + if(self::checkUrlMatch($regx,$rule)){ + return self::parseRule($rule,$route,$regx); + } + } + } + } + } + return false; + } + + // 检测URL和规则路由是否匹配 + static private function checkUrlMatch($regx,$rule) { + $m1 = explode('/',$regx); + $m2 = explode('/',$rule); + foreach ($m2 as $key=>$val){ + if(':' == substr($val,0,1)) {// 动态变量 + if(strpos($val,'\\')) { + $type = substr($val,-1); + if('d'==$type && !is_numeric($m1[$key])) { + return false; + } + }elseif(strpos($val,'^')){ + $array = explode('|',substr(strstr($val,'^'),1)); + if(in_array($m1[$key],$array)) { + return false; + } + } + }elseif(0 !== strcasecmp($val,$m1[$key])){ + return false; + } + } + return true; + } + + // 解析规范的路由地址 + // 地址格式 [模块/控制器/操作?]参数1=值1&参数2=值2... + static private function parseUrl($url) { + $var = []; + if(false !== strpos($url,'?')) { // [模块/控制器/操作?]参数1=值1&参数2=值2... + $info = parse_url($url); + $path = explode('/',$info['path']); + parse_str($info['query'],$var); + }elseif(strpos($url,'/')){ // [模块/控制器/操作] + $path = explode('/',$url); + }else{ // 参数1=值1&参数2=值2... + parse_str($url,$var); + } + if(isset($path)) { + $_GET[Config::get('var_action')] = array_pop($path); + if(!empty($path)) { + $_GET[Config::get('var_controll')] = array_pop($path); + } + if(!empty($path)) { + $_GET[Config::get('var_module')] = array_pop($path); + } + } + return $var; + } + + // 解析规则路由 + // '路由规则'=>'[模块/控制器/操作]?额外参数1=值1&额外参数2=值2...' + // '路由规则'=>array('[模块/控制器/操作]','额外参数1=值1&额外参数2=值2...') + // '路由规则'=>'外部地址' + // '路由规则'=>array('外部地址','重定向代码') + // 路由规则中 :开头 表示动态变量 + // 外部地址中可以用动态变量 采用 :1 :2 的方式 + // 'news/:month/:day/:id'=>array('News/read?cate=1','status=1'), + // 'new/:id'=>array('/new.php?id=:1',301), 重定向 + static private function parseRule($rule,$route,$regx) { + // 获取路由地址规则 + $url = is_array($route)?$route[0]:$route; + // 获取URL地址中的参数 + $paths = explode('/',$regx); + // 解析路由规则 + $matches = []; + $rule = explode('/',$rule); + foreach ($rule as $item){ + if(0===strpos($item,':')) { // 动态变量获取 + if($pos = strpos($item,'^') ) { + $var = substr($item,1,$pos-1); + }elseif(strpos($item,'\\')){ + $var = substr($item,1,-2); + }else{ + $var = substr($item,1); + } + $matches[$var] = array_shift($paths); + }else{ // 过滤URL中的静态变量 + array_shift($paths); + } + } + if(0=== strpos($url,'/') || 0===strpos($url,'http')) { // 路由重定向跳转 + if(strpos($url,':')) { // 传递动态参数 + $values = array_values($matches); + $url = preg_replace('/:(\d+)/e','$values[\\1-1]',$url); + } + header("Location: $url", true,(is_array($route) && isset($route[1]))?$route[1]:301); + exit; + }else{ + // 解析路由地址 + $var = self::parseUrl($url); + // 解析路由地址里面的动态参数 + $values = array_values($matches); + foreach ($var as $key=>$val){ + if(0===strpos($val,':')) { + $var[$key] = $values[substr($val,1)-1]; + } + } + $var = array_merge($matches,$var); + // 解析剩余的URL参数 + if($paths) { + preg_replace('@(\w+)\/([^\/]+)@e', '$var[strtolower(\'\\1\')]=strip_tags(\'\\2\');', implode('/',$paths)); + } + // 解析路由自动传人参数 + if(is_array($route) && isset($route[1])) { + parse_str($route[1],$params); + $var = array_merge($var,$params); + } + $_GET = array_merge($var,$_GET); + } + return true; + } + + // 解析正则路由 + // '路由正则'=>'[模块/控制器/操作]?参数1=值1&参数2=值2...' + // '路由正则'=>array('[模块/控制器/操作]?参数1=值1&参数2=值2...','额外参数1=值1&额外参数2=值2...') + // '路由正则'=>'外部地址' + // '路由正则'=>array('外部地址','重定向代码') + // 参数值和外部地址中可以用动态变量 采用 :1 :2 的方式 + // '/new\/(\d+)\/(\d+)/'=>array('News/read?id=:1&page=:2&cate=1','status=1'), + // '/new\/(\d+)/'=>array('/new.php?id=:1&page=:2&status=1','301'), 重定向 + static private function parseRegex($matches,$route,$regx) { + // 获取路由地址规则 + $url = is_array($route)?$route[0]:$route; + $url = preg_replace('/:(\d+)/e','$matches[\\1]',$url); + if(0=== strpos($url,'/') || 0===strpos($url,'http')) { // 路由重定向跳转 + header("Location: $url", true,(is_array($route) && isset($route[1]))?$route[1]:301); + exit; + }else{ + // 解析路由地址 + $var = self::parseUrl($url); + // 解析剩余的URL参数 + $regx = substr_replace($regx,'',0,strlen($matches[0])); + if($regx) { + preg_replace('@(\w+)\/([^,\/]+)@e', '$var[strtolower(\'\\1\')]=strip_tags(\'\\2\');', $regx); + } + // 解析路由自动传人参数 + if(is_array($route) && isset($route[1])) { + parse_str($route[1],$params); + $var = array_merge($var,$params); + } + $_GET = array_merge($var,$_GET); + } + return true; + } +} \ No newline at end of file diff --git a/Think/Session.php b/Think/Session.php new file mode 100644 index 00000000..3990abf3 --- /dev/null +++ b/Think/Session.php @@ -0,0 +1,166 @@ + +// +---------------------------------------------------------------------- +// $Id$ +namespace Think; + +class Session { + static protected $prefix = ''; + + /** + * 设置或者获取session作用域(前缀) + * @param string $prefix + * @return string|void + */ + static public function prefix($prefix=''){ + if(empty($prefix)) { + return self::$config['prefix']; + }else{ + self::$config['prefix'] = $prefix; + } + } + + /** + * session初始化 + * @param array $config + * @return void + */ + static public function init($config=[]) { + if(isset($config['prefix'])) self::$prefix = $config['prefix']; + if(isset($config['var_session_id']) && isset($_REQUEST[$config['var_session_id']])){ + session_id($_REQUEST[$config['var_session_id']]); + }elseif(isset($config['id'])) { + session_id($config['id']); + } + ini_set('session.auto_start', 0); + if(isset($config['name'])) session_name($config['name']); + if(isset($config['path'])) session_save_path($config['path']); + if(isset($config['domain'])) ini_set('session.cookie_domain', $config['domain']); + if(isset($config['expire'])) ini_set('session.gc_maxlifetime', $config['expire']); + if(isset($config['use_trans_sid'])) ini_set('session.use_trans_sid', $config['use_trans_sid']?1:0); + if(isset($config['use_cookies'])) ini_set('session.use_cookies', $config['use_cookies']?1:0); + if(isset($config['cache_limiter'])) session_cache_limiter($config['cache_limiter']); + if(isset($config['cache_expire'])) session_cache_expire($config['cache_expire']); + if(!empty($config['type'])) { // 读取session驱动 + $class = 'Think\\Session\\Driver\\'. ucwords(strtolower($config['type'])); + // 检查驱动类 + if(class_exists($class)) { + $hander = new $class(); + $hander->execute(); + }else { + // 类没有定义 + throw_exception(Lang::get('_CLASS_NOT_EXIST_').': ' . $class); + } + } + // 启动session + if($config['auto_start']) session_start(); + } + + /** + * session设置 + * @param string $name session名称 + * @param mixed $value session值 + * @param string $prefix 作用域(前缀) + * @return void + */ + static public function set($name,$value='',$prefix='') { + $prefix = $prefix?$prefix:self::$prefix; + if($prefix){ + if (!is_array($_SESSION[$prefix])) { + $_SESSION[$prefix] = []; + } + $_SESSION[$prefix][$name] = $value; + }else{ + $_SESSION[$name] = $value; + } + } + + /** + * session获取 + * @param string $name session名称 + * @param string $prefix 作用域(前缀) + * @return mixed + */ + static public function get($name,$prefix='') { + $prefix = $prefix?$prefix:self::$prefix; + if($prefix){ // 获取session + return isset($_SESSION[$prefix][$name])?$_SESSION[$prefix][$name]:null; + }else{ + return isset($_SESSION[$name])?$_SESSION[$name]:null; + } + } + + /** + * 删除session数据 + * @param string $name session名称 + * @param string $prefix 作用域(前缀) + * @return void + */ + static public function delete($name,$prefix='') { + $prefix = $prefix?$prefix:$this->prefix; + if($prefix){ + unset($_SESSION[$prefix][$name]); + }else{ + unset($_SESSION[$name]); + } + } + + /** + * 清空session数据 + * @param string $prefix 作用域(前缀) + * @return void + */ + static public function clear($prefix='') { + $prefix = $prefix?$prefix:self::$prefix; + if($prefix) { + unset($_SESSION[$prefix]); + }else{ + $_SESSION = []; + } + } + + /** + * 判断session数据 + * @param string $name session名称 + * @param mixed $value session值 + * @return boolean + */ + static public function has($name,$prefix='') { + $prefix = $prefix?$prefix:self::$prefix; + if($prefix){ + return isset($_SESSION[$prefix][$name]); + }else{ + return isset($_SESSION[$name]); + } + } + + /** + * session管理 + * @param string $name session操作名称 + * @return void + */ + static public function operate($name) { + if('pause'==$name){ // 暂停session + session_write_close(); + }elseif('start'==$name){ // 启动session + session_start(); + }elseif('destroy'==$name){ // 销毁session + $_SESSION = []; + session_unset(); + session_destroy(); + }elseif('regenerate'==$name){ // 重新生成id + session_regenerate_id(); + } + } + + static public function __callStatic($name,$args) { + self::operate($name); + } +} \ No newline at end of file diff --git a/Think/Tag.php b/Think/Tag.php new file mode 100644 index 00000000..668ba09c --- /dev/null +++ b/Think/Tag.php @@ -0,0 +1,90 @@ + +// +---------------------------------------------------------------------- +// $Id$ +namespace Think; +class Tag { + + static private $tags = []; + + /** + * 给某个tag注册行为 + * @param string $tag 标签名称 + * @param array $behaviors 行为集 + * @return void + */ + public static function register($tag,$behaviors) { + if(isset(self::$tags[$tag])) { + self::$tags[$tag] = array_merge(self::$tags[$tag],$behaviors); + }else{ + self::$tags[$tag] = $behaviors; + } + } + + /** + * 动态添加行为扩展到某个标签 + * @param string $tag 标签名称 + * @param mixed $behavior 行为名称 + * @return void + */ + public static function add($tag,$behavior,$range='') { + $range = $range?$range:ucwords(MODULE_NAME); + self::$tags[$tag][] = [$behavior,$range]; + } + + /** + * 批量导入行为 + * @param array $tags 标签行为 + * @return void + */ + public static function import($tags) { + self::$tags = array_merge(self::$tags,$tags); + } + + /** + * 监听标签的行为 + * @param string $tag 标签名称 + * @param mixed $params 传入参数 + * @return void + */ + public static function listen($tag, &$params=NULL) { + Log::record($tag,'INFO'); + Debug::remark($tag,'time'); + if(isset(self::$tags[$tag])) { + foreach (self::$tags[$tag] as $val) { + Debug::remark($val[0].'start','time'); + $result = self::exec($val[0], $params,$val[1]); + Log::record('Run '.$val[0].' Behavior [ RunTime:'.Debug::getUseTime($val[0].'start',$val[0].'end').'s ]','INFO'); + if(false === $result) { + // 如果返回false 则中断行为执行 + return ; + } + + } + } + return; + } + + /** + * 执行某个行为 + * @param string $name 行为名称 + * @param Mixed $params 传人的参数 + * @return void + */ + public static function exec($name, &$params=NULL,$range='') { + $class = '\\'.$range.'\Behavior\\'.$name; + if(class_exists($class)) { + $behavior = new $class(); + return $behavior->run($params); + } + return ; + } + +} \ No newline at end of file diff --git a/Think/Template.php b/Think/Template.php new file mode 100644 index 00000000..8020388e --- /dev/null +++ b/Think/Template.php @@ -0,0 +1,988 @@ + +// +---------------------------------------------------------------------- + +namespace Think; + +/** + * ThinkPHP分离出来的模板引擎 + * 支持XML标签和普通标签的模板解析 + * 编译型模板引擎 支持动态缓存 + */ +class Template { + protected $tVar = []; // 模板变量 + protected $config = [ // 引擎配置 + 'tpl_path' => '', + 'tpl_suffix' => '.html', // 默认模板文件后缀 + 'cache_suffix' => '.php', // 默认模板缓存后缀 + 'tpl_deny_func_list' => 'echo,exit', // 模板引擎禁用函数 + 'tpl_deny_php' => false, // 默认模板引擎是否禁用PHP原生代码 + 'tpl_begin' => '{', // 模板引擎普通标签开始标记 + 'tpl_end' => '}', // 模板引擎普通标签结束标记 + 'strip_space' => false, // 是否去除模板文件里面的html空格与换行 + 'tpl_cache' => true, // 是否开启模板编译缓存,设为false则每次都会重新编译 + 'cache_type' => 'file', + 'cache_path' => '', + 'cache_prefix' => '', // 模板缓存前缀标识,可以动态改变 + 'cache_time' => 0, // 模板缓存有效期 0 为永久,(以数字为值,单位:秒) + 'layout_item' => '{__CONTENT__}', // 布局模板的内容替换标识 + 'taglib_begin' => '<', // 标签库标签开始标记 + 'taglib_end' => '>', // 标签库标签结束标记 + 'taglib_load' => true, // 是否使用内置标签库之外的其它标签库,默认自动检测 + 'taglib_build_in' => 'cx', // 内置标签库名称(标签使用不必指定标签库名称),以逗号分隔 注意解析顺序 + 'taglib_pre_load' => '', // 需要额外加载的标签库(须指定标签库名称),多个以逗号分隔 + 'display_cache' => false, + ]; + + private $literal = []; + private $block = []; + protected $handler = null; + + /** + * 架构函数 + * @access public + */ + public function __construct($config=[]){ + if(!empty($config)) { + $this->config = array_merge($this->config,$config); + } + $this->config['taglib_begin'] = $this->stripPreg($this->config['taglib_begin']); + $this->config['taglib_end'] = $this->stripPreg($this->config['taglib_end']); + $this->config['tpl_begin'] = $this->stripPreg($this->config['tpl_begin']); + $this->config['tpl_end'] = $this->stripPreg($this->config['tpl_end']); + if($this->config['cache_type']) { + Cache::connect($this->config['cache_options']); + } + } + + /** + * 字符串替换 避免正则混淆 + * @access private + * @param string $str + */ + private function stripPreg($str) { + return str_replace( + ['{','}','(',')','|','[',']','-','+','*','.','^','?'], + ['\{','\}','\(','\)','\|','\[','\]','\-','\+','\*','\.','\^','\?'], + $str); + } + + /** + * 模板变量赋值 + * @access public + * @param mixed $name + * @param mixed $value + */ + public function assign($name,$value=''){ + if(is_array($name)) { + $this->tVar = array_merge($this->tVar,$name); + }else { + $this->tVar[$name] = $value; + } + } + + /** + * 模板引擎参数赋值 + * @access public + * @param mixed $name + * @param mixed $value + */ + public function __set($name,$value){ + $this->config[$name] = $value; + } + + public function get($name){ + return $this->tVar[$name]; + } + /** + * 渲染模板文件 + * @access public + * @param string $template 模板文件 + * @param array $var 模板变量 + * @param string $prefix 模板标识前缀 + * @return void + */ + public function display($template,$prefix='',$cacheId='') { + $prefix = $prefix ? $prefix : $this->config['cache_prefix']; + $template = $this->parseTemplateFile($template); + $cacheFile = $this->config['cache_path'].$prefix.md5($template).$this->config['cache_suffix']; + if(!$this->checkCache($template,$cacheFile)) { // 缓存无效 + // 模板编译 + $this->compiler(file_get_contents($template),$cacheFile); + } + // 页面缓存 + ob_start(); + ob_implicit_flush(0); + // 模板阵列变量分解成为独立变量 + extract($this->tVar, EXTR_OVERWRITE); + if($this->handler) { + echo substr(ThinkCache::get(md5($cacheFile)),12); + }else{ + //载入模版缓存文件 + include $cacheFile; + } + // 获取并清空缓存 + $content = ob_get_clean(); + if($cacheId && $this->config['display_cache'] && $this->handler) { + // 缓存页面输出 + Cache::set($cacheId,$content,$this->config['cache_time']); + } + echo $content; + } + + /** + * 渲染模板内容 + * @access public + * @param string $content 模板内容 + * @param array $var 模板变量 + * @param string $prefix 模板标识前缀 + * @return void + */ + public function fetch($content,$prefix='') { + $prefix = $prefix ? $prefix : $this->config['cache_prefix']; + $cacheFile = $this->config['cache_path'].$prefix.md5($content).$this->config['cache_suffix']; + if(!$this->checkCache($content,$cacheFile)) { // 缓存无效 + // 模板编译 + $this->compiler($content,$cacheFile); + } + // 模板阵列变量分解成为独立变量 + extract($this->tVar, EXTR_OVERWRITE); + if($this->handler) { + echo substr(Cache::get(md5($cacheFile)),12); + }else{ + //载入模版缓存文件 + include $cacheFile; + } + } + + /** + * 检查缓存文件是否有效 + * 如果无效则需要重新编译 + * @access private + * @param string $template 模板文件名 + * @param string $cacheFile 缓存文件名 + * @return boolen + */ + private function checkCache($template,$cacheFile) { + if (!$this->config['tpl_cache']) // 优先对配置设定检测 + return false; + if($this->handler) { + $data = Cache::get(md5($cacheFile)); + if(!$data) { + return false; + }elseif(is_file($template) && filemtime($template) > (int)substr($data,0, 12) ) { + return false; + } + }else{ + if(!is_file($cacheFile)|| (is_file($template) && filemtime($template) > filemtime($cacheFile))) { + // 模板文件如果有更新则缓存需要更新 + return false; + }elseif ($this->config['cache_time'] != 0 && time() > filemtime($cacheFile)+$this->config['cache_time']) { + // 缓存是否在有效期 + return false; + } + } + // 缓存有效 + return true; + } + + public function isCache($cacheId){ + if($cacheId && $this->config['display_cache'] && $this->handler) { + // 缓存页面输出 + return Cache::get($cacheId)?true:false; + } + return null; + } + + /** + * 编译模板文件内容 + * @access private + * @param string $content 模板内容 + * @param string $cacheFile 缓存文件名 + * @return void + */ + private function compiler($content,$cacheFile) { + // 模板解析 + $content = $this->parse($content); + // 还原被替换的Literal标签 + $content = preg_replace('//eis',"\$this->restoreLiteral('\\1')",$content); + // 添加安全代码 + $content = ''.$content; + if($this->config['strip_space']) { + /* 去除html空格与换行 */ + $find = ['~>\s+<~','~>(\s+\n|\r)~']; + $replace = ['><','>']; + $content = preg_replace($find, $replace, $content); + } + // 优化生成的php代码 + $content = str_replace('?>config['cache_type']) { + Cache::set(md5($cacheFile),sprintf('%012d',$_SERVER['REQUEST_TIME']).$content,$this->config['cache_time']); + }else{ + // 检测模板目录 + $dir = dirname($cacheFile); + if(!is_dir($dir)) + mkdir($dir,0755,true); + // 生成模板缓存文件 + if( false === file_put_contents($cacheFile,$content)) + throw_exception('_CACHE_WRITE_ERROR_:'.$cacheFile); + } + return ; + } + + /** + * 模板解析入口 + * 支持普通标签和TagLib解析 支持自定义标签库 + * @access public + * @param string $content 要解析的模板内容 + * @return string + */ + public function parse($content) { + // 内容为空不解析 + if(empty($content)) return ''; + $begin = $this->config['taglib_begin']; + $end = $this->config['taglib_end']; + // 检查include语法 + $content = $this->parseInclude($content); + // 检查PHP语法 + $content = $this->parsePhp($content); + // 首先替换literal标签内容 + $content = preg_replace('/'.$begin.'literal'.$end.'(.*?)'.$begin.'\/literal'.$end.'/eis',"\$this->parseLiteral('\\1')",$content); + + // 获取需要引入的标签库列表 + // 标签库只需要定义一次,允许引入多个一次 + // 一般放在文件的最前面 + // 格式: + // 当TAGLIB_LOAD配置为true时才会进行检测 + if($this->config['taglib_load']) { + $tagLibs = $this->getIncludeTagLib($content); + if(!empty($tagLibs)) { + // 对导入的TagLib进行解析 + foreach($tagLibs as $tagLibName) { + $this->parseTagLib($tagLibName,$content); + } + } + } + // 预先加载的标签库 无需在每个模板中使用taglib标签加载 但必须使用标签库XML前缀 + if($this->config['taglib_pre_load']) { + $tagLibs = explode(',',$this->config['taglib_pre_load']); + foreach ($tagLibs as $tag){ + $this->parseTagLib($tag,$content); + } + } + // 内置标签库 无需使用taglib标签导入就可以使用 并且不需使用标签库XML前缀 + $tagLibs = explode(',',$this->config['taglib_build_in']); + foreach ($tagLibs as $tag){ + $this->parseTagLib($tag,$content,true); + } + // 解析普通模板标签 {tagName} + $content = preg_replace('/('.$this->config['tpl_begin'].')([^\d\s'.$this->config['tpl_begin'].$this->config['tpl_end'].'].+?)('.$this->config['tpl_end'].')/eis',"\$this->parseTag('\\2','\\0')",$content); + return $content; + } + + // 检查PHP语法 + private function parsePhp($content) { + if(ini_get('short_open_tag')){ + // 开启短标签的情况要将'."\n", $content ); + } + // PHP语法检查 + if($this->config['tpl_deny_php'] && false !== strpos($content,'parseExtend($content); + // 解析布局 + $content = $this->parseLayout($content); + // 读取模板中的include标签 + $find = preg_match_all('/'.$this->config['taglib_begin'].'include\s(.+?)\s*?\/'.$this->config['taglib_end'].'/is',$content,$matches); + if($find) { + for($i=0;$i<$find;$i++) { + $include = $matches[1][$i]; + $array = $this->parseXmlAttrs($include); + $file = $array['file']; + unset($array['file']); + $content = str_replace($matches[0][$i],$this->parseIncludeItem($file,$array),$content); + } + } + return $content; + } + + // 解析模板中的布局标签 + private function parseLayout($content) { + // 读取模板中的布局标签 + $find = preg_match('/'.$this->config['taglib_begin'].'layout\s(.+?)\s*?\/'.$this->config['taglib_end'].'/is',$content,$matches); + if($find) { + //替换Layout标签 + $content = str_replace($matches[0],'',$content); + //解析Layout标签 + $array = $this->parseXmlAttrs($matches[1]); + // 读取布局模板 + $layoutFile = $this->config['tpl_path'].$array['name'].$this->config['tpl_suffix']; + $replace = isset($array['replace'])?$array['replace']:$this->config['layout_item']; + // 替换布局的主体内容 + $content = str_replace($replace,$content,file_get_contents($layoutFile)); + }else{ + $content = str_replace('{__NOLAYOUT__}','',$content); + } + return $content; + } + + // 解析模板中的extend标签 + private function parseExtend($content) { + $begin = $this->config['taglib_begin']; + $end = $this->config['taglib_end']; + // 读取模板中的继承标签 + $find = preg_match('/'.$begin.'extend\s(.+?)\s*?\/'.$end.'/is',$content,$matches); + if($find) { + //替换extend标签 + $content = str_replace($matches[0],'',$content); + // 记录页面中的block标签 + preg_replace('/'.$begin.'block\sname=(.+?)\s*?'.$end.'(.*?)'.$begin.'\/block'.$end.'/eis',"\$this->parseBlock('\\1','\\2')",$content); + // 读取继承模板 + $array = $this->parseXmlAttrs($matches[1]); + $content = $this->parseTemplateName($array['name']); + // 替换block标签 + $content = preg_replace('/'.$begin.'block\sname=(.+?)\s*?'.$end.'(.*?)'.$begin.'\/block'.$end.'/eis',"\$this->replaceBlock('\\1','\\2')",$content); + }else{ + $content = preg_replace('/'.$begin.'block\sname=(.+?)\s*?'.$end.'(.*?)'.$begin.'\/block'.$end.'/eis',"stripslashes('\\2')",$content); + } + return $content; + } + + /** + * 分析XML属性 + * @access private + * @param string $attrs XML属性字符串 + * @return array + */ + private function parseXmlAttrs($attrs) { + $xml = ''; + $xml = simplexml_load_string($xml); + if(!$xml) + exit('_XML_TAG_ERROR_'); + $xml = (array)($xml->tag->attributes()); + $array = array_change_key_case($xml['@attributes']); + return $array; + } + + /** + * 替换页面中的literal标签 + * @access private + * @param string $content 模板内容 + * @return string + */ + private function parseLiteral($content) { + if(trim($content)=='') return ''; + $content = stripslashes($content); + $i = count($this->literal); + $parseStr = ""; + $this->literal[$i] = $content; + return $parseStr; + } + + /** + * 还原被替换的literal标签 + * @access private + * @param string $tag literal标签序号 + * @return string + */ + private function restoreLiteral($tag) { + // 还原literal标签 + $parseStr = $this->literal[$tag]; + // 销毁literal记录 + unset($this->literal[$tag]); + return $parseStr; + } + + /** + * 记录当前页面中的block标签 + * @access private + * @param string $name block名称 + * @param string $content 模板内容 + * @return string + */ + private function parseBlock($name,$content) { + $this->block[$name] = $content; + return ''; + } + + /** + * 替换继承模板中的block标签 + * @access private + * @param string $name block名称 + * @param string $content 模板内容 + * @return string + */ + private function replaceBlock($name,$content) { + // 替换block标签 没有重新定义则使用原来的 + $replace = isset($this->block[$name])? $this->block[$name] : $content; + return stripslashes($replace); + } + + /** + * 搜索模板页面中包含的TagLib库 + * 并返回列表 + * @access private + * @param string $content 模板内容 + * @return array + */ + private function getIncludeTagLib(& $content) { + //搜索是否有TagLib标签 + $find = preg_match('/'.$this->config['taglib_begin'].'taglib\s(.+?)(\s*?)\/'.$this->config['taglib_end'].'\W/is',$content,$matches); + if($find) { + //替换TagLib标签 + $content = str_replace($matches[0],'',$content); + //解析TagLib标签 + $array = $this->parseXmlAttrs($matches[1]); + return explode(',',$array['name']); + } + return []; + } + + /** + * TagLib库解析 + * @access private + * @param string $tagLib 要解析的标签库 + * @param string $content 要解析的模板内容 + * @param boolen $hide 是否隐藏标签库前缀 + * @return void + */ + protected function parseTagLib($tagLib,&$content,$hide=false) { + $begin = $this->config['taglib_begin']; + $end = $this->config['taglib_end']; + include __DIR__.'/template/driver/'.$tagLib.'.php'; + $className = 'Think\\Template\\Driver\\'.ucwords($tagLib); + $tLib = new $className; + foreach ($tLib->getTags() as $name=>$val){ + $tags = [$name]; + if(isset($val['alias'])) {// 别名设置 + $tags = explode(',',$val['alias']); + $tags[] = $name; + } + $level = isset($val['level'])?$val['level']:1; + $closeTag = isset($val['close'])?$val['close']:true; + foreach ($tags as $tag){ + $parseTag = !$hide? $tagLib.':'.$tag: $tag;// 实际要解析的标签名称 + if(!method_exists($tLib,'_'.$tag)) { + // 别名可以无需定义解析方法 + $tag = $name; + } + $n1 = empty($val['attr'])?'(\s*?)':'\s([^'.$end.']*)'; + if (!$closeTag){ + $patterns = '/'.$begin.$parseTag.$n1.'\/(\s*?)'.$end.'/eis'; + $replacement = "\$this->parseXmlTag(\$tLib,'$tagLib','$tag','$1','')"; + $content = preg_replace($patterns, $replacement,$content); + }else{ + $patterns = '/'.$begin.$parseTag.$n1.$end.'(.*?)'.$begin.'\/'.$parseTag.'(\s*?)'.$end.'/eis'; + $replacement = "\$this->parseXmlTag(\$tLib,'$tagLib','$tag','$1','$2')"; + for($i=0;$i<$level;$i++) + $content=preg_replace($patterns,$replacement,$content); + } + } + } + } + + /** + * 解析标签库的标签 + * 需要调用对应的标签库文件解析类 + * @access private + * @param object $tLib 模板引擎实例 + * @param string $tagLib 标签库名称 + * @param string $tag 标签名 + * @param string $attr 标签属性 + * @param string $content 标签内容 + * @return string + */ + private function parseXmlTag($tLib,$tagLib,$tag,$attr,$content) { + $attr = stripslashes($attr); + $content= stripslashes($content); + if(ini_get('magic_quotes_sybase')) + $attr = str_replace('\"','\'',$attr); + $parse = '_'.$tag; + $content = trim($content); + $tags = $tLib->parseXmlAttr($attr,$tag); + $tLib->tpl = $this; + return $tLib->$parse($tags,$content); + } + + /** + * 模板标签解析 + * 格式: {TagName:args [|content] } + * @access private + * @param string $tagStr 标签内容 + * @param string $content 原始内容 + * @return string + */ + private function parseTag($tagStr,$content){ + $tagStr = stripslashes($tagStr); + + //还原非模板标签 + if(!preg_match('/^[\s|\d]/is',$tagStr)){ + $flag = substr($tagStr,0,1); + $flag2 = substr($tagStr,1,1); + $name = substr($tagStr,1); + if('$' == $flag && '.' != $flag2 && '(' != $flag2){ //解析模板变量 格式 {$varName} + return $this->parseVar($name); + }elseif('-' == $flag || '+'== $flag){ // 输出计算 + return ''; + }elseif(':' == $flag){ // 输出某个函数的结果 + return ''; + }elseif('~' == $flag){ // 执行某个函数 + return ''; + }elseif(substr($tagStr,0,2)=='//' || (substr($tagStr,0,2)=='/*' && substr($tagStr,-2)=='*/')){ + //注释标签 + return ''; + } + } + // 非法标签直接返回 + return $content; + } + + /** + * 模板变量解析,支持使用函数 + * 格式: {$varname|function1|function2=arg1,arg2} + * @access private + * @param string $varStr 变量数据 + * @return string + */ + private function parseVar($varStr){ + $varStr = trim($varStr); + static $_varParseList = []; + //如果已经解析过该变量字串,则直接返回变量值 + if(isset($_varParseList[$varStr])) return $_varParseList[$varStr]; + $parseStr = ''; + if(!empty($varStr)){ + $varArray = explode('|',$varStr); + //取得变量名称 + $var = array_shift($varArray); + if('Think.' == substr($var,0,6)){ + // 所有以Think.打头的以特殊变量对待 无需模板赋值就可以输出 + $name = $this->parseThinkVar($var); + }elseif( false !== strpos($var,'.')) { + //支持 {$var.property} + $vars = explode('.',$var); + $var = array_shift($vars); + $name = '$'.$var; + foreach ($vars as $key=>$val) + $name .= '["'.$val.'"]'; + }elseif(false !== strpos($var,'[')) { + //支持 {$var['key']} 方式输出数组 + $name = "$".$var; + }elseif(false !==strpos($var,':') && false ===strpos($var,'::') && false ===strpos($var,'?')){ + //支持 {$var:property} 方式输出对象的属性 + $vars = explode(':',$var); + $var = str_replace(':','->',$var); + $name = "$".$var; + }else { + $name = "$$var"; + } + //对变量使用函数 + if(count($varArray)>0) + $name = $this->parseVarFunction($name,$varArray); + $parseStr = ''; + } + $_varParseList[$varStr] = $parseStr; + return $parseStr; + } + + /** + * 对模板变量使用函数 + * 格式 {$varname|function1|function2=arg1,arg2} + * @access private + * @param string $name 变量名 + * @param array $varArray 函数列表 + * @return string + */ + private function parseVarFunction($name,$varArray){ + //对变量使用函数 + $length = count($varArray); + //取得模板禁止使用函数列表 + $template_deny_funs = explode(',',$this->config['tpl_deny_func_list']); + for($i=0;$i<$length ;$i++ ){ + $args = explode('=',$varArray[$i],2); + //模板函数过滤 + $fun = strtolower(trim($args[0])); + switch($fun) { + case 'default': // 特殊模板函数 + $name = '('.$name.')?('.$name.'):'.$args[1]; + break; + default: // 通用模板函数 + if(!in_array($fun,$template_deny_funs)){ + if(isset($args[1])){ + if(strstr($args[1],'###')){ + $args[1] = str_replace('###',$name,$args[1]); + $name = "$fun($args[1])"; + }else{ + $name = "$fun($name,$args[1])"; + } + }else if(!empty($args[0])){ + $name = "$fun($name)"; + } + } + } + } + return $name; + } + + /** + * 特殊模板变量解析 + * 格式 以 $Think. 打头的变量属于特殊模板变量 + * @access private + * @param string $varStr 变量字符串 + * @return string + */ + private function parseThinkVar($varStr){ + $vars = explode('.',$varStr); + $vars[1] = strtoupper(trim($vars[1])); + $parseStr = ''; + if(count($vars)>=3){ + $vars[2] = trim($vars[2]); + switch($vars[1]){ + case 'SERVER': + $parseStr = '$_SERVER[\''.strtoupper($vars[2]).'\']';break; + case 'GET': + $parseStr = '$_GET[\''.$vars[2].'\']';break; + case 'POST': + $parseStr = '$_POST[\''.$vars[2].'\']';break; + case 'COOKIE': + if(isset($vars[3])) { + $parseStr = '$_COOKIE[\''.$vars[2].'\'][\''.$vars[3].'\']'; + }else{ + $parseStr = 'cookie(\''.$vars[2].'\')'; + } + break; + case 'SESSION': + if(isset($vars[3])) { + $parseStr = '$_SESSION[\''.$vars[2].'\'][\''.$vars[3].'\']'; + }else{ + $parseStr = 'session(\''.$vars[2].'\')'; + } + break; + case 'ENV': + $parseStr = '$_ENV[\''.strtoupper($vars[2]).'\']';break; + case 'REQUEST': + $parseStr = '$_REQUEST[\''.$vars[2].'\']';break; + case 'CONST': + $parseStr = strtoupper($vars[2]);break; + case 'LANG': + $parseStr = 'L("'.$vars[2].'")';break; + case 'CONFIG': + if(isset($vars[3])) { + $vars[2] .= '.'.$vars[3]; + } + $parseStr = 'C("'.$vars[2].'")';break; + default:break; + } + }else if(count($vars)==2){ + switch($vars[1]){ + case 'NOW': + $parseStr = "date('Y-m-d g:i a',time())"; + break; + case 'VERSION': + $parseStr = 'THINK_TEMPLATE_VERSION'; + break; + case 'LDELIM': + $parseStr = $this->config['tpl_begin']; + break; + case 'RDELIM': + $parseStr = $this->config['tpl_end']; + break; + default: + if(defined($vars[1])) + $parseStr = $vars[1]; + } + } + return $parseStr; + } + + /** + * 加载公共模板并缓存 和当前模板在同一路径,否则使用相对路径 + * @access private + * @param string $tmplPublicName 公共模板文件名 + * @param array $vars 要传递的变量列表 + * @return string + */ + private function parseIncludeItem($tmplPublicName,$vars=[]){ + // 分析模板文件名并读取内容 + $parseStr = $this->parseTemplateName($tmplPublicName); + // 替换变量 + foreach ($vars as $key=>$val) { + $parseStr = str_replace('['.$key.']',$val,$parseStr); + } + // 再次对包含文件进行模板分析 + return $this->parseInclude($parseStr); + } + + /** + * 分析加载的模板文件并读取内容 支持多个模板文件读取 + * @access private + * @param string $tmplPublicName 模板文件名 + * @return string + */ + private function parseTemplateName($templateName){ + if(substr($templateName,0,1)=='$') + //支持加载变量文件名 + $templateName = $this->get(substr($templateName,1)); + $array = explode(',',$templateName); + $parseStr = ''; + foreach ($array as $templateName){ + $template = $this->parseTemplateFile($templateName); + // 获取模板文件内容 + $parseStr .= file_get_contents($template); + } + return $parseStr; + } + + private function parseTemplateFile($template) { + if(false === strpos($template,'.')) { + return $this->config['tpl_path'].$template.$this->config['tpl_suffix']; + }else{ + return $template; + } + } +} + +/** + * ThinkPHP标签库TagLib解析基类 + * @category Think + * @package Think + * @subpackage Template + * @author liu21st + */ +class TagLib { + + /** + * 标签库定义XML文件 + * @var string + * @access protected + */ + protected $xml = ''; + protected $tags = [];// 标签定义 + /** + * 标签库名称 + * @var string + * @access protected + */ + protected $tagLib =''; + + /** + * 标签库标签列表 + * @var string + * @access protected + */ + protected $tagList = []; + + /** + * 标签库分析数组 + * @var string + * @access protected + */ + protected $parse = []; + + /** + * 标签库是否有效 + * @var string + * @access protected + */ + protected $valid = false; + + /** + * 当前模板对象 + * @var object + * @access protected + */ + public $tpl; + + protected $comparison = [' nheq '=>' !== ',' heq '=>' === ',' neq '=>' != ',' eq '=>' == ',' egt '=>' >= ',' gt '=>' > ',' elt '=>' <= ',' lt '=>' < ']; + + /** + * TagLib标签属性分析 返回标签属性数组 + * @access public + * @param string $tagStr 标签内容 + * @return array + */ + public function parseXmlAttr($attr,$tag) { + if(''== trim($attr)) { + return []; + } + //XML解析安全过滤 + $attr = str_replace('&','___', $attr); + $xml = ''; + $xml = simplexml_load_string($xml); + if(!$xml) { + exit('_XML_TAG_ERROR_ : '.$attr); + } + $xml = (array)($xml->tag->attributes()); + $array = array_change_key_case($xml['@attributes']); + if($array) { + $tag = strtolower($tag); + if(isset($this->tags[$tag]['attr'])) { + $attrs = explode(',',$this->tags[$tag]['attr']); + if(isset($this->tags[strtolower($tag)]['must'])){ + $must = explode(',',$this->tags[$tag]['must']); + }else{ + $must = []; + } + foreach($attrs as $name) { + if( isset($array[$name])) { + $array[$name] = str_replace('___','&',$array[$name]); + }elseif(false !== array_search($name,$must)){ + exit('_PARAM_ERROR_:'.$name); + } + } + } + return $array; + } + } + + /** + * 解析条件表达式 + * @access public + * @param string $condition 表达式标签内容 + * @return array + */ + public function parseCondition($condition) { + $condition = str_ireplace(array_keys($this->comparison),array_values($this->comparison),$condition); + $condition = preg_replace('/\$(\w+):(\w+)\s/is','$\\1->\\2 ',$condition); + $condition = preg_replace('/\$(\w+)\.(\w+)\s/is','$\\1["\\2"] ',$condition); + + if(false !== strpos($condition, '$Think')) + $condition = preg_replace('/(\$Think.*?)\s/ies',"\$this->parseThinkVar('\\1');" , $condition); + return $condition; + } + + /** + * 自动识别构建变量 + * @access public + * @param string $name 变量描述 + * @return string + */ + public function autoBuildVar($name) { + if('Think.' == substr($name,0,6)){ + // 特殊变量 + return $this->parseThinkVar($name); + }elseif(strpos($name,'.')) { + $vars = explode('.',$name); + $var = array_shift($vars); + $name = '$'.$var; + foreach ($vars as $key=>$val){ + if(0===strpos($val,'$')) { + $name .= '["{'.$val.'}"]'; + }else{ + $name .= '["'.$val.'"]'; + } + } + }elseif(strpos($name,':')){ + // 额外的对象方式支持 + $name = '$'.str_replace(':','->',$name); + }elseif(!defined($name)) { + $name = '$'.$name; + } + return $name; + } + + /** + * 用于标签属性里面的特殊模板变量解析 + * 格式 以 Think. 打头的变量属于特殊模板变量 + * @access public + * @param string $varStr 变量字符串 + * @return string + */ + public function parseThinkVar($varStr){ + $vars = explode('.',$varStr); + $vars[1] = strtoupper(trim($vars[1])); + $parseStr = ''; + if(count($vars)>=3){ + $vars[2] = trim($vars[2]); + switch($vars[1]){ + case 'SERVER': $parseStr = '$_SERVER[\''.$vars[2].'\']';break; + case 'GET': $parseStr = '$_GET[\''.$vars[2].'\']';break; + case 'POST': $parseStr = '$_POST[\''.$vars[2].'\']';break; + case 'COOKIE': + if(isset($vars[3])) { + $parseStr = '$_COOKIE[\''.$vars[2].'\'][\''.$vars[3].'\']'; + }else{ + $parseStr = '$_COOKIE[\''.$vars[2].'\']'; + } + break; + case 'SESSION': + if(isset($vars[3])) { + $parseStr = '$_SESSION[\''.$vars[2].'\'][\''.$vars[3].'\']'; + }else{ + $parseStr = '$_SESSION[\''.$vars[2].'\']'; + } + break; + case 'ENV': $parseStr = '$_ENV[\''.$vars[2].'\']';break; + case 'REQUEST': $parseStr = '$_REQUEST[\''.$vars[2].'\']';break; + case 'CONST': $parseStr = strtoupper($vars[2]);break; + case 'LANG': + $parseStr = 'L("'.$vars[2].'")';break; + case 'CONFIG': + if(isset($vars[3])) { + $vars[2] .= '.'.$vars[3]; + } + $parseStr = 'C("'.$vars[2].'")';break; + } + }else if(count($vars)==2){ + switch($vars[1]){ + case 'NOW': $parseStr = "date('Y-m-d g:i a',time())";break; + case 'VERSION': $parseStr = 'THINK_VERSION';break; + default: if(defined($vars[1])) $parseStr = $vars[1]; + } + } + return $parseStr; + } + + /** + * 对模板变量使用函数 + * 格式 {$varname|function1|function2=arg1,arg2} + * @access protected + * @param string $name 变量名 + * @param array $varArray 函数列表 + * @return string + */ + protected function parseVarFunction($name,$varArray){ + //对变量使用函数 + $length = count($varArray); + for($i=0;$i<$length ;$i++ ){ + $args = explode('=',$varArray[$i],2); + //模板函数过滤 + $fun = strtolower(trim($args[0])); + switch($fun) { + case 'default': // 特殊模板函数 + $name = '('.$name.')?('.$name.'):'.$args[1]; + break; + default: // 通用模板函数 + if(isset($args[1])){ + if(strstr($args[1],'###')){ + $args[1] = str_replace('###',$name,$args[1]); + $name = "$fun($args[1])"; + }else{ + $name = "$fun($name,$args[1])"; + } + }else if(!empty($args[0])){ + $name = "$fun($name)"; + } + } + } + return $name; + } + + // 获取标签定义 + public function getTags(){ + return $this->tags; + } +} \ No newline at end of file diff --git a/Think/Template/Driver/Attr.php b/Think/Template/Driver/Attr.php new file mode 100644 index 00000000..302ae4ea --- /dev/null +++ b/Think/Template/Driver/Attr.php @@ -0,0 +1,382 @@ + +// +---------------------------------------------------------------------- +// $Id$ +namespace Think\Template\Driver; +// 属性类型标签库 +class Attr extends \Think\TagLib{ + // 标签定义 + protected $tags = array( + // 标签定义: attr 属性列表 close 是否闭合(0 或者1 默认1) alias 标签别名 level 嵌套层次 + 'attr'=>array('attr'=>'var,complex,type,default','close'=>0), + ); + + // + public function _attr($tag,$content){ + $var = $tag['var']; + $default = isset($tag['default'])?$tag['default']:1; + $read = isset($tag['read'])?$tag['read']:false; + if(!empty($tag['type'])) { + $parse = $this->_{$tag['type']}($var); + }else{ + $parse = ''.$this->_string($var,$default,$read); + $parse .= ''.$this->_num($var,$default,$read); + $parse .= ''.$this->_bool($var,$default,$read); + $parse .= ''.$this->_textarea($var,$default,$read); + $parse .= ''.$this->_text($var,$default,$read); + $parse .= ''.$this->_editor($var,$default,$read); + $parse .= ''.$this->_file($var,$read); + $parse .= ''.$this->_files($var,$read); + $parse .= ''.$this->_radio($var,$default,$read); + $parse .= ''.$this->_checkbox($var,$default,$read); + $parse .= ''.$this->_select($var,$default,$read); + $parse .= ''.$this->_image($var,$read); + $parse .= ''.$this->_images($var,$read); + $parse .= ''.$this->_date($var,$default,$read); + $parse .= ''.$this->_zone($var,$read); + $parse .= ''.$this->_html($var,$read); + $parse .= ''.$this->_dynamic($var,$read); + $parse .= ''.$this->_hidden($var,$read); + $parse .= ''.$this->_verify($var,$read); + $parse .= ''.$this->_password($var,$default,$read); + $parse .= ''.$this->_serialize($var,$read); + $parse .= ''.$this->_link($var,$read); + if(!empty($tag['complex'])) { + $parse .= ''.$this->_complex($var,$read); + } + $parse .= ''; + } + return $parse; + } + + // 验证码类型 + protected function _verify($var) { + $parse = ' '; + return $parse; + } + + // 密码类型 + protected function _password($var,$default) { + $value = $default?'{$'.$var.'.value}':''; + $parse = ' '; + return $parse; + } + + // 字符串类型 + protected function _string($var,$default,$read) { + $value = $default?'{$'.$var.'.value}':''; + if($read) { + $parse = $value; + }else{ + $parse = ' '; + } + return $parse; + } + + // 字符串类型 + protected function _link($var,$read) { + $value = '{$'.$var.'.value}'; + if($read) { + $parse = $value; + }else{ + $parse = ' '; + } + return $parse; + } + + // 数字类型 + protected function _num($var,$default,$read) { + $value = $default?'{$'.$var.'.value}':''; + if($read) { + $parse = $value; + }else{ + $parse = ' '; + } + return $parse; + } + + // 布尔类型 采用下拉列表模拟 + protected function _bool($var,$default,$read) { + if($read) { + $parse = ''; + $parse = ''; + }else{ + $parse = ' '; + } + return $parse; + } + + // 文本域类型 + protected function _textarea($var,$default,$read) { + $value = $default?'{$'.$var.'.value}':''; + if($read) { + $parse = $value; + }else{ + $parse = ' '; + } + return $parse; + } + + // 文本型 + protected function _text($var,$default,$read) { + $value = $default?'{$'.$var.'.value}':''; + if($read) { + $parse = $value; + }else{ + $parse = ' + '; + } + return $parse; + } + + // 编辑器型 + protected function _editor($var,$default,$read) { + $value = $default?'{$'.$var.'.value}':''; + if($read) { + $parse = $value; + }else{ + $parse = ' '; + } + return $parse; + } + + // 附件上传型 可配置 + protected function _file($var) { + $parse = '
{$'.$var.'.value|extension|showExt} {$'.$var.'.value}
'; + return $parse; + } + + // 多附件型 + protected function _files($var) { + $parse = '
+ $array = explode(\',\',$'.$var.'[\'value\']); + +
+ + + + + + . + + + + + {$attach|extension|showExt} {$attach} + +
+
'; + return $parse; + } + + // 单选型 + // 选项1,选项2,... + // 选项1:显示1,选项2:显示2,... + // @model.id 调用模型 + // :fun 函数 + protected function _radio($var,$default) { + $parse = ' + + $array = explode(\':\',$extra);$value = $array[0];$show = isset($array[1])?$array[1]:$array[0]; + checked':'').' value="{$value}" class="{$'.$var.'.readonly}" {$'.$var.'.readonly} /> {$show} + '; + return $parse; + } + + // 组合字段 + protected function _complex($var,$read) { + if($read) { + $parse = ' '; + }else{ + $parse = ' '; + } + return $parse; + } + + // 多选型 用多选下拉列表模拟 支持函数定义 + // 选项1,选项2,... + // 选项1:显示1,选项2:显示2,... + // @model.id 调用模型 + // :fun 函数 + protected function _checkbox($var,$default) { + $parse = ' + + + + + + + + +
选择{$'.$var.'.title}
+ +
+ + +
+
'; + return $parse; + } + + // 枚举型 采用下拉列表模拟 + // 选项1,选项2,... + // 选项1:显示1,选项2:显示2,... + // @model.id 调用模型 + // :fun 函数 + protected function _select($var,$default,$read) { + if($read) { + $parse = ' + $array = explode(\':\',$option);$value = $array[0];$show = isset($array[1])?$array[1]:$array[0]; + + '; + }else{ + $parse = ' '; + } + return $parse; + } + + // 序列化型 只支持字符串类型 + // 字段名1:显示名称:样式,... + protected function _serialize($var) { + $parse = ' + + $array = explode(\':\',$option);$var = $array[0];$show = isset($array[1])?$array[1]:\'\';$class = isset($array[2])?$array[2]:\'medium\';$value = $'.$var.'[\'value\'][$var]; + {$show} + '; + return $parse; + } + + // 图片型 支持配置 + protected function _image($var,$read) { + if($read) { + $parse = 'parse_str($'.$var.'[\'extra\'],$extra);$array = explode(\',\',$extra[\'thumbPrefix\']); + +   '; + }else{ + $parse = '
parse_str($'.$var.'[\'extra\'],$extra);$array = explode(\',\',$extra[\'thumbPrefix\']); + +   
'; + } + return $parse; + } + + // 多图型 + protected function _images($var) { + $parse = '
+ $array = explode(\',\',$'.$var.'[\'value\']); + +
  
+
'; + return $parse; + } + + // 日期型 + protected function _date($var,$default) { + $parse = ''; + return $parse; + } + + // 动态型 用方法控制输出显示 + protected function _dynamic($var) { + $parse = '$fun = $'.$var.'[\'extra\'];echo $fun($'.$var.'[\'value\']); '; + return $parse; + } + + // 地区联动 + protected function _zone($var) { + $parse = ' + + + + +
'; + return $parse; + } + + // 固定值 采用隐藏字段模拟 + protected function _hidden($var) { + $parse = ' '; + return $parse; + } + + // HTML型 + protected function _html($var) { + $parse = '{$'.$var.'.value}'; + return $parse; + } +} +?> \ No newline at end of file diff --git a/Think/Template/Driver/Cx.php b/Think/Template/Driver/Cx.php new file mode 100644 index 00000000..497b87f2 --- /dev/null +++ b/Think/Template/Driver/Cx.php @@ -0,0 +1,614 @@ + +// +---------------------------------------------------------------------- + +namespace Think\Template\Driver; +use Think\TagLib; +/** + * CX标签库解析类 + * @category Think + * @package Think + * @subpackage Driver.Taglib + * @author liu21st + */ +class Cx extends TagLib { + + // 标签定义 + protected $tags = array( + // 标签定义: attr 属性列表 close 是否闭合(0 或者1 默认1) alias 标签别名 level 嵌套层次 + 'php' => array(), + 'volist' => array('attr'=>'name,id,offset,length,key,mod','level'=>3,'alias'=>'iterate'), + 'foreach' => array('attr'=>'name,item,key','level'=>3), + 'if' => array('attr'=>'condition','level'=>2), + 'elseif' => array('attr'=>'condition','close'=>0), + 'else' => array('attr'=>'','close'=>0), + 'switch' => array('attr'=>'name','level'=>2), + 'case' => array('attr'=>'value,break'), + 'default' => array('attr'=>'','close'=>0), + 'compare' => array('attr'=>'name,value,type','level'=>3,'alias'=>'eq,equal,notequal,neq,gt,lt,egt,elt,heq,nheq'), + 'range' => array('attr'=>'name,value,type','level'=>3,'alias'=>'in,notin,between,notbetween'), + 'empty' => array('attr'=>'name','level'=>3), + 'notempty' => array('attr'=>'name','level'=>3), + 'present' => array('attr'=>'name','level'=>3), + 'notpresent'=> array('attr'=>'name','level'=>3), + 'defined' => array('attr'=>'name','level'=>3), + 'notdefined'=> array('attr'=>'name','level'=>3), + 'import' => array('attr'=>'file,href,type,value,basepath','close'=>0,'alias'=>'load,css,js'), + 'assign' => array('attr'=>'name,value','close'=>0), + 'define' => array('attr'=>'name,value','close'=>0), + 'for' => array('attr'=>'start,end,name,comparison,step', 'level'=>3), + ); + + /** + * php标签解析 + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function _php($tag,$content) { + $parseStr = ''; + return $parseStr; + } + + /** + * volist标签解析 循环输出数据集 + * 格式: + * + * {user.username} + * {user.email} + * + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string|void + */ + public function _volist($tag,$content) { + $name = $tag['name']; + $id = $tag['id']; + $empty = isset($tag['empty'])?$tag['empty']:''; + $key = !empty($tag['key'])?$tag['key']:'i'; + $mod = isset($tag['mod'])?$tag['mod']:'2'; + // 允许使用函数设定数据集 {$vo.name} + $parseStr = 'autoBuildVar($name); + } + $parseStr .= 'if(is_array('.$name.')): $'.$key.' = 0;'; + if(isset($tag['length']) && '' !=$tag['length'] ) { + $parseStr .= ' $__LIST__ = array_slice('.$name.','.$tag['offset'].','.$tag['length'].',true);'; + }elseif(isset($tag['offset']) && '' !=$tag['offset']){ + $parseStr .= ' $__LIST__ = array_slice('.$name.','.$tag['offset'].',null,true);'; + }else{ + $parseStr .= ' $__LIST__ = '.$name.';'; + } + $parseStr .= 'if( count($__LIST__)==0 ) : echo "'.$empty.'" ;'; + $parseStr .= 'else: '; + $parseStr .= 'foreach($__LIST__ as $key=>$'.$id.'): '; + $parseStr .= '$mod = ($'.$key.' % '.$mod.' );'; + $parseStr .= '++$'.$key.';?>'; + $parseStr .= ($content); + $parseStr .= ''; + + if(!empty($parseStr)) { + return $parseStr; + } + return ; + } + + /** + * foreach标签解析 循环输出数据集 + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string|void + */ + public function _foreach($tag,$content) { + $name = $tag['name']; + $item = $tag['item']; + $key = !empty($tag['key'])?$tag['key']:'key'; + $name = $this->autoBuildVar($name); + $parseStr = '$'.$item.'): ?>'; + $parseStr .= ($content); + $parseStr .= ''; + if(!empty($parseStr)) { + return $parseStr; + } + return ; + } + + /** + * if标签解析 + * 格式: + * + * + * + * + * 表达式支持 eq neq gt egt lt elt == > >= < <= or and || && + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function _if($tag,$content) { + $condition = $this->parseCondition($tag['condition']); + $parseStr = ''.$content.''; + return $parseStr; + } + + /** + * else标签解析 + * 格式:见if标签 + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function _elseif($tag,$content) { + $condition = $this->parseCondition($tag['condition']); + $parseStr = ''; + return $parseStr; + } + + /** + * else标签解析 + * @access public + * @param array $tag 标签属性 + * @return string + */ + public function _else($tag) { + $parseStr = ''; + return $parseStr; + } + + /** + * switch标签解析 + * 格式: + * + * 1 + * 2 + * other + * + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function _switch($tag,$content) { + $name = $tag['name']; + $varArray = explode('|',$name); + $name = array_shift($varArray); + $name = $this->autoBuildVar($name); + if(count($varArray)>0) + $name = $this->parseVarFunction($name,$varArray); + $parseStr = ''.$content.''; + return $parseStr; + } + + /** + * case标签解析 需要配合switch才有效 + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function _case($tag,$content) { + $value = $tag['value']; + if('$' == substr($value,0,1)) { + $varArray = explode('|',$value); + $value = array_shift($varArray); + $value = $this->autoBuildVar(substr($value,1)); + if(count($varArray)>0) + $value = $this->parseVarFunction($value,$varArray); + $value = 'case '.$value.': '; + }elseif(strpos($value,'|')){ + $values = explode('|',$value); + $value = ''; + foreach ($values as $val){ + $value .= 'case "'.addslashes($val).'": '; + } + }else{ + $value = 'case "'.$value.'": '; + } + $parseStr = ''.$content; + $isBreak = isset($tag['break']) ? $tag['break'] : ''; + if('' ==$isBreak || $isBreak) { + $parseStr .= ''; + } + return $parseStr; + } + + /** + * default标签解析 需要配合switch才有效 + * 使用: ddfdf + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function _default($tag) { + $parseStr = ''; + return $parseStr; + } + + /** + * compare标签解析 + * 用于值的比较 支持 eq neq gt lt egt elt heq nheq 默认是eq + * 格式: content + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function _compare($tag,$content,$type='eq') { + $name = $tag['name']; + $value = $tag['value']; + $type = isset($tag['type'])?$tag['type']:$type; + $type = $this->parseCondition(' '.$type.' '); + $varArray = explode('|',$name); + $name = array_shift($varArray); + $name = $this->autoBuildVar($name); + if(count($varArray)>0) + $name = $this->parseVarFunction($name,$varArray); + if('$' == substr($value,0,1)) { + $value = $this->autoBuildVar(substr($value,1)); + }else { + $value = '"'.$value.'"'; + } + $parseStr = ''.$content.''; + return $parseStr; + } + + public function _eq($tag,$content) { + return $this->_compare($tag,$content,'eq'); + } + + public function _equal($tag,$content) { + return $this->_compare($tag,$content,'eq'); + } + + public function _neq($tag,$content) { + return $this->_compare($tag,$content,'neq'); + } + + public function _notequal($tag,$content) { + return $this->_compare($tag,$content,'neq'); + } + + public function _gt($tag,$content) { + return $this->_compare($tag,$content,'gt'); + } + + public function _lt($tag,$content) { + return $this->_compare($tag,$content,'lt'); + } + + public function _egt($tag,$content) { + return $this->_compare($tag,$content,'egt'); + } + + public function _elt($tag,$content) { + return $this->_compare($tag,$content,'elt'); + } + + public function _heq($tag,$content) { + return $this->_compare($tag,$content,'heq'); + } + + public function _nheq($tag,$content) { + return $this->_compare($tag,$content,'nheq'); + } + + /** + * range标签解析 + * 如果某个变量存在于某个范围 则输出内容 type= in 表示在范围内 否则表示在范围外 + * 格式: content + * example: content + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @param string $type 比较类型 + * @return string + */ + public function _range($tag,$content,$type='in') { + $name = $tag['name']; + $value = $tag['value']; + $varArray = explode('|',$name); + $name = array_shift($varArray); + $name = $this->autoBuildVar($name); + if(count($varArray)>0) + $name = $this->parseVarFunction($name,$varArray); + + $type = isset($tag['type'])?$tag['type']:$type; + + if('$' == substr($value,0,1)) { + $value = $this->autoBuildVar(substr($value,1)); + $str = 'is_array('.$value.')?'.$value.':explode(\',\','.$value.')'; + }else{ + $value = '"'.$value.'"'; + $str = 'explode(\',\','.$value.')'; + } + if($type=='between') { + $parseStr = '= $_RANGE_VAR_[0] && '.$name.'<= $_RANGE_VAR_[1]):?>'.$content.''; + }elseif($type=='notbetween'){ + $parseStr = '$_RANGE_VAR_[1]):?>'.$content.''; + }else{ + $fun = ($type == 'in')? 'in_array' : '!in_array'; + $parseStr = ''.$content.''; + } + return $parseStr; + } + + // range标签的别名 用于in判断 + public function _in($tag,$content) { + return $this->_range($tag,$content,'in'); + } + + // range标签的别名 用于notin判断 + public function _notin($tag,$content) { + return $this->_range($tag,$content,'notin'); + } + + public function _between($tag,$content){ + return $this->_range($tag,$content,'between'); + } + + public function _notbetween($tag,$content){ + return $this->_range($tag,$content,'notbetween'); + } + + /** + * present标签解析 + * 如果某个变量已经设置 则输出内容 + * 格式: content + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function _present($tag,$content) { + $name = $tag['name']; + $name = $this->autoBuildVar($name); + $parseStr = ''.$content.''; + return $parseStr; + } + + /** + * notpresent标签解析 + * 如果某个变量没有设置,则输出内容 + * 格式: content + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function _notpresent($tag,$content) { + $name = $tag['name']; + $name = $this->autoBuildVar($name); + $parseStr = ''.$content.''; + return $parseStr; + } + + /** + * empty标签解析 + * 如果某个变量为empty 则输出内容 + * 格式: content + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function _empty($tag,$content) { + $name = $tag['name']; + $name = $this->autoBuildVar($name); + $parseStr = ''.$content.''; + return $parseStr; + } + + public function _notempty($tag,$content) { + $name = $tag['name']; + $name = $this->autoBuildVar($name); + $parseStr = ''.$content.''; + return $parseStr; + } + + /** + * 判断是否已经定义了该常量 + * 已定义 + * @param $tag + * @param $content + * @return string + */ + public function _defined($tag,$content) { + $name = $tag['name']; + $parseStr = ''.$content.''; + return $parseStr; + } + + public function _notdefined($tag,$content) { + $name = $tag['name']; + $parseStr = ''.$content.''; + return $parseStr; + } + + /** + * import 标签解析 + * + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @param boolean $isFile 是否文件方式 + * @param string $type 类型 + * @return string + */ + public function _import($tag,$content,$isFile=false,$type='') { + $file = isset($tag['file'])?$tag['file']:$tag['href']; + $parseStr = ''; + $endStr = ''; + // 判断是否存在加载条件 允许使用函数判断(默认为isset) + if (isset($tag['value'])) { + $varArray = explode('|',$tag['value']); + $name = array_shift($varArray); + $name = $this->autoBuildVar($name); + if (!empty($varArray)) + $name = $this->parseVarFunction($name,$varArray); + else + $name = 'isset('.$name.')'; + $parseStr .= ''; + $endStr = ''; + } + if($isFile) { + // 根据文件名后缀自动识别 + $type = $type?$type:(!empty($tag['type'])?strtolower($tag['type']):null); + // 文件方式导入 + $array = explode(',',$file); + foreach ($array as $val){ + if (!$type || isset($reset)) { + $type = $reset = strtolower(substr(strrchr($val, '.'),1)); + } + switch($type) { + case 'js': + $parseStr .= ''; + break; + case 'css': + $parseStr .= ''; + break; + case 'php': + $parseStr .= ''; + break; + } + } + }else{ + // 命名空间导入模式 默认是js + $type = $type?$type:(!empty($tag['type'])?strtolower($tag['type']):'js'); + $basepath = !empty($tag['basepath'])?$tag['basepath']:__ROOT__.'/Public'; + // 命名空间方式导入外部文件 + $array = explode(',',$file); + foreach ($array as $val){ + list($val,$version) = explode('?',$val); + switch($type) { + case 'js': + $parseStr .= ''; + break; + case 'css': + $parseStr .= ''; + break; + case 'php': + $parseStr .= ''; + break; + } + } + } + return $parseStr.$endStr; + } + + // import别名 采用文件方式加载(要使用命名空间必须用import) 例如 + public function _load($tag,$content) { + return $this->_import($tag,$content,true); + } + + // import别名使用 导入css文件 + public function _css($tag,$content) { + return $this->_import($tag,$content,true,'css'); + } + + // import别名使用 导入js文件 + public function _js($tag,$content) { + return $this->_import($tag,$content,true,'js'); + } + + /** + * assign标签解析 + * 在模板中给某个变量赋值 支持变量赋值 + * 格式: + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function _assign($tag,$content) { + $name = $this->autoBuildVar($tag['name']); + if('$'==substr($tag['value'],0,1)) { + $value = $this->autoBuildVar(substr($tag['value'],1)); + }else{ + $value = '\''.$tag['value']. '\''; + } + $parseStr = ''; + return $parseStr; + } + + /** + * define标签解析 + * 在模板中定义常量 支持变量赋值 + * 格式: + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function _define($tag,$content) { + $name = '\''.$tag['name']. '\''; + if('$'==substr($tag['value'],0,1)) { + $value = $this->autoBuildVar(substr($tag['value'],1)); + }else{ + $value = '\''.$tag['value']. '\''; + } + $parseStr = ''; + return $parseStr; + } + + /** + * for标签解析 + * 格式: + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function _for($tag, $content){ + //设置默认值 + $start = 0; + $end = 0; + $step = 1; + $comparison = 'lt'; + $name = 'i'; + $rand = rand(); //添加随机数,防止嵌套变量冲突 + //获取属性 + foreach ($tag as $key => $value){ + $value = trim($value); + if(':'==substr($value,0,1)) + $value = substr($value,1); + elseif('$'==substr($value,0,1)) + $value = $this->autoBuildVar(substr($value,1)); + switch ($key){ + case 'start': + $start = $value; break; + case 'end' : + $end = $value; break; + case 'step': + $step = $value; break; + case 'comparison': + $comparison = $value; break; + case 'name': + $name = $value; break; + } + } + + $parseStr = 'parseCondition('$'.$name.' '.$comparison.' $__FOR_END_'.$rand.'__').';$'.$name.'+='.$step.'){ ?>'; + $parseStr .= $content; + $parseStr .= ''; + return $parseStr; + } + + } \ No newline at end of file diff --git a/Think/Template/Driver/Html.php b/Think/Template/Driver/Html.php new file mode 100644 index 00000000..86990818 --- /dev/null +++ b/Think/Template/Driver/Html.php @@ -0,0 +1,570 @@ + +// +---------------------------------------------------------------------- +// $Id: TagLibHtml.class.php 2730 2012-02-12 04:45:34Z liu21st $ + +namespace Think\Template\Driver; +/** + +------------------------------- + * Html标签库驱动 + +------------------------------- + */ +class Html extends \Think\TagLib{ + // 标签定义 + protected $tags = array( + // 标签定义: attr 属性列表 close 是否闭合(0 或者1 默认1) alias 标签别名 level 嵌套层次 + 'editor' => array('attr'=>'id,name,style,width,height,type','close'=>1), + 'select' => array('attr'=>'name,options,values,output,multiple,id,size,first,change,selected,dblclick','close'=>0), + 'grid' => array('attr'=>'id,pk,style,action,actionlist,show,datasource','close'=>0), + 'list' => array('attr'=>'id,pk,style,action,actionlist,show,datasource,checkbox','close'=>0), + 'imagebtn' => array('attr'=>'id,name,value,type,style,click','close'=>0), + 'checkbox' => array('attr'=>'name,checkboxes,checked,separator','close'=>0), + 'radio' => array('attr'=>'name,radios,checked,separator','close'=>0) + ); + + /** + +---------------------------------------------------------- + * editor标签解析 插入可视化编辑器 + * 格式: {$vo.remark} + +---------------------------------------------------------- + * @access public + +---------------------------------------------------------- + * @param string $attr 标签属性 + +---------------------------------------------------------- + * @return string|void + +---------------------------------------------------------- + */ + public function _editor($tag,$content) { + $id = !empty($tag['id'])?$tag['id']: '_editor'; + $name = $tag['name']; + $style = !empty($tag['style'])?$tag['style']:''; + $width = !empty($tag['width'])?$tag['width']: '100%'; + $height = !empty($tag['height'])?$tag['height'] :'320px'; + // $content = $tag['content']; + $type = $tag['type'] ; + switch(strtoupper($type)) { + case 'FCKEDITOR': + $parseStr = ' '; + break; + case 'FCKMINI': + $parseStr = ' '; + break; + case 'EWEBEDITOR': + $parseStr = ""; + break; + case 'NETEASE': + $parseStr = ''; + break; + case 'UBB': + $parseStr = '
'; + break; + case 'KINDEDITOR': + $parseStr = ''; + break; + default : + $parseStr = ''; + } + + return $parseStr; + } + + /** + +---------------------------------------------------------- + * imageBtn标签解析 + * 格式: + +---------------------------------------------------------- + * @access public + +---------------------------------------------------------- + * @param string $attr 标签属性 + +---------------------------------------------------------- + * @return string|void + +---------------------------------------------------------- + */ + public function _imageBtn($tag) { + $name = $tag['name']; //名称 + $value = $tag['value']; //文字 + $id = isset($tag['id'])?$tag['id']:''; //ID + $style = isset($tag['style'])?$tag['style']:''; //样式名 + $click = isset($tag['click'])?$tag['click']:''; //点击 + $type = empty($tag['type'])?'button':$tag['type']; //按钮类型 + + if(!empty($name)) { + $parseStr = '
'; + }else { + $parseStr = '
'; + } + + return $parseStr; + } + + /** + +---------------------------------------------------------- + * imageLink标签解析 + * 格式: + +---------------------------------------------------------- + * @access public + +---------------------------------------------------------- + * @param string $attr 标签属性 + +---------------------------------------------------------- + * @return string|void + +---------------------------------------------------------- + */ + public function _imgLink($tag) { + $name = $tag['name']; //名称 + $alt = $tag['alt']; //文字 + $id = $tag['id']; //ID + $style = $tag['style']; //样式名 + $click = $tag['click']; //点击 + $type = $tag['type']; //点击 + if(empty($type)) { + $type = 'button'; + } + $parseStr = ''; + + return $parseStr; + } + + /** + +---------------------------------------------------------- + * select标签解析 + * 格式: + +---------------------------------------------------------- + * @access public + +---------------------------------------------------------- + * @param string $attr 标签属性 + +---------------------------------------------------------- + * @return string|void + +---------------------------------------------------------- + */ + public function _select($tag) { + $name = $tag['name']; + $options = $tag['options']; + $values = $tag['values']; + $output = $tag['output']; + $multiple = $tag['multiple']; + $id = $tag['id']; + $size = $tag['size']; + $first = $tag['first']; + $selected = $tag['selected']; + $style = $tag['style']; + $ondblclick = $tag['dblclick']; + $onchange = $tag['change']; + + if(!empty($multiple)) { + $parseStr = ''; + } + if(!empty($first)) { + $parseStr .= ''; + } + if(!empty($options)) { + $parseStr .= '$val) { ?>'; + if(!empty($selected)) { + $parseStr .= ''; + $parseStr .= ''; + $parseStr .= ''; + $parseStr .= ''; + }else { + $parseStr .= ''; + } + $parseStr .= ''; + }else if(!empty($values)) { + $parseStr .= ''; + if(!empty($selected)) { + $parseStr .= ''; + $parseStr .= ''; + $parseStr .= ''; + $parseStr .= ''; + }else { + $parseStr .= ''; + } + $parseStr .= ''; + } + $parseStr .= ''; + return $parseStr; + } + + /** + +---------------------------------------------------------- + * checkbox标签解析 + * 格式: + +---------------------------------------------------------- + * @access public + +---------------------------------------------------------- + * @param string $attr 标签属性 + +---------------------------------------------------------- + * @return string|void + +---------------------------------------------------------- + */ + public function _checkbox($tag,$content) { + $name = $tag['name']; + $checkboxes = $tag['checkboxes']; + $checked = $tag['checked']; + $separator = $tag['separator']; + $checkboxes = $this->tpl->get($checkboxes); + $checked = $this->tpl->get($checked)?$this->tpl->get($checked):$checked; + $parseStr = ''; + foreach($checkboxes as $key=>$val) { + if($checked == $key || in_array($key,$checked) ) { + $parseStr .= ''.$val.$separator; + }else { + $parseStr .= ''.$val.$separator; + } + } + return $parseStr; + } + + /** + +---------------------------------------------------------- + * radio标签解析 + * 格式: + +---------------------------------------------------------- + * @access public + +---------------------------------------------------------- + * @param string $attr 标签属性 + +---------------------------------------------------------- + * @return string|void + +---------------------------------------------------------- + */ + public function _radio($tag,$content) { + $name = $tag['name']; + $radios = $tag['radios']; + $checked = $tag['checked']; + $separator = $tag['separator']; + $radios = $this->tpl->get($radios); + $checked = $this->tpl->get($checked)?$this->tpl->get($checked):$checked; + $parseStr = ''; + foreach($radios as $key=>$val) { + if($checked == $key ) { + $parseStr .= ''.$val.$separator; + }else { + $parseStr .= ''.$val.$separator; + } + + } + return $parseStr; + } + + /** + +---------------------------------------------------------- + * list标签解析 + * 格式: + * + +---------------------------------------------------------- + * @access public + +---------------------------------------------------------- + * @param string $attr 标签属性 + +---------------------------------------------------------- + * @return string + +---------------------------------------------------------- + */ + public function _grid($tag,$content) { + $id = $tag['id']; //表格ID + $datasource = $tag['datasource']; //列表显示的数据源VoList名称 + $pk = empty($tag['pk'])?'id':$tag['pk'];//主键名,默认为id + $style = $tag['style']; //样式名 + $name = !empty($tag['name'])?$tag['name']:'vo'; //Vo对象名 + $action = !empty($tag['action'])?$tag['action']:false; //是否显示功能操作 + $key = !empty($tag['key'])?true:false; + if(isset($tag['actionlist'])) { + $actionlist = explode(',',trim($tag['actionlist'])); //指定功能列表 + } + + if(substr($tag['show'],0,1)=='$') { + $show = $this->tpl->get(substr($tag['show'],1)); + }else { + $show = $tag['show']; + } + $show = explode(',',$show); //列表显示字段列表 + + //计算表格的列数 + $colNum = count($show); + if(!empty($action)) $colNum++; + if(!empty($key)) $colNum++; + + //显示开始 + $parseStr = "\n"; + $parseStr .= ''; + $parseStr .= ''; + $parseStr .= ''; + //列表需要显示的字段 + $fields = array(); + foreach($show as $val) { + $fields[] = explode(':',$val); + } + + if(!empty($key)) { + $parseStr .= ''; + } + foreach($fields as $field) {//显示指定的字段 + $property = explode('|',$field[0]); + $showname = explode('|',$field[1]); + if(isset($showname[1])) { + $parseStr .= ''; + } + if(!empty($action)) {//如果指定显示操作功能列 + $parseStr .= ''; + } + $parseStr .= ''; + $parseStr .= ''; //支持鼠标移动单元行颜色变化 具体方法在js中定义 + + if(!empty($key)) { + $parseStr .= ''; + } + foreach($fields as $field) { + //显示定义的列表字段 + $parseStr .= ''; + + } + if(!empty($action)) {//显示功能操作 + if(!empty($actionlist[0])) {//显示指定的功能项 + $parseStr .= ''; + } + } + $parseStr .= '
No'; + }else { + $parseStr .= ''; + } + $parseStr .= $showname[0].'操作
{$i}'; + if(!empty($field[2])) { + // 支持列表字段链接功能 具体方法由JS函数实现 + $href = explode('|',$field[2]); + if(count($href)>1) { + //指定链接传的字段值 + // 支持多个字段传递 + $array = explode('^',$href[1]); + if(count($array)>1) { + foreach ($array as $a){ + $temp[] = '\'{$'.$name.'.'.$a.'|addslashes}\''; + } + $parseStr .= ''; + }else{ + $parseStr .= ''; + } + }else { + //如果没有指定默认传编号值 + $parseStr .= ''; + } + } + if(strpos($field[0],'^')) { + $property = explode('^',$field[0]); + foreach ($property as $p){ + $unit = explode('|',$p); + if(count($unit)>1) { + $parseStr .= '{$'.$name.'.'.$unit[0].'|'.$unit[1].'} '; + }else { + $parseStr .= '{$'.$name.'.'.$p.'} '; + } + } + }else{ + $property = explode('|',$field[0]); + if(count($property)>1) { + $parseStr .= '{$'.$name.'.'.$property[0].'|'.$property[1].'}'; + }else { + $parseStr .= '{$'.$name.'.'.$field[0].'}'; + } + } + if(!empty($field[2])) { + $parseStr .= ''; + } + $parseStr .= ''; + foreach($actionlist as $val) { + if(strpos($val,':')) { + $a = explode(':',$val); + if(count($a)>2) { + $parseStr .= ''.$a[1].' '; + }else { + $parseStr .= ''.$a[1].' '; + } + }else{ + $array = explode('|',$val); + if(count($array)>2) { + $parseStr .= ' '.$array[2].' '; + }else{ + $parseStr .= ' {$'.$name.'.'.$val.'} '; + } + } + } + $parseStr .= '
'; + $parseStr .= "\n\n"; + return $parseStr; + } + + /** + +---------------------------------------------------------- + * list标签解析 + * 格式: + * + +---------------------------------------------------------- + * @access public + +---------------------------------------------------------- + * @param string $attr 标签属性 + +---------------------------------------------------------- + * @return string + +---------------------------------------------------------- + */ + public function _list($tag,$content) { + $id = $tag['id']; //表格ID + $datasource = $tag['datasource']; //列表显示的数据源VoList名称 + $pk = empty($tag['pk'])?'id':$tag['pk'];//主键名,默认为id + $style = $tag['style']; //样式名 + $name = !empty($tag['name'])?$tag['name']:'vo'; //Vo对象名 + $action = $tag['action']=='true'?true:false; //是否显示功能操作 + $key = !empty($tag['key'])?true:false; + $sort = $tag['sort']=='false'?false:true; + $checkbox = $tag['checkbox']; //是否显示Checkbox + if(isset($tag['actionlist'])) { + if(substr($tag['actionlist'],0,1)=='$') { + $actionlist = $this->tpl->get(substr($tag['actionlist'],1)); + }else { + $actionlist = $tag['actionlist']; + } + $actionlist = explode(',',trim($actionlist)); //指定功能列表 + } + + if(substr($tag['show'],0,1)=='$') { + $show = $this->tpl->get(substr($tag['show'],1)); + }else { + $show = $tag['show']; + } + $show = explode(',',$show); //列表显示字段列表 + + //计算表格的列数 + $colNum = count($show); + if(!empty($checkbox)) $colNum++; + if(!empty($action)) $colNum++; + if(!empty($key)) $colNum++; + + //显示开始 + $parseStr = "\n"; + $parseStr .= ''; + $parseStr .= ''; + $parseStr .= ''; + //列表需要显示的字段 + $fields = array(); + foreach($show as $val) { + $fields[] = explode(':',$val); + } + if(!empty($checkbox) && 'true'==strtolower($checkbox)) {//如果指定需要显示checkbox列 + $parseStr .=''; + } + if(!empty($key)) { + $parseStr .= ''; + } + foreach($fields as $field) {//显示指定的字段 + $property = explode('|',$field[0]); + $showname = explode('|',$field[1]); + if(isset($showname[1])) { + $parseStr .= ''; + }else{ + $parseStr .= $showname[0].''; + } + + } + if(!empty($action)) {//如果指定显示操作功能列 + $parseStr .= ''; + } + + $parseStr .= ''; + $parseStr .= ''; + } + if(!empty($key)) { + $parseStr .= ''; + } + foreach($fields as $field) { + //显示定义的列表字段 + $parseStr .= ''; + + } + if(!empty($action)) {//显示功能操作 + if(!empty($actionlist[0])) {//显示指定的功能项 + $parseStr .= ''; + } + } + $parseStr .= '
No'; + }else { + $parseStr .= ''; + } + $showname[2] = isset($showname[2])?$showname[2]:$showname[0]; + if($sort) { + $parseStr .= ''.$showname[0].'操作
{$i}'; + if(!empty($field[2])) { + // 支持列表字段链接功能 具体方法由JS函数实现 + $href = explode('|',$field[2]); + if(count($href)>1) { + //指定链接传的字段值 + // 支持多个字段传递 + $array = explode('^',$href[1]); + if(count($array)>1) { + foreach ($array as $a){ + $temp[] = '\'{$'.$name.'.'.$a.'|addslashes}\''; + } + $parseStr .= ''; + }else{ + $parseStr .= ''; + } + }else { + //如果没有指定默认传编号值 + $parseStr .= ''; + } + } + if(strpos($field[0],'^')) { + $property = explode('^',$field[0]); + foreach ($property as $p){ + $unit = explode('|',$p); + if(count($unit)>1) { + $parseStr .= '{$'.$name.'.'.$unit[0].'|'.$unit[1].'} '; + }else { + $parseStr .= '{$'.$name.'.'.$p.'} '; + } + } + }else{ + $property = explode('|',$field[0]); + if(count($property)>1) { + $parseStr .= '{$'.$name.'.'.$property[0].'|'.$property[1].'}'; + }else { + $parseStr .= '{$'.$name.'.'.$field[0].'}'; + } + } + if(!empty($field[2])) { + $parseStr .= ''; + } + $parseStr .= ''; + foreach($actionlist as $val) { + if(strpos($val,':')) { + $a = explode(':',$val); + if(count($a)>2) { + $parseStr .= ''.$a[1].' '; + }else { + $parseStr .= ''.$a[1].' '; + } + }else{ + $array = explode('|',$val); + if(count($array)>2) { + $parseStr .= ' '.$array[2].' '; + }else{ + $parseStr .= ' {$'.$name.'.'.$val.'} '; + } + } + } + $parseStr .= '
'; + $parseStr .= "\n\n"; + return $parseStr; + } + +} +?> \ No newline at end of file diff --git a/Think/Upload.php b/Think/Upload.php new file mode 100644 index 00000000..b8415a2b --- /dev/null +++ b/Think/Upload.php @@ -0,0 +1,514 @@ + +// +---------------------------------------------------------------------- +// $Id$ +namespace Think; +class Upload { + protected $config = [ + 'max_size' => -1, // 上传文件的最大值 + 'support_multi' => true, // 是否支持多文件上传 + 'allow_exts' => [], // 允许上传的文件后缀 留空不作后缀检查 + 'allow_types' => [], // 允许上传的文件类型 留空不做检查 + 'thumb' => false, // 使用对上传图片进行缩略图处理 + 'thumb_max_width' => '',// 缩略图最大宽度 + 'thumb_max_height' => '',// 缩略图最大高度 + 'thumb_prefix' => 'thumb_',// 缩略图前缀 + 'thumb_suffix' => '', + 'thumb_path' => '',// 缩略图保存路径 + 'thumb_file' => '',// 缩略图文件名 + 'thumb_ext' => '',// 缩略图扩展名 + 'thumb_remove_origin' => false,// 是否移除原图 + 'zip_images' => false,// 压缩图片文件上传 + 'auto_sub' => false,// 启用子目录保存文件 + 'sub_type' => 'hash',// 子目录创建方式 可以使用hash date custom + 'sub_dir' => '', // 子目录名称 subType为custom方式后有效 + 'date_format' => 'Ymd', + 'hash_level' => 1, // hash的目录层次 + 'save_path' => '',// 上传文件保存路径 + 'auto_check' => true, // 是否自动检查附件 + 'upload_replace' => false,// 存在同名是否覆盖 + 'save_rule' => 'uniqid',// 上传文件命名规则 + 'hash_type' => 'md5_file',// 上传文件Hash规则函数名 + ]; + + // 错误信息 + private $error = ''; + // 上传成功的文件信息 + private $uploadFileInfo ; + + public function __get($name){ + if(isset($this->config[$name])) { + return $this->config[$name]; + } + return null; + } + + public function __set($name,$value){ + if(isset($this->config[$name])) { + $this->config[$name] = $value; + } + } + + public function __isset($name){ + return isset($this->config[$name]); + } + + /** + * 架构函数 + * @access public + * @param array $config 上传参数 + */ + public function __construct($config=[]) { + if(is_array($config)) { + $this->config = array_merge($this->config,$config); + } + } + + /** + * 上传一个文件 + * @access protected + * @param mixed $name 数据 + * @param string $value 数据表名 + * @return string + */ + protected function save($file) { + $filename = $file['save_path'].$file['savename']; + if(!$this->upload_replace && is_file($filename)) { + // 不覆盖同名文件 + $this->error = '文件已经存在!'.$filename; + return false; + } + // 如果是图像文件 检测文件格式 + if( in_array(strtolower($file['extension']),['gif','jpg','jpeg','bmp','png','swf'])) { + $info = getimagesize($file['tmp_name']); + if(false === $info || ('gif' == strtolower($file['extension']) && empty($info['bits']))){ + $this->error = '非法图像文件'; + return false; + } + } + if(!move_uploaded_file($file['tmp_name'], $this->autoCharset($filename,'utf-8','gbk'))) { + $this->error = '文件上传保存错误!'; + return false; + } + if($this->thumb && in_array(strtolower($file['extension']),['gif','jpg','jpeg','bmp','png'])) { + $image = getimagesize($filename); + if(false !== $image) { + //是图像文件生成缩略图 + $thumbWidth = explode(',',$this->thumb_max_width); + $thumbHeight = explode(',',$this->thumb_max_height); + $thumb_prefix = explode(',',$this->thumb_prefix); + $thumb_suffix = explode(',',$this->thumb_suffix); + $thumb_file = explode(',',$this->thumb_file); + $thumb_path = $this->thumb_path?$this->thumb_path:dirname($filename).'/'; + $thumb_ext = $this->thumb_ext ? $this->thumb_ext : $file['extension']; //自定义缩略图扩展名 + // 生成图像缩略图 + for($i=0,$len=count($thumbWidth); $i<$len; $i++) { + if(!empty($thumb_file[$i])) { + $thumbname = $thumb_file[$i]; + }else{ + $prefix = isset($thumb_prefix[$i])?$thumb_prefix[$i]:$thumb_prefix[0]; + $suffix = isset($thumb_suffix[$i])?$thumb_suffix[$i]:$thumb_suffix[0]; + $thumbname = $prefix.basename($filename,'.'.$file['extension']).$suffix; + } + ThinkImage::thumb($filename,$thumb_path.$thumbname.'.'.$thumb_ext,'',$thumbWidth[$i],$thumbHeight[$i],true); + } + if($this->thumb_remove_origin) { + // 生成缩略图之后删除原图 + unlink($filename); + } + } + } + if($this->zipImags) { + // TODO 对图片压缩包在线解压 + + } + return true; + } + + /** + * 上传所有文件 + * @access public + * @param string $savePath 上传文件保存路径 + * @return string + */ + public function upload($savePath ='') { + //如果不指定保存文件名,则由系统默认 + if(empty($savePath)) + $savePath = $this->save_path; + // 检查上传目录 + if(!is_dir($savePath)) { + // 检查目录是否编码后的 + if(is_dir(base64_decode($savePath))) { + $savePath = base64_decode($savePath); + }else{ + // 尝试创建目录 + if(!mkdir($savePath)){ + $this->error = '上传目录'.$savePath.'不存在'; + return false; + } + } + }else { + if(!is_writeable($savePath)) { + $this->error = '上传目录'.$savePath.'不可写'; + return false; + } + } + $fileInfo = []; + $isUpload = false; + + // 获取上传的文件信息 + // 对$_FILES数组信息处理 + $files = $this->dealFiles($_FILES); + foreach($files as $key => $file) { + //过滤无效的上传 + if(!empty($file['name'])) { + //登记上传文件的扩展信息 + if(!isset($file['key'])) $file['key'] = $key; + $file['extension'] = $this->getExt($file['name']); + $file['savepath'] = $savePath; + $file['savename'] = $this->getSaveName($file); + + // 自动检查附件 + if($this->auto_check) { + if(!$this->check($file)) + return false; + } + + //保存上传文件 + if(!$this->save($file)) return false; + if(function_exists($this->hash_type)) { + $fun = $this->hash_type; + $file['hash'] = $fun($this->autoCharset($file['savepath'].$file['savename'],'utf-8','gbk')); + } + //上传成功后保存文件信息,供其他地方调用 + unset($file['tmp_name'],$file['error']); + $fileInfo[] = $file; + $isUpload = true; + } + } + if($isUpload) { + $this->uploadFileInfo = $fileInfo; + return true; + }else { + $this->error = '没有选择上传文件'; + return false; + } + } + + /** + * 上传单个上传字段中的文件 支持多附件 + * @access public + * @param array $file 上传文件信息 + * @param string $savePath 上传文件保存路径 + * @return string + */ + public function uploadOne($file,$savePath=''){ + //如果不指定保存文件名,则由系统默认 + if(empty($savePath)) + $savePath = $this->save_path; + // 检查上传目录 + if(!is_dir($savePath)) { + // 尝试创建目录 + if(!mkdir($savePath,0777,true)){ + $this->error = '上传目录'.$savePath.'不存在'; + return false; + } + }else { + if(!is_writeable($savePath)) { + $this->error = '上传目录'.$savePath.'不可写'; + return false; + } + } + //过滤无效的上传 + if(!empty($file['name'])) { + $fileArray = []; + if(is_array($file['name'])) { + $keys = array_keys($file); + $count = count($file['name']); + for ($i=0; $i<$count; $i++) { + foreach ($keys as $key) + $fileArray[$i][$key] = $file[$key][$i]; + } + }else{ + $fileArray[] = $file; + } + $info = []; + foreach ($fileArray as $key=>$file){ + //登记上传文件的扩展信息 + $file['extension'] = $this->getExt($file['name']); + $file['savepath'] = $savePath; + $file['savename'] = $this->getSaveName($file); + // 自动检查附件 + if($this->auto_check) { + if(!$this->check($file)) + return false; + } + //保存上传文件 + if(!$this->save($file)) return false; + if(function_exists($this->hash_type)) { + $fun = $this->hash_type; + $file['hash'] = $fun($this->autoCharset($file['savepath'].$file['savename'],'utf-8','gbk')); + } + unset($file['tmp_name'],$file['error']); + $info[] = $file; + } + // 返回上传的文件信息 + return $info; + }else { + $this->error = '没有选择上传文件'; + return false; + } + } + + /** + * 转换上传文件数组变量为正确的方式 + * @access protected + * @param array $files 上传的文件变量 + * @return array + */ + protected function dealFiles($files) { + $fileArray = []; + $n = 0; + foreach ($files as $key=>$file){ + if(is_array($file['name'])) { + $keys = array_keys($file); + $count = count($file['name']); + for ($i=0; $i<$count; $i++) { + $fileArray[$n]['key'] = $key; + foreach ($keys as $_key){ + $fileArray[$n][$_key] = $file[$_key][$i]; + } + $n++; + } + }else{ + $fileArray[$key] = $file; + } + } + return $fileArray; + } + + /** + * 获取错误代码信息 + * @access public + * @param string $errorNo 错误号码 + * @return void + */ + protected function error($errorNo) { + switch($errorNo) { + case 1: + $this->error = '上传的文件超过了 php.ini 中 upload_max_filesize 选项限制的值'; + break; + case 2: + $this->error = '上传文件的大小超过了 HTML 表单中 MAX_FILE_SIZE 选项指定的值'; + break; + case 3: + $this->error = '文件只有部分被上传'; + break; + case 4: + $this->error = '没有文件被上传'; + break; + case 6: + $this->error = '找不到临时文件夹'; + break; + case 7: + $this->error = '文件写入失败'; + break; + default: + $this->error = '未知上传错误!'; + } + return ; + } + + /** + * 根据上传文件命名规则取得保存文件名 + * @access protected + * @param string $filename 数据 + * @return string + */ + protected function getSaveName($filename) { + $rule = $this->save_rule; + if(empty($rule)) {//没有定义命名规则,则保持文件名不变 + $saveName = $filename['name']; + }else { + if(function_exists($rule)) { + //使用函数生成一个唯一文件标识号 + $saveName = $rule().".".$filename['extension']; + }else { + //使用给定的文件名作为标识号 + $saveName = $rule.".".$filename['extension']; + } + } + if($this->auto_sub) { + // 使用子目录保存文件 + $filename['savename'] = $saveName; + $saveName = $this->getSubName($filename).$saveName; + } + return $saveName; + } + + /** + * 获取子目录的名称 + * @access protected + * @param array $file 上传的文件信息 + * @return string + */ + protected function getSubName($file) { + switch($this->sub_type) { + case 'custom': + $dir = $this->sub_dir; + break; + case 'date': + $dir = date($this->date_format,time()).'/'; + break; + case 'hash': + default: + $name = md5($file['savename']); + $dir = ''; + for($i=0;$i<$this->hash_level;$i++) { + $dir .= $name{$i}.'/'; + } + break; + } + if(!is_dir($file['savepath'].$dir)) { + mkdir($file['savepath'].$dir,0777,true); + } + return $dir; + } + + /** + * 检查上传的文件 + * @access protected + * @param array $file 文件信息 + * @return boolean + */ + protected function check($file) { + if($file['error']!== 0) { + //文件上传失败 + //捕获错误代码 + $this->error($file['error']); + return false; + } + //文件上传成功,进行自定义规则检查 + //检查文件大小 + if(!$this->checkSize($file['size'])) { + $this->error = '上传文件大小不符!'; + return false; + } + + //检查文件Mime类型 + if(!$this->checkType($file['type'])) { + $this->error = '上传文件MIME类型不允许!'; + return false; + } + //检查文件类型 + if(!$this->checkExt($file['extension'])) { + $this->error ='上传文件类型不允许'; + return false; + } + + //检查是否合法上传 + if(!$this->checkUpload($file['tmp_name'])) { + $this->error = '非法上传文件!'; + return false; + } + return true; + } + + // 自动转换字符集 支持数组转换 + protected function autoCharset($fContents, $from='gbk', $to='utf-8') { + $from = strtoupper($from) == 'UTF8' ? 'utf-8' : $from; + $to = strtoupper($to) == 'UTF8' ? 'utf-8' : $to; + if (strtoupper($from) === strtoupper($to) || empty($fContents) || (is_scalar($fContents) && !is_string($fContents))) { + //如果编码相同或者非字符串标量则不转换 + return $fContents; + } + if (function_exists('mb_convert_encoding')) { + return mb_convert_encoding($fContents, $to, $from); + } elseif (function_exists('iconv')) { + return iconv($from, $to, $fContents); + } else { + return $fContents; + } + } + + /** + * 检查上传的文件类型是否合法 + * @access protected + * @param string $type 数据 + * @return boolean + */ + protected function checkType($type) { + if(!empty($this->allow_types)) + return in_array(strtolower($type),$this->allow_types); + return true; + } + + + /** + * 检查上传的文件后缀是否合法 + * @access protected + * @param string $ext 后缀名 + * @return boolean + */ + protected function checkExt($ext) { + if(!empty($this->allow_exts)) + return in_array(strtolower($ext),$this->allow_exts,true); + return true; + } + + /** + * 检查文件大小是否合法 + * @access protected + * @param integer $size 数据 + * @return boolean + */ + protected function checkSize($size) { + return !($size > $this->max_size) || (-1 == $this->max_size); + } + + /** + * 检查文件是否非法提交 + * @access protected + * @param string $filename 文件名 + * @return boolean + */ + protected function checkUpload($filename) { + return is_uploaded_file($filename); + } + + /** + * 取得上传文件的后缀 + * @access protected + * @param string $filename 文件名 + * @return boolean + */ + protected function getExt($filename) { + $pathinfo = pathinfo($filename); + return $pathinfo['extension']; + } + + /** + * 取得上传文件的信息 + * @access public + * @return array + */ + public function getUploadFileInfo() { + return $this->uploadFileInfo; + } + + /** + * 取得最后一次错误信息 + * @access public + * @return string + */ + public function getErrorMsg() { + return $this->error; + } +} \ No newline at end of file diff --git a/Think/Url.php b/Think/Url.php new file mode 100644 index 00000000..e1844e1a --- /dev/null +++ b/Think/Url.php @@ -0,0 +1,151 @@ + +// +---------------------------------------------------------------------- +// $Id$ + +namespace Think; +class Url { + + static public function param($num,$default=''){ + $paths = explode(Config::get('url_pathinfo_depr'),trim($_SERVER['PATH_INFO'],'/')); + return isset($paths[$num])?$paths[$num]:$default; + } + + static public function route($route){ + } + + /** + * URL组装 支持不同URL模式 + * @param string $url URL表达式,格式:'[分组/模块/操作#锚点@域名]?参数1=值1&参数2=值2...' + * @param string|array $vars 传入的参数,支持数组和字符串 + * @param string $suffix 伪静态后缀,默认为true表示获取配置值 + * @param boolean $domain 是否显示域名 + * @return string + */ + static public function build($url='',$vars='',$suffix=true,$domain=false) { + // 解析URL + $info = parse_url($url); + $url = !empty($info['path'])?$info['path']:ACTION_NAME; + if(isset($info['fragment'])) { // 解析锚点 + $anchor = $info['fragment']; + if(false !== strpos($anchor,'?')) { // 解析参数 + list($anchor,$info['query']) = explode('?',$anchor,2); + } + if(false !== strpos($anchor,'@')) { // 解析域名 + list($anchor,$host) = explode('@',$anchor, 2); + } + }elseif(false !== strpos($url,'@')) { // 解析域名 + list($url,$host) = explode('@',$info['path'], 2); + } + // 解析子域名 + if(isset($host)) { + $domain = $host.(strpos($host,'.')?'':strstr($_SERVER['HTTP_HOST'],'.')); + }elseif($domain===true){ + $domain = $_SERVER['HTTP_HOST']; + if(Config::get('app_sub_domain_deplay') ) { // 开启子域名部署 + $domain = $domain=='localhost'?'localhost':'www'.strstr($_SERVER['HTTP_HOST'],'.'); + // '子域名'=>array('项目[/分组]'); + foreach (Config::get('app_sub_domain_rules') as $key => $rule) { + if(false === strpos($key,'*') && 0=== strpos($url,$rule[0])) { + $domain = $key.strstr($domain,'.'); // 生成对应子域名 + $url = substr_replace($url,'',0,strlen($rule[0])); + break; + } + } + } + } + + // 解析参数 + if(is_string($vars)) { // aaa=1&bbb=2 转换成数组 + parse_str($vars,$vars); + }elseif(!is_array($vars)){ + $vars = array(); + } + if(isset($info['query'])) { // 解析地址里面参数 合并到vars + parse_str($info['query'],$params); + $vars = array_merge($params,$vars); + } + + // URL组装 + $depr = Config::get('pathinfo_depr'); + if($url) { + if(0=== strpos($url,'/')) {// 定义路由 + $route = true; + $url = substr($url,1); + if('/' != $depr) { + $url = str_replace('/',$depr,$url); + } + }else{ + if('/' != $depr) { // 安全替换 + $url = str_replace('/',$depr,$url); + } + // 解析分组、模块和操作 + $url = trim($url,$depr); + $path = explode($depr,$url); + $var = array(); + $var[Config::get('var_action')] = !empty($path)?array_pop($path):ACTION_NAME; + if(Config::get('require_controll')) { + $var[Config::get('var_controll')] = !empty($path)?array_pop($path):CONTROLL_NAME; + } + if(Config::get('require_module')) { + $var[Config::get('var_module')] = !empty($path)?array_pop($path):MODULE_NAME; + } + } + } + + if(Config::get('url_model') == 0) { // 普通模式URL转换 + $url = Config::get('base_url').'?'.http_build_query(array_reverse($var)); + if(!empty($vars)) { + $vars = urldecode(http_build_query($vars)); + $url .= '&'.$vars; + } + }else{ // PATHINFO模式或者兼容URL模式 + if(isset($route)) { + $url = Config::get('base_url').'/'.rtrim($url,$depr); + }else{ + $url = Config::get('base_url').'/'.implode($depr,array_reverse($var)); + } + if(!empty($vars)) { // 添加参数 + foreach ($vars as $var => $val){ + if('' !== trim($val)) $url .= $depr . $var . $depr . urlencode($val); + } + } + if($suffix) { + $suffix = $suffix===true?Config::get('url_html_suffix'):$suffix; + if($pos = strpos($suffix, '|')){ + $suffix = substr($suffix, 0, $pos); + } + if($suffix && '/' != substr($url,-1)){ + $url .= '.'.ltrim($suffix,'.'); + } + } + } + if(isset($anchor)){ + $url .= '#'.$anchor; + } + if($domain) { + $url = (self::is_ssl()?'https://':'http://').$domain.$url; + } + return $url; + } + + /** + * 判断是否SSL协议 + * @return boolean + */ + static public function is_ssl() { + if(isset($_SERVER['HTTPS']) && ('1' == $_SERVER['HTTPS'] || 'on' == strtolower($_SERVER['HTTPS']))){ + return true; + }elseif(isset($_SERVER['SERVER_PORT']) && ('443' == $_SERVER['SERVER_PORT'] )) { + return true; + } + return false; + } +} \ No newline at end of file diff --git a/Think/Validate.php b/Think/Validate.php new file mode 100644 index 00000000..b1d7bbb1 --- /dev/null +++ b/Think/Validate.php @@ -0,0 +1,184 @@ + +// +---------------------------------------------------------------------- +// $Id$ + +class ThinkValidate { + + protected $validate = array(); // 自动验证定义 + // 是否批处理验证 + protected $patchValidate = false; + protected $error = ''; + + public function rule($rule){ + $this->validate = $rule; + return $this; + } + + /** + * 自动表单验证 + * @access protected + * @param array $data 创建数据 + * @param string $type 创建类型 + * @return boolean + */ + public function valid($data,$rule=array()) { + $validate = $rule?$rule:$this->validate; + // 属性验证 + if($validate) { // 如果设置了数据自动验证则进行数据验证 + if($this->patchValidate) { // 重置验证错误信息 + $this->error = array(); + } + foreach($validate as $key=>$val) { + // 验证因子定义格式 + // array(field,rule,message,condition,type,params) + // 判断是否需要执行验证 + if(0==strpos($val[2],'{%') && strpos($val[2],'}')) + // 支持提示信息的多语言 使用 {%语言定义} 方式 + $val[2] = L(substr($val[2],2,-1)); + $val[3] = isset($val[3])?$val[3]:0; + $val[4] = isset($val[4])?$val[4]:'regex'; + // 判断验证条件 + if( 1 == $val[3] || (2 == $val[3] && '' != trim($data[$val[0]])) || (0 == $val[3] && isset($data[$val[0]])) ) { + if(false === $this->_validationField($data,$val)) + return false; + } + } + // 批量验证的时候最后返回错误 + if(!empty($this->error)) return false; + } + return true; + } + + /** + * 验证表单字段 支持批量验证 + * 如果批量验证返回错误的数组信息 + * @access protected + * @param array $data 创建数据 + * @param array $val 验证因子 + * @return boolean + */ + protected function _validationField($data,$val) { + if(false === $this->_validationFieldItem($data,$val)){ + if($this->patchValidate) { + $this->error[$val[0]] = $val[2]; + }else{ + $this->error = $val[2]; + return false; + } + } + return ; + } + + /** + * 根据验证因子验证字段 + * @access protected + * @param array $data 创建数据 + * @param array $val 验证因子 + * @return boolean + */ + protected function _validationFieldItem($data,$val) { + switch(strtolower(trim($val[4]))) { + case 'callback':// 调用方法进行验证 + $args = isset($val[5])?(array)$val[5]:array(); + if(is_string($val[0]) && strpos($val[0], ',')) + $val[0] = explode(',', $val[0]); + if(is_array($val[0])){ + // 支持多个字段验证 + foreach($val[0] as $field) + $_data[$field] = $data[$field]; + array_unshift($args, $_data); + }else{ + array_unshift($args, $data[$val[0]]); + } + return call_user_func_array($val[1], $args); + case 'confirm': // 验证两个字段是否相同 + return $data[$val[0]] == $data[$val[1]]; + default: // 检查附加规则 + return $this->check($data[$val[0]],$val[1],$val[4]); + } + } + + /** + * 验证数据 支持 in between equal length regex expire ip_allow ip_deny + * @access public + * @param string $value 验证数据 + * @param mixed $rule 验证表达式 + * @param string $type 验证方式 默认为正则验证 + * @return boolean + */ + public function check($value,$rule,$type='regex'){ + $type = strtolower(trim($type)); + switch($type) { + case 'in': // 验证是否在某个指定范围之内 逗号分隔字符串或者数组 + case 'notin': + $range = is_array($rule)? $rule : explode(',',$rule); + return $type == 'in' ? in_array($value ,$range) : !in_array($value ,$range); + case 'between': // 验证是否在某个范围 + case 'notbetween': // 验证是否不在某个范围 + if (is_array($rule)){ + $min = $rule[0]; + $max = $rule[1]; + }else{ + list($min,$max) = explode(',',$rule); + } + return $type == 'between' ? $value>=$min && $value<=$max : $value<$min || $value>$max; + case 'equal': // 验证是否等于某个值 + case 'notequal': // 验证是否等于某个值 + return $type == 'equal' ? $value == $rule : $value != $rule; + case 'length': // 验证长度 + $length = mb_strlen($value,'utf-8'); // 当前数据长度 + if(strpos($rule,',')) { // 长度区间 + list($min,$max) = explode(',',$rule); + return $length >= $min && $length <= $max; + }else{// 指定长度 + return $length == $rule; + } + case 'expire': + list($start,$end) = explode(',',$rule); + if(!is_numeric($start)) $start = strtotime($start); + if(!is_numeric($end)) $end = strtotime($end); + return NOW_TIME >= $start && NOW_TIME <= $end; + case 'ip_allow': // IP 操作许可验证 + return in_array($_SERVER['REMOTE_ADDR'],explode(',',$rule)); + case 'ip_deny': // IP 操作禁止验证 + return !in_array($_SERVER['REMOTE_ADDR'],explode(',',$rule)); + case 'regex': + default: // 默认使用正则验证 可以使用验证类中定义的验证名称 + // 检查附加规则 + return $this->regex($value,$rule); + } + } + + /** + * 使用正则验证数据 + * @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(s?):\/\/(?:[A-za-z0-9-]+\.)+[A-za-z]{2,4}(?:[\/\?#][\/=\?%\-&~`@[\]\':+!\.#\w]*)?$/', + 'currency' => '/^\d+(\.\d+)?$/', + 'number' => '/^\d+$/', + 'zip' => '/^\d{6}$/', + 'integer' => '/^[-\+]?\d+$/', + 'double' => '/^[-\+]?\d+(\.\d+)?$/', + 'english' => '/^[A-Za-z]+$/', + ); + // 检查是否有内置的正则表达式 + if(isset($validate[strtolower($rule)])) + $rule = $validate[strtolower($rule)]; + return preg_match($rule,$value)===1; + } +} \ No newline at end of file diff --git a/Think/View.php b/Think/View.php new file mode 100644 index 00000000..165c4a7a --- /dev/null +++ b/Think/View.php @@ -0,0 +1,119 @@ + +// +---------------------------------------------------------------------- +// $Id$ +namespace Think; +class View { + + public $engine = null; // 模板引擎 + protected $template = null; // 模板文件 + protected $data = []; // 模板变量 + protected $config = []; // 视图参数 + + /** + * 模板变量赋值 + * @access public + * @param mixed $name + * @param mixed $value + */ + public function assign($name,$value=''){ + if(is_array($name)) { + $this->data = array_merge($this->data,$name); + return $this; + }else { + $this->data[$name] = $value; + } + } + + /** + * 视图参数设置 + * @access public + * @param mixed $name + * @param mixed $value + */ + public function __set($name,$value){ + $this->config[$name] = $value; + } + + public function __construct($config=[]){ + $this->config = $config; + if(!empty($this->config['template_engine'])) { + $this->engine($this->config['template_engine'],$config['template_options']); + } + } + + public function engine($engine,$config=[]){ + $class = '\Think\View\Driver\\'.ucwords($engine); + $this->engine = new $class($config); + return $this; + } + + /** + * 加载模板和页面输出 可以返回输出内容 + * @access public + * @param string $template 模板文件名 + * @param boolean $return 是否返回 + * @return mixed + */ + public function display($template='',$vars=[],$return=false) { + Tag::listen('view_begin',$template); + // 解析并获取模板内容 + $content = $this->fetch($template,$vars); + // 输出模板内容 + if($return) { + return $content; + }else{ + $this->render($content); + } + } + + /** + * 解析和获取模板内容 用于输出 + * @access protected + * @param string $template 模板文件名或者内容 + * @param array $vars 模板输出变量 + * @return string + */ + protected function fetch($template,$vars=[]) { + Tag::listen('view_template',$template); + $vars = $vars?$vars:$this->data; + // 页面缓存 + ob_start(); + ob_implicit_flush(0); + if($this->engine) { // 指定模板引擎 + $this->engine->fetch($template,$vars); + }else{ + extract($vars, EXTR_OVERWRITE); + is_file($template)?include $template:eval('?>'.$template); + } + // 获取并清空缓存 + $content = ob_get_clean(); + Tag::listen('content_filter',$content); + // 输出模板文件 + return $content; + } + + /** + * 输出内容文本可以包括Html + * @access private + * @param string $content 输出内容 + * @param string $charset 模板输出字符集 + * @param string $contentType 输出类型 + * @return mixed + */ + private function render($content){ + // 网页字符编码 + header('Content-Type:'.$this->config['http_content_type'].'; charset='.$this->config['http_charset']); + header('Cache-control: '.$this->config['http_cache_control']); // 页面缓存控制 + header('X-Powered-By:ThinkPHP'); + // 输出模板文件 + echo $content; + } +} \ No newline at end of file diff --git a/Think/View/Driver/Think.php b/Think/View/Driver/Think.php new file mode 100644 index 00000000..392fc116 --- /dev/null +++ b/Think/View/Driver/Think.php @@ -0,0 +1,28 @@ + +// +---------------------------------------------------------------------- +// $Id$ +namespace Think\View\Driver; +use Think\Template; +class Think { + private $template = null; + public function __construct($config=array()){ + $tpl = new Template($config); + $this->template = $tpl; + //$tpl->tpl_path = MODULE_PATH.'view/'; + //$tpl->cache_path = MODULE_PATH.'cache/'; + } + + public function fetch($template,$data=array()){ + $this->template->assign($data); + $this->template->display($template); + } + +} \ No newline at end of file diff --git a/alias.php b/alias.php new file mode 100644 index 00000000..b601c33d --- /dev/null +++ b/alias.php @@ -0,0 +1,32 @@ + +// +---------------------------------------------------------------------- +// $Id$ + +return array(/* + 'Think\App' => CORE_PATH.'app.php', + 'Think\Log' => CORE_PATH.'log.php', + 'Think\Config' => CORE_PATH.'config.php', + 'Think\Route' => CORE_PATH.'route.php', + 'Think\Exception'=> CORE_PATH.'exception.php', + 'Think\Model' => CORE_PATH.'model.php', + 'Think\Db' => CORE_PATH.'db.php', + 'Think\Template' => CORE_PATH.'template.php', + 'Think\Error' => CORE_PATH.'error.php', + 'Think\Cache' => CORE_PATH.'cache.php', + 'Think\Tag' => CORE_PATH.'tag.php', + 'Think\Session' => CORE_PATH.'session.php', + 'Think\Cookie' => CORE_PATH.'cookie.php', + 'Think\Controll' => CORE_PATH.'controll.php', + 'Think\View' => CORE_PATH.'view.php', + 'Think\Auth' => CORE_PATH.'auth.php', + 'Think\Url' => CORE_PATH.'url.php', + 'Think\Verify' => CORE_PATH.'verify.php',*/ +); \ No newline at end of file diff --git a/base.php b/base.php new file mode 100644 index 00000000..664a2f1f --- /dev/null +++ b/base.php @@ -0,0 +1,288 @@ + +// +---------------------------------------------------------------------- +// $Id$ + +if(version_compare(PHP_VERSION,'5.3.0','<')) die('require PHP > 5.3.0 !'); +if(version_compare(PHP_VERSION,'5.4.0','<')) { + ini_set('magic_quotes_runtime',0); + define('MAGIC_QUOTES_GPC',get_magic_quotes_gpc()?True:False); +}else{ + define('MAGIC_QUOTES_GPC',false); +} +// 版本信息 +define('THINK_VERSION', '4.0'); +// 系统常量 +defined('THINK_PATH') or define('THINK_PATH', dirname(__FILE__).'/'); +defined('CORE_PATH') or define('CORE_PATH', THINK_PATH.'Think/'); +defined('APP_PATH') or define('APP_PATH', dirname($_SERVER['SCRIPT_FILENAME']).'/'); +defined('LIB_PATH') or define('LIB_PATH', APP_PATH.'Library/'); +defined('RUNTIME_PATH') or define('RUNTIME_PATH', realpath(APP_PATH).'/Runtime/'); +defined('DATA_PATH') or define('DATA_PATH', RUNTIME_PATH.'Data/'); +defined('LOG_PATH') or define('LOG_PATH', RUNTIME_PATH.'Log/'); +defined('CACHE_PATH') or define('CACHE_PATH', RUNTIME_PATH.'Temp/'); +defined('VENDOR_PATH') or define('VENDOR_PATH', THINK_PATH.'Vendor/'); +defined('EXT') or define('EXT', '.php'); +defined('APP_DEBUG') or define('APP_DEBUG',false); // 是否调试模式 +defined('RUNTIME_FILE') or define('RUNTIME_FILE', RUNTIME_PATH.'~runtime.php'); + +// 为了方便导入第三方类库 设置Vendor目录到include_path +set_include_path(get_include_path() . PATH_SEPARATOR . VENDOR_PATH); +// 环境常量 +define('MEMORY_LIMIT_ON',function_exists('memory_get_usage')); +define('IS_CGI',strpos(PHP_SAPI, 'cgi')=== 0 ? 1 : 0 ); +define('IS_WIN',strstr(PHP_OS, 'WIN') ? 1 : 0 ); +define('IS_CLI',PHP_SAPI=='cli'? 1 : 0); +define('IS_AJAX', (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') ? true : false); +define('NOW_TIME', $_SERVER['REQUEST_TIME']); + +// 获取多语言变量 +function __($name){ + return \Think\Lang::get($name); +} + +// 获取配置参数 +function C($name='',$range='') { + return \Think\Config::get($name,$range); +} + +/** + * 记录和统计时间(微秒)和内存使用情况 + * 使用方法: + * + * G('begin'); // 记录开始标记位 + * // ... 区间运行代码 + * G('end'); // 记录结束标签位 + * echo G('begin','end',6); // 统计区间运行时间 精确到小数后6位 + * echo G('begin','end','m'); // 统计区间内存使用情况 + * 如果end标记位没有定义,则会自动以当前作为标记位 + * 其中统计内存使用需要 MEMORY_LIMIT_ON 常量为true才有效 + * + * @param string $start 开始标签 + * @param string $end 结束标签 + * @param integer|string $dec 小数位或者m + * @return mixed + */ +function G($name) { + Think\Debug::remark($name); +} + +/** + * 设置和获取统计数据 + * 使用方法: + * + * N('db',1); // 记录数据库操作次数 + * N('read',1); // 记录读取次数 + * echo N('db'); // 获取当前页面数据库的所有操作次数 + * echo N('read'); // 获取当前页面读取次数 + * + * @param string $key 标识位置 + * @param integer $step 步进值 + * @return mixed + */ +function N($key, $step=0) { + static $_num = array(); + if (!isset($_num[$key])) { + $_num[$key] = 0; + } + if (empty($step)) + return $_num[$key]; + else + $_num[$key] = $_num[$key] + (int) $step; +} + +/** + * M函数用于实例化一个没有模型文件的Model + * @param string $name Model名称 支持指定基础模型 例如 MongoModel:User + * @param string $tablePrefix 表前缀 + * @param mixed $connection 数据库连接信息 + * @return Model + */ +function M($name='', $tablePrefix='',$connection='') { + return Think\Loader::table($name,$tablePrefix,$connection); +} + +/** + * D函数用于实例化Model + * @param string $name Model名称 + * @param string $layer 业务层名称 + * @return ThinkModel + */ +function D($name='',$layer='model') { + return Think\Loader::model($name,$layer); +} + +/** + * A函数用于实例化控制器 格式:[分组/]模块 + * @param string $name 资源地址 + * @param string $layer 控制层名称 + * @return Action|false + */ +function A($name,$layer='') { + return Think\Loader::controll($name,$layer); +} + +/** + * 远程调用模块的操作方法 参数格式 [模块/控制器/]操作 + * @param string $url 调用地址 + * @param string|array $vars 调用参数 支持字符串和数组 + * @param string $layer 要调用的控制层名称 + * @return mixed + */ +function R($url,$vars=array(),$layer='') { + return Think\Loader::action($url,$vars,$layer); +} + +/** + * 字符串命名风格转换 + * type 0 将Java风格转换为C的风格 1 将C风格转换为Java的风格 + * @param string $name 字符串 + * @param integer $type 转换类型 + * @return string + */ +function parse_name($name, $type=0) { + if ($type) { + return ucfirst(preg_replace("/_([a-zA-Z])/e", "strtoupper('\\1')", $name)); + } else { + return strtolower(trim(preg_replace("/[A-Z]/", "_\\0", $name), "_")); + } +} + +/** + * 导入所需的类库 同java的Import 本函数有缓存功能 + * @param string $class 类库命名空间字符串 + * @param string $baseUrl 起始路径 + * @param string $ext 导入的文件扩展名 + * @return boolean + */ +function import($class, $baseUrl = '', $ext= EXT ) { + return Think\Loader::load($class,$baseUrl,$ext); +} + +/** + * 自定义异常处理 + * @param string $msg 异常消息 + * @param string $type 异常类型 默认为ThinkException + * @param integer $code 异常代码 默认为0 + * @return void + */ +function throw_exception($msg, $type='Think\Exception', $code=0) { + if (class_exists($type)) + throw new $type($msg, $code, true); + else + Think\Error::halt($msg); // 异常类型不存在则输出错误信息字串 +} + +/** + * 浏览器友好的变量输出 + * @param mixed $var 变量 + * @param boolean $echo 是否输出 默认为True 如果为false 则返回输出字符串 + * @param string $label 标签 默认为空 + * @return void|string + */ +function dump($var, $echo=true, $label=null) { + return \Think\Debug::dump($var,$echo,$label); +} + +/** + * 404处理 + * 调试模式会抛异常 + * 部署模式下面传入url参数可以指定跳转页面,否则发送404信息 + * @param string $msg 提示信息 + * @param string $url 跳转URL地址 + * @return void + */ +function _404($msg='',$url='') { + Think\Config::get('app_debug') && throw_exception($msg); + if($msg) Think\Log::record($msg,'ERR'); + $url = $url?$url:Think\Config::get('url_404_redirect'); + if($url) { + redirect($url); + }else{ + header('HTTP/1.1 404 Not Found'); + // 确保FastCGI模式下正常 + header('Status:404 Not Found'); + exit; + } +} + +/** + * 渲染输出Widget + * @param string $name Widget名称 + * @param array $data 传人的参数 + * @return void + */ +function W($name, $data=array()) { + echo R($name,$data,'Widget'); +} + +/** + * URL重定向 + * @param string $url 重定向的URL地址 + * @param integer $time 重定向的等待时间(秒) + * @param string $msg 重定向前的提示信息 + * @return void + */ +function redirect($url, $time=0, $msg='') { + //多行URL地址支持 + $url = str_replace(array("\n", "\r"), '', $url); + if (empty($msg)) + $msg = "系统将在{$time}秒之后自动跳转到{$url}!"; + if (!headers_sent()) { + // redirect + if (0 === $time) { + header('Location: ' . $url); + } else { + header("refresh:{$time};url={$url}"); + echo($msg); + } + exit(); + } else { + $str = ""; + if ($time != 0) + $str .= $msg; + exit($str); + } +} + +/** + * 缓存管理 + * @param mixed $name 缓存名称,如果为数组表示进行缓存设置 + * @param mixed $value 缓存值 + * @param mixed $options 缓存参数 + * @return mixed + */ +function S($name,$value='',$options=null) { + static $cache = null; + if(is_array($options)){ + // 缓存操作的同时初始化 + Think\Cache::connect($options); + }elseif(is_array($name)) { // 缓存初始化 + Think\Cache::connect($name); + }elseif(is_null($cache)) { // 自动初始化 + Think\Cache::connect(); + $cache = true; + } + if(''=== $value){ // 获取缓存 + return Think\Cache::get($name); + }elseif(is_null($value)) { // 删除缓存 + return Think\Cache::rm($name); + }else { // 缓存数据 + $expire = is_numeric($options)?$options:NULL; + return Think\Cache::set($name, $value, $expire); + } +} + +// 过滤表单中的表达式 +function filter_exp(&$value){ + if (in_array(strtolower($value),array('exp','or'))){ + $value .= ' '; + } +} \ No newline at end of file diff --git a/convention.php b/convention.php new file mode 100644 index 00000000..a235ff22 --- /dev/null +++ b/convention.php @@ -0,0 +1,80 @@ + +// +---------------------------------------------------------------------- +// $Id$ + +// 惯例配置文件 +return [ + 'app_debug' => true, // 调试模式 + 'app_status' => 'debug',// 调试模式状态 + 'var_module' => 'm', // 模块变量名 + 'var_controll' => 'c', // 控制器变量名 + 'var_action' => 'a', // 操作变量名 + 'var_pathinfo' => 's', // PATHINFO变量名 用于兼容模式 + 'pathinfo_fetch' => 'ORIG_PATH_INFO,REDIRECT_PATH_INFO,REDIRECT_URL', + 'pathinfo_depr' => '/', // pathinfo分隔符 + 'require_module' => true, // 是否显示模块 + 'default_module' => 'index', // 默认模块名 + 'require_controll' => true, // 是否显示控制器 + 'default_controll' => 'index', // 默认控制器名 + 'default_action' => 'index', // 默认操作名 + 'default_layer' => 'action', // 默认控制器层 + 'action_suffix' => '', // 操作方法后缀 + 'module_name' => '', // 当前模块名 + 'controll_name' => '', // 当前控制器名 + 'action_name' => '', // 当前操作名 + 'file_ext' => '.php', // 文件后缀 + 'url_model' => 1, // URL模式 + 'base_url' => $_SERVER["SCRIPT_NAME"], // 基础URL路径 + 'url_html_suffix' => '.html', + 'url_route' => true, // 是否开启路由 + 'url_route_rules' => '', // 路由规则 + 'url_params_bind' => false, // url变量绑定 + 'app_autoload_path' => '', // 自动加载搜索路径 + 'app_domain_deploy' => false, // 开启域名部署 + 'app_domain_rules' => '', // 域名部署规则 + 'app_doamin_deny' => '', // 域名禁止列表 + 'exception_tmpl' => THINK_PATH.'tpl/think_exception.tpl',// 异常页面的模板文件 + 'http_cache_control' => 'private', + + /* 错误设置 */ + 'ERROR_MESSAGE' => '页面错误!请稍后再试~',//错误显示信息,非调试模式有效 + 'ERROR_PAGE' => '', // 错误定向页面 + 'SHOW_ERROR_MSG' => false, // 显示错误信息 + 'TRACE_EXCEPTION' => false, // TRACE错误信息是否抛异常 针对trace方法 + + /* 日志设置 */ + 'LOG_RECORD' => false, // 默认不记录日志 + 'LOG_TYPE' => 'file', // 日志记录类型 0 系统 1 邮件 3 文件 4 SAPI 默认为文件方式 + 'LOG_LEVEL' => 'EMERG,ALERT,CRIT,ERR',// 允许记录的日志级别 + 'LOG_FILE_SIZE' => 2097152, // 日志文件大小限制 + 'LOG_EXCEPTION_RECORD' => false, // 是否记录异常信息日志 + 'log_path' => LOG_PATH, + + /* 数据库设置 */ + 'DB_TYPE' => 'mysql', // 数据库类型 + 'DB_HOST' => 'localhost', // 服务器地址 + 'DB_NAME' => '', // 数据库名 + 'DB_USER' => 'root', // 用户名 + 'DB_PWD' => '', // 密码 + 'DB_PORT' => '', // 端口 + 'db_prefix' => 'think_', // 数据库表前缀 + 'DB_FIELDTYPE_CHECK' => false, // 是否进行字段类型检查 + 'DB_FIELDS_CACHE' => true, // 启用字段缓存 + 'DB_CHARSET' => 'utf8', // 数据库编码默认采用utf8 + 'DB_DEPLOY_TYPE' => 0, // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器) + 'DB_RW_SEPARATE' => false, // 数据库读写是否分离 主从式有效 + 'DB_MASTER_NUM' => 1, // 读写分离后 主服务器数量 + 'DB_SLAVE_NO' => '', // 指定从服务器序号 + 'DB_SQL_BUILD_CACHE' => false, // 数据库查询的SQL创建缓存 + 'DB_SQL_BUILD_QUEUE' => 'file', // SQL缓存队列的缓存方式 支持 file xcache和apc + 'DB_SQL_BUILD_LENGTH' => 20, // SQL缓存的队列长度 + 'DB_SQL_LOG' => false, // SQL执行日志记录 +]; \ No newline at end of file diff --git a/start.php b/start.php new file mode 100644 index 00000000..b6a257b8 --- /dev/null +++ b/start.php @@ -0,0 +1,62 @@ + +// +---------------------------------------------------------------------- +// $Id$ + +namespace Think; +//-------------------------- +// ThinkPHP 引导文件 +//-------------------------- + +// 加载基础文件 +require dirname(__FILE__).'/base.php'; +require CORE_PATH.'loader.php'; + +// 注册自动加载 +Loader::register(); +// 导入系统别名 +Loader::loadMap(include THINK_PATH.'alias.php'); +// 加载应用类 +//require CORE_PATH.'app.php'; +// 加载错误类 +//require CORE_PATH.'error.php'; + +// 注册错误和异常处理机制 +register_shutdown_function(['Think\Error','appShutdown']); +set_error_handler(['Think\Error','appError']); +set_exception_handler(['Think\Error','appException']); + +// 导入系统惯例 +Config::load(THINK_PATH.'convention.php'); + +// 日志初始化 +Log::init(['type'=>Config::get('log_type'),'log_path'=> Config::get('log_path')]); + +// 缓存初始化 +Cache::connect(['type'=>Config::get('cache_type'),'temp'=> CACHE_PATH]); + +// 注册行为扩展 +//ThinkTag::add('route_check','route_check'); +Tag::add('content_filter','ContentReplace','Think'); +//Tag::add('app_end','ShowPageTrace','Think'); +Tag::add('view_template','LocationTemplate','Think'); +//Tag::add('action_begin','BeforeAction','Index'); +//Tag::add('app_end','test','Index'); + +// 启动session +if(!IS_CLI) { + Session::init(['prefix'=>'think','auto_start'=>true]); +} + +// 执行应用 +App::run(); + +// 保存日志 +//Log::save(); \ No newline at end of file diff --git a/tpl/page_trace.tpl b/tpl/page_trace.tpl new file mode 100644 index 00000000..caa4c570 --- /dev/null +++ b/tpl/page_trace.tpl @@ -0,0 +1,67 @@ +
+ + +
+
+ \ No newline at end of file diff --git a/tpl/think_exception.tpl b/tpl/think_exception.tpl new file mode 100644 index 00000000..84b27e97 --- /dev/null +++ b/tpl/think_exception.tpl @@ -0,0 +1,53 @@ + + + +系统发生错误 + + + +
+

:(

+

+
+ +
+
+

错误位置

+
+
+

FILE:  LINE:

+
+
+ + +
+
+

TRACE

+
+
+

+
+
+ +
+
+ + + \ No newline at end of file