From 50e4b6a0dec2123f8a859675004f8c7b452d7414 Mon Sep 17 00:00:00 2001 From: wangchunsheng Date: Mon, 8 Nov 2010 02:58:55 +0000 Subject: [PATCH] + import the framework to zentaopms. --- framework/control.class.php | 488 ++++++++ framework/helper.class.php | 387 ++++++ framework/model.class.php | 228 ++++ framework/router.class.php | 1574 ++++++++++++++++++++++++ framework/tests/helper/.case001.expect | 37 + framework/tests/helper/.case002.expect | 19 + framework/tests/helper/.case003.expect | 19 + framework/tests/helper/.case004.expect | 14 + framework/tests/helper/case001.php | 31 + framework/tests/helper/case002.php | 46 + framework/tests/helper/case003.php | 81 ++ framework/tests/helper/case004.php | 45 + framework/tests/helper/import1.php | 14 + framework/tests/helper/import2.php | 13 + lib/dao/dao.class.php | 1156 +++++++++++++++++ lib/filter/filter.class.php | 348 ++++++ lib/pager/pager.class.php | 347 ++++++ 17 files changed, 4847 insertions(+) create mode 100755 framework/control.class.php create mode 100644 framework/helper.class.php create mode 100755 framework/model.class.php create mode 100755 framework/router.class.php create mode 100755 framework/tests/helper/.case001.expect create mode 100755 framework/tests/helper/.case002.expect create mode 100755 framework/tests/helper/.case003.expect create mode 100755 framework/tests/helper/.case004.expect create mode 100755 framework/tests/helper/case001.php create mode 100755 framework/tests/helper/case002.php create mode 100755 framework/tests/helper/case003.php create mode 100755 framework/tests/helper/case004.php create mode 100755 framework/tests/helper/import1.php create mode 100755 framework/tests/helper/import2.php create mode 100755 lib/dao/dao.class.php create mode 100755 lib/filter/filter.class.php create mode 100755 lib/pager/pager.class.php diff --git a/framework/control.class.php b/framework/control.class.php new file mode 100755 index 0000000000..c4e61eba7f --- /dev/null +++ b/framework/control.class.php @@ -0,0 +1,488 @@ +. + * + * @copyright Copyright 2009-2010 青岛易软天创网络科技有限公司(www.cnezsoft.com) + * @author Chunsheng Wang + * @package ZenTaoPHP + * @version $Id: control.class.php 133 2010-09-11 07:22:48Z wwccss $ + * @link http://www.zentao.net + */ + +/** + * 控制器基类。 + * + * @package ZenTaoPHP + */ +class control +{ + /** + * 全局的$app对象。 + * + * @var object + * @access protected + */ + protected $app; + + /** + * 全局的$config对象。 + * + * @var object + * @access protected + */ + protected $config; + + /** + * 全局的$lang对象。 + * + * @var object + * @access protected + */ + protected $lang; + + /** + * 全局的$dbh(数据库访问句柄)对象。 + * + * @var object + * @access protected + */ + protected $dbh; + + /** + * dao对象。 + * + * @var object + * @access protected + */ + public $dao; + + /** + * POST对象。 + * + * @var ojbect + * @access public + */ + public $post; + + /** + * get对象。 + * + * @var ojbect + * @access public + */ + public $get; + + /** + * session对象。 + * + * @var ojbect + * @access public + */ + public $session; + + /** + * server对象。 + * + * @var ojbect + * @access public + */ + public $server; + + /** + * cookie对象。 + * + * @var ojbect + * @access public + */ + public $cookie; + + /** + * global对象。 + * + * @var ojbect + * @access public + */ + public $global; + + /** + * 所属模块的名字。 + * + * @var string + * @access protected + */ + protected $moduleName; + + /** + * 记录赋值到view的所有变量。 + * + * @var object + * @access public + */ + public $view; + + /** + * 视图类型 + * + * @var string + * @access private + */ + private $viewType; + + /** + * 要输出的内容。 + * + * @var string + * @access private + */ + private $output; + + /** + * 路径分隔符。 + * + * @var string + * @access protected + */ + protected $pathFix; + + /** + * 构造函数: + * + * 1. 引用全局对象,使之可以通过成员变量访问。 + * 2. 设置模块相应的路径信息,并加载对应的model文件。 + * 3. 自动将$lang和$config赋值到模板。 + * + * @access public + * @return void + */ + public function __construct($moduleName = '', $methodName = '') + { + /* 引用全局对象,并赋值。*/ + global $app, $config, $lang, $dbh; + $this->app = $app; + $this->config = $config; + $this->lang = $lang; + $this->dbh = $dbh; + $this->pathFix = $this->app->getPathFix(); + $this->viewType = $this->app->getViewType(); + + $this->setModuleName($moduleName); + $this->setMethodName($methodName); + + /* 自动加载当前模块的model文件。*/ + $this->loadModel(); + + /* 自动将$app, $config和$lang赋值到模板中。*/ + $this->assign('app', $app); + $this->assign('lang', $lang); + $this->assign('config', $config); + + if(isset($config->super2OBJ) and $config->super2OBJ) $this->setSuperVars(); + } + + //-------------------- model相关的方法。--------------------// + // + /* 设置模块名。*/ + private function setModuleName($moduleName = '') + { + $this->moduleName = $moduleName ? strtolower($moduleName) : $this->app->getModuleName(); + } + + /* 设置方法名。*/ + private function setMethodName($methodName = '') + { + $this->methodName = $methodName ? strtolower($methodName) : $this->app->getMethodName(); + } + + /** + * 加载某一个模块的model文件。 + * + * @param string $moduleName 模块名字,如果为空,则取当前的模块名作为model名。 + * @access public + * @return void + */ + public function loadModel($moduleName = '') + { + /* 如果没有指定module名,则取当前加载的模块的名作为model名。*/ + if(empty($moduleName)) $moduleName = $this->moduleName; + $modelFile = helper::setModelFile($moduleName); + if(!file_exists($modelFile)) return false; + + helper::import($modelFile); + $modelClass = class_exists('ext' . $moduleName. 'model') ? 'ext' . $moduleName . 'model' : $moduleName . 'model'; + if(!class_exists($modelClass)) $this->app->error(" The model $modelClass not found", __FILE__, __LINE__, $exit = true); + + $this->$moduleName = new $modelClass(); + if(isset($this->config->db->dao) and $this->config->db->dao) $this->dao = $this->$moduleName->dao; + return $this->$moduleName; + } + + /** + * 设置超全局变量。 + * + * @access protected + * @return void + */ + protected function setSuperVars() + { + $this->post = $this->app->post; + $this->get = $this->app->get; + $this->server = $this->app->server; + $this->session = $this->app->session; + $this->cookie = $this->app->cookie; + $this->global = $this->app->global; + } + + //-------------------- 加载view相关的方法。--------------------// + /** + * 设置视图文件。 + * + * 某一个module的控制器可以加载另外一个module的视图文件。 + * + * @param string $moduleName 模块名。 + * @param string $methodName 方法名。 + * @access private + * @return string 对应的视图文件。 + */ + private function setViewFile($moduleName, $methodName) + { + $moduleName = strtolower(trim($moduleName)); + $methodName = strtolower(trim($methodName)); + + $modulePath = $this->app->getModulePath($moduleName); + $viewExtPath = $this->app->getModuleExtPath($moduleName, 'view'); + + /* 主视图文件,扩展视图文件和扩展钩子文件。*/ + $mainViewFile = $modulePath . 'view' . $this->pathFix . $methodName . '.' . $this->viewType . '.php'; + $extViewFile = $viewExtPath . $methodName . ".{$this->viewType}.php"; + $extHookFile = $viewExtPath . $methodName . ".{$this->viewType}.hook.php"; + + $viewFile = file_exists($extViewFile) ? $extViewFile : $mainViewFile; + if(!file_exists($viewFile)) $this->app->error("the view file $viewFile not found", __FILE__, __LINE__, $exit = true); + if(file_exists($extHookFile)) return array('viewFile' => $viewFile, 'hookFile' => $extHookFile); + return $viewFile; + } + + /* 加载某一个视图文件所对应的扩展视图文件。*/ + public function getExtViewFile($viewFile) + { + $extPath = dirname(dirname(realpath($viewFile))) . '/opt/view/'; + $extViewFile = $extPath . basename($viewFile); + if(file_exists($extViewFile)) + { + helper::cd($extPath); + return $extViewFile; + } + } + + /** + * 赋值一个变量到view视图。 + * + * @param string $name 赋值到视图文件中的变量名。 + * @param mixed $value 所对应的值。 + * @access public + * @return void + */ + public function assign($name, $value) + { + $this->view->$name = $value; + } + + /** + * 重置output内容。 + * + * @access public + * @return void + */ + public function clear() + { + $this->output = ''; + } + + /** + * 解析视图文件。 + * + * 如果没有指定模块名和方法名,则取当前模块的当前方法。 + * + * @param string $moduleName 模块名。 + * @param string $methodName 方法名。 + * @access public + * @return void + */ + public function parse($moduleName = '', $methodName = '') + { + if(empty($moduleName)) $moduleName = $this->moduleName; + if(empty($methodName)) $methodName = $this->methodName; + + if($this->viewType == 'json') + { + $this->parseJSON($moduleName, $methodName); + } + else + { + $this->parseDefault($moduleName, $methodName); + } + return $this->output; + } + + /* 解析JSON格式的输出。*/ + private function parseJSON($moduleName, $methodName) + { + unset($this->view->app); + unset($this->view->config); + unset($this->view->lang); + unset($this->view->pager); + unset($this->view->header); + unset($this->view->position); + unset($this->view->moduleTree); + + $output['status'] = is_object($this->view) ? 'success' : 'fail'; + $output['data'] = json_encode($this->view); + $output['md5'] = md5(json_encode($this->view)); + $this->output = json_encode($output); + } + + /* 默认的输出。*/ + private function parseDefault($moduleName, $methodName) + { + /* 设置视图文件。*/ + $viewFile = $this->setViewFile($moduleName, $methodName); + if(is_array($viewFile)) extract($viewFile); + + /* 切换到视图文件所在的目录,以保证视图文件中的包含路径有效。*/ + $currentPWD = getcwd(); + chdir(dirname($viewFile)); + + extract((array)$this->view); + ob_start(); + include $viewFile; + if(isset($hookFile)) include $hookFile; + $this->output .= ob_get_contents(); + ob_end_clean(); + + /* 最后还要切换到原来的目录。*/ + chdir($currentPWD); + } + + /** + * 获取某一个模块的某一个方法的内容。 + * + * 如果没有指定模块名,则取当前模块当前方法的视图。如果指定了模块和方法,则调用对应的模块方法的视图内容。 + * + * @param string $moduleName 模块名。 + * @param string $methodName 方法名。 + * @param array $params 方法参数。 + * @access public + * @return string + */ + public function fetch($moduleName = '', $methodName = '', $params = array()) + { + if($moduleName == '') $moduleName = $this->moduleName; + if($methodName == '') $methodName = $this->methodName; + if($moduleName == $this->moduleName and $methodName == $this->methodName) + { + $this->parse($moduleName, $methodName); + return $this->output; + } + + /* 设置被调用的模块的路径及相应的文件。*/ + $modulePath = $this->app->getModulePath($moduleName); + $moduleControlFile = $modulePath . 'control.php'; + $actionExtFile = $this->app->getModuleExtPath($moduleName, 'control') . strtolower($methodName) . '.php'; + $file2Included = file_exists($actionExtFile) ? $actionExtFile : $moduleControlFile; + + /* 加载控制文件。*/ + if(!file_exists($file2Included)) $this->app->error("The control file $file2Included not found", __FILE__, __LINE__, $exit = true); + $currentPWD = getcwd(); + chdir(dirname($file2Included)); + if($moduleName != $this->moduleName) helper::import($file2Included); + + /* 设置要调用的类的名称。*/ + $className = class_exists("ext$moduleName") ? "ext$moduleName" : $moduleName; + if(!class_exists($className)) $this->app->error(" The class $className not found", __FILE__, __LINE__, $exit = true); + + /* 处理参数,生成对象。*/ + if(!is_array($params)) parse_str($params, $params); + $module = new $className($moduleName, $methodName); + + /* 调用方法,获得输出。*/ + ob_start(); + call_user_func_array(array($module, $methodName), $params); + $output = ob_get_contents(); + ob_end_clean(); + + unset($module); + chdir($currentPWD); + return $output; + } + + /** + * 显示视图内容。 + * + * @param string $moduleName 模块名。 + * @param string $methodName 方法名。 + * @access public + * @return void + */ + public function display($moduleName = '', $methodName = '') + { + if(empty($this->output)) $this->parse($moduleName, $methodName); + echo $this->output; + if($this->viewType == 'json') die(); + } + + /** + * 生成某一个模块某个方法的链接。 + * + * @param string $moduleName 模块名。 + * @param string $methodName 方法名。 + * @param mixed $vars 要传递的参数,可以是数组,array('var1'=>'value1')。也可以是var1=value1&var2=value2的形式。 + * @param string $viewType 视图格式。 + * @access public + * @return string + */ + public function createLink($moduleName, $methodName = 'index', $vars = array(), $viewType = '') + { + if(empty($moduleName)) $moduleName = $this->moduleName; + return helper::createLink($moduleName, $methodName, $vars, $viewType); + } + + /** + * 生成对本模块某个方法的链接。 + * + * @param string $methodName 方法名。 + * @param mixed $vars 要传递的参数,可以是数组,array('var1'=>'value1')。也可以是var1=value1&var2=value2的形式。 + * @param string $viewType 视图格式。 + * @access public + * @return string + */ + public function inlink($methodName = 'index', $vars = array(), $viewType = '') + { + return helper::createLink($this->moduleName, $methodName, $vars, $viewType); + } + + /** + * 跳转到另外一个页面。 + * + * @param string $url 要跳转的url地址。 + * @access public + * @return void + */ + public function locate($url) + { + header("location: $url"); + exit; + } +} diff --git a/framework/helper.class.php b/framework/helper.class.php new file mode 100644 index 0000000000..1b89f8cc49 --- /dev/null +++ b/framework/helper.class.php @@ -0,0 +1,387 @@ +. + * + * @copyright Copyright 2009-2010 青岛易软天创网络科技有限公司(www.cnezsoft.com) + * @author Chunsheng Wang + * @package ZenTaoPHP + * @version $Id: helper.class.php 138 2010-10-08 03:40:48Z wwccss $ + * @link http://www.zentao.net + */ +/** + * 工具类对象,存放着各种杂项的工具方法。 + * + * @package ZenTaoPHP + */ +class helper +{ + /** + * 为一个对象设置某一个属性,其中key可以是“father.child”的形式。 + * + * + * db->user = 'wwccss'; + * helper::setMember('lang', 'db.user', 'chunsheng.wang'); + * ?> + * + * @param string $objName 对象变量名。 + * @param string $key 要设置的属性,可以是father.child的形式。 + * @param mixed $value 要设置的值。 + * @static + * @access public + * @return void + */ + static public function setMember($objName, $key, $value) + { + global $$objName; + if(!is_object($$objName) or empty($key)) return false; + $key = str_replace('.', '->', $key); + $value = serialize($value); + $code = ("\$${objName}->{$key}=unserialize(<< + * 'value1', 'var2' => 'value2'); + * ?> + * + * @param string $moduleName 模块名。 + * @param string $methodName 方法名。 + * @param mixed $vars 要传递给method方法的各个参数,可以是数组,也可以是var1=value2&var2=value2的形式。 + * @param string $viewType 扩展名方式。 + * @static + * @access public + * @return string + */ + static public function createLink($moduleName, $methodName = 'index', $vars = '', $viewType = '') + { + global $app, $config; + + $link = $config->webRoot; + if($config->requestType == 'GET') + { + if(strpos($_SERVER['SCRIPT_NAME'], 'index.php') === false) + { + $link = $_SERVER['SCRIPT_NAME']; + } + } + + if(empty($viewType)) $viewType = $app->getViewType(); + + /* 如果传递进来的vars不是数组,尝试将其解析成数组格式。*/ + if(!is_array($vars)) parse_str($vars, $vars); + if($config->requestType == 'PATH_INFO') + { + $link .= "$moduleName{$config->requestFix}$methodName"; + if($config->pathType == 'full') + { + foreach($vars as $key => $value) $link .= "{$config->requestFix}$key{$config->requestFix}$value"; + } + else + { + foreach($vars as $value) $link .= "{$config->requestFix}$value"; + } + /* 如果访问的是/index/index.html,简化为/index.html。*/ + if($moduleName == $config->default->module and $methodName == $config->default->method) $link = $config->webRoot . 'index'; + $link .= '.' . $viewType; + } + elseif($config->requestType == 'GET') + { + $link .= "?{$config->moduleVar}=$moduleName&{$config->methodVar}=$methodName"; + if($viewType != 'html') $link .= "&{$config->viewVar}=" . $viewType; + foreach($vars as $key => $value) $link .= "&$key=$value"; + } + return $link; + } + + /** + * 将一个数组转成对象格式。此函数只是返回语句,需要eval。 + * + * + * + * + * @param array $array 要转换的数组。 + * @param string $objName 要转换成的对象的名字。 + * @param string $memberPath 成员变量路径,最开始为空,从根开始。 + * @param bool $firstRun 是否是第一次运行。 + * @static + * @access public + * @return void + */ + static public function array2Object($array, $objName, $memberPath = '', $firstRun = true) + { + if($firstRun) + { + if(!is_array($array) or empty($array)) return false; + } + static $code = ''; + $keys = array_keys($array); + foreach($keys as $keyNO => $key) + { + $value = $array[$key]; + if(is_int($key)) $key = 'item' . $key; + $memberID = $memberPath . '->' . $key; + if(!is_array($value)) + { + $value = addslashes($value); + $code .= "\$$objName$memberID='$value';\n"; + } + else + { + helper::array2object($value, $objName, $memberID, $firstRun = false); + } + } + return $code; + } + + /** + * 包含一个文件。router.class.php和control.class.php中包含文件都通过此函数来调用,这样保证文件不会重复加载。 + * + * @param string $file 要包含的文件的路径。 + * @static + * @access public + * @return void + */ + static public function import($file) + { + if(!file_exists($file)) return false; + static $includedFiles = array(); + if(!isset($includedFiles[$file])) + { + include $file; + $includedFiles[$file] = true; + return true; + } + return false; + } + + /** + * 设置model文件。 + * + * @param string $moduleName 模块名字。 + * @access private + * @return void + */ + static public function setModelFile($moduleName) + { + global $app; + + /* 设定主model文件和扩展路径,并获得所有的扩展文件。*/ + $mainModelFile = $app->getModulePath($moduleName) . 'model.php'; + $modelExtPath = $app->getModuleExtPath($moduleName, 'model'); + $extFiles = helper::ls($modelExtPath, '.php'); + + /* 不存在扩展文件,返回主配置文件。*/ + if(empty($extFiles)) return $mainModelFile; + + /* 存在扩展文件,判断是否需要更新。*/ + $mergedModelFile = $app->getTmpRoot() . 'model' . $app->getPathFix() . $moduleName . '.php'; + $needUpdate = false; + $lastTime = file_exists($mergedModelFile) ? filemtime($mergedModelFile) : 0; + + if(filemtime($mainModelFile) > $lastTime) + { + $needUpdate = true; + } + else + { + foreach($extFiles as $extFile) + { + if(filemtime($extFile) > $lastTime) + { + $needUpdate = true; + break; + } + } + } + + /* 如果不需要更新,则直接返回合并之后的model文件。*/ + if(!$needUpdate) return $mergedModelFile; + + if($needUpdate) + { + /* 加载主的model文件,并获得其方法列表。*/ + helper::import($mainModelFile); + $modelMethods = get_class_methods($moduleName . 'model'); + foreach($modelMethods as $key => $modelMethod) $modelMethods[$key] = strtolower($modelMethod); + + /* 将主model文件读入数组。*/ + $modelLines = rtrim(file_get_contents($mainModelFile)); + $modelLines = rtrim($modelLines, '?>'); + $modelLines = rtrim($modelLines); + $modelLines = explode("\n", $modelLines); + $lines2Delete = array(count($modelLines) - 1); + $lines2Append = array(); + + /* 循环处理每个扩展方法文件。*/ + foreach($extFiles as $extFile) + { + $methodName = strtolower(basename($extFile, '.php')); + if(in_array($methodName, $modelMethods)) + { + $method = new ReflectionMethod($moduleName . 'model', $methodName); + $startLine = $method->getStartLine() - 1; + $endLine = $method->getEndLine() - 1; + $lines2Delete = array_merge($lines2Delete, range($startLine, $endLine)); + } + $extLines = explode("\n", ltrim(trim(file_get_contents($extFile)), 'getModuleName(), $methodName, $vars, $viewType); +} + +/* 循环一个数组。*/ +function cycle($items) +{ + static $i = 0; + if(!is_array($items)) $items = explode(',', $items); + if(!isset($items[$i])) $i = 0; + return $items[$i++]; +} diff --git a/framework/model.class.php b/framework/model.class.php new file mode 100755 index 0000000000..5ab777afd4 --- /dev/null +++ b/framework/model.class.php @@ -0,0 +1,228 @@ +. + * + * @copyright Copyright 2009-2010 青岛易软天创网络科技有限公司(www.cnezsoft.com) + * @author Chunsheng Wang + * @package ZenTaoPHP + * @version $Id: model.class.php 135 2010-09-14 03:23:35Z yuren_@126.com $ + * @link http://www.zentao.net + */ +/** + * 模型基类。 + * + * @package ZenTaoPHP + */ +class model +{ + /** + * 全局的$app对象。 + * + * @var object + * @access protected + */ + protected $app; + + /** + * 全局的$config对象。 + * + * @var object + * @access protected + */ + protected $config; + + /** + * 全局的$lang对象。 + * + * @var object + * @access protected + */ + protected $lang; + + /** + * 全局的$dbh(数据库访问句柄)对象。 + * + * @var object + * @access protected + */ + protected $dbh; + + /** + * dao对象。 + * + * @var object + * @access protected + */ + public $dao; + + /** + * POST对象。 + * + * @var ojbect + * @access public + */ + public $post; + + /** + * get对象。 + * + * @var ojbect + * @access public + */ + public $get; + + /** + * session对象。 + * + * @var ojbect + * @access public + */ + public $session; + + /** + * server对象。 + * + * @var ojbect + * @access public + */ + public $server; + + /** + * cookie对象。 + * + * @var ojbect + * @access public + */ + public $cookie; + + /** + * global对象。 + * + * @var ojbect + * @access public + */ + public $global; + + /** + * 构造函数: + * + * 1. 引用全局变量,使之可以通过成员属性访问。 + * 2. 设置当前模块的路径、配置、语言等信息,并加载相应的文件。 + * + * @access public + * @return void + */ + public function __construct() + { + global $app, $config, $lang, $dbh; + $this->app = $app; + $this->config = $config; + $this->lang = $lang; + $this->dbh = $dbh; + + $moduleName = $this->getModuleName(); + $this->app->loadLang($moduleName, $exit = false); + $this->app->loadConfig($moduleName, $exit = false); + + if(isset($config->db->dao) and $config->db->dao) $this->loadDAO(); + if(isset($config->super2OBJ) and $config->super2OBJ) $this->setSuperVars(); + } + + /** + * 设置模块名:将类名中的model替换掉即为模块名。 + * 没有使用$app->getModule()方法,因为它返回的是当前调用的模块。 + * 而在一次请求中,当前模块的control文件很有可能会调用其他模块的model。 + * + * @access protected + * @return void + */ + protected function getModuleName() + { + $parentClass = get_parent_class($this); + $selfClass = get_class($this); + $className = $parentClass == 'model' ? $selfClass : $parentClass; + return strtolower(str_ireplace(array('ext', 'Model'), '', $className)); + } + + /** + * 设置超全局变量。 + * + * @access protected + * @return void + */ + protected function setSuperVars() + { + $this->post = $this->app->post; + $this->get = $this->app->get; + $this->server = $this->app->server; + $this->cookie = $this->app->cookie; + $this->session = $this->app->session; + $this->global = $this->app->global; + } + + /** + * 加载某一个模块的model文件。 + * + * @param string $moduleName 模块名字,如果为空,则取当前的模块名作为model名。 + * @access public + * @return void + */ + public function loadModel($moduleName) + { + if(empty($moduleName)) return false; + $modelFile = helper::setModelFile($moduleName); + if(!file_exists($modelFile)) return false; + + helper::import($modelFile); + $modelClass = class_exists('ext' . $moduleName. 'model') ? 'ext' . $moduleName . 'model' : $moduleName . 'model'; + if(!class_exists($modelClass)) $this->app->error(" The model $modelClass not found", __FILE__, __LINE__, $exit = true); + + $this->$moduleName = new $modelClass(); + return $this->$moduleName; + } + + //-------------------- 数据库操作相应的方法。--------------------// + + /** + * 加载DAO类,并返回对象。 + * + * @access private + * @return void + */ + private function loadDAO() + { + $this->dao = $this->app->loadClass('dao'); + } + + /* 将一条记录标记为已删除。*/ + public function delete($table, $id) + { + $this->dao->update($table)->set('deleted')->eq(1)->where('id')->eq($id)->exec(); + $object = str_replace($this->config->db->prefix, '', $table); + $this->loadModel('action')->create($object, $id, 'deleted', '', $extra = ACTIONMODEL::CAN_UNDELETED); + } + + /* 还原已经标记为删除的记录。*/ + public function undelete($actionID) + { + $action = $this->loadModel('action')->getById($actionID); + if($action->action != 'deleted') return; + $table = $this->config->action->objectTables[$action->objectType]; + $this->dao->update($table)->set('deleted')->eq(0)->where('id')->eq($action->objectID)->exec(); + $this->dao->update(TABLE_ACTION)->set('extra')->eq(ACTIONMODEL::BE_UNDELETED)->where('id')->eq($actionID)->exec(); + $this->action->create($action->objectType, $action->objectID, 'undeleted'); + } +} diff --git a/framework/router.class.php b/framework/router.class.php new file mode 100755 index 0000000000..3a7bf5b6f2 --- /dev/null +++ b/framework/router.class.php @@ -0,0 +1,1574 @@ +. + * + * @copyright Copyright 2009-2010 青岛易软天创网络科技有限公司(www.cnezsoft.com) + * @author Chunsheng Wang + * @package ZenTaoPHP + * @version $Id: router.class.php 133 2010-09-11 07:22:48Z wwccss $ + * @link http://www.zentao.net + */ +/** + * 路由类,也是整个框架最核心的类。 + * + * @package ZenTaoPHP + */ +class router +{ + /** + * 文件系统的路径分隔符。 + * + * @var string + * @access private + */ + private $pathFix; + + /** + * 应用的基准路径。 + * + * @var string + * @access private + */ + private $basePath; + + /** + * 框架基类文件所在的路径。 + * + * @var string + * @access private + */ + private $frameRoot; + + /** + * 框架所带的library目录。 + * + * @var string + * @access private + */ + private $coreLibRoot; + + /** + * 当前应用程序所在的目录。 + * + * @var string + * @access private + */ + private $appRoot; + + /** + * 应用程序的library目录。 + * + * @var string + * @access private + */ + private $appLibRoot; + + /** + * 临时文件所在的目录 + * + * @var string + * @access private + */ + private $tmpRoot; + + /** + * 缓存文件所在的根目录。 + * + * @var string + * @access private + */ + private $cacheRoot; + + /** + * 日志文件所在的目录。 + * + * @var string + * @access private + */ + private $logRoot; + + /** + * 配置文件所在的根目录。 + * + * @var string + * @access private + */ + private $configRoot; + + /** + * 各个模块所在的根目录。 + * + * @var string + * @access private + */ + private $moduleRoot; + + /** + * 主题文件所在的根目录。 + * + * @var string + * @access private + */ + private $themeRoot; + + /** + * 用户所使用的语言。 + * + * @var string + * @access private + */ + private $clientLang; + + /** + * 用户所使用的主题。 + * + * @var string + * @access private + */ + private $clientTheme; + + /** + * 当前需要加载的模块名称。 + * + * @var string + * @access private + */ + private $moduleName; + + /** + * 当前模块的扩展目录。 + * + * @var string + * @access private + */ + private $moduleExtRoot; + + /** + * 当前模块所对应的控制器文件。 + * + * @var string + * @access private + */ + private $controlFile; + + /** + * 需要调用的方法。 + * + * @var string + * @access private + */ + private $methodName; + + /** + * 当前模块所对应的派生出来的action文件。 + * + * @var string + * @access private + */ + private $extActionFile; + + /** + * 当前请求的URI。 + * + * @var string + * @access private + */ + private $URI; + + /** + * 要传递给给调用方法的参数。 + * + * @var array + * @access private + */ + private $params; + + /** + * 视图格式。 + * + * @var string + * @access private + */ + private $viewType; + + /** + * 配置对象。 + * + * @var string + * @access private + */ + public $config; + + /** + * 语言对象。 + * + * @var string + * @access private + */ + public $lang; + + /** + * 数据库访问对象。 + * + * @var string + * @access private + */ + public $dbh; + + /** + * POST对象。 + * + * @var ojbect + * @access public + */ + public $post; + + /** + * GET对象。 + * + * @var ojbect + * @access public + */ + public $get; + + /** + * session对象。 + * + * @var ojbect + * @access public + */ + public $session; + + /** + * server对象。 + * + * @var ojbect + * @access public + */ + public $server; + + /** + * cookie对象。 + * + * @var ojbect + * @access public + */ + public $cookie; + + + /** + * global对象。 + * + * @var ojbect + * @access public + */ + public $global; + + /** + * 构造函数。 + * + * 主要完成各个路径变量的设置。注意,该构造函数为私有函数,应当使用createApp方法来实例化路由对象。 + * + * @param string $appName 应用的名称,如果没有指定$appRoot变量,系统会根据$appName来计算应用的根目录。 + * @param string $appRoot 应用所在的根目录。 + * @access protected + * @return void + */ + protected function __construct($appName = 'demo', $appRoot = '') + { + $this->setPathFix(); + $this->setBasePath(); + $this->setFrameRoot(); + $this->setCoreLibRoot(); + $this->setAppRoot($appName, $appRoot); + $this->setAppLibRoot(); + $this->setTmpRoot(); + $this->setCacheRoot(); + $this->setLogRoot(); + $this->setConfigRoot(); + $this->setModuleRoot(); + $this->setThemeRoot(); + } + + /** + * 生成一个应用。 + * + * + * + * 或者指定demo应用所在的目录。 + * + * + * @param string $appName 应用的名称 + * @param string $appRoot 应用所在的根目录,可以为空。 + * @param string $className 对象名称,当从router派生一个子类,然后调用该方法时,可以指定该参数。 + * @static + * @access public + * @return void + */ + public static function createApp($appName = 'demo', $appRoot = '', $className = 'router') + { + if(empty($className)) $className = __CLASS__; + return new $className($appName, $appRoot); + } + + //-------------------- 路径相关的方法。--------------------// + + /** + * 设置路径分隔符,方便调用。 + * + * @access protected + * @return void + */ + protected function setPathFix() + { + $this->pathFix = DIRECTORY_SEPARATOR; + } + + /** + * 设置整个框架所在的根目录。 + * + * @access protected + * @return void + */ + protected function setBasePath() + { + $this->basePath = realpath(dirname(dirname(__FILE__))) . $this->pathFix; + } + + /** + * 设置框架核心类文件所在的根目录。 + * + * @access protected + * @return void + */ + protected function setFrameRoot() + { + $this->frameRoot = $this->basePath . 'framework' . $this->pathFix; + } + + /** + * 设置coreLib文件的根目录。 + * + * @access protected + * @return void + */ + protected function setCoreLibRoot() + { + $this->coreLibRoot = $this->basePath . 'lib' . $this->pathFix; + } + + /** + * 设置应用程序所在的根目录。 + * + * 默认情况下面根据appName来进行计算,如果指定了appRoot,直接用之。 + * 通过这种机制,框架和应用可以分开部署。 + * + * @param string $appName + * @param string $appRoot + * @access protected + * @return void + */ + protected function setAppRoot($appName = 'demo', $appRoot = '') + { + if(empty($appRoot)) + { + $this->appRoot = $this->basePath . 'app' . $this->pathFix . $appName . $this->pathFix; + } + else + { + $this->appRoot = realpath($appRoot) . $this->pathFix; + } + if(!is_dir($this->appRoot)) $this->error("The app you call not noud in {$this->appRoot}", __FILE__, __LINE__, $exit = true); + } + + /** + * 设置appLib文件的根目录。 + * + * @access protected + * @return void + */ + protected function setAppLibRoot() + { + $this->appLibRoot = $this->appRoot . 'lib' . $this->pathFix; + } + + /** + * 设置临时文件所在的目录。 + * + * @access protected + * @return void + */ + protected function setTmpRoot() + { + $this->tmpRoot = $this->appRoot . 'tmp' . $this->pathFix; + } + + /** + * 设置缓存文件所在的根目录。 + * + * @access protected + * @return void + */ + protected function setCacheRoot() + { + $this->cacheRoot = $this->tmpRoot . 'cache' . $this->pathFix; + } + + /** + * 设置日志文件所在的目录。 + * + * @access protected + * @return void + */ + protected function setLogRoot() + { + $this->logRoot = $this->tmpRoot . 'log' . $this->pathFix; + } + + /** + * 设置配置文件所在的根目录。 + * + * @access protected + * @return void + */ + protected function setConfigRoot() + { + $this->configRoot = $this->appRoot . 'config' . $this->pathFix; + } + + /** + * 设置module所在的根目录。 + * + * @access protected + * @return void + */ + protected function setModuleRoot() + { + $this->moduleRoot = $this->appRoot . 'module' . $this->pathFix; + } + + /** + * 设置客户端主题文件所在的根目录。 + * + * @access protected + * @return void + */ + protected function setThemeRoot() + { + $this->themeRoot = $this->appRoot . 'www' . $this->pathFix . 'theme' . $this->pathFix; + } + + /** + * 设置超全局变量。 + * + * @access protected + * @return void + */ + public function setSuperVars() + { + if(isset($this->config->super2OBJ) and $this->config->super2OBJ) + { + $this->post = new super('post'); + $this->get = new super('get'); + $this->server = new super('server'); + $this->cookie = new super('cookie'); + $this->session = new super('session'); + $this->global = new super('global'); + } + } + + /* 设置debug级别。*/ + public function setDebug() + { + isset($this->config->debug) and $this->config->debug ? error_reporting(E_ALL & ~ E_STRICT) : error_reporting(0); + } + + /* 设置时区。*/ + public function setTimezone() + { + if(isset($this->config->timezone)) date_default_timezone_set($this->config->timezone); + } + + /** + * 返回路径分隔符。 + * + * @access public + * @return string + */ + public function getPathFix() + { + return $this->pathFix; + } + + /** + * 返回整个框架的所在的目录。 + * + * @access public + * @return string + */ + public function getBasePath() + { + return $this->basePath; + } + + /** + * 返回框架核心类文件所在的根目录。 + * + * @access public + * @return string + */ + public function getFrameRoot() + { + return $this->frameRoot; + } + + /** + * 返回lib文件所在的根目录。 + * + * @access public + * @return string + */ + public function getCoreLibRoot() + { + return $this->coreLibRoot; + } + + /** + * 返回应用程序所在的根目录。 + * + * @access public + * @return string + */ + public function getAppRoot() + { + return $this->appRoot; + } + + /** + * 返回appLib文件所在的根目录。 + * + * @access public + * @return string + */ + public function getAppLibRoot() + { + return $this->appLibRoot; + } + + /** + * 返回临时文件所在的目录。 + * + * @access public + * @return string + */ + public function getTmpRoot() + { + return $this->tmpRoot; + } + + /** + * 返回缓存文件所在的根目录。 + * + * @access public + * @return string + */ + public function getCacheRoot() + { + return $this->cacheRoot; + } + + /** + * 返回日志文件所在的目录。 + * + * @access public + * @return string + */ + public function getLogRoot() + { + return $this->logRoot; + } + + /** + * 返回配置文件所在的根目录。 + * + * @access public + * @return string + */ + public function getConfigRoot() + { + return $this->configRoot; + } + + /** + * 返回模块文件所在的根目录。 + * + * @access public + * @return string + */ + public function getModuleRoot() + { + return $this->moduleRoot; + } + + /** + * 返回主题文件所在的根目录。 + * + * @access public + * @return string + */ + public function getThemeRoot() + { + return $this->themeRoot; + } + + //-------------------- 客户端环境设置。--------------------// + + /** + * 设置客户端所使用的语言。 + * + * 如果手工指定了语言的选项,则以手工指定为主。 + * 然后再查找session里面是否有登记, + * 然后再看cookie里面是否有登记。 + * 然后再查看浏览器支持的语言, + * 如果通过上面取出的语言选项和系统支持的不匹配,则使用默认的语言。 + * + * @param string $lang 形如zh-cn|zh-tw|zh-hk|en。 + * @access public + * @return void + */ + public function setClientLang($lang = '') + { + if(!empty($lang)) + { + $this->clientLang = $lang; + } + elseif(isset($_SESSION['lang'])) + { + $this->clientLang = $_SESSION['lang']; + } + elseif(isset($_COOKIE['lang'])) + { + $this->clientLang = $_COOKIE['lang']; + } + elseif(isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) + { + $this->clientLang = substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, strpos($_SERVER['HTTP_ACCEPT_LANGUAGE'], ',')); + } + if(!empty($this->clientLang)) + { + $this->clientLang = strtolower($this->clientLang); + if(!isset($this->config->langs[$this->clientLang])) $this->clientLang = $this->config->default->lang; + } + else + { + $this->clientLang = $this->config->default->lang; + } + setcookie('lang', $this->clientLang, $this->config->cookieLife, $this->config->cookiePath); + } + + /** + * 返回客户端使用的语言。 + * + * @access public + * @return string + */ + public function getClientLang() + { + return $this->clientLang; + } + + /** + * 设置客户端所使用的主题。逻辑同setClientLang(); + * + * 主题风格所对应的样式表、图片等文件应当存放在www/theme/,分目录存放。目录的名字就是风格的名字。 + * + * @param string $theme 主题风格。 + * @access public + * @return void + */ + public function setClientTheme($theme = '') + { + if(!empty($theme)) + { + $this->clientTheme = $theme; + } + elseif(isset($_COOKIE['theme'])) + { + $this->clientTheme = $_COOKIE['theme']; + } + elseif(isset($this->config->client->theme)) + { + $this->clientTheme = $this->config->client->theme; + } + + if(!empty($this->clientTheme)) + { + $this->clientTheme = strtolower($this->clientTheme); + if(strpos($this->config->themes, $this->clientTheme) === false) + { + $this->clientTheme = $this->config->default->theme; + } + } + else + { + $this->clientTheme = $this->config->default->theme; + } + setcookie('theme', $this->clientTheme, $this->config->cookieLife, $this->config->cookiePath); + } + + /** + * 返回客户端所使用的主题。 + * + * @access public + * @return string + */ + public function getClientTheme() + { + return $this->config->webRoot . 'theme/' . $this->clientTheme . '/'; + } + + /** + * 返回web的根目录。 + * + * @access public + * @return string + */ + public function getWebRoot() + { + return $this->config->webRoot; + } + + //-------------------- 解析请求。--------------------// + + /** + * 处理请求,分为PATH_INFO和GET两种模式。 + * + * @access public + * @return void + */ + public function parseRequest() + { + if($this->config->requestType == 'PATH_INFO') + { + $this->parsePathInfo(); + $this->setRouteByPathInfo(); + } + elseif($this->config->requestType == 'GET') + { + $this->parseGET(); + $this->setRouteByGET(); + } + else + { + $this->error("The request type {$this->config->requestType} not supported", __FILE__, __LINE__, $exit = true); + } + } + + /** + * 从请求中得出PATH_INFO信息。 + * + * @access public + * @return void + */ + public function parsePathInfo() + { + $pathInfo = $this->getPathInfo('PATH_INFO'); + if(empty($pathInfo)) $pathInfo = $this->getPathInfo('ORIG_PATH_INFO'); + if(!empty($pathInfo)) + { + $dotPos = strpos($pathInfo, '.'); + if($dotPos) + { + $this->URI = substr($pathInfo, 0, $dotPos); + $this->viewType = substr($pathInfo, $dotPos + 1); + if(strpos($this->config->views, ',' . $this->viewType . ',') === false) + { + $this->viewType = $this->config->default->view; + } + } + else + { + $this->URI = $pathInfo; + $this->viewType = $this->config->default->view; + } + } + else + { + $this->viewType = $this->config->default->view; + } + } + + /** + * 辅助函数:从env或者_SERVER变量中获取某个PATH_INFO的变种。 + * + * @param string $varName 目前支持PATH_INFO + * @access private + * @return string + */ + private function getPathInfo($varName) + { + $value = @getenv($varName); + if(isset($_SERVER[$varName])) $value = $_SERVER[$varName]; + return trim($value, '/'); + } + + /** + * 解析通过GET方式传递过来的参数。 + * + * @access private + * @return void + */ + private function parseGET() + { + if(isset($_GET[$this->config->viewVar])) + { + $this->viewType = $_GET[$this->config->viewVar]; + if(strpos($this->config->views, ',' . $this->viewType . ',') === false) + { + $this->viewType = $this->config->default->view; + } + } + else + { + $this->viewType = $this->config->default->view; + } + $this->URI = $_SERVER['REQUEST_URI']; + } + + /** + * 返回当前请求的URI。 + * + * @access public + * @return string + */ + public function getURI($full = false) + { + if($full and $this->config->requestType == 'PATH_INFO') + { + if($this->URI) return $this->config->webRoot . $this->URI . '.' . $this->viewType; + return $this->config->webRoot; + } + return $this->URI; + } + + /** + * 返回当前请求的viewType。 + * + * @access public + * @return string + */ + public function getViewType() + { + return $this->viewType; + } + + //-------------------- 路由相关的方法。--------------------// + + /** + * 加载公共的common模块。 + * + * 该公共模块可以来完成一些公用的任务,比如启动session,进行用户权限验证等。 + * 该方法没有自动调用,如果需要,可以在router文件中自己加入该逻辑。 + * 但需要注意的是该方法调用应当在lang, config, dbh等对象加载完成之后。 + * + * @access public + * @return object + */ + public function loadCommon() + { + $this->setModuleName('common'); + if($this->setControlFile($exitIfNone = false)) + { + include $this->controlFile; + if(class_exists('common')) + { + return new common(); + } + else + { + return false; + } + } + } + + /** + * 设置要调用的模块。 + * + * @param string $moduleName 模块名字。 + * @access public + * @return void + */ + public function setModuleName($moduleName = '') + { + $this->moduleName = strtolower($moduleName); + } + + /** + * 设置要加载的控制器文件。 + * + * @param bool $exitIfNone 如果没有发现控制器文件,是否退出。默认值true。 + * @access public + * @return bool + */ + public function setControlFile($exitIfNone = true) + { + $this->controlFile = $this->moduleRoot . $this->moduleName . $this->pathFix . 'control.php'; + if(!file_exists($this->controlFile)) + { + $this->error("the control file $this->controlFile not found.", __FILE__, __LINE__, $exitIfNone); + return false; + } + return true; + } + + /** + * 设置要调用的方法。 + * + * @param string $methodName 调用的方法名。 + * @access public + * @return void + */ + public function setMethodName($methodName = '') + { + $this->methodName = strtolower($methodName); + } + + /* 获得某一个模块的根目录。*/ + public function getModulePath($moduleName = '') + { + if($moduleName == '') $moduleName = $this->moduleName; + return $this->getModuleRoot() . strtolower(trim($moduleName)) . $this->pathFix; + } + + /* 获得某一个模块,某种扩展的目录。ext可选值:control, model, view, lang, config。*/ + public function getModuleExtPath($moduleName, $ext) + { + return $this->getModuleRoot() . strtolower(trim($moduleName)) . $this->pathFix . 'opt' . $this->pathFix . $ext . $this->pathFix; + } + + /** + * 设置当前调用方法对应的扩展文件。 + * + * @access public + * @return bool + */ + public function setActionExtFile() + { + $moduleExtPath = $this->getModuleExtPath($this->moduleName, 'control'); + $this->extActionFile = $moduleExtPath . $this->methodName . '.php'; + return file_exists($this->extActionFile); + } + + /** + * 根据PATH_INFO信息设置路由。 + * + * @access public + * @return void + */ + public function setRouteByPathInfo() + { + if(!empty($this->URI)) + { + /* URL中含有参数信息。*/ + if(strpos($this->URI, $this->config->requestFix) !== false) + { + $items = explode($this->config->requestFix, $this->URI); + $this->setModuleName($items[0]); + $this->setMethodName($items[1]); + } + else + { + $this->setModuleName($this->URI); + $this->setMethodName($this->config->default->method); // 取默认的方法。 + } + } + else + { + $this->setModuleName($this->config->default->module); // 取默认的模块。 + $this->setMethodName($this->config->default->method); // 取默认的方法。 + } + $this->setControlFile(); + } + + /** + * 通过GET变量来设置路由信息。 + * + * @access public + * @return void + */ + public function setRouteByGET() + { + $moduleName = isset($_GET[$this->config->moduleVar]) ? strtolower($_GET[$this->config->moduleVar]) : $this->config->default->module; + $methodName = isset($_GET[$this->config->methodVar]) ? strtolower($_GET[$this->config->methodVar]) : $this->config->default->method; + $this->setModuleName($moduleName); + $this->setControlFile(); + $this->setMethodName($methodName); + } + + /** + * 加载模块。 + * + * @access public + * @return void + */ + public function loadModule() + { + $moduleName = $this->moduleName; + $methodName = $this->methodName; + + /* 设置要加载的文件。*/ + $file2Included = $this->setActionExtFile() ? $this->extActionFile : $this->controlFile; + chdir(dirname($file2Included)); + include $file2Included; + + /* 设置要调用的类的名称。*/ + $className = class_exists("my$moduleName") ? "my$moduleName" : $moduleName; + if(!class_exists($className)) $this->error("the control $className not found", __FILE__, __LINE__, $exit = true); + + $module = new $className(); + if(!method_exists($module, $methodName)) $this->error("the module $moduleName has no $methodName method", __FILE__, __LINE__, $exit = true); + + /* 获取方法的参数定义。*/ + $defaultParams = array(); + $methodReflect = new reflectionMethod($className, $methodName); + foreach($methodReflect->getParameters() as $param) + { + $name = $param->getName(); + $default = $param->isDefaultValueAvailable() ? $param->getDefaultValue() : '_NOT_SET'; + $defaultParams[$name] = $default; + } + + /* 设置参数。*/ + if($this->config->requestType == 'PATH_INFO') + { + $this->setParamsByPathInfo($defaultParams); + } + elseif($this->config->requestType == 'GET') + { + $this->setParamsByGET($defaultParams); + } + + /* 调用相应的方法。*/ + call_user_func_array(array(&$module, $methodName), $this->params); + return $module; + } + + /** + * 通过PATH_INFO信息来设置需要传递给control类方法的参数。 + * + * @param array $defaultParams 方法定义中默认值列表 + * @access public + * @return void + */ + public function setParamsByPathInfo($defaultParams = array()) + { + /* 将请求串按照分割符分开。*/ + $items = explode($this->config->requestFix, $this->URI); + $itemCount = count($items); + + /** + * items前面两个元素分别为moduleName和methodName,因此从第二个元素开始。 + * 分别为clean模式和full模式。 + */ + + $params = array(); + if($this->config->pathType == 'clean') + { + for($i = 2; $i < $itemCount; $i ++) + { + $key = key($defaultParams); + $params[$key] = $items[$i]; + next($defaultParams); + } + } + elseif($this->config->pathType == 'full') + { + for($i = 2; $i < $itemCount; $i += 2) + { + $key = $items[$i]; + $value = $items[$i + 1]; + $params[$key] = $value; + } + } + $this->params = $this->mergeParams($defaultParams, $params); + } + + /** + * 通过GET变量设置需要传递给control类访问的参数。 + * + * @param array $defaultParams 方法定义中默认值列表。 + * @access public + * @return void + */ + public function setParamsByGET($defaultParams) + { + unset($_GET[$this->config->moduleVar]); + unset($_GET[$this->config->methodVar]); + unset($_GET[$this->config->viewVar]); + unset($_GET[$this->config->sessionVar]); + $this->params = $this->mergeParams($defaultParams, $_GET); + } + + /** + * 将方法定义中的默认值与用户请求中传递的值合并起来。 + * + * @param array $defaultParams 方法定义中的参数默认值列表。 + * @param array $passedParams 用户请求中传递的参数列表。 + * @access private + * @return array + */ + private function mergeParams($defaultParams, $passedParams) + { + /* 如果参数传递是非严格模式,认为passedParams的顺序和defaultParams是一致的。*/ + if(!isset($this->config->strictParams) or $this->config->strictParams == false) + { + $passedParams = array_values($passedParams); + $i = 0; + foreach($defaultParams as $key => $defaultValue) + { + if(isset($passedParams[$i])) + { + $defaultParams[$key] = $passedParams[$i]; + } + else + { + if($defaultValue === '_NOT_SET') $this->error("The param '$key' should pass value. ", __FILE__, __LINE__, $exit = true); + } + $i ++; + } + } + else + { + foreach($defaultParams as $key => $defaultValue) + { + if(isset($passedParams[$key])) + { + $defaultParams[$key] = $passedParams[$key]; + } + else + { + if($defaultValue === '_NOT_SET') $this->error("The param '$key' should pass value. ", __FILE__, __LINE__, $exit = true); + } + } + } + return $defaultParams; + } + + /** + * 返回当前调用的模块名称。 + * + * @access public + * @return string + */ + public function getModuleName() + { + return $this->moduleName; + } + + /** + * 返回control文件路径。 + * + * @access public + * @return string + */ + public function getControlFile() + { + return $this->controlFile; + } + + /** + * 返回当前调用的control的方法。 + * + * @access public + * @return string + */ + public function getMethodName() + { + return $this->methodName; + } + + /** + * 返回当前传递的参数名。 + * + * @access public + * @return string + */ + public function getParams() + { + return $this->params; + } + + //-------------------- 工具类方法。-------------------// + + /** + * 错误处理函数。 + * + * @param string $message 错误信息。 + * @param string $file 发生错误的文件名。 + * @param int $line 发生错误的行号。 + * @param bool $exit 是否中止程序。 + * @access public + * @return void + */ + public function error($message, $file, $line, $exit = false) + { + /* 记录错误信息。*/ + $log = "ERROR: $message in $file on line $line"; + if(isset($_SERVER['SCRIPT_URI'])) $log .= ", request: $_SERVER[SCRIPT_URI]";; + $trace = debug_backtrace(); + extract($trace[0]); + extract($trace[1]); + $log .= ", last called by $file on $line through function $function."; + error_log($log); + + /* 如果需要终止程序,则显示到终端用户的屏幕上。*/ + if($exit) + { + if($this->config->debug) die("$log"); + die(); + } + } + + /** + * 加载某一个类文件。 + * + * 该方法会首先尝试从appLibRoot下面查找,然后再到coreLibRoot下面查找。 + * + * @param mixed $className 类名。 + * @param mixed $static 是否为静态类。 + * @access public + * @return object 以类名为名的对象。 + */ + public function loadClass($className, $static = false) + { + $className = strtolower($className); + + /* 先试着加载appLibRoot下面的类文件。*/ + $classFile = $this->appLibRoot . $className; + if(is_dir($classFile)) $classFile .= $this->pathFix . $className; + $classFile .= '.class.php'; + + if(!file_exists($classFile)) + { + /* 再试着加载coreLibRoot下面的类文件。*/ + $classFile = $this->coreLibRoot . $className; + if(is_dir($classFile)) $classFile .= $this->pathFix . $className; + $classFile .= '.class.php'; + if(!file_exists($classFile)) $this->error("class file $classFile not found", __FILE__, __LINE__, $exit = true); + } + + helper::import($classFile); + + /* 如果是静态类的话,无需实例化,直接退出。*/ + if($static) return; + + /* 实例化,生成对象。*/ + global $$className; + if(!class_exists($className)) $this->error("the class $className not found in $classFile", __FILE__, __LINE__, $exit = true); + if(!is_object($$className)) $$className = new $className(); + return $$className; + } + + /** + * 加载配置文件,将其转换为对象,并返回作为全局的配置对象。 + * + * 如果模块的名字为common,则从配置的根目录查找,其他的模块则从模块路径下面查找。 + * + * @param mixed $moduleName 模块的名字。 + * @param bool $exitIfNone 如果主配置文件不存在,是否退出。 + * @access public + * @return object + */ + public function loadConfig($moduleName, $exitIfNone = true) + { + /* 设置模块对应的主配置文件和扩展配置文件。*/ + if($moduleName == 'common') + { + $mainConfigFile = $this->configRoot . 'config.php'; + $extConfigFiles = array(); + } + else + { + $mainConfigFile = $this->moduleRoot . $moduleName . $this->pathFix . 'config.php'; + $extConfigPath = $this->getModuleExtPath($moduleName, 'config'); + $extConfigFiles = helper::ls($extConfigPath, '.php'); + } + + /* 主配置文件不存在。*/ + if(!file_exists($mainConfigFile)) + { + if($exitIfNone) self::error("config file $mainConfigFile not found", __FILE__, __LINE__, true); + if(empty($extConfigFiles)) return; // 没有扩展配置文件,退出。 + $configFiles = $extConfigFiles; + } + else + { + $configFiles = array_merge(array($mainConfigFile), $extConfigFiles); + } + + global $config; + if(!is_object($config)) $config = new config(); + static $loadedConfigs = array(); + foreach($configFiles as $configFile) + { + if(in_array($configFile, $loadedConfigs)) continue; + include $configFile; + $loadedConfigs[] = $configFile; + } + $this->config = $config; + return $config; + } + + /* 将系统的配置参数输出为json格式,方便客户端调用。*/ + public function exportConfig() + { + $view->version = $this->config->version; + $view->requestType = $this->config->requestType; + $view->pathType = $this->config->pathType; + $view->requestFix = $this->config->requestFix; + $view->moduleVar = $this->config->moduleVar; + $view->methodVar = $this->config->methodVar; + $view->viewVar = $this->config->viewVar; + $view->sessionVar = $this->config->sessionVar; + echo json_encode($view); + } + + /** + * 加载语言文件,将其转换为对象,并返回作为全局的语言对象。 + * + * @param mixed $moduleName + * @access public + * @return void + */ + public function loadLang($moduleName) + { + $mainLangFile = $this->moduleRoot . $moduleName . $this->pathFix . 'lang' . $this->pathFix . $this->clientLang . '.php'; + $extLangPath = $this->getModuleExtPath($moduleName, 'lang'); + $extLangFiles = helper::ls($extLangPath . $this->clientLang, '.php'); + + /* 主配置文件不存在。*/ + if(!file_exists($mainLangFile)) + { + if(empty($extLangFiles)) return; // 没有扩展配置文件,退出。 + $langFiles = $extLangFiles; + } + else + { + $langFiles = array_merge(array($mainLangFile), $extLangFiles); + } + + global $lang; + if(!is_object($lang)) $lang = new language(); + + static $loadedLangs = array(); + foreach($langFiles as $langFile) + { + if(in_array($langFile, $loadedLangs)) continue; + include $langFile; + $loadedLangs[] = $langFile; + } + + $this->lang = $lang; + return $lang; + } + + /** + * 连接到数据库,返回$dbh对象。 + * + * @access public + * @return object + */ + public function connectDB() + { + global $config; + if(!isset($config->db->driver)) self::error('no pdo driver defined, it should be mysql or sqlite', __FILE__, __LINE__, $exit = true); + if($config->db->driver == 'mysql') + { + $dsn = "mysql:host={$config->db->host}; port={$config->db->port}; dbname={$config->db->name}"; + } + try + { + $dbh = new PDO($dsn, $config->db->user, $config->db->password, array(PDO::ATTR_PERSISTENT => $config->db->persistant)); + $dbh->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_OBJ); + $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + $dbh->exec("SET NAMES {$config->db->encoding}"); + if(isset($config->db->strictMode) and $config->db->strictMode == false) $dbh->exec("SET @@sql_mode= ''"); + $this->dbh = $dbh; + return $dbh; + } + catch (PDOException $exception) + { + self::error($exception->getMessage(), __FILE__, __LINE__, $exit = true); + } + } +} + +/** + * 配置类。 + * + * @package ZenTaoPHP + */ +class config +{ + /** + * 设置对象的值。 + * + * + * set('db.user', 'wwccss'); + * ?> + * + * @param string $key 要设置的属性,可以是father.child的形式。 + * @param mixed $value 要设置的值。 + * @access public + * @return void + */ + public function set($key, $value) + { + helper::setMember('config', $key, $value); + } +} + +/** + * 语言类。 + * + * @package ZenTaoPHP + */ +class language +{ + /** + * 设置对象的值。 + * + * + * set('version', '1.0版本'); + * ?> + * + * @param string $key 要设置的属性,可以是father.child的形式。 + * @param mixed $value 要设置的值。 + * @access public + * @return void + */ + public function set($key, $value) + { + helper::setMember('lang', $key, $value); + } + + /** + * 打印某一个对象的属性。 + */ + public function show($obj, $key) + { + $obj = (array)$obj; + if(isset($obj[$key])) echo $obj[$key]; else echo ''; + } +} + +/** + * 超全局变量类。 + * + * @package ZenTaoPHP + */ +class super +{ + /* 构造函数。*/ + public function __construct($scope) + { + $this->scope = $scope; + } + + /* 设置属性。*/ + public function set($key, $value) + { + if($this->scope == 'post') + { + $_POST[$key] = $value; + } + elseif($this->scope == 'get') + { + $_GET[$key] = $value; + } + elseif($this->scope == 'server') + { + $_SERVER[$key] = $value; + } + elseif($this->scope == 'cookie') + { + $_COOKIE[$key] = $value; + } + elseif($this->scope == 'session') + { + $_SESSION[$key] = $value; + } + elseif($this->scope == 'env') + { + $_ENV[$key] = $value; + } + elseif($this->scope == 'global') + { + $GLOBAL[$key] = $value; + } + } + + /* 魔术方法。*/ + public function __get($key) + { + if($this->scope == 'post') + { + if(isset($_POST[$key])) return $_POST[$key]; + return false; + } + elseif($this->scope == 'get') + { + if(isset($_GET[$key])) return $_GET[$key]; + return false; + } + elseif($this->scope == 'server') + { + if(isset($_SERVER[$key])) return $_SERVER[$key]; + return false; + } + elseif($this->scope == 'cookie') + { + if(isset($_COOKIE[$key])) return $_COOKIE[$key]; + return false; + } + elseif($this->scope == 'session') + { + if(isset($_SESSION[$key])) return $_SESSION[$key]; + return false; + } + elseif($this->scope == 'env') + { + if(isset($_ENV[$key])) return $_ENV[$key]; + return false; + } + elseif($this->scope == 'global') + { + if(isset($GLOBALS[$key])) return $GLOBALS[$key]; + return false; + } + else + { + return false; + } + } +} + diff --git a/framework/tests/helper/.case001.expect b/framework/tests/helper/.case001.expect new file mode 100755 index 0000000000..7ef92ed7e6 --- /dev/null +++ b/framework/tests/helper/.case001.expect @@ -0,0 +1,37 @@ +stdClass Object +( + [a1] => 1 + [a2] => 2 + [a3] => stdClass Object + ( + [b1] => 3 + [b2] => 4 + ) + + [a4] => stdClass Object + ( + [b3] => stdClass Object + ( + [c1] => 5 + ) + + ) + + [a5] => 6 + [a6] => stdClass Object + ( + [b4] => 7 + ) + + [a7] => stdClass Object + ( + [b5] => stdClass Object + ( + [c2] => 8 + ) + + ) + +) +3 +10 diff --git a/framework/tests/helper/.case002.expect b/framework/tests/helper/.case002.expect new file mode 100755 index 0000000000..4d1325d7f7 --- /dev/null +++ b/framework/tests/helper/.case002.expect @@ -0,0 +1,19 @@ +chunsheng +wang'chun"sheng +Array +( + [0] => a + [1] => b + [2] => c +) +stdClass Object +( +) +localhost +chunsheng'.wang +Array +( + [0] => 1 + [1] => 2 + [2] => 3 +) diff --git a/framework/tests/helper/.case003.expect b/framework/tests/helper/.case003.expect new file mode 100755 index 0000000000..fe1b829ece --- /dev/null +++ b/framework/tests/helper/.case003.expect @@ -0,0 +1,19 @@ +/index/index.html +/user/login.html +/user/view/k1/v1/k2/v2.html +/user/view/k1/v1/k2/v2.html + +/index/index.html +/user/login.html +/user/view/v1/v2.html +/user/view/v1/v2.html + +/index-index.html +/user-login.html +/user-view-v1-v2.html +/user-view-v1-v2.html + +/?m=index&f=index&t=html +/?m=user&f=login&t=html +/?m=user&f=view&t=html&k1=v1&k2=v2 +/?m=user&f=view&t=html&k1=v1&k2=v2 diff --git a/framework/tests/helper/.case004.expect b/framework/tests/helper/.case004.expect new file mode 100755 index 0000000000..98edef70f2 --- /dev/null +++ b/framework/tests/helper/.case004.expect @@ -0,0 +1,14 @@ +case004.php +helper.class.php +import1.php + +case004.php +helper.class.php +import1.php + +case004.php +helper.class.php +import1.php +import2.php + +bool(false) diff --git a/framework/tests/helper/case001.php b/framework/tests/helper/case001.php new file mode 100755 index 0000000000..9710c129df --- /dev/null +++ b/framework/tests/helper/case001.php @@ -0,0 +1,31 @@ +#!/usr/bin/env php + + * @package Testing + * @version $Id: case001.php 133 2010-09-11 07:22:48Z wwccss $ + * @link http://www.zentao.net + * @license http://opensource.org/licenses/lgpl-3.0.html LGPL + */ +include '../../helper.class.php'; +$array['a1'] = '1'; +$array['a2'] = '2'; +$array['a3']['b1'] = '3'; +$array['a3']['b2'] = '4'; +$array['a4']['b3']['c1'] = '5'; +$array['a5'] = '6'; +$array['a6']['b4'] = '7'; +$array['a7']['b5']['c2'] = '8'; + +$config = new stdClass(); +eval (helper::array2object($array, 'config')); +print_r($config); +echo $config->a3->b1; +echo "\n"; +helper::setMember('config', 'a3.b1', 10); +echo $config->a3->b1; +echo "\n"; +?> diff --git a/framework/tests/helper/case002.php b/framework/tests/helper/case002.php new file mode 100755 index 0000000000..52aa376b1f --- /dev/null +++ b/framework/tests/helper/case002.php @@ -0,0 +1,46 @@ +#!/usr/bin/env php + + * @package Testing + * @version $Id: case002.php 133 2010-09-11 07:22:48Z wwccss $ + * @link http://www.zentao.net + * @license http://opensource.org/licenses/lgpl-3.0.html LGPL + */ +include '../../helper.class.php'; +$config = new stdClass(); + +/* 测试一维属性的修改。*/ +$config->user = 'wwccss'; +helper::setMember('config', 'user', 'chunsheng'); +echo $config->user . "\n"; + +/* 赋值的变量含有单双引号。*/ +$config->name = 'wwccss'; +helper::setMember('config', 'name', "wang'chun\"sheng"); +echo $config->name . "\n"; + +/* 赋值的变量为一个数组。*/ +$config->users = array(1,2,3); +helper::setMember('config', 'users', array('a', 'b', 'c')); +print_r($config->users); + +/* 赋值的变量为一个对象。*/ +$config->obj = array(1,2,3); +helper::setMember('config', 'obj', new stdClass()); +print_r($config->obj); + +/* 测试二维属性的修改。*/ +$config->db->host = 'localhost'; +$config->db->user = 'wwccss'; +$config->db->param = array(); +helper::setMember('config', 'db.host', "localhost"); +helper::setMember('config', 'db.user', "chunsheng'.wang"); +helper::setMember('config', 'db.param', array('1', '2', '3')); +echo $config->db->host . "\n"; +echo $config->db->user . "\n"; +print_r($config->db->param); +?> diff --git a/framework/tests/helper/case003.php b/framework/tests/helper/case003.php new file mode 100755 index 0000000000..f5e078c9cc --- /dev/null +++ b/framework/tests/helper/case003.php @@ -0,0 +1,81 @@ +#!/usr/bin/env php + + * @package Testing + * @version $Id: case003.php 133 2010-09-11 07:22:48Z wwccss $ + * @link http://www.zentao.net + * @license http://opensource.org/licenses/lgpl-3.0.html LGPL + */ +include '../../helper.class.php'; + +/* 实例化app的mock对象。*/ +$app = new mockapp(); +$app->setViewType('html'); + +/* 设置cfg配置,并将其转换为$config对象。*/ +$cfg['webRoot'] = '/'; +$cfg['requestType'] = 'PATH_INFO'; +$cfg['requestFix'] = '/'; +$cfg['pathType'] = 'full'; +$cfg['moduleVar'] = 'm'; +$cfg['methodVar'] = 'f'; +$cfg['viewVar'] = 't'; +eval(helper::array2Object($cfg, 'config')); + +/* PATH_INFO + FULL*/ +$vars = array('k1' => 'v1', 'k2' => 'v2'); +echo helper::createLink('index') . "\n"; // 只有模块名。 +echo helper::createLink('user', 'login') . "\n"; // 增加方法名。 +echo helper::createLink('user', 'view', $vars) . "\n"; // 增加参数。 +$vars = 'k1=v1&k2=v2'; +echo helper::createLink('user', 'view', $vars) . "\n\n"; // 参数改成str形式。 + +/* PATH_INFO + CLEAN */ +$config->pathType = 'clean'; +$vars = array('k1' => 'v1', 'k2' => 'v2'); +echo helper::createLink('index') . "\n"; // 只有模块名。 +echo helper::createLink('user', 'login') . "\n"; // 增加方法名。 +echo helper::createLink('user', 'view', $vars) . "\n"; // 增加参数。 +$vars = 'k1=v1&k2=v2'; +echo helper::createLink('user', 'view', $vars) . "\n\n"; // 参数改成str形式。 + +/* PATH_INFO + CLEAN + REQUESTFIX */ +$config->requestFix = '-'; +$vars = array('k1' => 'v1', 'k2' => 'v2'); +echo helper::createLink('index') . "\n"; // 只有模块名。 +echo helper::createLink('user', 'login') . "\n"; // 增加方法名。 +echo helper::createLink('user', 'view', $vars) . "\n"; // 增加参数。 +$vars = 'k1=v1&k2=v2'; +echo helper::createLink('user', 'view', $vars) . "\n\n"; // 参数改成str形式。 + +/* GET + CLEAN */ +$config->requestType = 'GET'; +$vars = array('k1' => 'v1', 'k2' => 'v2'); +echo helper::createLink('index') . "\n"; // 只有模块名。 +echo helper::createLink('user', 'login') . "\n"; // 增加方法名。 +echo helper::createLink('user', 'view', $vars) . "\n"; // 增加参数。 +$vars = 'k1=v1&k2=v2'; +echo helper::createLink('user', 'view', $vars) . "\n"; // 参数改成str形式。 + +/** + * app的mock对象。 + * + * @package Testing + */ +class mockapp +{ + private $viewType; + public function setViewType($viewType) + { + $this->viewType = $viewType; + } + public function getViewType() + { + return $this->viewType; + } +} +?> diff --git a/framework/tests/helper/case004.php b/framework/tests/helper/case004.php new file mode 100755 index 0000000000..ce5170a49a --- /dev/null +++ b/framework/tests/helper/case004.php @@ -0,0 +1,45 @@ +#!/usr/bin/env php + + * @package Testing + * @version $Id: case004.php 133 2010-09-11 07:22:48Z wwccss $ + * @link http://www.zentao.net + * @license http://opensource.org/licenses/lgpl-3.0.html LGPL + */ +include '../../helper.class.php'; + +/* 首次包含。*/ +helper::import('import1.php'); +printIncluded(); + +/* 重复包含。*/ +helper::import('import1.php'); +printIncluded(); + +/* 包含第二个文件。*/ +helper::import('import2.php'); +printIncluded(); + +/* 包含不存在的文件。*/ +var_dump(helper::import('noexits.php')); + +/** + * 只打印包含文件的文件名。 + * + * @access public + * @return void + */ +function printIncluded() +{ + $files = get_included_files(); + foreach($files as $file) + { + echo basename($file) . "\n"; + } + echo "\n"; +} +?> diff --git a/framework/tests/helper/import1.php b/framework/tests/helper/import1.php new file mode 100755 index 0000000000..da1d516c3c --- /dev/null +++ b/framework/tests/helper/import1.php @@ -0,0 +1,14 @@ + + * @package Testing + * @version $Id: import1.php 133 2010-09-11 07:22:48Z wwccss $ + * @link http://www.zentao.net + * @license http://opensource.org/licenses/lgpl-3.0.html LGPL + */ + +class myclass1{}; +?> diff --git a/framework/tests/helper/import2.php b/framework/tests/helper/import2.php new file mode 100755 index 0000000000..f2221c727d --- /dev/null +++ b/framework/tests/helper/import2.php @@ -0,0 +1,13 @@ + + * @package Testing + * @version $Id: import2.php 133 2010-09-11 07:22:48Z wwccss $ + * @link http://www.zentao.net + * @license http://opensource.org/licenses/lgpl-3.0.html LGPL + */ +class myclass2{}; +?> diff --git a/lib/dao/dao.class.php b/lib/dao/dao.class.php new file mode 100755 index 0000000000..6ad0ce4e81 --- /dev/null +++ b/lib/dao/dao.class.php @@ -0,0 +1,1156 @@ +. + * + * @copyright Copyright 2009-2010 青岛易软天创网络科技有限公司(www.cnezsoft.com) + * @author Chunsheng Wang + * @package ZenTaoPHP + * @version $Id: dao.class.php 134 2010-09-11 07:24:27Z wwccss $ + * @link http://www.zentao.net + */ + +/** + * DAO类。提供各种便利的数据库操作方法。 + * + * @package ZenTaoPHP + */ +class dao +{ + /* 解决autoCompany带来的sql关键字冲突的问题。*/ + const WHERE = 'wHeRe'; + const GROUPBY = 'gRoUp bY'; + const HAVING = 'hAvInG'; + const ORDERBY = 'oRdEr bY'; + const LIMIT = 'lImiT'; + + /** + * 全局的$app对象。 + * + * @var object + * @access protected + */ + protected $app; + + /** + * 全局的$config对象。 + * + * @var object + * @access protected + */ + protected $config; + + /** + * 全局的$lang对象。 + * + * @var object + * @access protected + */ + protected $lang; + + /** + * 全局的$dbh(数据库访问句柄)对象。 + * + * @var object + * @access protected + */ + protected $dbh; + + /** + * 当前查询所对应的主表。 + * + * @var string + * @access public + */ + public $table; + + /** + * 主表所对应的alias + * + * @var string + * @access public + */ + public $alias; + + /** + * 当前查询所返回的字段列表。 + * + * @var string + * @access public + */ + public $fields; + + /** + * 查询的模式,现在支持两种,一种是通过魔术方法,一种是直接拼写sql查询。 + * + * 主要用来区分dao::from()方法和sql::from()方法。 + * + * @var string + * @access public + */ + public $mode; + + /** + * 查询的方法: insert, select, update, delete, replace + * + * @var string + * @access public + */ + public $method; + + /** + * 执行的sql查询列表。 + * + * 用来记录当前页面所有的sql查询。 + * + * @var array + * @access public + */ + static public $querys = array(); + + /** + * 数据检查结果。 + * + * @var array + * @access public + */ + static public $errors = array(); + + /** + * 构造函数。 + * + * 设置当前model对应的表名,并引用全局的变量。 + * + * @param string $table 表名。 + * @access public + * @return void + */ + public function __construct($table = '') + { + global $app, $config, $lang, $dbh; + $this->app = $app; + $this->config = $config; + $this->lang = $lang; + $this->dbh = $dbh; + + $this->reset(); + } + + /** + * 设置数据表。 + * + * @param string $table 表名。 + * @access private + * @return void + */ + private function setTable($table) + { + $this->table = $table; + } + + /** + * 设置当前查询主表的alias。 + * + * @param string $alias 别名。 + * @access private + * @return void + */ + private function setAlias($alias) + { + $this->alias = $alias; + } + + /** + * 设置返回的字段列表。 + * + * @param string $fields 字段列表。 + * @access private + * @return void + */ + private function setFields($fields) + { + $this->fields = $fields; + } + + /** + * 重新设置table, field, mode。 + * + * @access private + * @return void + */ + private function reset() + { + $this->setFields(''); + $this->setTable(''); + $this->setAlias(''); + $this->setMode(''); + $this->setMethod(''); + } + + //-------------------- 根据查询方式的不同,调用SQL类的对应方法。--------------------// + + /** + * 设置查询模式。magic是通过findby之类的魔术方法进行查询的,而raw则直接拼装sql进行查询。 + * + * @param string mode 查询模式: empty|magic|raw + * @access private + * @return void + */ + private function setMode($mode = '') + { + $this->mode = $mode; + } + + /* 设置查询的方法。select|update|insert|delete|replace */ + private function setMethod($method = '') + { + $this->method = $method; + } + + /* select:调用SQL类的select方法。*/ + public function select($fields = '*') + { + $this->setMode('raw'); + $this->setMethod('select'); + $this->sqlobj = sql::select($fields); + return $this; + } + + /* update:调用SQL类的update方法。*/ + public function update($table) + { + $this->setMode('raw'); + $this->setMethod('update'); + $this->sqlobj = sql::update($table); + $this->setTable($table); + return $this; + } + + /* delete:调用SQL类的delete方法。*/ + public function delete() + { + $this->setMode('raw'); + $this->setMethod('delete'); + $this->sqlobj = sql::delete(); + return $this; + } + + /* insert:调用SQL类的insert方法。*/ + public function insert($table) + { + $this->setMode('raw'); + $this->setMethod('insert'); + $this->sqlobj = sql::insert($table); + $this->setTable($table); + return $this; + } + + /* replace:调用SQL类的replace方法。*/ + public function replace($table) + { + $this->setMode('raw'); + $this->setMethod('replace'); + $this->sqlobj = sql::replace($table); + $this->setTable($table); + return $this; + } + + /* from: 设定要查询的table name。*/ + public function from($table) + { + $this->setTable($table); + if($this->mode == 'raw') $this->sqlobj->from($table); + return $this; + } + + /* fields方法:设置要查询的字段列表。*/ + public function fields($fields) + { + $this->setFields($fields); + return $this; + } + + /* alias方法。*/ + public function alias($alias) + { + if(empty($this->alias)) $this->setAlias($alias); + $this->sqlobj->alias($alias); + return $this; + } + + /* data方法。*/ + public function data($data, $autoCompany = true) + { + /* 如果当前模块不是company,都追加company字段。*/ + if(!is_object($data)) $data = (object)$data; + if($autoCompany and isset($this->app->company) and $this->table != TABLE_COMPANY and !isset($data->company)) $data->company = $this->app->company->id; + $this->sqlobj->data($data); + return $this; + } + + //-------------------- 拼装之后的SQL相关处理方法。--------------------// + + /* 返回SQL语句。*/ + public function get() + { + return $this->processKeywords($this->processSQL()); + } + + /* 打印SQL语句。*/ + public function printSQL() + { + echo $this->processSQL(); + } + + /* 处理SQL,将table和fields字段替换成对应的值。*/ + private function processSQL($autoCompany = true) + { + $sql = $this->sqlobj->get(); + + /* 如果查询模式是magic,处理fields和table两个变量。*/ + if($this->mode == 'magic') + { + if($this->fields == '') $this->fields = '*'; + if($this->table == '') $this->app->error('Must set the table name', __FILE__, __LINE__, $exit = true); + $sql = sprintf($this->sqlobj->get(), $this->fields, $this->table); + } + + /* 如果处理的不是company表,并且查询方法不是insert和replace, 追加company的查询条件。*/ + if(isset($this->app->company) and $autoCompany and $this->table != '' and $this->table != TABLE_COMPANY and $this->method != 'insert' and $this->method != 'replace') + { + /* 获得where 和 order by的位置。*/ + $wherePOS = strrpos($sql, DAO::WHERE); + $groupPOS = strrpos($sql, DAO::GROUPBY); // group by的位置。 + $havingPOS = strrpos($sql, DAO::HAVING); // having的位置。 + $orderPOS = strrpos($sql, DAO::ORDERBY); // order by的位置。 + $limitPOS = strrpos($sql, DAO::LIMIT); // limit的位置。 + $splitPOS = $orderPOS ? $orderPOS : $limitPOS; // order比limit靠前。 + $splitPOS = $havingPOS? $havingPOS: $splitPOS; // having比orer靠前。 + $splitPOS = $groupPOS ? $groupPOS : $splitPOS; // group比having靠前。 + + /* 要追加的条件语句。*/ + $tableName = !empty($this->alias) ? $this->alias : $this->table; + $companyCondition = " $tableName.company = '{$this->app->company->id}' "; + + /* SQL语句中有order by。*/ + if($splitPOS) + { + $firstPart = substr($sql, 0, $splitPOS); + $lastPart = substr($sql, $splitPOS); + if($wherePOS) + { + $sql = $firstPart . " AND $companyCondition " . $lastPart; + } + else + { + $sql = $firstPart . " WHERE $companyCondition " . $lastPart; + } + } + else + { + $sql .= $wherePOS ? " AND $companyCondition" : " WHERE $companyCondition"; + } + } + self::$querys[] = $this->processKeywords($sql); + return $sql; + } + + /* 处理SQL关键字。*/ + private function processKeywords($sql) + { + return str_replace(array(DAO::WHERE, DAO::GROUPBY, DAO::HAVING, DAO::ORDERBY, DAO::LIMIT), array('WHERE', 'GROUP BY', 'HAVING', 'ORDER BY', 'LIMIT'), $sql); + } + + //-------------------- SQL查询相关的方法。--------------------// + + /* 设置数据库访问句柄。*/ + public function dbh($dbh) + { + $this->dbh = $dbh; + return $this; + } + + /* 执行sql查询,返回stmt对象。autoComapny设定是否自动追加company的查询条件。*/ + public function query($autoCompany = true) + { + /* 如果dao::$errors不为空,返回一个空的stmt对象,这样后续的方法调用还可以继续。*/ + if(!empty(dao::$errors)) return new PDOStatement(); + + /* 处理一下SQL语句。*/ + $sql = $this->processSQL($autoCompany); + try + { + $this->reset(); + return $this->dbh->query($sql); + } + catch (PDOException $e) + { + $this->app->error($e->getMessage() . "

The sql is: $sql

", __FILE__, __LINE__, $exit = true); + } + } + + /* 执行分页。*/ + public function page($pager) + { + if(!is_object($pager)) return $this; + + /* 没有传递recTotal,则自己进行计算。*/ + if($pager->recTotal == 0) + { + /* 获得SELECT和FROM的位置,据此算出查询的字段,然后将其替换为count(*)。*/ + $sql = $this->get(); + $selectPOS = strpos($sql, 'SELECT') + strlen('SELECT'); + $fromPOS = strpos($sql, 'FROM'); + $fields = substr($sql, $selectPOS, $fromPOS - $selectPOS ); + $sql = str_replace($fields, ' COUNT(*) AS recTotal ', $sql); + + /* 取得order 或者limit的位置,将后面的去掉。*/ + $subLength = strlen($sql); + $orderPOS = strripos($sql, 'order'); + $limitPOS = strripos($sql , 'limit'); + if($limitPOS) $subLength = $limitPOS; + if($orderPOS) $subLength = $orderPOS; + $sql = substr($sql, 0, $subLength); + self::$querys[] = $sql; + + /* 获得记录总数。*/ + try + { + $row = $this->dbh->query($sql)->fetch(PDO::FETCH_OBJ); + } + catch (PDOException $e) + { + $this->app->error($e->getMessage() . "

The sql is: $sql

", __FILE__, __LINE__, $exit = true); + } + + $pager->setRecTotal($row->recTotal); + $pager->setPageTotal(); + } + $this->sqlobj->limit($pager->limit()); + return $this; + } + + /* 执行sql查询,返回受影响的记录数。autoComapny设定是否自动追加company的查询条件。*/ + public function exec($autoCompany = true) + { + /* 如果dao::$errors不为空,返回一个空的stmt对象,这样后续的方法调用还可以继续。*/ + if(!empty(dao::$errors)) return new PDOStatement(); + + /* 处理一下SQL语句。*/ + $sql = $this->processSQL($autoCompany); + try + { + $this->reset(); + return $this->dbh->exec($sql); + } + catch (PDOException $e) + { + $this->app->error($e->getMessage() . "

The sql is: $sql

", __FILE__, __LINE__, $exit = true); + } + } + + //-------------------- 数据获取相关的方法。--------------------// + + /* 返回一条记录,如果指定了$field字段, 则直接返回该字段对应的值。*/ + public function fetch($field = '', $autoCompany = true) + { + if(empty($field)) return $this->query($autoCompany)->fetch(); + $this->setFields($field); + $result = $this->query($autoCompany)->fetch(PDO::FETCH_OBJ); + if($result) return $result->$field; + } + + /* 返回全部的结果。如果指定了$keyField,则以keyField的值作为key。*/ + public function fetchAll($keyField = '', $autoCompany = true) + { + $stmt = $this->query($autoCompany); + if(empty($keyField)) return $stmt->fetchAll(); + $rows = array(); + while($row = $stmt->fetch()) $rows[$row->$keyField] = $row; + return $rows; + } + + /* 返回结果并按照某个字段进行分组。*/ + public function fetchGroup($groupField, $keyField = '', $autoCompany = true) + { + $stmt = $this->query($autoCompany); + $rows = array(); + while($row = $stmt->fetch()) + { + empty($keyField) ? $rows[$row->$groupField][] = $row : $rows[$row->$groupField][$row->$keyField] = $row; + } + return $rows; + } + + /* fetchPairs方法:如果没有指定key和value字段,则取行字段里面的第一个作为key,最后一个作为value。*/ + public function fetchPairs($keyField = '', $valueField = '', $autoCompany = true) + { + $pairs = array(); + $ready = false; + $stmt = $this->query($autoCompany); + while($row = $stmt->fetch(PDO::FETCH_ASSOC)) + { + if(!$ready) + { + if(empty($keyField)) $keyField = key($row); + if(empty($valueField)) + { + end($row); + $valueField = key($row); + } + $ready = true; + } + + $pairs[$row[$keyField]] = $row[$valueField]; + } + return $pairs; + } + + /* 获取最后插入的id。*/ + public function lastInsertID() + { + return $this->dbh->lastInsertID(); + } + + //-------------------- 各种魔术方法。--------------------// + + /** + * 魔术方法,籍此提供各种便利的查询方法。 + * + * @param string $funcName 被调用的方法名。 + * @param array $funcArgs 传入的参数列表。 + * @access public + * @return void + */ + public function __call($funcName, $funcArgs) + { + /* 将funcName转为小写。*/ + $funcName = strtolower($funcName); + + /* findBy类的方法。*/ + if(strpos($funcName, 'findby') !== false) + { + $this->setMode('magic'); + $field = str_replace('findby', '', $funcName); + if(count($funcArgs) == 1) + { + $operator = '='; + $value = $funcArgs[0]; + } + else + { + $operator = $funcArgs[0]; + $value = $funcArgs[1]; + } + $this->sqlobj = sql::select('%s')->from('%s')->where($field, $operator, $value); // 使用占位符,执行查询之前替换为相应的字段和表名。 + return $this; + } + /* fetch10方法,真正的数据查询。*/ + elseif(strpos($funcName, 'fetch') !== false) + { + $max = str_replace('fetch', '', $funcName); + $stmt = $this->query(); + + /* 设定了key字段。 */ + $rows = array(); + $key = isset($funcArgs[0]) ? $funcArgs[0] : ''; + $i = 0; + while($row = $stmt->fetch()) + { + $key ? $rows[$row->$key] = $row : $rows[] = $row; + $i ++; + if($i == $max) break; + } + return $rows; + } + /* 其余的都直接调用sql类里面的方法。*/ + else + { + /* 取SQL类方法中参数个数最大值,生成一个最大集合的参数列表。。*/ + for($i = 0; $i < SQL::MAX_ARGS; $i ++) + { + ${"arg$i"} = isset($funcArgs[$i]) ? $funcArgs[$i] : null; + } + $this->sqlobj->$funcName($arg0, $arg1, $arg2); + return $this; + } + } + + //-------------------- 数据检查相关的方法。--------------------// + + /* 按照某个规则检查值是否符合要求。*/ + public function check($fieldName, $funcName) + { + /* 如果data变量里面没有这个字段,直接返回。*/ + if(!isset($this->sqlobj->data->$fieldName)) return $this; + + /* 引用全局的config, lang。*/ + global $lang, $config, $app; + $table = strtolower(str_replace($config->db->prefix, '', $this->table)); + $fieldLabel = isset($lang->$table->$fieldName) ? $lang->$table->$fieldName : $fieldName; + $value = $this->sqlobj->data->$fieldName; + + if($funcName == 'unique') + { + $args = func_get_args(); + $sql = "SELECT COUNT(*) AS count FROM $this->table WHERE `$fieldName` = " . $this->sqlobj->quote($value); + if($this->table != TABLE_COMPANY) $sql .= " AND company = {$this->app->company->id} "; + if(isset($args[2])) $sql .= ' AND ' . $args[2]; + try + { + $row = $this->dbh->query($sql)->fetch(); + if($row->count != 0) $this->logError($funcName, $fieldName, $fieldLabel, array($value)); + } + catch (PDOException $e) + { + $this->app->error($e->getMessage() . "

The sql is: $sql

", __FILE__, __LINE__, $exit = true); + } + } + else + { + /* 取validate类方法中参数个数最大值,生成一个最大集合的参数列表。。*/ + $funcArgs = func_get_args(); + unset($funcArgs[0]); + unset($funcArgs[1]); + + for($i = 0; $i < VALIDATER::MAX_ARGS; $i ++) + { + ${"arg$i"} = isset($funcArgs[$i + 2]) ? $funcArgs[$i + 2] : null; + } + $checkFunc = 'check' . $funcName; + if(validater::$checkFunc($value, $arg0, $arg1, $arg2) === false) + { + $this->logError($funcName, $fieldName, $fieldLabel, $funcArgs); + } + } + + return $this; + } + + /* 如果满足某一个条件,按照某个规则检查值是否符合要求。*/ + public function checkIF($condition, $fieldName, $funcName) + { + if(!$condition) return $this; + $funcArgs = func_get_args(); + for($i = 0; $i < VALIDATER::MAX_ARGS; $i ++) + { + ${"arg$i"} = isset($funcArgs[$i + 3]) ? $funcArgs[$i + 3] : null; + } + $this->check($fieldName, $funcName, $arg0, $arg1, $arg2); + return $this; + } + + /* 批量检查。*/ + public function batchCheck($fields, $funcName) + { + $fields = explode(',', str_replace(' ', '', $fields)); + $funcArgs = func_get_args(); + for($i = 0; $i < VALIDATER::MAX_ARGS; $i ++) + { + ${"arg$i"} = isset($funcArgs[$i + 2]) ? $funcArgs[$i + 2] : null; + } + foreach($fields as $fieldName) $this->check($fieldName, $funcName, $arg0, $arg1, $arg2); + return $this; + } + + /* 批量条件检查。*/ + public function batchCheckIF($condition, $fields, $funcName) + { + if(!$condition) return $this; + $fields = explode(',', str_replace(' ', '', $fields)); + $funcArgs = func_get_args(); + for($i = 0; $i < VALIDATER::MAX_ARGS; $i ++) + { + ${"arg$i"} = isset($funcArgs[$i + 2]) ? $funcArgs[$i + 2] : null; + } + foreach($fields as $fieldName) $this->check($fieldName, $funcName, $arg0, $arg1, $arg2); + return $this; + } + + /* 自动根据数据库中表的字段格式进行检查。*/ + public function autoCheck($skipFields = '') + { + $fields = $this->getFieldsType(); + $skipFields = ",$skipFields,"; + + foreach($fields as $fieldName => $validater) + { + if(strpos($skipFields, $fieldName) !== false) continue; // 忽略。 + if(!isset($this->sqlobj->data->$fieldName)) continue; + if($validater['rule'] == 'skip') continue; + $options = array(); + if(isset($validater['options'])) $options = array_values($validater['options']); + for($i = 0; $i < VALIDATER::MAX_ARGS; $i ++) + { + ${"arg$i"} = isset($options[$i]) ? $options[$i] : null; + } + $this->check($fieldName, $validater['rule'], $arg0, $arg1, $arg2); + } + return $this; + } + + /* 记录错误。*/ + public function logError($checkType, $fieldName, $fieldLabel, $funcArgs = array()) + { + global $lang; + $error = $lang->error->$checkType; + $replaces = array_merge(array($fieldLabel), $funcArgs); + + /* 如果error不是数组,只是字符串,则循环replace,依次替换%s。*/ + if(!is_array($error)) + { + foreach($replaces as $replace) + { + $pos = strpos($error, '%s'); + if($pos === false) break; + $error = substr($error, 0, $pos) . $replace . substr($error, $pos +2); + } + } + /* 如果error是一个数组,则从数组中挑选%s个数与replace元素个数相同的。*/ + else + { + /* 去掉replace中空白的元素。*/ + foreach($replaces as $key => $value) if(is_null($value)) unset($replaces[$key]); + $replacesCount = count($replaces); + foreach($error as $errorString) + { + if(substr_count($errorString, '%s') == $replacesCount) + { + $error = vsprintf($errorString, $replaces); + } + } + } + dao::$errors[$fieldName][] = $error; + } + + /* 判断这次查询是否有错误。*/ + public function isError() + { + return !empty(dao::$errors); + } + + /* 返回error。*/ + public function getError() + { + $errors = dao::$errors; + dao::$errors = array(); + return $errors; + } + + /* 获得某一个表的字段类型。*/ + private function getFieldsType() + { + try + { + $this->dbh->setAttribute(PDO::ATTR_CASE, PDO::CASE_LOWER); + $sql = "DESC $this->table"; + $rawFields = $this->dbh->query($sql)->fetchAll(); + $this->dbh->setAttribute(PDO::ATTR_CASE, PDO::CASE_NATURAL); + } + catch (PDOException $e) + { + $this->app->error($e->getMessage() . "

The sql is: $sql

", __FILE__, __LINE__, $exit = true); + } + + foreach($rawFields as $rawField) + { + $firstPOS = strpos($rawField->type, '('); + $type = substr($rawField->type, 0, $firstPOS > 0 ? $firstPOS : strlen($rawField->type)); + $type = str_replace(array('big', 'small', 'medium', 'tiny', 'var'), '', $type); + $field = array(); + + if($type == 'enum' or $type == 'set') + { + $rangeBegin = $firstPOS + 2; // 将第一个引号去掉。 + $rangeEnd = strrpos($rawField->type, ')') - 1; // 将最后一个引号去掉。 + $range = substr($rawField->type, $rangeBegin, $rangeEnd - $rangeBegin); + $field['rule'] = 'reg'; + $field['options']['reg'] = '/' . str_replace("','", '|', $range) . '/'; + } + elseif($type == 'char') + { + $begin = $firstPOS + 1; + $end = strpos($rawField->type, ')', $begin); + $length = substr($rawField->type, $begin, $end - $begin); + $field['rule'] = 'length'; + $field['options']['max'] = $length; + $field['options']['min'] = 0; + } + elseif($type == 'int') + { + $field['rule'] = 'int'; + } + elseif($type == 'float' or $type == 'double') + { + $field['rule'] = 'float'; + } + elseif($type == 'date') + { + $field['rule'] = 'date'; + } + else + { + $field['rule'] = 'skip'; + } + $fields[$rawField->field] = $field; + } + return $fields; + } +} + +/** + * SQL查询封装类。 + * + * @package ZenTaoPHP + */ +class sql +{ + /** + * 所有方法的参数个数最大值。 + * + */ + const MAX_ARGS = 3; + + /** + * SQL语句。 + * + * @var string + * @access private + */ + private $sql = ''; + + /** + * 全局的$dbh(数据库访问句柄)对象。 + * + * @var object + * @access protected + */ + protected $dbh; + + /** + * INSERT或者UPDATE时赋给的数据。 + * + * @var mix + * @access protected + */ + public $data; + + /** + * 是否是首次调用set。 + * + * @var bool + * @access private; + */ + private $isFirstSet = true; + + /** + * 是否在条件判断中。 + * + * @var bool + * @access private; + */ + private $inCondition = false; + + /** + * 判断条件是否为ture。 + * + * @var bool + * @access private; + */ + private $conditionIsTrue = false; + + /** + * 是否自动magic quote。 + * + * @var bool + * @access public + */ + public $magicQuote; + + /* 构造函数。*/ + private function __construct($table = '') + { + global $dbh; + $this->dbh = $dbh; + $this->magicQuote = get_magic_quotes_gpc(); + } + + /* 实例化方法,通过该方法实例对象。*/ + public function factory($table = '') + { + return new sql($table); + } + + /* 查询语句开始。*/ + public function select($field = '*') + { + $sqlobj = self::factory(); + $sqlobj->sql = "SELECT $field "; + return $sqlobj; + } + + /* 更新语句开始。*/ + public function update($table) + { + $sqlobj = self::factory(); + $sqlobj->sql = "UPDATE $table SET "; + return $sqlobj; + } + + /* 插入语句开始。*/ + public function insert($table) + { + $sqlobj = self::factory(); + $sqlobj->sql = "INSERT INTO $table SET "; + return $sqlobj; + } + + /* 替换语句开始。*/ + public function replace($table) + { + $sqlobj = self::factory(); + $sqlobj->sql = "REPLACE $table SET "; + return $sqlobj; + } + + /* 删除语句开始。*/ + public function delete() + { + $sqlobj = self::factory(); + $sqlobj->sql = "DELETE "; + return $sqlobj; + } + + /* 给定一个key=>value结构的数组或者对象,拼装成key = value的形式。*/ + public function data($data) + { + $this->data = $data; + foreach($data as $field => $value) $this->sql .= "`$field` = " . $this->quote($value) . ','; + $this->sql = rtrim($this->sql, ','); // 去掉最后面的逗号。 + return $this; + } + + /* 加左边的括弧。*/ + public function markLeft($count = 1) + { + $this->sql .= str_repeat('(', $count); + return $this; + } + + /* 加右边的括弧。*/ + public function markRight($count = 1) + { + $this->sql .= str_repeat(')', $count); + return $this; + } + + /* SET key=value。*/ + public function set($set) + { + if($this->isFirstSet) + { + $this->sql .= " $set "; + $this->isFirstSet = false; + } + else + { + $this->sql .= ", $set"; + } + return $this; + } + + /* 设定要查询的表名。*/ + public function from($table) + { + $this->sql .= "FROM $table"; + return $this; + } + + /* 设置别名。*/ + public function alias($alias) + { + $this->sql .= " AS $alias "; + } + + /* 设定LEFT JOIN语句。*/ + public function leftJoin($table) + { + $this->sql .= " LEFT JOIN $table"; + return $this; + } + + /* 设定ON条件。*/ + public function on($condition) + { + $this->sql .= " ON $condition "; + return $this; + } + + /* 条件判断开始。*/ + public function beginIF($condition) + { + $this->inCondition = true; + $this->conditionIsTrue = $condition; + } + + /* 条件判断结束。*/ + public function fi() + { + $this->inCondition = false; + $this->conditionIsTrue = false; + } + + /* WHERE语句部分开始。*/ + public function where($arg1, $arg2 = null, $arg3 = null) + { + if($this->inCondition and !$this->conditionIsTrue) return $this; + if($arg3 !== null) + { + $value = $this->quote($arg3); + $condition = "`$arg1` $arg2 " . $this->quote($arg3); + } + else + { + $condition = $arg1; + } + + $this->sql .= ' ' . DAO::WHERE ." $condition "; + return $this; + } + + /* 追加AND条件。*/ + public function andWhere($condition) + { + if($this->inCondition and !$this->conditionIsTrue) return $this; + $this->sql .= " AND $condition "; + return $this; + } + + /* 追加OR条件。*/ + public function orWhere($condition) + { + if($this->inCondition and !$this->conditionIsTrue) return $this; + $this->sql .= " OR $condition "; + return $this; + } + + /* 等于。*/ + public function eq($value) + { + if($this->inCondition and !$this->conditionIsTrue) return $this; + $this->sql .= " = " . $this->quote($value); + return $this; + } + + /* 不等于。*/ + public function ne($value) + { + if($this->inCondition and !$this->conditionIsTrue) return $this; + $this->sql .= " != " . $this->quote($value); + return $this; + } + + /* 大于。*/ + public function gt($value) + { + if($this->inCondition and !$this->conditionIsTrue) return $this; + $this->sql .= " > " . $this->quote($value); + return $this; + } + + /* 小于。*/ + public function lt($value) + { + if($this->inCondition and !$this->conditionIsTrue) return $this; + $this->sql .= " < " . $this->quote($value); + return $this; + } + + /* 生成between语句。*/ + public function between($min, $max) + { + if($this->inCondition and !$this->conditionIsTrue) return $this; + $this->sql .= " BETWEEN $min AND $max "; + return $this; + } + + /* 生成 IN部分语句。*/ + public function in($ids) + { + if($this->inCondition and !$this->conditionIsTrue) return $this; + $this->sql .= helper::dbIN($ids); + return $this; + } + + /* 生成 NOTIN部分语句。*/ + public function notin($ids) + { + if($this->inCondition and !$this->conditionIsTrue) return $this; + $this->sql .= ' NOT ' . helper::dbIN($ids); + return $this; + } + + /* 生成LIKE部分语句。*/ + public function like($string) + { + if($this->inCondition and !$this->conditionIsTrue) return $this; + $this->sql .= " LIKE " . $this->quote($string); + return $this; + } + + /* 设定ORDER BY。*/ + public function orderBy($order) + { + $order = str_replace(array('|', '', '_'), ' ', $order); + $order = str_replace('left', '`left`', $order); // 处理left关键字。 + $this->sql .= ' ' . DAO::ORDERBY . " $order"; + return $this; + } + + /* 设定LIMIT。*/ + public function limit($limit) + { + if(empty($limit)) return $this; + stripos($limit, 'limit') !== false ? $this->sql .= " $limit " : $this->sql .= ' ' . DAO::LIMIT . " $limit "; + return $this; + } + + /* 设定GROUP BY。*/ + public function groupBy($groupBy) + { + $this->sql .= ' ' . DAO::GROUPBY . " $groupBy"; + return $this; + } + + /* 设定having。*/ + public function having($having) + { + $this->sql .= ' ' . DAO::HAVING . " $having"; + return $this; + } + + /* 返回拼装好的语句。*/ + public function get() + { + return $this->sql; + } + + /* 转义。*/ + public function quote($value) + { + if($this->magicQuote) $value = stripslashes($value); + return $this->dbh->quote($value); + } +} diff --git a/lib/filter/filter.class.php b/lib/filter/filter.class.php new file mode 100755 index 0000000000..e761eef3b2 --- /dev/null +++ b/lib/filter/filter.class.php @@ -0,0 +1,348 @@ +. + * + * @copyright Copyright 2009-2010 青岛易软天创网络科技有限公司(www.cnezsoft.com) + * @author Chunsheng Wang + * @package ZenTaoPHP + * @version $Id: filter.class.php 134 2010-09-11 07:24:27Z wwccss $ + * @link http://www.zentao.net + */ +/** + * validate类,提供对数据的验证。 + * + * @package ZenTaoPHP + */ +class validater +{ + /** + * 参数个数的最大值。 + */ + const MAX_ARGS = 3; + + /* 检查是否是布尔型。*/ + public static function checkBool($var) + { + return filter_var($var, FILTER_VALIDATE_BOOLEAN); + } + + /* 检查是否是整型。*/ + public static function checkInt($var) + { + $args = func_get_args(); + if($var != 0) $var = ltrim($var, 0); // 将左边的0去掉,filter的这个过滤规则比较严格。 + + /* 设置了min。*/ + if(isset($args[1])) + { + /* 同时设置了max。*/ + if(isset($args[2])) + { + $options = array('options' => array('min_range' => $args[1], 'max_range' => $args[2])); + } + else + { + $options = array('options' => array('min_range' => $args[1])); + } + + return filter_var($var, FILTER_VALIDATE_INT, $options); + } + else + { + return filter_var($var, FILTER_VALIDATE_INT); + } + } + + /* 检查是否是浮点型。*/ + public static function checkFloat($var, $decimal = '.') + { + return filter_var($var, FILTER_VALIDATE_FLOAT, array('options' => array('decimail' => $decimal))); + } + + /* 检查是否是email地址。*/ + public static function checkEmail($var) + { + return filter_var($var, FILTER_VALIDATE_EMAIL); + } + + /* 检查是否是URL地址。备注:filter的这个检查并不靠普,比如如果url地址含有中文,就会失效。 */ + public static function checkURL($var) + { + return filter_var($var, FILTER_VALIDATE_URL); + } + + /* 检查是否是IP地址。NO_PRIV_RANGE是检查是否是私有地址,NO_RES_RANGE检查是否是保留IP地址。*/ + public static function checkIP($var, $range = 'all') + { + if($range == 'all') return filter_var($var, FILTER_VALIDATE_IP); + if($range == 'public static') return filter_var($var, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE); + if($range == 'private') + { + if(filter_var($var, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE) === false) return $var; + return false; + } + } + + /* 检查是否是日期。bug: 2009-09-31会被认为合法的日期,因为strtotime自动将其改为了10-01。*/ + public static function checkDate($date) + { + if($date == '0000-00-00') return true; + $stamp = strtotime($date); + if(!is_numeric($stamp)) return false; + return checkdate(date('m', $stamp), date('d', $stamp), date('Y', $stamp)); + } + + /* 检查是否符合正则表达式。*/ + public static function checkREG($var, $reg) + { + return filter_var($var, FILTER_VALIDATE_REGEXP, array('options' => array('regexp' => $reg))); + } + + /* 检查长度是否在指定的范围内。*/ + public static function checkLength($var, $max, $min = 0) + { + return self::checkInt(strlen($var), $min, $max); + } + + /* 检查长度是否在指定的范围内。*/ + public static function checkNotEmpty($var) + { + return !empty($var); + } + + /* 检查用户名。*/ + public static function checkAccount($var) + { + return self::checkREG($var, '|[a-zA-Z0-9._]{3}|'); + } + + /* 必须为某值。*/ + public static function checkEqual($var, $value) + { + return $var == $value; + } + + /* 调用回掉函数。*/ + public static function call($var, $func) + { + return filter_var($var, FILTER_CALLBACK, array('options' => $func)); + } +} + +/** + * fixer类,提供对数据的修正。 + * + * @package ZenTaoPHP + */ +class fixer +{ + /** + * 要处理的数据。 + * + * @var ojbect + * @access private + */ + private $data; + + /* 构造函数。*/ + private function __construct($scope) + { + switch($scope) + { + case 'post': + $this->data = (object)$_POST; + break; + case 'server': + $this->data = (object)$_SERVER; + break; + case 'get': + $this->data = (object)$_GET; + break; + case 'session': + $this->data = (object)$_SESSION; + break; + case 'cookie': + $this->data = (object)$_COOKIE; + break; + case 'env': + $this->data = (object)$_ENV; + break; + case 'file': + $this->data = (object)$_FILES; + break; + + default: + die('scope not supported, should be post|get|server|session|cookie|env'); + } + } + + /* factory。*/ + public function input($scope) + { + return new fixer($scope); + } + + /* 去除email里面的非法字符。*/ + public function cleanEmail($fieldName) + { + $fields = $this->processFields($fieldName); + foreach($fields as $fieldName) $this->data->$fieldName = filter_var($this->data->$fieldName, FILTER_SANITIZE_EMAIL); + return $this; + } + + /* 对URL进行编码。*/ + public function encodeURL($fieldName) + { + $fields = $this->processFields($fieldName); + $args = func_get_args(); + foreach($fields as $fieldName) + { + $this->data->$fieldName = isset($args[1]) ? filter_var($this->data->$fieldName, FILTER_SANITIZE_ENCODED, $args[1]) : filter_var($this->data->$fieldName, FILTER_SANITIZE_ENCODED); + } + return $this; + } + + /* 去除url里面的非法字符。*/ + public function cleanURL($fieldName) + { + $fields = $this->processFields($fieldName); + foreach($fields as $fieldName) $this->data->$fieldName = filter_var($this->data->$fieldName, FILTER_SANITIZE_URL); + return $this; + } + + /* 获取浮点数。*/ + public function cleanFloat($fieldName) + { + $fields = $this->processFields($fieldName); + foreach($fields as $fieldName) $this->data->$fieldName = filter_var($this->data->$fieldName, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION|FILTER_FLAG_ALLOW_THOUSAND); + return $this; + } + + /* 获取整型。*/ + public function cleanINT($fieldName = '') + { + $fields = $this->processFields($fieldName); + foreach($fields as $fieldName) $this->data->$fieldName = filter_var($this->data->$fieldName, FILTER_SANITIZE_NUMBER_INT); + return $this; + } + + /* 处理特殊字符。*/ + public function specialChars($fieldName) + { + $fields = $this->processFields($fieldName); + foreach($fields as $fieldName) $this->data->$fieldName = htmlspecialchars($this->data->$fieldName); + return $this; + } + + /* 去除字符串里面的标签。*/ + public function stripTags($fieldName) + { + $fields = $this->processFields($fieldName); + foreach($fields as $fieldName) $this->data->$fieldName = filter_var($this->data->$fieldName, FILTER_SANITIZE_STRING); + return $this; + } + + /* 添加斜线。*/ + public function quote($fieldName) + { + $fields = $this->processFields($fieldName); + foreach($fields as $fieldName) $this->data->$fieldName = filter_var($this->data->$fieldName, FILTER_SANITIZE_MAGIC_QUOTES); + return $this; + } + + /* 设置默认值。*/ + public function setDefault($fields, $value) + { + $fields = strpos($fields, ',') ? explode(',', str_replace(' ', '', $fields)) : array($fields); + foreach($fields as $fieldName)if(!isset($this->data->$fieldName) or empty($this->data->$fieldName)) $this->data->$fieldName = $value; + return $this; + } + + /* 条件设置。*/ + public function setIF($condition, $fieldName, $value) + { + if($condition) $this->data->$fieldName = $value; + return $this; + } + + /* 强制设置。*/ + public function setForce($fieldName, $value) + { + $this->data->$fieldName = $value; + return $this; + } + + /* 删除某一个字段。*/ + public function remove($fieldName) + { + $fields = $this->processFields($fieldName); + foreach($fields as $fieldName) unset($this->data->$fieldName); + return $this; + } + + /* 条件删除。*/ + public function removeIF($condition, $fields) + { + $fields = $this->processFields($fields); + if($condition) foreach($fields as $fieldName) unset($this->data->$fieldName); + return $this; + } + + /* 添加一个字段。*/ + public function add($fieldName, $value) + { + $this->data->$fieldName = $value; + return $this; + } + + /* 条件添加。*/ + public function addIF($condition, $fieldName, $value) + { + if($condition) $this->data->$fieldName = $value; + return $this; + } + + /* 连接。*/ + public function join($fieldName, $value) + { + if(!isset($this->data->$fieldName) or !is_array($this->data->$fieldName)) return $this; + $this->data->$fieldName = join($value, $this->data->$fieldName); + return $this; + } + + /* 调用回掉函数。*/ + public function callFunc($fieldName, $func) + { + $fields = $this->processFields($fieldName); + foreach($fields as $fieldName) $this->data->$fieldName = filter_var($this->data->$fieldName, FILTER_CALLBACK, array('options' => $func)); + return $this; + } + + /* 返回最终处理之后的数据。*/ + public function get($fieldName = '') + { + if(empty($fieldName)) return $this->data; + return $this->data->$fieldName; + } + + /* 处理传入的字段名:如果含有逗号,将其拆为数组。然后检查data变量中是否有这个字段。*/ + private function processFields($fields) + { + $fields = strpos($fields, ',') ? explode(',', str_replace(' ', '', $fields)) : array($fields); + foreach($fields as $key => $fieldName) if(!isset($this->data->$fieldName)) unset($fields[$key]); + return $fields; + } +} diff --git a/lib/pager/pager.class.php b/lib/pager/pager.class.php new file mode 100755 index 0000000000..43b69924c4 --- /dev/null +++ b/lib/pager/pager.class.php @@ -0,0 +1,347 @@ +. + * + * @copyright Copyright 2009-2010 青岛易软天创网络科技有限公司(www.cnezsoft.com) + * @author Chunsheng Wang + * @package ZenTaoPHP + * @version $Id: pager.class.php 134 2010-09-11 07:24:27Z wwccss $ + * @link http://www.zentao.net + */ +/** + * pager类,提供对数据库分页的操作。 + * + * @package ZenTaoPHP + */ +class pager +{ + /** + * 默认每页显示记录数。 + * + * @public int + */ + const DEFAULT_REC_PRE_PAGE = 20; + + /** + * 记录总数。 + * + * @public int + */ + public $recTotal; + + /** + * 每页显示记录数。 + * + * @public int + */ + public $recPerPage; + + /** + * 总共的页数。 + * + * @public int + */ + public $pageTotal; + + /** + * 当前页数范围。 + * + * @public int + */ + public $pageID; + + /** + * 全局的app对象。 + * + * @private object + */ + private $app; + + /** + * 全局的lang对象。 + * + * @private object + */ + private $lang; + + /** + * 当前请求的moduleName。 + * + * @private string + */ + private $moduleName; + + /** + * 当前请求的methodName。 + * + * @private string + */ + private $methodName; + + /** + * 当前请求的参数。 + * + * @private array + */ + private $params; + + /** + * 构造函数。 + * + * @param int $recTotal 记录总数 + * @param int $recPerPage 每页记录数。 + * @param int $pageID 当前分页ID。 + */ + public function __construct($recTotal = 0, $recPerPage = 20, $pageID = 1) + { + $this->setRecTotal($recTotal); + $this->setRecPerPage($recPerPage); + $this->setPageTotal(); + $this->setPageID($pageID); + $this->setApp(); + $this->setLang(); + $this->setModuleName(); + $this->setMethodName(); + } + + /* factory. */ + public function init($recTotal = 0, $recPerPage = 20, $pageID = 1) + { + return new pager($recTotal, $recPerPage, $pageID); + } + + /* 设置记录总数。*/ + public function setRecTotal($recTotal = 0) + { + $this->recTotal = (int)$recTotal; + } + + /* 设置每页显示的记录数。*/ + public function setRecPerPage($recPerPage) + { + $this->recPerPage = ($recPerPage > 0) ? $recPerPage : PAGER::DEFAULT_REC_PRE_PAGE; + } + + /* 设置总共的页数。*/ + public function setPageTotal() + { + $this->pageTotal = ceil($this->recTotal / $this->recPerPage); + } + + /* 设置当前的分页ID。*/ + public function setPageID($pageID) + { + if($pageID > 0 and $pageID <= $this->pageTotal) + { + $this->pageID = $pageID; + } + else + { + $this->pageID = 1; + } + } + + /* 设置app对象。*/ + private function setApp() + { + global $app; + $this->app = $app; + } + + /* 设置lang对象。*/ + private function setLang() + { + global $lang; + $this->lang = $lang; + } + + /* 设置moduleName。*/ + private function setModuleName() + { + $this->moduleName = $this->app->getModuleName(); + } + + /* 设置methodName。*/ + private function setMethodName() + { + $this->methodName = $this->app->getMethodName(); + } + + /* 设置params。备注:该方法应当在生成html代码之前被调用。*/ + private function setParams() + { + $this->params = $this->app->getParams(); + foreach($this->params as $key => $value) + { + if(strtolower($key) == 'rectotal') $this->params[$key] = $this->recTotal; + if(strtolower($key) == 'recperpage') $this->params[$key] = $this->recPerPage; + if(strtolower($key) == 'pageID') $this->params[$key] = $this->pageID; + } + } + + /* 生成limit语句。*/ + public function limit() + { + $limit = ''; + if($this->pageTotal > 1) $limit = ' limit ' . ($this->pageID - 1) * $this->recPerPage . ", $this->recPerPage"; + return $limit; + } + + /* 直接显示分页代码。*/ + public function show($align = 'right', $type = 'full') + { + echo $this->get($align, $type); + } + + /** + * 返回pager的html代码。 + * + * @param string $align Alignment, left|center|right, the default is right. + * @param string $type type, full|short|shortest. + * @return string The html code of the pager. + */ + function get($align = 'right', $type = 'full') + { + /* If the RecTotal is zero, return with no record. */ + if($this->recTotal == 0) { return "
{$this->lang->pager->noRecord}
"; } + + /* 设置当前请求传递的参数。*/ + $this->setParams(); + + /* 所有模式下都有的内容。*/ + $pager = $this->createPrePage(); + $pager .= $this->createNextPage(); + + /* short和full模式都有的内容。*/ + if($type !== 'shortest') + { + $pager = $this->createFirstPage() . $pager; + $pager .= $this->createLastPage(); + } + + /* Full模式有的内容。*/ + if($type == 'full') + { + $pager = $this->createDigest() . $pager; + $pager .= $this->createGoTo(); + $pager .= $this->createRecPerPageJS(); + } + + return "
$pager
"; + } + + /* 生成摘要代码。*/ + function createDigest() + { + return sprintf($this->lang->pager->digest, $this->recTotal, $this->createRecPerPageList(), $this->pageID, $this->pageTotal); + } + + /* 生成首页链接。*/ + function createFirstPage() + { + if($this->pageID == 1) return $this->lang->pager->first . ' '; + $this->params['pageID'] = 1; + return html::a(helper::createLink($this->moduleName, $this->methodName, $this->params), $this->lang->pager->first); + } + + /* 生成前页链接。*/ + function createPrePage() + { + if($this->pageID == 1) return $this->lang->pager->pre . ' '; + $this->params['pageID'] = $this->pageID - 1; + return html::a(helper::createLink($this->moduleName, $this->methodName, $this->params), $this->lang->pager->pre); + } + + /* 生成下页链接。*/ + function createNextPage() + { + if($this->pageID == $this->pageTotal) return $this->lang->pager->next . ' '; + $this->params['pageID'] = $this->pageID + 1; + return html::a(helper::createLink($this->moduleName, $this->methodName, $this->params), $this->lang->pager->next); + } + + /* 生成末页链接。*/ + function createLastPage() + { + if($this->pageID == $this->pageTotal) return $this->lang->pager->last . ' '; + $this->params['pageID'] = $this->pageTotal; + return html::a(helper::createLink($this->moduleName, $this->methodName, $this->params), $this->lang->pager->last); + } + + /* 生成JS代码。*/ + function createRecPerPageJS() + { + /* 重新修正params,将其中关于分页的变量对应的值设置为特殊的标记,然后使用js将其替换为对应的值。*/ + $params = $this->params; + foreach($params as $key => $value) + { + if(strtolower($key) == 'rectotal') $params[$key] = '_recTotal_'; + if(strtolower($key) == 'recperpage') $params[$key] = '_recPerPage_'; + if(strtolower($key) == 'pageid') $params[$key] = '_pageID_'; + } + $vars = ''; + foreach($params as $key => $value) $vars .= "$key=$value&"; + $vars = rtrim($vars, '&'); + + $js = << + vars = '$vars'; + function submitPage(mode) + { + pageTotal = parseInt(document.getElementById('_pageTotal').value); + pageID = document.getElementById('_pageID').value; + recPerPage = document.getElementById('_recPerPage').value; + recTotal = document.getElementById('_recTotal').value; + if(mode == 'changePageID') + { + if(pageID > pageTotal) pageID = pageTotal; + if(pageID < 1) pageID = 1; + } + else if(mode == 'changeRecPerPage') + { + pageID = 1; + } + + vars = vars.replace('_recTotal_', recTotal) + vars = vars.replace('_recPerPage_', recPerPage) + vars = vars.replace('_pageID_', pageID); + location.href=createLink('$this->moduleName', '$this->methodName', vars); + } + +EOT; + return $js; + } + + /* Create the select list of RecPerPage. */ + function createRecPerPageList() + { + for($i = 5; $i <= 50; $i += 5) $range[$i] = $i; + $range[100] = 100; + $range[200] = 200; + $range[500] = 500; + return html::select('_recPerPage', $range, $this->recPerPage, "onchange='submitPage(\"changeRecPerPage\");'"); + } + + /* Create the link html code of goto box. */ + function createGoTo() + { + $goToHtml = "\n"; + $goToHtml .= "\n"; + $goToHtml .= " \n"; + $goToHtml .= ""; + return $goToHtml; + } +}