rabin 7 年之前
當前提交
ec47c90bc4
共有 100 個文件被更改,包括 21613 次插入0 次删除
  1. 444 0
      KIF/.svn/entries
  2. 5 0
      KIF/.svn/prop-base/Cookie.class.php.svn-base
  3. 5 0
      KIF/.svn/prop-base/Curl.class.php.svn-base
  4. 5 0
      KIF/.svn/prop-base/Image.class.php.svn-base
  5. 5 0
      KIF/.svn/prop-base/Route.class.php.svn-base
  6. 5 0
      KIF/.svn/prop-base/TODO.txt.svn-base
  7. 5 0
      KIF/.svn/prop-base/Upload.class.php.svn-base
  8. 5 0
      KIF/.svn/prop-base/Url.class.php.svn-base
  9. 5 0
      KIF/.svn/prop-base/Verify.class.php.svn-base
  10. 5 0
      KIF/.svn/prop-base/global_constants.php.svn-base
  11. 5 0
      KIF/.svn/prop-base/init.php.svn-base
  12. 5 0
      KIF/.svn/prop-base/phpQuery.php.svn-base
  13. 65 0
      KIF/.svn/text-base/Cookie.class.php.svn-base
  14. 417 0
      KIF/.svn/text-base/Curl.class.php.svn-base
  15. 99 0
      KIF/.svn/text-base/Image.class.php.svn-base
  16. 210 0
      KIF/.svn/text-base/Route.class.php.svn-base
  17. 19 0
      KIF/.svn/text-base/TODO.txt.svn-base
  18. 159 0
      KIF/.svn/text-base/Upload.class.php.svn-base
  19. 9 0
      KIF/.svn/text-base/Url.class.php.svn-base
  20. 97 0
      KIF/.svn/text-base/Verify.class.php.svn-base
  21. 21 0
      KIF/.svn/text-base/global_constants.php.svn-base
  22. 70 0
      KIF/.svn/text-base/init.php.svn-base
  23. 5702 0
      KIF/.svn/text-base/phpQuery.php.svn-base
  24. 96 0
      KIF/Cache/.svn/entries
  25. 5 0
      KIF/Cache/.svn/prop-base/Memcached.class.php.svn-base
  26. 5 0
      KIF/Cache/.svn/prop-base/TTServer.class.php.svn-base
  27. 453 0
      KIF/Cache/.svn/text-base/Memcached.class.php.svn-base
  28. 292 0
      KIF/Cache/.svn/text-base/TTServer.class.php.svn-base
  29. 453 0
      KIF/Cache/Memcached.class.php
  30. 292 0
      KIF/Cache/TTServer.class.php
  31. 62 0
      KIF/Cli/.svn/entries
  32. 5 0
      KIF/Cli/.svn/prop-base/SingleProcess.class.php.svn-base
  33. 260 0
      KIF/Cli/.svn/text-base/SingleProcess.class.php.svn-base
  34. 260 0
      KIF/Cli/SingleProcess.class.php
  35. 65 0
      KIF/Cookie.class.php
  36. 436 0
      KIF/Core/.svn/entries
  37. 5 0
      KIF/Core/.svn/prop-base/AbstractDaemon.class.php.svn-base
  38. 5 0
      KIF/Core/.svn/prop-base/BKController.class.php.svn-base
  39. 5 0
      KIF/Core/.svn/prop-base/Config.class.php.svn-base
  40. 5 0
      KIF/Core/.svn/prop-base/Controller.class.php.svn-base
  41. 5 0
      KIF/Core/.svn/prop-base/Core.class.php.svn-base
  42. 5 0
      KIF/Core/.svn/prop-base/FController.class.php.svn-base
  43. 5 0
      KIF/Core/.svn/prop-base/IPC.class.php.svn-base
  44. 5 0
      KIF/Core/.svn/prop-base/Model.class.php.svn-base
  45. 5 0
      KIF/Core/.svn/prop-base/PermissionController.class.php.svn-base
  46. 5 0
      KIF/Core/.svn/prop-base/Request.class.php.svn-base
  47. 5 0
      KIF/Core/.svn/prop-base/View.class.php.svn-base
  48. 5 0
      KIF/Core/.svn/prop-base/WXController.class.php.svn-base
  49. 88 0
      KIF/Core/.svn/text-base/AbstractDaemon.class.php.svn-base
  50. 88 0
      KIF/Core/.svn/text-base/BKController.class.php.svn-base
  51. 144 0
      KIF/Core/.svn/text-base/Config.class.php.svn-base
  52. 238 0
      KIF/Core/.svn/text-base/Controller.class.php.svn-base
  53. 59 0
      KIF/Core/.svn/text-base/Core.class.php.svn-base
  54. 400 0
      KIF/Core/.svn/text-base/FController.class.php.svn-base
  55. 64 0
      KIF/Core/.svn/text-base/IPC.class.php.svn-base
  56. 47 0
      KIF/Core/.svn/text-base/Model.class.php.svn-base
  57. 227 0
      KIF/Core/.svn/text-base/PermissionController.class.php.svn-base
  58. 714 0
      KIF/Core/.svn/text-base/Request.class.php.svn-base
  59. 88 0
      KIF/Core/.svn/text-base/View.class.php.svn-base
  60. 167 0
      KIF/Core/.svn/text-base/WXController.class.php.svn-base
  61. 88 0
      KIF/Core/AbstractDaemon.class.php
  62. 88 0
      KIF/Core/BKController.class.php
  63. 144 0
      KIF/Core/Config.class.php
  64. 238 0
      KIF/Core/Controller.class.php
  65. 59 0
      KIF/Core/Core.class.php
  66. 400 0
      KIF/Core/FController.class.php
  67. 64 0
      KIF/Core/IPC.class.php
  68. 47 0
      KIF/Core/Model.class.php
  69. 227 0
      KIF/Core/PermissionController.class.php
  70. 714 0
      KIF/Core/Request.class.php
  71. 88 0
      KIF/Core/View.class.php
  72. 167 0
      KIF/Core/WXController.class.php
  73. 417 0
      KIF/Curl.class.php
  74. 130 0
      KIF/Dao/.svn/entries
  75. 5 0
      KIF/Dao/.svn/prop-base/AbstractDao.class.php.svn-base
  76. 5 0
      KIF/Dao/.svn/prop-base/DBAgileDev.class.php.svn-base
  77. 5 0
      KIF/Dao/.svn/prop-base/SqlHelper.class.php.svn-base
  78. 829 0
      KIF/Dao/.svn/text-base/AbstractDao.class.php.svn-base
  79. 278 0
      KIF/Dao/.svn/text-base/DBAgileDev.class.php.svn-base
  80. 157 0
      KIF/Dao/.svn/text-base/SqlHelper.class.php.svn-base
  81. 829 0
      KIF/Dao/AbstractDao.class.php
  82. 278 0
      KIF/Dao/DBAgileDev.class.php
  83. 157 0
      KIF/Dao/SqlHelper.class.php
  84. 96 0
      KIF/Data/.svn/entries
  85. 5 0
      KIF/Data/.svn/prop-base/Convert.class.php.svn-base
  86. 5 0
      KIF/Data/.svn/prop-base/ResultWrapper.class.php.svn-base
  87. 219 0
      KIF/Data/.svn/text-base/Convert.class.php.svn-base
  88. 70 0
      KIF/Data/.svn/text-base/ResultWrapper.class.php.svn-base
  89. 219 0
      KIF/Data/Convert.class.php
  90. 70 0
      KIF/Data/ResultWrapper.class.php
  91. 96 0
      KIF/Db/.svn/entries
  92. 5 0
      KIF/Db/.svn/prop-base/MySQLi.class.php.svn-base
  93. 5 0
      KIF/Db/.svn/prop-base/Transaction.class.php.svn-base
  94. 1354 0
      KIF/Db/.svn/text-base/MySQLi.class.php.svn-base
  95. 80 0
      KIF/Db/.svn/text-base/Transaction.class.php.svn-base
  96. 1354 0
      KIF/Db/MySQLi.class.php
  97. 80 0
      KIF/Db/Transaction.class.php
  98. 96 0
      KIF/Debug/.svn/entries
  99. 5 0
      KIF/Debug/.svn/prop-base/Debug.class.php.svn-base
  100. 5 0
      KIF/Debug/.svn/prop-base/FirePHP.class.php.svn-base

+ 444 - 0
KIF/.svn/entries

@@ -0,0 +1,444 @@
+10
+
+dir
+924
+svn://182.92.3.30/project/onepage/KIF
+svn://182.92.3.30/project
+
+
+
+2016-01-21T06:27:41.361155Z
+137
+yubin
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+140960d9-11d8-4647-83e1-275f326d1242
+
+Page
+dir
+
+Image.class.php
+file
+
+
+
+
+2016-02-15T00:10:53.000000Z
+cc63b1656a65366edd41921b1ccaca66
+2016-01-21T06:27:41.361155Z
+137
+yubin
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+2879
+
+Upload.class.php
+file
+
+
+
+
+2016-02-15T00:10:53.000000Z
+b7bcd5eae1b3ebf6d960dfefa85d62d0
+2016-01-21T06:27:41.361155Z
+137
+yubin
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+4794
+
+test
+dir
+
+smarty
+dir
+
+Mail
+dir
+
+global_constants.php
+file
+
+
+
+
+2016-02-15T00:10:53.000000Z
+b1b79dea573444340b7c6e1f491989e0
+2016-01-21T06:27:41.361155Z
+137
+yubin
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+449
+
+TODO.txt
+file
+
+
+
+
+2016-02-15T00:10:53.000000Z
+11e88be3fd9a36b18a434c3779d410d3
+2016-01-21T06:27:41.361155Z
+137
+yubin
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+715
+
+Db
+dir
+
+Debug
+dir
+
+Core
+dir
+
+Route.class.php
+file
+
+
+
+
+2016-02-15T00:10:53.000000Z
+580300ff42886841d65878685628b797
+2016-01-21T06:27:41.361155Z
+137
+yubin
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+5311
+
+init.php
+file
+
+
+
+
+2016-02-15T00:10:53.000000Z
+e859f294342b9215e49c43b6f0859e47
+2016-01-21T06:27:41.361155Z
+137
+yubin
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+2224
+
+Math
+dir
+
+Url.class.php
+file
+
+
+
+
+2016-02-15T00:10:53.000000Z
+7414268b383b7ee76c145aa652412067
+2016-01-21T06:27:41.361155Z
+137
+yubin
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+62
+
+Verify.class.php
+file
+
+
+
+
+2016-02-15T00:10:53.000000Z
+8e2a6809b72b78f7a52d9eede16a2534
+2016-01-21T06:27:41.361155Z
+137
+yubin
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+2528
+
+Exception
+dir
+
+Curl.class.php
+file
+
+
+
+
+2016-02-15T00:10:53.000000Z
+8293ca1bea9965172b8ec0a8a6e957cc
+2016-01-21T06:27:41.361155Z
+137
+yubin
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+10200
+
+Cookie.class.php
+file
+
+
+
+
+2016-02-15T00:10:53.000000Z
+8b827698732bce6db29a64715d12e28b
+2016-01-21T06:27:41.361155Z
+137
+yubin
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+2418
+
+phpQuery.php
+file
+
+
+
+
+2016-02-15T00:10:53.000000Z
+5cb41d4d677a5f076c2a1fc26430dfe6
+2016-01-21T06:27:41.361155Z
+137
+yubin
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+167335
+
+Dao
+dir
+
+Cache
+dir
+
+String
+dir
+
+Cli
+dir
+
+Data
+dir
+

+ 5 - 0
KIF/.svn/prop-base/Cookie.class.php.svn-base

@@ -0,0 +1,5 @@
+K 14
+svn:executable
+V 1
+*
+END

+ 5 - 0
KIF/.svn/prop-base/Curl.class.php.svn-base

@@ -0,0 +1,5 @@
+K 14
+svn:executable
+V 1
+*
+END

+ 5 - 0
KIF/.svn/prop-base/Image.class.php.svn-base

@@ -0,0 +1,5 @@
+K 14
+svn:executable
+V 1
+*
+END

+ 5 - 0
KIF/.svn/prop-base/Route.class.php.svn-base

@@ -0,0 +1,5 @@
+K 14
+svn:executable
+V 1
+*
+END

+ 5 - 0
KIF/.svn/prop-base/TODO.txt.svn-base

@@ -0,0 +1,5 @@
+K 14
+svn:executable
+V 1
+*
+END

+ 5 - 0
KIF/.svn/prop-base/Upload.class.php.svn-base

@@ -0,0 +1,5 @@
+K 14
+svn:executable
+V 1
+*
+END

+ 5 - 0
KIF/.svn/prop-base/Url.class.php.svn-base

@@ -0,0 +1,5 @@
+K 14
+svn:executable
+V 1
+*
+END

+ 5 - 0
KIF/.svn/prop-base/Verify.class.php.svn-base

@@ -0,0 +1,5 @@
+K 14
+svn:executable
+V 1
+*
+END

+ 5 - 0
KIF/.svn/prop-base/global_constants.php.svn-base

@@ -0,0 +1,5 @@
+K 14
+svn:executable
+V 1
+*
+END

+ 5 - 0
KIF/.svn/prop-base/init.php.svn-base

@@ -0,0 +1,5 @@
+K 14
+svn:executable
+V 1
+*
+END

+ 5 - 0
KIF/.svn/prop-base/phpQuery.php.svn-base

@@ -0,0 +1,5 @@
+K 14
+svn:executable
+V 1
+*
+END

+ 65 - 0
KIF/.svn/text-base/Cookie.class.php.svn-base

@@ -0,0 +1,65 @@
+<?php
+namespace KIF;
+
+use KIF\Core\Request;
+use KIF\String\Filter;
+
+
+/**
+ *
+ * cookie处理类
+ * @author gaoxiaogang@gmail.com
+ *
+ */
+class Cookie {
+
+	/**
+	 * 封闭cookie的读操作,便于cookie的统一处理
+	 *
+	 * @param string $key
+	 * @return string | Boolean 不存在返回false;存在返回值
+	 */
+    static public function get($key, $filters = array(Filter::HTMLSPECIALCHARS, Filter::TRIM)) {
+    	return Request::c($key, $filters);
+    }
+
+    /**
+     * 包装 php的 setcookie,便于cookie的统一处理
+     *
+     * @param string $key cookie名
+     * @param string $value cookie值
+     * @param int $expiration cookie过期的时间。 实际发送的值可以是一个Unix时间戳(自1970年1月1日起至失效时间的整型秒数),或者是一个从现在算起的以秒为单位的数字。
+     *            对于后一种情况,这个秒数不能超过60×60×24×30(30天时间的秒数);如果失效的值大于这个值,会将其作为一个真实的Unix时间戳来处理而不是自当前时间的偏移。
+     * @param string $cookie_domain 默认为 DOMAIN常量,如未指定,则为 当前访问的根域名
+     * @param string $cookie_path cookie保存路径,默认为根目录 /
+     * @param boolean $secure 是否只能通过https协议访问。默认为false
+     * @param boolean $httponly 是否只能通过http协议读取cookie。值为true时,客户端的javascript不能读到该cookie。默认为false。
+     * @return boolean
+     */
+    static public function set($key, $value, $expiration = 0, $cookie_domain = null, $cookie_path = '/', $secure = false, $httponly = false) {
+    	if (is_null($cookie_domain)) {
+    		if (defined('DOMAIN')) {
+    			$cookie_domain = DOMAIN;
+    		} else {
+    			$cookie_domain = Request::rootDomain();
+    		}
+    	}
+    	if (is_null($cookie_path)) {
+    		$cookie_path = '/';
+    	}
+    	if (is_null($secure)) {
+    		$secure = false;
+    	}
+    	if (is_null($httponly)) {
+    		$httponly = false;
+    	}
+    	# 如果 $expiration 指定的过期时间小于30天,则
+    	if ($expiration > 0 && $expiration <= 60*60*24*30) {
+    		$expiration += time();
+    	}
+
+    	# 设置 $_COOKIE 变量,否则当前进程写完cookie后是无法立即读取到的。
+    	$_COOKIE[$key] = $value;
+    	return setcookie($key, $value, $expiration, $cookie_path, $cookie_domain, $secure, $httponly);
+    }
+}

+ 417 - 0
KIF/.svn/text-base/Curl.class.php.svn-base

@@ -0,0 +1,417 @@
+<?php
+namespace KIF;
+/**
+ * @name Curl.class.php
+ * @desc curl操作类
+ * @author 曹晓冬
+ * @createtime 2008-12-01 14:58
+ * @updatetime
+ * $curl_obj = new Curl('http://www.demo.com/');
+ * 基本使用方法
+ * $curl_obj->init();
+ * $curl_obj->setOptions(array("type"=>"post", "fields"=>$fields, "return"=>1, "onerror"=>1));
+ * $result = $curl_obj->getResult();
+ * 另外还提供了两种快速使用方法
+ * 1.post方式
+ * $result = $curl_obj->post(array('param' => 'value'));
+ * 2.get方法
+ * $result = $curl_obj->get();
+ * 
+ */
+
+class Curl
+{
+  /**
+  * 当前curl对话
+  * @var resource
+  * @access private
+  */
+  private $ch;
+  
+  /**
+  * 当前发送的地址
+  *
+  * @var string
+  * @access private
+  */
+  private $url;
+  
+  /**
+  * 调试信息
+  *
+  * @var string
+  * @access private
+  */
+  private $debug; 
+  
+  /**
+  * 返回是否包括header头
+  *
+  * @var integer
+  * @access private
+  */
+  private $header = 0; 
+  
+  /**
+  * 构造函数
+  * @return void
+  * @access public
+  */
+  public function __construct($url)
+  {
+      $this->url = $url;
+  	  $this->ch = @curl_init();
+      if (!$this->ch)
+      {
+          return false;
+      }
+  }
+  
+  /**
+  * 设定返回是否包括header 头
+  * @param integer $header 0或1,1包括,0不包括
+  * @return void 
+  * @access public
+  */
+  public function setHeader($header = 0)
+  {
+      $this->header = $header;
+  }
+  
+  
+  /**
+  * 初始化curl对话
+  * @param string $url 当前发送的地址
+  * @return boolean
+  * @access private
+  */
+  public function init()
+  {
+      $this->basic();
+      return true;
+  }
+  
+  /** 
+  * 基本选项
+  *
+  * @return void
+  * @access private
+  */
+  private function basic()
+  {
+      curl_setopt($this->ch, CURLOPT_URL, $this->url);
+      curl_setopt($this->ch, CURLOPT_HEADER, $this->header);
+  }
+  
+  /**
+   * 
+   * 调整用curl原生的方法,直接设置option
+   * @param unknown_type $key
+   * @param unknown_type $value
+   */
+  public function setRawOption($key, $value) {
+  	curl_setopt($this->ch, $key, $value);  
+  }
+
+  /**
+  * 设置选项
+  *
+  * @return void
+  * @access public
+  */
+  public function setOptions($options = array())
+  {
+      if (is_array($options))
+      {
+          foreach ($options as $key=>$value)    
+          {
+              $this->$key = $value;    
+          }
+      }
+      //如果HTTP返回大于300, 是否显示错误
+      if (isset($this->onerror) && $this->onerror)
+      {
+          curl_setopt($this->ch, CURLOPT_FAILONERROR, 1);    
+      }
+      
+      //是否有返回值
+      if (isset($this->return) && $this->return == true && !isset($this->file)) 
+      {
+          curl_setopt($this->ch, CURLOPT_RETURNTRANSFER, 1);
+      }
+      
+      //HTTP 认证
+      if (isset($this->username) && $this->username != "") 
+      {
+          curl_setopt($this->ch, CURLOPT_USERPWD, "{$this->username}:{$this->password}");
+      }
+      
+      //SSL 检查
+      if (isset($this->sslVersion)) 
+      {
+          curl_setopt($this->ch, CURLOPT_SSLVERSION, $this->sslVersion);
+      }
+      if (isset($this->sslCert)) 
+      {
+          curl_setopt($this->ch, CURLOPT_SSLCERT, $this->sslCert);
+      }
+      if (isset($this->sslCertPasswd)) 
+      {
+          curl_setopt($this->ch, CURLOPT_SSLCERTPASSWD, $this->sslCertPasswd);
+      }
+      
+      //代理服务器
+      if (isset($this->proxy))
+      {
+          curl_setopt($this->ch, CURLOPT_PROXY, $this->proxy);
+      }
+      if (isset($this->proxyUser) || isset($this->proxyPassword)) 
+      {
+          curl_setopt($this->ch, CURLOPT_PROXYUSERPWD, "{$this->proxyUser}:{$this->proxyPassword}");
+      }
+      
+      //传输类型
+      if (isset($this->type)) 
+      {
+          switch (strtolower($this->type)) 
+          {
+              case "post":
+                  curl_setopt($this->ch, CURLOPT_POST, 1);
+                  break;
+              case "put":
+                  curl_setopt($this->ch, CURLOPT_PUT, 1);
+                  break;
+          }
+      }        
+      
+      //上传相关
+      if (isset($this->file)) 
+      {
+          if (!isset($this->filesize)) 
+          {
+              $this->filesize = filesize($this->file);
+          }
+          curl_setopt($this->ch, CURLOPT_INFILE, $this->file);
+          curl_setopt($this->ch, CURLOPT_INFILESIZE, $this->filesize);
+          curl_setopt($this->ch, CURLOPT_UPLOAD, 1);
+      }
+      
+      //数据发送
+      if (isset($this->fields)) 
+      {
+          if (!is_array($this->fields))
+          {
+              if (!isset($this->type))
+              {
+                  $this->type = "post";
+                  curl_setopt($this->ch, CURLOPT_POST, 1);
+              }
+              curl_setopt($this->ch, CURLOPT_POSTFIELDS, $this->fields);
+          }
+          else 
+          {
+              if (!empty($this->fields))
+              {
+                  $p = array();
+                  foreach ($this->fields as $key=>$value)
+                  {
+                      $p[] = $key . "=" . urlencode($value);
+                  }
+                  if (!isset($this->type))
+                  {
+                      $this->type = "post";
+                      curl_setopt($this->ch, CURLOPT_POST, 1);
+                  }
+                  curl_setopt($this->ch, CURLOPT_POSTFIELDS, implode("&", $p));
+              }        
+          }
+      }
+      
+      
+      //错误相关
+      if (isset($this->progress) && $this->progress == true) 
+      {
+          curl_setopt($this->ch, CURLOPT_PROGRESS, 1);
+      }
+      if (isset($this->verbose) && $this->verbose == true) 
+      {
+          curl_setopt($this->ch, CURLOPT_VERBOSE, 1);
+      }
+      if (isset($this->mute) && !$this->mute) 
+      {
+          curl_setopt($this->ch, CURLOPT_MUTE, 0);
+      }
+
+      //其它相关
+      if (isset($this->followLocation))
+      {
+          curl_setopt($this->ch, CURLOPT_FOLLOWLOCATION, $this->followLocation);
+      }
+      if (isset($this->timeout) && $this->timeout>0)
+      {
+          curl_setopt($this->ch, CURLOPT_TIMEOUT, $this->timeout);    
+      }
+      else
+      {
+          curl_setopt($this->ch, CURLOPT_TIMEOUT, 20);
+      }
+      if (isset($this->connecttimeout) && $this->connecttimeout>0)
+      {
+          curl_setopt($this->ch, CURLOPT_CONNECTTIMEOUT, $this->connecttimeout);    
+      }
+      else
+      {
+          curl_setopt($this->ch, CURLOPT_CONNECTTIMEOUT, 5);
+      }
+      if (isset($this->userAgent)) 
+      {
+          curl_setopt($this->ch, CURLOPT_USERAGENT, $this->userAgent);
+      }
+      
+      //cookie 相关
+      if (isset($this->cookie)) 
+      {
+          $cookieData = "";
+          foreach ($this->cookie as $name => $value) 
+          {
+              $cookieData .= urlencode($name) . "=" . urlencode($value) . ";";
+          }
+          curl_setopt($this->ch, CURLOPT_COOKIE, $cookieData);
+      }
+      
+      //http 头
+      if (isset($this->httpHeaders)) 
+      {
+          curl_setopt($this->ch, CURLOPT_HTTPHEADER, $this->httpHeaders );
+      }
+      
+      curl_setopt($this->ch, CURLOPT_HTTPAUTH, 'CURLAUTH_DIGEST');
+  }    
+      
+  /**
+  * 设置返回结果选项,并返回结果
+  * @return string 返回结果
+  * @access public
+  */
+  public function getResult()
+  {
+      $result = curl_exec($this->ch);
+      return $result;
+  }
+  
+  /**
+  * 关闭当前curl对话
+  * @return void
+  * @access public
+  */
+  public function close()
+  {
+      @curl_close($this->ch);    
+  }
+  
+  /**
+  * 得到对话中产生的错误描述
+  * @return string 错误描述
+  * @access public
+  */
+  public function getError()
+  {
+      return curl_error($this->ch);    
+  }
+  
+  /**
+  * 得到对话中产生的错误号
+  * @return integer 错误号
+  * @access public
+  */
+  public function getErrno()
+  {
+      return curl_errno($this->ch);    
+  }
+  
+  /**
+  * 中断执行,并输出错误信息
+  * @param string $msg 错误信息
+  * @return void
+  * @access private
+  */
+  private function halt($msg)
+  {
+      $message = "\n<br>信息:{$msg}";
+      $message .= "\n<br>错误号:".$this->getErrno();
+      $message .= "\n<br>错误:".$this->getError();
+      echo $message;
+      exit;
+  }    
+  
+  /**
+  * 调试信息
+  * 
+  * @return void
+  * @access private
+  */
+  private function debug()
+  {
+      $message .= "\n<br>错误号:".$this->getErrno();
+      $message .= "\n<br>错误:".$this->getError();
+      $this->debug = $message;
+  }    
+  
+  /**
+  * 获得以POST方式发送的结果
+  * @param array/string $fields 发送的数据
+  * @return string 返回的结果
+  * @access public
+  */
+  public function post($fields = array())
+  {
+      $re = $this->init();
+      if ($re){
+          $this->setOptions(array("type"=>"post", "fields"=>$fields, "return"=>1, "onerror"=>1));
+          $result = $this->getResult();
+          //$this->close();
+          return $result;
+      }else{
+          return "";
+      }
+  }
+  
+  /**
+  * 获得以GET方式发送的结果
+  * @return string 返回的结果
+  * @access public
+  */
+  public function get()
+  {
+      $re = $this->init();
+      if ($re){
+          $this->setOptions(array("return"=>1, "onerror"=>1));
+          $result = $this->getResult();
+          $this->close();
+          return $result; 
+      }else{
+          return "";
+      }
+  }
+  
+  /**
+  * 静态调用,获得以COOKIE方式发送的结果
+  * @param string $url 发送的地址
+  * @param array/string $fields 发送的数据
+  * @return string 返回的结果
+  * @access public
+  */
+  public function cookie($fields = array())
+  {
+      $re = $this->init();
+      if ($re){
+          $this->setOptions(array("cookie"=>$fields, "return"=>1, "onerror"=>1));
+          $result = $this->getResult();
+          $this->close();
+          return $result; 
+      }else{
+          return "";
+      }
+  }
+}
+?>

+ 99 - 0
KIF/.svn/text-base/Image.class.php.svn-base

@@ -0,0 +1,99 @@
+<?php
+namespace KIF;
+
+use KIF\Data\ResultWrapper;
+
+class Image {
+	
+	/**
+	 * @desc 缩放图片
+	 * @param string $src 图片源地址(全路径)
+	 * @param int $dst_w 目标宽度
+	 * @param int $dst_h 目标高度
+	 * @param string $dst 目标地址(全路径) 如果指定,则把缩放后的图片直接写入到$dst指定的路径;否则则返回图片的二进制值
+	 * @param boolean $isHold 是否锁定原图的高宽比。如果false(不锁定),则严格按照指定的$dst_w和$dst_h生成新的图片
+	 * @param string $format 缩放后图片的格式。如果不指定,则使用原图的格式
+	 * @return InternalResultTransfer
+	 */
+	static function resize($src, $dst_w, $dst_h, $dst = null, $isHold = false, $format = null) {
+		if (empty($src)) {
+			return ResultWrapper::fail("请指定原图");
+		}
+		if (!file_exists($src)) {
+			return ResultWrapper::fail("{$src} 该图片文件不存在");
+		}
+
+		$objImagick = new \Imagick();
+		$objImagick ->readImage($src);
+		if ($isHold) {
+			$src_h = $objImagick->getImageHeight();
+			$src_w = $objImagick->getImageWidth();
+			/// 源图片比目标图片要小
+			if ($src_w < $dst_w && $src_h < $dst_h) {
+				$hratio = $dst_h / $src_h;
+				$wratio = $dst_w / $src_w;
+				$ratio = $hratio < $wratio ? $hratio : $wratio;
+				$dst_h = $src_h * $ratio;
+				$dst_w = $src_w * $ratio;
+				$isHold = false;
+			}
+		}
+		$objImagick->resizeImage($dst_w, $dst_h, \Imagick::FILTER_UNDEFINED, 1, $isHold);
+
+		if (is_null($format)) {
+			$format = $objImagick->getImageFormat();
+		}
+		$objImagick->setImageFormat($format);
+
+		if (is_null($dst)) {// 返回图像内容
+			$data = $objImagick->getImageBlob();
+			$ret = ResultWrapper::success($data);
+		} else {
+			$tmpWriteResult = $objImagick->writeImage($dst);
+			if ($tmpWriteResult) {
+				$ret = ResultWrapper::success(array(
+					'w' => $objImagick->getImageWidth(),
+                	'h' => $objImagick->getImageHeight(),
+				));
+			} else {
+				$ret = ResultWrapper::fail("写入目标地址失败");
+			}
+		}
+
+		$objImagick->destroy();
+
+		return $ret;
+	}
+	
+	/**
+	 * @desc 图片格式转换
+	 * @param string $source 源图地址
+	 * @param string $destina 保存地址
+	 * @param sring $format 保存格式 (JPG、JPEG、PNG)
+	 * @return ResultWrapper
+	 */
+	static public function transformFormat($source, $destina = null, $format) {
+		if (empty($source)) {
+			return ResultWrapper::fail('请指定原图路径');
+		}
+		
+		if (empty($format)) {
+			return ResultWrapper::fail('请指定图片保存的格式');
+		}
+		
+		$objImagick = new \Imagick();
+		$objImagick->readimage($source);
+		$objImagick->setformat($format);
+		
+		if ($destina === null) {
+			$data = $objImagick->getImageBlob();
+			$result = ResultWrapper::success($data);
+		} else {
+			if ($objImagick->writeImage($destina)) {
+				$result = ResultWrapper::success();
+			}
+		}
+		
+		return $result;
+	}
+}

+ 210 - 0
KIF/.svn/text-base/Route.class.php.svn-base

@@ -0,0 +1,210 @@
+<?php
+namespace KIF;
+
+use KIF\Core\Request;
+use KIF\Core\Config;
+use KIF\String\String;
+use KIF\Math\Math;
+
+/**
+ * 
+ * 路由类
+ * @author gaoxiaogang
+ *
+ */
+class Route {
+	
+	/**
+	 * 
+	 * Enter description here ...
+	 * @var array 格式:array(
+	 *     $route_file => array(
+	 *     	   'instance'  => (Route),
+	 *         'rules'	=> array(
+	 *             (string) $rewrite_pattern	=> (array) $rule,
+	 *             ... ,
+	 *         ),
+	 *         'reverse_rules' => array(
+	 *             (string)$fingerprint => (string)$rewrite_pattern,
+	 *             ... ,
+	 *         ),
+	 *     ),
+	 * );
+	 */
+	static private $structs;
+	
+	protected $struct;
+	
+	# 禁止被外部new
+	private function __construct($route_file) {
+		if (!file_exists($route_file) || !is_file($route_file)) {
+			$route_rules = array();
+		} else {
+			$route_rules = include($route_file);
+		}
+		$rules = array();
+		$reverse_rules = array();
+		if (!$route_rules || !is_array($route_rules)) {
+			self::$structs[$route_file]['rules'] = $rules;
+			self::$structs[$route_file]['reverse_rules'] = $reverse_rules;
+			return;
+		}
+		
+		foreach ($route_rules as $rewrite_pattern	=> $rule) {
+			if (!isset($rule['a']) || !$rule['a']) {
+				$rule['a'] = 'default';
+			}
+			$fingerprint = $this->generateFingerprint($rule);
+			
+			$rules[$rewrite_pattern] = $rule;
+			$reverse_rules[$fingerprint] = $rewrite_pattern;
+		}
+		self::$structs[$route_file]['rules'] = $rules;
+		self::$structs[$route_file]['reverse_rules'] = $reverse_rules;
+	}
+	
+	/**
+	 * 
+	 * 生成指纹,方便反向rewrite时高效查找。
+	 * @param array $args
+	 * @return string
+	 */
+	private function generateFingerprint($args) {
+		if (!isset($args['a'])) {
+			$args['a'] = 'default';
+		}
+		$arg_eles = array_combine(array_keys($args), array_fill(0, count($args), null));
+		if (isset($args['a']))
+			$arg_eles['a'] = $args['a'];
+		if (isset($args['c']))
+			$arg_eles['c'] = $args['c'];
+		ksort($arg_eles);
+		return Math::md5_16(print_r($arg_eles, true));
+	}
+	
+	/**
+	 * 
+	 * @return KIF\Route
+	 */
+	static public function getInstance() {
+		$route_file = Config::getInstance()->get('route_file');
+		$route_file = realpath($route_file);
+		if (!isset(self::$structs[$route_file])) {
+			self::$structs[$route_file]['instance'] = new self($route_file);
+		}
+		self::$structs[$route_file]['instance']->struct = & self::$structs[$route_file];
+		return self::$structs[$route_file]['instance'];
+	}
+	
+	/**
+	 * 
+	 * 实现细节:
+	 *     根据当前app的路由配置,查找到指定条目,并将当前path里携带的参数存放进 $_GET和$_REQUEST变量里
+	 * 对当前请求路由
+	 * 
+	 */
+	public function rewrite() {
+		$path = Request::getInstance()->path();
+		$rules = $this->struct['rules'];
+		foreach ($rules as $pattern => $rule) {
+			$pattern = "#^{$pattern}$#";
+			if (preg_match($pattern, $path, $matches)) {
+				foreach ($rule as $param	=> $paramVal) {
+					if ($paramVal{0} != '$') {
+						$_GET[$param] = $paramVal;
+						$_REQUEST[$param] = $paramVal;
+					} else {
+						$match_pos = substr($paramVal, 1);
+						if (Verify::naturalNumber($match_pos)) {
+							$_GET[$param] = $matches[$match_pos];
+							$_REQUEST[$param] = $matches[$match_pos];
+						}
+					}
+					
+				}
+				break;
+			}
+		}
+	}
+	
+	/**
+	 * 
+	 * 获取经过路由后得到的请求参数。目前该方法被 Request::pageUrlTpl使用
+	 * @return array 如果没有对应的路由,返回空数组
+	 */
+	public function getsRouteParams() {
+		$route_params = array();
+		$path = Request::getInstance()->path();
+		$rules = $this->struct['rules'];
+		foreach ($rules as $pattern => $rule) {
+			$pattern = "#^{$pattern}$#";
+			if (preg_match($pattern, $path, $matches)) {
+				foreach ($rule as $param	=> $paramVal) {
+					if ($paramVal{0} != '$') {
+						$route_params[$param] = $paramVal;
+					} else {
+						$match_pos = substr($paramVal, 1);
+						if (Verify::naturalNumber($match_pos)) {
+							$route_params[$param] = $matches[$match_pos];
+						}
+					}
+				}
+				break;
+			}
+		}
+		
+		return $route_params;
+	}
+	
+	/**
+	 * 
+	 * 反向rewrite。通过 $args 给定的参数,逆向rewrite规则,获取rewrite的url
+	 * @param array $args 给定的参数值
+	 * @return String
+	 */
+	public function reverse(array $args) {
+		if (!$args) {
+			return false;
+		}
+		if (!isset($args['a'])) {
+			$args['a'] = 'default';
+		}
+		$arg_fingerprint = $this->generateFingerprint($args);
+		if (isset($this->struct['reverse_rules'][$arg_fingerprint])) {
+			$rewrite_pattern = $this->struct['reverse_rules'][$arg_fingerprint];
+			if (strpos($rewrite_pattern, '(') === false) {
+				return $rewrite_pattern;
+			}
+			$rule = $this->struct['rules'][$rewrite_pattern];
+			
+			$rule = array_filter($rule, function ($tmpV) {
+				if ($tmpV{0} != '$') {
+					return false;
+				}
+				return $tmpV;
+			});
+			
+			$match_times = 0;
+			$url = preg_replace_callback('#\([^)]+\)#', function ($matches) use (& $match_times, $rule, $args) {
+				$match_times++;
+				$key = array_search("\${$match_times}", $rule);
+				if (!$key) {
+					return false;
+				}
+				if (!isset($args[$key])) {
+					return false;
+				}
+				
+				return $args[$key];
+			}, $rewrite_pattern);
+			
+			if (is_null($url)) {//说明发生错误了。
+				return String::jointUrl('', $args);
+			}
+			
+			return $url;
+		}
+		
+		return String::jointUrl('', $args);
+	}
+}

+ 19 - 0
KIF/.svn/text-base/TODO.txt.svn-base

@@ -0,0 +1,19 @@
+待解决问题:
+1、Controller(OK)
+	a.1:web端访问->做router->解析请求参数
+	a.2:cli端访问->解析命令行参数
+	b:调用控制器及对应action
+	
+	
+2、不同APP间 in-process calls 问题(APP间PHP进程内调用,跨进程调用,通过REST解决)。
+	a、只调用其他APP的FRONT层,即业务逻辑层
+	b、配置(数据库、memcache)等各APP自满足
+	
+3、PHP闭包的应用 http://blog.zol.com.cn/1722/article_1721359.html
+
+4、数据访问层的缓存(OK)
+http://www.cnblogs.com/RicCC/archive/2009/09/28/mysql-2.html
+
+5、是否要重构Request,以便支持 subRequest的概念。
+
+6、前端类叫什么?Front 还是 modules (OK)  先不考虑处理了。

+ 159 - 0
KIF/.svn/text-base/Upload.class.php.svn-base

@@ -0,0 +1,159 @@
+<?php
+namespace KIF;
+
+/**
+ * @name 单文件上传类
+ * @desc 本控件主要对文件上传过程的监控,需要时可自动创建目标目录的上层目录。文件保存的路径和命名规则不在控件内设置。
+ * @author lishuming@kimiss.com
+ * 使用方法
+ * $objUpload = new \KIF\Upload($custom_config);
+ * $objUpload->execute();
+ * 
+ * @param array 自定义配置参数
+ * $custom_config = array(
+ * 		'save_path'	=> '/tmp/dir/', (必填)文件保存路径
+ * 		'save_filename'	=> 'name', (选填)保存文件名。不需要添加文件后缀。留空时使用原名
+ * 		'file_type'	=> '', (必填)上传文件类型
+ * 		'field'	=> 'myFile', (必填)客户端文件上传控件名,input 中的表单名
+ * 		...
+ * );
+ */
+
+use KIF\Data\ResultWrapper;
+
+class Upload {
+	
+	/**
+	 * 上传文件控件配置
+	 * @var array
+	 */
+	private $config = array();
+	
+	/**
+	 * 上传文件控件默认配置
+	 * @var array
+	 */
+	private $default_config = array(
+		'save_path'		=> '',		//文件保存目录路径。格式:/tmp/dir/
+		'save_filename'	=> null,		//保存文件名。默认为空时使用原名
+		'file_type'		=> '', //上传文件的类型。可选项有:image,flash,media,file
+		'allow_ext_arr'	=> array(	//定义允许上传文件的扩展名
+			'image'		=> array('gif', 'jpg', 'jpeg', 'png', 'bmp'),
+			'flash'		=> array('swf', 'flv'),
+			'media'		=> array('swf', 'flv', 'mp3', 'wav', 'wma', 'wmv', 'mid', 'avi', 'mpg', 'asf', 'rm', 'rmvb'),
+			'file'		=> array('doc', 'docx', 'xls', 'xlsx', 'ppt', 'htm', 'html', 'txt', 'zip', 'rar', 'gz', 'bz2'),
+		),
+		'max_size'		=> 1000000,	//允许上传最大文件的大小 (单位字节)
+		'field'			=> '',		//客户端文件上传控件名,input 中的表单名
+		'dchmod'		=> 0777,	//新创建的目录权限设置
+		'fchmod'		=> 0777,	//新创建的文件权限设置
+	);
+	
+	public function __construct(/*自定义配置*/$custom_config = array()) {
+		if (!empty($custom_config)) {
+			$this->config = array_merge($this->default_config, $custom_config);
+		} else {
+			$this->config = $this->default_config;
+		}
+	}
+	
+	public function execute() {
+		if (!$this->config['save_path']) {
+			return ResultWrapper::fail('请设置文件保存路径。');
+		}
+		$save_path = rtrim($this->config['save_path'], DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
+		
+		if (!$this->config['file_type']) {
+			return ResultWrapper::fail('请设置上传文件类型。');
+		}
+		$file_type = $this->config['file_type'];
+		
+		if (!$this->config['field']) {
+			return ResultWrapper::fail('请设置客户端文件上传控件名。');
+		}
+		$field = $this->config['field'];
+		
+		$ext_arr = $this->config['allow_ext_arr'];
+		
+		$max_size = $this->config['max_size'];
+		
+		if (empty($_FILES)) {
+			return ResultWrapper::fail('没有需要上传的文件。');
+		}
+		
+		#保存文件名
+		if (!$this->config['save_filename']) {
+			$save_filename = $_FILES[$field]['name'];
+		}
+		$save_filename = $this->config['save_filename'];
+		
+		#原文件名
+		$file_name = $_FILES[$field]['name'];
+		
+		#检查文件名
+		if (!$file_name) {
+			return ResultWrapper::fail('请选择上传文件。');
+		}
+		
+		#服务器上临时文件名
+		$tmp_name = $_FILES[$field]['tmp_name'];
+		
+		#文件大小
+		$file_size = $_FILES[$field]['size'];
+		
+		#检查是否已上传
+		if (@is_uploaded_file($tmp_name) === false) {
+			return ResultWrapper::fail("临时文件可能不是上传文件。");
+		}
+		
+		#检查文件大小
+		if ($file_size > $max_size) {
+			return ResultWrapper::fail("上传文件大小超过限制。");
+		}
+		
+		#获得文件扩展名
+		$file_ext = $this->getExtension($file_name);
+		#检查扩展名
+		if (in_array($file_ext, $ext_arr[$file_type]) === false) {
+			return ResultWrapper::fail("上传文件扩展名是不允许的扩展名。");
+		}
+		
+		#检查目录
+		if (@is_dir($save_path) === false) {
+			#创建目录
+			if (!mkdir($save_path, intval($this->config['dchmod']), true)) {
+				return ResultWrapper::fail("创建目录失败:{$save_path}");
+			}
+		}
+		
+		#检查目录写权限
+		if (@is_writable($save_path) === false) {
+			return ResultWrapper::fail("上传目录没有写权限。");
+		}
+		
+		#移动文件
+		$file_path = $save_path . $save_filename . '.' . $file_ext;
+		if (move_uploaded_file($tmp_name, $file_path) === false) {
+			return ResultWrapper::fail("上传文件失败。");
+		}
+		
+		@chmod($file_path, intval($this->config['fchmod']));
+		
+		return ResultWrapper::success(array(
+			'file_path'	=> $file_path,
+		));
+	}
+	
+	/**
+	 * 获取文件扩展名
+	 * @param string $filename
+	 * @return string
+	 */
+	static public function getExtension($filename) {
+		$temp_arr = explode(".", $filename);
+		$file_ext = array_pop($temp_arr);
+		$file_ext = trim($file_ext);
+		return strtolower($file_ext);
+	}
+	
+}

+ 9 - 0
KIF/.svn/text-base/Url.class.php.svn-base

@@ -0,0 +1,9 @@
+<?php
+namespace KIF;
+/**
+ * Url 相关类
+ */
+
+class Url {
+	
+}

+ 97 - 0
KIF/.svn/text-base/Verify.class.php.svn-base

@@ -0,0 +1,97 @@
+<?php
+namespace KIF;
+/**
+ * 封装了一些验证数据的方法
+ * @author gaoxiaogang@gmail.com
+ *
+ */
+class Verify {
+	/**
+	 * 验证 $email 是否有效
+	 *
+	 * @param string $email
+	 * @return Boolean
+	 */
+    static public function email($email) {
+	    return (boolean) filter_var($email, FILTER_VALIDATE_EMAIL);
+    }
+
+	/**
+	 * 验证是否是整数。
+	 * 1、支持正、负整数
+	 * 2、支持字数串表示法。如:'-200'、'125'。
+	 * 原由:php的is_int方法只能判断数值型变量,纯数字的字符串认为不是整数。但实际应用时,纯数字的字符串需要被当成整数来处理。
+	 * @param mixed $str
+	 * @return boolean
+	 */
+	static function int($str) {
+	    if (!is_scalar($str)) return false;
+	    # 修正bug。否则当$str === true时,该函数返回true:问题出在preg_match函数。
+	    if (is_bool($str)) {
+	        return false;
+	    }
+	    return (boolean) preg_match('#^\-?\d+$#', $str);
+	}
+
+	/**
+	 * 验证是否是正整数。即大于0的整数
+	 *
+	 * @param mixed $str
+	 * @return boolean
+	 */
+	static function unsignedInt($str) {
+        return (self::int($str) && $str > 0);
+	}
+
+	/**
+	 * 验证是否自然数。即大于等于0的整数
+	 *
+	 * @param mixed $str
+	 * @return boolean
+	 */
+	static function naturalNumber($str) {
+        return (self::int($str) && $str >= 0);
+	}
+
+	/**
+	 * 验证是否有效的金额。即不超出2位的浮点数
+	 * @param float $money
+	 * @return boolean
+	 */
+	static function money($money) {
+		return (boolean) preg_match('#^\d+(\.\d{1,2})?$#', $money);
+	}
+
+	/**
+	 * 验证 $ip 是否有效的ipv4地址
+	 * FILTER_FLAG_IPV4 - 要求值是合法的 IPv4 IP(比如 255.255.255.255)
+	 * @param string $ip
+	 * @return boolean
+	 */
+	static function ip($ip) {
+        return (boolean) filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4);
+	}
+
+	/**
+     * 验证 $ip 是否有效的 公网 ipv4地址
+     * FILTER_FLAG_IPV4 - 要求值是合法的 IPv4 IP(比如 255.255.255.255)
+     * FILTER_FLAG_NO_PRIV_RANGE - 要求值不是 RFC 指定的私域 IP (比如 192.168.0.90、10.0.0.90)
+     * @param string $ip
+     * @return boolean
+     */
+	static function publicIp($ip) {
+		return (boolean) filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_NO_PRIV_RANGE);
+	}
+
+	/**
+	 * 验证手机号是否合法
+	 * @param string $mobile
+	 * @return boolean
+	 */
+	static function mobile($mobile){
+		if(!preg_match('/^[1]{1}[3|5|8]{1}[0-9]{9}$/', $mobile)) {
+			return false;
+		}
+		return $mobile;
+	}
+}

+ 21 - 0
KIF/.svn/text-base/global_constants.php.svn-base

@@ -0,0 +1,21 @@
+<?php
+/**
+ * 框架级常量定义
+ * @author lishumingoo@gmail.com
+ */
+
+/*
+ * 权限系统配置文件路径 
+ */
+// if (!defined('PERMISSION_KIF_CONFIG_PATH')) {
+// 	define('PERMISSION_KIF_CONFIG_PATH', '/export/manager/vogue2/permission2/config/config.inc.php');
+// }
+
+/*
+ * Wechat配置文件路径
+ */
+// if (!defined('WECHAT_KIF_CONFIG_PATH')) {
+// 	define('WECHAT_KIF_CONFIG_PATH', '/export/manager/vogue2/cas/config/config.inc.php');
+// }
+
+

+ 70 - 0
KIF/.svn/text-base/init.php.svn-base

@@ -0,0 +1,70 @@
+<?php
+
+use KIF\String\String;
+
+if (PHP_VERSION < '5.3.0') {
+	echo 'PHP VERSION MUST > 5.3.0';
+	exit;
+}
+
+if (!defined('DS')) {
+	define('DS', DIRECTORY_SEPARATOR);
+}
+if (!defined('KIF_PATH')) {
+	define('KIF_PATH', dirname(__FILE__));
+}
+if (!defined('KIF_DEBUG_PASS')) {
+	define('KIF_DEBUG_PASS', '@kimiss');
+}
+if (!defined('KIF_ERROR_LEVEL'))	{
+	define('KIF_ERROR_LEVEL', E_ALL & ~E_NOTICE & ~E_DEPRECATED & ~E_WARNING);
+}
+/**
+ *
+ * 获取用于标识主库写入成功的cookie key
+ * # 定义常量,用于标识主数据库有写入操作。值为MASTER_DB_HAS_WRITE md5 16位后的值,避免明文被有心人猜测
+ * # 使用场景,MySQLite类执行写入语句成功时,会给该常量为key写一个cookie。当php页面读到该cookie时,会把查询的sql强制走主库
+ * # 使用场景2,当varnish获取到该key的cookies时,强制将用户请求pass到后端
+ * @return string
+ */
+if (!defined('KIF_MASTER_DB_HAS_WRITE_COOKIE_KEY')) {
+	define('KIF_MASTER_DB_HAS_WRITE_COOKIE_KEY', '17439a6561e7666b');
+}
+
+require_once KIF_PATH . DS . 'Core/Core.class.php';
+spl_autoload_register(array('KIF\Core\Core', 'autoload'));
+
+# 包含定义的框架级常量
+require_once KIF_PATH . DS . 'global_constants.php';
+
+# 默认将显示错误关闭
+ini_set('display_errors', false);
+
+# 设置默认时区
+date_default_timezone_set('PRC');
+
+#############################################
+if (get_magic_quotes_runtime()) {//表示系统自动将数据库读入或文件读入的数据添加转义
+    set_magic_quotes_runtime(0);//不做这步,由应用来管理
+}
+# 外部输入的数据,不需要转义。由应用自行处理即可!
+if (get_magic_quotes_gpc()) {//系统开启了进站数据转义
+    foreach (array('_GET', '_POST', '_FILES', '_COOKIE', '_REQUEST') as $_v) {
+        $$_v = String::stripslashes($$_v);
+    }   
+}
+#############################################
+
+if (isset($_REQUEST['kif_debug']) && $_REQUEST['kif_debug'] == KIF_DEBUG_PASS) {
+	# Debug模式将错误打开
+	ini_set('display_errors', true);
+	# 设置错误级别
+	error_reporting(KIF_ERROR_LEVEL);
+	//开启ob函数
+	ob_start();
+	
+	KIF\Debug\Debug::start();
+	register_shutdown_function(array('KIF\Debug\Debug', 'show'));
+}
+
+

+ 5702 - 0
KIF/.svn/text-base/phpQuery.php.svn-base

@@ -0,0 +1,5702 @@
+<?php
+/**
+ * phpQuery is a server-side, chainable, CSS3 selector driven
+ * Document Object Model (DOM) API based on jQuery JavaScript Library.
+ *
+ * @version 0.9.5
+ * @link http://code.google.com/p/phpquery/
+ * @link http://phpquery-library.blogspot.com/
+ * @link http://jquery.com/
+ * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com>
+ * @license http://www.opensource.org/licenses/mit-license.php MIT License
+ * @package phpQuery
+ */
+
+// class names for instanceof
+// TODO move them as class constants into phpQuery
+define('DOMDOCUMENT', 'DOMDocument');
+define('DOMELEMENT', 'DOMElement');
+define('DOMNODELIST', 'DOMNodeList');
+define('DOMNODE', 'DOMNode');
+
+/**
+ * DOMEvent class.
+ *
+ * Based on
+ * @link http://developer.mozilla.org/En/DOM:event
+ * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com>
+ * @package phpQuery
+ * @todo implement ArrayAccess ?
+ */
+class DOMEvent {
+	/**
+	 * Returns a boolean indicating whether the event bubbles up through the DOM or not.
+	 *
+	 * @var unknown_type
+	 */
+	public $bubbles = true;
+	/**
+	 * Returns a boolean indicating whether the event is cancelable.
+	 *
+	 * @var unknown_type
+	 */
+	public $cancelable = true;
+	/**
+	 * Returns a reference to the currently registered target for the event.
+	 *
+	 * @var unknown_type
+	 */
+	public $currentTarget;
+	/**
+	 * Returns detail about the event, depending on the type of event.
+	 *
+	 * @var unknown_type
+	 * @link http://developer.mozilla.org/en/DOM/event.detail
+	 */
+	public $detail;	// ???
+	/**
+	 * Used to indicate which phase of the event flow is currently being evaluated.
+	 *
+	 * NOT IMPLEMENTED
+	 *
+	 * @var unknown_type
+	 * @link http://developer.mozilla.org/en/DOM/event.eventPhase
+	 */
+	public $eventPhase;	// ???
+	/**
+	 * The explicit original target of the event (Mozilla-specific).
+	 *
+	 * NOT IMPLEMENTED
+	 *
+	 * @var unknown_type
+	 */
+	public $explicitOriginalTarget; // moz only
+	/**
+	 * The original target of the event, before any retargetings (Mozilla-specific).
+	 *
+	 * NOT IMPLEMENTED
+	 *
+	 * @var unknown_type
+	 */
+	public $originalTarget;	// moz only
+	/**
+	 * Identifies a secondary target for the event.
+	 *
+	 * @var unknown_type
+	 */
+	public $relatedTarget;
+	/**
+	 * Returns a reference to the target to which the event was originally dispatched.
+	 *
+	 * @var unknown_type
+	 */
+	public $target;
+	/**
+	 * Returns the time that the event was created.
+	 *
+	 * @var unknown_type
+	 */
+	public $timeStamp;
+	/**
+	 * Returns the name of the event (case-insensitive).
+	 */
+	public $type;
+	public $runDefault = true;
+	public $data = null;
+	public function __construct($data) {
+		foreach($data as $k => $v) {
+			$this->$k = $v;
+		}
+		if (! $this->timeStamp)
+			$this->timeStamp = time();
+	}
+	/**
+	 * Cancels the event (if it is cancelable).
+	 *
+	 */
+	public function preventDefault() {
+		$this->runDefault = false;
+	}
+	/**
+	 * Stops the propagation of events further along in the DOM.
+	 *
+	 */
+	public function stopPropagation() {
+		$this->bubbles = false;
+	}
+}
+
+
+/**
+ * DOMDocumentWrapper class simplifies work with DOMDocument.
+ *
+ * Know bug:
+ * - in XHTML fragments, <br /> changes to <br clear="none" />
+ *
+ * @todo check XML catalogs compatibility
+ * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com>
+ * @package phpQuery
+ */
+class DOMDocumentWrapper {
+	/**
+	 * @var DOMDocument
+	 */
+	public $document;
+	public $id;
+	/**
+	 * @todo Rewrite as method and quess if null.
+	 * @var unknown_type
+	 */
+	public $contentType = '';
+	public $xpath;
+	public $uuid = 0;
+	public $data = array();
+	public $dataNodes = array();
+	public $events = array();
+	public $eventsNodes = array();
+	public $eventsGlobal = array();
+	/**
+	 * @TODO iframes support http://code.google.com/p/phpquery/issues/detail?id=28
+	 * @var unknown_type
+	 */
+	public $frames = array();
+	/**
+	 * Document root, by default equals to document itself.
+	 * Used by documentFragments.
+	 *
+	 * @var DOMNode
+	 */
+	public $root;
+	public $isDocumentFragment;
+	public $isXML = false;
+	public $isXHTML = false;
+	public $isHTML = false;
+	public $charset;
+	public function __construct($markup = null, $contentType = null, $newDocumentID = null) {
+		if (isset($markup))
+			$this->load($markup, $contentType, $newDocumentID);
+		$this->id = $newDocumentID
+			? $newDocumentID
+			: md5(microtime());
+	}
+	public function load($markup, $contentType = null, $newDocumentID = null) {
+//		phpQuery::$documents[$id] = $this;
+		$this->contentType = strtolower($contentType);
+		if ($markup instanceof DOMDOCUMENT) {
+			$this->document = $markup;
+			$this->root = $this->document;
+			$this->charset = $this->document->encoding;
+			// TODO isDocumentFragment
+		} else {
+			$loaded = $this->loadMarkup($markup);
+		}
+		if ($loaded) {
+//			$this->document->formatOutput = true;
+			$this->document->preserveWhiteSpace = true;
+			$this->xpath = new DOMXPath($this->document);
+			$this->afterMarkupLoad();
+			return true;
+			// remember last loaded document
+//			return phpQuery::selectDocument($id);
+		}
+		return false;
+	}
+	protected function afterMarkupLoad() {
+		if ($this->isXHTML) {
+			$this->xpath->registerNamespace("html", "http://www.w3.org/1999/xhtml");
+		}
+	}
+	protected function loadMarkup($markup) {
+		$loaded = false;
+		if ($this->contentType) {
+			self::debug("Load markup for content type {$this->contentType}");
+			// content determined by contentType
+			list($contentType, $charset) = $this->contentTypeToArray($this->contentType);
+			switch($contentType) {
+				case 'text/html':
+					phpQuery::debug("Loading HTML, content type '{$this->contentType}'");
+					$loaded = $this->loadMarkupHTML($markup, $charset);
+				break;
+				case 'text/xml':
+				case 'application/xhtml+xml':
+					phpQuery::debug("Loading XML, content type '{$this->contentType}'");
+					$loaded = $this->loadMarkupXML($markup, $charset);
+				break;
+				default:
+					// for feeds or anything that sometimes doesn't use text/xml
+					if (strpos('xml', $this->contentType) !== false) {
+						phpQuery::debug("Loading XML, content type '{$this->contentType}'");
+						$loaded = $this->loadMarkupXML($markup, $charset);
+					} else
+						phpQuery::debug("Could not determine document type from content type '{$this->contentType}'");
+			}
+		} else {
+			// content type autodetection
+			if ($this->isXML($markup)) {
+				phpQuery::debug("Loading XML, isXML() == true");
+				$loaded = $this->loadMarkupXML($markup);
+				if (! $loaded && $this->isXHTML) {
+					phpQuery::debug('Loading as XML failed, trying to load as HTML, isXHTML == true');
+					$loaded = $this->loadMarkupHTML($markup);
+				}
+			} else {
+				phpQuery::debug("Loading HTML, isXML() == false");
+				$loaded = $this->loadMarkupHTML($markup);
+			}
+		}
+		return $loaded;
+	}
+	protected function loadMarkupReset() {
+		$this->isXML = $this->isXHTML = $this->isHTML = false;
+	}
+	protected function documentCreate($charset, $version = '1.0') {
+		if (! $version)
+			$version = '1.0';
+		$this->document = new DOMDocument($version, $charset);
+		$this->charset = $this->document->encoding;
+//		$this->document->encoding = $charset;
+		$this->document->formatOutput = true;
+		$this->document->preserveWhiteSpace = true;
+	}
+	protected function loadMarkupHTML($markup, $requestedCharset = null) {
+		if (phpQuery::$debug)
+			phpQuery::debug('Full markup load (HTML): '.substr($markup, 0, 250));
+		$this->loadMarkupReset();
+		$this->isHTML = true;
+		if (!isset($this->isDocumentFragment))
+			$this->isDocumentFragment = self::isDocumentFragmentHTML($markup);
+		$charset = null;
+		$documentCharset = $this->charsetFromHTML($markup);
+		$addDocumentCharset = false;
+		if ($documentCharset) {
+			$charset = $documentCharset;
+			$markup = $this->charsetFixHTML($markup);
+		} else if ($requestedCharset) {
+			$charset = $requestedCharset;
+		}
+		if (! $charset)
+			$charset = phpQuery::$defaultCharset;
+		// HTTP 1.1 says that the default charset is ISO-8859-1
+		// @see http://www.w3.org/International/O-HTTP-charset
+		if (! $documentCharset) {
+			$documentCharset = 'ISO-8859-1';
+			$addDocumentCharset = true;	
+		}
+		// Should be careful here, still need 'magic encoding detection' since lots of pages have other 'default encoding'
+		// Worse, some pages can have mixed encodings... we'll try not to worry about that
+		$requestedCharset = strtoupper($requestedCharset);
+		$documentCharset = strtoupper($documentCharset);
+		phpQuery::debug("DOC: $documentCharset REQ: $requestedCharset");
+		if ($requestedCharset && $documentCharset && $requestedCharset !== $documentCharset) {
+			phpQuery::debug("CHARSET CONVERT");
+			// Document Encoding Conversion
+			// http://code.google.com/p/phpquery/issues/detail?id=86
+			if (function_exists('mb_detect_encoding')) {
+				$possibleCharsets = array($documentCharset, $requestedCharset, 'AUTO');
+				$docEncoding = mb_detect_encoding($markup, implode(', ', $possibleCharsets));
+				if (! $docEncoding)
+					$docEncoding = $documentCharset; // ok trust the document
+				phpQuery::debug("DETECTED '$docEncoding'");
+				// Detected does not match what document says...
+				if ($docEncoding !== $documentCharset) {
+					// Tricky..
+				}
+				if ($docEncoding !== $requestedCharset) {
+					phpQuery::debug("CONVERT $docEncoding => $requestedCharset");
+					$markup = mb_convert_encoding($markup, $requestedCharset, $docEncoding);
+					$markup = $this->charsetAppendToHTML($markup, $requestedCharset);
+					$charset = $requestedCharset;
+				}
+			} else {
+				phpQuery::debug("TODO: charset conversion without mbstring...");
+			}
+		}
+		$return = false;
+		if ($this->isDocumentFragment) {
+			phpQuery::debug("Full markup load (HTML), DocumentFragment detected, using charset '$charset'");
+			$return = $this->documentFragmentLoadMarkup($this, $charset, $markup);
+		} else {
+			if ($addDocumentCharset) {
+				phpQuery::debug("Full markup load (HTML), appending charset: '$charset'");
+				$markup = $this->charsetAppendToHTML($markup, $charset);
+			}
+			phpQuery::debug("Full markup load (HTML), documentCreate('$charset')");
+			$this->documentCreate($charset);
+			$return = phpQuery::$debug === 2
+				? $this->document->loadHTML($markup)
+				: @$this->document->loadHTML($markup);
+			if ($return)
+				$this->root = $this->document;
+		}
+		if ($return && ! $this->contentType)
+			$this->contentType = 'text/html';
+		return $return;
+	}
+	protected function loadMarkupXML($markup, $requestedCharset = null) {
+		if (phpQuery::$debug)
+			phpQuery::debug('Full markup load (XML): '.substr($markup, 0, 250));
+		$this->loadMarkupReset();
+		$this->isXML = true;
+		// check agains XHTML in contentType or markup
+		$isContentTypeXHTML = $this->isXHTML();
+		$isMarkupXHTML = $this->isXHTML($markup);
+		if ($isContentTypeXHTML || $isMarkupXHTML) {
+			self::debug('Full markup load (XML), XHTML detected');
+			$this->isXHTML = true;
+		}
+		// determine document fragment
+		if (! isset($this->isDocumentFragment))
+			$this->isDocumentFragment = $this->isXHTML
+				? self::isDocumentFragmentXHTML($markup)
+				: self::isDocumentFragmentXML($markup);
+		// this charset will be used
+		$charset = null;
+		// charset from XML declaration @var string
+		$documentCharset = $this->charsetFromXML($markup);
+		if (! $documentCharset) {
+			if ($this->isXHTML) {
+				// this is XHTML, try to get charset from content-type meta header
+				$documentCharset = $this->charsetFromHTML($markup);
+				if ($documentCharset) {
+					phpQuery::debug("Full markup load (XML), appending XHTML charset '$documentCharset'");
+					$this->charsetAppendToXML($markup, $documentCharset);
+					$charset = $documentCharset;
+				}
+			}
+			if (! $documentCharset) {
+				// if still no document charset...
+				$charset = $requestedCharset;
+			}
+		} else if ($requestedCharset) {
+			$charset = $requestedCharset;
+		}
+		if (! $charset) {
+			$charset = phpQuery::$defaultCharset;
+		}
+		if ($requestedCharset && $documentCharset && $requestedCharset != $documentCharset) {
+			// TODO place for charset conversion
+//			$charset = $requestedCharset;
+		}
+		$return = false;
+		if ($this->isDocumentFragment) {
+			phpQuery::debug("Full markup load (XML), DocumentFragment detected, using charset '$charset'");
+			$return = $this->documentFragmentLoadMarkup($this, $charset, $markup);
+		} else {
+			// FIXME ???
+			if ($isContentTypeXHTML && ! $isMarkupXHTML)
+			if (! $documentCharset) {
+				phpQuery::debug("Full markup load (XML), appending charset '$charset'");
+				$markup = $this->charsetAppendToXML($markup, $charset);
+			}
+			// see http://pl2.php.net/manual/en/book.dom.php#78929
+			// LIBXML_DTDLOAD (>= PHP 5.1)
+			// does XML ctalogues works with LIBXML_NONET
+	//		$this->document->resolveExternals = true;
+			// TODO test LIBXML_COMPACT for performance improvement
+			// create document
+			$this->documentCreate($charset);
+			if (phpversion() < 5.1) {
+				$this->document->resolveExternals = true;
+				$return = phpQuery::$debug === 2
+					? $this->document->loadXML($markup)
+					: @$this->document->loadXML($markup);
+			} else {
+				/** @link http://pl2.php.net/manual/en/libxml.constants.php */
+				$libxmlStatic = phpQuery::$debug === 2
+					? LIBXML_DTDLOAD|LIBXML_DTDATTR|LIBXML_NONET
+					: LIBXML_DTDLOAD|LIBXML_DTDATTR|LIBXML_NONET|LIBXML_NOWARNING|LIBXML_NOERROR;
+				$return = $this->document->loadXML($markup, $libxmlStatic);
+// 				if (! $return)
+// 					$return = $this->document->loadHTML($markup);
+			}
+			if ($return)
+				$this->root = $this->document;
+		}
+		if ($return) {
+			if (! $this->contentType) {
+				if ($this->isXHTML)
+					$this->contentType = 'application/xhtml+xml';
+				else
+					$this->contentType = 'text/xml';
+			}
+			return $return;
+		} else {
+			throw new Exception("Error loading XML markup");
+		}
+	}
+	protected function isXHTML($markup = null) {
+		if (! isset($markup)) {
+			return strpos($this->contentType, 'xhtml') !== false;
+		}
+		// XXX ok ?
+		return strpos($markup, "<!DOCTYPE html") !== false;
+//		return stripos($doctype, 'xhtml') !== false;
+//		$doctype = isset($dom->doctype) && is_object($dom->doctype)
+//			? $dom->doctype->publicId
+//			: self::$defaultDoctype;
+	}
+	protected function isXML($markup) {
+//		return strpos($markup, '<?xml') !== false && stripos($markup, 'xhtml') === false;
+		return strpos(substr($markup, 0, 100), '<'.'?xml') !== false;
+	}
+	protected function contentTypeToArray($contentType) {
+		$matches = explode(';', trim(strtolower($contentType)));
+		if (isset($matches[1])) {
+			$matches[1] = explode('=', $matches[1]);
+			// strip 'charset='
+			$matches[1] = isset($matches[1][1]) && trim($matches[1][1])
+				? $matches[1][1]
+				: $matches[1][0];
+		} else
+			$matches[1] = null;
+		return $matches;
+	}
+	/**
+	 *
+	 * @param $markup
+	 * @return array contentType, charset
+	 */
+	protected function contentTypeFromHTML($markup) {
+		$matches = array();
+		// find meta tag
+		preg_match('@<meta[^>]+http-equiv\\s*=\\s*(["|\'])Content-Type\\1([^>]+?)>@i',
+			$markup, $matches
+		);
+		if (! isset($matches[0]))
+			return array(null, null);
+		// get attr 'content'
+		preg_match('@content\\s*=\\s*(["|\'])(.+?)\\1@', $matches[0], $matches);
+		if (! isset($matches[0]))
+			return array(null, null);
+		return $this->contentTypeToArray($matches[2]);
+	}
+	protected function charsetFromHTML($markup) {
+		$contentType = $this->contentTypeFromHTML($markup);
+		return $contentType[1];
+	}
+	protected function charsetFromXML($markup) {
+		$matches;
+		// find declaration
+		preg_match('@<'.'?xml[^>]+encoding\\s*=\\s*(["|\'])(.*?)\\1@i',
+			$markup, $matches
+		);
+		return isset($matches[2])
+			? strtolower($matches[2])
+			: null;
+	}
+	/**
+	 * Repositions meta[type=charset] at the start of head. Bypasses DOMDocument bug.
+	 *
+	 * @link http://code.google.com/p/phpquery/issues/detail?id=80
+	 * @param $html
+	 */
+	protected function charsetFixHTML($markup) {
+		$matches = array();
+		// find meta tag
+		preg_match('@\s*<meta[^>]+http-equiv\\s*=\\s*(["|\'])Content-Type\\1([^>]+?)>@i',
+			$markup, $matches, PREG_OFFSET_CAPTURE
+		);
+		if (! isset($matches[0]))
+			return;
+		$metaContentType = $matches[0][0];
+		$markup = substr($markup, 0, $matches[0][1])
+			.substr($markup, $matches[0][1]+strlen($metaContentType));
+		$headStart = stripos($markup, '<head>');
+		$markup = substr($markup, 0, $headStart+6).$metaContentType
+			.substr($markup, $headStart+6);
+		return $markup;
+	}
+	protected function charsetAppendToHTML($html, $charset, $xhtml = false) {
+		// remove existing meta[type=content-type]
+		$html = preg_replace('@\s*<meta[^>]+http-equiv\\s*=\\s*(["|\'])Content-Type\\1([^>]+?)>@i', '', $html);
+		$meta = '<meta http-equiv="Content-Type" content="text/html;charset='
+			.$charset.'" '
+			.($xhtml ? '/' : '')
+			.'>';
+		if (strpos($html, '<head') === false) {
+			if (strpos($hltml, '<html') === false) {
+				return $meta.$html;
+			} else {
+				return preg_replace(
+					'@<html(.*?)(?(?<!\?)>)@s',
+					"<html\\1><head>{$meta}</head>",
+					$html
+				);
+			}
+		} else {
+			return preg_replace(
+				'@<head(.*?)(?(?<!\?)>)@s',
+				'<head\\1>'.$meta,
+				$html
+			);
+		}
+	}
+	protected function charsetAppendToXML($markup, $charset) {
+		$declaration = '<'.'?xml version="1.0" encoding="'.$charset.'"?'.'>';
+		return $declaration.$markup;
+	}
+	public static function isDocumentFragmentHTML($markup) {
+		return stripos($markup, '<html') === false && stripos($markup, '<!doctype') === false;
+	}
+	public static function isDocumentFragmentXML($markup) {
+		return stripos($markup, '<'.'?xml') === false;
+	}
+	public static function isDocumentFragmentXHTML($markup) {
+		return self::isDocumentFragmentHTML($markup);
+	}
+	public function importAttr($value) {
+		// TODO
+	}
+	/**
+	 *
+	 * @param $source
+	 * @param $target
+	 * @param $sourceCharset
+	 * @return array Array of imported nodes.
+	 */
+	public function import($source, $sourceCharset = null) {
+		// TODO charset conversions
+		$return = array();
+		if ($source instanceof DOMNODE && !($source instanceof DOMNODELIST))
+			$source = array($source);
+//		if (is_array($source)) {
+//			foreach($source as $node) {
+//				if (is_string($node)) {
+//					// string markup
+//					$fake = $this->documentFragmentCreate($node, $sourceCharset);
+//					if ($fake === false)
+//						throw new Exception("Error loading documentFragment markup");
+//					else
+//						$return = array_merge($return, 
+//							$this->import($fake->root->childNodes)
+//						);
+//				} else {
+//					$return[] = $this->document->importNode($node, true);
+//				}
+//			}
+//			return $return;
+//		} else {
+//			// string markup
+//			$fake = $this->documentFragmentCreate($source, $sourceCharset);
+//			if ($fake === false)
+//				throw new Exception("Error loading documentFragment markup");
+//			else
+//				return $this->import($fake->root->childNodes);
+//		}
+		if (is_array($source) || $source instanceof DOMNODELIST) {
+			// dom nodes
+			self::debug('Importing nodes to document');
+			foreach($source as $node)
+				$return[] = $this->document->importNode($node, true);
+		} else {
+			// string markup
+			$fake = $this->documentFragmentCreate($source, $sourceCharset);
+			if ($fake === false)
+				throw new Exception("Error loading documentFragment markup");
+			else
+				return $this->import($fake->root->childNodes);
+		}
+		return $return;
+	}
+	/**
+	 * Creates new document fragment.
+	 *
+	 * @param $source
+	 * @return DOMDocumentWrapper
+	 */
+	protected function documentFragmentCreate($source, $charset = null) {
+		$fake = new DOMDocumentWrapper();
+		$fake->contentType = $this->contentType;
+		$fake->isXML = $this->isXML;
+		$fake->isHTML = $this->isHTML;
+		$fake->isXHTML = $this->isXHTML;
+		$fake->root = $fake->document;
+		if (! $charset)
+			$charset = $this->charset;
+//	$fake->documentCreate($this->charset);
+		if ($source instanceof DOMNODE && !($source instanceof DOMNODELIST))
+			$source = array($source);
+		if (is_array($source) || $source instanceof DOMNODELIST) {
+			// dom nodes
+			// load fake document
+			if (! $this->documentFragmentLoadMarkup($fake, $charset))
+				return false;
+			$nodes = $fake->import($source);
+			foreach($nodes as $node)
+				$fake->root->appendChild($node);
+		} else {
+			// string markup
+			$this->documentFragmentLoadMarkup($fake, $charset, $source);
+		}
+		return $fake;
+	}
+	/**
+	 *
+	 * @param $document DOMDocumentWrapper
+	 * @param $markup
+	 * @return $document
+	 */
+	private function documentFragmentLoadMarkup($fragment, $charset, $markup = null) {
+		// TODO error handling
+		// TODO copy doctype
+		// tempolary turn off
+		$fragment->isDocumentFragment = false;
+		if ($fragment->isXML) {
+			if ($fragment->isXHTML) {
+				// add FAKE element to set default namespace
+				$fragment->loadMarkupXML('<?xml version="1.0" encoding="'.$charset.'"?>'
+					.'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" '
+					.'"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">'
+					.'<fake xmlns="http://www.w3.org/1999/xhtml">'.$markup.'</fake>');
+				$fragment->root = $fragment->document->firstChild->nextSibling;
+			} else {
+				$fragment->loadMarkupXML('<?xml version="1.0" encoding="'.$charset.'"?><fake>'.$markup.'</fake>');
+				$fragment->root = $fragment->document->firstChild;
+			}
+		} else {
+			$markup2 = phpQuery::$defaultDoctype.'<html><head><meta http-equiv="Content-Type" content="text/html;charset='
+				.$charset.'"></head>';
+			$noBody = strpos($markup, '<body') === false;
+			if ($noBody)
+				$markup2 .= '<body>';
+			$markup2 .= $markup;
+			if ($noBody)
+				$markup2 .= '</body>';
+			$markup2 .= '</html>';
+			$fragment->loadMarkupHTML($markup2);
+			// TODO resolv body tag merging issue
+			$fragment->root = $noBody
+				? $fragment->document->firstChild->nextSibling->firstChild->nextSibling
+				: $fragment->document->firstChild->nextSibling->firstChild->nextSibling;
+		}
+		if (! $fragment->root)
+			return false;
+		$fragment->isDocumentFragment = true;
+		return true;
+	}
+	protected function documentFragmentToMarkup($fragment) {
+		phpQuery::debug('documentFragmentToMarkup');
+		$tmp = $fragment->isDocumentFragment;
+		$fragment->isDocumentFragment = false;
+		$markup = $fragment->markup();
+		if ($fragment->isXML) {
+			$markup = substr($markup, 0, strrpos($markup, '</fake>'));
+			if ($fragment->isXHTML) {
+				$markup = substr($markup, strpos($markup, '<fake')+43);
+			} else {
+				$markup = substr($markup, strpos($markup, '<fake>')+6);
+			}
+		} else {
+				$markup = substr($markup, strpos($markup, '<body>')+6);
+				$markup = substr($markup, 0, strrpos($markup, '</body>'));
+		}
+		$fragment->isDocumentFragment = $tmp;
+		if (phpQuery::$debug)
+			phpQuery::debug('documentFragmentToMarkup: '.substr($markup, 0, 150));
+		return $markup;
+	}
+	/**
+	 * Return document markup, starting with optional $nodes as root.
+	 *
+	 * @param $nodes	DOMNode|DOMNodeList
+	 * @return string
+	 */
+	public function markup($nodes = null, $innerMarkup = false) {
+		if (isset($nodes) && count($nodes) == 1 && $nodes[0] instanceof DOMDOCUMENT)
+			$nodes = null;
+		if (isset($nodes)) {
+			$markup = '';
+			if (!is_array($nodes) && !($nodes instanceof DOMNODELIST) )
+				$nodes = array($nodes);
+			if ($this->isDocumentFragment && ! $innerMarkup)
+				foreach($nodes as $i => $node)
+					if ($node->isSameNode($this->root)) {
+					//	var_dump($node);
+						$nodes = array_slice($nodes, 0, $i)
+							+ phpQuery::DOMNodeListToArray($node->childNodes)
+							+ array_slice($nodes, $i+1);
+						}
+			if ($this->isXML && ! $innerMarkup) {
+				self::debug("Getting outerXML with charset '{$this->charset}'");
+				// we need outerXML, so we can benefit from
+				// $node param support in saveXML()
+				foreach($nodes as $node)
+					$markup .= $this->document->saveXML($node);
+			} else {
+				$loop = array();
+				if ($innerMarkup)
+					foreach($nodes as $node) {
+						if ($node->childNodes)
+							foreach($node->childNodes as $child)
+								$loop[] = $child;
+						else
+							$loop[] = $node;
+					}
+				else
+					$loop = $nodes;
+				self::debug("Getting markup, moving selected nodes (".count($loop).") to new DocumentFragment");
+				$fake = $this->documentFragmentCreate($loop);
+				$markup = $this->documentFragmentToMarkup($fake);
+			}
+			if ($this->isXHTML) {
+				self::debug("Fixing XHTML");
+				$markup = self::markupFixXHTML($markup);
+			}
+			self::debug("Markup: ".substr($markup, 0, 250));
+			return $markup;
+		} else {
+			if ($this->isDocumentFragment) {
+				// documentFragment, html only...
+				self::debug("Getting markup, DocumentFragment detected");
+//				return $this->markup(
+////					$this->document->getElementsByTagName('body')->item(0)
+//					$this->document->root, true
+//				);
+				$markup = $this->documentFragmentToMarkup($this);
+				// no need for markupFixXHTML, as it's done thought markup($nodes) method
+				return $markup;
+			} else {
+				self::debug("Getting markup (".($this->isXML?'XML':'HTML')."), final with charset '{$this->charset}'");
+				$markup = $this->isXML
+					? $this->document->saveXML()
+					: $this->document->saveHTML();
+				if ($this->isXHTML) {
+					self::debug("Fixing XHTML");
+					$markup = self::markupFixXHTML($markup);
+				}
+				self::debug("Markup: ".substr($markup, 0, 250));
+				return $markup;
+			}
+		}
+	}
+	protected static function markupFixXHTML($markup) {
+		$markup = self::expandEmptyTag('script', $markup);
+		$markup = self::expandEmptyTag('select', $markup);
+		$markup = self::expandEmptyTag('textarea', $markup);
+		return $markup;
+	}
+	public static function debug($text) {
+		phpQuery::debug($text);
+	}
+	/**
+	 * expandEmptyTag
+	 *
+	 * @param $tag
+	 * @param $xml
+	 * @return unknown_type
+	 * @author mjaque at ilkebenson dot com
+	 * @link http://php.net/manual/en/domdocument.savehtml.php#81256
+	 */
+	public static function expandEmptyTag($tag, $xml){
+        $indice = 0;
+        while ($indice< strlen($xml)){
+            $pos = strpos($xml, "<$tag ", $indice);
+            if ($pos){
+                $posCierre = strpos($xml, ">", $pos);
+                if ($xml[$posCierre-1] == "/"){
+                    $xml = substr_replace($xml, "></$tag>", $posCierre-1, 2);
+                }
+                $indice = $posCierre;
+            }
+            else break;
+        }
+        return $xml;
+	}
+}
+
+/**
+ * Event handling class.
+ *
+ * @author Tobiasz Cudnik
+ * @package phpQuery
+ * @static
+ */
+abstract class phpQueryEvents {
+	/**
+	 * Trigger a type of event on every matched element.
+	 *
+	 * @param DOMNode|phpQueryObject|string $document
+	 * @param unknown_type $type
+	 * @param unknown_type $data
+	 *
+	 * @TODO exclusive events (with !)
+	 * @TODO global events (test)
+	 * @TODO support more than event in $type (space-separated)
+	 */
+	public static function trigger($document, $type, $data = array(), $node = null) {
+		// trigger: function(type, data, elem, donative, extra) {
+		$documentID = phpQuery::getDocumentID($document);
+		$namespace = null;
+		if (strpos($type, '.') !== false)
+			list($name, $namespace) = explode('.', $type);
+		else
+			$name = $type;
+		if (! $node) {
+			if (self::issetGlobal($documentID, $type)) {
+				$pq = phpQuery::getDocument($documentID);
+				// TODO check add($pq->document)
+				$pq->find('*')->add($pq->document)
+					->trigger($type, $data);
+			}
+		} else {
+			if (isset($data[0]) && $data[0] instanceof DOMEvent) {
+				$event = $data[0];
+				$event->relatedTarget = $event->target;
+				$event->target = $node;
+				$data = array_slice($data, 1);
+			} else {
+				$event = new DOMEvent(array(
+					'type' => $type,
+					'target' => $node,
+					'timeStamp' => time(),
+				));
+			}
+			$i = 0;
+			while($node) {
+				// TODO whois
+				phpQuery::debug("Triggering ".($i?"bubbled ":'')."event '{$type}' on "
+					."node \n");//.phpQueryObject::whois($node)."\n");
+				$event->currentTarget = $node;
+				$eventNode = self::getNode($documentID, $node);
+				if (isset($eventNode->eventHandlers)) {
+					foreach($eventNode->eventHandlers as $eventType => $handlers) {
+						$eventNamespace = null;
+						if (strpos($type, '.') !== false)
+							list($eventName, $eventNamespace) = explode('.', $eventType);
+						else
+							$eventName = $eventType;
+						if ($name != $eventName)
+							continue;
+						if ($namespace && $eventNamespace && $namespace != $eventNamespace)
+							continue;
+						foreach($handlers as $handler) {
+							phpQuery::debug("Calling event handler\n");
+							$event->data = $handler['data']
+								? $handler['data']
+								: null;
+							$params = array_merge(array($event), $data);
+							$return = phpQuery::callbackRun($handler['callback'], $params);
+							if ($return === false) {
+								$event->bubbles = false;
+							}
+						}
+					}
+				}
+				// to bubble or not to bubble...
+				if (! $event->bubbles)
+					break;
+				$node = $node->parentNode;
+				$i++;
+			}
+		}
+	}
+	/**
+	 * Binds a handler to one or more events (like click) for each matched element.
+	 * Can also bind custom events.
+	 *
+	 * @param DOMNode|phpQueryObject|string $document
+	 * @param unknown_type $type
+	 * @param unknown_type $data Optional
+	 * @param unknown_type $callback
+	 *
+	 * @TODO support '!' (exclusive) events
+	 * @TODO support more than event in $type (space-separated)
+	 * @TODO support binding to global events
+	 */
+	public static function add($document, $node, $type, $data, $callback = null) {
+		phpQuery::debug("Binding '$type' event");
+		$documentID = phpQuery::getDocumentID($document);
+//		if (is_null($callback) && is_callable($data)) {
+//			$callback = $data;
+//			$data = null;
+//		}
+		$eventNode = self::getNode($documentID, $node);
+		if (! $eventNode)
+			$eventNode = self::setNode($documentID, $node);
+		if (!isset($eventNode->eventHandlers[$type]))
+			$eventNode->eventHandlers[$type] = array();
+		$eventNode->eventHandlers[$type][] = array(
+			'callback' => $callback,
+			'data' => $data,
+		);
+	}
+	/**
+	 * Enter description here...
+	 *
+	 * @param DOMNode|phpQueryObject|string $document
+	 * @param unknown_type $type
+	 * @param unknown_type $callback
+	 *
+	 * @TODO namespace events
+	 * @TODO support more than event in $type (space-separated)
+	 */
+	public static function remove($document, $node, $type = null, $callback = null) {
+		$documentID = phpQuery::getDocumentID($document);
+		$eventNode = self::getNode($documentID, $node);
+		if (is_object($eventNode) && isset($eventNode->eventHandlers[$type])) {
+			if ($callback) {
+				foreach($eventNode->eventHandlers[$type] as $k => $handler)
+					if ($handler['callback'] == $callback)
+						unset($eventNode->eventHandlers[$type][$k]);
+			} else {
+				unset($eventNode->eventHandlers[$type]);
+			}
+		}
+	}
+	protected static function getNode($documentID, $node) {
+		foreach(phpQuery::$documents[$documentID]->eventsNodes as $eventNode) {
+			if ($node->isSameNode($eventNode))
+				return $eventNode;
+		}
+	}
+	protected static function setNode($documentID, $node) {
+		phpQuery::$documents[$documentID]->eventsNodes[] = $node;
+		return phpQuery::$documents[$documentID]->eventsNodes[
+			count(phpQuery::$documents[$documentID]->eventsNodes)-1
+		];
+	}
+	protected static function issetGlobal($documentID, $type) {
+		return isset(phpQuery::$documents[$documentID])
+			? in_array($type, phpQuery::$documents[$documentID]->eventsGlobal)
+			: false;
+	}
+}
+
+
+interface ICallbackNamed {
+	function hasName();
+	function getName();
+}
+/**
+ * Callback class introduces currying-like pattern.
+ * 
+ * Example:
+ * function foo($param1, $param2, $param3) {
+ *   var_dump($param1, $param2, $param3);
+ * }
+ * $fooCurried = new Callback('foo', 
+ *   'param1 is now statically set', 
+ *   new CallbackParam, new CallbackParam
+ * );
+ * phpQuery::callbackRun($fooCurried,
+ * 	array('param2 value', 'param3 value'
+ * );
+ * 
+ * Callback class is supported in all phpQuery methods which accepts callbacks. 
+ *
+ * @link http://code.google.com/p/phpquery/wiki/Callbacks#Param_Structures
+ * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com>
+ * 
+ * @TODO??? return fake forwarding function created via create_function
+ * @TODO honor paramStructure
+ */
+class Callback
+	implements ICallbackNamed {
+	public $callback = null;
+	public $params = null;
+	protected $name;
+	public function __construct($callback, $param1 = null, $param2 = null, 
+			$param3 = null) {
+		$params = func_get_args();
+		$params = array_slice($params, 1);
+		if ($callback instanceof Callback) {
+			// TODO implement recurention
+		} else {
+			$this->callback = $callback;
+			$this->params = $params;
+		}
+	}
+	public function getName() {
+		return 'Callback: '.$this->name;
+	}
+	public function hasName() {
+		return isset($this->name) && $this->name;
+	}
+	public function setName($name) {
+		$this->name = $name;
+		return $this;
+	}
+	// TODO test me
+//	public function addParams() {
+//		$params = func_get_args();
+//		return new Callback($this->callback, $this->params+$params);
+//	}
+}
+/**
+ * Shorthand for new Callback(create_function(...), ...);
+ * 
+ * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com>
+ */
+class CallbackBody extends Callback {
+	public function __construct($paramList, $code, $param1 = null, $param2 = null, 
+			$param3 = null) {
+		$params = func_get_args();
+		$params = array_slice($params, 2);
+		$this->callback = create_function($paramList, $code);
+		$this->params = $params;
+	}
+}
+/**
+ * Callback type which on execution returns reference passed during creation.
+ * 
+ * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com>
+ */
+class CallbackReturnReference extends Callback
+	implements ICallbackNamed {
+	protected $reference;
+	public function __construct(&$reference, $name = null){
+		$this->reference =& $reference;
+		$this->callback = array($this, 'callback');
+	}
+	public function callback() {
+		return $this->reference;
+	}
+	public function getName() {
+		return 'Callback: '.$this->name;
+	}
+	public function hasName() {
+		return isset($this->name) && $this->name;
+	}
+}
+/**
+ * Callback type which on execution returns value passed during creation.
+ * 
+ * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com>
+ */
+class CallbackReturnValue extends Callback
+	implements ICallbackNamed {
+	protected $value;
+	protected $name;
+	public function __construct($value, $name = null){
+		$this->value =& $value;
+		$this->name = $name;
+		$this->callback = array($this, 'callback');
+	}
+	public function callback() {
+		return $this->value;
+	}
+	public function __toString() {
+		return $this->getName();
+	}
+	public function getName() {
+		return 'Callback: '.$this->name;
+	}
+	public function hasName() {
+		return isset($this->name) && $this->name;
+	}
+}
+/**
+ * CallbackParameterToReference can be used when we don't really want a callback,
+ * only parameter passed to it. CallbackParameterToReference takes first 
+ * parameter's value and passes it to reference.
+ *
+ * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com>
+ */
+class CallbackParameterToReference extends Callback {
+	/**
+	 * @param $reference
+	 * @TODO implement $paramIndex; 
+	 * param index choose which callback param will be passed to reference
+	 */
+	public function __construct(&$reference){
+		$this->callback =& $reference;
+	}
+}
+//class CallbackReference extends Callback {
+//	/**
+//	 *
+//	 * @param $reference
+//	 * @param $paramIndex
+//	 * @todo implement $paramIndex; param index choose which callback param will be passed to reference
+//	 */
+//	public function __construct(&$reference, $name = null){
+//		$this->callback =& $reference;
+//	}
+//}
+class CallbackParam {}
+
+/**
+ * Class representing phpQuery objects.
+ *
+ * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com>
+ * @package phpQuery
+ * @method phpQueryObject clone() clone()
+ * @method phpQueryObject empty() empty()
+ * @method phpQueryObject next() next($selector = null)
+ * @method phpQueryObject prev() prev($selector = null)
+ * @property Int $length
+ */
+class phpQueryObject
+	implements Iterator, Countable, ArrayAccess {
+	public $documentID = null;
+	/**
+	 * DOMDocument class.
+	 *
+	 * @var DOMDocument
+	 */
+	public $document = null;
+	public $charset = null;
+	/**
+	 *
+	 * @var DOMDocumentWrapper
+	 */
+	public $documentWrapper = null;
+	/**
+	 * XPath interface.
+	 *
+	 * @var DOMXPath
+	 */
+	public $xpath = null;
+	/**
+	 * Stack of selected elements.
+	 * @TODO refactor to ->nodes
+	 * @var array
+	 */
+	public $elements = array();
+	/**
+	 * @access private
+	 */
+	protected $elementsBackup = array();
+	/**
+	 * @access private
+	 */
+	protected $previous = null;
+	/**
+	 * @access private
+	 * @TODO deprecate
+	 */
+	protected $root = array();
+	/**
+	 * Indicated if doument is just a fragment (no <html> tag).
+	 *
+	 * Every document is realy a full document, so even documentFragments can
+	 * be queried against <html>, but getDocument(id)->htmlOuter() will return
+	 * only contents of <body>.
+	 *
+	 * @var bool
+	 */
+	public $documentFragment = true;
+	/**
+	 * Iterator interface helper
+	 * @access private
+	 */
+	protected $elementsInterator = array();
+	/**
+	 * Iterator interface helper
+	 * @access private
+	 */
+	protected $valid = false;
+	/**
+	 * Iterator interface helper
+	 * @access private
+	 */
+	protected $current = null;
+	/**
+	 * Enter description here...
+	 *
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 */
+	public function __construct($documentID) {
+//		if ($documentID instanceof self)
+//			var_dump($documentID->getDocumentID());
+		$id = $documentID instanceof self
+			? $documentID->getDocumentID()
+			: $documentID;
+//		var_dump($id);
+		if (! isset(phpQuery::$documents[$id] )) {
+//			var_dump(phpQuery::$documents);
+			throw new Exception("Document with ID '{$id}' isn't loaded. Use phpQuery::newDocument(\$html) or phpQuery::newDocumentFile(\$file) first.");
+		}
+		$this->documentID = $id;
+		$this->documentWrapper =& phpQuery::$documents[$id];
+		$this->document =& $this->documentWrapper->document;
+		$this->xpath =& $this->documentWrapper->xpath;
+		$this->charset =& $this->documentWrapper->charset;
+		$this->documentFragment =& $this->documentWrapper->isDocumentFragment;
+		// TODO check $this->DOM->documentElement;
+//		$this->root = $this->document->documentElement;
+		$this->root =& $this->documentWrapper->root;
+//		$this->toRoot();
+		$this->elements = array($this->root);
+	}
+	/**
+	 *
+	 * @access private
+	 * @param $attr
+	 * @return unknown_type
+	 */
+	public function __get($attr) {
+		switch($attr) {
+			// FIXME doesnt work at all ?
+			case 'length':
+				return $this->size();
+			break;
+			default:
+				return $this->$attr;
+		}
+	}
+	/**
+	 * Saves actual object to $var by reference.
+	 * Useful when need to break chain.
+	 * @param phpQueryObject $var
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 */
+	public function toReference(&$var) {
+		return $var = $this;
+	}
+	public function documentFragment($state = null) {
+		if ($state) {
+			phpQuery::$documents[$this->getDocumentID()]['documentFragment'] = $state;
+			return $this;
+		}
+		return $this->documentFragment;
+	}
+	/**
+   * @access private
+   * @TODO documentWrapper
+	 */
+	protected function isRoot( $node) {
+//		return $node instanceof DOMDOCUMENT || $node->tagName == 'html';
+		return $node instanceof DOMDOCUMENT
+			|| ($node instanceof DOMELEMENT && $node->tagName == 'html')
+			|| $this->root->isSameNode($node);
+	}
+	/**
+   * @access private
+	 */
+	protected function stackIsRoot() {
+		return $this->size() == 1 && $this->isRoot($this->elements[0]);
+	}
+	/**
+	 * Enter description here...
+	 * NON JQUERY METHOD
+	 *
+	 * Watch out, it doesn't creates new instance, can be reverted with end().
+	 *
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 */
+	public function toRoot() {
+		$this->elements = array($this->root);
+		return $this;
+//		return $this->newInstance(array($this->root));
+	}
+	/**
+	 * Saves object's DocumentID to $var by reference.
+	 * <code>
+	 * $myDocumentId;
+	 * phpQuery::newDocument('<div/>')
+	 *     ->getDocumentIDRef($myDocumentId)
+	 *     ->find('div')->...
+	 * </code>
+	 *
+	 * @param unknown_type $domId
+	 * @see phpQuery::newDocument
+	 * @see phpQuery::newDocumentFile
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 */
+	public function getDocumentIDRef(&$documentID) {
+		$documentID = $this->getDocumentID();
+		return $this;
+	}
+	/**
+	 * Returns object with stack set to document root.
+	 *
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 */
+	public function getDocument() {
+		return phpQuery::getDocument($this->getDocumentID());
+	}
+	/**
+	 *
+	 * @return DOMDocument
+	 */
+	public function getDOMDocument() {
+		return $this->document;
+	}
+	/**
+	 * Get object's Document ID.
+	 *
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 */
+	public function getDocumentID() {
+		return $this->documentID;
+	}
+	/**
+	 * Unloads whole document from memory.
+	 * CAUTION! None further operations will be possible on this document.
+	 * All objects refering to it will be useless.
+	 *
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 */
+	public function unloadDocument() {
+		phpQuery::unloadDocuments($this->getDocumentID());
+	}
+	public function isHTML() {
+		return $this->documentWrapper->isHTML;
+	}
+	public function isXHTML() {
+		return $this->documentWrapper->isXHTML;
+	}
+	public function isXML() {
+		return $this->documentWrapper->isXML;
+	}
+	/**
+	 * Enter description here...
+	 *
+	 * @link http://docs.jquery.com/Ajax/serialize
+	 * @return string
+	 */
+	public function serialize() {
+		return phpQuery::param($this->serializeArray());
+	}
+	/**
+	 * Enter description here...
+	 *
+	 * @link http://docs.jquery.com/Ajax/serializeArray
+	 * @return array
+	 */
+	public function serializeArray($submit = null) {
+		$source = $this->filter('form, input, select, textarea')
+			->find('input, select, textarea')
+			->andSelf()
+			->not('form');
+		$return = array();
+//		$source->dumpDie();
+		foreach($source as $input) {
+			$input = phpQuery::pq($input);
+			if ($input->is('[disabled]'))
+				continue;
+			if (!$input->is('[name]'))
+				continue;
+			if ($input->is('[type=checkbox]') && !$input->is('[checked]'))
+				continue;
+			// jquery diff
+			if ($submit && $input->is('[type=submit]')) {
+				if ($submit instanceof DOMELEMENT && ! $input->elements[0]->isSameNode($submit))
+					continue;
+				else if (is_string($submit) && $input->attr('name') != $submit)
+					continue;
+			}
+			$return[] = array(
+				'name' => $input->attr('name'),
+				'value' => $input->val(),
+			);
+		}
+		return $return;
+	}
+	/**
+	 * @access private
+	 */
+	protected function debug($in) {
+		if (! phpQuery::$debug )
+			return;
+		print('<pre>');
+		print_r($in);
+		// file debug
+//		file_put_contents(dirname(__FILE__).'/phpQuery.log', print_r($in, true)."\n", FILE_APPEND);
+		// quite handy debug trace
+//		if ( is_array($in))
+//			print_r(array_slice(debug_backtrace(), 3));
+		print("</pre>\n");
+	}
+	/**
+	 * @access private
+	 */
+	protected function isRegexp($pattern) {
+		return in_array(
+			$pattern[ mb_strlen($pattern)-1 ],
+			array('^','*','$')
+		);
+	}
+	/**
+	 * Determines if $char is really a char.
+	 *
+	 * @param string $char
+	 * @return bool
+	 * @todo rewrite me to charcode range ! ;)
+	 * @access private
+	 */
+	protected function isChar($char) {
+		return extension_loaded('mbstring') && phpQuery::$mbstringSupport
+			? mb_eregi('\w', $char)
+			: preg_match('@\w@', $char);
+	}
+	/**
+	 * @access private
+	 */
+	protected function parseSelector($query) {
+		// clean spaces
+		// TODO include this inside parsing ?
+		$query = trim(
+			preg_replace('@\s+@', ' ',
+				preg_replace('@\s*(>|\\+|~)\s*@', '\\1', $query)
+			)
+		);
+		$queries = array(array());
+		if (! $query)
+			return $queries;
+		$return =& $queries[0];
+		$specialChars = array('>',' ');
+//		$specialCharsMapping = array('/' => '>');
+		$specialCharsMapping = array();
+		$strlen = mb_strlen($query);
+		$classChars = array('.', '-');
+		$pseudoChars = array('-');
+		$tagChars = array('*', '|', '-');
+		// split multibyte string
+		// http://code.google.com/p/phpquery/issues/detail?id=76
+		$_query = array();
+		for ($i=0; $i<$strlen; $i++)
+			$_query[] = mb_substr($query, $i, 1);
+		$query = $_query;
+		// it works, but i dont like it...
+		$i = 0;
+		while( $i < $strlen) {
+			$c = $query[$i];
+			$tmp = '';
+			// TAG
+			if ($this->isChar($c) || in_array($c, $tagChars)) {
+				while(isset($query[$i])
+					&& ($this->isChar($query[$i]) || in_array($query[$i], $tagChars))) {
+					$tmp .= $query[$i];
+					$i++;
+				}
+				$return[] = $tmp;
+			// IDs
+			} else if ( $c == '#') {
+				$i++;
+				while( isset($query[$i]) && ($this->isChar($query[$i]) || $query[$i] == '-')) {
+					$tmp .= $query[$i];
+					$i++;
+				}
+				$return[] = '#'.$tmp;
+			// SPECIAL CHARS
+			} else if (in_array($c, $specialChars)) {
+				$return[] = $c;
+				$i++;
+			// MAPPED SPECIAL MULTICHARS
+//			} else if ( $c.$query[$i+1] == '//') {
+//				$return[] = ' ';
+//				$i = $i+2;
+			// MAPPED SPECIAL CHARS
+			} else if ( isset($specialCharsMapping[$c])) {
+				$return[] = $specialCharsMapping[$c];
+				$i++;
+			// COMMA
+			} else if ( $c == ',') {
+				$queries[] = array();
+				$return =& $queries[ count($queries)-1 ];
+				$i++;
+				while( isset($query[$i]) && $query[$i] == ' ')
+					$i++;
+			// CLASSES
+			} else if ($c == '.') {
+				while( isset($query[$i]) && ($this->isChar($query[$i]) || in_array($query[$i], $classChars))) {
+					$tmp .= $query[$i];
+					$i++;
+				}
+				$return[] = $tmp;
+			// ~ General Sibling Selector
+			} else if ($c == '~') {
+				$spaceAllowed = true;
+				$tmp .= $query[$i++];
+				while( isset($query[$i])
+					&& ($this->isChar($query[$i])
+						|| in_array($query[$i], $classChars)
+						|| $query[$i] == '*'
+						|| ($query[$i] == ' ' && $spaceAllowed)
+					)) {
+					if ($query[$i] != ' ')
+						$spaceAllowed = false;
+					$tmp .= $query[$i];
+					$i++;
+				}
+				$return[] = $tmp;
+			// + Adjacent sibling selectors
+			} else if ($c == '+') {
+				$spaceAllowed = true;
+				$tmp .= $query[$i++];
+				while( isset($query[$i])
+					&& ($this->isChar($query[$i])
+						|| in_array($query[$i], $classChars)
+						|| $query[$i] == '*'
+						|| ($spaceAllowed && $query[$i] == ' ')
+					)) {
+					if ($query[$i] != ' ')
+						$spaceAllowed = false;
+					$tmp .= $query[$i];
+					$i++;
+				}
+				$return[] = $tmp;
+			// ATTRS
+			} else if ($c == '[') {
+				$stack = 1;
+				$tmp .= $c;
+				while( isset($query[++$i])) {
+					$tmp .= $query[$i];
+					if ( $query[$i] == '[') {
+						$stack++;
+					} else if ( $query[$i] == ']') {
+						$stack--;
+						if (! $stack )
+							break;
+					}
+				}
+				$return[] = $tmp;
+				$i++;
+			// PSEUDO CLASSES
+			} else if ($c == ':') {
+				$stack = 1;
+				$tmp .= $query[$i++];
+				while( isset($query[$i]) && ($this->isChar($query[$i]) || in_array($query[$i], $pseudoChars))) {
+					$tmp .= $query[$i];
+					$i++;
+				}
+				// with arguments ?
+				if ( isset($query[$i]) && $query[$i] == '(') {
+					$tmp .= $query[$i];
+					$stack = 1;
+					while( isset($query[++$i])) {
+						$tmp .= $query[$i];
+						if ( $query[$i] == '(') {
+							$stack++;
+						} else if ( $query[$i] == ')') {
+							$stack--;
+							if (! $stack )
+								break;
+						}
+					}
+					$return[] = $tmp;
+					$i++;
+				} else {
+					$return[] = $tmp;
+				}
+			} else {
+				$i++;
+			}
+		}
+		foreach($queries as $k => $q) {
+			if (isset($q[0])) {
+				if (isset($q[0][0]) && $q[0][0] == ':')
+					array_unshift($queries[$k], '*');
+				if ($q[0] != '>')
+					array_unshift($queries[$k], ' ');
+			}
+		}
+		return $queries;
+	}
+	/**
+	 * Return matched DOM nodes.
+	 *
+	 * @param int $index
+	 * @return array|DOMElement Single DOMElement or array of DOMElement.
+	 */
+	public function get($index = null, $callback1 = null, $callback2 = null, $callback3 = null) {
+		$return = isset($index)
+			? (isset($this->elements[$index]) ? $this->elements[$index] : null)
+			: $this->elements;
+		// pass thou callbacks
+		$args = func_get_args();
+		$args = array_slice($args, 1);
+		foreach($args as $callback) {
+			if (is_array($return))
+				foreach($return as $k => $v)
+					$return[$k] = phpQuery::callbackRun($callback, array($v));
+			else
+				$return = phpQuery::callbackRun($callback, array($return));
+		}
+		return $return;
+	}
+	/**
+	 * Return matched DOM nodes.
+	 * jQuery difference.
+	 *
+	 * @param int $index
+	 * @return array|string Returns string if $index != null
+	 * @todo implement callbacks
+	 * @todo return only arrays ?
+	 * @todo maybe other name...
+	 */
+	public function getString($index = null, $callback1 = null, $callback2 = null, $callback3 = null) {
+		if ($index)
+			$return = $this->eq($index)->text();
+		else {
+			$return = array();
+			for($i = 0; $i < $this->size(); $i++) {
+				$return[] = $this->eq($i)->text();
+			}
+		}
+		// pass thou callbacks
+		$args = func_get_args();
+		$args = array_slice($args, 1);
+		foreach($args as $callback) {
+			$return = phpQuery::callbackRun($callback, array($return));
+		}
+		return $return;
+	}
+	/**
+	 * Return matched DOM nodes.
+	 * jQuery difference.
+	 *
+	 * @param int $index
+	 * @return array|string Returns string if $index != null
+	 * @todo implement callbacks
+	 * @todo return only arrays ?
+	 * @todo maybe other name...
+	 */
+	public function getStrings($index = null, $callback1 = null, $callback2 = null, $callback3 = null) {
+		if ($index)
+			$return = $this->eq($index)->text();
+		else {
+			$return = array();
+			for($i = 0; $i < $this->size(); $i++) {
+				$return[] = $this->eq($i)->text();
+			}
+			// pass thou callbacks
+			$args = func_get_args();
+			$args = array_slice($args, 1);
+		}
+		foreach($args as $callback) {
+			if (is_array($return))
+				foreach($return as $k => $v)
+					$return[$k] = phpQuery::callbackRun($callback, array($v));
+			else
+				$return = phpQuery::callbackRun($callback, array($return));
+		}
+		return $return;
+	}
+	/**
+	 * Returns new instance of actual class.
+	 *
+	 * @param array $newStack Optional. Will replace old stack with new and move old one to history.c
+	 */
+	public function newInstance($newStack = null) {
+		$class = get_class($this);
+		// support inheritance by passing old object to overloaded constructor
+		$new = $class != 'phpQuery'
+			? new $class($this, $this->getDocumentID())
+			: new phpQueryObject($this->getDocumentID());
+		$new->previous = $this;
+		if (is_null($newStack)) {
+			$new->elements = $this->elements;
+			if ($this->elementsBackup)
+				$this->elements = $this->elementsBackup;
+		} else if (is_string($newStack)) {
+			$new->elements = phpQuery::pq($newStack, $this->getDocumentID())->stack();
+		} else {
+			$new->elements = $newStack;
+		}
+		return $new;
+	}
+	/**
+	 * Enter description here...
+	 *
+	 * In the future, when PHP will support XLS 2.0, then we would do that this way:
+	 * contains(tokenize(@class, '\s'), "something")
+	 * @param unknown_type $class
+	 * @param unknown_type $node
+	 * @return boolean
+	 * @access private
+	 */
+	protected function matchClasses($class, $node) {
+		// multi-class
+		if ( mb_strpos($class, '.', 1)) {
+			$classes = explode('.', substr($class, 1));
+			$classesCount = count( $classes );
+			$nodeClasses = explode(' ', $node->getAttribute('class') );
+			$nodeClassesCount = count( $nodeClasses );
+			if ( $classesCount > $nodeClassesCount )
+				return false;
+			$diff = count(
+				array_diff(
+					$classes,
+					$nodeClasses
+				)
+			);
+			if (! $diff )
+				return true;
+		// single-class
+		} else {
+			return in_array(
+				// strip leading dot from class name
+				substr($class, 1),
+				// get classes for element as array
+				explode(' ', $node->getAttribute('class') )
+			);
+		}
+	}
+	/**
+	 * @access private
+	 */
+	protected function runQuery($XQuery, $selector = null, $compare = null) {
+		if ($compare && ! method_exists($this, $compare))
+			return false;
+		$stack = array();
+		if (! $this->elements)
+			$this->debug('Stack empty, skipping...');
+//		var_dump($this->elements[0]->nodeType);
+		// element, document
+		foreach($this->stack(array(1, 9, 13)) as $k => $stackNode) {
+			$detachAfter = false;
+			// to work on detached nodes we need temporary place them somewhere
+			// thats because context xpath queries sucks ;]
+			$testNode = $stackNode;
+			while ($testNode) {
+				if (! $testNode->parentNode && ! $this->isRoot($testNode)) {
+					$this->root->appendChild($testNode);
+					$detachAfter = $testNode;
+					break;
+				}
+				$testNode = isset($testNode->parentNode)
+					? $testNode->parentNode
+					: null;
+			}
+			// XXX tmp ?
+			$xpath = $this->documentWrapper->isXHTML
+				? $this->getNodeXpath($stackNode, 'html')
+				: $this->getNodeXpath($stackNode);
+			// FIXME pseudoclasses-only query, support XML
+			$query = $XQuery == '//' && $xpath == '/html[1]'
+				? '//*'
+				: $xpath.$XQuery;
+			$this->debug("XPATH: {$query}");
+			// run query, get elements
+			$nodes = $this->xpath->query($query);
+			$this->debug("QUERY FETCHED");
+			if (! $nodes->length )
+				$this->debug('Nothing found');
+			$debug = array();
+			foreach($nodes as $node) {
+				$matched = false;
+				if ( $compare) {
+					phpQuery::$debug ?
+						$this->debug("Found: ".$this->whois( $node ).", comparing with {$compare}()")
+						: null;
+					$phpQueryDebug = phpQuery::$debug;
+					phpQuery::$debug = false;
+					// TODO ??? use phpQuery::callbackRun()
+					if (call_user_func_array(array($this, $compare), array($selector, $node)))
+						$matched = true;
+					phpQuery::$debug = $phpQueryDebug;
+				} else {
+					$matched = true;
+				}
+				if ( $matched) {
+					if (phpQuery::$debug)
+						$debug[] = $this->whois( $node );
+					$stack[] = $node;
+				}
+			}
+			if (phpQuery::$debug) {
+				$this->debug("Matched ".count($debug).": ".implode(', ', $debug));
+			}
+			if ($detachAfter)
+				$this->root->removeChild($detachAfter);
+		}
+		$this->elements = $stack;
+	}
+	/**
+	 * Enter description here...
+	 *
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 */
+	public function find($selectors, $context = null, $noHistory = false) {
+		if (!$noHistory)
+			// backup last stack /for end()/
+			$this->elementsBackup = $this->elements;
+		// allow to define context
+		// TODO combine code below with phpQuery::pq() context guessing code
+		//   as generic function
+		if ($context) {
+			if (! is_array($context) && $context instanceof DOMELEMENT)
+				$this->elements = array($context);
+			else if (is_array($context)) {
+				$this->elements = array();
+				foreach ($context as $c)
+					if ($c instanceof DOMELEMENT)
+						$this->elements[] = $c;
+			} else if ( $context instanceof self )
+				$this->elements = $context->elements;
+		}
+		$queries = $this->parseSelector($selectors);
+		$this->debug(array('FIND', $selectors, $queries));
+		$XQuery = '';
+		// remember stack state because of multi-queries
+		$oldStack = $this->elements;
+		// here we will be keeping found elements
+		$stack = array();
+		foreach($queries as $selector) {
+			$this->elements = $oldStack;
+			$delimiterBefore = false;
+			foreach($selector as $s) {
+				// TAG
+				$isTag = extension_loaded('mbstring') && phpQuery::$mbstringSupport
+					? mb_ereg_match('^[\w|\||-]+$', $s) || $s == '*'
+					: preg_match('@^[\w|\||-]+$@', $s) || $s == '*';
+				if ($isTag) {
+					if ($this->isXML()) {
+						// namespace support
+						if (mb_strpos($s, '|') !== false) {
+							$ns = $tag = null;
+							list($ns, $tag) = explode('|', $s);
+							$XQuery .= "$ns:$tag";
+						} else if ($s == '*') {
+							$XQuery .= "*";
+						} else {
+							$XQuery .= "*[local-name()='$s']";
+						}
+					} else {
+						$XQuery .= $s;
+					}
+				// ID
+				} else if ($s[0] == '#') {
+					if ($delimiterBefore)
+						$XQuery .= '*';
+					$XQuery .= "[@id='".substr($s, 1)."']";
+				// ATTRIBUTES
+				} else if ($s[0] == '[') {
+					if ($delimiterBefore)
+						$XQuery .= '*';
+					// strip side brackets
+					$attr = trim($s, '][');
+					$execute = false;
+					// attr with specifed value
+					if (mb_strpos($s, '=')) {
+						$value = null;
+						list($attr, $value) = explode('=', $attr);
+						$value = trim($value, "'\"");
+						if ($this->isRegexp($attr)) {
+							// cut regexp character
+							$attr = substr($attr, 0, -1);
+							$execute = true;
+							$XQuery .= "[@{$attr}]";
+						} else {
+							$XQuery .= "[@{$attr}='{$value}']";
+						}
+					// attr without specified value
+					} else {
+						$XQuery .= "[@{$attr}]";
+					}
+					if ($execute) {
+						$this->runQuery($XQuery, $s, 'is');
+						$XQuery = '';
+						if (! $this->length())
+							break;
+					}
+				// CLASSES
+				} else if ($s[0] == '.') {
+					// TODO use return $this->find("./self::*[contains(concat(\" \",@class,\" \"), \" $class \")]");
+					// thx wizDom ;)
+					if ($delimiterBefore)
+						$XQuery .= '*';
+					$XQuery .= '[@class]';
+					$this->runQuery($XQuery, $s, 'matchClasses');
+					$XQuery = '';
+					if (! $this->length() )
+						break;
+				// ~ General Sibling Selector
+				} else if ($s[0] == '~') {
+					$this->runQuery($XQuery);
+					$XQuery = '';
+					$this->elements = $this
+						->siblings(
+							substr($s, 1)
+						)->elements;
+					if (! $this->length() )
+						break;
+				// + Adjacent sibling selectors
+				} else if ($s[0] == '+') {
+					// TODO /following-sibling::
+					$this->runQuery($XQuery);
+					$XQuery = '';
+					$subSelector = substr($s, 1);
+					$subElements = $this->elements;
+					$this->elements = array();
+					foreach($subElements as $node) {
+						// search first DOMElement sibling
+						$test = $node->nextSibling;
+						while($test && ! ($test instanceof DOMELEMENT))
+							$test = $test->nextSibling;
+						if ($test && $this->is($subSelector, $test))
+							$this->elements[] = $test;
+					}
+					if (! $this->length() )
+						break;
+				// PSEUDO CLASSES
+				} else if ($s[0] == ':') {
+					// TODO optimization for :first :last
+					if ($XQuery) {
+						$this->runQuery($XQuery);
+						$XQuery = '';
+					}
+					if (! $this->length())
+						break;
+					$this->pseudoClasses($s);
+					if (! $this->length())
+						break;
+				// DIRECT DESCENDANDS
+				} else if ($s == '>') {
+					$XQuery .= '/';
+					$delimiterBefore = 2;
+				// ALL DESCENDANDS
+				} else if ($s == ' ') {
+					$XQuery .= '//';
+					$delimiterBefore = 2;
+				// ERRORS
+				} else {
+					phpQuery::debug("Unrecognized token '$s'");
+				}
+				$delimiterBefore = $delimiterBefore === 2;
+			}
+			// run query if any
+			if ($XQuery && $XQuery != '//') {
+				$this->runQuery($XQuery);
+				$XQuery = '';
+			}
+			foreach($this->elements as $node)
+				if (! $this->elementsContainsNode($node, $stack))
+					$stack[] = $node;
+		}
+		$this->elements = $stack;
+		return $this->newInstance();
+	}
+	/**
+	 * @todo create API for classes with pseudoselectors
+	 * @access private
+	 */
+	protected function pseudoClasses($class) {
+		// TODO clean args parsing ?
+		$class = ltrim($class, ':');
+		$haveArgs = mb_strpos($class, '(');
+		if ($haveArgs !== false) {
+			$args = substr($class, $haveArgs+1, -1);
+			$class = substr($class, 0, $haveArgs);
+		}
+		switch($class) {
+			case 'even':
+			case 'odd':
+				$stack = array();
+				foreach($this->elements as $i => $node) {
+					if ($class == 'even' && ($i%2) == 0)
+						$stack[] = $node;
+					else if ( $class == 'odd' && $i % 2 )
+						$stack[] = $node;
+				}
+				$this->elements = $stack;
+				break;
+			case 'eq':
+				$k = intval($args);
+				$this->elements = isset( $this->elements[$k] )
+					? array( $this->elements[$k] )
+					: array();
+				break;
+			case 'gt':
+				$this->elements = array_slice($this->elements, $args+1);
+				break;
+			case 'lt':
+				$this->elements = array_slice($this->elements, 0, $args+1);
+				break;
+			case 'first':
+				if (isset($this->elements[0]))
+					$this->elements = array($this->elements[0]);
+				break;
+			case 'last':
+				if ($this->elements)
+					$this->elements = array($this->elements[count($this->elements)-1]);
+				break;
+			/*case 'parent':
+				$stack = array();
+				foreach($this->elements as $node) {
+					if ( $node->childNodes->length )
+						$stack[] = $node;
+				}
+				$this->elements = $stack;
+				break;*/
+			case 'contains':
+				$text = trim($args, "\"'");
+				$stack = array();
+				foreach($this->elements as $node) {
+					if (mb_stripos($node->textContent, $text) === false)
+						continue;
+					$stack[] = $node;
+				}
+				$this->elements = $stack;
+				break;
+			case 'not':
+				$selector = self::unQuote($args);
+				$this->elements = $this->not($selector)->stack();
+				break;
+			case 'slice':
+				// TODO jQuery difference ?
+				$args = explode(',',
+					str_replace(', ', ',', trim($args, "\"'"))
+				);
+				$start = $args[0];
+				$end = isset($args[1])
+					? $args[1]
+					: null;
+				if ($end > 0)
+					$end = $end-$start;
+				$this->elements = array_slice($this->elements, $start, $end);
+				break;
+			case 'has':
+				$selector = trim($args, "\"'");
+				$stack = array();
+				foreach($this->stack(1) as $el) {
+					if ($this->find($selector, $el, true)->length)
+						$stack[] = $el;
+				}
+				$this->elements = $stack;
+				break;
+			case 'submit':
+			case 'reset':
+				$this->elements = phpQuery::merge(
+					$this->map(array($this, 'is'),
+						"input[type=$class]", new CallbackParam()
+					),
+					$this->map(array($this, 'is'),
+						"button[type=$class]", new CallbackParam()
+					)
+				);
+			break;
+//				$stack = array();
+//				foreach($this->elements as $node)
+//					if ($node->is('input[type=submit]') || $node->is('button[type=submit]'))
+//						$stack[] = $el;
+//				$this->elements = $stack;
+			case 'input':
+				$this->elements = $this->map(
+					array($this, 'is'),
+					'input', new CallbackParam()
+				)->elements;
+			break;
+			case 'password':
+			case 'checkbox':
+			case 'radio':
+			case 'hidden':
+			case 'image':
+			case 'file':
+				$this->elements = $this->map(
+					array($this, 'is'),
+					"input[type=$class]", new CallbackParam()
+				)->elements;
+			break;
+			case 'parent':
+				$this->elements = $this->map(
+					create_function('$node', '
+						return $node instanceof DOMELEMENT && $node->childNodes->length
+							? $node : null;')
+				)->elements;
+			break;
+			case 'empty':
+				$this->elements = $this->map(
+					create_function('$node', '
+						return $node instanceof DOMELEMENT && $node->childNodes->length
+							? null : $node;')
+				)->elements;
+			break;
+			case 'disabled':
+			case 'selected':
+			case 'checked':
+				$this->elements = $this->map(
+					array($this, 'is'),
+					"[$class]", new CallbackParam()
+				)->elements;
+			break;
+			case 'enabled':
+				$this->elements = $this->map(
+					create_function('$node', '
+						return pq($node)->not(":disabled") ? $node : null;')
+				)->elements;
+			break;
+			case 'header':
+				$this->elements = $this->map(
+					create_function('$node',
+						'$isHeader = isset($node->tagName) && in_array($node->tagName, array(
+							"h1", "h2", "h3", "h4", "h5", "h6", "h7"
+						));
+						return $isHeader
+							? $node
+							: null;')
+				)->elements;
+//				$this->elements = $this->map(
+//					create_function('$node', '$node = pq($node);
+//						return $node->is("h1")
+//							|| $node->is("h2")
+//							|| $node->is("h3")
+//							|| $node->is("h4")
+//							|| $node->is("h5")
+//							|| $node->is("h6")
+//							|| $node->is("h7")
+//							? $node
+//							: null;')
+//				)->elements;
+			break;
+			case 'only-child':
+				$this->elements = $this->map(
+					create_function('$node',
+						'return pq($node)->siblings()->size() == 0 ? $node : null;')
+				)->elements;
+			break;
+			case 'first-child':
+				$this->elements = $this->map(
+					create_function('$node', 'return pq($node)->prevAll()->size() == 0 ? $node : null;')
+				)->elements;
+			break;
+			case 'last-child':
+				$this->elements = $this->map(
+					create_function('$node', 'return pq($node)->nextAll()->size() == 0 ? $node : null;')
+				)->elements;
+			break;
+			case 'nth-child':
+				$param = trim($args, "\"'");
+				if (! $param)
+					break;
+					// nth-child(n+b) to nth-child(1n+b)
+				if ($param{0} == 'n')
+					$param = '1'.$param;
+				// :nth-child(index/even/odd/equation)
+				if ($param == 'even' || $param == 'odd')
+					$mapped = $this->map(
+						create_function('$node, $param',
+							'$index = pq($node)->prevAll()->size()+1;
+							if ($param == "even" && ($index%2) == 0)
+								return $node;
+							else if ($param == "odd" && $index%2 == 1)
+								return $node;
+							else
+								return null;'),
+						new CallbackParam(), $param
+					);
+				else if (mb_strlen($param) > 1 && $param{1} == 'n')
+					// an+b
+					$mapped = $this->map(
+						create_function('$node, $param',
+							'$prevs = pq($node)->prevAll()->size();
+							$index = 1+$prevs;
+							$b = mb_strlen($param) > 3
+								? $param{3}
+								: 0;
+							$a = $param{0};
+							if ($b && $param{2} == "-")
+								$b = -$b;
+							if ($a > 0) {
+								return ($index-$b)%$a == 0
+									? $node
+									: null;
+								phpQuery::debug($a."*".floor($index/$a)."+$b-1 == ".($a*floor($index/$a)+$b-1)." ?= $prevs");
+								return $a*floor($index/$a)+$b-1 == $prevs
+										? $node
+										: null;
+							} else if ($a == 0)
+								return $index == $b
+										? $node
+										: null;
+							else
+								// negative value
+								return $index <= $b
+										? $node
+										: null;
+//							if (! $b)
+//								return $index%$a == 0
+//									? $node
+//									: null;
+//							else
+//								return ($index-$b)%$a == 0
+//									? $node
+//									: null;
+							'),
+						new CallbackParam(), $param
+					);
+				else
+					// index
+					$mapped = $this->map(
+						create_function('$node, $index',
+							'$prevs = pq($node)->prevAll()->size();
+							if ($prevs && $prevs == $index-1)
+								return $node;
+							else if (! $prevs && $index == 1)
+								return $node;
+							else
+								return null;'),
+						new CallbackParam(), $param
+					);
+				$this->elements = $mapped->elements;
+			break;
+			default:
+				$this->debug("Unknown pseudoclass '{$class}', skipping...");
+		}
+	}
+	/**
+	 * @access private
+	 */
+	protected function __pseudoClassParam($paramsString) {
+		// TODO;
+	}
+	/**
+	 * Enter description here...
+	 *
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 */
+	public function is($selector, $nodes = null) {
+		phpQuery::debug(array("Is:", $selector));
+		if (! $selector)
+			return false;
+		$oldStack = $this->elements;
+		$returnArray = false;
+		if ($nodes && is_array($nodes)) {
+			$this->elements = $nodes;
+		} else if ($nodes)
+			$this->elements = array($nodes);
+		$this->filter($selector, true);
+		$stack = $this->elements;
+		$this->elements = $oldStack;
+		if ($nodes)
+			return $stack ? $stack : null;
+		return (bool)count($stack);
+	}
+	/**
+	 * Enter description here...
+	 * jQuery difference.
+	 *
+	 * Callback:
+	 * - $index int
+	 * - $node DOMNode
+	 *
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 * @link http://docs.jquery.com/Traversing/filter
+	 */
+	public function filterCallback($callback, $_skipHistory = false) {
+		if (! $_skipHistory) {
+			$this->elementsBackup = $this->elements;
+			$this->debug("Filtering by callback");
+		}
+		$newStack = array();
+		foreach($this->elements as $index => $node) {
+			$result = phpQuery::callbackRun($callback, array($index, $node));
+			if (is_null($result) || (! is_null($result) && $result))
+				$newStack[] = $node;
+		}
+		$this->elements = $newStack;
+		return $_skipHistory
+			? $this
+			: $this->newInstance();
+	}
+	/**
+	 * Enter description here...
+	 *
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 * @link http://docs.jquery.com/Traversing/filter
+	 */
+	public function filter($selectors, $_skipHistory = false) {
+		if ($selectors instanceof Callback OR $selectors instanceof Closure)
+			return $this->filterCallback($selectors, $_skipHistory);
+		if (! $_skipHistory)
+			$this->elementsBackup = $this->elements;
+		$notSimpleSelector = array(' ', '>', '~', '+', '/');
+		if (! is_array($selectors))
+			$selectors = $this->parseSelector($selectors);
+		if (! $_skipHistory)
+			$this->debug(array("Filtering:", $selectors));
+		$finalStack = array();
+		foreach($selectors as $selector) {
+			$stack = array();
+			if (! $selector)
+				break;
+			// avoid first space or /
+			if (in_array($selector[0], $notSimpleSelector))
+				$selector = array_slice($selector, 1);
+			// PER NODE selector chunks
+			foreach($this->stack() as $node) {
+				$break = false;
+				foreach($selector as $s) {
+					if (!($node instanceof DOMELEMENT)) {
+						// all besides DOMElement
+						if ( $s[0] == '[') {
+							$attr = trim($s, '[]');
+							if ( mb_strpos($attr, '=')) {
+								list( $attr, $val ) = explode('=', $attr);
+								if ($attr == 'nodeType' && $node->nodeType != $val)
+									$break = true;
+							}
+						} else
+							$break = true;
+					} else {
+						// DOMElement only
+						// ID
+						if ( $s[0] == '#') {
+							if ( $node->getAttribute('id') != substr($s, 1) )
+								$break = true;
+						// CLASSES
+						} else if ( $s[0] == '.') {
+							if (! $this->matchClasses( $s, $node ) )
+								$break = true;
+						// ATTRS
+						} else if ( $s[0] == '[') {
+							// strip side brackets
+							$attr = trim($s, '[]');
+							if (mb_strpos($attr, '=')) {
+								list($attr, $val) = explode('=', $attr);
+								$val = self::unQuote($val);
+								if ($attr == 'nodeType') {
+									if ($val != $node->nodeType)
+										$break = true;
+								} else if ($this->isRegexp($attr)) {
+									$val = extension_loaded('mbstring') && phpQuery::$mbstringSupport
+										? quotemeta(trim($val, '"\''))
+										: preg_quote(trim($val, '"\''), '@');
+									// switch last character
+									switch( substr($attr, -1)) {
+										// quotemeta used insted of preg_quote
+										// http://code.google.com/p/phpquery/issues/detail?id=76
+										case '^':
+											$pattern = '^'.$val;
+											break;
+										case '*':
+											$pattern = '.*'.$val.'.*';
+											break;
+										case '$':
+											$pattern = '.*'.$val.'$';
+											break;
+									}
+									// cut last character
+									$attr = substr($attr, 0, -1);
+									$isMatch = extension_loaded('mbstring') && phpQuery::$mbstringSupport
+										? mb_ereg_match($pattern, $node->getAttribute($attr))
+										: preg_match("@{$pattern}@", $node->getAttribute($attr));
+									if (! $isMatch)
+										$break = true;
+								} else if ($node->getAttribute($attr) != $val)
+									$break = true;
+							} else if (! $node->hasAttribute($attr))
+								$break = true;
+						// PSEUDO CLASSES
+						} else if ( $s[0] == ':') {
+							// skip
+						// TAG
+						} else if (trim($s)) {
+							if ($s != '*') {
+								// TODO namespaces
+								if (isset($node->tagName)) {
+									if ($node->tagName != $s)
+										$break = true;
+								} else if ($s == 'html' && ! $this->isRoot($node))
+									$break = true;
+							}
+						// AVOID NON-SIMPLE SELECTORS
+						} else if (in_array($s, $notSimpleSelector)) {
+							$break = true;
+							$this->debug(array('Skipping non simple selector', $selector));
+						}
+					}
+					if ($break)
+						break;
+				}
+				// if element passed all chunks of selector - add it to new stack
+				if (! $break )
+					$stack[] = $node;
+			}
+			$tmpStack = $this->elements;
+			$this->elements = $stack;
+			// PER ALL NODES selector chunks
+			foreach($selector as $s)
+				// PSEUDO CLASSES
+				if ($s[0] == ':')
+					$this->pseudoClasses($s);
+			foreach($this->elements as $node)
+				// XXX it should be merged without duplicates
+				// but jQuery doesnt do that
+				$finalStack[] = $node;
+			$this->elements = $tmpStack;
+		}
+		$this->elements = $finalStack;
+		if ($_skipHistory) {
+			return $this;
+		} else {
+			$this->debug("Stack length after filter(): ".count($finalStack));
+			return $this->newInstance();
+		}
+	}
+	/**
+	 *
+	 * @param $value
+	 * @return unknown_type
+	 * @TODO implement in all methods using passed parameters
+	 */
+	protected static function unQuote($value) {
+		return $value[0] == '\'' || $value[0] == '"'
+			? substr($value, 1, -1)
+			: $value;
+	}
+	/**
+	 * Enter description here...
+	 *
+	 * @link http://docs.jquery.com/Ajax/load
+	 * @return phpQuery|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 * @todo Support $selector
+	 */
+	public function load($url, $data = null, $callback = null) {
+		if ($data && ! is_array($data)) {
+			$callback = $data;
+			$data = null;
+		}
+		if (mb_strpos($url, ' ') !== false) {
+			$matches = null;
+			if (extension_loaded('mbstring') && phpQuery::$mbstringSupport)
+				mb_ereg('^([^ ]+) (.*)$', $url, $matches);
+			else
+				preg_match('^([^ ]+) (.*)$', $url, $matches);
+			$url = $matches[1];
+			$selector = $matches[2];
+			// FIXME this sucks, pass as callback param
+			$this->_loadSelector = $selector;
+		}
+		$ajax = array(
+			'url' => $url,
+			'type' => $data ? 'POST' : 'GET',
+			'data' => $data,
+			'complete' => $callback,
+			'success' => array($this, '__loadSuccess')
+		);
+		phpQuery::ajax($ajax);
+		return $this;
+	}
+	/**
+	 * @access private
+	 * @param $html
+	 * @return unknown_type
+	 */
+	public function __loadSuccess($html) {
+		if ($this->_loadSelector) {
+			$html = phpQuery::newDocument($html)->find($this->_loadSelector);
+			unset($this->_loadSelector);
+		}
+		foreach($this->stack(1) as $node) {
+			phpQuery::pq($node, $this->getDocumentID())
+				->markup($html);
+		}
+	}
+	/**
+	 * Enter description here...
+	 *
+	 * @return phpQuery|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 * @todo
+	 */
+	public function css() {
+		// TODO
+		return $this;
+	}
+	/**
+	 * @todo
+	 *
+	 */
+	public function show(){
+		// TODO
+		return $this;
+	}
+	/**
+	 * @todo
+	 *
+	 */
+	public function hide(){
+		// TODO
+		return $this;
+	}
+	/**
+	 * Trigger a type of event on every matched element.
+	 *
+	 * @param unknown_type $type
+	 * @param unknown_type $data
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 * @TODO support more than event in $type (space-separated)
+	 */
+	public function trigger($type, $data = array()) {
+		foreach($this->elements as $node)
+			phpQueryEvents::trigger($this->getDocumentID(), $type, $data, $node);
+		return $this;
+	}
+	/**
+	 * This particular method triggers all bound event handlers on an element (for a specific event type) WITHOUT executing the browsers default actions.
+	 *
+	 * @param unknown_type $type
+	 * @param unknown_type $data
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 * @TODO
+	 */
+	public function triggerHandler($type, $data = array()) {
+		// TODO;
+	}
+	/**
+	 * Binds a handler to one or more events (like click) for each matched element.
+	 * Can also bind custom events.
+	 *
+	 * @param unknown_type $type
+	 * @param unknown_type $data Optional
+	 * @param unknown_type $callback
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 * @TODO support '!' (exclusive) events
+	 * @TODO support more than event in $type (space-separated)
+	 */
+	public function bind($type, $data, $callback = null) {
+		// TODO check if $data is callable, not using is_callable
+		if (! isset($callback)) {
+			$callback = $data;
+			$data = null;
+		}
+		foreach($this->elements as $node)
+			phpQueryEvents::add($this->getDocumentID(), $node, $type, $data, $callback);
+		return $this;
+	}
+	/**
+	 * Enter description here...
+	 *
+	 * @param unknown_type $type
+	 * @param unknown_type $callback
+	 * @return unknown
+	 * @TODO namespace events
+	 * @TODO support more than event in $type (space-separated)
+	 */
+	public function unbind($type = null, $callback = null) {
+		foreach($this->elements as $node)
+			phpQueryEvents::remove($this->getDocumentID(), $node, $type, $callback);
+		return $this;
+	}
+	/**
+	 * Enter description here...
+	 *
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 */
+	public function change($callback = null) {
+		if ($callback)
+			return $this->bind('change', $callback);
+		return $this->trigger('change');
+	}
+	/**
+	 * Enter description here...
+	 *
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 */
+	public function submit($callback = null) {
+		if ($callback)
+			return $this->bind('submit', $callback);
+		return $this->trigger('submit');
+	}
+	/**
+	 * Enter description here...
+	 *
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 */
+	public function click($callback = null) {
+		if ($callback)
+			return $this->bind('click', $callback);
+		return $this->trigger('click');
+	}
+	/**
+	 * Enter description here...
+	 *
+	 * @param String|phpQuery
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 */
+	public function wrapAllOld($wrapper) {
+		$wrapper = pq($wrapper)->_clone();
+		if (! $wrapper->length() || ! $this->length() )
+			return $this;
+		$wrapper->insertBefore($this->elements[0]);
+		$deepest = $wrapper->elements[0];
+		while($deepest->firstChild && $deepest->firstChild instanceof DOMELEMENT)
+			$deepest = $deepest->firstChild;
+		pq($deepest)->append($this);
+		return $this;
+	}
+	/**
+	 * Enter description here...
+	 *
+	 * TODO testme...
+	 * @param String|phpQuery
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 */
+	public function wrapAll($wrapper) {
+		if (! $this->length())
+			return $this;
+		return phpQuery::pq($wrapper, $this->getDocumentID())
+			->clone()
+			->insertBefore($this->get(0))
+			->map(array($this, '___wrapAllCallback'))
+			->append($this);
+	}
+  /**
+   *
+	 * @param $node
+	 * @return unknown_type
+	 * @access private
+   */
+	public function ___wrapAllCallback($node) {
+		$deepest = $node;
+		while($deepest->firstChild && $deepest->firstChild instanceof DOMELEMENT)
+			$deepest = $deepest->firstChild;
+		return $deepest;
+	}
+	/**
+	 * Enter description here...
+	 * NON JQUERY METHOD
+	 *
+	 * @param String|phpQuery
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 */
+	public function wrapAllPHP($codeBefore, $codeAfter) {
+		return $this
+			->slice(0, 1)
+				->beforePHP($codeBefore)
+			->end()
+			->slice(-1)
+				->afterPHP($codeAfter)
+			->end();
+	}
+	/**
+	 * Enter description here...
+	 *
+	 * @param String|phpQuery
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 */
+	public function wrap($wrapper) {
+		foreach($this->stack() as $node)
+			phpQuery::pq($node, $this->getDocumentID())->wrapAll($wrapper);
+		return $this;
+	}
+	/**
+	 * Enter description here...
+	 *
+	 * @param String|phpQuery
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 */
+	public function wrapPHP($codeBefore, $codeAfter) {
+		foreach($this->stack() as $node)
+			phpQuery::pq($node, $this->getDocumentID())->wrapAllPHP($codeBefore, $codeAfter);
+		return $this;
+	}
+	/**
+	 * Enter description here...
+	 *
+	 * @param String|phpQuery
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 */
+	public function wrapInner($wrapper) {
+		foreach($this->stack() as $node)
+			phpQuery::pq($node, $this->getDocumentID())->contents()->wrapAll($wrapper);
+		return $this;
+	}
+	/**
+	 * Enter description here...
+	 *
+	 * @param String|phpQuery
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 */
+	public function wrapInnerPHP($codeBefore, $codeAfter) {
+		foreach($this->stack(1) as $node)
+			phpQuery::pq($node, $this->getDocumentID())->contents()
+				->wrapAllPHP($codeBefore, $codeAfter);
+		return $this;
+	}
+	/**
+	 * Enter description here...
+	 *
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 * @testme Support for text nodes
+	 */
+	public function contents() {
+		$stack = array();
+		foreach($this->stack(1) as $el) {
+			// FIXME (fixed) http://code.google.com/p/phpquery/issues/detail?id=56
+//			if (! isset($el->childNodes))
+//				continue;
+			foreach($el->childNodes as $node) {
+				$stack[] = $node;
+			}
+		}
+		return $this->newInstance($stack);
+	}
+	/**
+	 * Enter description here...
+	 *
+	 * jQuery difference.
+	 *
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 */
+	public function contentsUnwrap() {
+		foreach($this->stack(1) as $node) {
+			if (! $node->parentNode )
+				continue;
+			$childNodes = array();
+			// any modification in DOM tree breaks childNodes iteration, so cache them first
+			foreach($node->childNodes as $chNode )
+				$childNodes[] = $chNode;
+			foreach($childNodes as $chNode )
+//				$node->parentNode->appendChild($chNode);
+				$node->parentNode->insertBefore($chNode, $node);
+			$node->parentNode->removeChild($node);
+		}
+		return $this;
+	}
+	/**
+	 * Enter description here...
+	 *
+	 * jQuery difference.
+	 */
+	public function switchWith($markup) {
+		$markup = pq($markup, $this->getDocumentID());
+		$content = null;
+		foreach($this->stack(1) as $node) {
+			pq($node)
+				->contents()->toReference($content)->end()
+				->replaceWith($markup->clone()->append($content));
+		}
+		return $this;
+	}
+	/**
+	 * Enter description here...
+	 *
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 */
+	public function eq($num) {
+		$oldStack = $this->elements;
+		$this->elementsBackup = $this->elements;
+		$this->elements = array();
+		if ( isset($oldStack[$num]) )
+			$this->elements[] = $oldStack[$num];
+		return $this->newInstance();
+	}
+	/**
+	 * Enter description here...
+	 *
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 */
+	public function size() {
+		return count($this->elements);
+	}
+	/**
+	 * Enter description here...
+	 *
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 * @deprecated Use length as attribute
+	 */
+	public function length() {
+		return $this->size();
+	}
+	public function count() {
+		return $this->size();
+	}
+	/**
+	 * Enter description here...
+	 *
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 * @todo $level
+	 */
+	public function end($level = 1) {
+//		$this->elements = array_pop( $this->history );
+//		return $this;
+//		$this->previous->DOM = $this->DOM;
+//		$this->previous->XPath = $this->XPath;
+		return $this->previous
+			? $this->previous
+			: $this;
+	}
+	/**
+	 * Enter description here...
+	 * Normal use ->clone() .
+	 *
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 * @access private
+	 */
+	public function _clone() {
+		$newStack = array();
+		//pr(array('copy... ', $this->whois()));
+		//$this->dumpHistory('copy');
+		$this->elementsBackup = $this->elements;
+		foreach($this->elements as $node) {
+			$newStack[] = $node->cloneNode(true);
+		}
+		$this->elements = $newStack;
+		return $this->newInstance();
+	}
+	/**
+	 * Enter description here...
+	 *
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 */
+	public function replaceWithPHP($code) {
+		return $this->replaceWith(phpQuery::php($code));
+	}
+	/**
+	 * Enter description here...
+	 *
+	 * @param String|phpQuery $content
+	 * @link http://docs.jquery.com/Manipulation/replaceWith#content
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 */
+	public function replaceWith($content) {
+		return $this->after($content)->remove();
+	}
+	/**
+	 * Enter description here...
+	 *
+	 * @param String $selector
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 * @todo this works ?
+	 */
+	public function replaceAll($selector) {
+		foreach(phpQuery::pq($selector, $this->getDocumentID()) as $node)
+			phpQuery::pq($node, $this->getDocumentID())
+				->after($this->_clone())
+				->remove();
+		return $this;
+	}
+	/**
+	 * Enter description here...
+	 *
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 */
+	public function remove($selector = null) {
+		$loop = $selector
+			? $this->filter($selector)->elements
+			: $this->elements;
+		foreach($loop as $node) {
+			if (! $node->parentNode )
+				continue;
+			if (isset($node->tagName))
+				$this->debug("Removing '{$node->tagName}'");
+			$node->parentNode->removeChild($node);
+			// Mutation event
+			$event = new DOMEvent(array(
+				'target' => $node,
+				'type' => 'DOMNodeRemoved'
+			));
+			phpQueryEvents::trigger($this->getDocumentID(),
+				$event->type, array($event), $node
+			);
+		}
+		return $this;
+	}
+	protected function markupEvents($newMarkup, $oldMarkup, $node) {
+		if ($node->tagName == 'textarea' && $newMarkup != $oldMarkup) {
+			$event = new DOMEvent(array(
+				'target' => $node,
+				'type' => 'change'
+			));
+			phpQueryEvents::trigger($this->getDocumentID(),
+				$event->type, array($event), $node
+			);
+		}
+	}
+	/**
+	 * jQuey difference
+	 *
+	 * @param $markup
+	 * @return unknown_type
+	 * @TODO trigger change event for textarea
+	 */
+	public function markup($markup = null, $callback1 = null, $callback2 = null, $callback3 = null) {
+		$args = func_get_args();
+		if ($this->documentWrapper->isXML)
+			return call_user_func_array(array($this, 'xml'), $args);
+		else
+			return call_user_func_array(array($this, 'html'), $args);
+	}
+	/**
+	 * jQuey difference
+	 *
+	 * @param $markup
+	 * @return unknown_type
+	 */
+	public function markupOuter($callback1 = null, $callback2 = null, $callback3 = null) {
+		$args = func_get_args();
+		if ($this->documentWrapper->isXML)
+			return call_user_func_array(array($this, 'xmlOuter'), $args);
+		else
+			return call_user_func_array(array($this, 'htmlOuter'), $args);
+	}
+	/**
+	 * Enter description here...
+	 *
+	 * @param unknown_type $html
+	 * @return string|phpQuery|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 * @TODO force html result
+	 */
+	public function html($html = null, $callback1 = null, $callback2 = null, $callback3 = null) {
+		if (isset($html)) {
+			// INSERT
+			$nodes = $this->documentWrapper->import($html);
+			$this->empty();
+			foreach($this->stack(1) as $alreadyAdded => $node) {
+				// for now, limit events for textarea
+				if (($this->isXHTML() || $this->isHTML()) && $node->tagName == 'textarea')
+					$oldHtml = pq($node, $this->getDocumentID())->markup();
+				foreach($nodes as $newNode) {
+					$node->appendChild($alreadyAdded
+						? $newNode->cloneNode(true)
+						: $newNode
+					);
+				}
+				// for now, limit events for textarea
+				if (($this->isXHTML() || $this->isHTML()) && $node->tagName == 'textarea')
+					$this->markupEvents($html, $oldHtml, $node);
+			}
+			return $this;
+		} else {
+			// FETCH
+			$return = $this->documentWrapper->markup($this->elements, true);
+			$args = func_get_args();
+			foreach(array_slice($args, 1) as $callback) {
+				$return = phpQuery::callbackRun($callback, array($return));
+			}
+			return $return;
+		}
+	}
+	/**
+	 * @TODO force xml result
+	 */
+	public function xml($xml = null, $callback1 = null, $callback2 = null, $callback3 = null) {
+		$args = func_get_args();
+		return call_user_func_array(array($this, 'html'), $args);
+	}
+	/**
+	 * Enter description here...
+	 * @TODO force html result
+	 *
+	 * @return String
+	 */
+	public function htmlOuter($callback1 = null, $callback2 = null, $callback3 = null) {
+		$markup = $this->documentWrapper->markup($this->elements);
+		// pass thou callbacks
+		$args = func_get_args();
+		foreach($args as $callback) {
+			$markup = phpQuery::callbackRun($callback, array($markup));
+		}
+		return $markup;
+	}
+	/**
+	 * @TODO force xml result
+	 */
+	public function xmlOuter($callback1 = null, $callback2 = null, $callback3 = null) {
+		$args = func_get_args();
+		return call_user_func_array(array($this, 'htmlOuter'), $args);
+	}
+	public function __toString() {
+		return $this->markupOuter();
+	}
+	/**
+	 * Just like html(), but returns markup with VALID (dangerous) PHP tags.
+	 *
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 * @todo support returning markup with PHP tags when called without param
+	 */
+	public function php($code = null) {
+		return $this->markupPHP($code);
+	}
+	/**
+	 * Enter description here...
+	 * 
+	 * @param $code
+	 * @return unknown_type
+	 */
+	public function markupPHP($code = null) {
+		return isset($code)
+			? $this->markup(phpQuery::php($code))
+			: phpQuery::markupToPHP($this->markup());
+	}
+	/**
+	 * Enter description here...
+	 * 
+	 * @param $code
+	 * @return unknown_type
+	 */
+	public function markupOuterPHP() {
+		return phpQuery::markupToPHP($this->markupOuter());
+	}
+	/**
+	 * Enter description here...
+	 *
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 */
+	public function children($selector = null) {
+		$stack = array();
+		foreach($this->stack(1) as $node) {
+//			foreach($node->getElementsByTagName('*') as $newNode) {
+			foreach($node->childNodes as $newNode) {
+				if ($newNode->nodeType != 1)
+					continue;
+				if ($selector && ! $this->is($selector, $newNode))
+					continue;
+				if ($this->elementsContainsNode($newNode, $stack))
+					continue;
+				$stack[] = $newNode;
+			}
+		}
+		$this->elementsBackup = $this->elements;
+		$this->elements = $stack;
+		return $this->newInstance();
+	}
+	/**
+	 * Enter description here...
+	 *
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 */
+	public function ancestors($selector = null) {
+		return $this->children( $selector );
+	}
+	/**
+	 * Enter description here...
+	 *
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 */
+	public function append( $content) {
+		return $this->insert($content, __FUNCTION__);
+	}
+	/**
+	 * Enter description here...
+	 *
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 */
+	public function appendPHP( $content) {
+		return $this->insert("<php><!-- {$content} --></php>", 'append');
+	}
+	/**
+	 * Enter description here...
+	 *
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 */
+	public function appendTo( $seletor) {
+		return $this->insert($seletor, __FUNCTION__);
+	}
+	/**
+	 * Enter description here...
+	 *
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 */
+	public function prepend( $content) {
+		return $this->insert($content, __FUNCTION__);
+	}
+	/**
+	 * Enter description here...
+	 *
+	 * @todo accept many arguments, which are joined, arrays maybe also
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 */
+	public function prependPHP( $content) {
+		return $this->insert("<php><!-- {$content} --></php>", 'prepend');
+	}
+	/**
+	 * Enter description here...
+	 *
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 */
+	public function prependTo( $seletor) {
+		return $this->insert($seletor, __FUNCTION__);
+	}
+	/**
+	 * Enter description here...
+	 *
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 */
+	public function before($content) {
+		return $this->insert($content, __FUNCTION__);
+	}
+	/**
+	 * Enter description here...
+	 *
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 */
+	public function beforePHP( $content) {
+		return $this->insert("<php><!-- {$content} --></php>", 'before');
+	}
+	/**
+	 * Enter description here...
+	 *
+	 * @param String|phpQuery
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 */
+	public function insertBefore( $seletor) {
+		return $this->insert($seletor, __FUNCTION__);
+	}
+	/**
+	 * Enter description here...
+	 *
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 */
+	public function after( $content) {
+		return $this->insert($content, __FUNCTION__);
+	}
+	/**
+	 * Enter description here...
+	 *
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 */
+	public function afterPHP( $content) {
+		return $this->insert("<php><!-- {$content} --></php>", 'after');
+	}
+	/**
+	 * Enter description here...
+	 *
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 */
+	public function insertAfter( $seletor) {
+		return $this->insert($seletor, __FUNCTION__);
+	}
+	/**
+	 * Internal insert method. Don't use it.
+	 *
+	 * @param unknown_type $target
+	 * @param unknown_type $type
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 * @access private
+	 */
+	public function insert($target, $type) {
+		$this->debug("Inserting data with '{$type}'");
+		$to = false;
+		switch( $type) {
+			case 'appendTo':
+			case 'prependTo':
+			case 'insertBefore':
+			case 'insertAfter':
+				$to = true;
+		}
+		switch(gettype($target)) {
+			case 'string':
+				$insertFrom = $insertTo = array();
+				if ($to) {
+					// INSERT TO
+					$insertFrom = $this->elements;
+					if (phpQuery::isMarkup($target)) {
+						// $target is new markup, import it
+						$insertTo = $this->documentWrapper->import($target);
+					// insert into selected element
+					} else {
+						// $tagret is a selector
+						$thisStack = $this->elements;
+						$this->toRoot();
+						$insertTo = $this->find($target)->elements;
+						$this->elements = $thisStack;
+					}
+				} else {
+					// INSERT FROM
+					$insertTo = $this->elements;
+					$insertFrom = $this->documentWrapper->import($target);
+				}
+				break;
+			case 'object':
+				$insertFrom = $insertTo = array();
+				// phpQuery
+				if ($target instanceof self) {
+					if ($to) {
+						$insertTo = $target->elements;
+						if ($this->documentFragment && $this->stackIsRoot())
+							// get all body children
+//							$loop = $this->find('body > *')->elements;
+							// TODO test it, test it hard...
+//							$loop = $this->newInstance($this->root)->find('> *')->elements;
+							$loop = $this->root->childNodes;
+						else
+							$loop = $this->elements;
+						// import nodes if needed
+						$insertFrom = $this->getDocumentID() == $target->getDocumentID()
+							? $loop
+							: $target->documentWrapper->import($loop);
+					} else {
+						$insertTo = $this->elements;
+						if ( $target->documentFragment && $target->stackIsRoot() )
+							// get all body children
+//							$loop = $target->find('body > *')->elements;
+							$loop = $target->root->childNodes;
+						else
+							$loop = $target->elements;
+						// import nodes if needed
+						$insertFrom = $this->getDocumentID() == $target->getDocumentID()
+							? $loop
+							: $this->documentWrapper->import($loop);
+					}
+				// DOMNODE
+				} elseif ($target instanceof DOMNODE) {
+					// import node if needed
+//					if ( $target->ownerDocument != $this->DOM )
+//						$target = $this->DOM->importNode($target, true);
+					if ( $to) {
+						$insertTo = array($target);
+						if ($this->documentFragment && $this->stackIsRoot())
+							// get all body children
+							$loop = $this->root->childNodes;
+//							$loop = $this->find('body > *')->elements;
+						else
+							$loop = $this->elements;
+						foreach($loop as $fromNode)
+							// import nodes if needed
+							$insertFrom[] = ! $fromNode->ownerDocument->isSameNode($target->ownerDocument)
+								? $target->ownerDocument->importNode($fromNode, true)
+								: $fromNode;
+					} else {
+						// import node if needed
+						if (! $target->ownerDocument->isSameNode($this->document))
+							$target = $this->document->importNode($target, true);
+						$insertTo = $this->elements;
+						$insertFrom[] = $target;
+					}
+				}
+				break;
+		}
+		phpQuery::debug("From ".count($insertFrom)."; To ".count($insertTo)." nodes");
+		foreach($insertTo as $insertNumber => $toNode) {
+			// we need static relative elements in some cases
+			switch( $type) {
+				case 'prependTo':
+				case 'prepend':
+					$firstChild = $toNode->firstChild;
+					break;
+				case 'insertAfter':
+				case 'after':
+					$nextSibling = $toNode->nextSibling;
+					break;
+			}
+			foreach($insertFrom as $fromNode) {
+				// clone if inserted already before
+				$insert = $insertNumber
+					? $fromNode->cloneNode(true)
+					: $fromNode;
+				switch($type) {
+					case 'appendTo':
+					case 'append':
+//						$toNode->insertBefore(
+//							$fromNode,
+//							$toNode->lastChild->nextSibling
+//						);
+						$toNode->appendChild($insert);
+						$eventTarget = $insert;
+						break;
+					case 'prependTo':
+					case 'prepend':
+						$toNode->insertBefore(
+							$insert,
+							$firstChild
+						);
+						break;
+					case 'insertBefore':
+					case 'before':
+						if (! $toNode->parentNode)
+							throw new Exception("No parentNode, can't do {$type}()");
+						else
+							$toNode->parentNode->insertBefore(
+								$insert,
+								$toNode
+							);
+						break;
+					case 'insertAfter':
+					case 'after':
+						if (! $toNode->parentNode)
+							throw new Exception("No parentNode, can't do {$type}()");
+						else
+							$toNode->parentNode->insertBefore(
+								$insert,
+								$nextSibling
+							);
+						break;
+				}
+				// Mutation event
+				$event = new DOMEvent(array(
+					'target' => $insert,
+					'type' => 'DOMNodeInserted'
+				));
+				phpQueryEvents::trigger($this->getDocumentID(),
+					$event->type, array($event), $insert
+				);
+			}
+		}
+		return $this;
+	}
+	/**
+	 * Enter description here...
+	 *
+	 * @return Int
+	 */
+	public function index($subject) {
+		$index = -1;
+		$subject = $subject instanceof phpQueryObject
+			? $subject->elements[0]
+			: $subject;
+		foreach($this->newInstance() as $k => $node) {
+			if ($node->isSameNode($subject))
+				$index = $k;
+		}
+		return $index;
+	}
+	/**
+	 * Enter description here...
+	 *
+	 * @param unknown_type $start
+	 * @param unknown_type $end
+	 *
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 * @testme
+	 */
+	public function slice($start, $end = null) {
+//		$last = count($this->elements)-1;
+//		$end = $end
+//			? min($end, $last)
+//			: $last;
+//		if ($start < 0)
+//			$start = $last+$start;
+//		if ($start > $last)
+//			return array();
+		if ($end > 0)
+			$end = $end-$start;
+		return $this->newInstance(
+			array_slice($this->elements, $start, $end)
+		);
+	}
+	/**
+	 * Enter description here...
+	 *
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 */
+	public function reverse() {
+		$this->elementsBackup = $this->elements;
+		$this->elements = array_reverse($this->elements);
+		return $this->newInstance();
+	}
+	/**
+	 * Return joined text content.
+	 * @return String
+	 */
+	public function text($text = null, $callback1 = null, $callback2 = null, $callback3 = null) {
+		if (isset($text))
+			return $this->html(htmlspecialchars($text));
+		$args = func_get_args();
+		$args = array_slice($args, 1);
+		$return = '';
+		foreach($this->elements as $node) {
+			$text = $node->textContent;
+			if (count($this->elements) > 1 && $text)
+				$text .= "\n";
+			foreach($args as $callback) {
+				$text = phpQuery::callbackRun($callback, array($text));
+			}
+			$return .= $text;
+		}
+		return $return;
+	}
+	/**
+	 * Enter description here...
+	 *
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 */
+	public function plugin($class, $file = null) {
+		phpQuery::plugin($class, $file);
+		return $this;
+	}
+	/**
+	 * Deprecated, use $pq->plugin() instead.
+	 *
+	 * @deprecated
+	 * @param $class
+	 * @param $file
+	 * @return unknown_type
+	 */
+	public static function extend($class, $file = null) {
+		return $this->plugin($class, $file);
+	}
+	/**
+	 *
+	 * @access private
+	 * @param $method
+	 * @param $args
+	 * @return unknown_type
+	 */
+	public function __call($method, $args) {
+		$aliasMethods = array('clone', 'empty');
+		if (isset(phpQuery::$extendMethods[$method])) {
+			array_unshift($args, $this);
+			return phpQuery::callbackRun(
+				phpQuery::$extendMethods[$method], $args
+			);
+		} else if (isset(phpQuery::$pluginsMethods[$method])) {
+			array_unshift($args, $this);
+			$class = phpQuery::$pluginsMethods[$method];
+			$realClass = "phpQueryObjectPlugin_$class";
+			$return = call_user_func_array(
+				array($realClass, $method),
+				$args
+			);
+			// XXX deprecate ?
+			return is_null($return)
+				? $this
+				: $return;
+		} else if (in_array($method, $aliasMethods)) {
+			return call_user_func_array(array($this, '_'.$method), $args);
+		} else
+			throw new Exception("Method '{$method}' doesnt exist");
+	}
+	/**
+	 * Safe rename of next().
+	 *
+	 * Use it ONLY when need to call next() on an iterated object (in same time).
+	 * Normaly there is no need to do such thing ;)
+	 *
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 * @access private
+	 */
+	public function _next($selector = null) {
+		return $this->newInstance(
+			$this->getElementSiblings('nextSibling', $selector, true)
+		);
+	}
+	/**
+	 * Use prev() and next().
+	 *
+	 * @deprecated
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 * @access private
+	 */
+	public function _prev($selector = null) {
+		return $this->prev($selector);
+	}
+	/**
+	 * Enter description here...
+	 *
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 */
+	public function prev($selector = null) {
+		return $this->newInstance(
+			$this->getElementSiblings('previousSibling', $selector, true)
+		);
+	}
+	/**
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 * @todo
+	 */
+	public function prevAll($selector = null) {
+		return $this->newInstance(
+			$this->getElementSiblings('previousSibling', $selector)
+		);
+	}
+	/**
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 * @todo FIXME: returns source elements insted of next siblings
+	 */
+	public function nextAll($selector = null) {
+		return $this->newInstance(
+			$this->getElementSiblings('nextSibling', $selector)
+		);
+	}
+	/**
+	 * @access private
+	 */
+	protected function getElementSiblings($direction, $selector = null, $limitToOne = false) {
+		$stack = array();
+		$count = 0;
+		foreach($this->stack() as $node) {
+			$test = $node;
+			while( isset($test->{$direction}) && $test->{$direction}) {
+				$test = $test->{$direction};
+				if (! $test instanceof DOMELEMENT)
+					continue;
+				$stack[] = $test;
+				if ($limitToOne)
+					break;
+			}
+		}
+		if ($selector) {
+			$stackOld = $this->elements;
+			$this->elements = $stack;
+			$stack = $this->filter($selector, true)->stack();
+			$this->elements = $stackOld;
+		}
+		return $stack;
+	}
+	/**
+	 * Enter description here...
+	 *
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 */
+	public function siblings($selector = null) {
+		$stack = array();
+		$siblings = array_merge(
+			$this->getElementSiblings('previousSibling', $selector),
+			$this->getElementSiblings('nextSibling', $selector)
+		);
+		foreach($siblings as $node) {
+			if (! $this->elementsContainsNode($node, $stack))
+				$stack[] = $node;
+		}
+		return $this->newInstance($stack);
+	}
+	/**
+	 * Enter description here...
+	 *
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 */
+	public function not($selector = null) {
+		if (is_string($selector))
+			phpQuery::debug(array('not', $selector));
+		else
+			phpQuery::debug('not');
+		$stack = array();
+		if ($selector instanceof self || $selector instanceof DOMNODE) {
+			foreach($this->stack() as $node) {
+				if ($selector instanceof self) {
+					$matchFound = false;
+					foreach($selector->stack() as $notNode) {
+						if ($notNode->isSameNode($node))
+							$matchFound = true;
+					}
+					if (! $matchFound)
+						$stack[] = $node;
+				} else if ($selector instanceof DOMNODE) {
+					if (! $selector->isSameNode($node))
+						$stack[] = $node;
+				} else {
+					if (! $this->is($selector))
+						$stack[] = $node;
+				}
+			}
+		} else {
+			$orgStack = $this->stack();
+			$matched = $this->filter($selector, true)->stack();
+//			$matched = array();
+//			// simulate OR in filter() instead of AND 5y
+//			foreach($this->parseSelector($selector) as $s) {
+//				$matched = array_merge($matched,
+//					$this->filter(array($s))->stack()
+//				);
+//			}
+			foreach($orgStack as $node)
+				if (! $this->elementsContainsNode($node, $matched))
+					$stack[] = $node;
+		}
+		return $this->newInstance($stack);
+	}
+	/**
+	 * Enter description here...
+	 *
+	 * @param string|phpQueryObject
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 */
+	public function add($selector = null) {
+		if (! $selector)
+			return $this;
+		$stack = array();
+		$this->elementsBackup = $this->elements;
+		$found = phpQuery::pq($selector, $this->getDocumentID());
+		$this->merge($found->elements);
+		return $this->newInstance();
+	}
+	/**
+	 * @access private
+	 */
+	protected function merge() {
+		foreach(func_get_args() as $nodes)
+			foreach($nodes as $newNode )
+				if (! $this->elementsContainsNode($newNode) )
+					$this->elements[] = $newNode;
+	}
+	/**
+	 * @access private
+	 * TODO refactor to stackContainsNode
+	 */
+	protected function elementsContainsNode($nodeToCheck, $elementsStack = null) {
+		$loop = ! is_null($elementsStack)
+			? $elementsStack
+			: $this->elements;
+		foreach($loop as $node) {
+			if ( $node->isSameNode( $nodeToCheck ) )
+				return true;
+		}
+		return false;
+	}
+	/**
+	 * Enter description here...
+	 *
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 */
+	public function parent($selector = null) {
+		$stack = array();
+		foreach($this->elements as $node )
+			if ( $node->parentNode && ! $this->elementsContainsNode($node->parentNode, $stack) )
+				$stack[] = $node->parentNode;
+		$this->elementsBackup = $this->elements;
+		$this->elements = $stack;
+		if ( $selector )
+			$this->filter($selector, true);
+		return $this->newInstance();
+	}
+	/**
+	 * Enter description here...
+	 *
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 */
+	public function parents($selector = null) {
+		$stack = array();
+		if (! $this->elements )
+			$this->debug('parents() - stack empty');
+		foreach($this->elements as $node) {
+			$test = $node;
+			while( $test->parentNode) {
+				$test = $test->parentNode;
+				if ($this->isRoot($test))
+					break;
+				if (! $this->elementsContainsNode($test, $stack)) {
+					$stack[] = $test;
+					continue;
+				}
+			}
+		}
+		$this->elementsBackup = $this->elements;
+		$this->elements = $stack;
+		if ( $selector )
+			$this->filter($selector, true);
+		return $this->newInstance();
+	}
+	/**
+	 * Internal stack iterator.
+	 *
+	 * @access private
+	 */
+	public function stack($nodeTypes = null) {
+		if (!isset($nodeTypes))
+			return $this->elements;
+		if (!is_array($nodeTypes))
+			$nodeTypes = array($nodeTypes);
+		$return = array();
+		foreach($this->elements as $node) {
+			if (in_array($node->nodeType, $nodeTypes))
+				$return[] = $node;
+		}
+		return $return;
+	}
+	// TODO phpdoc; $oldAttr is result of hasAttribute, before any changes
+	protected function attrEvents($attr, $oldAttr, $oldValue, $node) {
+		// skip events for XML documents
+		if (! $this->isXHTML() && ! $this->isHTML())
+			return;
+		$event = null;
+		// identify
+		$isInputValue = $node->tagName == 'input'
+			&& (
+				in_array($node->getAttribute('type'),
+					array('text', 'password', 'hidden'))
+				|| !$node->getAttribute('type')
+				 );
+		$isRadio = $node->tagName == 'input'
+			&& $node->getAttribute('type') == 'radio';
+		$isCheckbox = $node->tagName == 'input'
+			&& $node->getAttribute('type') == 'checkbox';
+		$isOption = $node->tagName == 'option';
+		if ($isInputValue && $attr == 'value' && $oldValue != $node->getAttribute($attr)) {
+			$event = new DOMEvent(array(
+				'target' => $node,
+				'type' => 'change'
+			));
+		} else if (($isRadio || $isCheckbox) && $attr == 'checked' && (
+				// check
+				(! $oldAttr && $node->hasAttribute($attr))
+				// un-check
+				|| (! $node->hasAttribute($attr) && $oldAttr)
+			)) {
+			$event = new DOMEvent(array(
+				'target' => $node,
+				'type' => 'change'
+			));
+		} else if ($isOption && $node->parentNode && $attr == 'selected' && (
+				// select
+				(! $oldAttr && $node->hasAttribute($attr))
+				// un-select
+				|| (! $node->hasAttribute($attr) && $oldAttr)
+			)) {
+			$event = new DOMEvent(array(
+				'target' => $node->parentNode,
+				'type' => 'change'
+			));
+		}
+		if ($event) {
+			phpQueryEvents::trigger($this->getDocumentID(),
+				$event->type, array($event), $node
+			);
+		}
+	}
+	public function attr($attr = null, $value = null) {
+		foreach($this->stack(1) as $node) {
+			if (! is_null($value)) {
+				$loop = $attr == '*'
+					? $this->getNodeAttrs($node)
+					: array($attr);
+				foreach($loop as $a) {
+					$oldValue = $node->getAttribute($a);
+					$oldAttr = $node->hasAttribute($a);
+					// TODO raises an error when charset other than UTF-8
+					// while document's charset is also not UTF-8
+					@$node->setAttribute($a, $value);
+					$this->attrEvents($a, $oldAttr, $oldValue, $node);
+				}
+			} else if ($attr == '*') {
+				// jQuery difference
+				$return = array();
+				foreach($node->attributes as $n => $v)
+					$return[$n] = $v->value;
+				return $return;
+			} else
+				return $node->hasAttribute($attr)
+					? $node->getAttribute($attr)
+					: null;
+		}
+		return is_null($value)
+			? '' : $this;
+	}
+	/**
+	 * @access private
+	 */
+	protected function getNodeAttrs($node) {
+		$return = array();
+		foreach($node->attributes as $n => $o)
+			$return[] = $n;
+		return $return;
+	}
+	/**
+	 * Enter description here...
+	 *
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 * @todo check CDATA ???
+	 */
+	public function attrPHP($attr, $code) {
+		if (! is_null($code)) {
+			$value = '<'.'?php '.$code.' ?'.'>';
+			// TODO tempolary solution
+			// http://code.google.com/p/phpquery/issues/detail?id=17
+//			if (function_exists('mb_detect_encoding') && mb_detect_encoding($value) == 'ASCII')
+//				$value	= mb_convert_encoding($value, 'UTF-8', 'HTML-ENTITIES');
+		}
+		foreach($this->stack(1) as $node) {
+			if (! is_null($code)) {
+//				$attrNode = $this->DOM->createAttribute($attr);
+				$node->setAttribute($attr, $value);
+//				$attrNode->value = $value;
+//				$node->appendChild($attrNode);
+			} else if ( $attr == '*') {
+				// jQuery diff
+				$return = array();
+				foreach($node->attributes as $n => $v)
+					$return[$n] = $v->value;
+				return $return;
+			} else
+				return $node->getAttribute($attr);
+		}
+		return $this;
+	}
+	/**
+	 * Enter description here...
+	 *
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 */
+	public function removeAttr($attr) {
+		foreach($this->stack(1) as $node) {
+			$loop = $attr == '*'
+				? $this->getNodeAttrs($node)
+				: array($attr);
+			foreach($loop as $a) {
+				$oldValue = $node->getAttribute($a);
+				$node->removeAttribute($a);
+				$this->attrEvents($a, $oldValue, null, $node);
+			}
+		}
+		return $this;
+	}
+	/**
+	 * Return form element value.
+	 *
+	 * @return String Fields value.
+	 */
+	public function val($val = null) {
+		if (! isset($val)) {
+			if ($this->eq(0)->is('select')) {
+					$selected = $this->eq(0)->find('option[selected=selected]');
+					if ($selected->is('[value]'))
+						return $selected->attr('value');
+					else
+						return $selected->text();
+			} else if ($this->eq(0)->is('textarea'))
+					return $this->eq(0)->markup();
+				else
+					return $this->eq(0)->attr('value');
+		} else {
+			$_val = null;
+			foreach($this->stack(1) as $node) {
+				$node = pq($node, $this->getDocumentID());
+				if (is_array($val) && in_array($node->attr('type'), array('checkbox', 'radio'))) {
+					$isChecked = in_array($node->attr('value'), $val)
+							|| in_array($node->attr('name'), $val);
+					if ($isChecked)
+						$node->attr('checked', 'checked');
+					else
+						$node->removeAttr('checked');
+				} else if ($node->get(0)->tagName == 'select') {
+					if (! isset($_val)) {
+						$_val = array();
+						if (! is_array($val))
+							$_val = array((string)$val);
+						else
+							foreach($val as $v)
+								$_val[] = $v;
+					}
+					foreach($node['option']->stack(1) as $option) {
+						$option = pq($option, $this->getDocumentID());
+						$selected = false;
+						// XXX: workaround for string comparsion, see issue #96
+						// http://code.google.com/p/phpquery/issues/detail?id=96
+						$selected = is_null($option->attr('value'))
+							? in_array($option->markup(), $_val)
+							: in_array($option->attr('value'), $_val);
+//						$optionValue = $option->attr('value');
+//						$optionText = $option->text();
+//						$optionTextLenght = mb_strlen($optionText);
+//						foreach($_val as $v)
+//							if ($optionValue == $v)
+//								$selected = true;
+//							else if ($optionText == $v && $optionTextLenght == mb_strlen($v))
+//								$selected = true;
+						if ($selected)
+							$option->attr('selected', 'selected');
+						else
+							$option->removeAttr('selected');
+					}
+				} else if ($node->get(0)->tagName == 'textarea')
+					$node->markup($val);
+				else
+					$node->attr('value', $val);
+			}
+		}
+		return $this;
+	}
+	/**
+	 * Enter description here...
+	 *
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 */
+	public function andSelf() {
+		if ( $this->previous )
+			$this->elements = array_merge($this->elements, $this->previous->elements);
+		return $this;
+	}
+	/**
+	 * Enter description here...
+	 *
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 */
+	public function addClass( $className) {
+		if (! $className)
+			return $this;
+		foreach($this->stack(1) as $node) {
+			if (! $this->is(".$className", $node))
+				$node->setAttribute(
+					'class',
+					trim($node->getAttribute('class').' '.$className)
+				);
+		}
+		return $this;
+	}
+	/**
+	 * Enter description here...
+	 *
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 */
+	public function addClassPHP( $className) {
+		foreach($this->stack(1) as $node) {
+				$classes = $node->getAttribute('class');
+				$newValue = $classes
+					? $classes.' <'.'?php '.$className.' ?'.'>'
+					: '<'.'?php '.$className.' ?'.'>';
+				$node->setAttribute('class', $newValue);
+		}
+		return $this;
+	}
+	/**
+	 * Enter description here...
+	 *
+	 * @param	string	$className
+	 * @return	bool
+	 */
+	public function hasClass($className) {
+		foreach($this->stack(1) as $node) {
+			if ( $this->is(".$className", $node))
+				return true;
+		}
+		return false;
+	}
+	/**
+	 * Enter description here...
+	 *
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 */
+	public function removeClass($className) {
+		foreach($this->stack(1) as $node) {
+			$classes = explode( ' ', $node->getAttribute('class'));
+			if ( in_array($className, $classes)) {
+				$classes = array_diff($classes, array($className));
+				if ( $classes )
+					$node->setAttribute('class', implode(' ', $classes));
+				else
+					$node->removeAttribute('class');
+			}
+		}
+		return $this;
+	}
+	/**
+	 * Enter description here...
+	 *
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 */
+	public function toggleClass($className) {
+		foreach($this->stack(1) as $node) {
+			if ( $this->is( $node, '.'.$className ))
+				$this->removeClass($className);
+			else
+				$this->addClass($className);
+		}
+		return $this;
+	}
+	/**
+	 * Proper name without underscore (just ->empty()) also works.
+	 *
+	 * Removes all child nodes from the set of matched elements.
+	 *
+	 * Example:
+	 * pq("p")._empty()
+	 *
+	 * HTML:
+	 * <p>Hello, <span>Person</span> <a href="#">and person</a></p>
+	 *
+	 * Result:
+	 * [ <p></p> ]
+	 *
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 * @access private
+	 */
+	public function _empty() {
+		foreach($this->stack(1) as $node) {
+			// thx to 'dave at dgx dot cz'
+			$node->nodeValue = '';
+		}
+		return $this;
+	}
+	/**
+	 * Enter description here...
+	 *
+	 * @param array|string $callback Expects $node as first param, $index as second
+	 * @param array $scope External variables passed to callback. Use compact('varName1', 'varName2'...) and extract($scope)
+	 * @param array $arg1 Will ba passed as third and futher args to callback.
+	 * @param array $arg2 Will ba passed as fourth and futher args to callback, and so on...
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 */
+	public function each($callback, $param1 = null, $param2 = null, $param3 = null) {
+		$paramStructure = null;
+		if (func_num_args() > 1) {
+			$paramStructure = func_get_args();
+			$paramStructure = array_slice($paramStructure, 1);
+		}
+		foreach($this->elements as $v)
+			phpQuery::callbackRun($callback, array($v), $paramStructure);
+		return $this;
+	}
+	/**
+	 * Run callback on actual object.
+	 *
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 */
+	public function callback($callback, $param1 = null, $param2 = null, $param3 = null) {
+		$params = func_get_args();
+		$params[0] = $this;
+		phpQuery::callbackRun($callback, $params);
+		return $this;
+	}
+	/**
+	 * Enter description here...
+	 *
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 * @todo add $scope and $args as in each() ???
+	 */
+	public function map($callback, $param1 = null, $param2 = null, $param3 = null) {
+//		$stack = array();
+////		foreach($this->newInstance() as $node) {
+//		foreach($this->newInstance() as $node) {
+//			$result = call_user_func($callback, $node);
+//			if ($result)
+//				$stack[] = $result;
+//		}
+		$params = func_get_args();
+		array_unshift($params, $this->elements);
+		return $this->newInstance(
+			call_user_func_array(array('phpQuery', 'map'), $params)
+//			phpQuery::map($this->elements, $callback)
+		);
+	}
+	/**
+	 * Enter description here...
+	 * 
+	 * @param <type> $key
+	 * @param <type> $value
+	 */
+	public function data($key, $value = null) {
+		if (! isset($value)) {
+			// TODO? implement specific jQuery behavior od returning parent values
+			// is child which we look up doesn't exist
+			return phpQuery::data($this->get(0), $key, $value, $this->getDocumentID());
+		} else {
+			foreach($this as $node)
+				phpQuery::data($node, $key, $value, $this->getDocumentID());
+			return $this;
+		}
+	}
+	/**
+	 * Enter description here...
+	 * 
+	 * @param <type> $key
+	 */
+	public function removeData($key) {
+		foreach($this as $node)
+			phpQuery::removeData($node, $key, $this->getDocumentID());
+		return $this;
+	}
+	// INTERFACE IMPLEMENTATIONS
+
+	// ITERATOR INTERFACE
+	/**
+   * @access private
+	 */
+	public function rewind(){
+		$this->debug('iterating foreach');
+//		phpQuery::selectDocument($this->getDocumentID());
+		$this->elementsBackup = $this->elements;
+		$this->elementsInterator = $this->elements;
+		$this->valid = isset( $this->elements[0] )
+			? 1 : 0;
+// 		$this->elements = $this->valid
+// 			? array($this->elements[0])
+// 			: array();
+		$this->current = 0;
+	}
+	/**
+   * @access private
+	 */
+	public function current(){
+		return $this->elementsInterator[ $this->current ];
+	}
+	/**
+   * @access private
+	 */
+	public function key(){
+		return $this->current;
+	}
+	/**
+	 * Double-function method.
+	 *
+	 * First: main iterator interface method.
+	 * Second: Returning next sibling, alias for _next().
+	 *
+	 * Proper functionality is choosed automagicaly.
+	 *
+	 * @see phpQueryObject::_next()
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 */
+	public function next($cssSelector = null){
+//		if ($cssSelector || $this->valid)
+//			return $this->_next($cssSelector);
+		$this->valid = isset( $this->elementsInterator[ $this->current+1 ] )
+			? true
+			: false;
+		if (! $this->valid && $this->elementsInterator) {
+			$this->elementsInterator = null;
+		} else if ($this->valid) {
+			$this->current++;
+		} else {
+			return $this->_next($cssSelector);
+		}
+	}
+	/**
+   * @access private
+	 */
+	public function valid(){
+		return $this->valid;
+	}
+	// ITERATOR INTERFACE END
+	// ARRAYACCESS INTERFACE
+	/**
+   * @access private
+	 */
+	public function offsetExists($offset) {
+		return $this->find($offset)->size() > 0;
+	}
+	/**
+   * @access private
+	 */
+	public function offsetGet($offset) {
+		return $this->find($offset);
+	}
+	/**
+   * @access private
+	 */
+	public function offsetSet($offset, $value) {
+//		$this->find($offset)->replaceWith($value);
+		$this->find($offset)->html($value);
+	}
+	/**
+   * @access private
+	 */
+	public function offsetUnset($offset) {
+		// empty
+		throw new Exception("Can't do unset, use array interface only for calling queries and replacing HTML.");
+	}
+	// ARRAYACCESS INTERFACE END
+	/**
+	 * Returns node's XPath.
+	 *
+	 * @param unknown_type $oneNode
+	 * @return string
+	 * @TODO use native getNodePath is avaible
+	 * @access private
+	 */
+	protected function getNodeXpath($oneNode = null, $namespace = null) {
+		$return = array();
+		$loop = $oneNode
+			? array($oneNode)
+			: $this->elements;
+//		if ($namespace)
+//			$namespace .= ':';
+		foreach($loop as $node) {
+			if ($node instanceof DOMDOCUMENT) {
+				$return[] = '';
+				continue;
+			}
+			$xpath = array();
+			while(! ($node instanceof DOMDOCUMENT)) {
+				$i = 1;
+				$sibling = $node;
+				while($sibling->previousSibling) {
+					$sibling = $sibling->previousSibling;
+					$isElement = $sibling instanceof DOMELEMENT;
+					if ($isElement && $sibling->tagName == $node->tagName)
+						$i++;
+				}
+				$xpath[] = $this->isXML()
+					? "*[local-name()='{$node->tagName}'][{$i}]"
+					: "{$node->tagName}[{$i}]";
+				$node = $node->parentNode;
+			}
+			$xpath = join('/', array_reverse($xpath));
+			$return[] = '/'.$xpath;
+		}
+		return $oneNode
+			? $return[0]
+			: $return;
+	}
+	// HELPERS
+	public function whois($oneNode = null) {
+		$return = array();
+		$loop = $oneNode
+			? array( $oneNode )
+			: $this->elements;
+		foreach($loop as $node) {
+			if (isset($node->tagName)) {
+				$tag = in_array($node->tagName, array('php', 'js'))
+					? strtoupper($node->tagName)
+					: $node->tagName;
+				$return[] = $tag
+					.($node->getAttribute('id')
+						? '#'.$node->getAttribute('id'):'')
+					.($node->getAttribute('class')
+						? '.'.join('.', split(' ', $node->getAttribute('class'))):'')
+					.($node->getAttribute('name')
+						? '[name="'.$node->getAttribute('name').'"]':'')
+					.($node->getAttribute('value') && strpos($node->getAttribute('value'), '<'.'?php') === false
+						? '[value="'.substr(str_replace("\n", '', $node->getAttribute('value')), 0, 15).'"]':'')
+					.($node->getAttribute('value') && strpos($node->getAttribute('value'), '<'.'?php') !== false
+						? '[value=PHP]':'')
+					.($node->getAttribute('selected')
+						? '[selected]':'')
+					.($node->getAttribute('checked')
+						? '[checked]':'')
+				;
+			} else if ($node instanceof DOMTEXT) {
+				if (trim($node->textContent))
+					$return[] = 'Text:'.substr(str_replace("\n", ' ', $node->textContent), 0, 15);
+			} else {
+
+			}
+		}
+		return $oneNode && isset($return[0])
+			? $return[0]
+			: $return;
+	}
+	/**
+	 * Dump htmlOuter and preserve chain. Usefull for debugging.
+	 *
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 *
+	 */
+	public function dump() {
+		print 'DUMP #'.(phpQuery::$dumpCount++).' ';
+		$debug = phpQuery::$debug;
+		phpQuery::$debug = false;
+//		print __FILE__.':'.__LINE__."\n";
+		var_dump($this->htmlOuter());
+		return $this;
+	}
+	public function dumpWhois() {
+		print 'DUMP #'.(phpQuery::$dumpCount++).' ';
+		$debug = phpQuery::$debug;
+		phpQuery::$debug = false;
+//		print __FILE__.':'.__LINE__."\n";
+		var_dump('whois', $this->whois());
+		phpQuery::$debug = $debug;
+		return $this;
+	}
+	public function dumpLength() {
+		print 'DUMP #'.(phpQuery::$dumpCount++).' ';
+		$debug = phpQuery::$debug;
+		phpQuery::$debug = false;
+//		print __FILE__.':'.__LINE__."\n";
+		var_dump('length', $this->length());
+		phpQuery::$debug = $debug;
+		return $this;
+	}
+	public function dumpTree($html = true, $title = true) {
+		$output = $title
+			? 'DUMP #'.(phpQuery::$dumpCount++)." \n" : '';
+		$debug = phpQuery::$debug;
+		phpQuery::$debug = false;
+		foreach($this->stack() as $node)
+			$output .= $this->__dumpTree($node);
+		phpQuery::$debug = $debug;
+		print $html
+			? nl2br(str_replace(' ', '&nbsp;', $output))
+			: $output;
+		return $this;
+	}
+	private function __dumpTree($node, $intend = 0) {
+		$whois = $this->whois($node);
+		$return = '';
+		if ($whois)
+			$return .= str_repeat(' - ', $intend).$whois."\n";
+		if (isset($node->childNodes))
+			foreach($node->childNodes as $chNode)
+				$return .= $this->__dumpTree($chNode, $intend+1);
+		return $return;
+	}
+	/**
+	 * Dump htmlOuter and stop script execution. Usefull for debugging.
+	 *
+	 */
+	public function dumpDie() {
+		print __FILE__.':'.__LINE__;
+		var_dump($this->htmlOuter());
+		die();
+	}
+}
+
+
+// -- Multibyte Compatibility functions ---------------------------------------
+// http://svn.iphonewebdev.com/lace/lib/mb_compat.php
+
+/**
+ *  mb_internal_encoding()
+ *
+ *  Included for mbstring pseudo-compatability.
+ */
+if (!function_exists('mb_internal_encoding'))
+{
+	function mb_internal_encoding($enc) {return true; }
+}
+
+/**
+ *  mb_regex_encoding()
+ *
+ *  Included for mbstring pseudo-compatability.
+ */
+if (!function_exists('mb_regex_encoding'))
+{
+	function mb_regex_encoding($enc) {return true; }
+}
+
+/**
+ *  mb_strlen()
+ *
+ *  Included for mbstring pseudo-compatability.
+ */
+if (!function_exists('mb_strlen'))
+{
+	function mb_strlen($str)
+	{
+		return strlen($str);
+	}
+}
+
+/**
+ *  mb_strpos()
+ *
+ *  Included for mbstring pseudo-compatability.
+ */
+if (!function_exists('mb_strpos'))
+{
+	function mb_strpos($haystack, $needle, $offset=0)
+	{
+		return strpos($haystack, $needle, $offset);
+	}
+}
+/**
+ *  mb_stripos()
+ *
+ *  Included for mbstring pseudo-compatability.
+ */
+if (!function_exists('mb_stripos'))
+{
+	function mb_stripos($haystack, $needle, $offset=0)
+	{
+		return stripos($haystack, $needle, $offset);
+	}
+}
+
+/**
+ *  mb_substr()
+ *
+ *  Included for mbstring pseudo-compatability.
+ */
+if (!function_exists('mb_substr'))
+{
+	function mb_substr($str, $start, $length=0)
+	{
+		return substr($str, $start, $length);
+	}
+}
+
+/**
+ *  mb_substr_count()
+ *
+ *  Included for mbstring pseudo-compatability.
+ */
+if (!function_exists('mb_substr_count'))
+{
+	function mb_substr_count($haystack, $needle)
+	{
+		return substr_count($haystack, $needle);
+	}
+}
+
+
+/**
+ * Static namespace for phpQuery functions.
+ *
+ * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com>
+ * @package phpQuery
+ */
+abstract class phpQuery {
+	/**
+	 * XXX: Workaround for mbstring problems 
+	 * 
+	 * @var bool
+	 */
+	public static $mbstringSupport = true;
+	public static $debug = false;
+	public static $documents = array();
+	public static $defaultDocumentID = null;
+//	public static $defaultDoctype = 'html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"';
+	/**
+	 * Applies only to HTML.
+	 *
+	 * @var unknown_type
+	 */
+	public static $defaultDoctype = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+"http://www.w3.org/TR/html4/loose.dtd">';
+	public static $defaultCharset = 'UTF-8';
+	/**
+	 * Static namespace for plugins.
+	 *
+	 * @var object
+	 */
+	public static $plugins = array();
+	/**
+	 * List of loaded plugins.
+	 *
+	 * @var unknown_type
+	 */
+	public static $pluginsLoaded = array();
+	public static $pluginsMethods = array();
+	public static $pluginsStaticMethods = array();
+	public static $extendMethods = array();
+	/**
+	 * @TODO implement
+	 */
+	public static $extendStaticMethods = array();
+	/**
+	 * Hosts allowed for AJAX connections.
+	 * Dot '.' means $_SERVER['HTTP_HOST'] (if any).
+	 *
+	 * @var array
+	 */
+	public static $ajaxAllowedHosts = array(
+		'.'
+	);
+	/**
+	 * AJAX settings.
+	 *
+	 * @var array
+	 * XXX should it be static or not ?
+	 */
+	public static $ajaxSettings = array(
+		'url' => '',//TODO
+		'global' => true,
+		'type' => "GET",
+		'timeout' => null,
+		'contentType' => "application/x-www-form-urlencoded",
+		'processData' => true,
+//		'async' => true,
+		'data' => null,
+		'username' => null,
+		'password' => null,
+		'accepts' => array(
+			'xml' => "application/xml, text/xml",
+			'html' => "text/html",
+			'script' => "text/javascript, application/javascript",
+			'json' => "application/json, text/javascript",
+			'text' => "text/plain",
+			'_default' => "*/*"
+		)
+	);
+	public static $lastModified = null;
+	public static $active = 0;
+	public static $dumpCount = 0;
+	/**
+	 * Multi-purpose function.
+	 * Use pq() as shortcut.
+	 *
+	 * In below examples, $pq is any result of pq(); function.
+	 *
+	 * 1. Import markup into existing document (without any attaching):
+	 * - Import into selected document:
+	 *   pq('<div/>')				// DOESNT accept text nodes at beginning of input string !
+	 * - Import into document with ID from $pq->getDocumentID():
+	 *   pq('<div/>', $pq->getDocumentID())
+	 * - Import into same document as DOMNode belongs to:
+	 *   pq('<div/>', DOMNode)
+	 * - Import into document from phpQuery object:
+	 *   pq('<div/>', $pq)
+	 *
+	 * 2. Run query:
+	 * - Run query on last selected document:
+	 *   pq('div.myClass')
+	 * - Run query on document with ID from $pq->getDocumentID():
+	 *   pq('div.myClass', $pq->getDocumentID())
+	 * - Run query on same document as DOMNode belongs to and use node(s)as root for query:
+	 *   pq('div.myClass', DOMNode)
+	 * - Run query on document from phpQuery object
+	 *   and use object's stack as root node(s) for query:
+	 *   pq('div.myClass', $pq)
+	 *
+	 * @param string|DOMNode|DOMNodeList|array	$arg1	HTML markup, CSS Selector, DOMNode or array of DOMNodes
+	 * @param string|phpQueryObject|DOMNode	$context	DOM ID from $pq->getDocumentID(), phpQuery object (determines also query root) or DOMNode (determines also query root)
+	 *
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery|QueryTemplatesPhpQuery|false
+   * phpQuery object or false in case of error.
+	 */
+	public static function pq($arg1, $context = null) {
+		if ($arg1 instanceof DOMNODE && ! isset($context)) {
+			foreach(phpQuery::$documents as $documentWrapper) {
+				$compare = $arg1 instanceof DOMDocument
+					? $arg1 : $arg1->ownerDocument;
+				if ($documentWrapper->document->isSameNode($compare))
+					$context = $documentWrapper->id;
+			}
+		}
+		if (! $context) {
+			$domId = self::$defaultDocumentID;
+			if (! $domId)
+				throw new Exception("Can't use last created DOM, because there isn't any. Use phpQuery::newDocument() first.");
+//		} else if (is_object($context) && ($context instanceof PHPQUERY || is_subclass_of($context, 'phpQueryObject')))
+		} else if (is_object($context) && $context instanceof phpQueryObject)
+			$domId = $context->getDocumentID();
+		else if ($context instanceof DOMDOCUMENT) {
+			$domId = self::getDocumentID($context);
+			if (! $domId) {
+				//throw new Exception('Orphaned DOMDocument');
+				$domId = self::newDocument($context)->getDocumentID();
+			}
+		} else if ($context instanceof DOMNODE) {
+			$domId = self::getDocumentID($context);
+			if (! $domId) {
+				throw new Exception('Orphaned DOMNode');
+//				$domId = self::newDocument($context->ownerDocument);
+			}
+		} else
+			$domId = $context;
+		if ($arg1 instanceof phpQueryObject) {
+//		if (is_object($arg1) && (get_class($arg1) == 'phpQueryObject' || $arg1 instanceof PHPQUERY || is_subclass_of($arg1, 'phpQueryObject'))) {
+			/**
+			 * Return $arg1 or import $arg1 stack if document differs:
+			 * pq(pq('<div/>'))
+			 */
+			if ($arg1->getDocumentID() == $domId)
+				return $arg1;
+			$class = get_class($arg1);
+			// support inheritance by passing old object to overloaded constructor
+			$phpQuery = $class != 'phpQuery'
+				? new $class($arg1, $domId)
+				: new phpQueryObject($domId);
+			$phpQuery->elements = array();
+			foreach($arg1->elements as $node)
+				$phpQuery->elements[] = $phpQuery->document->importNode($node, true);
+			return $phpQuery;
+		} else if ($arg1 instanceof DOMNODE || (is_array($arg1) && isset($arg1[0]) && $arg1[0] instanceof DOMNODE)) {
+			/*
+			 * Wrap DOM nodes with phpQuery object, import into document when needed:
+			 * pq(array($domNode1, $domNode2))
+			 */
+			$phpQuery = new phpQueryObject($domId);
+			if (!($arg1 instanceof DOMNODELIST) && ! is_array($arg1))
+				$arg1 = array($arg1);
+			$phpQuery->elements = array();
+			foreach($arg1 as $node) {
+				$sameDocument = $node->ownerDocument instanceof DOMDOCUMENT
+					&& ! $node->ownerDocument->isSameNode($phpQuery->document);
+				$phpQuery->elements[] = $sameDocument
+					? $phpQuery->document->importNode($node, true)
+					: $node;
+			}
+			return $phpQuery;
+		} else if (self::isMarkup($arg1)) {
+			/**
+			 * Import HTML:
+			 * pq('<div/>')
+			 */
+			$phpQuery = new phpQueryObject($domId);
+			return $phpQuery->newInstance(
+				$phpQuery->documentWrapper->import($arg1)
+			);
+		} else {
+			/**
+			 * Run CSS query:
+			 * pq('div.myClass')
+			 */
+			$phpQuery = new phpQueryObject($domId);
+//			if ($context && ($context instanceof PHPQUERY || is_subclass_of($context, 'phpQueryObject')))
+			if ($context && $context instanceof phpQueryObject)
+				$phpQuery->elements = $context->elements;
+			else if ($context && $context instanceof DOMNODELIST) {
+				$phpQuery->elements = array();
+				foreach($context as $node)
+					$phpQuery->elements[] = $node;
+			} else if ($context && $context instanceof DOMNODE)
+				$phpQuery->elements = array($context);
+			return $phpQuery->find($arg1);
+		}
+	}
+	/**
+	 * Sets default document to $id. Document has to be loaded prior
+	 * to using this method.
+	 * $id can be retrived via getDocumentID() or getDocumentIDRef().
+	 *
+	 * @param unknown_type $id
+	 */
+	public static function selectDocument($id) {
+		$id = self::getDocumentID($id);
+		self::debug("Selecting document '$id' as default one");
+		self::$defaultDocumentID = self::getDocumentID($id);
+	}
+	/**
+	 * Returns document with id $id or last used as phpQueryObject.
+	 * $id can be retrived via getDocumentID() or getDocumentIDRef().
+	 * Chainable.
+	 *
+	 * @see phpQuery::selectDocument()
+	 * @param unknown_type $id
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 */
+	public static function getDocument($id = null) {
+		if ($id)
+			phpQuery::selectDocument($id);
+		else
+			$id = phpQuery::$defaultDocumentID;
+		return new phpQueryObject($id);
+	}
+	/**
+	 * Creates new document from markup.
+	 * Chainable.
+	 *
+	 * @param unknown_type $markup
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 */
+	public static function newDocument($markup = null, $contentType = null) {
+		if (! $markup)
+			$markup = '';
+		$documentID = phpQuery::createDocumentWrapper($markup, $contentType);
+		return new phpQueryObject($documentID);
+	}
+	/**
+	 * Creates new document from markup.
+	 * Chainable.
+	 *
+	 * @param unknown_type $markup
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 */
+	public static function newDocumentHTML($markup = null, $charset = null) {
+		$contentType = $charset
+			? ";charset=$charset"
+			: '';
+		return self::newDocument($markup, "text/html{$contentType}");
+	}
+	/**
+	 * Creates new document from markup.
+	 * Chainable.
+	 *
+	 * @param unknown_type $markup
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 */
+	public static function newDocumentXML($markup = null, $charset = null) {
+		$contentType = $charset
+			? ";charset=$charset"
+			: '';
+		return self::newDocument($markup, "text/xml{$contentType}");
+	}
+	/**
+	 * Creates new document from markup.
+	 * Chainable.
+	 *
+	 * @param unknown_type $markup
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 */
+	public static function newDocumentXHTML($markup = null, $charset = null) {
+		$contentType = $charset
+			? ";charset=$charset"
+			: '';
+		return self::newDocument($markup, "application/xhtml+xml{$contentType}");
+	}
+	/**
+	 * Creates new document from markup.
+	 * Chainable.
+	 *
+	 * @param unknown_type $markup
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 */
+	public static function newDocumentPHP($markup = null, $contentType = "text/html") {
+		// TODO pass charset to phpToMarkup if possible (use DOMDocumentWrapper function)
+		$markup = phpQuery::phpToMarkup($markup, self::$defaultCharset);
+		return self::newDocument($markup, $contentType);
+	}
+	public static function phpToMarkup($php, $charset = 'utf-8') {
+		$regexes = array(
+			'@(<(?!\\?)(?:[^>]|\\?>)+\\w+\\s*=\\s*)(\')([^\']*)<'.'?php?(.*?)(?:\\?>)([^\']*)\'@s',
+			'@(<(?!\\?)(?:[^>]|\\?>)+\\w+\\s*=\\s*)(")([^"]*)<'.'?php?(.*?)(?:\\?>)([^"]*)"@s',
+		);
+		foreach($regexes as $regex)
+			while (preg_match($regex, $php, $matches)) {
+				$php = preg_replace_callback(
+					$regex,
+//					create_function('$m, $charset = "'.$charset.'"',
+//						'return $m[1].$m[2]
+//							.htmlspecialchars("<"."?php".$m[4]."?".">", ENT_QUOTES|ENT_NOQUOTES, $charset)
+//							.$m[5].$m[2];'
+//					),
+					array('phpQuery', '_phpToMarkupCallback'),
+					$php
+				);
+			}
+		$regex = '@(^|>[^<]*)+?(<\?php(.*?)(\?>))@s';
+//preg_match_all($regex, $php, $matches);
+//var_dump($matches);
+		$php = preg_replace($regex, '\\1<php><!-- \\3 --></php>', $php);
+		return $php;
+	}
+	public static function _phpToMarkupCallback($php, $charset = 'utf-8') {
+		return $m[1].$m[2]
+			.htmlspecialchars("<"."?php".$m[4]."?".">", ENT_QUOTES|ENT_NOQUOTES, $charset)
+			.$m[5].$m[2];
+	}
+	public static function _markupToPHPCallback($m) {
+		return "<"."?php ".htmlspecialchars_decode($m[1])." ?".">";
+	}
+	/**
+	 * Converts document markup containing PHP code generated by phpQuery::php()
+	 * into valid (executable) PHP code syntax.
+	 *
+	 * @param string|phpQueryObject $content
+	 * @return string PHP code.
+	 */
+	public static function markupToPHP($content) {
+		if ($content instanceof phpQueryObject)
+			$content = $content->markupOuter();
+		/* <php>...</php> to <?php...? > */
+		$content = preg_replace_callback(
+			'@<php>\s*<!--(.*?)-->\s*</php>@s',
+//			create_function('$m',
+//				'return "<'.'?php ".htmlspecialchars_decode($m[1])." ?'.'>";'
+//			),
+			array('phpQuery', '_markupToPHPCallback'),
+			$content
+		);
+		/* <node attr='< ?php ? >'> extra space added to save highlighters */
+		$regexes = array(
+			'@(<(?!\\?)(?:[^>]|\\?>)+\\w+\\s*=\\s*)(\')([^\']*)(?:&lt;|%3C)\\?(?:php)?(.*?)(?:\\?(?:&gt;|%3E))([^\']*)\'@s',
+			'@(<(?!\\?)(?:[^>]|\\?>)+\\w+\\s*=\\s*)(")([^"]*)(?:&lt;|%3C)\\?(?:php)?(.*?)(?:\\?(?:&gt;|%3E))([^"]*)"@s',
+		);
+		foreach($regexes as $regex)
+			while (preg_match($regex, $content))
+				$content = preg_replace_callback(
+					$regex,
+					create_function('$m',
+						'return $m[1].$m[2].$m[3]."<?php "
+							.str_replace(
+								array("%20", "%3E", "%09", "&#10;", "&#9;", "%7B", "%24", "%7D", "%22", "%5B", "%5D"),
+								array(" ", ">", "	", "\n", "	", "{", "$", "}", \'"\', "[", "]"),
+								htmlspecialchars_decode($m[4])
+							)
+							." ?>".$m[5].$m[2];'
+					),
+					$content
+				);
+		return $content;
+	}
+	/**
+	 * Creates new document from file $file.
+	 * Chainable.
+	 *
+	 * @param string $file URLs allowed. See File wrapper page at php.net for more supported sources.
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 */
+	public static function newDocumentFile($file, $contentType = null) {
+		$documentID = self::createDocumentWrapper(
+			file_get_contents($file), $contentType
+		);
+		return new phpQueryObject($documentID);
+	}
+	/**
+	 * Creates new document from markup.
+	 * Chainable.
+	 *
+	 * @param unknown_type $markup
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 */
+	public static function newDocumentFileHTML($file, $charset = null) {
+		$contentType = $charset
+			? ";charset=$charset"
+			: '';
+		return self::newDocumentFile($file, "text/html{$contentType}");
+	}
+	/**
+	 * Creates new document from markup.
+	 * Chainable.
+	 *
+	 * @param unknown_type $markup
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 */
+	public static function newDocumentFileXML($file, $charset = null) {
+		$contentType = $charset
+			? ";charset=$charset"
+			: '';
+		return self::newDocumentFile($file, "text/xml{$contentType}");
+	}
+	/**
+	 * Creates new document from markup.
+	 * Chainable.
+	 *
+	 * @param unknown_type $markup
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 */
+	public static function newDocumentFileXHTML($file, $charset = null) {
+		$contentType = $charset
+			? ";charset=$charset"
+			: '';
+		return self::newDocumentFile($file, "application/xhtml+xml{$contentType}");
+	}
+	/**
+	 * Creates new document from markup.
+	 * Chainable.
+	 *
+	 * @param unknown_type $markup
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 */
+	public static function newDocumentFilePHP($file, $contentType = null) {
+		return self::newDocumentPHP(file_get_contents($file), $contentType);
+	}
+	/**
+	 * Reuses existing DOMDocument object.
+	 * Chainable.
+	 *
+	 * @param $document DOMDocument
+	 * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+	 * @TODO support DOMDocument
+	 */
+	public static function loadDocument($document) {
+		// TODO
+		die('TODO loadDocument');
+	}
+	/**
+	 * Enter description here...
+	 *
+	 * @param unknown_type $html
+	 * @param unknown_type $domId
+	 * @return unknown New DOM ID
+	 * @todo support PHP tags in input
+	 * @todo support passing DOMDocument object from self::loadDocument
+	 */
+	protected static function createDocumentWrapper($html, $contentType = null, $documentID = null) {
+		if (function_exists('domxml_open_mem'))
+			throw new Exception("Old PHP4 DOM XML extension detected. phpQuery won't work until this extension is enabled.");
+//		$id = $documentID
+//			? $documentID
+//			: md5(microtime());
+		$document = null;
+		if ($html instanceof DOMDOCUMENT) {
+			if (self::getDocumentID($html)) {
+				// document already exists in phpQuery::$documents, make a copy
+				$document = clone $html;
+			} else {
+				// new document, add it to phpQuery::$documents
+				$wrapper = new DOMDocumentWrapper($html, $contentType, $documentID);
+			}
+		} else {
+			$wrapper = new DOMDocumentWrapper($html, $contentType, $documentID);
+		}
+//		$wrapper->id = $id;
+		// bind document
+		phpQuery::$documents[$wrapper->id] = $wrapper;
+		// remember last loaded document
+		phpQuery::selectDocument($wrapper->id);
+		return $wrapper->id;
+	}
+	/**
+	 * Extend class namespace.
+	 *
+	 * @param string|array $target
+	 * @param array $source
+	 * @TODO support string $source
+	 * @return unknown_type
+	 */
+	public static function extend($target, $source) {
+		switch($target) {
+			case 'phpQueryObject':
+				$targetRef = &self::$extendMethods;
+				$targetRef2 = &self::$pluginsMethods;
+				break;
+			case 'phpQuery':
+				$targetRef = &self::$extendStaticMethods;
+				$targetRef2 = &self::$pluginsStaticMethods;
+				break;
+			default:
+				throw new Exception("Unsupported \$target type");
+		}
+		if (is_string($source))
+			$source = array($source => $source);
+		foreach($source as $method => $callback) {
+			if (isset($targetRef[$method])) {
+//				throw new Exception
+				self::debug("Duplicate method '{$method}', can\'t extend '{$target}'");
+				continue;
+			}
+			if (isset($targetRef2[$method])) {
+//				throw new Exception
+				self::debug("Duplicate method '{$method}' from plugin '{$targetRef2[$method]}',"
+					." can\'t extend '{$target}'");
+				continue;
+			}
+			$targetRef[$method] = $callback;
+		}
+		return true;
+	}
+	/**
+	 * Extend phpQuery with $class from $file.
+	 *
+	 * @param string $class Extending class name. Real class name can be prepended phpQuery_.
+	 * @param string $file Filename to include. Defaults to "{$class}.php".
+	 */
+	public static function plugin($class, $file = null) {
+		// TODO $class checked agains phpQuery_$class
+//		if (strpos($class, 'phpQuery') === 0)
+//			$class = substr($class, 8);
+		if (in_array($class, self::$pluginsLoaded))
+			return true;
+		if (! $file)
+			$file = $class.'.php';
+		$objectClassExists = class_exists('phpQueryObjectPlugin_'.$class);
+		$staticClassExists = class_exists('phpQueryPlugin_'.$class);
+		if (! $objectClassExists && ! $staticClassExists)
+			require_once($file);
+		self::$pluginsLoaded[] = $class;
+		// static methods
+		if (class_exists('phpQueryPlugin_'.$class)) {
+			$realClass = 'phpQueryPlugin_'.$class;
+			$vars = get_class_vars($realClass);
+			$loop = isset($vars['phpQueryMethods'])
+				&& ! is_null($vars['phpQueryMethods'])
+				? $vars['phpQueryMethods']
+				: get_class_methods($realClass);
+			foreach($loop as $method) {
+				if ($method == '__initialize')
+					continue;
+				if (! is_callable(array($realClass, $method)))
+					continue;
+				if (isset(self::$pluginsStaticMethods[$method])) {
+					throw new Exception("Duplicate method '{$method}' from plugin '{$c}' conflicts with same method from plugin '".self::$pluginsStaticMethods[$method]."'");
+					return;
+				}
+				self::$pluginsStaticMethods[$method] = $class;
+			}
+			if (method_exists($realClass, '__initialize'))
+				call_user_func_array(array($realClass, '__initialize'), array());
+		}
+		// object methods
+		if (class_exists('phpQueryObjectPlugin_'.$class)) {
+			$realClass = 'phpQueryObjectPlugin_'.$class;
+			$vars = get_class_vars($realClass);
+			$loop = isset($vars['phpQueryMethods'])
+				&& ! is_null($vars['phpQueryMethods'])
+				? $vars['phpQueryMethods']
+				: get_class_methods($realClass);
+			foreach($loop as $method) {
+				if (! is_callable(array($realClass, $method)))
+					continue;
+				if (isset(self::$pluginsMethods[$method])) {
+					throw new Exception("Duplicate method '{$method}' from plugin '{$c}' conflicts with same method from plugin '".self::$pluginsMethods[$method]."'");
+					continue;
+				}
+				self::$pluginsMethods[$method] = $class;
+			}
+		}
+		return true;
+	}
+	/**
+	 * Unloades all or specified document from memory.
+	 *
+	 * @param mixed $documentID @see phpQuery::getDocumentID() for supported types.
+	 */
+	public static function unloadDocuments($id = null) {
+		if (isset($id)) {
+			if ($id = self::getDocumentID($id))
+				unset(phpQuery::$documents[$id]);
+		} else {
+			foreach(phpQuery::$documents as $k => $v) {
+				unset(phpQuery::$documents[$k]);
+			}
+		}
+	}
+	/**
+	 * Parses phpQuery object or HTML result against PHP tags and makes them active.
+	 *
+	 * @param phpQuery|string $content
+	 * @deprecated
+	 * @return string
+	 */
+	public static function unsafePHPTags($content) {
+		return self::markupToPHP($content);
+	}
+	public static function DOMNodeListToArray($DOMNodeList) {
+		$array = array();
+		if (! $DOMNodeList)
+			return $array;
+		foreach($DOMNodeList as $node)
+			$array[] = $node;
+		return $array;
+	}
+	/**
+	 * Checks if $input is HTML string, which has to start with '<'.
+	 *
+	 * @deprecated
+	 * @param String $input
+	 * @return Bool
+	 * @todo still used ?
+	 */
+	public static function isMarkup($input) {
+		return ! is_array($input) && substr(trim($input), 0, 1) == '<';
+	}
+	public static function debug($text) {
+		if (self::$debug)
+			print var_dump($text);
+	}
+	/**
+	 * Make an AJAX request.
+	 *
+	 * @param array See $options http://docs.jquery.com/Ajax/jQuery.ajax#toptions
+	 * Additional options are:
+	 * 'document' - document for global events, @see phpQuery::getDocumentID()
+	 * 'referer' - implemented
+	 * 'requested_with' - TODO; not implemented (X-Requested-With)
+	 * @return Zend_Http_Client
+	 * @link http://docs.jquery.com/Ajax/jQuery.ajax
+	 *
+	 * @TODO $options['cache']
+	 * @TODO $options['processData']
+	 * @TODO $options['xhr']
+	 * @TODO $options['data'] as string
+	 * @TODO XHR interface
+	 */
+	public static function ajax($options = array(), $xhr = null) {
+		$options = array_merge(
+			self::$ajaxSettings, $options
+		);
+		$documentID = isset($options['document'])
+			? self::getDocumentID($options['document'])
+			: null;
+		if ($xhr) {
+			// reuse existing XHR object, but clean it up
+			$client = $xhr;
+//			$client->setParameterPost(null);
+//			$client->setParameterGet(null);
+			$client->setAuth(false);
+			$client->setHeaders("If-Modified-Since", null);
+			$client->setHeaders("Referer", null);
+			$client->resetParameters();
+		} else {
+			// create new XHR object
+			require_once('Zend/Http/Client.php');
+			$client = new Zend_Http_Client();
+			$client->setCookieJar();
+		}
+		if (isset($options['timeout']))
+			$client->setConfig(array(
+				'timeout'      => $options['timeout'],
+			));
+//			'maxredirects' => 0,
+		foreach(self::$ajaxAllowedHosts as $k => $host)
+			if ($host == '.' && isset($_SERVER['HTTP_HOST']))
+				self::$ajaxAllowedHosts[$k] = $_SERVER['HTTP_HOST'];
+		$host = parse_url($options['url'], PHP_URL_HOST);
+		if (! in_array($host, self::$ajaxAllowedHosts)) {
+			throw new Exception("Request not permitted, host '$host' not present in "
+				."phpQuery::\$ajaxAllowedHosts");
+		}
+		// JSONP
+		$jsre = "/=\\?(&|$)/";
+		if (isset($options['dataType']) && $options['dataType'] == 'jsonp') {
+			$jsonpCallbackParam = $options['jsonp']
+				? $options['jsonp'] : 'callback';
+			if (strtolower($options['type']) == 'get') {
+				if (! preg_match($jsre, $options['url'])) {
+					$sep = strpos($options['url'], '?')
+						? '&' : '?';
+					$options['url'] .= "$sep$jsonpCallbackParam=?";
+				}
+			} else if ($options['data']) {
+				$jsonp = false;
+				foreach($options['data'] as $n => $v) {
+					if ($v == '?')
+						$jsonp = true;
+				}
+				if (! $jsonp) {
+					$options['data'][$jsonpCallbackParam] = '?';
+				}
+			}
+			$options['dataType'] = 'json';
+		}
+		if (isset($options['dataType']) && $options['dataType'] == 'json') {
+			$jsonpCallback = 'json_'.md5(microtime());
+			$jsonpData = $jsonpUrl = false;
+			if ($options['data']) {
+				foreach($options['data'] as $n => $v) {
+					if ($v == '?')
+						$jsonpData = $n;
+				}
+			}
+			if (preg_match($jsre, $options['url']))
+				$jsonpUrl = true;
+			if ($jsonpData !== false || $jsonpUrl) {
+				// remember callback name for httpData()
+				$options['_jsonp'] = $jsonpCallback;
+				if ($jsonpData !== false)
+					$options['data'][$jsonpData] = $jsonpCallback;
+				if ($jsonpUrl)
+					$options['url'] = preg_replace($jsre, "=$jsonpCallback\\1", $options['url']);
+			}
+		}
+		$client->setUri($options['url']);
+		$client->setMethod(strtoupper($options['type']));
+		if (isset($options['referer']) && $options['referer'])
+			$client->setHeaders('Referer', $options['referer']);
+		$client->setHeaders(array(
+//			'content-type' => $options['contentType'],
+			'User-Agent' => 'Mozilla/5.0 (X11; U; Linux x86; en-US; rv:1.9.0.5) Gecko'
+				 .'/2008122010 Firefox/3.0.5',
+	 		// TODO custom charset
+			'Accept-Charset' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.7',
+// 	 		'Connection' => 'keep-alive',
+// 			'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
+	 		'Accept-Language' => 'en-us,en;q=0.5',
+		));
+		if ($options['username'])
+			$client->setAuth($options['username'], $options['password']);
+		if (isset($options['ifModified']) && $options['ifModified'])
+			$client->setHeaders("If-Modified-Since",
+				self::$lastModified
+					? self::$lastModified
+					: "Thu, 01 Jan 1970 00:00:00 GMT"
+			);
+		$client->setHeaders("Accept",
+			isset($options['dataType'])
+			&& isset(self::$ajaxSettings['accepts'][ $options['dataType'] ])
+				? self::$ajaxSettings['accepts'][ $options['dataType'] ].", */*"
+				: self::$ajaxSettings['accepts']['_default']
+		);
+		// TODO $options['processData']
+		if ($options['data'] instanceof phpQueryObject) {
+			$serialized = $options['data']->serializeArray($options['data']);
+			$options['data'] = array();
+			foreach($serialized as $r)
+				$options['data'][ $r['name'] ] = $r['value'];
+		}
+		if (strtolower($options['type']) == 'get') {
+			$client->setParameterGet($options['data']);
+		} else if (strtolower($options['type']) == 'post') {
+			$client->setEncType($options['contentType']);
+			$client->setParameterPost($options['data']);
+		}
+		if (self::$active == 0 && $options['global'])
+			phpQueryEvents::trigger($documentID, 'ajaxStart');
+		self::$active++;
+		// beforeSend callback
+		if (isset($options['beforeSend']) && $options['beforeSend'])
+			phpQuery::callbackRun($options['beforeSend'], array($client));
+		// ajaxSend event
+		if ($options['global'])
+			phpQueryEvents::trigger($documentID, 'ajaxSend', array($client, $options));
+		if (phpQuery::$debug) {
+			self::debug("{$options['type']}: {$options['url']}\n");
+			self::debug("Options: <pre>".var_export($options, true)."</pre>\n");
+//			if ($client->getCookieJar())
+//				self::debug("Cookies: <pre>".var_export($client->getCookieJar()->getMatchingCookies($options['url']), true)."</pre>\n");
+		}
+		// request
+		$response = $client->request();
+		if (phpQuery::$debug) {
+			self::debug('Status: '.$response->getStatus().' / '.$response->getMessage());
+			self::debug($client->getLastRequest());
+			self::debug($response->getHeaders());
+		}
+		if ($response->isSuccessful()) {
+			// XXX tempolary
+			self::$lastModified = $response->getHeader('Last-Modified');
+			$data = self::httpData($response->getBody(), $options['dataType'], $options);
+			if (isset($options['success']) && $options['success'])
+				phpQuery::callbackRun($options['success'], array($data, $response->getStatus(), $options));
+			if ($options['global'])
+				phpQueryEvents::trigger($documentID, 'ajaxSuccess', array($client, $options));
+		} else {
+			if (isset($options['error']) && $options['error'])
+				phpQuery::callbackRun($options['error'], array($client, $response->getStatus(), $response->getMessage()));
+			if ($options['global'])
+				phpQueryEvents::trigger($documentID, 'ajaxError', array($client, /*$response->getStatus(),*/$response->getMessage(), $options));
+		}
+		if (isset($options['complete']) && $options['complete'])
+			phpQuery::callbackRun($options['complete'], array($client, $response->getStatus()));
+		if ($options['global'])
+			phpQueryEvents::trigger($documentID, 'ajaxComplete', array($client, $options));
+		if ($options['global'] && ! --self::$active)
+			phpQueryEvents::trigger($documentID, 'ajaxStop');
+		return $client;
+//		if (is_null($domId))
+//			$domId = self::$defaultDocumentID ? self::$defaultDocumentID : false;
+//		return new phpQueryAjaxResponse($response, $domId);
+	}
+	protected static function httpData($data, $type, $options) {
+		if (isset($options['dataFilter']) && $options['dataFilter'])
+			$data = self::callbackRun($options['dataFilter'], array($data, $type));
+		if (is_string($data)) {
+			if ($type == "json") {
+				if (isset($options['_jsonp']) && $options['_jsonp']) {
+					$data = preg_replace('/^\s*\w+\((.*)\)\s*$/s', '$1', $data);
+				}
+				$data = self::parseJSON($data);
+			}
+		}
+		return $data;
+	}
+	/**
+	 * Enter description here...
+	 *
+	 * @param array|phpQuery $data
+	 *
+	 */
+	public static function param($data) {
+		return http_build_query($data, null, '&');
+	}
+	public static function get($url, $data = null, $callback = null, $type = null) {
+		if (!is_array($data)) {
+			$callback = $data;
+			$data = null;
+		}
+		// TODO some array_values on this shit
+		return phpQuery::ajax(array(
+			'type' => 'GET',
+			'url' => $url,
+			'data' => $data,
+			'success' => $callback,
+			'dataType' => $type,
+		));
+	}
+	public static function post($url, $data = null, $callback = null, $type = null) {
+		if (!is_array($data)) {
+			$callback = $data;
+			$data = null;
+		}
+		return phpQuery::ajax(array(
+			'type' => 'POST',
+			'url' => $url,
+			'data' => $data,
+			'success' => $callback,
+			'dataType' => $type,
+		));
+	}
+	public static function getJSON($url, $data = null, $callback = null) {
+		if (!is_array($data)) {
+			$callback = $data;
+			$data = null;
+		}
+		// TODO some array_values on this shit
+		return phpQuery::ajax(array(
+			'type' => 'GET',
+			'url' => $url,
+			'data' => $data,
+			'success' => $callback,
+			'dataType' => 'json',
+		));
+	}
+	public static function ajaxSetup($options) {
+		self::$ajaxSettings = array_merge(
+			self::$ajaxSettings,
+			$options
+		);
+	}
+	public static function ajaxAllowHost($host1, $host2 = null, $host3 = null) {
+		$loop = is_array($host1)
+			? $host1
+			: func_get_args();
+		foreach($loop as $host) {
+			if ($host && ! in_array($host, phpQuery::$ajaxAllowedHosts)) {
+				phpQuery::$ajaxAllowedHosts[] = $host;
+			}
+		}
+	}
+	public static function ajaxAllowURL($url1, $url2 = null, $url3 = null) {
+		$loop = is_array($url1)
+			? $url1
+			: func_get_args();
+		foreach($loop as $url)
+			phpQuery::ajaxAllowHost(parse_url($url, PHP_URL_HOST));
+	}
+	/**
+	 * Returns JSON representation of $data.
+	 *
+	 * @static
+	 * @param mixed $data
+	 * @return string
+	 */
+	public static function toJSON($data) {
+		if (function_exists('json_encode'))
+			return json_encode($data);
+		require_once('Zend/Json/Encoder.php');
+		return Zend_Json_Encoder::encode($data);
+	}
+	/**
+	 * Parses JSON into proper PHP type.
+	 *
+	 * @static
+	 * @param string $json
+	 * @return mixed
+	 */
+	public static function parseJSON($json) {
+		if (function_exists('json_decode')) {
+			$return = json_decode(trim($json), true);
+			// json_decode and UTF8 issues
+			if (isset($return))
+				return $return;
+		}
+		require_once('Zend/Json/Decoder.php');
+		return Zend_Json_Decoder::decode($json);
+	}
+	/**
+	 * Returns source's document ID.
+	 *
+	 * @param $source DOMNode|phpQueryObject
+	 * @return string
+	 */
+	public static function getDocumentID($source) {
+		if ($source instanceof DOMDOCUMENT) {
+			foreach(phpQuery::$documents as $id => $document) {
+				if ($source->isSameNode($document->document))
+					return $id;
+			}
+		} else if ($source instanceof DOMNODE) {
+			foreach(phpQuery::$documents as $id => $document) {
+				if ($source->ownerDocument->isSameNode($document->document))
+					return $id;
+			}
+		} else if ($source instanceof phpQueryObject)
+			return $source->getDocumentID();
+		else if (is_string($source) && isset(phpQuery::$documents[$source]))
+			return $source;
+	}
+	/**
+	 * Get DOMDocument object related to $source.
+	 * Returns null if such document doesn't exist.
+	 *
+	 * @param $source DOMNode|phpQueryObject|string
+	 * @return string
+	 */
+	public static function getDOMDocument($source) {
+		if ($source instanceof DOMDOCUMENT)
+			return $source;
+		$source = self::getDocumentID($source);
+		return $source
+			? self::$documents[$id]['document']
+			: null;
+	}
+
+	// UTILITIES
+	// http://docs.jquery.com/Utilities
+
+	/**
+	 *
+	 * @return unknown_type
+	 * @link http://docs.jquery.com/Utilities/jQuery.makeArray
+	 */
+	public static function makeArray($obj) {
+		$array = array();
+		if (is_object($object) && $object instanceof DOMNODELIST) {
+			foreach($object as $value)
+				$array[] = $value;
+		} else if (is_object($object) && ! ($object instanceof Iterator)) {
+			foreach(get_object_vars($object) as $name => $value)
+				$array[0][$name] = $value;
+		} else {
+			foreach($object as $name => $value)
+				$array[0][$name] = $value;
+		}
+		return $array;
+	}
+	public static function inArray($value, $array) {
+		return in_array($value, $array);
+	}
+	/**
+	 *
+	 * @param $object
+	 * @param $callback
+	 * @return unknown_type
+	 * @link http://docs.jquery.com/Utilities/jQuery.each
+	 */
+	public static function each($object, $callback, $param1 = null, $param2 = null, $param3 = null) {
+		$paramStructure = null;
+		if (func_num_args() > 2) {
+			$paramStructure = func_get_args();
+			$paramStructure = array_slice($paramStructure, 2);
+		}
+		if (is_object($object) && ! ($object instanceof Iterator)) {
+			foreach(get_object_vars($object) as $name => $value)
+				phpQuery::callbackRun($callback, array($name, $value), $paramStructure);
+		} else {
+			foreach($object as $name => $value)
+				phpQuery::callbackRun($callback, array($name, $value), $paramStructure);
+		}
+	}
+	/**
+	 *
+	 * @link http://docs.jquery.com/Utilities/jQuery.map
+	 */
+	public static function map($array, $callback, $param1 = null, $param2 = null, $param3 = null) {
+		$result = array();
+		$paramStructure = null;
+		if (func_num_args() > 2) {
+			$paramStructure = func_get_args();
+			$paramStructure = array_slice($paramStructure, 2);
+		}
+		foreach($array as $v) {
+			$vv = phpQuery::callbackRun($callback, array($v), $paramStructure);
+//			$callbackArgs = $args;
+//			foreach($args as $i => $arg) {
+//				$callbackArgs[$i] = $arg instanceof CallbackParam
+//					? $v
+//					: $arg;
+//			}
+//			$vv = call_user_func_array($callback, $callbackArgs);
+			if (is_array($vv))  {
+				foreach($vv as $vvv)
+					$result[] = $vvv;
+			} else if ($vv !== null) {
+				$result[] = $vv;
+			}
+		}
+		return $result;
+	}
+	/**
+	 *
+	 * @param $callback Callback
+	 * @param $params
+	 * @param $paramStructure
+	 * @return unknown_type
+	 */
+	public static function callbackRun($callback, $params = array(), $paramStructure = null) {
+		if (! $callback)
+			return;
+		if ($callback instanceof CallbackParameterToReference) {
+			// TODO support ParamStructure to select which $param push to reference
+			if (isset($params[0]))
+				$callback->callback = $params[0];
+			return true;
+		}
+		if ($callback instanceof Callback) {
+			$paramStructure = $callback->params;
+			$callback = $callback->callback;
+		}
+		if (! $paramStructure)
+			return call_user_func_array($callback, $params);
+		$p = 0;
+		foreach($paramStructure as $i => $v) {
+			$paramStructure[$i] = $v instanceof CallbackParam
+				? $params[$p++]
+				: $v;
+		}
+		return call_user_func_array($callback, $paramStructure);
+	}
+	/**
+	 * Merge 2 phpQuery objects.
+	 * @param array $one
+	 * @param array $two
+	 * @protected
+	 * @todo node lists, phpQueryObject
+	 */
+	public static function merge($one, $two) {
+		$elements = $one->elements;
+		foreach($two->elements as $node) {
+			$exists = false;
+			foreach($elements as $node2) {
+				if ($node2->isSameNode($node))
+					$exists = true;
+			}
+			if (! $exists)
+				$elements[] = $node;
+		}
+		return $elements;
+//		$one = $one->newInstance();
+//		$one->elements = $elements;
+//		return $one;
+	}
+	/**
+	 *
+	 * @param $array
+	 * @param $callback
+	 * @param $invert
+	 * @return unknown_type
+	 * @link http://docs.jquery.com/Utilities/jQuery.grep
+	 */
+	public static function grep($array, $callback, $invert = false) {
+		$result = array();
+		foreach($array as $k => $v) {
+			$r = call_user_func_array($callback, array($v, $k));
+			if ($r === !(bool)$invert)
+				$result[] = $v;
+		}
+		return $result;
+	}
+	public static function unique($array) {
+		return array_unique($array);
+	}
+	/**
+	 *
+	 * @param $function
+	 * @return unknown_type
+	 * @TODO there are problems with non-static methods, second parameter pass it
+	 * 	but doesnt verify is method is really callable
+	 */
+	public static function isFunction($function) {
+		return is_callable($function);
+	}
+	public static function trim($str) {
+		return trim($str);
+	}
+	/* PLUGINS NAMESPACE */
+	/**
+	 *
+	 * @param $url
+	 * @param $callback
+	 * @param $param1
+	 * @param $param2
+	 * @param $param3
+	 * @return phpQueryObject
+	 */
+	public static function browserGet($url, $callback, $param1 = null, $param2 = null, $param3 = null) {
+		if (self::plugin('WebBrowser')) {
+			$params = func_get_args();
+			return self::callbackRun(array(self::$plugins, 'browserGet'), $params);
+		} else {
+			self::debug('WebBrowser plugin not available...');
+		}
+	}
+	/**
+	 *
+	 * @param $url
+	 * @param $data
+	 * @param $callback
+	 * @param $param1
+	 * @param $param2
+	 * @param $param3
+	 * @return phpQueryObject
+	 */
+	public static function browserPost($url, $data, $callback, $param1 = null, $param2 = null, $param3 = null) {
+		if (self::plugin('WebBrowser')) {
+			$params = func_get_args();
+			return self::callbackRun(array(self::$plugins, 'browserPost'), $params);
+		} else {
+			self::debug('WebBrowser plugin not available...');
+		}
+	}
+	/**
+	 *
+	 * @param $ajaxSettings
+	 * @param $callback
+	 * @param $param1
+	 * @param $param2
+	 * @param $param3
+	 * @return phpQueryObject
+	 */
+	public static function browser($ajaxSettings, $callback, $param1 = null, $param2 = null, $param3 = null) {
+		if (self::plugin('WebBrowser')) {
+			$params = func_get_args();
+			return self::callbackRun(array(self::$plugins, 'browser'), $params);
+		} else {
+			self::debug('WebBrowser plugin not available...');
+		}
+	}
+	/**
+	 *
+	 * @param $code
+	 * @return string
+	 */
+	public static function php($code) {
+		return self::code('php', $code);
+	}
+	/**
+	 *
+	 * @param $type
+	 * @param $code
+	 * @return string
+	 */
+	public static function code($type, $code) {
+		return "<$type><!-- ".trim($code)." --></$type>";
+	}
+
+	public static function __callStatic($method, $params) {
+		return call_user_func_array(
+			array(phpQuery::$plugins, $method),
+			$params
+		);
+	}
+	protected static function dataSetupNode($node, $documentID) {
+		// search are return if alredy exists
+		foreach(phpQuery::$documents[$documentID]->dataNodes as $dataNode) {
+			if ($node->isSameNode($dataNode))
+				return $dataNode;
+		}
+		// if doesn't, add it
+		phpQuery::$documents[$documentID]->dataNodes[] = $node;
+		return $node;
+	}
+	protected static function dataRemoveNode($node, $documentID) {
+		// search are return if alredy exists
+		foreach(phpQuery::$documents[$documentID]->dataNodes as $k => $dataNode) {
+			if ($node->isSameNode($dataNode)) {
+				unset(self::$documents[$documentID]->dataNodes[$k]);
+				unset(self::$documents[$documentID]->data[ $dataNode->dataID ]);
+			}
+		}
+	}
+	public static function data($node, $name, $data, $documentID = null) {
+		if (! $documentID)
+			// TODO check if this works
+			$documentID = self::getDocumentID($node);
+		$document = phpQuery::$documents[$documentID];
+		$node = self::dataSetupNode($node, $documentID);
+		if (! isset($node->dataID))
+			$node->dataID = ++phpQuery::$documents[$documentID]->uuid;
+		$id = $node->dataID;
+		if (! isset($document->data[$id]))
+			$document->data[$id] = array();
+		if (! is_null($data))
+			$document->data[$id][$name] = $data;
+		if ($name) {
+			if (isset($document->data[$id][$name]))
+				return $document->data[$id][$name];
+		} else
+			return $id;
+	}
+	public static function removeData($node, $name, $documentID) {
+		if (! $documentID)
+			// TODO check if this works
+			$documentID = self::getDocumentID($node);
+		$document = phpQuery::$documents[$documentID];
+		$node = self::dataSetupNode($node, $documentID);
+		$id = $node->dataID;
+		if ($name) {
+			if (isset($document->data[$id][$name]))
+				unset($document->data[$id][$name]);
+			$name = null;
+			foreach($document->data[$id] as $name)
+				break;
+			if (! $name)
+				self::removeData($node, $name, $documentID);
+		} else {
+			self::dataRemoveNode($node, $documentID);
+		}
+	}
+}
+/**
+ * Plugins static namespace class.
+ *
+ * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com>
+ * @package phpQuery
+ * @todo move plugin methods here (as statics)
+ */
+class phpQueryPlugins {
+	public function __call($method, $args) {
+		if (isset(phpQuery::$extendStaticMethods[$method])) {
+			$return = call_user_func_array(
+				phpQuery::$extendStaticMethods[$method],
+				$args
+			);
+		} else if (isset(phpQuery::$pluginsStaticMethods[$method])) {
+			$class = phpQuery::$pluginsStaticMethods[$method];
+			$realClass = "phpQueryPlugin_$class";
+			$return = call_user_func_array(
+				array($realClass, $method),
+				$args
+			);
+			return isset($return)
+				? $return
+				: $this;
+		} else
+			throw new Exception("Method '{$method}' doesnt exist");
+	}
+}
+/**
+ * Shortcut to phpQuery::pq($arg1, $context)
+ * Chainable.
+ *
+ * @see phpQuery::pq()
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com>
+ * @package phpQuery
+ */
+function pq($arg1, $context = null) {
+	$args = func_get_args();
+	return call_user_func_array(
+		array('phpQuery', 'pq'),
+		$args
+	);
+}
+// add plugins dir and Zend framework to include path
+set_include_path(
+	get_include_path()
+		.PATH_SEPARATOR.dirname(__FILE__).'/phpQuery/'
+		.PATH_SEPARATOR.dirname(__FILE__).'/phpQuery/plugins/'
+);
+// why ? no __call nor __get for statics in php...
+// XXX __callStatic will be available in PHP 5.3
+phpQuery::$plugins = new phpQueryPlugins();
+// include bootstrap file (personal library config)
+if (file_exists(dirname(__FILE__).'/phpQuery/bootstrap.php'))
+	require_once dirname(__FILE__).'/phpQuery/bootstrap.php';

+ 96 - 0
KIF/Cache/.svn/entries

@@ -0,0 +1,96 @@
+10
+
+dir
+924
+svn://182.92.3.30/project/onepage/KIF/Cache
+svn://182.92.3.30/project
+
+
+
+2016-01-21T06:27:41.361155Z
+137
+yubin
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+140960d9-11d8-4647-83e1-275f326d1242
+
+Memcached.class.php
+file
+
+
+
+
+2016-02-15T00:10:46.000000Z
+0b00b8d77dff18174f6c9e4fcd0a2e6b
+2016-01-21T06:27:41.361155Z
+137
+yubin
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+18449
+
+TTServer.class.php
+file
+
+
+
+
+2016-02-15T00:10:46.000000Z
+929d82d64f8fe915f30a539028b6132f
+2016-01-21T06:27:41.361155Z
+137
+yubin
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+9123
+

+ 5 - 0
KIF/Cache/.svn/prop-base/Memcached.class.php.svn-base

@@ -0,0 +1,5 @@
+K 14
+svn:executable
+V 1
+*
+END

+ 5 - 0
KIF/Cache/.svn/prop-base/TTServer.class.php.svn-base

@@ -0,0 +1,5 @@
+K 14
+svn:executable
+V 1
+*
+END

+ 453 - 0
KIF/Cache/.svn/text-base/Memcached.class.php.svn-base

@@ -0,0 +1,453 @@
+<?php
+namespace KIF\Cache;
+
+use Memcached as MemcachedResource;
+use KIF\Debug\Debug;
+use KIF\Verify;
+use KIF\Exception\ParamsException;
+use KIF\Core\Config;
+
+/**
+ * 对php扩展 Memcached 的包装
+ * @author gaoxiaogang@gmail.com
+ *
+ */
+class Memcached {
+	/**
+	 * 存放已建立的 Memcached 实例
+	 * @var array
+	 */
+	static private $objMemcacheds = array();
+
+	/**
+	 *
+	 * 当前所使用的服务器集群
+	 * @var array
+	 */
+	private $servers;
+
+	/**
+	 *
+	 * 保存当前实例下到MemcachedResource的连接
+	 * @var MemcachedResource
+	 */
+	private $objMemcached;
+
+	public function __construct($cluster_flag = 'default') {
+		$memcachedConfig = Config::getInstance()->get('memcached');
+
+		if (!$memcachedConfig || !isset($memcachedConfig[$cluster_flag])) {
+			throw new ParamsException("不存在的memcache集群标志 {$cluster_flag}");
+		}
+
+		$this->servers = $memcachedConfig[$cluster_flag];
+
+		if (!isset(self::$objMemcacheds[$cluster_flag])) {
+			$objMemcached = new MemcachedResource();
+
+			# 设置socket连接的超时时间,单位是毫秒。这个值如果设置的太小,容易导致memcached操作失败率增加。
+			$objMemcached->setOption(MemcachedResource::OPT_CONNECT_TIMEOUT, 1000);
+
+			# 等待失败的连接重试的时间,单位秒
+//			$objMemcached->setOption(MemcachedResource::OPT_RETRY_TIMEOUT, 0);
+
+			$objMemcached->setOption(MemcachedResource::OPT_TCP_NODELAY, true);
+
+			# 使用一致性分布算法
+			$objMemcached->setOption(MemcachedResource::OPT_DISTRIBUTION, MemcachedResource::DISTRIBUTION_CONSISTENT);
+			$objMemcached->setOption(MemcachedResource::OPT_LIBKETAMA_COMPATIBLE, true);
+
+			$tmpResult = $objMemcached->addServers($this->servers);
+			if (!$tmpResult) {
+				return false;
+			}
+			self::$objMemcacheds[$cluster_flag] = $objMemcached;
+		}
+
+		$this->objMemcached = self::$objMemcacheds[$cluster_flag];
+	}
+
+	/**
+	 *
+	 * 给 $key 设置对应的值
+	 * @param string $key
+	 * @param mixed $value
+	 * @param int $expiration 过期时间 实际发送的值可以 是一个Unix时间戳,或者是一个从现在算起的以秒为单位的数字。对于后一种情况,这个 秒数不能超过60×60×24×30(30天时间的秒数);如果失效的值大于这个值, 服务端会将其作为一个真实的Unix时间戳来处理而不是 自当前时间的偏移。
+	 * 		如果失效值被设置为0(默认),此元素永不过期(但是它可能由于服务端为了给其他新的元素分配空间而被删除)。
+	 * @return Boolean 成功:true;失败:false
+	 *
+	 */
+	public function set($key, $value, $expiration = 0) {
+		$begin_microtime = Debug::getTime();
+
+		$tmpResult = $this->objMemcached->set($key, $value, $expiration);
+
+		if ($this->getResultCode() == MemcachedResource::RES_SUCCESS) {
+			Debug::cache($this->servers, $key, Debug::getTime() - $begin_microtime, $tmpResult, 'set');
+		} else {
+			Debug::cache($this->servers, $key, Debug::getTime() - $begin_microtime, 'false'.'::'.$this->getResultCode().'::'.$this->getResultMessage(), 'set');
+		}
+
+		return $tmpResult;
+	}
+
+	/**
+	 *
+	 * 存储多个元素
+	 * @param array $items 存放在服务器上的键/值对数组。
+	 * @param int $expiration 到期时间,默认为 0
+	 * @return Boolean 有任何一个key设置失败,都会返回false,并且该key后的key不会再去存储;但是前面存储成功的key仍然有效。
+	 * !!! 所以,批量设置的行为,具有不可预料性
+	 */
+	public function sets(array $items, $expiration = 0) {
+		$begin_microtime = Debug::getTime();
+
+		if (empty($items)) {
+			throw new ParamsException('sets方法的参数items不能为空数组');
+		}
+		$tmpResult = $this->objMemcached->setMulti($items, $expiration);
+
+		Debug::cache($this->servers, print_r($items, true), Debug::getTime() - $begin_microtime, $tmpResult, 'sets');
+		return $tmpResult;
+	}
+
+	/**
+	 *
+	 * 获取 $key 对应的值
+	 * PS: 如果指定了 通用缓存回调函数$cache_cb,并且想取得$cas_token,会使用 匿名函数
+	 *     对 $cache_cb 进行包装。原因是 Memcached 扩展的 get 方法,在这种情况下是获取不到 $cas_token 的。
+	 * @param string $key
+	 * @param callback $cache_cb 通用缓存回调函数。如:'myfunc', array('MyClass', 'classMethod'),
+	 * 		  array($obj, 'method')。
+	 * @param float $cas_token 引用传递
+	 * @return null | false | mixed 值不存在:返回 null;存在:返回值;其它原因导致的获取不到值:返回 false。
+	 * !!! 如果 $key set 的值本身是false,请调用 getResultCode 方法来与 失败原因导致的 false 相区分。
+	 *
+	 */
+	public function get($key, $cache_cb = null, & $cas_token = null) {
+		$begin_microtime = Debug::getTime();
+
+		if (func_num_args()	== 3 && !is_null($cache_cb)) {
+			$cb_success = null;
+			$tmpResult = $this->objMemcached->get($key, function ($objMemcached, $cacheKey, & $ref_value) use ($cache_cb, & $cb_success) {
+				$cb_success = $cache_cb($objMemcached, $cacheKey, $ref_value);
+				return $cb_success;
+			}, $cas_token);
+			if ($cb_success) {// 修复php \Memcached::get()的一个bug,当同时指定回调函数和$cas_token时,获取不到$cas_token。
+				$this->objMemcached->get($key, null, $cas_token);
+			}
+		} else {
+			$tmpResult = $this->objMemcached->get($key, $cache_cb, $cas_token);
+		}
+
+		if ($this->getResultCode() == MemcachedResource::RES_SUCCESS) {
+			Debug::cache($this->servers, $key, Debug::getTime() - $begin_microtime, $tmpResult, 'get');
+
+			return $tmpResult;
+		}
+
+		if (MemcachedResource::RES_NOTFOUND == $this->getResultCode()) {
+			Debug::cache($this->servers, $key, Debug::getTime() - $begin_microtime, null, 'get');
+
+			return null;// 值不存在,返回 null
+		} else {
+			Debug::cache($this->servers, $key, Debug::getTime() - $begin_microtime, 'false'.'::'.$this->getResultCode().'::'.$this->getResultMessage(), 'get');
+
+			return false;
+		}
+	}
+
+	/**
+	 *
+	 * 批量获取 $keys 对应的值
+	 * @param array $keys
+	 * @param array $cas_tokens 引用传值
+	 * @return false | array 失败:返回false,获取其中任何一个key出错,都会返回false;
+	 * 						 成功:array,值不存在的key,对应的值是null。
+	 */
+	public function gets(array $keys, array & $cas_tokens = null) {
+		if (empty($keys)) {
+			return array();
+		}
+
+		$begin_microtime = Debug::getTime();
+
+		$tmpResult = $this->objMemcached->getMulti($keys, $cas_tokens, MemcachedResource::GET_PRESERVE_ORDER);
+
+		Debug::cache($this->servers, print_r($keys, true), Debug::getTime() - $begin_microtime, $tmpResult, 'gets');
+
+		return $tmpResult;
+	}
+
+	/**
+	 *
+	 * 执行一个“检查并设置”的操作,因此,它仅在当前客户端最后一次取值后,该key 对应的值没有被其他客户端修改的情况下
+	 * ,才能够将值写入。检查是通过cas_token参数进行的, 这个参数是Memcach指定给已经存在的元素的一个唯一的64位值
+	 * ,怎样获取这个值请查看 Memcached::get*() 系列方法的文档
+	 * 。注意:这个值作为double类型是因为PHP的整型空间限制。
+	 * PS:这是Memcached扩展比Memcache扩展一个非常重要的优势,在这样一个系统级(Memcache自身提供)
+	 * 的冲突检测机制(乐观锁)下, 我们才能保证高并发下的数据安全。
+	 *
+	 * @param float $cas_token 与已存在元素关联的唯一的值,由Memcache生成。
+	 * @param string $key 用于存储值的键名。
+	 * @param mixed $value 存储的值
+	 * @param int $expiration 到期时间,默认为 0
+	 * @return null | boolean 成功:true;失败:false;$cas_token检查不通过:null
+	 * 		   如果在元素尝试存储时发现在本客户端最后一次获取后被其他客户端修改(即$cas_token检查不通过)
+	 * 		   ,Memcached::getResultCode() 将返回Memcached::RES_DATA_EXISTS。
+	 */
+	public function cas($cas_token, $key, $value, $expiration = 0) {
+		$begin_microtime = Debug::getTime();
+
+		$tmpResult = $this->objMemcached->cas($cas_token, $key, $value, $expiration);
+		if ($tmpResult) {
+			Debug::cache($this->servers, "{$cas_token}::{$key}::{$value}", Debug::getTime() - $begin_microtime, $tmpResult, 'cas');
+			return true;
+		}
+
+		if ($this->getResultCode() == MemcachedResource::RES_DATA_EXISTS) {
+			Debug::cache($this->servers, "{$cas_token}::{$key}::{$value}", Debug::getTime() - $begin_microtime, null, 'cas');
+			return null;
+		}
+
+		Debug::cache($this->servers, "{$cas_token}::{$key}::{$value}", Debug::getTime() - $begin_microtime, false, 'cas');
+		return false;
+	}
+
+	/**
+	 *
+	 * 删除一个元素
+	 * 从服务端删除key对应的元素。 参数time是一个秒为单位的时间(或一个UNIX时间戳表明直到那个时间)
+	 * ,用来表明 客户端希望服务端在这段时间拒绝对这个key的add和replace命令。
+	 * 由于这个时间段的存在, 元素被放入一个删除队列, 表明它不可以通过get命令获取到值,
+	 * 但是同时 add和replace命令也会失败(无论如何set命令都会成功)。
+	 * 在这段时间过去后, 元素最终被从服务端内存删除。
+	 * time参数默认0(表明元素会被立即删除并且之后对这个 key的存储命令也会成功)。
+	 * @param string $key 要删除的key
+	 * @param int $time 服务端等待删除该元素的总时间(或一个Unix时间戳表明的实际删除时间).
+	 * @return null | boolean 成功:true;失败:false;key不存在:null
+	 * 		   如果key不存在, Memcached::getResultCode()将会返回Memcached::RES_NOTFOUND
+	 */
+	public function delete($key, $time = 0) {
+		$begin_microtime = Debug::getTime();
+
+		$tmpResult = $this->objMemcached->delete($key, $time);
+
+		if ($tmpResult) {
+			Debug::cache($this->servers, $key, Debug::getTime() - $begin_microtime, true, 'delete');
+
+			return true;
+		}
+
+		if (MemcachedResource::RES_NOTFOUND == $this->getResultCode()) {
+			Debug::cache($this->servers, $key, Debug::getTime() - $begin_microtime, null, 'delete');
+			return null;
+		}
+
+		Debug::cache($this->servers, $key, Debug::getTime() - $begin_microtime, false, 'delete');
+		return false;
+	}
+	
+	/**
+	 *
+	 * !!!!
+	 * !! 这个方法与 delete有些区别,delete了cache里不存在的key时,会返回false;但deletes却会返回true。
+	 * !!!!
+	 * 删除一批元素
+	 * 从服务端删除keys对应的元素。 参数time是一个秒为单位的时间(或一个UNIX时间戳表明直到那个时间)
+	 * ,用来表明 客户端希望服务端在这段时间拒绝对这个key的add和replace命令。
+	 * 由于这个时间段的存在, 元素被放入一个删除队列, 表明它不可以通过get命令获取到值,
+	 * 但是同时 add和replace命令也会失败(无论如何set命令都会成功)。
+	 * 在这段时间过去后, 元素最终被从服务端内存删除。
+	 * time参数默认0(表明元素会被立即删除并且之后对这个 key的存储命令也会成功)。
+	 * @param array $keys 要删除的keys
+	 * @param int $time 服务端等待删除该元素的总时间(或一个Unix时间戳表明的实际删除时间).
+	 * @return null | boolean 成功:true;失败:false;key不存在:null
+	 * 		   如果key不存在, Memcached::getResultCode()将会返回Memcached::RES_NOTFOUND
+	 */
+	public function deletes(array $keys, $time = 0) {
+		$begin_microtime = Debug::getTime();
+
+		$tmpResult = $this->objMemcached->deleteMulti($keys, $time);
+
+		if ($tmpResult) {
+			Debug::cache($this->servers, print_r($keys, true), Debug::getTime() - $begin_microtime, true, 'deletes');
+
+			return true;
+		}
+
+		if (MemcachedResource::RES_NOTFOUND == $this->getResultCode()) {
+			Debug::cache($this->servers, print_r($keys, true), Debug::getTime() - $begin_microtime, null, 'deletes');
+			return null;
+		}
+
+		Debug::cache($this->servers, print_r($keys, true), Debug::getTime() - $begin_microtime, false, 'deletes');
+		return false;
+	}
+
+	/**
+	 *
+	 * 向一个新的key下面增加一个元素
+	 * @param string $key 用于存储值的键名。
+	 * @param mixed $value 存储的值
+	 * @param int $expiration 到期时间,默认为 0
+	 * @return null | boolean 成功:true;失败:false;key存在:null
+	 * 		   如果key已经存在, Memcached::getResultCode()方法将会返回Memcached::RES_NOTSTORED。
+	 */
+	public function add($key, $value, $expiration = 0) {
+		$begin_microtime = Debug::getTime();
+
+		$tmpResult = $this->objMemcached->add($key, $value, $expiration);
+
+		if ($tmpResult) {
+			Debug::cache($this->servers, "{$key}::{$value}", Debug::getTime() - $begin_microtime, true, 'add');
+			return true;
+		}
+
+		if (MemcachedResource::RES_NOTSTORED == $this->getResultCode()) {
+			Debug::cache($this->servers, "{$key}::{$value}", Debug::getTime() - $begin_microtime, null, 'add');
+			return null;
+		}
+
+		Debug::cache($this->servers, "{$key}::{$value}", Debug::getTime() - $begin_microtime, false, 'add');
+		return false;
+	}
+
+	/**
+	 *
+	 * 替换已存在key下的元素
+	 * 如果 服务端不存在key,操作将失败。
+	 * @param string $key 用于存储值的键名。
+	 * @param mixed $value 存储的值
+	 * @param int $expiration 到期时间,默认为 0
+	 * @return null | boolean 成功:true;失败:false;key不存在:null
+	 */
+	public function replace($key, $value, $expiration = 0) {
+		$begin_microtime = Debug::getTime();
+
+		$tmpResult = $this->objMemcached->replace($key, $value, $expiration);
+
+		if ($tmpResult) {
+			Debug::cache($this->servers, "{$key}::{$value}", Debug::getTime() - $begin_microtime, true, 'replace');
+			return true;
+		}
+
+		if (MemcachedResource::RES_NOTSTORED == $this->getResultCode()) {
+			Debug::cache($this->servers, "{$key}::{$value}", Debug::getTime() - $begin_microtime, null, 'replace');
+			return null;
+		}
+
+		Debug::cache($this->servers, "{$key}::{$value}", Debug::getTime() - $begin_microtime, false, 'replace');
+		return false;
+	}
+
+	/**
+	 *
+	 * 将一个数值元素增加参数offset指定的大小。如果元素的值不是数值类型,返回false
+	 * 经实验发现,Memcached 扩展的increment方法存在bug,这里对其做了修复。
+	 * @param string $key 要增加值的元素的key。
+	 * @param int $offset 要将元素的值增加的大小。
+	 * @return null | int | false 成功:int(递增后的值);失败:false;key不存在:null
+	 * 		   如果key不存在 Memcached::getResultCode()方法返回Memcached::RES_NOTFOUND。
+	 * !!! 如果 $key 对应的值不是 int 型:通过 $this->get 方法返回的值不是 int 型
+	 * a、如果不是 boolean 型:结果是0,并且 Memcached::getResultCode() 返回 Memcached::RES_SUCCESS
+	 * b、如果是 boolean 型:会以0为基准递增值。
+	 * !!! 所以,请尽量尽量不要对非 int 型的值使用 increment 方法
+	 */
+	public function increment($key, $offset = 1) {
+		$begin_microtime = Debug::getTime();
+
+		$tmpGetResult = $this->get($key);
+		if (is_null($tmpGetResult)) {// $key 对应的值不存在
+			Debug::cache($this->servers, "{$key}::{$offset}", Debug::getTime() - $begin_microtime, null, 'increment');
+			return null;
+		}
+
+		if ($this->getResultCode() != MemcachedResource::RES_SUCCESS ) {
+			Debug::cache($this->servers, "{$key}::{$offset}", Debug::getTime() - $begin_microtime, false, 'increment');
+			return false;
+		}
+
+		if (!Verify::naturalNumber($tmpGetResult)) {
+			Debug::cache($this->servers, "{$key}::{$offset}", Debug::getTime() - $begin_microtime, false, 'increment');
+			return false;
+		}
+
+		$tmpResult = $this->objMemcached->increment($key, $offset);
+		if (Verify::unsignedInt($tmpResult)) {
+			Debug::cache($this->servers, "{$key}::{$offset}", Debug::getTime() - $begin_microtime, $tmpResult, 'increment');
+			return $tmpResult;
+		}
+
+		Debug::cache($this->servers, "{$key}::{$offset}", Debug::getTime() - $begin_microtime, false, 'increment');
+		return false;
+	}
+
+	/**
+	 *
+	 * 减小一个数值元素的值,减小多少由参数offset决定。如果元素的值不是数值类型,返回false。
+	 * 如果减小后的值小于0,则新的值被设置为0。
+	 * 如果元素不存在,返回 null。
+	 *
+	 * 经实验发现,Memcached 扩展的decrement方法存在bug,这里对其做了修复。
+	 * @param string $key 要减少值的元素的key。
+	 * @param int $offset 要将元素的值减少的大小。
+	 * @return null | int | false 成功:int(减少后的值,有可能是 0);失败:false;key不存在:null
+	 * 		   如果key不存在 Memcached::getResultCode()方法返回Memcached::RES_NOTFOUND。
+	 */
+	public function decrement($key, $offset = 1) {
+		$begin_microtime = Debug::getTime();
+
+		$tmpGetResult = $this->get($key);
+		if (is_null($tmpGetResult)) {// $key 对应的值不存在
+			Debug::cache($this->servers, "{$key}::{$offset}", Debug::getTime() - $begin_microtime, null, 'decrement');
+			return null;
+		}
+
+		if ($this->getResultCode() != MemcachedResource::RES_SUCCESS ) {
+			Debug::cache($this->servers, "{$key}::{$offset}", Debug::getTime() - $begin_microtime, false, 'decrement');
+			return false;
+		}
+
+		if (!Verify::naturalNumber($tmpGetResult)) {
+			Debug::cache($this->servers, "{$key}::{$offset}", Debug::getTime() - $begin_microtime, false, 'decrement');
+			return false;
+		}
+
+		$tmpResult = $this->objMemcached->decrement($key, $offset);
+		if (Verify::naturalNumber($tmpResult)) {
+			Debug::cache($this->servers, "{$key}::{$offset}", Debug::getTime() - $begin_microtime, $tmpResult, 'decrement');
+			return $tmpResult;
+		}
+
+		Debug::cache($this->servers, "{$key}::{$offset}", Debug::getTime() - $begin_microtime, false, 'decrement');
+		return false;
+	}
+
+	/**
+	 *
+	 * 返回最后一次操作的结果代码
+	 * 返回Memcached::RES_*系列常量中的一个来表明最后一次执行Memcached方法的结果
+	 *
+	 * 比如常用的几个常量是:
+	 * 1、Memcached::RES_SUCCESS 表示操作成功
+	 * 2、Memcached::RES_NOTFOUND 元素未找到(通过get或cas操作时)
+	 * 3、Memcached::RES_NOTSTORED 元素没有被存储,但并不是因为一个错误。
+	 * 				这通常表明add(元素已存在)或replace(元素不存在)方式存储数据失败或者元素已经在一个删除序列中(延时删除)。
+	 *
+	 * @return int
+	 */
+	public function getResultCode() {
+		return $this->objMemcached->getResultCode();
+	}
+
+	/**
+	 *
+	 * 返回一个字符串来描述最后一次Memcached方法执行的结果。
+	 * @return string
+	 */
+	public function getResultMessage() {
+		return $this->objMemcached->getResultMessage();
+	}
+
+}

+ 292 - 0
KIF/Cache/.svn/text-base/TTServer.class.php.svn-base

@@ -0,0 +1,292 @@
+<?php
+namespace KIF\Cache;
+
+use KIF\Exception\ParamsException;
+use Memcache as MemcacheResource;
+use KIF\Debug\Debug;
+use KIF\Verify;
+use KIF\Core\Config;
+
+/**
+ * 对php扩展 Memcache 的包装,用于操作 tokyotyrant。
+ * tokyotyrant是网络接口,其后的存储由tokyocabinet。该存储里的值都是永久的,不像memcache一样,能够设置过期时间。
+ * @author gaoxiaogang@gmail.com
+ *
+ */
+class TTServer {
+	/**
+	 * 存放已建立的 Memcache 实例
+	 * @var array
+	 */
+	static private $objMemcaches = array();
+
+	/**
+	 *
+	 * 当前所使用的服务器集群
+	 * @var array
+	 */
+	private $servers;
+
+	/**
+	 *
+	 * 保存当前实例下到Memcached的连接
+	 * @var Memcache
+	 */
+	private $objMemcache;
+
+	public function __construct($cluster_flag = 'default') {
+		$ttserverConfig = Config::getInstance()->get('ttserver');
+		
+		if (!$ttserverConfig || !isset($ttserverConfig[$cluster_flag])) {
+			throw new ParamsException("不存在的ttserver集群标志 {$cluster_flag}");
+		}
+
+		$this->servers = $ttserverConfig[$cluster_flag];
+
+		if (!isset(self::$objMemcaches[$cluster_flag])) {
+			$objMemcache = new MemcacheResource();
+			foreach ($this->servers as $server) {
+				$tmpHost = isset($server['host']) ? $server['host'] : '127.0.0.1';
+				$tmpPort = isset($server['port']) ? $server['port'] : '11211';
+				$tmpPersistent = isset($server['persistent']) ? (bool) $server['persistent'] : false;
+				$tmpWeight = isset($server['weight']) ? $server['weight'] : 1;
+				$tmpTimeout = isset($server['timeout']) ? $server['timeout'] : 2;// 默认2秒的超时
+				$tmpRetry_interval = isset($server['retry_interval']) ? $server['retry_interval'] : 5;// 默认5秒超时重试
+
+				$tmpResult = $objMemcache->addServer($tmpHost, $tmpPort, $tmpPersistent
+					, $tmpWeight, $tmpTimeout, $tmpRetry_interval
+				);
+			}
+			self::$objMemcaches[$cluster_flag] = $objMemcache;
+		}
+
+		$this->objMemcache = self::$objMemcaches[$cluster_flag];
+	}
+
+	/**
+	 *
+	 * 给 $key 设置对应的值
+	 * @param string $key
+	 * @param mixed $value
+	 * @return Boolean 成功:true;失败:false
+	 *
+	 */
+	public function set($key, $value) {
+		$begin_microtime = Debug::getTime();
+
+		if (!is_scalar($value)) {
+			$value = serialize($value);
+		}
+		$tmpResult = $this->objMemcache->set($key, $value);
+		Debug::cache($this->servers, $key, Debug::getTime() - $begin_microtime, $tmpResult, 'set');
+
+		return $tmpResult;
+	}
+
+	/**
+	 *
+	 * 获取 $key 对应的值
+	 * @param string $key
+	 * @return false | mixed 存在:返回值;值不存在 或 其它原因导致的获取不到值:返回 false。
+	 *
+	 */
+	public function get($key) {
+		$begin_microtime = Debug::getTime();
+
+		$tmpResult = $this->objMemcache->get($key);
+		if ($tmpResult !== false) {
+			# 尝试序列化
+			$tmpUnserializeVal = @unserialize($tmpResult);
+			if ($tmpUnserializeVal !== false) {// 反序列化成功,则使用序列化后的值
+				$tmpResult = $tmpUnserializeVal;
+			}
+			Debug::cache($this->servers, $key, Debug::getTime() - $begin_microtime, $tmpResult, 'get');
+
+			return $tmpResult;
+		}
+
+		Debug::cache($this->servers, $key, Debug::getTime() - $begin_microtime, false, 'get');
+		return false;
+	}
+
+	/**
+	 *
+	 * 批量获取 $keys 对应的值
+	 * @param array $keys
+	 * @return false | array 失败:返回false,获取其中任何一个key出错,都会返回false;
+	 * 						 成功:array,值不存在的key,对应的值是null。
+	 */
+	public function gets(array $keys) {
+		if (empty($keys)) {
+			return array();
+		}
+
+		$begin_microtime = Debug::getTime();
+
+		$tmpResult = $this->objMemcache->get($keys);
+
+		Debug::cache($this->servers, print_r($keys, true), Debug::getTime() - $begin_microtime, false, 'gets');
+
+		if ($tmpResult === false) {
+			return false;
+		}
+
+		$return = array();
+		foreach ($keys as $key) {
+			if (isset($tmpResult[$key])) {
+				# 尝试序列化
+				$tmpUnserializeVal = @unserialize($tmpResult[$key]);
+				if ($tmpUnserializeVal !== false) {// 反序列化成功,则使用序列化后的值
+					$return[$key] = $tmpUnserializeVal;
+				} else {
+					$return[$key] = $tmpResult[$key];
+				}
+			} else {
+				$return[$key] = null;
+			}
+		}
+		return $return;
+	}
+
+	/**
+	 *
+	 * 删除一个元素
+	 * @param string $key 要删除的key
+	 * @return null | boolean 成功:true;失败:false;
+	 */
+	public function delete($key) {
+		$begin_microtime = Debug::getTime();
+
+		$tmpResult = $this->objMemcache->delete($key);
+
+		if ($tmpResult) {
+			Debug::cache($this->servers, $key, Debug::getTime() - $begin_microtime, true, 'delete');
+
+			return true;
+		}
+
+		Debug::cache($this->servers, $key, Debug::getTime() - $begin_microtime, false, 'delete');
+		return false;
+	}
+
+	/**
+	 *
+	 * 向一个新的key下面增加一个元素
+	 * @param string $key 用于存储值的键名。
+	 * @param mixed $value 存储的值
+	 * @return boolean 成功:true;失败:false;
+	 */
+	public function add($key, $value) {
+		$begin_microtime = Debug::getTime();
+
+		if (!is_scalar($value)) {
+			$value = serialize($value);
+		}
+
+		$tmpResult = $this->objMemcache->add($key, $value);
+
+		if ($tmpResult) {
+			Debug::cache($this->servers, "{$key}::{$value}", Debug::getTime() - $begin_microtime, true, 'add');
+			return true;
+		}
+
+		Debug::cache($this->servers, "{$key}::{$value}", Debug::getTime() - $begin_microtime, false, 'add');
+		return false;
+	}
+
+	/**
+	 *
+	 * 替换已存在key下的元素
+	 * 如果 服务端不存在key,操作将失败。
+	 * @param string $key 用于存储值的键名。
+	 * @param mixed $value 存储的值
+	 * @return null | boolean 成功:true;失败:false;
+	 */
+	public function replace($key, $value) {
+		$begin_microtime = Debug::getTime();
+
+		if (!is_scalar($value)) {
+			$value = serialize($value);
+		}
+		$tmpResult = $this->objMemcache->replace($key, $value);
+
+		if ($tmpResult) {
+			Debug::cache($this->servers, "{$key}::{$value}", Debug::getTime() - $begin_microtime, true, 'replace');
+			return true;
+		}
+
+		Debug::cache($this->servers, "{$key}::{$value}", Debug::getTime() - $begin_microtime, false, 'replace');
+		return false;
+	}
+
+	/**
+	 *
+	 * 将一个数值元素增加参数offset指定的大小。如果元素的值不是数值类型,返回false
+	 * 经实验发现,Memcached 扩展的increment方法存在bug,这里对其做了修复。
+	 * @param string $key 要增加值的元素的key。
+	 * @param int $offset 要将元素的值增加的大小。
+	 * @return int | false 成功:int(递增后的值);失败或key不存在:false
+	 * !!! 如果 $key 对应的值不是 int 型:通过 $this->get 方法返回的值不是 int 型
+	 * a、如果不是 boolean 型:结果是0
+	 * b、如果是 boolean 型:会以0为基准递增值。
+	 * !!! 所以,请尽量尽量不要对非 int 型的值使用 increment 方法
+	 */
+	public function increment($key, $offset = 1) {
+		$begin_microtime = Debug::getTime();
+
+		$tmpGetResult = $this->get($key);
+		if (is_null($tmpGetResult)) {// $key 对应的值不存在
+			Debug::cache($this->servers, "{$key}::{$offset}", Debug::getTime() - $begin_microtime, null, 'increment');
+			return false;
+		}
+
+		if (!Verify::naturalNumber($tmpGetResult)) {
+			Debug::cache($this->servers, "{$key}::{$offset}", Debug::getTime() - $begin_microtime, false, 'increment');
+			return false;
+		}
+
+		$tmpResult = $this->objMemcache->increment($key, $offset);
+		if (Verify::unsignedInt($tmpResult)) {
+			Debug::cache($this->servers, "{$key}::{$offset}", Debug::getTime() - $begin_microtime, $tmpResult, 'increment');
+			return $tmpResult;
+		}
+
+		Debug::cache($this->servers, "{$key}::{$offset}", Debug::getTime() - $begin_microtime, false, 'increment');
+		return false;
+	}
+
+	/**
+	 *
+	 * 减小一个数值元素的值,减小多少由参数offset决定。如果元素的值不是数值类型,返回false。
+	 * 如果减小后的值小于0,则新的值被设置为0。
+	 * 如果元素不存在,返回 null。
+	 *
+	 * 经实验发现,Memcached 扩展的decrement方法存在bug,这里对其做了修复。
+	 * @param string $key 要减少值的元素的key。
+	 * @param int $offset 要将元素的值减少的大小。
+	 * @return int | false 成功:int(减少后的值,有可能是 0);失败或key不存在:false;
+	 */
+	public function decrement($key, $offset = 1) {
+		$begin_microtime = Debug::getTime();
+
+		$tmpGetResult = $this->get($key);
+		if (is_null($tmpGetResult)) {// $key 对应的值不存在
+			Debug::cache($this->servers, "{$key}::{$offset}", Debug::getTime() - $begin_microtime, null, 'decrement');
+			return false;
+		}
+
+		if (!Verify::naturalNumber($tmpGetResult)) {
+			Debug::cache($this->servers, "{$key}::{$offset}", Debug::getTime() - $begin_microtime, false, 'decrement');
+			return false;
+		}
+
+		$tmpResult = $this->objMemcache->decrement($key, $offset);
+		if (Verify::naturalNumber($tmpResult)) {
+			Debug::cache($this->servers, "{$key}::{$offset}", Debug::getTime() - $begin_microtime, $tmpResult, 'decrement');
+			return $tmpResult;
+		}
+
+		Debug::cache($this->servers, "{$key}::{$offset}", Debug::getTime() - $begin_microtime, false, 'decrement');
+		return false;
+	}
+}

+ 453 - 0
KIF/Cache/Memcached.class.php

@@ -0,0 +1,453 @@
+<?php
+namespace KIF\Cache;
+
+use Memcached as MemcachedResource;
+use KIF\Debug\Debug;
+use KIF\Verify;
+use KIF\Exception\ParamsException;
+use KIF\Core\Config;
+
+/**
+ * 对php扩展 Memcached 的包装
+ * @author gaoxiaogang@gmail.com
+ *
+ */
+class Memcached {
+	/**
+	 * 存放已建立的 Memcached 实例
+	 * @var array
+	 */
+	static private $objMemcacheds = array();
+
+	/**
+	 *
+	 * 当前所使用的服务器集群
+	 * @var array
+	 */
+	private $servers;
+
+	/**
+	 *
+	 * 保存当前实例下到MemcachedResource的连接
+	 * @var MemcachedResource
+	 */
+	private $objMemcached;
+
+	public function __construct($cluster_flag = 'default') {
+		$memcachedConfig = Config::getInstance()->get('memcached');
+
+		if (!$memcachedConfig || !isset($memcachedConfig[$cluster_flag])) {
+			throw new ParamsException("不存在的memcache集群标志 {$cluster_flag}");
+		}
+
+		$this->servers = $memcachedConfig[$cluster_flag];
+
+		if (!isset(self::$objMemcacheds[$cluster_flag])) {
+			$objMemcached = new MemcachedResource();
+
+			# 设置socket连接的超时时间,单位是毫秒。这个值如果设置的太小,容易导致memcached操作失败率增加。
+			$objMemcached->setOption(MemcachedResource::OPT_CONNECT_TIMEOUT, 1000);
+
+			# 等待失败的连接重试的时间,单位秒
+//			$objMemcached->setOption(MemcachedResource::OPT_RETRY_TIMEOUT, 0);
+
+			$objMemcached->setOption(MemcachedResource::OPT_TCP_NODELAY, true);
+
+			# 使用一致性分布算法
+			$objMemcached->setOption(MemcachedResource::OPT_DISTRIBUTION, MemcachedResource::DISTRIBUTION_CONSISTENT);
+			$objMemcached->setOption(MemcachedResource::OPT_LIBKETAMA_COMPATIBLE, true);
+
+			$tmpResult = $objMemcached->addServers($this->servers);
+			if (!$tmpResult) {
+				return false;
+			}
+			self::$objMemcacheds[$cluster_flag] = $objMemcached;
+		}
+
+		$this->objMemcached = self::$objMemcacheds[$cluster_flag];
+	}
+
+	/**
+	 *
+	 * 给 $key 设置对应的值
+	 * @param string $key
+	 * @param mixed $value
+	 * @param int $expiration 过期时间 实际发送的值可以 是一个Unix时间戳,或者是一个从现在算起的以秒为单位的数字。对于后一种情况,这个 秒数不能超过60×60×24×3030天时间的秒数);如果失效的值大于这个值, 服务端会将其作为一个真实的Unix时间戳来处理而不是 自当前时间的偏移。
+	 * 		如果失效值被设置为0(默认),此元素永不过期(但是它可能由于服务端为了给其他新的元素分配空间而被删除)。
+	 * @return Boolean 成功:true;失败:false
+	 *
+	 */
+	public function set($key, $value, $expiration = 0) {
+		$begin_microtime = Debug::getTime();
+
+		$tmpResult = $this->objMemcached->set($key, $value, $expiration);
+
+		if ($this->getResultCode() == MemcachedResource::RES_SUCCESS) {
+			Debug::cache($this->servers, $key, Debug::getTime() - $begin_microtime, $tmpResult, 'set');
+		} else {
+			Debug::cache($this->servers, $key, Debug::getTime() - $begin_microtime, 'false'.'::'.$this->getResultCode().'::'.$this->getResultMessage(), 'set');
+		}
+
+		return $tmpResult;
+	}
+
+	/**
+	 *
+	 * 存储多个元素
+	 * @param array $items 存放在服务器上的键/值对数组。
+	 * @param int $expiration 到期时间,默认为 0
+	 * @return Boolean 有任何一个key设置失败,都会返回false,并且该key后的key不会再去存储;但是前面存储成功的key仍然有效。
+	 * !!! 所以,批量设置的行为,具有不可预料性
+	 */
+	public function sets(array $items, $expiration = 0) {
+		$begin_microtime = Debug::getTime();
+
+		if (empty($items)) {
+			throw new ParamsException('sets方法的参数items不能为空数组');
+		}
+		$tmpResult = $this->objMemcached->setMulti($items, $expiration);
+
+		Debug::cache($this->servers, print_r($items, true), Debug::getTime() - $begin_microtime, $tmpResult, 'sets');
+		return $tmpResult;
+	}
+
+	/**
+	 *
+	 * 获取 $key 对应的值
+	 * PS: 如果指定了 通用缓存回调函数$cache_cb,并且想取得$cas_token,会使用 匿名函数
+	 *     对 $cache_cb 进行包装。原因是 Memcached 扩展的 get 方法,在这种情况下是获取不到 $cas_token 的。
+	 * @param string $key
+	 * @param callback $cache_cb 通用缓存回调函数。如:'myfunc', array('MyClass', 'classMethod'),
+	 * 		  array($obj, 'method')。
+	 * @param float $cas_token 引用传递
+	 * @return null | false | mixed 值不存在:返回 null;存在:返回值;其它原因导致的获取不到值:返回 false
+	 * !!! 如果 $key set 的值本身是false,请调用 getResultCode 方法来与 失败原因导致的 false 相区分。
+	 *
+	 */
+	public function get($key, $cache_cb = null, & $cas_token = null) {
+		$begin_microtime = Debug::getTime();
+
+		if (func_num_args()	== 3 && !is_null($cache_cb)) {
+			$cb_success = null;
+			$tmpResult = $this->objMemcached->get($key, function ($objMemcached, $cacheKey, & $ref_value) use ($cache_cb, & $cb_success) {
+				$cb_success = $cache_cb($objMemcached, $cacheKey, $ref_value);
+				return $cb_success;
+			}, $cas_token);
+			if ($cb_success) {// 修复php \Memcached::get()的一个bug,当同时指定回调函数和$cas_token时,获取不到$cas_token。
+				$this->objMemcached->get($key, null, $cas_token);
+			}
+		} else {
+			$tmpResult = $this->objMemcached->get($key, $cache_cb, $cas_token);
+		}
+
+		if ($this->getResultCode() == MemcachedResource::RES_SUCCESS) {
+			Debug::cache($this->servers, $key, Debug::getTime() - $begin_microtime, $tmpResult, 'get');
+
+			return $tmpResult;
+		}
+
+		if (MemcachedResource::RES_NOTFOUND == $this->getResultCode()) {
+			Debug::cache($this->servers, $key, Debug::getTime() - $begin_microtime, null, 'get');
+
+			return null;// 值不存在,返回 null
+		} else {
+			Debug::cache($this->servers, $key, Debug::getTime() - $begin_microtime, 'false'.'::'.$this->getResultCode().'::'.$this->getResultMessage(), 'get');
+
+			return false;
+		}
+	}
+
+	/**
+	 *
+	 * 批量获取 $keys 对应的值
+	 * @param array $keys
+	 * @param array $cas_tokens 引用传值
+	 * @return false | array 失败:返回false,获取其中任何一个key出错,都会返回false
+	 * 						 成功:array,值不存在的key,对应的值是null
+	 */
+	public function gets(array $keys, array & $cas_tokens = null) {
+		if (empty($keys)) {
+			return array();
+		}
+
+		$begin_microtime = Debug::getTime();
+
+		$tmpResult = $this->objMemcached->getMulti($keys, $cas_tokens, MemcachedResource::GET_PRESERVE_ORDER);
+
+		Debug::cache($this->servers, print_r($keys, true), Debug::getTime() - $begin_microtime, $tmpResult, 'gets');
+
+		return $tmpResult;
+	}
+
+	/**
+	 *
+	 * 执行一个“检查并设置”的操作,因此,它仅在当前客户端最后一次取值后,该key 对应的值没有被其他客户端修改的情况下
+	 * ,才能够将值写入。检查是通过cas_token参数进行的, 这个参数是Memcach指定给已经存在的元素的一个唯一的64位值
+	 * ,怎样获取这个值请查看 Memcached::get*() 系列方法的文档
+	 * 。注意:这个值作为double类型是因为PHP的整型空间限制。
+	 * PS:这是Memcached扩展比Memcache扩展一个非常重要的优势,在这样一个系统级(Memcache自身提供)
+	 * 的冲突检测机制(乐观锁)下, 我们才能保证高并发下的数据安全。
+	 *
+	 * @param float $cas_token 与已存在元素关联的唯一的值,由Memcache生成。
+	 * @param string $key 用于存储值的键名。
+	 * @param mixed $value 存储的值
+	 * @param int $expiration 到期时间,默认为 0
+	 * @return null | boolean 成功:true;失败:false;$cas_token检查不通过:null
+	 * 		   如果在元素尝试存储时发现在本客户端最后一次获取后被其他客户端修改(即$cas_token检查不通过)
+	 * 		   ,Memcached::getResultCode() 将返回Memcached::RES_DATA_EXISTS。
+	 */
+	public function cas($cas_token, $key, $value, $expiration = 0) {
+		$begin_microtime = Debug::getTime();
+
+		$tmpResult = $this->objMemcached->cas($cas_token, $key, $value, $expiration);
+		if ($tmpResult) {
+			Debug::cache($this->servers, "{$cas_token}::{$key}::{$value}", Debug::getTime() - $begin_microtime, $tmpResult, 'cas');
+			return true;
+		}
+
+		if ($this->getResultCode() == MemcachedResource::RES_DATA_EXISTS) {
+			Debug::cache($this->servers, "{$cas_token}::{$key}::{$value}", Debug::getTime() - $begin_microtime, null, 'cas');
+			return null;
+		}
+
+		Debug::cache($this->servers, "{$cas_token}::{$key}::{$value}", Debug::getTime() - $begin_microtime, false, 'cas');
+		return false;
+	}
+
+	/**
+	 *
+	 * 删除一个元素
+	 * 从服务端删除key对应的元素。 参数time是一个秒为单位的时间(或一个UNIX时间戳表明直到那个时间)
+	 * ,用来表明 客户端希望服务端在这段时间拒绝对这个key的add和replace命令。
+	 * 由于这个时间段的存在, 元素被放入一个删除队列, 表明它不可以通过get命令获取到值,
+	 * 但是同时 add和replace命令也会失败(无论如何set命令都会成功)。
+	 * 在这段时间过去后, 元素最终被从服务端内存删除。
+	 * time参数默认0(表明元素会被立即删除并且之后对这个 key的存储命令也会成功)。
+	 * @param string $key 要删除的key
+	 * @param int $time 服务端等待删除该元素的总时间(或一个Unix时间戳表明的实际删除时间).
+	 * @return null | boolean 成功:true;失败:false;key不存在:null
+	 * 		   如果key不存在, Memcached::getResultCode()将会返回Memcached::RES_NOTFOUND
+	 */
+	public function delete($key, $time = 0) {
+		$begin_microtime = Debug::getTime();
+
+		$tmpResult = $this->objMemcached->delete($key, $time);
+
+		if ($tmpResult) {
+			Debug::cache($this->servers, $key, Debug::getTime() - $begin_microtime, true, 'delete');
+
+			return true;
+		}
+
+		if (MemcachedResource::RES_NOTFOUND == $this->getResultCode()) {
+			Debug::cache($this->servers, $key, Debug::getTime() - $begin_microtime, null, 'delete');
+			return null;
+		}
+
+		Debug::cache($this->servers, $key, Debug::getTime() - $begin_microtime, false, 'delete');
+		return false;
+	}
+	
+	/**
+	 *
+	 * !!!!
+	 * !! 这个方法与 delete有些区别,delete了cache里不存在的key时,会返回false;但deletes却会返回true
+	 * !!!!
+	 * 删除一批元素
+	 * 从服务端删除keys对应的元素。 参数time是一个秒为单位的时间(或一个UNIX时间戳表明直到那个时间)
+	 * ,用来表明 客户端希望服务端在这段时间拒绝对这个key的add和replace命令。
+	 * 由于这个时间段的存在, 元素被放入一个删除队列, 表明它不可以通过get命令获取到值,
+	 * 但是同时 add和replace命令也会失败(无论如何set命令都会成功)。
+	 * 在这段时间过去后, 元素最终被从服务端内存删除。
+	 * time参数默认0(表明元素会被立即删除并且之后对这个 key的存储命令也会成功)。
+	 * @param array $keys 要删除的keys
+	 * @param int $time 服务端等待删除该元素的总时间(或一个Unix时间戳表明的实际删除时间).
+	 * @return null | boolean 成功:true;失败:false;key不存在:null
+	 * 		   如果key不存在, Memcached::getResultCode()将会返回Memcached::RES_NOTFOUND
+	 */
+	public function deletes(array $keys, $time = 0) {
+		$begin_microtime = Debug::getTime();
+
+		$tmpResult = $this->objMemcached->deleteMulti($keys, $time);
+
+		if ($tmpResult) {
+			Debug::cache($this->servers, print_r($keys, true), Debug::getTime() - $begin_microtime, true, 'deletes');
+
+			return true;
+		}
+
+		if (MemcachedResource::RES_NOTFOUND == $this->getResultCode()) {
+			Debug::cache($this->servers, print_r($keys, true), Debug::getTime() - $begin_microtime, null, 'deletes');
+			return null;
+		}
+
+		Debug::cache($this->servers, print_r($keys, true), Debug::getTime() - $begin_microtime, false, 'deletes');
+		return false;
+	}
+
+	/**
+	 *
+	 * 向一个新的key下面增加一个元素
+	 * @param string $key 用于存储值的键名。
+	 * @param mixed $value 存储的值
+	 * @param int $expiration 到期时间,默认为 0
+	 * @return null | boolean 成功:true;失败:false;key存在:null
+	 * 		   如果key已经存在, Memcached::getResultCode()方法将会返回Memcached::RES_NOTSTORED。
+	 */
+	public function add($key, $value, $expiration = 0) {
+		$begin_microtime = Debug::getTime();
+
+		$tmpResult = $this->objMemcached->add($key, $value, $expiration);
+
+		if ($tmpResult) {
+			Debug::cache($this->servers, "{$key}::{$value}", Debug::getTime() - $begin_microtime, true, 'add');
+			return true;
+		}
+
+		if (MemcachedResource::RES_NOTSTORED == $this->getResultCode()) {
+			Debug::cache($this->servers, "{$key}::{$value}", Debug::getTime() - $begin_microtime, null, 'add');
+			return null;
+		}
+
+		Debug::cache($this->servers, "{$key}::{$value}", Debug::getTime() - $begin_microtime, false, 'add');
+		return false;
+	}
+
+	/**
+	 *
+	 * 替换已存在key下的元素
+	 * 如果 服务端不存在key,操作将失败。
+	 * @param string $key 用于存储值的键名。
+	 * @param mixed $value 存储的值
+	 * @param int $expiration 到期时间,默认为 0
+	 * @return null | boolean 成功:true;失败:false;key不存在:null
+	 */
+	public function replace($key, $value, $expiration = 0) {
+		$begin_microtime = Debug::getTime();
+
+		$tmpResult = $this->objMemcached->replace($key, $value, $expiration);
+
+		if ($tmpResult) {
+			Debug::cache($this->servers, "{$key}::{$value}", Debug::getTime() - $begin_microtime, true, 'replace');
+			return true;
+		}
+
+		if (MemcachedResource::RES_NOTSTORED == $this->getResultCode()) {
+			Debug::cache($this->servers, "{$key}::{$value}", Debug::getTime() - $begin_microtime, null, 'replace');
+			return null;
+		}
+
+		Debug::cache($this->servers, "{$key}::{$value}", Debug::getTime() - $begin_microtime, false, 'replace');
+		return false;
+	}
+
+	/**
+	 *
+	 * 将一个数值元素增加参数offset指定的大小。如果元素的值不是数值类型,返回false
+	 * 经实验发现,Memcached 扩展的increment方法存在bug,这里对其做了修复。
+	 * @param string $key 要增加值的元素的key。
+	 * @param int $offset 要将元素的值增加的大小。
+	 * @return null | int | false 成功:int(递增后的值);失败:false;key不存在:null
+	 * 		   如果key不存在 Memcached::getResultCode()方法返回Memcached::RES_NOTFOUND。
+	 * !!! 如果 $key 对应的值不是 int 型:通过 $this->get 方法返回的值不是 int 型
+	 * a、如果不是 boolean 型:结果是0,并且 Memcached::getResultCode() 返回 Memcached::RES_SUCCESS
+	 * b、如果是 boolean 型:会以0为基准递增值。
+	 * !!! 所以,请尽量尽量不要对非 int 型的值使用 increment 方法
+	 */
+	public function increment($key, $offset = 1) {
+		$begin_microtime = Debug::getTime();
+
+		$tmpGetResult = $this->get($key);
+		if (is_null($tmpGetResult)) {// $key 对应的值不存在
+			Debug::cache($this->servers, "{$key}::{$offset}", Debug::getTime() - $begin_microtime, null, 'increment');
+			return null;
+		}
+
+		if ($this->getResultCode() != MemcachedResource::RES_SUCCESS ) {
+			Debug::cache($this->servers, "{$key}::{$offset}", Debug::getTime() - $begin_microtime, false, 'increment');
+			return false;
+		}
+
+		if (!Verify::naturalNumber($tmpGetResult)) {
+			Debug::cache($this->servers, "{$key}::{$offset}", Debug::getTime() - $begin_microtime, false, 'increment');
+			return false;
+		}
+
+		$tmpResult = $this->objMemcached->increment($key, $offset);
+		if (Verify::unsignedInt($tmpResult)) {
+			Debug::cache($this->servers, "{$key}::{$offset}", Debug::getTime() - $begin_microtime, $tmpResult, 'increment');
+			return $tmpResult;
+		}
+
+		Debug::cache($this->servers, "{$key}::{$offset}", Debug::getTime() - $begin_microtime, false, 'increment');
+		return false;
+	}
+
+	/**
+	 *
+	 * 减小一个数值元素的值,减小多少由参数offset决定。如果元素的值不是数值类型,返回false
+	 * 如果减小后的值小于0,则新的值被设置为0
+	 * 如果元素不存在,返回 null
+	 *
+	 * 经实验发现,Memcached 扩展的decrement方法存在bug,这里对其做了修复。
+	 * @param string $key 要减少值的元素的key。
+	 * @param int $offset 要将元素的值减少的大小。
+	 * @return null | int | false 成功:int(减少后的值,有可能是 0);失败:false;key不存在:null
+	 * 		   如果key不存在 Memcached::getResultCode()方法返回Memcached::RES_NOTFOUND。
+	 */
+	public function decrement($key, $offset = 1) {
+		$begin_microtime = Debug::getTime();
+
+		$tmpGetResult = $this->get($key);
+		if (is_null($tmpGetResult)) {// $key 对应的值不存在
+			Debug::cache($this->servers, "{$key}::{$offset}", Debug::getTime() - $begin_microtime, null, 'decrement');
+			return null;
+		}
+
+		if ($this->getResultCode() != MemcachedResource::RES_SUCCESS ) {
+			Debug::cache($this->servers, "{$key}::{$offset}", Debug::getTime() - $begin_microtime, false, 'decrement');
+			return false;
+		}
+
+		if (!Verify::naturalNumber($tmpGetResult)) {
+			Debug::cache($this->servers, "{$key}::{$offset}", Debug::getTime() - $begin_microtime, false, 'decrement');
+			return false;
+		}
+
+		$tmpResult = $this->objMemcached->decrement($key, $offset);
+		if (Verify::naturalNumber($tmpResult)) {
+			Debug::cache($this->servers, "{$key}::{$offset}", Debug::getTime() - $begin_microtime, $tmpResult, 'decrement');
+			return $tmpResult;
+		}
+
+		Debug::cache($this->servers, "{$key}::{$offset}", Debug::getTime() - $begin_microtime, false, 'decrement');
+		return false;
+	}
+
+	/**
+	 *
+	 * 返回最后一次操作的结果代码
+	 * 返回Memcached::RES_*系列常量中的一个来表明最后一次执行Memcached方法的结果
+	 *
+	 * 比如常用的几个常量是:
+	 * 1、Memcached::RES_SUCCESS 表示操作成功
+	 * 2、Memcached::RES_NOTFOUND 元素未找到(通过get或cas操作时)
+	 * 3、Memcached::RES_NOTSTORED 元素没有被存储,但并不是因为一个错误。
+	 * 				这通常表明add(元素已存在)或replace(元素不存在)方式存储数据失败或者元素已经在一个删除序列中(延时删除)。
+	 *
+	 * @return int
+	 */
+	public function getResultCode() {
+		return $this->objMemcached->getResultCode();
+	}
+
+	/**
+	 *
+	 * 返回一个字符串来描述最后一次Memcached方法执行的结果。
+	 * @return string
+	 */
+	public function getResultMessage() {
+		return $this->objMemcached->getResultMessage();
+	}
+
+}

+ 292 - 0
KIF/Cache/TTServer.class.php

@@ -0,0 +1,292 @@
+<?php
+namespace KIF\Cache;
+
+use KIF\Exception\ParamsException;
+use Memcache as MemcacheResource;
+use KIF\Debug\Debug;
+use KIF\Verify;
+use KIF\Core\Config;
+
+/**
+ * 对php扩展 Memcache 的包装,用于操作 tokyotyrant。
+ * tokyotyrant是网络接口,其后的存储由tokyocabinet。该存储里的值都是永久的,不像memcache一样,能够设置过期时间。
+ * @author gaoxiaogang@gmail.com
+ *
+ */
+class TTServer {
+	/**
+	 * 存放已建立的 Memcache 实例
+	 * @var array
+	 */
+	static private $objMemcaches = array();
+
+	/**
+	 *
+	 * 当前所使用的服务器集群
+	 * @var array
+	 */
+	private $servers;
+
+	/**
+	 *
+	 * 保存当前实例下到Memcached的连接
+	 * @var Memcache
+	 */
+	private $objMemcache;
+
+	public function __construct($cluster_flag = 'default') {
+		$ttserverConfig = Config::getInstance()->get('ttserver');
+		
+		if (!$ttserverConfig || !isset($ttserverConfig[$cluster_flag])) {
+			throw new ParamsException("不存在的ttserver集群标志 {$cluster_flag}");
+		}
+
+		$this->servers = $ttserverConfig[$cluster_flag];
+
+		if (!isset(self::$objMemcaches[$cluster_flag])) {
+			$objMemcache = new MemcacheResource();
+			foreach ($this->servers as $server) {
+				$tmpHost = isset($server['host']) ? $server['host'] : '127.0.0.1';
+				$tmpPort = isset($server['port']) ? $server['port'] : '11211';
+				$tmpPersistent = isset($server['persistent']) ? (bool) $server['persistent'] : false;
+				$tmpWeight = isset($server['weight']) ? $server['weight'] : 1;
+				$tmpTimeout = isset($server['timeout']) ? $server['timeout'] : 2;// 默认2秒的超时
+				$tmpRetry_interval = isset($server['retry_interval']) ? $server['retry_interval'] : 5;// 默认5秒超时重试
+
+				$tmpResult = $objMemcache->addServer($tmpHost, $tmpPort, $tmpPersistent
+					, $tmpWeight, $tmpTimeout, $tmpRetry_interval
+				);
+			}
+			self::$objMemcaches[$cluster_flag] = $objMemcache;
+		}
+
+		$this->objMemcache = self::$objMemcaches[$cluster_flag];
+	}
+
+	/**
+	 *
+	 * 给 $key 设置对应的值
+	 * @param string $key
+	 * @param mixed $value
+	 * @return Boolean 成功:true;失败:false
+	 *
+	 */
+	public function set($key, $value) {
+		$begin_microtime = Debug::getTime();
+
+		if (!is_scalar($value)) {
+			$value = serialize($value);
+		}
+		$tmpResult = $this->objMemcache->set($key, $value);
+		Debug::cache($this->servers, $key, Debug::getTime() - $begin_microtime, $tmpResult, 'set');
+
+		return $tmpResult;
+	}
+
+	/**
+	 *
+	 * 获取 $key 对应的值
+	 * @param string $key
+	 * @return false | mixed 存在:返回值;值不存在 或 其它原因导致的获取不到值:返回 false
+	 *
+	 */
+	public function get($key) {
+		$begin_microtime = Debug::getTime();
+
+		$tmpResult = $this->objMemcache->get($key);
+		if ($tmpResult !== false) {
+			# 尝试序列化
+			$tmpUnserializeVal = @unserialize($tmpResult);
+			if ($tmpUnserializeVal !== false) {// 反序列化成功,则使用序列化后的值
+				$tmpResult = $tmpUnserializeVal;
+			}
+			Debug::cache($this->servers, $key, Debug::getTime() - $begin_microtime, $tmpResult, 'get');
+
+			return $tmpResult;
+		}
+
+		Debug::cache($this->servers, $key, Debug::getTime() - $begin_microtime, false, 'get');
+		return false;
+	}
+
+	/**
+	 *
+	 * 批量获取 $keys 对应的值
+	 * @param array $keys
+	 * @return false | array 失败:返回false,获取其中任何一个key出错,都会返回false
+	 * 						 成功:array,值不存在的key,对应的值是null
+	 */
+	public function gets(array $keys) {
+		if (empty($keys)) {
+			return array();
+		}
+
+		$begin_microtime = Debug::getTime();
+
+		$tmpResult = $this->objMemcache->get($keys);
+
+		Debug::cache($this->servers, print_r($keys, true), Debug::getTime() - $begin_microtime, false, 'gets');
+
+		if ($tmpResult === false) {
+			return false;
+		}
+
+		$return = array();
+		foreach ($keys as $key) {
+			if (isset($tmpResult[$key])) {
+				# 尝试序列化
+				$tmpUnserializeVal = @unserialize($tmpResult[$key]);
+				if ($tmpUnserializeVal !== false) {// 反序列化成功,则使用序列化后的值
+					$return[$key] = $tmpUnserializeVal;
+				} else {
+					$return[$key] = $tmpResult[$key];
+				}
+			} else {
+				$return[$key] = null;
+			}
+		}
+		return $return;
+	}
+
+	/**
+	 *
+	 * 删除一个元素
+	 * @param string $key 要删除的key
+	 * @return null | boolean 成功:true;失败:false
+	 */
+	public function delete($key) {
+		$begin_microtime = Debug::getTime();
+
+		$tmpResult = $this->objMemcache->delete($key);
+
+		if ($tmpResult) {
+			Debug::cache($this->servers, $key, Debug::getTime() - $begin_microtime, true, 'delete');
+
+			return true;
+		}
+
+		Debug::cache($this->servers, $key, Debug::getTime() - $begin_microtime, false, 'delete');
+		return false;
+	}
+
+	/**
+	 *
+	 * 向一个新的key下面增加一个元素
+	 * @param string $key 用于存储值的键名。
+	 * @param mixed $value 存储的值
+	 * @return boolean 成功:true;失败:false
+	 */
+	public function add($key, $value) {
+		$begin_microtime = Debug::getTime();
+
+		if (!is_scalar($value)) {
+			$value = serialize($value);
+		}
+
+		$tmpResult = $this->objMemcache->add($key, $value);
+
+		if ($tmpResult) {
+			Debug::cache($this->servers, "{$key}::{$value}", Debug::getTime() - $begin_microtime, true, 'add');
+			return true;
+		}
+
+		Debug::cache($this->servers, "{$key}::{$value}", Debug::getTime() - $begin_microtime, false, 'add');
+		return false;
+	}
+
+	/**
+	 *
+	 * 替换已存在key下的元素
+	 * 如果 服务端不存在key,操作将失败。
+	 * @param string $key 用于存储值的键名。
+	 * @param mixed $value 存储的值
+	 * @return null | boolean 成功:true;失败:false
+	 */
+	public function replace($key, $value) {
+		$begin_microtime = Debug::getTime();
+
+		if (!is_scalar($value)) {
+			$value = serialize($value);
+		}
+		$tmpResult = $this->objMemcache->replace($key, $value);
+
+		if ($tmpResult) {
+			Debug::cache($this->servers, "{$key}::{$value}", Debug::getTime() - $begin_microtime, true, 'replace');
+			return true;
+		}
+
+		Debug::cache($this->servers, "{$key}::{$value}", Debug::getTime() - $begin_microtime, false, 'replace');
+		return false;
+	}
+
+	/**
+	 *
+	 * 将一个数值元素增加参数offset指定的大小。如果元素的值不是数值类型,返回false
+	 * 经实验发现,Memcached 扩展的increment方法存在bug,这里对其做了修复。
+	 * @param string $key 要增加值的元素的key。
+	 * @param int $offset 要将元素的值增加的大小。
+	 * @return int | false 成功:int(递增后的值);失败或key不存在:false
+	 * !!! 如果 $key 对应的值不是 int 型:通过 $this->get 方法返回的值不是 int 型
+	 * a、如果不是 boolean 型:结果是0
+	 * b、如果是 boolean 型:会以0为基准递增值。
+	 * !!! 所以,请尽量尽量不要对非 int 型的值使用 increment 方法
+	 */
+	public function increment($key, $offset = 1) {
+		$begin_microtime = Debug::getTime();
+
+		$tmpGetResult = $this->get($key);
+		if (is_null($tmpGetResult)) {// $key 对应的值不存在
+			Debug::cache($this->servers, "{$key}::{$offset}", Debug::getTime() - $begin_microtime, null, 'increment');
+			return false;
+		}
+
+		if (!Verify::naturalNumber($tmpGetResult)) {
+			Debug::cache($this->servers, "{$key}::{$offset}", Debug::getTime() - $begin_microtime, false, 'increment');
+			return false;
+		}
+
+		$tmpResult = $this->objMemcache->increment($key, $offset);
+		if (Verify::unsignedInt($tmpResult)) {
+			Debug::cache($this->servers, "{$key}::{$offset}", Debug::getTime() - $begin_microtime, $tmpResult, 'increment');
+			return $tmpResult;
+		}
+
+		Debug::cache($this->servers, "{$key}::{$offset}", Debug::getTime() - $begin_microtime, false, 'increment');
+		return false;
+	}
+
+	/**
+	 *
+	 * 减小一个数值元素的值,减小多少由参数offset决定。如果元素的值不是数值类型,返回false
+	 * 如果减小后的值小于0,则新的值被设置为0
+	 * 如果元素不存在,返回 null
+	 *
+	 * 经实验发现,Memcached 扩展的decrement方法存在bug,这里对其做了修复。
+	 * @param string $key 要减少值的元素的key。
+	 * @param int $offset 要将元素的值减少的大小。
+	 * @return int | false 成功:int(减少后的值,有可能是 0);失败或key不存在:false
+	 */
+	public function decrement($key, $offset = 1) {
+		$begin_microtime = Debug::getTime();
+
+		$tmpGetResult = $this->get($key);
+		if (is_null($tmpGetResult)) {// $key 对应的值不存在
+			Debug::cache($this->servers, "{$key}::{$offset}", Debug::getTime() - $begin_microtime, null, 'decrement');
+			return false;
+		}
+
+		if (!Verify::naturalNumber($tmpGetResult)) {
+			Debug::cache($this->servers, "{$key}::{$offset}", Debug::getTime() - $begin_microtime, false, 'decrement');
+			return false;
+		}
+
+		$tmpResult = $this->objMemcache->decrement($key, $offset);
+		if (Verify::naturalNumber($tmpResult)) {
+			Debug::cache($this->servers, "{$key}::{$offset}", Debug::getTime() - $begin_microtime, $tmpResult, 'decrement');
+			return $tmpResult;
+		}
+
+		Debug::cache($this->servers, "{$key}::{$offset}", Debug::getTime() - $begin_microtime, false, 'decrement');
+		return false;
+	}
+}

+ 62 - 0
KIF/Cli/.svn/entries

@@ -0,0 +1,62 @@
+10
+
+dir
+924
+svn://182.92.3.30/project/onepage/KIF/Cli
+svn://182.92.3.30/project
+
+
+
+2016-01-21T06:27:41.361155Z
+137
+yubin
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+140960d9-11d8-4647-83e1-275f326d1242
+
+SingleProcess.class.php
+file
+
+
+
+
+2016-02-15T00:10:47.000000Z
+342c17dc1fedcb7e7bc97d939ea36eb8
+2016-01-21T06:27:41.361155Z
+137
+yubin
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+6456
+

+ 5 - 0
KIF/Cli/.svn/prop-base/SingleProcess.class.php.svn-base

@@ -0,0 +1,5 @@
+K 14
+svn:executable
+V 1
+*
+END

+ 260 - 0
KIF/Cli/.svn/text-base/SingleProcess.class.php.svn-base

@@ -0,0 +1,260 @@
+<?php
+namespace KIF\Cli;
+
+use KIF\Verify;
+use Exception;
+use KIF\Core\Config;
+
+/**
+ * 这是一个辅助性的东西,当你希望某个操作只运行一个进程的时候(例如生成缓存,既然生成缓存,几十个同时跑,我觉得还不如不要呢)
+ * 还有像优享团结团后,发成功通知的短信,发了一次还发?sb了呢!
+ *
+ * @author gaoxiaogang@gmail.com
+ *
+ */
+class SingleProcess {
+    /**
+     * 唯一标识
+     * @var string
+     */
+    private $pid;
+
+    /**
+     * 最大生存时间 单位:秒
+     * @var int
+     */
+    private $timeout;
+
+    /**
+     * 是否检查操作系统进程
+     * 该值为true时,将检查操作系统中的进程是否存在。如果不存在,返回已超时。
+     * 只用于(unix/linux)
+     * @var boolean
+     */
+    private $checkSystemProcess;
+
+    public function __construct($pid, $timeout = 600, $checkSystemProcess = false) {
+        if (!self::isValidPid($pid)) {
+            throw new Exception('invalid $pid');
+        }
+
+        if (!Verify::int($timeout) || $timeout < 1) {
+            throw new Exception('invalid $lifetime');
+        }
+
+        if (!is_bool($checkSystemProcess)) {
+            throw new Exception('invalid $checkSystemProcess');
+        }
+
+        $this->pid = $pid;
+        $this->timeout = (int) $timeout;
+        $this->checkSystemProcess = $checkSystemProcess;
+    }
+
+    /**
+     * 1、检测一个进程是否正在运行,如果返回true 那么就是正在运行,如果false 表示已经结束,你可以放心使用!
+     * 2、当进程已经超时,也会返回false
+     * 3、如果指定$this->checkSystemProcess为true,将检查操作系统中的进程是否存在。如果不存在,返回false。
+     *
+     * @return boolean
+     */
+    public function isRun() {
+        $pf = $this->getPidPath();
+
+        # 不存在
+        if (!file_exists( $pf )) {
+            return false;
+        }
+
+        # 超时了
+        if ($this->isTimeout()) {
+            $this->kill();
+            return false;
+        }
+
+        # 系统进程不存在了,就认为前一个进程已经结束了。
+        if ($this->checkSystemProcess) {
+	        $spid = $this->getSystemPid();
+	        if (!self::existSystemProcess($spid)) {
+	        	return false;
+	        }
+        }
+
+        return true;
+    }
+
+    /**
+     * 开始运行一个进程(声明:这只是一个虚拟的辅助操作,不要乱想)
+     *
+     * @return Boolean
+     */
+    public function run() {
+        file_put_contents($this->getPidPath(), getmypid());
+
+        return true;
+    }
+
+	/**
+	 * 激活
+	 * 如果new SignleProcess 时指定的timeout是600秒,当这个时间到达时,另一个进程就会将当前进程杀死。
+	 * 而active方法的作用,就是重新计算超时时间。
+	 * @param $activeFrequency 激活频率   300秒,即五分钟激活一次
+	 * @return boolean
+	 */
+	public function active($activeFrequency = 300) {
+		static $time_prev = null;
+		if (is_null($time_prev)) {
+			$time_prev = time();
+		}
+
+		$time_now = time();
+	    # 达到激活频率,声明脚本还活着
+	    if ($time_now - $time_prev >= $activeFrequency) {
+	        $time_prev = $time_now;//更新上次激活时间
+	        $this->run();
+	        return true;
+	    }
+	    return false;
+	}
+
+    /**
+     * 进程结束
+     *
+     * @return Boolean
+     */
+    public function complete() {
+        @unlink($this->getPidPath());
+
+        return true;
+    }
+
+    /**
+     * 终止一个进程
+     *
+     * @return Boolean
+     */
+    public function kill() {
+    	$pf = $this->getPidPath();
+        if (!file_exists($pf)) {
+            return true;
+        }
+
+        if ($this->checkSystemProcess) {
+        	$spid = $this->getSystemPid();
+        	if ($spid) {
+        		exec("kill -9 {$spid}");
+        	}
+        }
+
+        @unlink($pf);
+        sleep(1);
+
+        return true;
+    }
+
+    /**
+     * 清除所有进程记录,不管有没结束
+     *
+     * @param boolean $checkSystemProcess 是否检查操作系统进程
+     * @return Boolean
+     */
+    static public function cleanup($checkSystemProcess = false) {
+        $path = self::getPath().'*';
+
+        foreach (glob($path) as $filename) {
+        	if ($checkSystemProcess) {
+        		$spid = file_get_contents($filename);
+        		if (self::existSystemProcess($spid)) {
+        			exec("kill -9 {$spid}");//同时杀死该pid对应的操作系统进程
+        		}
+        	}
+
+            @unlink($filename);
+        }
+        sleep(10);//休眠10秒
+        return true;
+    }
+
+    /**
+     * 取得缓存路径,如果不存在,自动创建
+     *
+     * @return String
+     */
+    static private function getPath() {
+    	# TODO 配置文件中 Log_Path
+    	$logPath = Config::getInstance ()->get ( 'Log_Path' );
+    	
+    	if(empty($logPath)){
+    		throw new Exception('Config  Log_Path is empty');
+    	}
+    	
+        $path  = $logPath . DS . 'sspfiles' . DS;
+        if (!file_exists($path)) {
+            if (!mkdir($path, 0777, true)) {
+                return false;
+            }
+        }
+        return $path;
+    }
+
+    /**
+     * 是否超时
+     * @return Boolean
+     */
+    private function isTimeout() {
+        if ((time() - filemtime($this->getPidPath())) > $this->timeout) {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * 获取存放pid的全路径,包括pid文件名
+     * @return string
+     */
+    private function getPidPath() {
+        return self::getPath() . md5($this->pid);
+    }
+
+    /**
+     * 获取系统进程pid
+     * @return int | false
+     */
+    private function getSystemPid() {
+        $spid = @file_get_contents($this->getPidPath());
+        if ($spid) {
+            return $spid;
+        }
+
+        return false;
+    }
+
+    /**
+     * 是否存在操作系统进程
+     *
+     * @param int $spid 操作系统进程id
+     * @return Boolean
+     */
+    static private function existSystemProcess($spid) {
+    	if (!Verify::int($spid) || $spid < 1) {
+    		return false;
+    	}
+        $cmd = 'ps -eo pid | egrep ^\ *'.$spid.'$';
+        $out = shell_exec($cmd);
+        return empty($out) ? false : true;
+    }
+
+    /**
+     * 是否有效的pid
+     * @param string $pid
+     * @return boolean
+     */
+    static private function isValidPid($pid) {
+        if (!is_string($pid) || empty($pid)) {
+            return false;
+        }
+
+        return true;
+    }
+}

+ 260 - 0
KIF/Cli/SingleProcess.class.php

@@ -0,0 +1,260 @@
+<?php
+namespace KIF\Cli;
+
+use KIF\Verify;
+use Exception;
+use KIF\Core\Config;
+
+/**
+ * 这是一个辅助性的东西,当你希望某个操作只运行一个进程的时候(例如生成缓存,既然生成缓存,几十个同时跑,我觉得还不如不要呢)
+ * 还有像优享团结团后,发成功通知的短信,发了一次还发?sb了呢!
+ *
+ * @author gaoxiaogang@gmail.com
+ *
+ */
+class SingleProcess {
+    /**
+     * 唯一标识
+     * @var string
+     */
+    private $pid;
+
+    /**
+     * 最大生存时间 单位:秒
+     * @var int
+     */
+    private $timeout;
+
+    /**
+     * 是否检查操作系统进程
+     * 该值为true时,将检查操作系统中的进程是否存在。如果不存在,返回已超时。
+     * 只用于(unix/linux)
+     * @var boolean
+     */
+    private $checkSystemProcess;
+
+    public function __construct($pid, $timeout = 600, $checkSystemProcess = false) {
+        if (!self::isValidPid($pid)) {
+            throw new Exception('invalid $pid');
+        }
+
+        if (!Verify::int($timeout) || $timeout < 1) {
+            throw new Exception('invalid $lifetime');
+        }
+
+        if (!is_bool($checkSystemProcess)) {
+            throw new Exception('invalid $checkSystemProcess');
+        }
+
+        $this->pid = $pid;
+        $this->timeout = (int) $timeout;
+        $this->checkSystemProcess = $checkSystemProcess;
+    }
+
+    /**
+     * 1、检测一个进程是否正在运行,如果返回true 那么就是正在运行,如果false 表示已经结束,你可以放心使用!
+     * 2、当进程已经超时,也会返回false
+     * 3、如果指定$this->checkSystemProcess为true,将检查操作系统中的进程是否存在。如果不存在,返回false
+     *
+     * @return boolean
+     */
+    public function isRun() {
+        $pf = $this->getPidPath();
+
+        # 不存在
+        if (!file_exists( $pf )) {
+            return false;
+        }
+
+        # 超时了
+        if ($this->isTimeout()) {
+            $this->kill();
+            return false;
+        }
+
+        # 系统进程不存在了,就认为前一个进程已经结束了。
+        if ($this->checkSystemProcess) {
+	        $spid = $this->getSystemPid();
+	        if (!self::existSystemProcess($spid)) {
+	        	return false;
+	        }
+        }
+
+        return true;
+    }
+
+    /**
+     * 开始运行一个进程(声明:这只是一个虚拟的辅助操作,不要乱想)
+     *
+     * @return Boolean
+     */
+    public function run() {
+        file_put_contents($this->getPidPath(), getmypid());
+
+        return true;
+    }
+
+	/**
+	 * 激活
+	 * 如果new SignleProcess 时指定的timeout是600秒,当这个时间到达时,另一个进程就会将当前进程杀死。
+	 * 而active方法的作用,就是重新计算超时时间。
+	 * @param $activeFrequency 激活频率   300秒,即五分钟激活一次
+	 * @return boolean
+	 */
+	public function active($activeFrequency = 300) {
+		static $time_prev = null;
+		if (is_null($time_prev)) {
+			$time_prev = time();
+		}
+
+		$time_now = time();
+	    # 达到激活频率,声明脚本还活着
+	    if ($time_now - $time_prev >= $activeFrequency) {
+	        $time_prev = $time_now;//更新上次激活时间
+	        $this->run();
+	        return true;
+	    }
+	    return false;
+	}
+
+    /**
+     * 进程结束
+     *
+     * @return Boolean
+     */
+    public function complete() {
+        @unlink($this->getPidPath());
+
+        return true;
+    }
+
+    /**
+     * 终止一个进程
+     *
+     * @return Boolean
+     */
+    public function kill() {
+    	$pf = $this->getPidPath();
+        if (!file_exists($pf)) {
+            return true;
+        }
+
+        if ($this->checkSystemProcess) {
+        	$spid = $this->getSystemPid();
+        	if ($spid) {
+        		exec("kill -9 {$spid}");
+        	}
+        }
+
+        @unlink($pf);
+        sleep(1);
+
+        return true;
+    }
+
+    /**
+     * 清除所有进程记录,不管有没结束
+     *
+     * @param boolean $checkSystemProcess 是否检查操作系统进程
+     * @return Boolean
+     */
+    static public function cleanup($checkSystemProcess = false) {
+        $path = self::getPath().'*';
+
+        foreach (glob($path) as $filename) {
+        	if ($checkSystemProcess) {
+        		$spid = file_get_contents($filename);
+        		if (self::existSystemProcess($spid)) {
+        			exec("kill -9 {$spid}");//同时杀死该pid对应的操作系统进程
+        		}
+        	}
+
+            @unlink($filename);
+        }
+        sleep(10);//休眠10秒
+        return true;
+    }
+
+    /**
+     * 取得缓存路径,如果不存在,自动创建
+     *
+     * @return String
+     */
+    static private function getPath() {
+    	# TODO 配置文件中 Log_Path
+    	$logPath = Config::getInstance ()->get ( 'Log_Path' );
+    	
+    	if(empty($logPath)){
+    		throw new Exception('Config  Log_Path is empty');
+    	}
+    	
+        $path  = $logPath . DS . 'sspfiles' . DS;
+        if (!file_exists($path)) {
+            if (!mkdir($path, 0777, true)) {
+                return false;
+            }
+        }
+        return $path;
+    }
+
+    /**
+     * 是否超时
+     * @return Boolean
+     */
+    private function isTimeout() {
+        if ((time() - filemtime($this->getPidPath())) > $this->timeout) {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * 获取存放pid的全路径,包括pid文件名
+     * @return string
+     */
+    private function getPidPath() {
+        return self::getPath() . md5($this->pid);
+    }
+
+    /**
+     * 获取系统进程pid
+     * @return int | false
+     */
+    private function getSystemPid() {
+        $spid = @file_get_contents($this->getPidPath());
+        if ($spid) {
+            return $spid;
+        }
+
+        return false;
+    }
+
+    /**
+     * 是否存在操作系统进程
+     *
+     * @param int $spid 操作系统进程id
+     * @return Boolean
+     */
+    static private function existSystemProcess($spid) {
+    	if (!Verify::int($spid) || $spid < 1) {
+    		return false;
+    	}
+        $cmd = 'ps -eo pid | egrep ^\ *'.$spid.'$';
+        $out = shell_exec($cmd);
+        return empty($out) ? false : true;
+    }
+
+    /**
+     * 是否有效的pid
+     * @param string $pid
+     * @return boolean
+     */
+    static private function isValidPid($pid) {
+        if (!is_string($pid) || empty($pid)) {
+            return false;
+        }
+
+        return true;
+    }
+}

+ 65 - 0
KIF/Cookie.class.php

@@ -0,0 +1,65 @@
+<?php
+namespace KIF;
+
+use KIF\Core\Request;
+use KIF\String\Filter;
+
+
+/**
+ *
+ * cookie处理类
+ * @author gaoxiaogang@gmail.com
+ *
+ */
+class Cookie {
+
+	/**
+	 * 封闭cookie的读操作,便于cookie的统一处理
+	 *
+	 * @param string $key
+	 * @return string | Boolean 不存在返回false;存在返回值
+	 */
+    static public function get($key, $filters = array(Filter::HTMLSPECIALCHARS, Filter::TRIM)) {
+    	return Request::c($key, $filters);
+    }
+
+    /**
+     * 包装 php的 setcookie,便于cookie的统一处理
+     *
+     * @param string $key cookie名
+     * @param string $value cookie值
+     * @param int $expiration cookie过期的时间。 实际发送的值可以是一个Unix时间戳(自197011日起至失效时间的整型秒数),或者是一个从现在算起的以秒为单位的数字。
+     *            对于后一种情况,这个秒数不能超过60×60×24×3030天时间的秒数);如果失效的值大于这个值,会将其作为一个真实的Unix时间戳来处理而不是自当前时间的偏移。
+     * @param string $cookie_domain 默认为 DOMAIN常量,如未指定,则为 当前访问的根域名
+     * @param string $cookie_path cookie保存路径,默认为根目录 /
+     * @param boolean $secure 是否只能通过https协议访问。默认为false
+     * @param boolean $httponly 是否只能通过http协议读取cookie。值为true时,客户端的javascript不能读到该cookie。默认为false
+     * @return boolean
+     */
+    static public function set($key, $value, $expiration = 0, $cookie_domain = null, $cookie_path = '/', $secure = false, $httponly = false) {
+    	if (is_null($cookie_domain)) {
+    		if (defined('DOMAIN')) {
+    			$cookie_domain = DOMAIN;
+    		} else {
+    			$cookie_domain = Request::rootDomain();
+    		}
+    	}
+    	if (is_null($cookie_path)) {
+    		$cookie_path = '/';
+    	}
+    	if (is_null($secure)) {
+    		$secure = false;
+    	}
+    	if (is_null($httponly)) {
+    		$httponly = false;
+    	}
+    	# 如果 $expiration 指定的过期时间小于30天,则
+    	if ($expiration > 0 && $expiration <= 60*60*24*30) {
+    		$expiration += time();
+    	}
+
+    	# 设置 $_COOKIE 变量,否则当前进程写完cookie后是无法立即读取到的。
+    	$_COOKIE[$key] = $value;
+    	return setcookie($key, $value, $expiration, $cookie_path, $cookie_domain, $secure, $httponly);
+    }
+}

+ 436 - 0
KIF/Core/.svn/entries

@@ -0,0 +1,436 @@
+10
+
+dir
+924
+svn://182.92.3.30/project/onepage/KIF/Core
+svn://182.92.3.30/project
+
+
+
+2016-01-21T06:27:41.361155Z
+137
+yubin
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+140960d9-11d8-4647-83e1-275f326d1242
+
+BKController.class.php
+file
+
+
+
+
+2016-02-15T00:10:45.000000Z
+1f18953a89064bd3d4c25b60ae7193a9
+2016-01-21T06:27:41.361155Z
+137
+yubin
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+1577
+
+Model.class.php
+file
+
+
+
+
+2016-02-15T00:10:45.000000Z
+1336cb2fb54dbd4d7f8fd5c7da07dfea
+2016-01-21T06:27:41.361155Z
+137
+yubin
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+970
+
+WXController.class.php
+file
+
+
+
+
+2016-02-15T00:10:45.000000Z
+4dc4e1e9361cfe1659b283d50fd2e906
+2016-01-21T06:27:41.361155Z
+137
+yubin
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+3303
+
+Controller.class.php
+file
+
+
+
+
+2016-02-15T00:10:45.000000Z
+1c15715bf1b35086b25ed60e6add86cb
+2016-01-21T06:27:41.361155Z
+137
+yubin
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+5600
+
+Config.class.php
+file
+
+
+
+
+2016-02-15T00:10:45.000000Z
+301b3d8ab14cba06995359b387b77e3d
+2016-01-21T06:27:41.361155Z
+137
+yubin
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+2762
+
+AbstractDaemon.class.php
+file
+
+
+
+
+2016-02-15T00:10:45.000000Z
+7b9a6a473a64ba49a8b84aaf15fc25d5
+2016-01-21T06:27:41.361155Z
+137
+yubin
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+1644
+
+Core.class.php
+file
+
+
+
+
+2016-02-15T00:10:45.000000Z
+7bccc8010604a79af00999a1b0180781
+2016-01-21T06:27:41.361155Z
+137
+yubin
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+1652
+
+Request.class.php
+file
+
+
+
+
+2016-02-15T00:10:45.000000Z
+c6d6ea0ec82886ab188fe39cccf3596c
+2016-01-21T06:27:41.361155Z
+137
+yubin
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+19980
+
+FController.class.php
+file
+
+
+
+
+2016-02-15T00:10:45.000000Z
+1a307a2f24e59a1a5f3bf1a46369b5ea
+2016-01-21T06:27:41.361155Z
+137
+yubin
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+7799
+
+View.class.php
+file
+
+
+
+
+2016-02-15T00:10:45.000000Z
+698615375e6488ce686b00aea944647a
+2016-01-21T06:27:41.361155Z
+137
+yubin
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+2467
+
+IPC.class.php
+file
+
+
+
+
+2016-02-15T00:10:45.000000Z
+022f09eb88fc03ebd9581c9e81f88392
+2016-01-21T06:27:41.361155Z
+137
+yubin
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+1456
+
+PermissionController.class.php
+file
+
+
+
+
+2016-02-15T00:10:45.000000Z
+60cdb5a50c8423c303ae5ae8b6f52284
+2016-01-21T06:27:41.361155Z
+137
+yubin
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+5810
+

+ 5 - 0
KIF/Core/.svn/prop-base/AbstractDaemon.class.php.svn-base

@@ -0,0 +1,5 @@
+K 14
+svn:executable
+V 1
+*
+END

+ 5 - 0
KIF/Core/.svn/prop-base/BKController.class.php.svn-base

@@ -0,0 +1,5 @@
+K 14
+svn:executable
+V 1
+*
+END

+ 5 - 0
KIF/Core/.svn/prop-base/Config.class.php.svn-base

@@ -0,0 +1,5 @@
+K 14
+svn:executable
+V 1
+*
+END

+ 5 - 0
KIF/Core/.svn/prop-base/Controller.class.php.svn-base

@@ -0,0 +1,5 @@
+K 14
+svn:executable
+V 1
+*
+END

+ 5 - 0
KIF/Core/.svn/prop-base/Core.class.php.svn-base

@@ -0,0 +1,5 @@
+K 14
+svn:executable
+V 1
+*
+END

+ 5 - 0
KIF/Core/.svn/prop-base/FController.class.php.svn-base

@@ -0,0 +1,5 @@
+K 14
+svn:executable
+V 1
+*
+END

+ 5 - 0
KIF/Core/.svn/prop-base/IPC.class.php.svn-base

@@ -0,0 +1,5 @@
+K 14
+svn:executable
+V 1
+*
+END

+ 5 - 0
KIF/Core/.svn/prop-base/Model.class.php.svn-base

@@ -0,0 +1,5 @@
+K 14
+svn:executable
+V 1
+*
+END

+ 5 - 0
KIF/Core/.svn/prop-base/PermissionController.class.php.svn-base

@@ -0,0 +1,5 @@
+K 14
+svn:executable
+V 1
+*
+END

+ 5 - 0
KIF/Core/.svn/prop-base/Request.class.php.svn-base

@@ -0,0 +1,5 @@
+K 14
+svn:executable
+V 1
+*
+END

+ 5 - 0
KIF/Core/.svn/prop-base/View.class.php.svn-base

@@ -0,0 +1,5 @@
+K 14
+svn:executable
+V 1
+*
+END

+ 5 - 0
KIF/Core/.svn/prop-base/WXController.class.php.svn-base

@@ -0,0 +1,5 @@
+K 14
+svn:executable
+V 1
+*
+END

+ 88 - 0
KIF/Core/.svn/text-base/AbstractDaemon.class.php.svn-base

@@ -0,0 +1,88 @@
+<?php
+namespace KIF\Core;
+
+use KIF\Cli\SingleProcess;
+
+abstract class AbstractDaemon extends Controller {
+	
+	public function run() {
+		$action = $this->action;
+		$this->$action();
+	}
+	
+	/**
+	 * 
+	 * 要完成的业务逻辑,由子类实现
+	 */
+	abstract protected function doSomething();
+	
+	/**
+	 * 
+	 * 脚本存活时间,可由子类重写
+	 * @var int
+	 */
+	protected $aliveTime = 7200;// 2小时
+	
+	
+	/**
+	 * 
+	 * Enter description here ...
+	 * @var KIF\Cli\SingleProcess
+	 */
+	private $objSingleProcess;
+	
+	public function __construct() {
+		$processId = realpath(__FILE__) . '-' . get_class($this);
+		
+		$timeout = 2 * $this->aliveTime;// 允许外部脚本kill当前脚本的时间,设置为存活时间的2倍。确保存活时间内,脚本不会被意外杀死。
+		$this->objSingleProcess = new SingleProcess($processId, $timeout, true);
+		
+	}
+	
+	/**
+	 *
+	 * 判断脚本是否还能存活
+	 * @return Boolean
+	 */
+	protected function isAlive() {
+		static $time_init = null;
+		if (is_null($time_init)) {
+			$time_init = time();
+		}
+	
+		$time_now = time();
+	
+	    if ($time_now - $time_init >= $this->aliveTime) {//超过脚本的生存时间
+	        return false;
+	    }
+	
+	    return true;
+	}
+	
+	
+	protected function doDefault() {
+		if ($this->objSingleProcess->isRun()) {
+		    echo "已经有进程在跑了\r\n";
+		    exit();
+		}
+		$this->objSingleProcess->run();
+		
+		do {
+			if (!$this->isAlive()) {
+	    		break;
+	    	}
+	    	
+	    	$this->doSomething();
+	    	
+	    	
+			
+			usleep(1000000);
+			echo "休息...\r\n";
+		} while ( true );
+		
+		$this->objSingleProcess->complete();
+		echo "done\r\n";
+		exit ();
+	}
+	
+}

+ 88 - 0
KIF/Core/.svn/text-base/BKController.class.php.svn-base

@@ -0,0 +1,88 @@
+<?php
+namespace KIF\Core;
+
+use Cas\Module\BackPassport;
+
+/**
+ * 继承自MVC里的Controller,封装了后台用户登录状态、用户信息等一些方法
+ * @author lishumingoo@gmail.com
+ */
+class BKController extends \KIF\Core\Controller {
+	
+	/**
+	 * 判断用户登录状态
+	 * @return Boolean
+	 */
+	static public function isLogin() {
+		return (boolean) self::getUid();
+	}
+	
+	/**
+	 * 要求未登陆用户去登陆
+	 * @return boolean
+	 */
+	static public function requireLogin() {
+		if (!self::isLogin()) {
+			$referer = Request::url();
+			$url = Request::schemeDomain() . '/?c=Admin_BackUser&a=Login&referer=' . urlencode($referer);
+			parent::redirect($url);
+		}
+	
+		return true;
+	}
+	
+	/**
+	 * 获取用户id
+	 * @return false | int
+	 */
+	static public function getUid() {
+		$user = self::getUser();
+		if (!$user) {
+			return false;
+		}
+		
+		return $user['uid'];
+	}
+	
+	/**
+	 * 获取用户名
+	 * 这里的用户名是不靠谱的!!!
+	 * @return string
+	 */
+	static public function getUsername() {
+		$user = self::getUser();
+		if (!$user) {
+			return false;
+		}
+		
+		return $user['name'];
+	}
+	
+	/**
+	 *
+	 * 从Cookie中获取用户的登陆信息
+	 * @return boolean | array
+	 */
+	public function getUser() {
+		static $user = null;
+		if ($user) {
+			return $user;
+		}
+	
+		$user = array();
+		
+			$objBackPassport = new BackPassport();
+			$tmpResult = $objBackPassport->getLoginInfo();
+			if ($tmpResult->isSuccess()) {
+				$user = $tmpResult->getData();
+			}
+			
+	
+		return $user;
+	}
+	
+	public function run() {
+		$action = $this->action;
+		$this->$action();
+	}
+}

+ 144 - 0
KIF/Core/.svn/text-base/Config.class.php.svn-base

@@ -0,0 +1,144 @@
+<?php
+namespace KIF\Core;
+
+use Exception;
+
+/**
+ * 
+ * 配置类。作用:
+ * 1、作为框架需要配置参数的那些类(如KIF\Cache、KIF\Dao 里的类)的参数获取接口。
+ * 2、为KIF框架下的不同APP间 php in-process calls (APP间PHP进程内调用) 提供核心支持
+ * 
+ * @author gaoxiaogang@gmail.com
+ *
+ */
+class Config {
+	
+	static private $instance;
+	
+	/**
+	 * 所有app的配置集
+	 * array(
+	 * 		md5($app_conf_path) => array(
+	 * 
+	 * 		),
+	 * 		... ,
+	 * );
+	 * @var array
+	 */
+	static protected $appConfigs = array();
+	
+	/**
+	 * 
+	 * 当前app的配置
+	 * @var array
+	 */
+	protected $appConfig;
+	
+	/**
+	 * 
+	 * 不允许直接 new
+	 */
+	private function __construct() {
+		
+	}
+	
+	/**
+	 * 
+	 * 载入配置,如果成功,返回instance
+	 * @param string $app_conf_path 配置文件的路径
+	 * @return KIF\Core\Config
+	 */
+	static public function load($app_conf_path) {
+		$app_conf_path = realpath($app_conf_path);
+		$app_conf_key = md5($app_conf_path);
+		if (!isset(self::$appConfigs[$app_conf_key])) {
+			$config = include($app_conf_path);
+			if (!is_array($config)) {
+				throw new Exception("load app_conf_path fail: {$app_conf_path}");
+			}
+			# 将conf的path存进来
+			$config['conf_path'] = $app_conf_path;
+			
+			self::$appConfigs[$app_conf_key] = $config;
+		}
+		
+		if (is_null(self::$instance)) {
+			self::$instance = new self();
+		}
+		
+		self::$instance->appConfig = self::$appConfigs[$app_conf_key];
+		
+		return self::$instance;
+	}
+	
+	/**
+	 * 
+	 * 单例模式
+	 * @param string $app_conf_path
+	 * @return KIF\Core\Config
+	 */
+	static public function getInstance() {
+		if (is_null(self::$instance)) {
+			self::$instance = new self();
+		}
+		
+		if (is_null(self::$instance->appConfig)) {
+			throw new Exception('instance appconfig is null, pleace run KIF\Core\Config::load !');
+		}
+		
+		return self::$instance;
+	}
+	
+	/**
+	 * 
+	 * 获取指定配置项的值
+	 * @param string $key
+	 * @return mixed
+	 */
+	public function get($key) {
+		if (empty($key)) {
+            return false;
+        }   
+    
+        $keys = explode('.', $key);
+    
+        $value = $this->appConfig;
+    
+        foreach ($keys as $tmpKey) {
+            if (!isset($value[$tmpKey])) {
+                return false;
+            }   
+            $value = $value[$tmpKey];
+        }   
+        return $value;
+	}
+	
+	/**
+	 * 
+	 * 指定配置项是否设置了
+	 * @param string $key
+	 * @return boolean
+	 */
+	public function exists($key) {
+		return isset($this->appConfig[$key]);
+	}
+	
+	/**
+	 * 
+	 * 获取当前的配置信息
+	 * @return array
+	 */
+	public function current() {
+		return $this->appConfig;
+	}
+	
+	/**
+	 * 
+	 * 获取所有app的配置集
+	 * @return array
+	 */
+	static public function all() {
+		return self::$appConfigs;
+	}
+}

+ 238 - 0
KIF/Core/.svn/text-base/Controller.class.php.svn-base

@@ -0,0 +1,238 @@
+<?php
+namespace KIF\Core;
+
+/**
+ * 定义MVC里的C抽象类,即Controller
+ * @author gaoxiaogang@gmail.com
+ */
+use KIF\String\Filter;
+
+use KIF\Core\View;
+use Exception;
+
+abstract class Controller {
+	protected $action;
+	
+	/**
+	 * 
+	 * 所有输出到模版的变量,都存放到这里面,以便统一管理
+	 * @var array
+	 */
+	protected $output = array();
+	
+	protected $tpl;
+	
+	/**
+     * 导航菜单
+     *
+     * @var Array
+     */
+    protected $navMenus = array();
+	
+	/**
+	 * 
+	 * 控制器入口方法
+	 * 
+	 */
+	abstract public function run();
+	
+	/**
+	 * 
+	 * 设定指定的名、值,模版里可以使用到
+	 * @param string $name
+	 * @param mixed $value
+	 * @throws Exception
+	 */
+	protected function setOutput($name, $value) {
+        if (!is_string($name)) {
+            throw new Exception('set output to template error, name not string !');
+        }
+        $this->output[$name] = $value;
+    }
+    
+    /**
+     * 
+     * 设置行为
+     * @param string $action
+     */
+    public function setAction($action) {
+    	if (empty($action)) {
+    		$action = 'default';
+    	}
+    	$this->action = 'do' . ucfirst($action);
+    }
+    
+	/**
+     * 添加导航栏
+     *
+     * @param string $title
+     * @param string $href
+     * @param string $target
+     * @param string $icon
+     * @return \KIF\Core\Controller
+     */
+    protected function addNavMenu($title, $href = null, $target = '_self', $icon = null) {
+        $this->navMenus[] = array(
+            'title' => $title,
+            'href' => $href,
+        	'target'	=> $target,
+            'icon' => $icon,
+        );
+        return $this;
+    }
+    
+    /**
+     * 
+     * 设置一批输出
+     * @param array $outputs
+     * 
+     */
+    protected function setOutputs(array $outputs) {
+    	$this->output = array_merge($this->output, $outputs);
+    }
+	
+	/**
+	 * 
+	 * 渲染结果
+	 * @param boolean $return 是否返回。true:返回渲染结果;false:直接输出结果
+	 * @return string | NULL
+	 */
+	public function render($return = false) {
+		if (!$this->tpl) {// 没有指定模文件
+			return null;
+		}
+		$objView = new View();
+		
+		$this->output['navMenus'] = $this->navMenus;
+		
+		$config = Config::getInstance()->current();
+		$this->output['web_cfg'] = $config['web_cfg'];
+		
+		$objView->assign($this->output);
+		
+		if ($return) {
+			return $objView->r($this->tpl);
+		} else {
+			$objView->d($this->tpl);
+		}
+	}
+	
+	/**
+	 *
+	 * 操作失败
+	 * !!如果页面请求的参数里含有 cross_cb,则认为是需要做跨域ajax的支持,跳转到 cross_cb 参数里指定的url
+	 * @param string $msg 失败描述
+	 */
+	protected function ajax_fail_exit($msg) {
+		$return = array(
+			'ok'	=> false,
+			'msg'	=> $msg,
+		);
+
+		# 支持跨域的AJAX GET,其实是基于jQuery的$.getJson实现的。之所以把AJAX POST、GET做不同实现,是出于性能和可用性考虑。
+		$jsonp_cb = Request::r('jsonp_cb', Filter::TRIM);
+		if ($jsonp_cb) {
+			$jsonp_cb = Filter::htmlspecialchars($jsonp_cb);// 防止css漏洞
+			$this->echo_exit("{$jsonp_cb}(".json_encode($return).")");
+		}
+
+		$this->echo_exit(json_encode($return));
+	}
+	
+	/**
+	 *
+	 * 操作成功
+	 * !!如果页面请求的参数里含有 cross_cb,则认为是需要做跨域ajax的支持,跳转到 cross_cb 参数里指定的url
+	 * @param string $msg 成功描述
+	 */
+	protected function ajax_success_exit($msg) {
+		$return = array(
+			'ok'	=> true,
+			'msg'	=> $msg,
+		);
+
+		# 支持跨域的AJAX GET,其实是基于jQuery的$.getJson实现的。之所以把AJAX POST、GET做不同实现,是出于性能和可用性考虑。
+		$jsonp_cb = Request::r('jsonp_cb', Filter::TRIM);
+		if ($jsonp_cb) {
+			$jsonp_cb = Filter::htmlspecialchars($jsonp_cb);// 防止css漏洞
+			$this->echo_exit("{$jsonp_cb}(".json_encode($return).")");
+		}
+
+		$this->echo_exit(json_encode($return));
+	}
+	
+	protected function echo_exit($msg) {
+		echo $msg;
+		exit;
+	}
+	
+	protected function echo_msg($msg) {
+		echo '['.date('Y-m-d H:i:s').'] '. $msg;
+		$this->newline();
+	}
+	
+	/**
+	 * 
+	 * 输出新行。
+	 */
+	protected function newline() {
+		if (Request::isCLI()) {
+			echo "\r\n";
+		} else {
+			echo "<br />";
+		}
+	}
+	
+	/**
+	 * 链接跳转
+	 * @param string $url 跳转的url
+	 * @return void
+	 **/
+	protected function redirect($url,$status ='') {
+		if ($status == '301') {
+			header("HTTP/1.1 301 Moved Permanently");
+		}
+		if (!empty($url)) {
+			header("Location: ".$url."");
+		}
+		exit;
+	}
+	
+	/**
+	 * 输出错误消息 - 后台
+	 * @author li.shuming@kimiss.com
+	 * @param string $msg
+	 */
+	protected function fail_exit_bs($msg = null) {
+		$permission_template_dir = Config::getInstance()->get('App_Path') . DS . 'template_dir';
+		$this->tpl = $permission_template_dir . '/prompt_message';
+		$this->setOutputs(array(
+				'type'	=> 'fail',
+				'msg'	=> $msg,
+				'referer'	=> Request::referer(),
+				'header_tpl'=> $permission_template_dir . '/header.html',
+				'bottom_tpl'=> $permission_template_dir . '/bottom.html',
+		));
+		$this->render();
+		exit;
+	}
+	
+	/**
+	 * 输出成功消息 - 后台
+	 * @author li.shuming@kimiss.com
+	 * @param string $msg
+	 */
+	protected function success_exit_bs($msg = null) {
+		$permission_template_dir = Config::getInstance()->get('App_Path') . DS . 'template_dir';
+		$this->tpl = $permission_template_dir . '/prompt_message';
+		$this->setOutputs(array(
+				'type'	=> 'success',
+				'msg'	=> $msg,
+				'referer'	=> Request::referer(),
+				'header_tpl'=> $permission_template_dir . '/header.html',
+				'bottom_tpl'=> $permission_template_dir . '/bottom.html',
+		));
+		$this->render();
+		exit;
+	}
+}

+ 59 - 0
KIF/Core/.svn/text-base/Core.class.php.svn-base

@@ -0,0 +1,59 @@
+<?php
+namespace KIF\Core;
+
+use Exception;
+
+const VERSION	= '0.0.1';
+const AUTHOR	= 'gaoxiaogang@gmail.com';
+
+class Core {
+	static public function autoload($class_name) {
+//		var_dump($class_name);
+		
+		$class_name = str_replace('/', '', $class_name);
+		$class_name = str_replace('.', '', $class_name);
+		
+		$class_name = str_replace('\\', '/', $class_name);
+		$tmp_ds_pos = strpos($class_name, '/');
+		if ($tmp_ds_pos !== false) {
+			$root_namespace = substr($class_name, 0, $tmp_ds_pos);
+			$class_name = substr($class_name, $tmp_ds_pos+1);
+		} else {
+			$root_namespace = '';
+		}
+		# 当前app的库路径
+		if ($root_namespace) {
+			if ($root_namespace == 'KIF') {
+				$class_path = KIF_PATH . DS . $class_name . '.class.php';
+			} else {
+				try {# 框架一定会先加载KIF命名空间下的类,所以到这里Config肯定已被外部app初始化了
+					$allConfigs = Config::all();
+					foreach ($allConfigs as $config) {
+						if ($config['Namespace'] == $root_namespace) {
+							$app_lib_path = $config['Lib_Path'];
+							$class_path = $app_lib_path . DS . $class_name . '.class.php';
+							
+							break;
+						}
+					}
+				} catch (Exception $e) {
+					// do nothing
+				}
+			}
+			if (file_exists($class_path)) {
+				return require_once($class_path);
+			}
+		} else {
+			$class_path = KIF_PATH . DS . $class_name . '.class.php';
+			if (file_exists($class_path)) {
+				return require_once($class_path);
+			} else {
+				$app_lib_path = Config::getInstance()->get('Lib_Path');
+				$class_path = $app_lib_path . DS . $class_name . '.class.php';
+				if (file_exists($class_path)) {
+					return require_once($class_path);
+				}
+			}
+		}
+	}
+}

+ 400 - 0
KIF/Core/.svn/text-base/FController.class.php.svn-base

@@ -0,0 +1,400 @@
+<?php
+
+namespace KIF\Core;
+
+use Exception;
+
+abstract class FController {
+	
+	/**
+	 * @var 请求
+	 */
+	protected $_setTemplate = false;
+	
+	/**
+	 * @var 模板类
+	 */
+	protected $_view;
+	
+	
+	/**
+	 *
+	 * 所有输出到模版的变量,都存放到这里面,以便统一管理
+	 * @var array
+	 */
+	protected $output = array();
+	
+	protected $tpl;
+	
+	/**
+	 * 导航菜单
+	 *
+	 * @var Array
+	 */
+	protected $navMenus = array();
+	
+	
+	protected $telData ;
+	
+	
+	public function __construct() {
+		//这行一定放在最前面; *因为__set方法用到它(当$this->xx = xx 的时候调用此方法)*
+		$this->_view = new CViewRender ();
+		$this->telData = $this->includeTemplateData();
+		
+		$this->ctl = $this->getController ();
+		$this->act = $this->getAction ();
+		$this->exe = $this->getExecName ();
+	
+	}
+	
+	/**
+	 * 加载环境
+	 */
+	abstract public function prepare($args);
+	
+	/**
+	 * 回显html内容
+	*/
+	public function display() {
+		if ($this->_setTemplate) {
+			$content = $this->_view->display ();
+			return $content;
+		}
+	}
+	
+	/**
+	 * 获取模板解析后的内容
+	 */
+	public function fetch($file) {
+		
+		///////////////增加/////////////////
+		if(isset($this->telData[$file])){
+			$this->tpl = $this->telData[$file];
+			return $this->render(true);
+		}
+		////////////////////////////////
+		
+		$this->setTemplate ( $file );
+		$content = $this->_view->fetch ();
+		return $content;
+	}
+	
+	/**
+	 * 设置模板
+	 */
+	public function setTemplate($file) {
+		///////////////增加/////////////////
+		if(isset($this->telData[$file])){
+			$this->tpl = $this->telData[$file];
+			$this->render();
+			return true;
+		}
+		////////////////////////////////
+		
+		
+		$this->_setTemplate = true;
+		$this->_view->setTemplate ( $file );
+	}
+	
+	/**
+	 * 魔术方法,自动给模板提供数据 *important!*
+	 */
+	public function __set($key, $val) {
+		$this->$key = $val;
+		$this->setOutput($key, $val);
+		//再分配给模板
+		$this->_view->set ( $key, $val );
+	}
+	
+	/**
+	 * 魔术方法,控制器中,调用没有的方法,直接到request中调用 *important!*
+	 * 如:$this->getController();
+	 */
+	public function __call($method, $args) {
+		//return call_user_func_array(array(CBase::$request, $method), $args);
+	}
+	
+	/**
+	 * 消息提示,后续扩展
+	 */
+	public function showMessage($msg, $url = '') {
+	
+		$str = '<script>';
+		if ($msg) {
+			$str .= "alert('$msg');";
+		}
+		if ($url) {
+			$str .= "location.href ='$url';";
+		} else {
+			if ($second) {
+				$str .= "top.location.reload();";
+			} else {
+				$str .= "history.go(-1);";
+			}
+		}
+		$str .= '</script>';
+		die ( $str );
+	}
+	
+	/**
+	 * 表单提交后,映射到相应的处理方法
+	 */
+	public function submitMap() {
+		$funcName = "_" . strtolower ( $this->act ) . $this->exe;
+		if (! method_exists ( $this, $funcName )) {
+			$this->showMessage ( '参数错误,非法操作~' );
+		}
+		//处理
+		$this->$funcName ();
+	}
+	
+	
+	/**
+	 * 获取控制器名称
+	 */
+	public function getController()
+	{
+		$conName = ucfirst(strip_tags($_REQUEST['c']));
+		return $conName ? $conName : 'Default';
+	}
+	
+	/**
+	 * 获取动作
+	 */
+	public function getAction()
+	{
+		return isset($_REQUEST['a']) ?  ucfirst(strip_tags($_REQUEST['a'])) : 'Default';
+	}
+	
+	/**
+	 * 获取动作
+	 */
+	public function getExecName()
+	{
+		return isset($_REQUEST['e']) ?  ucfirst(strip_tags($_REQUEST['e'])) : '';
+	}
+	
+	
+	/**
+	 *
+	 * 渲染结果
+	 * @param boolean $return 是否返回。true:返回渲染结果;false:直接输出结果
+	 * @return string | NULL
+	 */
+	public function render($return = false) {
+		if (!$this->tpl) {// 没有指定模文件
+			return null;
+		}
+		
+		$objView = new View();
+	
+		$this->output['navMenus'] = $this->navMenus;
+	
+		$config = Config::getInstance()->current();
+		$this->output['web_cfg'] = $config['web_cfg'];
+	
+		$objView->assign($this->output);
+		
+		if ($return) {
+			return $objView->r($this->tpl);
+		} else {
+			$objView->d($this->tpl);
+		}
+	}
+	
+	/**
+	 * 添加导航栏
+	 *
+	 * @param string $title
+	 * @param string $href
+	 * @param string $target
+	 * @param string $icon
+	 * @return \KIF\Core\Controller
+	 */
+	protected function addNavMenu($title, $href = null, $target = '_self', $icon = null) {
+		$this->navMenus[] = array(
+				'title' => $title,
+				'href' => $href,
+				'target'	=> $target,
+				'icon' => $icon,
+		);
+		return $this;
+	}
+	
+	/**
+	 *
+	 * 设定指定的名、值,模版里可以使用到
+	 * @param string $name
+	 * @param mixed $value
+	 * @throws Exception
+	 */
+	protected function setOutput($name, $value) {
+		if (!is_string($name)) {
+			throw new Exception('set output to template error, name not string !');
+		}
+		$this->output[$name] = $value;
+	}
+	
+	/**
+	 * 获取专题数据文件
+	 *
+	 * @param string $name
+	 */
+	static public function includeTemplateData() {
+		//$appPath = Config::getInstance ()->get ( 'App_Path' );
+		return include '/export/manager/product/app/product/kif/include/FControllerTemplate.data.php';
+	}
+	
+	
+}
+
+
+class CViewRender extends CRender {
+	/**
+	 * 构造函数
+	 *
+	 * @param string $templateFile    模板文件(扩展名为:tpl.php)
+	 * @param string $templateEngine  模板引擎
+	 */
+	public function __construct($templateFile = null, $templateEngine = "php") {
+		
+		if (! $templateEngine)
+			$templateEngine = "php";
+		if (! is_null ( $templateFile ))
+			$this->setTemplate ( $templateFile );
+	}
+
+	/**
+	 * 显示解析模板内容
+	 */
+	public function display() {
+		//$timer2 = new CTimer('模板渲染耗时:');
+		$content = $this->render ();
+		echo $content;
+		return $content;
+
+		//echo $timer2->mark('secs ', 6)."<br/>";
+	}
+
+	/**
+	 * 包含模板文件
+	 */
+	public function embedFile($file) {
+		if ($this->checkTemplateFile ( $file )) {
+			if ($this->_viewData) {
+				extract ( $this->_viewData );
+			}
+			include_once (APP_TEMPLATE . '/' . $file . '.tpl.php');
+		}
+	}
+
+	/**
+	 * 获取模板内容
+	 */
+	public function fetch() {
+		return $this->render ();
+	}
+
+	/**
+	 * 设置$_viewData数组值
+	 */
+	public function set($key, $val) {
+		$this->_viewData [$key] = $val;
+	}
+
+	/**
+	 * 魔术函数  给类属性赋值
+	 */
+	public function __set($key, $val) {
+		$this->set ( $key, $val );
+	}
+
+	/**
+	 * 设置模板文件
+	 *
+	 * @param  string $templateFile   缺损模板文件名
+	 */
+	public function setTemplate($file) {
+		$this->_templateFile = (defined ( 'APP_TEMPLATE' ) ? APP_TEMPLATE . '/' : "") . $file . ".tpl.php";
+	}
+
+	/**
+	 * 获取模板文件名
+	 */
+	public function getTemplate() {
+		return $this->_templateFile;
+	}
+
+	/**
+	 * 解析模板并返回内容
+	 *
+	 * @return string 解析内容
+	 */
+	public function render() {
+		//$timer2 = new CTimer($this->_templateFile.'渲染耗时内部调试:');
+		if (! $this->checkTemplateFile ( $this->_templateFile )) {
+			trigger_error ( "($this->_templateFile)模板文件不存在" );
+		}
+
+		if ($this->_viewData) {
+			extract ( $this->_viewData );
+		}
+		//开始缓存
+		ob_start ();
+		include ($this->_templateFile);
+		$content = ob_get_clean ();
+		ob_flush ();
+		flush ();
+		//echo $timer2->mark('secs ', 6)."<br/>";
+		return $content;
+	}
+}
+
+
+abstract class CRender {
+	/**
+	 * @var array  提取的内容数据
+	 */
+	protected $_viewData;
+
+	/**
+	 * @var string   模板文件
+	 */
+	protected $_templateFile;
+
+	/**
+	 * 解析模板文件并输出渲染内容
+	 */
+	abstract function display();
+
+	/**
+	 * 获取模板内容
+	*/
+	abstract function fetch();
+
+	/**
+	 * 渲染内容
+	*/
+	abstract function render();
+
+	/**
+	 * 验证文件是否存在
+	 *
+	 * @return boolean
+	*/
+	public function checkTemplateFile() {
+
+		if (! $this->_templateFile) {
+			//           throw new Exception("ddd");
+			return false;
+		}
+
+		if (! is_file ( $this->_templateFile )) {
+			//             throw new Exception("模板文件不存在:".$this->_templateFile);
+			return false;
+		}
+
+		return true;
+	}
+}

+ 64 - 0
KIF/Core/.svn/text-base/IPC.class.php.svn-base

@@ -0,0 +1,64 @@
+<?php
+namespace KIF\Core;
+
+use KIF\Core\Config;
+use Exception;
+
+/**
+ * 
+ * In Process Call的缩写,即 进程内调用
+ * 目的:
+ *     1、保护上下文环境
+ *     2、简化调用代码
+ * 使用场景:
+ *     支持在任何地方调用基于KIF框架的app。
+ * @author husserlgao
+ *
+ */
+class IPC {
+	/**
+	 * 
+	 * 调用
+	 * @param string $conf_file 要调用的app类的配置文件
+	 * @param callback $callback 回调执行的函数
+	 */
+	static public function call($callback, $conf_file) {
+		# 尝试保存调用前的配置
+		try {
+			$pre_app_conf_path = Config::getInstance()->get('conf_path');
+		} catch (Exception $e) {
+			$pre_app_conf_path = false;
+		}
+		
+		Config::load($conf_file);
+		$callback();
+		
+		# 恢复到调用前的应用配置
+		if ($pre_app_conf_path) {
+			Config::load($pre_app_conf_path);
+		}
+	}
+	
+/**
+	 * 
+	 * 调用!!!这是个临时的方法,只因没有时间去把调call的地方改过来。
+	 * @param string $conf_file 要调用的app类的配置文件
+	 * @param callback $callback 回调执行的函数
+	 */
+	static public function call2($callback, $conf_file) {
+		# 尝试保存调用前的配置
+		try {
+			$pre_app_conf_path = Config::getInstance()->get('conf_path');
+		} catch (Exception $e) {
+			$pre_app_conf_path = false;
+		}
+		
+		Config::load($conf_file);
+		$callback();
+		
+		# 恢复到调用前的应用配置
+		if ($pre_app_conf_path) {
+			Config::load($pre_app_conf_path);
+		}
+	}
+}

+ 47 - 0
KIF/Core/.svn/text-base/Model.class.php.svn-base

@@ -0,0 +1,47 @@
+<?php
+namespace KIF\Core;
+
+use KIF\Dao\DBAgileDev;
+
+/**
+ * 定义MVC里的M抽象类,即Model
+ * @author gaoxiaogang@gmail.com
+ */
+abstract class Model {
+	/**
+	 * @var KIF\Dao\DBAgileDev
+	 */
+    protected $objMainDao;
+    
+    /**
+     * 
+     * 设置该Model对应的主Dao类
+     */
+	abstract protected function setMainDao();
+
+    public function __construct() {
+    	$this->setMainDao();
+    }
+    
+	public function totals($condtion = null) {
+        return $this->objMainDao->totals($condtion);
+    }
+
+    public function get($id) {
+        $result = $this->gets(array($id));
+        if (!$result) {
+            return false;
+        }
+        return array_pop($result);
+    }
+
+    public function gets(array $ids) {
+        $result = $this->objMainDao->gets($ids);
+        return $result;
+    }
+    
+	public function getsAll($order = null, $limit = null) {
+        $ids = $this->objMainDao->getsAllIds($order, $limit);
+        return $this->gets($ids);
+    }
+}

+ 227 - 0
KIF/Core/.svn/text-base/PermissionController.class.php.svn-base

@@ -0,0 +1,227 @@
+<?php
+namespace KIF\Core;
+
+/**
+ * 权限管理控制器
+ * 对整个控制器设定访问权限,也可以对控制器中的单个action设定访问权限
+ * @author li.shuming@kimiss.com
+ */
+
+use KIF\Verify;
+
+class PermissionController extends \KIF\Core\BKController {
+	
+	/**
+	 * 开启权限管理
+	 * 如果这里设置为true,那么只要继承了Permission的控制器,内部所有的行为都需要有权限才允许操作。
+	 * 默认为true,设置为false 时,可以在需要有权限操作的方法里加上 $this->requireCompetence()方法,要求有操作权限
+	 * @var Boolean
+	 */
+	static private $useCompetence = true;
+	
+	/**
+	 * 用户访问权限
+	 * @param Boolean $isHalt 没有权限访问时,是否要停机。默认停机,跳转到错误提示页
+	 * @return Boolean
+	 */
+	public function requireCompetence($isHalt = true) {
+		if (!self::$useCompetence) {
+			return true;
+		}
+		
+		if (is_null($isHalt)) {
+			$isHalt = true;
+		}
+		
+		# 先登录
+		parent::requireLogin();
+		
+		if (!self::isCompetence()) {
+			if ($isHalt) {
+				#TODO
+				self::no_permission_exit();
+			}
+			
+			return false;
+		}
+		
+		return true;
+	}
+	
+	/**
+	 * 当前用户是否有权访问
+	 * @return Boolean
+	 */
+	private function isCompetence() {
+		if (!self::isLogin()) {
+			return false;
+		}
+		
+		# 超级管理员啥权限都有
+		if (self::isSuperadmin()) {
+			return true;
+		}
+		
+		# 以下普通帐号权限
+		$c = Request::getInstance()->param('c');
+		$arr_class_path = array_map(function ($tmpV) {
+			return lcfirst($tmpV);
+		}, explode('_', $c));
+		
+		$c = implode('_', $arr_class_path);
+		$a = Request::getInstance()->param('a');
+		
+		$requestParams = array( //当前请求参数名称
+			'app_name'		=> lcfirst(Config::getInstance()->get('Namespace')),
+			'control_name'	=> $c,
+			'action_name'	=> $a ? lcfirst($a) : 'default',
+		);
+		
+		$is_cpt = true;
+		
+		//普通帐号没有的权限
+		$notCompetences = array(
+				array('control_name' => 'admin_platform_index'), //平台列表管理
+				array('control_name' => 'admin_backUser', 'action_name' => 'userList'), //帐号管理 列表
+				array('control_name' => 'admin_backUser', 'action_name' => 'CreateUser'),//帐号管理 创建帐号
+				array('control_name' => 'admin_backUser', 'action_name' => 'setPermission'),
+				array('control_name' => 'admin_backUser', 'action_name' => 'MP'),
+				array('control_name' => 'admin_platform_bulletin', 'action_name' => 'ReqCreate'), //添加公告
+				array('control_name' => 'admin_platform_bulletin', 'action_name' => 'PageUp'), //编辑公告
+		);
+		
+		foreach ($notCompetences as $tmpCompetence) {
+			if (lcfirst($tmpCompetence['control_name']) != $requestParams['control_name']) {
+				continue;
+			}
+			
+			if (!$tmpCompetence['action_name']) { //对控制器下的所有行为拥有权限
+				$is_cpt = false;
+				break;
+			}
+			
+			if (lcfirst($tmpCompetence['action_name']) == $requestParams['action_name']) {
+				$is_cpt = false;
+				break;
+			}
+		}
+		
+		return $is_cpt;
+	}
+	
+	/**
+	 * 通过用户id获取用户所属的组id集合
+	 * @param int $uid
+	 * @return array
+	 */
+	public function getsGroupidsByUid($uid) {
+		if (!Verify::unsignedInt($uid)) {
+			return array();
+		}
+		
+		$groupids = array();
+		
+			$objDKifUsergroupRelation = new \Cas\Dao\KifUsergroupRelation();
+			$groupids = $objDKifUsergroupRelation->getsGroupids($uid);
+		
+		return $groupids;
+	}
+	
+	/**
+	 * 获取用户组所有的权限设置
+	 * @param array $groupids
+	 * @return array
+	 */
+	public function getsCompetencesByGroupids($groupids) {
+		if (!$groupids) {
+			return array();
+		}
+		
+		$competences = array();
+		
+			$objDKifUsergroupPermission = new \Cas\Dao\KifUsergroupPermission();
+			$competences = $objDKifUsergroupPermission->getsCompetencesByGroupids($groupids);
+		
+		return $competences;
+	}
+	
+	/**
+	 * 是否超级管理员
+	 * @return boolean
+	 */
+	static public function isSuperadmin() {
+		if (!self::$useCompetence) {
+			return true;
+		}
+		
+		$result = \Cas\Module\Permission::isSuperadmin();
+		
+		return $result;
+	}
+	
+	/**
+	 * 输出错误消息
+	 * @param string $msg
+	 */
+	public function fail_exit_cpt($msg = null) {
+		$permission_template_dir = Config::getInstance()->get('App_Path') . DS . 'template_dir';
+		$this->tpl = $permission_template_dir . '/admin/permission/prompt_message';
+		$this->setOutputs(array(
+				'type'	=> 'fail',
+				'msg'	=> $msg,
+				'referer'	=> Request::referer(),
+				'header_tpl'=> $permission_template_dir . '/header.html',
+				'bottom_tpl'=> $permission_template_dir . '/bottom.html',
+		));
+		$this->render();
+		exit;
+	}
+	
+	/**
+	 * 输出成功消息
+	 * @param string $msg
+	 */
+	public function success_exit_cpt($msg = null) {
+		$permission_template_dir = Config::getInstance()->get('App_Path') . DS . 'template_dir';
+		$this->tpl = $permission_template_dir . '/admin/permission/prompt_message';
+		$this->setOutputs(array(
+				'type'	=> 'success',
+				'msg'	=> $msg,
+				'referer'	=> Request::referer(),
+				'header_tpl'=> $permission_template_dir . '/header.html',
+				'bottom_tpl'=> $permission_template_dir . '/bottom.html',
+		));
+		$this->render();
+		exit;
+	}
+	
+	public function no_permission_exit() {
+		$permission_template_dir = Config::getInstance()->get('App_Path') . DS . 'template_dir';
+		$this->tpl = $permission_template_dir . '/admin/permission/prompt_message';
+		$this->setOutputs(array(
+				'type'	=> 'no_permission',
+		));
+		$this->render();
+		exit;
+	}
+	
+	public function run() {
+		if (isset(self::$useCompetence) && self::$useCompetence) {
+			$this->requireCompetence();
+		}
+		
+		# 登陆用户
+		$this->setOutput('backuser', $this->getUser());
+		
+		# 是否管理员
+		$IS_ADMIN = false;
+		if (self::isSuperadmin()) {
+			$IS_ADMIN = true;
+		}
+		$this->setOutput('IS_ADMIN', $IS_ADMIN);
+		
+		$action = $this->action;
+		$this->$action();
+	}
+	
+}

+ 714 - 0
KIF/Core/.svn/text-base/Request.class.php.svn-base

@@ -0,0 +1,714 @@
+<?php
+namespace KIF\Core;
+
+use KIF\Route;
+
+use KIF\String\String;
+
+use KIF\String\Filter;
+use Exception;
+use KIF\Exception\ParamsException;
+use KIF\Core\Config;
+use KIF\Verify;
+
+/**
+ * 封装了一些方法,用于方便的处理从客户端提交过来的数据
+ * @author gaoxiaogang@gmail.com
+ */
+class Request {
+	/**
+	 * 
+	 * 存放当前请求的参数
+	 * @var array
+	 */
+	protected $params;
+	
+	static protected $instance;
+	
+	# 不允许被new
+	private function __construct() {
+		
+	}
+	
+	static public function getInstance() {
+		if (is_null(self::$instance)) {
+			self::$instance = new self();
+		}
+		
+		self::$instance->params = self::params();
+		
+		return self::$instance;
+	}
+	
+	/**
+	 * 获取ip地址
+	 *
+	 * @return string ip地址:如 59.151.9.90
+	 */
+    public function ip() {
+        if (isset($_SERVER)) {
+            if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
+                $realip = $_SERVER['HTTP_X_FORWARDED_FOR'];
+            } elseif (isset($_SERVER['HTTP_CLIENT_IP'])) {
+                $realip = $_SERVER['HTTP_CLIENT_IP'];
+            } else {
+                $realip = $_SERVER['REMOTE_ADDR'];
+            }
+        } else {
+            if (getenv("HTTP_X_FORWARDED_FOR")) {
+                $realip = getenv("HTTP_X_FORWARDED_FOR");
+            } elseif (getenv("HTTP_CLIENT_IP")) {
+                $realip = getenv("HTTP_CLIENT_IP");
+            } else {
+                $realip = getenv("REMOTE_ADDR");
+            }
+        }
+        $realip = preg_replace('#,.*$#', '', $realip);
+        $realip = preg_replace('#[^\d\.]+#', '', $realip);// 从支付平台发现,有时会获取到这种ip,%20%2058.255.8.39,说明前面有有空格,处理一下
+        return $realip;
+    }
+
+    /**
+     * 从 REQUEST_URI 字段获取请求路径
+     * 如:/article/index.php?id=857&action=show 会被处理成:article/index.php
+     *
+     * @param string | null $request_uri
+     * @return string
+     */
+    public function path($request_uri = null) {
+    	if (is_null($request_uri)) {
+            $request_uri = $_SERVER['REQUEST_URI'];
+        }
+
+        if (($iPos = strpos($request_uri, '?')) !== false) {
+            $request_uri = substr($request_uri, 0, $iPos);
+        }
+        $request_uri = str_replace(' ', '/', trim(str_replace('/', ' ', $request_uri)));
+        $request_uri = urldecode($request_uri);
+
+        return $request_uri;
+    }
+
+    /**
+     *
+     * 获取当前请求的url
+     * @return String 完整的url,如:http://www.kimiss.com/test.php?c=Default
+     */
+    public function url() {
+    	$port = $_SERVER['SERVER_PORT'] == '80' ? '' : ":{$_SERVER['SERVER_PORT']}";
+    	$url = "http://{$_SERVER['HTTP_HOST']}{$_SERVER['REQUEST_URI']}";
+    	return $url;
+    }
+    
+	/**
+	 * 
+	 * 获取分类url的模版
+	 * @param string $pageKey 请求里表示分页的参数
+	 * @return string
+	 */
+	public function pageUrlTpl($pageKey = 'page') {
+		$routeParams = Route::getInstance()->getsRouteParams();
+		if ($routeParams) {
+			$routeParams[$pageKey] = '{page}';
+			$path = Route::getInstance()->reverse($routeParams);
+			$url = "http://{$_SERVER['HTTP_HOST']}/" . $path;
+			return String::jointUrl($url, array_diff_key($_GET, $routeParams));
+		} else {
+			$request_params = $_GET;
+			$request_params[$pageKey] = '{page}';
+			$url = "http://{$_SERVER['HTTP_HOST']}/" . $this->path();
+			return String::jointUrl($url, $request_params);
+		}
+	}
+	
+	/**
+	 * 
+	 * 获取带请求协议的域名url,如:https://kimiss.com
+	 * @return string
+	 */
+	static public function schemeDomain() {
+		$protocol_str = $_SERVER['SERVER_PROTOCOL'];
+		if (!$protocol_str) {
+			return '';
+		}
+		list($protocol,) = explode('/', $protocol_str);
+		return strtolower($protocol) . "://{$_SERVER['HTTP_HOST']}";
+	}
+    
+    /**
+     * 
+     * 获取当前请求的顶级域名
+     * @param string $host
+     * @return string
+     */
+    public function rootDomain($host = null) {
+    	if (is_null($host))
+    		$host = $_SERVER['HTTP_HOST'];
+    	
+    	if (empty($host)) {
+    		return '';
+    	}
+    	$host = strtolower($host);
+    	
+    	# 是个ip直访的url
+    	if (filter_var($host, FILTER_VALIDATE_IP)) {
+    		return '';
+    	}
+    	# 不带.,可能是绑host的域名
+    	if (strpos($host, '.') === FALSE) {
+    		return $host;
+    	}
+    	
+    	$domain_suffs = array(
+    		'.com', '.cn', '.net', '.com.cn', '.net.cn', 
+    		'.org', '.me', '.biz', '.name', '.org.cn', '.gov.cn',
+    		'.info', '.so', '.tel', '.mobi', '.asia', '.cc', '.tv', '.co',
+    	);
+    	
+    	foreach ($domain_suffs as $suff) {
+    		$tmp_stuff_pos = strrpos($host, $suff);
+    		if ($tmp_stuff_pos === false) {
+    			continue;
+    		}
+    		$tmp_rootdomain_pos = strrpos($host, '.', 0-strlen(substr($host, $tmp_stuff_pos))-1);
+    		if ($tmp_rootdomain_pos === false) {
+    			return $host;
+    		}
+    		
+    		return substr($host, $tmp_rootdomain_pos+1);
+    	}
+    }
+
+    /**
+     * 获取请求url中的查询字符串
+     *
+     * @return string
+     */
+    static public function queryString() {
+    	return $_SERVER['QUERY_STRING'];
+    }
+
+    /**
+     *
+     * 获取请求的referer
+     * @return string
+     */
+    static public function referer() {
+    	return $_SERVER['HTTP_REFERER'];
+    }
+
+    /**
+     * 是否POST请求
+     *
+     * @return boolean
+     */
+    static public function isPost() {
+    	return strtoupper($_SERVER['REQUEST_METHOD']) == 'POST';
+    }
+
+    /**
+     * 是否GET请求
+     *
+     * @return boolean
+     */
+    static public function isGet() {
+    	return strtoupper($_SERVER['REQUEST_METHOD']) == 'GET';
+    }
+
+    /**
+     * 该请求是否由 蜘蛛 发出
+     *
+     * @return boolean
+     */
+    public function isSpider() {
+        if (self::isCLI()) {
+            return false;
+        }
+        if (!isset($_SERVER['HTTP_USER_AGENT'])) {
+            return false;
+        }
+        $bots = array(
+            'Googebot', 'Baiduspider', 'Yahoo! Slurp', 'Sosospider', 'Sogou', 'Sogou-Test-Spider',
+            'Sogou head spider', 'YoudaoBot', 'qihoobot', 'iaskspider', 'LeapTag', 'MSIECrawler'
+        );
+        foreach ($bots as $bot) {
+            if (strpos($_SERVER['HTTP_USER_AGENT'], $bot) !== false) {
+                return true;
+            }
+        }
+        if (stristr($_SERVER['HTTP_USER_AGENT'], 'Windows') !== false) {
+            return false;
+        }
+        $bots = array(
+            'spider', 'bit', 'crawler', 'slurp', 'subscriber', 'http',
+            'rssreader', 'blogline', 'greatnews', 'feed', 'alexa', 'php'
+        );
+        foreach ($bots as $bot) {
+            if (stristr($_SERVER['HTTP_USER_AGENT'], $bot) !== false) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * 该请求是否从相同的主机过来,即来源页是否与当前页处于相同域下
+     *
+     * @return boolean
+     */
+    public function isFromSameHost() {
+        if (!isset($_SERVER['HTTP_REFERER'])) {
+            return true;
+        }
+        $url_parts = parse_url($_SERVER['HTTP_REFERER']);
+        $host = $url_parts['host'];
+        if ($_SERVER['HTTP_HOST'] == $host) {
+        	return true;
+        }
+        return false;
+    }
+
+    /**
+     * 从请求的 $_GET 变量里获取 变量的整数值,如果不存在该变量或不为整数,则返回 $default 值
+     * @param string $varName 变量名
+     * @param int $default 默认值
+     * @return int
+     */
+    public function varGetInt($varName, $default = 0) {
+    	if (!isset($_GET[$varName])) {
+    		return $default;
+    	}
+
+    	if (!Verify::unsignedInt($_GET[$varName])) {
+    		return $default;
+    	}
+
+    	return $_GET[$varName];
+    }
+
+    /**
+     * 从请求的 $_POST 变量里获取 变量的整数值,如果不存在该变量或不为整数,则返回 $default 值
+     * @param string $varName 变量名
+     * @param int $default 默认值
+     * @return int
+     */
+    public function varPostInt($varName, $default = 0) {
+    	if (!isset($_POST[$varName])) {
+            return $default;
+        }
+
+        if (!Verify::unsignedInt($_POST[$varName])) {
+            return $default;
+        }
+
+        return $_POST[$varName];
+    }
+
+    /**
+     * 从请求的 $_POST 变量里获取 变量的整数值,如果不存在该变量或不为整数,则返回 $default 值
+     * @param string $varName 变量名
+     * @param int $default 默认值
+     * @return int
+     */
+    public function varRequestInt($varName, $default = 0) {
+        if (!isset($_REQUEST[$varName])) {
+            return $default;
+        }
+
+        if (!Verify::unsignedInt($_REQUEST[$varName])) {
+            return $default;
+        }
+
+        return $_REQUEST[$varName];
+    }
+
+	/**
+	 * 获取请求的参数集。
+	 * 支持http访问的参数 以及 命令行下访问的参数
+	 * demo1:php test.php -p3 -t=abc --opt=valopt --opt2 valopt2
+	 * demo2 http://test.kimiss.com/index.php?c=xxx&a=ddd
+	 * @return array
+	 */
+	public function params() {
+	    if (self::isCLI()) {
+	    	return self::cliParams();
+	    } else {
+	    	return $_REQUEST;
+	    }
+	}
+	
+	/**
+	 * 
+	 * 获取命令行下传递进来的参数
+	 * 只支持以 - 或 -- 开头的参数
+	 * demo:php test.php -p3 -t=abc --opt=valopt --opt2 valopt2
+	 * @return array
+	 */
+	private function cliParams() {
+        $result = array();
+        $params = $GLOBALS['argv'];
+        
+        array_shift($params);
+        reset($params);
+        do {
+        	$tmpEachResult = each($params);
+        	if (!$tmpEachResult) {
+        		break;
+        	}
+        	list($tmp, $p) = $tmpEachResult;
+        	
+            if ($p{0} == '-') {
+                $pname = substr($p, 1);
+                $value = false;
+                if ($pname{0} == '-') {// 长选项 (--<param>)
+                    $pname = substr($pname, 1);
+                    if (strpos($p, '=') !== false) {
+                        // value specified inline (--<param>=<value>)
+                        list($pname, $value) = explode('=', substr($p, 2), 2);
+                    }
+                } else {// 短选项
+                	if (strpos($p, '=') !== false) {
+                        // value specified inline (-<param>=<value>)
+                        list($pname, $value) = explode('=', substr($p, 1), 2);
+                    } else if (strlen($p) > 1)  {
+                    	$pname = substr($p, 1, 1);
+                    	$value = substr($p, 2);
+                    }
+                }
+                # 如果上面没有取到值,并且下一个不是以-开头的,则下一个值为当前参数的值
+                $nextparm = current($params);
+                if ($value === false
+                	&& $nextparm !== false
+                	&& $nextparm{0} != '-'
+                ) {
+                	list($tmp, $value) = each($params);
+                }
+                $result[$pname] = (string) $value;// 将 false转为空串,以便与http访问时对参数的处理一致
+            } else {
+                # 不是以-指定开始的参数,一律丢弃
+                //$result[] = $p;
+            }
+        } while (true);
+        
+        return $result;
+    }
+
+	/**
+	 *
+	 * 获取 $_GET 里指定key $name 的值,同时可以指定 $filters 过滤方法处理值
+	 *
+	 * 用法:
+	 * 1、对值不做任何处理:Request::getInstance()->get('content', null);
+	 * 2、对值做html转义处理:Request::getInstance()->get('name');
+	 *
+	 * @param string $name
+	 * @param mixed $filters 默认使用html转义过滤。当值为null里,对值不做任何过滤。
+	 * $filters支持以下格式:
+	 * 1) 标量类型,如:
+	 *    Filter::HTMLSPECIALCHARS;
+	 * 2) 多个filter,如:
+	 *    array(
+	 *        Filter::HTMLSPECIALCHARS,
+	 *        Filter::STRIP_TAGS,
+	 *    );
+	 * 3) 多个filter,并且某些filter另外指定参数,如:
+	 *    array(
+	 *        array(
+	 *            Filter::HTMLSPECIALCHARS	=> ENT_QUOTES,
+	 *        ),
+	 *        array(
+	 *            Filter::STRIP_TAGS	=> "<a>",
+	 *        ),
+	 *        Filter::STRIP_SELECTED_TAGS,
+	 *    );
+	 *
+	 * @throws ParamsException "无效的过滤方法filter"
+	 * @return string
+	 */
+	public function get($name, $filters = array(Filter::HTMLSPECIALCHARS, Filter::TRIM)) {
+		if (!isset($_GET[$name])) {
+			return false;
+		}
+
+		$val = $_GET[$name];
+
+		if (is_null($filters)) {
+			return $val;
+		}
+
+		if (!is_array($filters)) {
+			$filters = array($filters);
+		}
+
+		return self::filter($val, $filters);
+	}
+
+	/**
+	 *
+	 * get的别名方法
+	 * @param string $name
+	 * @param mixed $filters 默认使用html转义过滤。当值为null里,对值不做任何过滤。
+	 * @throws ParamsException "无效的过滤方法filter"
+	 * @return string
+	 */
+	public function g($name, $filters = array(Filter::HTMLSPECIALCHARS, Filter::TRIM)) {
+		return self::get($name, $filters);
+	}
+
+	/**
+	 *
+	 * 获取 $_POST 里指定key $name 的值,同时可以指定 $filters 过滤方法处理值
+	 *
+	 * 用法:
+	 * 1、对值不做任何处理:Request::getInstance()->post('content', null);
+	 * 2、对值做html转义处理:Request::getInstance()->post('name');
+	 *
+	 * @param string $name
+	 * @param mixed $filters 默认使用html转义过滤。当值为null里,对值不做任何过滤。
+	 * @throws ParamsException "无效的过滤方法filter"
+	 * @return string
+	 */
+	public function post($name, $filters = array(Filter::HTMLSPECIALCHARS, Filter::TRIM)) {
+		if (!isset($_POST[$name])) {
+			return false;
+		}
+
+		$val = $_POST[$name];
+
+		if (is_null($filters)) {
+			return $val;
+		}
+
+		if (!is_array($filters)) {
+			$filters = array($filters);
+		}
+
+		return self::filter($val, $filters);
+	}
+
+	/**
+	 *
+	 * post的别名方法
+	 * @param string $name
+	 * @param mixed $filters 默认使用html转义过滤。当值为null里,对值不做任何过滤。
+	 * @throws ParamsException "无效的过滤方法filter"
+	 * @return string
+	 */
+	public function p($name, $filters = array(Filter::HTMLSPECIALCHARS, Filter::TRIM)) {
+		return self::post($name, $filters);
+	}
+
+	/**
+	 *
+	 * 获取 $_REQUEST 里指定key $name 的值,同时可以指定 $filters 过滤方法处理值
+	 * !! 因为与类同名的方法会被认为是构造函数,所以这个方法名加个小字符 !!
+	 *
+	 * 用法:
+	 * 1、对值不做任何处理:Request::getInstance()->requestV('content', null);
+	 * 2、对值做html转义处理:Request::getInstance()->requestV('name');
+	 *
+	 * @param string $name
+	 * @param mixed $filters 默认使用html转义过滤。当值为null里,对值不做任何过滤。
+	 * @throws ParamsException "无效的过滤方法filter"
+	 * @return string
+	 */
+	public function requestV($name, $filters = array(Filter::HTMLSPECIALCHARS, Filter::TRIM)) {
+		if (!isset($_REQUEST[$name])) {
+			return false;
+		}
+
+		$val = $_REQUEST[$name];
+
+		if (is_null($filters)) {
+			return $val;
+		}
+
+		if (!is_array($filters)) {
+			$filters = array($filters);
+		}
+
+		return self::filter($val, $filters);
+	}
+
+	/**
+	 *
+	 * requestV的别名方法
+	 * @param string $name
+	 * @param mixed $filters 默认使用html转义过滤。当值为null里,对值不做任何过滤。
+	 * @throws ParamsException "无效的过滤方法filter"
+	 * @return string
+	 */
+	public function r($name, $filters = array(Filter::HTMLSPECIALCHARS, Filter::TRIM)) {
+		return self::requestV($name, $filters);
+	}
+
+	/**
+	 *
+	 * 获取 $_COOKIE 里指定key $name 的值,同时可以指定 $filters 过滤方法处理值
+	 *
+	 * 用法:
+	 * 1、对值不做任何处理:Request::getInstance()->cookie('content', null);
+	 * 2、对值做html转义处理:Request::getInstance()->cookie('name');
+	 *
+	 * @param string $name
+	 * @param mixed $filters 默认使用html转义过滤。当值为null里,对值不做任何过滤。
+	 * @throws ParamsException "无效的过滤方法filter"
+	 * @return string | false
+	 */
+	public function cookie($name, $filters = array(Filter::HTMLSPECIALCHARS, Filter::TRIM)) {
+		if (!isset($_COOKIE[$name])) {
+			return false;
+		}
+
+		$val = $_COOKIE[$name];
+
+		if (is_null($filters)) {
+			return $val;
+		}
+
+		if (!is_array($filters)) {
+			$filters = array($filters);
+		}
+
+		return self::filter($val, $filters);
+	}
+
+	/**
+	 *
+	 * cookie的别名方法
+	 * @param string $name
+	 * @param mixed $filters 默认使用html转义过滤。当值为null里,对值不做任何过滤。
+	 * @throws ParamsException "无效的过滤方法filter"
+	 * @return string | false
+	 */
+	public function c($name, $filters = array(Filter::HTMLSPECIALCHARS, Filter::TRIM)) {
+		return self::cookie($name, $filters);
+	}
+	
+	/**
+	 *
+	 * 获取当前请求参数里指定key $name 的值,同时可以指定 $filters 过滤方法处理值
+	 *
+	 * 用法:
+	 * 1、对值不做任何处理:Request::getInstance()->param('content', null);
+	 * 2、对值做html转义处理:Request::getInstance()->param('name');
+	 *
+	 * @param string $name
+	 * @param mixed $filters 默认使用html转义过滤。当值为null里,对值不做任何过滤。
+	 * @throws ParamsException "无效的过滤方法filter"
+	 * @return string | false
+	 */
+	public function param($name, $filters = array(Filter::HTMLSPECIALCHARS, Filter::TRIM)) {
+		if (!isset($this->params[$name])) {
+			return false;
+		}
+
+		$val = $this->params[$name];
+
+		if (is_null($filters)) {
+			return $val;
+		}
+
+		if (!is_array($filters)) {
+			$filters = array($filters);
+		}
+
+		return self::filter($val, $filters);
+	}
+
+	/**
+	 *
+	 * 使用用户指定的 过滤方法,过滤值 $val
+	 * @param string $val
+	 * @param array $filters 过滤方法相关信息
+	 * @throws ParamsException "无效的过滤方法filter"
+	 * @return string
+	 */
+	private function filter($val, array $filters) {
+		foreach ($filters as $filter) {
+			$params = array($val);
+			if (is_array($filter)) {
+				list($filter_name, $filter_options) = each($filter);
+			} else {
+				$filter_name = $filter;
+				$filter_options = null;
+			}
+			if (!Filter::isValid($filter_name)) {
+				throw new ParamsException("无效的过滤方法filter");
+			}
+			$val = call_user_func(array('KIF\String\Filter', $filter_name), $val, $filter_options);
+		}
+
+		return $val;
+	}
+
+	/**
+     * 是否处于命令行下
+     * @return Boolean
+     */
+    static public function isCLI() {
+        if (php_sapi_name() == "cli"//PHP 4 >= 4.0.1, PHP 5 support php_sapi_name function
+            || empty($_SERVER['PHP_SELF'])//If PHP is running as a command-line processor this variable contains the script name since PHP 4.3.0. Previously it was not available.
+        ) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * 是否类unix系统
+     * @return Boolean
+     */
+    static public function isUnixLike() {
+        $os = strtolower(PHP_OS);
+        if (in_array($os, array('linux', 'freebsd', 'unix', 'netbsd'))) {
+            return true;
+        }
+        return false;
+    }
+    
+    /**
+     * 
+     * 分发请求,调用Controller以及对应的action
+     */
+    public function dispatch() {
+    	$params = $this->params;
+    	$controllerName = self::getController($params);
+    	
+    	if (!class_exists($controllerName)) {
+    		throw new Exception("controller: {$controllerName} not exists!");
+    	}
+    	
+    	$action = self::getAction($params);
+    	
+    	$controller = new $controllerName();
+    	$controller->setAction($action);
+    	$controller->run();
+    	
+    	
+    	if (method_exists($controller, 'display')) {
+    		$controller->display();
+    	}
+    }
+    
+    public function getController() {
+    	$c = self::param('c');
+    	$c = trim($c, '_');
+    	
+    	$arr_class_path = array_map(function ($tmpV) {
+    		return ucfirst($tmpV);
+    	}, explode('_', $c));
+    	$c = join('\\', $arr_class_path);
+    	$NS = Config::getInstance()->get('Namespace');
+    	$controller = "{$NS}\Controller\\{$c}";
+    	return $controller;
+    }
+    
+    public function getAction(array $params = null) {
+    	$action = self::param('a');
+    	
+    	return $action;
+    }
+}

+ 88 - 0
KIF/Core/.svn/text-base/View.class.php.svn-base

@@ -0,0 +1,88 @@
+<?php
+namespace KIF\Core;
+use Smarty;
+
+include KIF_PATH . "/smarty/Smarty.class.php";
+
+use KIF\Core\Config;
+
+/**
+ * 定义MVC里的V,即View
+ * @author gaoxiaogang@gmail.com
+ */
+class View extends Smarty
+{
+	/**
+	 *
+	 * 存放要自动注册的 函数 信息
+	 * @var array
+	 */
+	static private $auto_registers = array();
+
+	private $tpl_type ;  //静态文件的类型
+	
+	private $directory;  //模板子目录
+	
+	public function __construct($directory = '')
+	{
+		$this->left_delimiter = "<{";
+		$this->right_delimiter = "}>";
+		$this->directory = $directory;
+
+		foreach (self::$auto_registers as $tmpV) {
+			$this->register_function($tmpV['smarty_func'], $tmpV['php_func'], $tmpV['cacheable'], $tmpV['cache_attrs']);
+		}
+	}
+
+	/**
+	 *
+	 * 类方法.通知模版引擎自动注册函数
+	 * 目的:在使用项目处,方便的自动注册一些全局性的模版函数
+	 * @param string $function 注册后,模版使用时的 函数名
+	 * @param string $function_impl 要被注册的php函数名
+	 * @param bookean $cacheable
+	 * @param unknown_type $cache_attrs
+	 * @author gxg@gaojie100.com
+	 */
+	static function auto_register_function($function, $function_impl, $cacheable=true, $cache_attrs=null) {
+		self::$auto_registers[] = array(
+			'smarty_func'	=> $function,
+			'php_func'		=> $function_impl,
+			'cacheable'		=> $cacheable,
+			'cache_attrs'	=> $cache_attrs,
+		);
+	}
+
+	/**
+	 * 
+	 * 渲染模版并打印出来
+	 * @param string $tpl 模版文件名
+	 * @param unknown_type $cache_id
+	 * @param unknown_type $compile_id
+	 * @return NULL 无返回值
+	 */
+	public function d($tpl, $cache_id = null, $compile_id = null)
+	{
+		$this->r($tpl, $cache_id , $compile_id ,true);
+	}
+	
+	/**
+	 * 
+	 * 渲染模版。默认时返回内容
+	 * @param string $tpl 模版文件名
+	 * @param unknown_type $cache_id
+	 * @param unknown_type $compile_id
+	 * @param boolean $display 是否打印出来?
+	 * @return string
+	 */
+	public function r($tpl, $cache_id = null, $compile_id = null, $display = false)
+	{
+		$smarty_config = Config::getInstance()->get('smarty');
+		
+		$this->template_dir = $smarty_config['template_dir'];
+		$this->compile_dir = $smarty_config['compile_dir'];
+		$this->tpl_type = $smarty_config['tpl_type'] ? $smarty_config['tpl_type'] : 'html';
+		$content = $this->fetch($tpl . "." . $this->tpl_type, $cache_id , $compile_id , $display);
+		return $content;
+	}
+}

+ 167 - 0
KIF/Core/.svn/text-base/WXController.class.php.svn-base

@@ -0,0 +1,167 @@
+<?php
+namespace KIF\Core;
+use Cas\Module\Passport;
+use KIF\Data\ResultWrapper;
+use KIF\String\String;
+/**
+ * 
+ * 继承自Controller,封装了微信用户登录状态、用户信息等一些方法
+ * @author lishumingoo@gmail.com
+ */
+class WXController extends \KIF\Core\Controller {
+	
+	/**
+	 * 
+	 * 判断是否登录
+	 * @return boolean
+	 */
+	public function isLogin() {
+		$user =  self::getUser();
+		if (!$user) {
+			return false;
+		}
+		
+		if ($user['loginType'] == Passport::LOGIN_TYPE_NOT_AUTH) {
+			return false;
+		}
+		
+		return true;
+	}
+	
+	/**
+	 * 
+	 * 获取用户id
+	 * @return boolean | int
+	 */
+	public function getUid() {
+		$user = $this->getUser();
+		if (!$user) {
+			return false;
+		}
+		
+		return $user['uid'];
+	}
+	
+	/**
+	 * 
+	 * 获取用户登陆类型
+	 * 授权、未授权登陆
+	 * @return boolean | int Passport::LOGIN_TYPE_NOT_AUTH Passport::LOGIN_TYPE_HAS_AUTH
+	 */
+	public function getLoginType() {
+		$user = $this->getUser();
+		if (!$user) {
+			return false;
+		}
+		
+		return $user['loginType'];
+	}
+	
+	/**
+	 * 
+	 * 获取登陆用户openid
+	 * @return boolean | string
+	 */
+	public function getOpenid() {
+		$user = $this->getUser();
+		if (!$user) {
+			return false;
+		}
+		
+		return $user['openid'];
+	}
+	
+	/**
+	 * 
+	 * 获取用户微信昵称
+	 * @return boolean | string
+	 */
+	public function getNickname() {
+		$user = $this->getUser();
+		if (!$user) {
+			return false;
+		}
+		
+		return $user['nickname'];
+	}
+	
+	/**
+	 * 
+	 * 获取用户微信头像
+	 * @return boolean | string
+	 */
+	public function getHeadimgurl() {
+		$user = $this->getUser();
+		if (!$user) {
+			return false;
+		}
+		
+		return $user['headimgurl'];
+	}
+	
+	/**
+	 * 
+	 * 从Cookie中获取用户的登陆信息
+	 * @return boolean | array
+	 */
+	public function getUser() {
+		static $user = null;
+		if ($user) {
+			return $user;
+		}
+		
+		$objPassport = new Passport();
+		$tmpResult = $objPassport->getLoginInfo();
+		if ($tmpResult->isSuccess()) {
+			$user = $tmpResult->getData();
+		}
+		
+		return $user;
+	}
+	
+	/**
+	 * 判断登陆状态是否满足活动要求,满足时返回true,不满足返回授权地址
+	 * @param Boolean $isAuth 是否需要授权。true时需要loginType = Passport::LOGIN_TYPE_HAS_AUTH; false时loginType至少属于Passport::LOGIN_TYPE_NOT_AUTH;
+	 * @return ResultWrapper success 满足条件; fail 不满足条件,需要通过返回的授权url进行授权
+	 */
+	public function checkLoginStatus($isAuth = false, $from) {
+		$authUrl = Request::schemeDomain() . '/connect/';
+		
+		if (!$isAuth) {
+			if (!self::getUser()) {
+				$state = 'base';
+			}
+		} else {
+			if (!self::isLogin()) {
+				$state = 'userinfo';
+			}
+		}
+		
+		if ($state) {
+			return ResultWrapper::fail(String::jointUrl($authUrl, array('from' => $from, 'state' => $state)));
+		}
+		
+		return ResultWrapper::success();
+	}
+	
+	public function __construct() {
+		if (self::isLogin()) {
+			define('IS_LOGIN', 1);
+			define('UID', self::getUid());
+			define('NICKNAME', self::getNickname());
+			define('AVATAR', self::getHeadimgurl());
+		} else {
+// 			$tmpResult = self::checkLoginStatus(false, Request::url());
+// 			if (!$tmpResult->isSuccess()) {
+// 				self::redirect($tmpResult->getData());
+// 			}
+			
+			define('IS_LOGIN', 0);
+		}
+	}
+	
+	public function run() {
+		$action = $this->action;
+		$this->$action();
+	}
+}

+ 88 - 0
KIF/Core/AbstractDaemon.class.php

@@ -0,0 +1,88 @@
+<?php
+namespace KIF\Core;
+
+use KIF\Cli\SingleProcess;
+
+abstract class AbstractDaemon extends Controller {
+	
+	public function run() {
+		$action = $this->action;
+		$this->$action();
+	}
+	
+	/**
+	 * 
+	 * 要完成的业务逻辑,由子类实现
+	 */
+	abstract protected function doSomething();
+	
+	/**
+	 * 
+	 * 脚本存活时间,可由子类重写
+	 * @var int
+	 */
+	protected $aliveTime = 7200;// 2小时
+	
+	
+	/**
+	 * 
+	 * Enter description here ...
+	 * @var KIF\Cli\SingleProcess
+	 */
+	private $objSingleProcess;
+	
+	public function __construct() {
+		$processId = realpath(__FILE__) . '-' . get_class($this);
+		
+		$timeout = 2 * $this->aliveTime;// 允许外部脚本kill当前脚本的时间,设置为存活时间的2倍。确保存活时间内,脚本不会被意外杀死。
+		$this->objSingleProcess = new SingleProcess($processId, $timeout, true);
+		
+	}
+	
+	/**
+	 *
+	 * 判断脚本是否还能存活
+	 * @return Boolean
+	 */
+	protected function isAlive() {
+		static $time_init = null;
+		if (is_null($time_init)) {
+			$time_init = time();
+		}
+	
+		$time_now = time();
+	
+	    if ($time_now - $time_init >= $this->aliveTime) {//超过脚本的生存时间
+	        return false;
+	    }
+	
+	    return true;
+	}
+	
+	
+	protected function doDefault() {
+		if ($this->objSingleProcess->isRun()) {
+		    echo "已经有进程在跑了\r\n";
+		    exit();
+		}
+		$this->objSingleProcess->run();
+		
+		do {
+			if (!$this->isAlive()) {
+	    		break;
+	    	}
+	    	
+	    	$this->doSomething();
+	    	
+	    	
+			
+			usleep(1000000);
+			echo "休息...\r\n";
+		} while ( true );
+		
+		$this->objSingleProcess->complete();
+		echo "done\r\n";
+		exit ();
+	}
+	
+}

+ 88 - 0
KIF/Core/BKController.class.php

@@ -0,0 +1,88 @@
+<?php
+namespace KIF\Core;
+
+use Cas\Module\BackPassport;
+
+/**
+ * 继承自MVC里的Controller,封装了后台用户登录状态、用户信息等一些方法
+ * @author lishumingoo@gmail.com
+ */
+class BKController extends \KIF\Core\Controller {
+	
+	/**
+	 * 判断用户登录状态
+	 * @return Boolean
+	 */
+	static public function isLogin() {
+		return (boolean) self::getUid();
+	}
+	
+	/**
+	 * 要求未登陆用户去登陆
+	 * @return boolean
+	 */
+	static public function requireLogin() {
+		if (!self::isLogin()) {
+			$referer = Request::url();
+			$url = Request::schemeDomain() . '/?c=Admin_BackUser&a=Login&referer=' . urlencode($referer);
+			parent::redirect($url);
+		}
+	
+		return true;
+	}
+	
+	/**
+	 * 获取用户id
+	 * @return false | int
+	 */
+	static public function getUid() {
+		$user = self::getUser();
+		if (!$user) {
+			return false;
+		}
+		
+		return $user['uid'];
+	}
+	
+	/**
+	 * 获取用户名
+	 * 这里的用户名是不靠谱的!!!
+	 * @return string
+	 */
+	static public function getUsername() {
+		$user = self::getUser();
+		if (!$user) {
+			return false;
+		}
+		
+		return $user['name'];
+	}
+	
+	/**
+	 *
+	 * 从Cookie中获取用户的登陆信息
+	 * @return boolean | array
+	 */
+	public function getUser() {
+		static $user = null;
+		if ($user) {
+			return $user;
+		}
+	
+		$user = array();
+		
+			$objBackPassport = new BackPassport();
+			$tmpResult = $objBackPassport->getLoginInfo();
+			if ($tmpResult->isSuccess()) {
+				$user = $tmpResult->getData();
+			}
+			
+	
+		return $user;
+	}
+	
+	public function run() {
+		$action = $this->action;
+		$this->$action();
+	}
+}

+ 144 - 0
KIF/Core/Config.class.php

@@ -0,0 +1,144 @@
+<?php
+namespace KIF\Core;
+
+use Exception;
+
+/**
+ * 
+ * 配置类。作用:
+ * 1、作为框架需要配置参数的那些类(如KIF\Cache、KIF\Dao 里的类)的参数获取接口。
+ * 2、为KIF框架下的不同APP间 php in-process calls (APP间PHP进程内调用) 提供核心支持
+ * 
+ * @author gaoxiaogang@gmail.com
+ *
+ */
+class Config {
+	
+	static private $instance;
+	
+	/**
+	 * 所有app的配置集
+	 * array(
+	 * 		md5($app_conf_path) => array(
+	 * 
+	 * 		),
+	 * 		... ,
+	 * );
+	 * @var array
+	 */
+	static protected $appConfigs = array();
+	
+	/**
+	 * 
+	 * 当前app的配置
+	 * @var array
+	 */
+	protected $appConfig;
+	
+	/**
+	 * 
+	 * 不允许直接 new
+	 */
+	private function __construct() {
+		
+	}
+	
+	/**
+	 * 
+	 * 载入配置,如果成功,返回instance
+	 * @param string $app_conf_path 配置文件的路径
+	 * @return KIF\Core\Config
+	 */
+	static public function load($app_conf_path) {
+		$app_conf_path = realpath($app_conf_path);
+		$app_conf_key = md5($app_conf_path);
+		if (!isset(self::$appConfigs[$app_conf_key])) {
+			$config = include($app_conf_path);
+			if (!is_array($config)) {
+				throw new Exception("load app_conf_path fail: {$app_conf_path}");
+			}
+			# 将conf的path存进来
+			$config['conf_path'] = $app_conf_path;
+			
+			self::$appConfigs[$app_conf_key] = $config;
+		}
+		
+		if (is_null(self::$instance)) {
+			self::$instance = new self();
+		}
+		
+		self::$instance->appConfig = self::$appConfigs[$app_conf_key];
+		
+		return self::$instance;
+	}
+	
+	/**
+	 * 
+	 * 单例模式
+	 * @param string $app_conf_path
+	 * @return KIF\Core\Config
+	 */
+	static public function getInstance() {
+		if (is_null(self::$instance)) {
+			self::$instance = new self();
+		}
+		
+		if (is_null(self::$instance->appConfig)) {
+			throw new Exception('instance appconfig is null, pleace run KIF\Core\Config::load !');
+		}
+		
+		return self::$instance;
+	}
+	
+	/**
+	 * 
+	 * 获取指定配置项的值
+	 * @param string $key
+	 * @return mixed
+	 */
+	public function get($key) {
+		if (empty($key)) {
+            return false;
+        }   
+    
+        $keys = explode('.', $key);
+    
+        $value = $this->appConfig;
+    
+        foreach ($keys as $tmpKey) {
+            if (!isset($value[$tmpKey])) {
+                return false;
+            }   
+            $value = $value[$tmpKey];
+        }   
+        return $value;
+	}
+	
+	/**
+	 * 
+	 * 指定配置项是否设置了
+	 * @param string $key
+	 * @return boolean
+	 */
+	public function exists($key) {
+		return isset($this->appConfig[$key]);
+	}
+	
+	/**
+	 * 
+	 * 获取当前的配置信息
+	 * @return array
+	 */
+	public function current() {
+		return $this->appConfig;
+	}
+	
+	/**
+	 * 
+	 * 获取所有app的配置集
+	 * @return array
+	 */
+	static public function all() {
+		return self::$appConfigs;
+	}
+}

+ 238 - 0
KIF/Core/Controller.class.php

@@ -0,0 +1,238 @@
+<?php
+namespace KIF\Core;
+
+/**
+ * 定义MVC里的C抽象类,即Controller
+ * @author gaoxiaogang@gmail.com
+ */
+use KIF\String\Filter;
+
+use KIF\Core\View;
+use Exception;
+
+abstract class Controller {
+	protected $action;
+	
+	/**
+	 * 
+	 * 所有输出到模版的变量,都存放到这里面,以便统一管理
+	 * @var array
+	 */
+	protected $output = array();
+	
+	protected $tpl;
+	
+	/**
+     * 导航菜单
+     *
+     * @var Array
+     */
+    protected $navMenus = array();
+	
+	/**
+	 * 
+	 * 控制器入口方法
+	 * 
+	 */
+	abstract public function run();
+	
+	/**
+	 * 
+	 * 设定指定的名、值,模版里可以使用到
+	 * @param string $name
+	 * @param mixed $value
+	 * @throws Exception
+	 */
+	protected function setOutput($name, $value) {
+        if (!is_string($name)) {
+            throw new Exception('set output to template error, name not string !');
+        }
+        $this->output[$name] = $value;
+    }
+    
+    /**
+     * 
+     * 设置行为
+     * @param string $action
+     */
+    public function setAction($action) {
+    	if (empty($action)) {
+    		$action = 'default';
+    	}
+    	$this->action = 'do' . ucfirst($action);
+    }
+    
+	/**
+     * 添加导航栏
+     *
+     * @param string $title
+     * @param string $href
+     * @param string $target
+     * @param string $icon
+     * @return \KIF\Core\Controller
+     */
+    protected function addNavMenu($title, $href = null, $target = '_self', $icon = null) {
+        $this->navMenus[] = array(
+            'title' => $title,
+            'href' => $href,
+        	'target'	=> $target,
+            'icon' => $icon,
+        );
+        return $this;
+    }
+    
+    /**
+     * 
+     * 设置一批输出
+     * @param array $outputs
+     * 
+     */
+    protected function setOutputs(array $outputs) {
+    	$this->output = array_merge($this->output, $outputs);
+    }
+	
+	/**
+	 * 
+	 * 渲染结果
+	 * @param boolean $return 是否返回。true:返回渲染结果;false:直接输出结果
+	 * @return string | NULL
+	 */
+	public function render($return = false) {
+		if (!$this->tpl) {// 没有指定模文件
+			return null;
+		}
+		$objView = new View();
+		
+		$this->output['navMenus'] = $this->navMenus;
+		
+		$config = Config::getInstance()->current();
+		$this->output['web_cfg'] = $config['web_cfg'];
+		
+		$objView->assign($this->output);
+		
+		if ($return) {
+			return $objView->r($this->tpl);
+		} else {
+			$objView->d($this->tpl);
+		}
+	}
+	
+	/**
+	 *
+	 * 操作失败
+	 * !!如果页面请求的参数里含有 cross_cb,则认为是需要做跨域ajax的支持,跳转到 cross_cb 参数里指定的url
+	 * @param string $msg 失败描述
+	 */
+	protected function ajax_fail_exit($msg) {
+		$return = array(
+			'ok'	=> false,
+			'msg'	=> $msg,
+		);
+
+		# 支持跨域的AJAX GET,其实是基于jQuery的$.getJson实现的。之所以把AJAX POST、GET做不同实现,是出于性能和可用性考虑。
+		$jsonp_cb = Request::r('jsonp_cb', Filter::TRIM);
+		if ($jsonp_cb) {
+			$jsonp_cb = Filter::htmlspecialchars($jsonp_cb);// 防止css漏洞
+			$this->echo_exit("{$jsonp_cb}(".json_encode($return).")");
+		}
+
+		$this->echo_exit(json_encode($return));
+	}
+	
+	/**
+	 *
+	 * 操作成功
+	 * !!如果页面请求的参数里含有 cross_cb,则认为是需要做跨域ajax的支持,跳转到 cross_cb 参数里指定的url
+	 * @param string $msg 成功描述
+	 */
+	protected function ajax_success_exit($msg) {
+		$return = array(
+			'ok'	=> true,
+			'msg'	=> $msg,
+		);
+
+		# 支持跨域的AJAX GET,其实是基于jQuery的$.getJson实现的。之所以把AJAX POST、GET做不同实现,是出于性能和可用性考虑。
+		$jsonp_cb = Request::r('jsonp_cb', Filter::TRIM);
+		if ($jsonp_cb) {
+			$jsonp_cb = Filter::htmlspecialchars($jsonp_cb);// 防止css漏洞
+			$this->echo_exit("{$jsonp_cb}(".json_encode($return).")");
+		}
+
+		$this->echo_exit(json_encode($return));
+	}
+	
+	protected function echo_exit($msg) {
+		echo $msg;
+		exit;
+	}
+	
+	protected function echo_msg($msg) {
+		echo '['.date('Y-m-d H:i:s').'] '. $msg;
+		$this->newline();
+	}
+	
+	/**
+	 * 
+	 * 输出新行。
+	 */
+	protected function newline() {
+		if (Request::isCLI()) {
+			echo "\r\n";
+		} else {
+			echo "<br />";
+		}
+	}
+	
+	/**
+	 * 链接跳转
+	 * @param string $url 跳转的url
+	 * @return void
+	 **/
+	protected function redirect($url,$status ='') {
+		if ($status == '301') {
+			header("HTTP/1.1 301 Moved Permanently");
+		}
+		if (!empty($url)) {
+			header("Location: ".$url."");
+		}
+		exit;
+	}
+	
+	/**
+	 * 输出错误消息 - 后台
+	 * @author li.shuming@kimiss.com
+	 * @param string $msg
+	 */
+	protected function fail_exit_bs($msg = null) {
+		$permission_template_dir = Config::getInstance()->get('App_Path') . DS . 'template_dir';
+		$this->tpl = $permission_template_dir . '/prompt_message';
+		$this->setOutputs(array(
+				'type'	=> 'fail',
+				'msg'	=> $msg,
+				'referer'	=> Request::referer(),
+				'header_tpl'=> $permission_template_dir . '/header.html',
+				'bottom_tpl'=> $permission_template_dir . '/bottom.html',
+		));
+		$this->render();
+		exit;
+	}
+	
+	/**
+	 * 输出成功消息 - 后台
+	 * @author li.shuming@kimiss.com
+	 * @param string $msg
+	 */
+	protected function success_exit_bs($msg = null) {
+		$permission_template_dir = Config::getInstance()->get('App_Path') . DS . 'template_dir';
+		$this->tpl = $permission_template_dir . '/prompt_message';
+		$this->setOutputs(array(
+				'type'	=> 'success',
+				'msg'	=> $msg,
+				'referer'	=> Request::referer(),
+				'header_tpl'=> $permission_template_dir . '/header.html',
+				'bottom_tpl'=> $permission_template_dir . '/bottom.html',
+		));
+		$this->render();
+		exit;
+	}
+}

+ 59 - 0
KIF/Core/Core.class.php

@@ -0,0 +1,59 @@
+<?php
+namespace KIF\Core;
+
+use Exception;
+
+const VERSION	= '0.0.1';
+const AUTHOR	= 'gaoxiaogang@gmail.com';
+
+class Core {
+	static public function autoload($class_name) {
+//		var_dump($class_name);
+		
+		$class_name = str_replace('/', '', $class_name);
+		$class_name = str_replace('.', '', $class_name);
+		
+		$class_name = str_replace('\\', '/', $class_name);
+		$tmp_ds_pos = strpos($class_name, '/');
+		if ($tmp_ds_pos !== false) {
+			$root_namespace = substr($class_name, 0, $tmp_ds_pos);
+			$class_name = substr($class_name, $tmp_ds_pos+1);
+		} else {
+			$root_namespace = '';
+		}
+		# 当前app的库路径
+		if ($root_namespace) {
+			if ($root_namespace == 'KIF') {
+				$class_path = KIF_PATH . DS . $class_name . '.class.php';
+			} else {
+				try {# 框架一定会先加载KIF命名空间下的类,所以到这里Config肯定已被外部app初始化了
+					$allConfigs = Config::all();
+					foreach ($allConfigs as $config) {
+						if ($config['Namespace'] == $root_namespace) {
+							$app_lib_path = $config['Lib_Path'];
+							$class_path = $app_lib_path . DS . $class_name . '.class.php';
+							
+							break;
+						}
+					}
+				} catch (Exception $e) {
+					// do nothing
+				}
+			}
+			if (file_exists($class_path)) {
+				return require_once($class_path);
+			}
+		} else {
+			$class_path = KIF_PATH . DS . $class_name . '.class.php';
+			if (file_exists($class_path)) {
+				return require_once($class_path);
+			} else {
+				$app_lib_path = Config::getInstance()->get('Lib_Path');
+				$class_path = $app_lib_path . DS . $class_name . '.class.php';
+				if (file_exists($class_path)) {
+					return require_once($class_path);
+				}
+			}
+		}
+	}
+}

+ 400 - 0
KIF/Core/FController.class.php

@@ -0,0 +1,400 @@
+<?php
+
+namespace KIF\Core;
+
+use Exception;
+
+abstract class FController {
+	
+	/**
+	 * @var 请求
+	 */
+	protected $_setTemplate = false;
+	
+	/**
+	 * @var 模板类
+	 */
+	protected $_view;
+	
+	
+	/**
+	 *
+	 * 所有输出到模版的变量,都存放到这里面,以便统一管理
+	 * @var array
+	 */
+	protected $output = array();
+	
+	protected $tpl;
+	
+	/**
+	 * 导航菜单
+	 *
+	 * @var Array
+	 */
+	protected $navMenus = array();
+	
+	
+	protected $telData ;
+	
+	
+	public function __construct() {
+		//这行一定放在最前面; *因为__set方法用到它(当$this->xx = xx 的时候调用此方法)*
+		$this->_view = new CViewRender ();
+		$this->telData = $this->includeTemplateData();
+		
+		$this->ctl = $this->getController ();
+		$this->act = $this->getAction ();
+		$this->exe = $this->getExecName ();
+	
+	}
+	
+	/**
+	 * 加载环境
+	 */
+	abstract public function prepare($args);
+	
+	/**
+	 * 回显html内容
+	*/
+	public function display() {
+		if ($this->_setTemplate) {
+			$content = $this->_view->display ();
+			return $content;
+		}
+	}
+	
+	/**
+	 * 获取模板解析后的内容
+	 */
+	public function fetch($file) {
+		
+		///////////////增加/////////////////
+		if(isset($this->telData[$file])){
+			$this->tpl = $this->telData[$file];
+			return $this->render(true);
+		}
+		////////////////////////////////
+		
+		$this->setTemplate ( $file );
+		$content = $this->_view->fetch ();
+		return $content;
+	}
+	
+	/**
+	 * 设置模板
+	 */
+	public function setTemplate($file) {
+		///////////////增加/////////////////
+		if(isset($this->telData[$file])){
+			$this->tpl = $this->telData[$file];
+			$this->render();
+			return true;
+		}
+		////////////////////////////////
+		
+		
+		$this->_setTemplate = true;
+		$this->_view->setTemplate ( $file );
+	}
+	
+	/**
+	 * 魔术方法,自动给模板提供数据 *important!*
+	 */
+	public function __set($key, $val) {
+		$this->$key = $val;
+		$this->setOutput($key, $val);
+		//再分配给模板
+		$this->_view->set ( $key, $val );
+	}
+	
+	/**
+	 * 魔术方法,控制器中,调用没有的方法,直接到request中调用 *important!*
+	 * 如:$this->getController();
+	 */
+	public function __call($method, $args) {
+		//return call_user_func_array(array(CBase::$request, $method), $args);
+	}
+	
+	/**
+	 * 消息提示,后续扩展
+	 */
+	public function showMessage($msg, $url = '') {
+	
+		$str = '<script>';
+		if ($msg) {
+			$str .= "alert('$msg');";
+		}
+		if ($url) {
+			$str .= "location.href ='$url';";
+		} else {
+			if ($second) {
+				$str .= "top.location.reload();";
+			} else {
+				$str .= "history.go(-1);";
+			}
+		}
+		$str .= '</script>';
+		die ( $str );
+	}
+	
+	/**
+	 * 表单提交后,映射到相应的处理方法
+	 */
+	public function submitMap() {
+		$funcName = "_" . strtolower ( $this->act ) . $this->exe;
+		if (! method_exists ( $this, $funcName )) {
+			$this->showMessage ( '参数错误,非法操作~' );
+		}
+		//处理
+		$this->$funcName ();
+	}
+	
+	
+	/**
+	 * 获取控制器名称
+	 */
+	public function getController()
+	{
+		$conName = ucfirst(strip_tags($_REQUEST['c']));
+		return $conName ? $conName : 'Default';
+	}
+	
+	/**
+	 * 获取动作
+	 */
+	public function getAction()
+	{
+		return isset($_REQUEST['a']) ?  ucfirst(strip_tags($_REQUEST['a'])) : 'Default';
+	}
+	
+	/**
+	 * 获取动作
+	 */
+	public function getExecName()
+	{
+		return isset($_REQUEST['e']) ?  ucfirst(strip_tags($_REQUEST['e'])) : '';
+	}
+	
+	
+	/**
+	 *
+	 * 渲染结果
+	 * @param boolean $return 是否返回。true:返回渲染结果;false:直接输出结果
+	 * @return string | NULL
+	 */
+	public function render($return = false) {
+		if (!$this->tpl) {// 没有指定模文件
+			return null;
+		}
+		
+		$objView = new View();
+	
+		$this->output['navMenus'] = $this->navMenus;
+	
+		$config = Config::getInstance()->current();
+		$this->output['web_cfg'] = $config['web_cfg'];
+	
+		$objView->assign($this->output);
+		
+		if ($return) {
+			return $objView->r($this->tpl);
+		} else {
+			$objView->d($this->tpl);
+		}
+	}
+	
+	/**
+	 * 添加导航栏
+	 *
+	 * @param string $title
+	 * @param string $href
+	 * @param string $target
+	 * @param string $icon
+	 * @return \KIF\Core\Controller
+	 */
+	protected function addNavMenu($title, $href = null, $target = '_self', $icon = null) {
+		$this->navMenus[] = array(
+				'title' => $title,
+				'href' => $href,
+				'target'	=> $target,
+				'icon' => $icon,
+		);
+		return $this;
+	}
+	
+	/**
+	 *
+	 * 设定指定的名、值,模版里可以使用到
+	 * @param string $name
+	 * @param mixed $value
+	 * @throws Exception
+	 */
+	protected function setOutput($name, $value) {
+		if (!is_string($name)) {
+			throw new Exception('set output to template error, name not string !');
+		}
+		$this->output[$name] = $value;
+	}
+	
+	/**
+	 * 获取专题数据文件
+	 *
+	 * @param string $name
+	 */
+	static public function includeTemplateData() {
+		//$appPath = Config::getInstance ()->get ( 'App_Path' );
+		return include '/export/manager/product/app/product/kif/include/FControllerTemplate.data.php';
+	}
+	
+	
+}
+
+
+class CViewRender extends CRender {
+	/**
+	 * 构造函数
+	 *
+	 * @param string $templateFile    模板文件(扩展名为:tpl.php)
+	 * @param string $templateEngine  模板引擎
+	 */
+	public function __construct($templateFile = null, $templateEngine = "php") {
+		
+		if (! $templateEngine)
+			$templateEngine = "php";
+		if (! is_null ( $templateFile ))
+			$this->setTemplate ( $templateFile );
+	}
+
+	/**
+	 * 显示解析模板内容
+	 */
+	public function display() {
+		//$timer2 = new CTimer('模板渲染耗时:');
+		$content = $this->render ();
+		echo $content;
+		return $content;
+
+		//echo $timer2->mark('secs ', 6)."<br/>";
+	}
+
+	/**
+	 * 包含模板文件
+	 */
+	public function embedFile($file) {
+		if ($this->checkTemplateFile ( $file )) {
+			if ($this->_viewData) {
+				extract ( $this->_viewData );
+			}
+			include_once (APP_TEMPLATE . '/' . $file . '.tpl.php');
+		}
+	}
+
+	/**
+	 * 获取模板内容
+	 */
+	public function fetch() {
+		return $this->render ();
+	}
+
+	/**
+	 * 设置$_viewData数组值
+	 */
+	public function set($key, $val) {
+		$this->_viewData [$key] = $val;
+	}
+
+	/**
+	 * 魔术函数  给类属性赋值
+	 */
+	public function __set($key, $val) {
+		$this->set ( $key, $val );
+	}
+
+	/**
+	 * 设置模板文件
+	 *
+	 * @param  string $templateFile   缺损模板文件名
+	 */
+	public function setTemplate($file) {
+		$this->_templateFile = (defined ( 'APP_TEMPLATE' ) ? APP_TEMPLATE . '/' : "") . $file . ".tpl.php";
+	}
+
+	/**
+	 * 获取模板文件名
+	 */
+	public function getTemplate() {
+		return $this->_templateFile;
+	}
+
+	/**
+	 * 解析模板并返回内容
+	 *
+	 * @return string 解析内容
+	 */
+	public function render() {
+		//$timer2 = new CTimer($this->_templateFile.'渲染耗时内部调试:');
+		if (! $this->checkTemplateFile ( $this->_templateFile )) {
+			trigger_error ( "($this->_templateFile)模板文件不存在" );
+		}
+
+		if ($this->_viewData) {
+			extract ( $this->_viewData );
+		}
+		//开始缓存
+		ob_start ();
+		include ($this->_templateFile);
+		$content = ob_get_clean ();
+		ob_flush ();
+		flush ();
+		//echo $timer2->mark('secs ', 6)."<br/>";
+		return $content;
+	}
+}
+
+
+abstract class CRender {
+	/**
+	 * @var array  提取的内容数据
+	 */
+	protected $_viewData;
+
+	/**
+	 * @var string   模板文件
+	 */
+	protected $_templateFile;
+
+	/**
+	 * 解析模板文件并输出渲染内容
+	 */
+	abstract function display();
+
+	/**
+	 * 获取模板内容
+	*/
+	abstract function fetch();
+
+	/**
+	 * 渲染内容
+	*/
+	abstract function render();
+
+	/**
+	 * 验证文件是否存在
+	 *
+	 * @return boolean
+	*/
+	public function checkTemplateFile() {
+
+		if (! $this->_templateFile) {
+			//           throw new Exception("ddd");
+			return false;
+		}
+
+		if (! is_file ( $this->_templateFile )) {
+			//             throw new Exception("模板文件不存在:".$this->_templateFile);
+			return false;
+		}
+
+		return true;
+	}
+}

+ 64 - 0
KIF/Core/IPC.class.php

@@ -0,0 +1,64 @@
+<?php
+namespace KIF\Core;
+
+use KIF\Core\Config;
+use Exception;
+
+/**
+ * 
+ * In Process Call的缩写,即 进程内调用
+ * 目的:
+ *     1、保护上下文环境
+ *     2、简化调用代码
+ * 使用场景:
+ *     支持在任何地方调用基于KIF框架的app。
+ * @author husserlgao
+ *
+ */
+class IPC {
+	/**
+	 * 
+	 * 调用
+	 * @param string $conf_file 要调用的app类的配置文件
+	 * @param callback $callback 回调执行的函数
+	 */
+	static public function call($callback, $conf_file) {
+		# 尝试保存调用前的配置
+		try {
+			$pre_app_conf_path = Config::getInstance()->get('conf_path');
+		} catch (Exception $e) {
+			$pre_app_conf_path = false;
+		}
+		
+		Config::load($conf_file);
+		$callback();
+		
+		# 恢复到调用前的应用配置
+		if ($pre_app_conf_path) {
+			Config::load($pre_app_conf_path);
+		}
+	}
+	
+/**
+	 * 
+	 * 调用!!!这是个临时的方法,只因没有时间去把调call的地方改过来。
+	 * @param string $conf_file 要调用的app类的配置文件
+	 * @param callback $callback 回调执行的函数
+	 */
+	static public function call2($callback, $conf_file) {
+		# 尝试保存调用前的配置
+		try {
+			$pre_app_conf_path = Config::getInstance()->get('conf_path');
+		} catch (Exception $e) {
+			$pre_app_conf_path = false;
+		}
+		
+		Config::load($conf_file);
+		$callback();
+		
+		# 恢复到调用前的应用配置
+		if ($pre_app_conf_path) {
+			Config::load($pre_app_conf_path);
+		}
+	}
+}

+ 47 - 0
KIF/Core/Model.class.php

@@ -0,0 +1,47 @@
+<?php
+namespace KIF\Core;
+
+use KIF\Dao\DBAgileDev;
+
+/**
+ * 定义MVC里的M抽象类,即Model
+ * @author gaoxiaogang@gmail.com
+ */
+abstract class Model {
+	/**
+	 * @var KIF\Dao\DBAgileDev
+	 */
+    protected $objMainDao;
+    
+    /**
+     * 
+     * 设置该Model对应的主Dao类
+     */
+	abstract protected function setMainDao();
+
+    public function __construct() {
+    	$this->setMainDao();
+    }
+    
+	public function totals($condtion = null) {
+        return $this->objMainDao->totals($condtion);
+    }
+
+    public function get($id) {
+        $result = $this->gets(array($id));
+        if (!$result) {
+            return false;
+        }
+        return array_pop($result);
+    }
+
+    public function gets(array $ids) {
+        $result = $this->objMainDao->gets($ids);
+        return $result;
+    }
+    
+	public function getsAll($order = null, $limit = null) {
+        $ids = $this->objMainDao->getsAllIds($order, $limit);
+        return $this->gets($ids);
+    }
+}

+ 227 - 0
KIF/Core/PermissionController.class.php

@@ -0,0 +1,227 @@
+<?php
+namespace KIF\Core;
+
+/**
+ * 权限管理控制器
+ * 对整个控制器设定访问权限,也可以对控制器中的单个action设定访问权限
+ * @author li.shuming@kimiss.com
+ */
+
+use KIF\Verify;
+
+class PermissionController extends \KIF\Core\BKController {
+	
+	/**
+	 * 开启权限管理
+	 * 如果这里设置为true,那么只要继承了Permission的控制器,内部所有的行为都需要有权限才允许操作。
+	 * 默认为true,设置为false 时,可以在需要有权限操作的方法里加上 $this->requireCompetence()方法,要求有操作权限
+	 * @var Boolean
+	 */
+	static private $useCompetence = true;
+	
+	/**
+	 * 用户访问权限
+	 * @param Boolean $isHalt 没有权限访问时,是否要停机。默认停机,跳转到错误提示页
+	 * @return Boolean
+	 */
+	public function requireCompetence($isHalt = true) {
+		if (!self::$useCompetence) {
+			return true;
+		}
+		
+		if (is_null($isHalt)) {
+			$isHalt = true;
+		}
+		
+		# 先登录
+		parent::requireLogin();
+		
+		if (!self::isCompetence()) {
+			if ($isHalt) {
+				#TODO
+				self::no_permission_exit();
+			}
+			
+			return false;
+		}
+		
+		return true;
+	}
+	
+	/**
+	 * 当前用户是否有权访问
+	 * @return Boolean
+	 */
+	private function isCompetence() {
+		if (!self::isLogin()) {
+			return false;
+		}
+		
+		# 超级管理员啥权限都有
+		if (self::isSuperadmin()) {
+			return true;
+		}
+		
+		# 以下普通帐号权限
+		$c = Request::getInstance()->param('c');
+		$arr_class_path = array_map(function ($tmpV) {
+			return lcfirst($tmpV);
+		}, explode('_', $c));
+		
+		$c = implode('_', $arr_class_path);
+		$a = Request::getInstance()->param('a');
+		
+		$requestParams = array( //当前请求参数名称
+			'app_name'		=> lcfirst(Config::getInstance()->get('Namespace')),
+			'control_name'	=> $c,
+			'action_name'	=> $a ? lcfirst($a) : 'default',
+		);
+		
+		$is_cpt = true;
+		
+		//普通帐号没有的权限
+		$notCompetences = array(
+				array('control_name' => 'admin_platform_index'), //平台列表管理
+				array('control_name' => 'admin_backUser', 'action_name' => 'userList'), //帐号管理 列表
+				array('control_name' => 'admin_backUser', 'action_name' => 'CreateUser'),//帐号管理 创建帐号
+				array('control_name' => 'admin_backUser', 'action_name' => 'setPermission'),
+				array('control_name' => 'admin_backUser', 'action_name' => 'MP'),
+				array('control_name' => 'admin_platform_bulletin', 'action_name' => 'ReqCreate'), //添加公告
+				array('control_name' => 'admin_platform_bulletin', 'action_name' => 'PageUp'), //编辑公告
+		);
+		
+		foreach ($notCompetences as $tmpCompetence) {
+			if (lcfirst($tmpCompetence['control_name']) != $requestParams['control_name']) {
+				continue;
+			}
+			
+			if (!$tmpCompetence['action_name']) { //对控制器下的所有行为拥有权限
+				$is_cpt = false;
+				break;
+			}
+			
+			if (lcfirst($tmpCompetence['action_name']) == $requestParams['action_name']) {
+				$is_cpt = false;
+				break;
+			}
+		}
+		
+		return $is_cpt;
+	}
+	
+	/**
+	 * 通过用户id获取用户所属的组id集合
+	 * @param int $uid
+	 * @return array
+	 */
+	public function getsGroupidsByUid($uid) {
+		if (!Verify::unsignedInt($uid)) {
+			return array();
+		}
+		
+		$groupids = array();
+		
+			$objDKifUsergroupRelation = new \Cas\Dao\KifUsergroupRelation();
+			$groupids = $objDKifUsergroupRelation->getsGroupids($uid);
+		
+		return $groupids;
+	}
+	
+	/**
+	 * 获取用户组所有的权限设置
+	 * @param array $groupids
+	 * @return array
+	 */
+	public function getsCompetencesByGroupids($groupids) {
+		if (!$groupids) {
+			return array();
+		}
+		
+		$competences = array();
+		
+			$objDKifUsergroupPermission = new \Cas\Dao\KifUsergroupPermission();
+			$competences = $objDKifUsergroupPermission->getsCompetencesByGroupids($groupids);
+		
+		return $competences;
+	}
+	
+	/**
+	 * 是否超级管理员
+	 * @return boolean
+	 */
+	static public function isSuperadmin() {
+		if (!self::$useCompetence) {
+			return true;
+		}
+		
+		$result = \Cas\Module\Permission::isSuperadmin();
+		
+		return $result;
+	}
+	
+	/**
+	 * 输出错误消息
+	 * @param string $msg
+	 */
+	public function fail_exit_cpt($msg = null) {
+		$permission_template_dir = Config::getInstance()->get('App_Path') . DS . 'template_dir';
+		$this->tpl = $permission_template_dir . '/admin/permission/prompt_message';
+		$this->setOutputs(array(
+				'type'	=> 'fail',
+				'msg'	=> $msg,
+				'referer'	=> Request::referer(),
+				'header_tpl'=> $permission_template_dir . '/header.html',
+				'bottom_tpl'=> $permission_template_dir . '/bottom.html',
+		));
+		$this->render();
+		exit;
+	}
+	
+	/**
+	 * 输出成功消息
+	 * @param string $msg
+	 */
+	public function success_exit_cpt($msg = null) {
+		$permission_template_dir = Config::getInstance()->get('App_Path') . DS . 'template_dir';
+		$this->tpl = $permission_template_dir . '/admin/permission/prompt_message';
+		$this->setOutputs(array(
+				'type'	=> 'success',
+				'msg'	=> $msg,
+				'referer'	=> Request::referer(),
+				'header_tpl'=> $permission_template_dir . '/header.html',
+				'bottom_tpl'=> $permission_template_dir . '/bottom.html',
+		));
+		$this->render();
+		exit;
+	}
+	
+	public function no_permission_exit() {
+		$permission_template_dir = Config::getInstance()->get('App_Path') . DS . 'template_dir';
+		$this->tpl = $permission_template_dir . '/admin/permission/prompt_message';
+		$this->setOutputs(array(
+				'type'	=> 'no_permission',
+		));
+		$this->render();
+		exit;
+	}
+	
+	public function run() {
+		if (isset(self::$useCompetence) && self::$useCompetence) {
+			$this->requireCompetence();
+		}
+		
+		# 登陆用户
+		$this->setOutput('backuser', $this->getUser());
+		
+		# 是否管理员
+		$IS_ADMIN = false;
+		if (self::isSuperadmin()) {
+			$IS_ADMIN = true;
+		}
+		$this->setOutput('IS_ADMIN', $IS_ADMIN);
+		
+		$action = $this->action;
+		$this->$action();
+	}
+	
+}

+ 714 - 0
KIF/Core/Request.class.php

@@ -0,0 +1,714 @@
+<?php
+namespace KIF\Core;
+
+use KIF\Route;
+
+use KIF\String\String;
+
+use KIF\String\Filter;
+use Exception;
+use KIF\Exception\ParamsException;
+use KIF\Core\Config;
+use KIF\Verify;
+
+/**
+ * 封装了一些方法,用于方便的处理从客户端提交过来的数据
+ * @author gaoxiaogang@gmail.com
+ */
+class Request {
+	/**
+	 * 
+	 * 存放当前请求的参数
+	 * @var array
+	 */
+	protected $params;
+	
+	static protected $instance;
+	
+	# 不允许被new
+	private function __construct() {
+		
+	}
+	
+	static public function getInstance() {
+		if (is_null(self::$instance)) {
+			self::$instance = new self();
+		}
+		
+		self::$instance->params = self::params();
+		
+		return self::$instance;
+	}
+	
+	/**
+	 * 获取ip地址
+	 *
+	 * @return string ip地址:如 59.151.9.90
+	 */
+    public function ip() {
+        if (isset($_SERVER)) {
+            if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
+                $realip = $_SERVER['HTTP_X_FORWARDED_FOR'];
+            } elseif (isset($_SERVER['HTTP_CLIENT_IP'])) {
+                $realip = $_SERVER['HTTP_CLIENT_IP'];
+            } else {
+                $realip = $_SERVER['REMOTE_ADDR'];
+            }
+        } else {
+            if (getenv("HTTP_X_FORWARDED_FOR")) {
+                $realip = getenv("HTTP_X_FORWARDED_FOR");
+            } elseif (getenv("HTTP_CLIENT_IP")) {
+                $realip = getenv("HTTP_CLIENT_IP");
+            } else {
+                $realip = getenv("REMOTE_ADDR");
+            }
+        }
+        $realip = preg_replace('#,.*$#', '', $realip);
+        $realip = preg_replace('#[^\d\.]+#', '', $realip);// 从支付平台发现,有时会获取到这种ip,%20%2058.255.8.39,说明前面有有空格,处理一下
+        return $realip;
+    }
+
+    /**
+     * 从 REQUEST_URI 字段获取请求路径
+     * 如:/article/index.php?id=857&action=show 会被处理成:article/index.php
+     *
+     * @param string | null $request_uri
+     * @return string
+     */
+    public function path($request_uri = null) {
+    	if (is_null($request_uri)) {
+            $request_uri = $_SERVER['REQUEST_URI'];
+        }
+
+        if (($iPos = strpos($request_uri, '?')) !== false) {
+            $request_uri = substr($request_uri, 0, $iPos);
+        }
+        $request_uri = str_replace(' ', '/', trim(str_replace('/', ' ', $request_uri)));
+        $request_uri = urldecode($request_uri);
+
+        return $request_uri;
+    }
+
+    /**
+     *
+     * 获取当前请求的url
+     * @return String 完整的url,如:http://www.kimiss.com/test.php?c=Default
+     */
+    public function url() {
+    	$port = $_SERVER['SERVER_PORT'] == '80' ? '' : ":{$_SERVER['SERVER_PORT']}";
+    	$url = "http://{$_SERVER['HTTP_HOST']}{$_SERVER['REQUEST_URI']}";
+    	return $url;
+    }
+    
+	/**
+	 * 
+	 * 获取分类url的模版
+	 * @param string $pageKey 请求里表示分页的参数
+	 * @return string
+	 */
+	public function pageUrlTpl($pageKey = 'page') {
+		$routeParams = Route::getInstance()->getsRouteParams();
+		if ($routeParams) {
+			$routeParams[$pageKey] = '{page}';
+			$path = Route::getInstance()->reverse($routeParams);
+			$url = "http://{$_SERVER['HTTP_HOST']}/" . $path;
+			return String::jointUrl($url, array_diff_key($_GET, $routeParams));
+		} else {
+			$request_params = $_GET;
+			$request_params[$pageKey] = '{page}';
+			$url = "http://{$_SERVER['HTTP_HOST']}/" . $this->path();
+			return String::jointUrl($url, $request_params);
+		}
+	}
+	
+	/**
+	 * 
+	 * 获取带请求协议的域名url,如:https://kimiss.com
+	 * @return string
+	 */
+	static public function schemeDomain() {
+		$protocol_str = $_SERVER['SERVER_PROTOCOL'];
+		if (!$protocol_str) {
+			return '';
+		}
+		list($protocol,) = explode('/', $protocol_str);
+		return strtolower($protocol) . "://{$_SERVER['HTTP_HOST']}";
+	}
+    
+    /**
+     * 
+     * 获取当前请求的顶级域名
+     * @param string $host
+     * @return string
+     */
+    public function rootDomain($host = null) {
+    	if (is_null($host))
+    		$host = $_SERVER['HTTP_HOST'];
+    	
+    	if (empty($host)) {
+    		return '';
+    	}
+    	$host = strtolower($host);
+    	
+    	# 是个ip直访的url
+    	if (filter_var($host, FILTER_VALIDATE_IP)) {
+    		return '';
+    	}
+    	# 不带.,可能是绑host的域名
+    	if (strpos($host, '.') === FALSE) {
+    		return $host;
+    	}
+    	
+    	$domain_suffs = array(
+    		'.com', '.cn', '.net', '.com.cn', '.net.cn', 
+    		'.org', '.me', '.biz', '.name', '.org.cn', '.gov.cn',
+    		'.info', '.so', '.tel', '.mobi', '.asia', '.cc', '.tv', '.co',
+    	);
+    	
+    	foreach ($domain_suffs as $suff) {
+    		$tmp_stuff_pos = strrpos($host, $suff);
+    		if ($tmp_stuff_pos === false) {
+    			continue;
+    		}
+    		$tmp_rootdomain_pos = strrpos($host, '.', 0-strlen(substr($host, $tmp_stuff_pos))-1);
+    		if ($tmp_rootdomain_pos === false) {
+    			return $host;
+    		}
+    		
+    		return substr($host, $tmp_rootdomain_pos+1);
+    	}
+    }
+
+    /**
+     * 获取请求url中的查询字符串
+     *
+     * @return string
+     */
+    static public function queryString() {
+    	return $_SERVER['QUERY_STRING'];
+    }
+
+    /**
+     *
+     * 获取请求的referer
+     * @return string
+     */
+    static public function referer() {
+    	return $_SERVER['HTTP_REFERER'];
+    }
+
+    /**
+     * 是否POST请求
+     *
+     * @return boolean
+     */
+    static public function isPost() {
+    	return strtoupper($_SERVER['REQUEST_METHOD']) == 'POST';
+    }
+
+    /**
+     * 是否GET请求
+     *
+     * @return boolean
+     */
+    static public function isGet() {
+    	return strtoupper($_SERVER['REQUEST_METHOD']) == 'GET';
+    }
+
+    /**
+     * 该请求是否由 蜘蛛 发出
+     *
+     * @return boolean
+     */
+    public function isSpider() {
+        if (self::isCLI()) {
+            return false;
+        }
+        if (!isset($_SERVER['HTTP_USER_AGENT'])) {
+            return false;
+        }
+        $bots = array(
+            'Googebot', 'Baiduspider', 'Yahoo! Slurp', 'Sosospider', 'Sogou', 'Sogou-Test-Spider',
+            'Sogou head spider', 'YoudaoBot', 'qihoobot', 'iaskspider', 'LeapTag', 'MSIECrawler'
+        );
+        foreach ($bots as $bot) {
+            if (strpos($_SERVER['HTTP_USER_AGENT'], $bot) !== false) {
+                return true;
+            }
+        }
+        if (stristr($_SERVER['HTTP_USER_AGENT'], 'Windows') !== false) {
+            return false;
+        }
+        $bots = array(
+            'spider', 'bit', 'crawler', 'slurp', 'subscriber', 'http',
+            'rssreader', 'blogline', 'greatnews', 'feed', 'alexa', 'php'
+        );
+        foreach ($bots as $bot) {
+            if (stristr($_SERVER['HTTP_USER_AGENT'], $bot) !== false) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * 该请求是否从相同的主机过来,即来源页是否与当前页处于相同域下
+     *
+     * @return boolean
+     */
+    public function isFromSameHost() {
+        if (!isset($_SERVER['HTTP_REFERER'])) {
+            return true;
+        }
+        $url_parts = parse_url($_SERVER['HTTP_REFERER']);
+        $host = $url_parts['host'];
+        if ($_SERVER['HTTP_HOST'] == $host) {
+        	return true;
+        }
+        return false;
+    }
+
+    /**
+     * 从请求的 $_GET 变量里获取 变量的整数值,如果不存在该变量或不为整数,则返回 $default 值
+     * @param string $varName 变量名
+     * @param int $default 默认值
+     * @return int
+     */
+    public function varGetInt($varName, $default = 0) {
+    	if (!isset($_GET[$varName])) {
+    		return $default;
+    	}
+
+    	if (!Verify::unsignedInt($_GET[$varName])) {
+    		return $default;
+    	}
+
+    	return $_GET[$varName];
+    }
+
+    /**
+     * 从请求的 $_POST 变量里获取 变量的整数值,如果不存在该变量或不为整数,则返回 $default 值
+     * @param string $varName 变量名
+     * @param int $default 默认值
+     * @return int
+     */
+    public function varPostInt($varName, $default = 0) {
+    	if (!isset($_POST[$varName])) {
+            return $default;
+        }
+
+        if (!Verify::unsignedInt($_POST[$varName])) {
+            return $default;
+        }
+
+        return $_POST[$varName];
+    }
+
+    /**
+     * 从请求的 $_POST 变量里获取 变量的整数值,如果不存在该变量或不为整数,则返回 $default 值
+     * @param string $varName 变量名
+     * @param int $default 默认值
+     * @return int
+     */
+    public function varRequestInt($varName, $default = 0) {
+        if (!isset($_REQUEST[$varName])) {
+            return $default;
+        }
+
+        if (!Verify::unsignedInt($_REQUEST[$varName])) {
+            return $default;
+        }
+
+        return $_REQUEST[$varName];
+    }
+
+	/**
+	 * 获取请求的参数集。
+	 * 支持http访问的参数 以及 命令行下访问的参数
+	 * demo1:php test.php -p3 -t=abc --opt=valopt --opt2 valopt2
+	 * demo2 http://test.kimiss.com/index.php?c=xxx&a=ddd
+	 * @return array
+	 */
+	public function params() {
+	    if (self::isCLI()) {
+	    	return self::cliParams();
+	    } else {
+	    	return $_REQUEST;
+	    }
+	}
+	
+	/**
+	 * 
+	 * 获取命令行下传递进来的参数
+	 * 只支持以 - 或 -- 开头的参数
+	 * demo:php test.php -p3 -t=abc --opt=valopt --opt2 valopt2
+	 * @return array
+	 */
+	private function cliParams() {
+        $result = array();
+        $params = $GLOBALS['argv'];
+        
+        array_shift($params);
+        reset($params);
+        do {
+        	$tmpEachResult = each($params);
+        	if (!$tmpEachResult) {
+        		break;
+        	}
+        	list($tmp, $p) = $tmpEachResult;
+        	
+            if ($p{0} == '-') {
+                $pname = substr($p, 1);
+                $value = false;
+                if ($pname{0} == '-') {// 长选项 (--<param>)
+                    $pname = substr($pname, 1);
+                    if (strpos($p, '=') !== false) {
+                        // value specified inline (--<param>=<value>)
+                        list($pname, $value) = explode('=', substr($p, 2), 2);
+                    }
+                } else {// 短选项
+                	if (strpos($p, '=') !== false) {
+                        // value specified inline (-<param>=<value>)
+                        list($pname, $value) = explode('=', substr($p, 1), 2);
+                    } else if (strlen($p) > 1)  {
+                    	$pname = substr($p, 1, 1);
+                    	$value = substr($p, 2);
+                    }
+                }
+                # 如果上面没有取到值,并且下一个不是以-开头的,则下一个值为当前参数的值
+                $nextparm = current($params);
+                if ($value === false
+                	&& $nextparm !== false
+                	&& $nextparm{0} != '-'
+                ) {
+                	list($tmp, $value) = each($params);
+                }
+                $result[$pname] = (string) $value;// 将 false转为空串,以便与http访问时对参数的处理一致
+            } else {
+                # 不是以-指定开始的参数,一律丢弃
+                //$result[] = $p;
+            }
+        } while (true);
+        
+        return $result;
+    }
+
+	/**
+	 *
+	 * 获取 $_GET 里指定key $name 的值,同时可以指定 $filters 过滤方法处理值
+	 *
+	 * 用法:
+	 * 1、对值不做任何处理:Request::getInstance()->get('content', null);
+	 * 2、对值做html转义处理:Request::getInstance()->get('name');
+	 *
+	 * @param string $name
+	 * @param mixed $filters 默认使用html转义过滤。当值为null里,对值不做任何过滤。
+	 * $filters支持以下格式:
+	 * 1) 标量类型,如:
+	 *    Filter::HTMLSPECIALCHARS;
+	 * 2) 多个filter,如:
+	 *    array(
+	 *        Filter::HTMLSPECIALCHARS,
+	 *        Filter::STRIP_TAGS,
+	 *    );
+	 * 3) 多个filter,并且某些filter另外指定参数,如:
+	 *    array(
+	 *        array(
+	 *            Filter::HTMLSPECIALCHARS	=> ENT_QUOTES,
+	 *        ),
+	 *        array(
+	 *            Filter::STRIP_TAGS	=> "<a>",
+	 *        ),
+	 *        Filter::STRIP_SELECTED_TAGS,
+	 *    );
+	 *
+	 * @throws ParamsException "无效的过滤方法filter"
+	 * @return string
+	 */
+	public function get($name, $filters = array(Filter::HTMLSPECIALCHARS, Filter::TRIM)) {
+		if (!isset($_GET[$name])) {
+			return false;
+		}
+
+		$val = $_GET[$name];
+
+		if (is_null($filters)) {
+			return $val;
+		}
+
+		if (!is_array($filters)) {
+			$filters = array($filters);
+		}
+
+		return self::filter($val, $filters);
+	}
+
+	/**
+	 *
+	 * get的别名方法
+	 * @param string $name
+	 * @param mixed $filters 默认使用html转义过滤。当值为null里,对值不做任何过滤。
+	 * @throws ParamsException "无效的过滤方法filter"
+	 * @return string
+	 */
+	public function g($name, $filters = array(Filter::HTMLSPECIALCHARS, Filter::TRIM)) {
+		return self::get($name, $filters);
+	}
+
+	/**
+	 *
+	 * 获取 $_POST 里指定key $name 的值,同时可以指定 $filters 过滤方法处理值
+	 *
+	 * 用法:
+	 * 1、对值不做任何处理:Request::getInstance()->post('content', null);
+	 * 2、对值做html转义处理:Request::getInstance()->post('name');
+	 *
+	 * @param string $name
+	 * @param mixed $filters 默认使用html转义过滤。当值为null里,对值不做任何过滤。
+	 * @throws ParamsException "无效的过滤方法filter"
+	 * @return string
+	 */
+	public function post($name, $filters = array(Filter::HTMLSPECIALCHARS, Filter::TRIM)) {
+		if (!isset($_POST[$name])) {
+			return false;
+		}
+
+		$val = $_POST[$name];
+
+		if (is_null($filters)) {
+			return $val;
+		}
+
+		if (!is_array($filters)) {
+			$filters = array($filters);
+		}
+
+		return self::filter($val, $filters);
+	}
+
+	/**
+	 *
+	 * post的别名方法
+	 * @param string $name
+	 * @param mixed $filters 默认使用html转义过滤。当值为null里,对值不做任何过滤。
+	 * @throws ParamsException "无效的过滤方法filter"
+	 * @return string
+	 */
+	public function p($name, $filters = array(Filter::HTMLSPECIALCHARS, Filter::TRIM)) {
+		return self::post($name, $filters);
+	}
+
+	/**
+	 *
+	 * 获取 $_REQUEST 里指定key $name 的值,同时可以指定 $filters 过滤方法处理值
+	 * !! 因为与类同名的方法会被认为是构造函数,所以这个方法名加个小字符 !!
+	 *
+	 * 用法:
+	 * 1、对值不做任何处理:Request::getInstance()->requestV('content', null);
+	 * 2、对值做html转义处理:Request::getInstance()->requestV('name');
+	 *
+	 * @param string $name
+	 * @param mixed $filters 默认使用html转义过滤。当值为null里,对值不做任何过滤。
+	 * @throws ParamsException "无效的过滤方法filter"
+	 * @return string
+	 */
+	public function requestV($name, $filters = array(Filter::HTMLSPECIALCHARS, Filter::TRIM)) {
+		if (!isset($_REQUEST[$name])) {
+			return false;
+		}
+
+		$val = $_REQUEST[$name];
+
+		if (is_null($filters)) {
+			return $val;
+		}
+
+		if (!is_array($filters)) {
+			$filters = array($filters);
+		}
+
+		return self::filter($val, $filters);
+	}
+
+	/**
+	 *
+	 * requestV的别名方法
+	 * @param string $name
+	 * @param mixed $filters 默认使用html转义过滤。当值为null里,对值不做任何过滤。
+	 * @throws ParamsException "无效的过滤方法filter"
+	 * @return string
+	 */
+	public function r($name, $filters = array(Filter::HTMLSPECIALCHARS, Filter::TRIM)) {
+		return self::requestV($name, $filters);
+	}
+
+	/**
+	 *
+	 * 获取 $_COOKIE 里指定key $name 的值,同时可以指定 $filters 过滤方法处理值
+	 *
+	 * 用法:
+	 * 1、对值不做任何处理:Request::getInstance()->cookie('content', null);
+	 * 2、对值做html转义处理:Request::getInstance()->cookie('name');
+	 *
+	 * @param string $name
+	 * @param mixed $filters 默认使用html转义过滤。当值为null里,对值不做任何过滤。
+	 * @throws ParamsException "无效的过滤方法filter"
+	 * @return string | false
+	 */
+	public function cookie($name, $filters = array(Filter::HTMLSPECIALCHARS, Filter::TRIM)) {
+		if (!isset($_COOKIE[$name])) {
+			return false;
+		}
+
+		$val = $_COOKIE[$name];
+
+		if (is_null($filters)) {
+			return $val;
+		}
+
+		if (!is_array($filters)) {
+			$filters = array($filters);
+		}
+
+		return self::filter($val, $filters);
+	}
+
+	/**
+	 *
+	 * cookie的别名方法
+	 * @param string $name
+	 * @param mixed $filters 默认使用html转义过滤。当值为null里,对值不做任何过滤。
+	 * @throws ParamsException "无效的过滤方法filter"
+	 * @return string | false
+	 */
+	public function c($name, $filters = array(Filter::HTMLSPECIALCHARS, Filter::TRIM)) {
+		return self::cookie($name, $filters);
+	}
+	
+	/**
+	 *
+	 * 获取当前请求参数里指定key $name 的值,同时可以指定 $filters 过滤方法处理值
+	 *
+	 * 用法:
+	 * 1、对值不做任何处理:Request::getInstance()->param('content', null);
+	 * 2、对值做html转义处理:Request::getInstance()->param('name');
+	 *
+	 * @param string $name
+	 * @param mixed $filters 默认使用html转义过滤。当值为null里,对值不做任何过滤。
+	 * @throws ParamsException "无效的过滤方法filter"
+	 * @return string | false
+	 */
+	public function param($name, $filters = array(Filter::HTMLSPECIALCHARS, Filter::TRIM)) {
+		if (!isset($this->params[$name])) {
+			return false;
+		}
+
+		$val = $this->params[$name];
+
+		if (is_null($filters)) {
+			return $val;
+		}
+
+		if (!is_array($filters)) {
+			$filters = array($filters);
+		}
+
+		return self::filter($val, $filters);
+	}
+
+	/**
+	 *
+	 * 使用用户指定的 过滤方法,过滤值 $val
+	 * @param string $val
+	 * @param array $filters 过滤方法相关信息
+	 * @throws ParamsException "无效的过滤方法filter"
+	 * @return string
+	 */
+	private function filter($val, array $filters) {
+		foreach ($filters as $filter) {
+			$params = array($val);
+			if (is_array($filter)) {
+				list($filter_name, $filter_options) = each($filter);
+			} else {
+				$filter_name = $filter;
+				$filter_options = null;
+			}
+			if (!Filter::isValid($filter_name)) {
+				throw new ParamsException("无效的过滤方法filter");
+			}
+			$val = call_user_func(array('KIF\String\Filter', $filter_name), $val, $filter_options);
+		}
+
+		return $val;
+	}
+
+	/**
+     * 是否处于命令行下
+     * @return Boolean
+     */
+    static public function isCLI() {
+        if (php_sapi_name() == "cli"//PHP 4 >= 4.0.1, PHP 5 support php_sapi_name function
+            || empty($_SERVER['PHP_SELF'])//If PHP is running as a command-line processor this variable contains the script name since PHP 4.3.0. Previously it was not available.
+        ) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * 是否类unix系统
+     * @return Boolean
+     */
+    static public function isUnixLike() {
+        $os = strtolower(PHP_OS);
+        if (in_array($os, array('linux', 'freebsd', 'unix', 'netbsd'))) {
+            return true;
+        }
+        return false;
+    }
+    
+    /**
+     * 
+     * 分发请求,调用Controller以及对应的action
+     */
+    public function dispatch() {
+    	$params = $this->params;
+    	$controllerName = self::getController($params);
+    	
+    	if (!class_exists($controllerName)) {
+    		throw new Exception("controller: {$controllerName} not exists!");
+    	}
+    	
+    	$action = self::getAction($params);
+    	
+    	$controller = new $controllerName();
+    	$controller->setAction($action);
+    	$controller->run();
+    	
+    	
+    	if (method_exists($controller, 'display')) {
+    		$controller->display();
+    	}
+    }
+    
+    public function getController() {
+    	$c = self::param('c');
+    	$c = trim($c, '_');
+    	
+    	$arr_class_path = array_map(function ($tmpV) {
+    		return ucfirst($tmpV);
+    	}, explode('_', $c));
+    	$c = join('\\', $arr_class_path);
+    	$NS = Config::getInstance()->get('Namespace');
+    	$controller = "{$NS}\Controller\\{$c}";
+    	return $controller;
+    }
+    
+    public function getAction(array $params = null) {
+    	$action = self::param('a');
+    	
+    	return $action;
+    }
+}

+ 88 - 0
KIF/Core/View.class.php

@@ -0,0 +1,88 @@
+<?php
+namespace KIF\Core;
+use Smarty;
+
+include KIF_PATH . "/smarty/Smarty.class.php";
+
+use KIF\Core\Config;
+
+/**
+ * 定义MVC里的V,即View
+ * @author gaoxiaogang@gmail.com
+ */
+class View extends Smarty
+{
+	/**
+	 *
+	 * 存放要自动注册的 函数 信息
+	 * @var array
+	 */
+	static private $auto_registers = array();
+
+	private $tpl_type ;  //静态文件的类型
+	
+	private $directory;  //模板子目录
+	
+	public function __construct($directory = '')
+	{
+		$this->left_delimiter = "<{";
+		$this->right_delimiter = "}>";
+		$this->directory = $directory;
+
+		foreach (self::$auto_registers as $tmpV) {
+			$this->register_function($tmpV['smarty_func'], $tmpV['php_func'], $tmpV['cacheable'], $tmpV['cache_attrs']);
+		}
+	}
+
+	/**
+	 *
+	 * 类方法.通知模版引擎自动注册函数
+	 * 目的:在使用项目处,方便的自动注册一些全局性的模版函数
+	 * @param string $function 注册后,模版使用时的 函数名
+	 * @param string $function_impl 要被注册的php函数名
+	 * @param bookean $cacheable
+	 * @param unknown_type $cache_attrs
+	 * @author gxg@gaojie100.com
+	 */
+	static function auto_register_function($function, $function_impl, $cacheable=true, $cache_attrs=null) {
+		self::$auto_registers[] = array(
+			'smarty_func'	=> $function,
+			'php_func'		=> $function_impl,
+			'cacheable'		=> $cacheable,
+			'cache_attrs'	=> $cache_attrs,
+		);
+	}
+
+	/**
+	 * 
+	 * 渲染模版并打印出来
+	 * @param string $tpl 模版文件名
+	 * @param unknown_type $cache_id
+	 * @param unknown_type $compile_id
+	 * @return NULL 无返回值
+	 */
+	public function d($tpl, $cache_id = null, $compile_id = null)
+	{
+		$this->r($tpl, $cache_id , $compile_id ,true);
+	}
+	
+	/**
+	 * 
+	 * 渲染模版。默认时返回内容
+	 * @param string $tpl 模版文件名
+	 * @param unknown_type $cache_id
+	 * @param unknown_type $compile_id
+	 * @param boolean $display 是否打印出来?
+	 * @return string
+	 */
+	public function r($tpl, $cache_id = null, $compile_id = null, $display = false)
+	{
+		$smarty_config = Config::getInstance()->get('smarty');
+		
+		$this->template_dir = $smarty_config['template_dir'];
+		$this->compile_dir = $smarty_config['compile_dir'];
+		$this->tpl_type = $smarty_config['tpl_type'] ? $smarty_config['tpl_type'] : 'html';
+		$content = $this->fetch($tpl . "." . $this->tpl_type, $cache_id , $compile_id , $display);
+		return $content;
+	}
+}

+ 167 - 0
KIF/Core/WXController.class.php

@@ -0,0 +1,167 @@
+<?php
+namespace KIF\Core;
+use Cas\Module\Passport;
+use KIF\Data\ResultWrapper;
+use KIF\String\String;
+/**
+ * 
+ * 继承自Controller,封装了微信用户登录状态、用户信息等一些方法
+ * @author lishumingoo@gmail.com
+ */
+class WXController extends \KIF\Core\Controller {
+	
+	/**
+	 * 
+	 * 判断是否登录
+	 * @return boolean
+	 */
+	public function isLogin() {
+		$user =  self::getUser();
+		if (!$user) {
+			return false;
+		}
+		
+		if ($user['loginType'] == Passport::LOGIN_TYPE_NOT_AUTH) {
+			return false;
+		}
+		
+		return true;
+	}
+	
+	/**
+	 * 
+	 * 获取用户id
+	 * @return boolean | int
+	 */
+	public function getUid() {
+		$user = $this->getUser();
+		if (!$user) {
+			return false;
+		}
+		
+		return $user['uid'];
+	}
+	
+	/**
+	 * 
+	 * 获取用户登陆类型
+	 * 授权、未授权登陆
+	 * @return boolean | int Passport::LOGIN_TYPE_NOT_AUTH Passport::LOGIN_TYPE_HAS_AUTH
+	 */
+	public function getLoginType() {
+		$user = $this->getUser();
+		if (!$user) {
+			return false;
+		}
+		
+		return $user['loginType'];
+	}
+	
+	/**
+	 * 
+	 * 获取登陆用户openid
+	 * @return boolean | string
+	 */
+	public function getOpenid() {
+		$user = $this->getUser();
+		if (!$user) {
+			return false;
+		}
+		
+		return $user['openid'];
+	}
+	
+	/**
+	 * 
+	 * 获取用户微信昵称
+	 * @return boolean | string
+	 */
+	public function getNickname() {
+		$user = $this->getUser();
+		if (!$user) {
+			return false;
+		}
+		
+		return $user['nickname'];
+	}
+	
+	/**
+	 * 
+	 * 获取用户微信头像
+	 * @return boolean | string
+	 */
+	public function getHeadimgurl() {
+		$user = $this->getUser();
+		if (!$user) {
+			return false;
+		}
+		
+		return $user['headimgurl'];
+	}
+	
+	/**
+	 * 
+	 * 从Cookie中获取用户的登陆信息
+	 * @return boolean | array
+	 */
+	public function getUser() {
+		static $user = null;
+		if ($user) {
+			return $user;
+		}
+		
+		$objPassport = new Passport();
+		$tmpResult = $objPassport->getLoginInfo();
+		if ($tmpResult->isSuccess()) {
+			$user = $tmpResult->getData();
+		}
+		
+		return $user;
+	}
+	
+	/**
+	 * 判断登陆状态是否满足活动要求,满足时返回true,不满足返回授权地址
+	 * @param Boolean $isAuth 是否需要授权。true时需要loginType = Passport::LOGIN_TYPE_HAS_AUTH; false时loginType至少属于Passport::LOGIN_TYPE_NOT_AUTH;
+	 * @return ResultWrapper success 满足条件; fail 不满足条件,需要通过返回的授权url进行授权
+	 */
+	public function checkLoginStatus($isAuth = false, $from) {
+		$authUrl = Request::schemeDomain() . '/connect/';
+		
+		if (!$isAuth) {
+			if (!self::getUser()) {
+				$state = 'base';
+			}
+		} else {
+			if (!self::isLogin()) {
+				$state = 'userinfo';
+			}
+		}
+		
+		if ($state) {
+			return ResultWrapper::fail(String::jointUrl($authUrl, array('from' => $from, 'state' => $state)));
+		}
+		
+		return ResultWrapper::success();
+	}
+	
+	public function __construct() {
+		if (self::isLogin()) {
+			define('IS_LOGIN', 1);
+			define('UID', self::getUid());
+			define('NICKNAME', self::getNickname());
+			define('AVATAR', self::getHeadimgurl());
+		} else {
+// 			$tmpResult = self::checkLoginStatus(false, Request::url());
+// 			if (!$tmpResult->isSuccess()) {
+// 				self::redirect($tmpResult->getData());
+// 			}
+			
+			define('IS_LOGIN', 0);
+		}
+	}
+	
+	public function run() {
+		$action = $this->action;
+		$this->$action();
+	}
+}

+ 417 - 0
KIF/Curl.class.php

@@ -0,0 +1,417 @@
+<?php
+namespace KIF;
+/**
+ * @name Curl.class.php
+ * @desc curl操作类
+ * @author 曹晓冬
+ * @createtime 2008-12-01 14:58
+ * @updatetime
+ * $curl_obj = new Curl('http://www.demo.com/');
+ * 基本使用方法
+ * $curl_obj->init();
+ * $curl_obj->setOptions(array("type"=>"post", "fields"=>$fields, "return"=>1, "onerror"=>1));
+ * $result = $curl_obj->getResult();
+ * 另外还提供了两种快速使用方法
+ * 1.post方式
+ * $result = $curl_obj->post(array('param' => 'value'));
+ * 2.get方法
+ * $result = $curl_obj->get();
+ * 
+ */
+
+class Curl
+{
+  /**
+  * 当前curl对话
+  * @var resource
+  * @access private
+  */
+  private $ch;
+  
+  /**
+  * 当前发送的地址
+  *
+  * @var string
+  * @access private
+  */
+  private $url;
+  
+  /**
+  * 调试信息
+  *
+  * @var string
+  * @access private
+  */
+  private $debug; 
+  
+  /**
+  * 返回是否包括header头
+  *
+  * @var integer
+  * @access private
+  */
+  private $header = 0; 
+  
+  /**
+  * 构造函数
+  * @return void
+  * @access public
+  */
+  public function __construct($url)
+  {
+      $this->url = $url;
+  	  $this->ch = @curl_init();
+      if (!$this->ch)
+      {
+          return false;
+      }
+  }
+  
+  /**
+  * 设定返回是否包括header 头
+  * @param integer $header 01,1包括,0不包括
+  * @return void 
+  * @access public
+  */
+  public function setHeader($header = 0)
+  {
+      $this->header = $header;
+  }
+  
+  
+  /**
+  * 初始化curl对话
+  * @param string $url 当前发送的地址
+  * @return boolean
+  * @access private
+  */
+  public function init()
+  {
+      $this->basic();
+      return true;
+  }
+  
+  /** 
+  * 基本选项
+  *
+  * @return void
+  * @access private
+  */
+  private function basic()
+  {
+      curl_setopt($this->ch, CURLOPT_URL, $this->url);
+      curl_setopt($this->ch, CURLOPT_HEADER, $this->header);
+  }
+  
+  /**
+   * 
+   * 调整用curl原生的方法,直接设置option
+   * @param unknown_type $key
+   * @param unknown_type $value
+   */
+  public function setRawOption($key, $value) {
+  	curl_setopt($this->ch, $key, $value);  
+  }
+
+  /**
+  * 设置选项
+  *
+  * @return void
+  * @access public
+  */
+  public function setOptions($options = array())
+  {
+      if (is_array($options))
+      {
+          foreach ($options as $key=>$value)    
+          {
+              $this->$key = $value;    
+          }
+      }
+      //如果HTTP返回大于300, 是否显示错误
+      if (isset($this->onerror) && $this->onerror)
+      {
+          curl_setopt($this->ch, CURLOPT_FAILONERROR, 1);    
+      }
+      
+      //是否有返回值
+      if (isset($this->return) && $this->return == true && !isset($this->file)) 
+      {
+          curl_setopt($this->ch, CURLOPT_RETURNTRANSFER, 1);
+      }
+      
+      //HTTP 认证
+      if (isset($this->username) && $this->username != "") 
+      {
+          curl_setopt($this->ch, CURLOPT_USERPWD, "{$this->username}:{$this->password}");
+      }
+      
+      //SSL 检查
+      if (isset($this->sslVersion)) 
+      {
+          curl_setopt($this->ch, CURLOPT_SSLVERSION, $this->sslVersion);
+      }
+      if (isset($this->sslCert)) 
+      {
+          curl_setopt($this->ch, CURLOPT_SSLCERT, $this->sslCert);
+      }
+      if (isset($this->sslCertPasswd)) 
+      {
+          curl_setopt($this->ch, CURLOPT_SSLCERTPASSWD, $this->sslCertPasswd);
+      }
+      
+      //代理服务器
+      if (isset($this->proxy))
+      {
+          curl_setopt($this->ch, CURLOPT_PROXY, $this->proxy);
+      }
+      if (isset($this->proxyUser) || isset($this->proxyPassword)) 
+      {
+          curl_setopt($this->ch, CURLOPT_PROXYUSERPWD, "{$this->proxyUser}:{$this->proxyPassword}");
+      }
+      
+      //传输类型
+      if (isset($this->type)) 
+      {
+          switch (strtolower($this->type)) 
+          {
+              case "post":
+                  curl_setopt($this->ch, CURLOPT_POST, 1);
+                  break;
+              case "put":
+                  curl_setopt($this->ch, CURLOPT_PUT, 1);
+                  break;
+          }
+      }        
+      
+      //上传相关
+      if (isset($this->file)) 
+      {
+          if (!isset($this->filesize)) 
+          {
+              $this->filesize = filesize($this->file);
+          }
+          curl_setopt($this->ch, CURLOPT_INFILE, $this->file);
+          curl_setopt($this->ch, CURLOPT_INFILESIZE, $this->filesize);
+          curl_setopt($this->ch, CURLOPT_UPLOAD, 1);
+      }
+      
+      //数据发送
+      if (isset($this->fields)) 
+      {
+          if (!is_array($this->fields))
+          {
+              if (!isset($this->type))
+              {
+                  $this->type = "post";
+                  curl_setopt($this->ch, CURLOPT_POST, 1);
+              }
+              curl_setopt($this->ch, CURLOPT_POSTFIELDS, $this->fields);
+          }
+          else 
+          {
+              if (!empty($this->fields))
+              {
+                  $p = array();
+                  foreach ($this->fields as $key=>$value)
+                  {
+                      $p[] = $key . "=" . urlencode($value);
+                  }
+                  if (!isset($this->type))
+                  {
+                      $this->type = "post";
+                      curl_setopt($this->ch, CURLOPT_POST, 1);
+                  }
+                  curl_setopt($this->ch, CURLOPT_POSTFIELDS, implode("&", $p));
+              }        
+          }
+      }
+      
+      
+      //错误相关
+      if (isset($this->progress) && $this->progress == true) 
+      {
+          curl_setopt($this->ch, CURLOPT_PROGRESS, 1);
+      }
+      if (isset($this->verbose) && $this->verbose == true) 
+      {
+          curl_setopt($this->ch, CURLOPT_VERBOSE, 1);
+      }
+      if (isset($this->mute) && !$this->mute) 
+      {
+          curl_setopt($this->ch, CURLOPT_MUTE, 0);
+      }
+
+      //其它相关
+      if (isset($this->followLocation))
+      {
+          curl_setopt($this->ch, CURLOPT_FOLLOWLOCATION, $this->followLocation);
+      }
+      if (isset($this->timeout) && $this->timeout>0)
+      {
+          curl_setopt($this->ch, CURLOPT_TIMEOUT, $this->timeout);    
+      }
+      else
+      {
+          curl_setopt($this->ch, CURLOPT_TIMEOUT, 20);
+      }
+      if (isset($this->connecttimeout) && $this->connecttimeout>0)
+      {
+          curl_setopt($this->ch, CURLOPT_CONNECTTIMEOUT, $this->connecttimeout);    
+      }
+      else
+      {
+          curl_setopt($this->ch, CURLOPT_CONNECTTIMEOUT, 5);
+      }
+      if (isset($this->userAgent)) 
+      {
+          curl_setopt($this->ch, CURLOPT_USERAGENT, $this->userAgent);
+      }
+      
+      //cookie 相关
+      if (isset($this->cookie)) 
+      {
+          $cookieData = "";
+          foreach ($this->cookie as $name => $value) 
+          {
+              $cookieData .= urlencode($name) . "=" . urlencode($value) . ";";
+          }
+          curl_setopt($this->ch, CURLOPT_COOKIE, $cookieData);
+      }
+      
+      //http 头
+      if (isset($this->httpHeaders)) 
+      {
+          curl_setopt($this->ch, CURLOPT_HTTPHEADER, $this->httpHeaders );
+      }
+      
+      curl_setopt($this->ch, CURLOPT_HTTPAUTH, 'CURLAUTH_DIGEST');
+  }    
+      
+  /**
+  * 设置返回结果选项,并返回结果
+  * @return string 返回结果
+  * @access public
+  */
+  public function getResult()
+  {
+      $result = curl_exec($this->ch);
+      return $result;
+  }
+  
+  /**
+  * 关闭当前curl对话
+  * @return void
+  * @access public
+  */
+  public function close()
+  {
+      @curl_close($this->ch);    
+  }
+  
+  /**
+  * 得到对话中产生的错误描述
+  * @return string 错误描述
+  * @access public
+  */
+  public function getError()
+  {
+      return curl_error($this->ch);    
+  }
+  
+  /**
+  * 得到对话中产生的错误号
+  * @return integer 错误号
+  * @access public
+  */
+  public function getErrno()
+  {
+      return curl_errno($this->ch);    
+  }
+  
+  /**
+  * 中断执行,并输出错误信息
+  * @param string $msg 错误信息
+  * @return void
+  * @access private
+  */
+  private function halt($msg)
+  {
+      $message = "\n<br>信息:{$msg}";
+      $message .= "\n<br>错误号:".$this->getErrno();
+      $message .= "\n<br>错误:".$this->getError();
+      echo $message;
+      exit;
+  }    
+  
+  /**
+  * 调试信息
+  * 
+  * @return void
+  * @access private
+  */
+  private function debug()
+  {
+      $message .= "\n<br>错误号:".$this->getErrno();
+      $message .= "\n<br>错误:".$this->getError();
+      $this->debug = $message;
+  }    
+  
+  /**
+  * 获得以POST方式发送的结果
+  * @param array/string $fields 发送的数据
+  * @return string 返回的结果
+  * @access public
+  */
+  public function post($fields = array())
+  {
+      $re = $this->init();
+      if ($re){
+          $this->setOptions(array("type"=>"post", "fields"=>$fields, "return"=>1, "onerror"=>1));
+          $result = $this->getResult();
+          //$this->close();
+          return $result;
+      }else{
+          return "";
+      }
+  }
+  
+  /**
+  * 获得以GET方式发送的结果
+  * @return string 返回的结果
+  * @access public
+  */
+  public function get()
+  {
+      $re = $this->init();
+      if ($re){
+          $this->setOptions(array("return"=>1, "onerror"=>1));
+          $result = $this->getResult();
+          $this->close();
+          return $result; 
+      }else{
+          return "";
+      }
+  }
+  
+  /**
+  * 静态调用,获得以COOKIE方式发送的结果
+  * @param string $url 发送的地址
+  * @param array/string $fields 发送的数据
+  * @return string 返回的结果
+  * @access public
+  */
+  public function cookie($fields = array())
+  {
+      $re = $this->init();
+      if ($re){
+          $this->setOptions(array("cookie"=>$fields, "return"=>1, "onerror"=>1));
+          $result = $this->getResult();
+          $this->close();
+          return $result; 
+      }else{
+          return "";
+      }
+  }
+}
+?>

+ 130 - 0
KIF/Dao/.svn/entries

@@ -0,0 +1,130 @@
+10
+
+dir
+924
+svn://182.92.3.30/project/onepage/KIF/Dao
+svn://182.92.3.30/project
+
+
+
+2016-01-21T06:27:41.361155Z
+137
+yubin
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+140960d9-11d8-4647-83e1-275f326d1242
+
+DBAgileDev.class.php
+file
+
+
+
+
+2016-02-15T00:10:46.000000Z
+45a2bc41612a80d22a71585fcefed151
+2016-01-21T06:27:41.361155Z
+137
+yubin
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+9454
+
+AbstractDao.class.php
+file
+
+
+
+
+2016-02-15T00:10:46.000000Z
+04698d720c688006e6918218d9ff459d
+2016-01-21T06:27:41.361155Z
+137
+yubin
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+24488
+
+SqlHelper.class.php
+file
+
+
+
+
+2016-02-15T00:10:46.000000Z
+dd8edf2a1125a15ffbd27429e22f94b0
+2016-01-21T06:27:41.361155Z
+137
+yubin
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+4185
+

+ 5 - 0
KIF/Dao/.svn/prop-base/AbstractDao.class.php.svn-base

@@ -0,0 +1,5 @@
+K 14
+svn:executable
+V 1
+*
+END

+ 5 - 0
KIF/Dao/.svn/prop-base/DBAgileDev.class.php.svn-base

@@ -0,0 +1,5 @@
+K 14
+svn:executable
+V 1
+*
+END

+ 5 - 0
KIF/Dao/.svn/prop-base/SqlHelper.class.php.svn-base

@@ -0,0 +1,5 @@
+K 14
+svn:executable
+V 1
+*
+END

+ 829 - 0
KIF/Dao/.svn/text-base/AbstractDao.class.php.svn-base

@@ -0,0 +1,829 @@
+<?php
+namespace KIF\Dao;
+
+use KIF\Exception\DaoException;
+
+use KIF\Verify;
+
+use KIF\Exception\ParamsException;
+use Exception;
+use KIF\Db\MySQLi;
+use KIF\Dao\SqlHelper;
+use KIF\Core\Config;
+use KIF\Cache\Memcached;
+use KIF\Math\Math;
+
+/**
+ * 数据库操作的抽象类
+ * @author gaoxiaogang@yoka.com
+ *
+ */
+abstract class AbstractDao {
+    /**
+     * 数据表名
+     * 该抽象类针对表的操作(如:delete、create、findBy)都是基于该属性。由继承的子类填充该值。
+     * 好处:1、如果继承的子类只针对一个表,把tableName设为静态表名即可。
+     * 2、如果继承的子类针对一类表操作(如根据某个唯一标志把相同结构的数据hash到多张表中),则针对不同表操作时,动态设置该值
+     * @var string
+     */
+    protected $tableName;
+
+    /**
+     * 表的主键名。默认为id
+     * @var string
+     */
+    protected $primaryKey = 'id';
+    
+    /**
+     * 
+     * 是否使用缓存
+     * @var boolean
+     */
+    private $useCache = false;
+    
+    /**
+     * 
+     * 缓存失效时间 ,默认1小时
+     * @var int
+     */
+    private $cache_expiration = 3600;
+    
+    /**
+     * 
+     * 存放这个数据表的缓存标志。
+     * 目的:清空这个标志,能清空这张表的所有行记录缓存
+     * @var string
+     */
+    private $tableCacheFlag;
+    
+    /**
+     * 
+     * 用于缓存表记录的memcache缓存集群标志
+     * 优先使用config里的 memcached->dao_cache;如不存在,则使用 memcached->default;如仍不存在,则不启用缓存.
+     * @var string
+     */
+    private $memcacheClusterFlag;
+
+    /**
+     *
+     * 插入行为
+     * @var string
+     */
+    const PARAM_CREATE_ACTION_INSERT = 'INSERT INTO';
+
+    /**
+     *
+     * 插入,但重复时忽略
+     * @var string
+     */
+    const PARAM_CREATE_ACTION_INSERT_IGNORE = 'INSERT IGNORE';
+
+    /**
+     *
+     * 插入,但重复时完全replace
+     * @var string
+     */
+    const PARAM_CREATE_ACTION_REPLACE = 'REPLACE INTO';
+
+    /**
+     *
+     * 插入,但重复时自动转为update
+     * @var string
+     */
+    const PARAM_CREATE_ACTION_ONDUPLICATE = 'ON DUPLICATE KEY UPDATE';
+
+    /**
+     * 数据库实例,可被动态切换到主库或从库
+     *
+     * @var KIF\Db\MySQLi
+     */
+    protected $db;
+    
+    /**
+     * 
+     * mysql集群标志
+     * @var string
+     */
+    private $cluster_flag;
+
+    /**
+     * 
+     * @param string $cluster_flag mysql集群标志
+     * 
+     */
+    public function __construct($cluster_flag = 'default') {
+    	$appConfig = Config::getInstance()->current();
+    	$dbConfig = $appConfig['db'];
+    	
+    	if (!$dbConfig || !isset($dbConfig[$cluster_flag]) || !is_string($dbConfig[$cluster_flag])) {
+    		throw new ParamsException("load config error:{$dbConfig}");
+    	}
+    	$this->cluster_flag = $cluster_flag;
+    	
+    	if (isset($appConfig['memcached']['dao_cache'])) {
+    		$this->memcacheClusterFlag = 'dao_cache';
+    	} elseif (isset($appConfig['memcached']['default'])) {
+    		$this->memcacheClusterFlag = 'default';
+    	} else {# 没有缓存配置,不启用缓存
+    		$this->useCache(false);
+    	}
+
+    	$dsn = $dbConfig[$cluster_flag];
+    	$this->db = new MySQLi($dsn);
+    }
+
+    /**
+     *
+     * 获取设置的主键名
+     * @return string
+     */
+    public function getPrimaryKey() {
+    	return $this->primaryKey;
+    }
+
+    /**
+     * 设置表名
+     * 动态设置表名是为了拆表
+     * @param $tableName
+     */
+    public function setTableName($tableName) {
+        $this->tableName = $tableName;
+    }
+    
+    /**
+     * 
+     * 控制是否使用缓存。使用缓存,会将数据表的行记录缓存起来,极大的提高效率和高可用;同时也会在记录删除、更新时,清空对应的缓存。
+     * @param boolean $useCache
+     */
+    public function useCache($useCache = true) {
+    	$this->useCache = (bool) $useCache;
+    }
+
+    /**
+     * 解析条件
+     * @param array|string $condition
+     * @throws Exception parameter errors
+     * @return string
+     */
+    public function parseCondition($condition = null) {
+        if(empty($condition)) return '';
+        
+        // 如果条件语句是字符串 直接拼接
+        if(is_string($condition)){
+        	return "
+            WHERE ". $condition ." ";
+        }
+        // 如果不是字符串 强制传递数组 如果不是数组抛异常
+        if(!is_array($condition)){
+        	throw new Exception('parameter errors');
+        }
+		$condition = $this->quote($condition);
+        $where = array();
+        foreach($condition as $tmpK => $tmpV) {
+        	# 避免条件字段重复,支持二维数组。比如$conditon = array(
+        	# 	array(
+        	#		'publish_time'	=> SqlHelper::addCompareOperator(">=", $s_ts),
+        	#	),
+        	#	array(
+        	#		'publish_time'	=> SqlHelper::addCompareOperator("<", $e_ts),
+        	#	),
+        	# );
+        	# 以前只能这样变相支持:$condition = array(
+        	# 	'publish_time'	=> SqlHelper::addCompareOperator(">=", $s_ts),
+        	#	'`publish_time`'	=> SqlHelper::addCompareOperator("<", $e_ts),
+        	# );
+        	if (Verify::naturalNumber($tmpK)) {
+        		list($key, $val) = each($tmpV);
+        	} else {
+        		$key = $tmpK;
+        		$val = $tmpV;
+        	}
+        	
+        	if (strpos($key, '`') === false // 没用使用 ` 字符,比如 `status`
+        		&& strpos($key, '(') === false) // 也不含有括号(即不是函数),比如 last_insert_id(status)
+        	{
+        		$key = "`{$key}`";
+        	}
+            if(is_scalar($val)) {
+                $where[] = "{$key} " . SqlHelper::explodeCompareOperator($val);
+            } elseif(is_array($val)) {
+                $where[] = "{$key} IN (".join(',', $val).")";
+            } else {
+                throw new Exception('parameter errors');
+            }
+        }
+        return "
+            WHERE ".join(' && ', $where)."
+        ";
+    }
+
+    /**
+     * 根据条件查找主键id列表集
+     * 做这个方法,是出于架构上的考虑。只取出主键id集,然后通过主键id集才去取数据(内存或数据库)
+     * @param array|String $condition 条件
+     * @param string | int $limit 指定分页
+     * @param string $order 指定排序
+     * @return array 如:array(123, 124)。无值时返回空数组
+     */
+    public function findIdsBy($condition = null, $limit = null, $order = null) {
+        $result = $this->findBy($condition, $this->primaryKey, $limit, $this->primaryKey, $order);
+        if (! $result) {
+            return array();
+        }
+
+        return array_keys($result);
+    }
+
+    /**
+     * 查找的底层方法。对查找只提供有限支持,太复杂的查询请手动sql
+     * final 让该方法禁止被继承
+     * 1、根据条件查找,多条件只支持 与(&&),不支持或之类的
+     * 2、支持limit
+     * 3、group by
+     * @param array|string $condition
+     * @param string $returnAssociateKey 如果指定该指,返回的值不再以0,1,2为key,而是以其对应的值为key
+     * @param string | int $limit
+     * @param string $selectCols 要获取的列。语法:id, uid ,默认为 *
+     * @param string $order 指定排序
+     * @return array
+     */
+    final public function findBy($condition = null, $returnAssociateKey = null, $limit = null, $selectCols = '*', $order = null) {
+        $where = $this->parseCondition($condition);
+        if (!isset($limit) || !preg_match('#^(?:\d+\s*,\s*)?\d+$#', $limit)) {
+            $strLimit = ' ';
+        } else {
+            $strLimit = " LIMIT $limit ";
+        }
+
+        $strOrder = '';
+        if(!empty($order)) {
+            $strOrder = " ORDER BY {$order} ";
+        }
+
+        if(!isset($selectCols)) {
+            $selectCols = '*';
+        }
+
+        $sql = "SELECT {$selectCols} FROM {$this->tableName}
+            {$where}
+            {$strOrder}
+            {$strLimit}
+            ;
+        ";
+        return $this->db->fetchAll($sql, $returnAssociateKey);
+    }
+
+    public function fetchOne($condition = null, $selectCols = '*', $order = null) {
+        $result = self::findBy($condition, null, 1, $selectCols, $order);
+        if (!$result) {
+        	return false;
+        }
+        return array_pop($result);
+    }
+
+    /**
+     * 获取所有
+     * @param string $order 排序方式。默认以主键倒序;有效的格式:"create_time desc"
+     * @param string $limit 该参数用于支持分页,默认为不使用分页。格式 "offset, length"
+     * @return array
+     */
+    public function getsAll($order = null, $limit = null) {
+        $ids = self::getsAllIds($order, $limit);
+        return $this->gets($ids);
+    }
+
+    /**
+     * 获取所有id列表集
+     * @param string $order 排序方式。默认以主键倒序;有效的格式:"create_time desc"
+     * @param string $limit 该参数用于支持分页,默认为不使用分页。格式 "offset, length"
+     * @return array
+     */
+    public function getsAllIds($order = null, $limit = null) {
+    	if (is_null($order)) {
+            $order = "{$this->primaryKey} desc";
+        }
+        if (!is_string($order)) {
+            throw new Exception('$order 必须是字符串或null');
+        }
+
+        $condition = null;
+        $ids = self::findIdsBy($condition, $limit, $order);
+        return $ids;
+    }
+
+    /**
+     * 获取指定$id
+     * @param int | string $id
+     * @return false | array
+     */
+    public function get($id) {
+        if (Verify::int($id)) {
+            if ($id < 1) {
+                return false;
+            }
+        } elseif (is_string($id)) {
+            if (strlen($id) == 0) {
+                return false;
+            }
+        } else {
+            return false;
+        }
+
+        $result = self::gets(array($id));// 调用该类自身的gets方法,而不是继承者的gets方法!
+        if (!$result) {
+            return false;
+        }
+        return array_pop($result);
+    }
+
+    /**
+     * 批量获取信息
+     * @param array $ids id组成的数组
+     * @return array 无结果时返回空数组
+     */
+    public function gets(array $ids) {
+    	$return = array();
+        if (empty($ids)) {
+            return $return;
+        }
+        $ids = array_unique($ids);
+        
+    	# 从缓存取 
+        $cache_return = self::getsFromCache($ids);
+        $non_cache_ids = array_diff($ids, array_keys($cache_return));
+        if (!$non_cache_ids) {
+        	return $cache_return;
+        }
+        
+        # 从DB取
+        $db_return = self::getsFromDB($non_cache_ids);
+        if ($db_return) {
+        	self::setToCache($db_return);
+        }
+        foreach ($ids as $id) {
+        	if (isset($cache_return[$id])) {
+        		$return[$id] = $cache_return[$id];
+        		continue;
+        	}
+        	
+        	if (isset($db_return[$id])) {
+        		$return[$id] = $db_return[$id];
+        		continue;
+        	}
+        }
+        
+        return $return;
+    }
+    
+    /**
+     * 
+     * 从后端数据库取结果
+     * @param array $ids
+     * @return array 格式:array(
+     * 		(int) id => (mixed) value,
+     * 		... ,
+     * )
+     * 
+     */
+    private function getsFromDB(array $ids) {
+    	$return = array();
+    	$condition = array(
+            $this->primaryKey => $ids,
+        );
+        $result = $this->findBy($condition, $this->primaryKey);
+        if (!$result) {
+            return $return;
+        }
+
+        foreach ($ids as $id) {
+        	$id = (string) $id;// php的数组下标是int整时,如果超出了操作系统平台的最大有符号正整数后,会取不到值。转成字符串型,解决这个bug
+            if (array_key_exists($id, $result)) {
+                $return[$id] = $result[$id];
+            }
+        }
+        return $return;
+    }
+    
+    /**
+     * 
+     * 获取表缓存的标志。后续这个表里每行的缓存,都会关联上这个表缓存标志。
+     * 所以只要清空这个标志,即可使整个表记录的缓存失效。
+     * @Exception DaoException::CACHE_SET_TABLE_FLAG_ERROR
+     * @Exception DaoException::CACHE_SERVICE_UNAVAILABLE
+     * @return String
+     */
+    public function getTableCacheFlag() {
+    	if ($this->tableCacheFlag) {
+    		return $this->tableCacheFlag;
+    	}
+    	
+    	$dbconfig = Config::getInstance()->get('db');
+    	$cacheKey_elements = array(
+    		serialize($dbconfig[$this->cluster_flag]),
+    		$this->tableName,
+    		'table_cache_flag',
+    	);
+    	$key = Math::md5_16(join($cacheKey_elements, '::'));
+    	$objMemcached = new Memcached($this->memcacheClusterFlag);
+    	$tmpGetResult = $objMemcached->get($key);
+    	if ($tmpGetResult) {
+    		$this->tableCacheFlag = $tmpGetResult;
+    		return $this->tableCacheFlag;
+    	}
+    	
+    	if (is_null($tmpGetResult)) {// 缓存不存在,建立
+    		$tmpVal = microtime(true);
+    		if ($objMemcached->set($key, $tmpVal, 0)) {// 永不过期
+    			$this->tableCacheFlag = $tmpVal;
+    			return $this->tableCacheFlag;
+    		}
+    		
+    		# TODO 设置缓存失败的处理
+    		throw new DaoException(DaoException::CACHE_SET_TABLE_FLAG_ERROR);
+    	}
+    	
+    	if (!$tmpGetResult) {
+    		# TODO memcache服务不可用的处理
+    		throw new DaoException(DaoException::CACHE_SERVICE_UNAVAILABLE);
+    	}
+    }
+    
+    /**
+     * 
+     * 获取指定ids集对应的缓存key集
+     * @param array $ids  array(13343, 9939);
+     * @Exception DaoException::CACHE_SET_TABLE_FLAG_ERROR
+     * @Exception DaoException::CACHE_SERVICE_UNAVAILABLE
+     * @return array 格式:array(
+     *  (int) id => (string) cacheKey,
+     *  ... ,
+     * 
+     * )
+     */
+    public function getsCacheKeys(array $ids) {
+    	$tableCacheFlag = $this->getTableCacheFlag();
+    	$cacheKeys = array();
+    	$dbconfig = Config::getInstance()->get('db');
+    	foreach ($ids as $id) {
+    		$cacheKey_elements = array(
+    			$tableCacheFlag,
+    			serialize($dbconfig[$this->cluster_flag]),
+    			$this->tableName,
+    			$id,
+    			'row_cache',// 这是针对数据库记录行的缓存
+    		);
+    		$cacheKeys[$id] = Math::md5_16(join($cacheKey_elements, '::'));
+    	}
+    	
+    	return $cacheKeys;
+    }
+    
+    /**
+     * 
+     * 从缓存里取数据表对应的行记录
+     * @param array $ids
+     * @return array 只返回缓存里有的值,如果缓存里没有任何值,则返回空数组
+     */
+    private function getsFromCache(array $ids) {
+    	$return = array();
+    	if (!$this->useCache) {
+    		return $return;
+    	}
+    	try {
+    		$cacheKeys = self::getsCacheKeys($ids);
+    	} catch (DaoException $e) {
+    		return $return;
+    	}
+    	
+    	$objMemcached = new Memcached($this->memcacheClusterFlag);
+    	$cacheVals = $objMemcached->gets($cacheKeys);
+    	
+    	$cache_return = array_combine(array_keys($cacheKeys), $cacheVals);
+    	foreach ($cacheVals as $tmpK => $tmpV) {
+    		if (is_null($tmpV)) {
+    			continue;
+    		}
+    		
+    		$return[array_search($tmpK, $cacheKeys)] = $tmpV;
+    	}
+    	
+    	return $return;
+    }
+    
+    /**
+     * 
+     * 从缓存里将$id对应的行记录删除
+     * @param int $id
+     * @throws Exception
+     * @return Boolean
+     */
+    private function deleteFromCache($id) {
+    	if (!$this->useCache) {
+    		return false;
+    	}
+    	$objMemcached = new Memcached($this->memcacheClusterFlag);
+    	
+    	if (Verify::unsignedInt($id)) {
+	    	try {
+	    		$cacheKeys = self::getsCacheKeys(array($id));
+	    	} catch (DaoException $e) {
+	    		return false;
+	    	}
+    		return $objMemcached->delete(array_pop($cacheKeys));
+    	}
+    	
+    	if (is_array($id) && !empty($id)) {
+    		try {
+	    		$cacheKeys = self::getsCacheKeys($id);
+	    	} catch (DaoException $e) {
+	    		return false;
+	    	}
+	    	
+    		if (count($cacheKeys) == 1) {
+    			return $objMemcached->delete(array_pop($cacheKeys));
+    		}
+    		return $objMemcached->deletes($cacheKeys);
+    	}
+    	
+    	return false;
+    }
+    
+    /**
+     * 
+     * 清除缓存的这张表的所有记录
+     * @return Boolean
+     */
+    public function deleteAllCaches() {
+    	if (!$this->useCache) {
+    		return false;
+    	}
+    	
+    	try {
+    		$tableCacheFlag = $this->getTableCacheFlag();
+    	} catch (DaoException $e) {
+    		return false;
+    	}
+    	
+    	$objMemcached = new Memcached($this->memcacheClusterFlag);
+    	return $objMemcached->delete($tableCacheFlag);
+    }
+    
+    /**
+     * 
+     * 将gets出来的结果集,存到缓存
+     * @param array $results
+     * @return boolean
+     */
+    private function setToCache(array $results) {
+    	if (!$this->useCache) {
+    		return false;
+    	}
+    	if (!$results) {
+    		return false;
+    	}
+    	try {
+    		$cacheKeys = self::getsCacheKeys(array_keys($results));
+    	} catch (DaoException $e) {
+    		return false;
+    	}
+    	$objMemcached = new Memcached($this->memcacheClusterFlag);
+    	return $objMemcached->sets(array_combine($cacheKeys, $results), $this->cache_expiration);
+    }
+
+    /**
+     * 创建一条记录
+     * @param array $tableInfo 待插入的数据
+     * @param boolean $isAutoIncrement 操作成功时,如果该值为true,返回最后插入的id;否则返回true
+     * @return boolean | int
+     */
+    private function _create(array $tableInfo, $isAutoIncrement = true, $action = self::PARAM_CREATE_ACTION_INSERT) {
+        if(empty($tableInfo)) return false;
+
+        switch($action) {
+            case self::PARAM_CREATE_ACTION_INSERT :
+            case self::PARAM_CREATE_ACTION_INSERT_IGNORE :
+            case self::PARAM_CREATE_ACTION_REPLACE :
+                break;
+            default:
+                throw new Exception('error insert action');
+        }
+
+        $sql = "{$action} {$this->tableName}
+            SET
+        ";
+        $sqlSets = '';
+        $tableInfo = $this->quote($tableInfo);
+        foreach($tableInfo as $key => $val) {
+            if($sqlSets != '') $sqlSets .= ' ,';
+            $sqlSets .= "
+               `{$key}` = {$val}
+            ";
+        }
+        $sql .= $sqlSets;
+
+        if($this->db->query($sql)) {
+            if($isAutoIncrement) {
+                $id = $this->db->insertId();
+                
+                # 清空这条记录的缓存
+                self::deleteFromCache($id);
+                
+                return $id > 0 ? $id : true;
+            } else {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * 创建一条记录,如果重复,则替换
+     * @param array $tableInfo 待插入的数据
+     * @param boolean $isAutoIncrement 操作成功时,如果该值为true,返回最后插入的id;否则返回true
+     * @return boolean | int
+     */
+    public function replace(array $tableInfo, $isAutoIncrement = true) {
+        return $this->_create($tableInfo, $isAutoIncrement, self::PARAM_CREATE_ACTION_REPLACE);
+    }
+
+    /**
+     * 创建一条记录
+     * @param array $tableInfo 待插入的数据
+     * @param boolean $isAutoIncrement 操作成功时,如果该值为true,返回最后插入的id;否则返回true
+     * @return boolean | int
+     */
+    public function create(array $tableInfo, $isAutoIncrement = true) {
+        return $this->_create($tableInfo, $isAutoIncrement, self::PARAM_CREATE_ACTION_INSERT);
+    }
+
+    /**
+     * 创建一条记录,如果重复,则忽略
+     * @param array $tableInfo 待插入的数据
+     * @param boolean $isAutoIncrement 操作成功时,如果该值为true,返回最后插入的id;否则返回true
+     * @return boolean | int PS:$isAutoIncrement = true时:1、如果插入了,返回自动id值;2、如果已存在,返回true。
+     */
+    public function insertIgnore(array $tableInfo, $isAutoIncrement = true) {
+        return $this->_create($tableInfo, $isAutoIncrement, self::PARAM_CREATE_ACTION_INSERT_IGNORE);
+    }
+
+    /**
+     *
+     * 插入一条记录,如果重复,自动转为更新语句
+     * @param array $tableInfo
+     * @param array $onDuplicate 如果重复时,需要更新的信息。如果不指定,则使用$tableInfo的值,即认为要全部更新
+     * @return int | Boolean
+     * 		   int:只要存在,无论之前记录是否存在,都会返回记录的id;
+     * 		   true:执行成功,但获取记录id时失败;
+     * 		   false:执行失败
+     */
+    public function insertDuplicate(array $tableInfo, array $onDuplicate = array()) {
+    	if (!$tableInfo) {
+    		return false;
+    	}
+    	$tmpArrKeys = array();
+		foreach ($tableInfo as $tmpKey => $tmpV) {
+			$tmpArrKeys[] = "`{$tmpKey}`";
+		}
+		$sql = "INSERT INTO {$this->tableName} (" . join(', ', $tmpArrKeys). ") VALUES ";
+
+        $tmpArrValues = array();
+        $new_tableInfo = $this->quote($tableInfo);
+        foreach ($new_tableInfo as $tmpKey => $tmpV) {
+			$tmpArrValues[] = $tmpV;
+        }
+        $sql .= " ( " . join(', ', $tmpArrValues) . " ) ";
+
+        $sql .= "
+            ON DUPLICATE KEY UPDATE
+        ";
+        $tmpArrDps = array();
+        if (empty($onDuplicate)) {
+        	$onDuplicate = $tableInfo;
+        }
+
+        $new_onDuplicate = $this->quote($onDuplicate);
+		foreach ($new_onDuplicate as $tmpKey => $tmpV) {
+			$tmpArrDps[] = " `{$tmpKey}` = {$tmpV} ";
+		}
+		$sql .= join(', ', $tmpArrDps);
+
+		if (!$this->db->query($sql)) {
+			return false;
+		}
+
+		$id = $this->db->insertId();
+		
+		# 清空这条记录的缓存
+        self::deleteFromCache($id);
+        
+        return $id > 0 ? $id : true;
+    }
+
+    /**
+     * 根据条件更新指定数据
+     * @param array $tableInfo 待更新的数据(与数据库字段对应的数据)
+     * @param array $condition 条件(与数据库字段对应的数据)
+     * @return boolean
+     */
+    public function update(array $tableInfo, array $condition) {
+        if(empty($tableInfo)) return false;
+
+        $sql = "UPDATE {$this->tableName}
+            SET
+        ";
+        $sqlSets = '';
+        foreach($tableInfo as $key => $val) {
+            if($sqlSets != '') $sqlSets .= ' ,';
+            $sqlSets .= "
+               `{$key}` = {$this->quote($val)}
+            ";
+        }
+        $sql .= $sqlSets;
+
+        $where = $this->parseCondition($condition);
+        $sql .= "
+            {$where}
+            ;
+        ";
+
+        $tmpQueryResult = $this->db->query($sql);
+        
+        if ($tmpQueryResult) {
+        	if (isset($condition[$this->primaryKey])) {
+        		# 清空这条记录的缓存,条件里有可能是一批ids
+        		self::deleteFromCache($condition[$this->primaryKey]);
+        	} else {
+        		$this->deleteAllCaches();
+        	}
+        }
+        
+        return $tmpQueryResult;
+    }
+
+    /**
+     * 根据条件删除数据
+     * @param Array|String $condition 条件
+     * @return boolean
+     */
+    public function delete($condition) {
+        $where = $this->parseCondition($condition);
+
+        $sql = "DELETE FROM {$this->tableName}
+            {$where}
+            ;
+        ";
+           
+        $tmpQueryResult = $this->db->query($sql);
+        
+        if ($tmpQueryResult) {
+        	if (isset($condition[$this->primaryKey])) {
+        		# 清空这条记录的缓存,条件里有可能是一批ids
+        		self::deleteFromCache($condition[$this->primaryKey]);
+        	} else {
+        		$this->deleteAllCaches();
+        	}
+        }
+        
+        return $tmpQueryResult;
+    }
+
+    /**
+     * 转义数据
+     * @param mixed $data
+     */
+    public function quote($data) {
+        return SqlHelper::escape($data, true);
+    }
+
+    /**
+     * 判断给定的值是否是有效的自增主键值
+     * @param mixid $pk
+     * @return boolean
+     */
+    static protected function isValidPK($pk) {
+        return SqlHelper::isValidPK($pk);
+    }
+
+    /**
+     * 判断给定数组的某个key的值,是否是有效的自增主键值
+     * @param array $arr
+     * @param mixid $key
+     * @return boolean
+     */
+    static protected function isValidPKWithArray(array $arr, $key) {
+        return SqlHelper::isValidPKWithArray($arr, $key);
+    }
+
+    /**
+     * 获取指定条件的记录数
+     * @return int
+     */
+    public function totals($condtion = null){
+        $result = $this->findBy($condtion, null, null, "count(*)");
+        if (!$result) {
+            return 0;
+        }
+        return (int) array_pop(array_pop($result));
+    }
+}

+ 278 - 0
KIF/Dao/.svn/text-base/DBAgileDev.class.php.svn-base

@@ -0,0 +1,278 @@
+<?php
+namespace KIF\Dao;
+
+use KIF\Verify;
+use KIF\Exception\ParamsException;
+use KIF\Data\ResultWrapper;
+
+/**
+ * 数据库操作的敏捷开发模式抽象类。  敏捷开发(Agile development)
+ * 目的:统一数据库表的设计模式,为 增、删、改 提供便捷
+ * PS: 通过 cas_token 字段,提供冲突检测机制(乐观锁),保证高并发下的数据安全
+ *     这是模仿自 php 的 Memcached 扩展,非常好的功能,故借鉴过来
+ * @author gaoxiaogang@gmail.com
+ *
+ */
+abstract class DBAgileDev extends AbstractDao {
+
+    /**
+     * 除敏捷模式必须要求的字段外,多增加的字段
+     * 该实例变量由继承类填充
+     * @var array
+     */
+    protected $other_field = array();
+
+
+    /**
+     * 数据库里的真实字段
+     * 这个实例变量的值是自动计算出来的
+     * @var array
+     */
+    private $real_field;
+
+    /**
+     * 
+     * Enter description here ...
+     * @param unknown_type $master_flag
+     * @return KIF\Dao\DBAgileDev
+     */
+    public function __construct($master_flag = 'default') {
+    	parent::__construct($master_flag);
+
+    	# 固定字段
+    	$fixed_field = array(
+    	    $this->primaryKey,
+    	    'extend',
+    	    'create_time',
+    	    'update_time',
+    	    'cas_token',// 通过这个字段,提供冲突检测机制(乐观锁),保证高并发下的数据安全
+    	);
+
+    	$this->real_field = array_merge($fixed_field, $this->other_field);
+    }
+
+    /**
+     *
+     * 添加一条记录
+     * @param array $info
+     * @param string $actionType 动态类型,这个参数控制以下行为中的一种: create、replace、insertIgnore
+     *
+     * ## 当$actionType的值为parent::PARAM_CREATE_ACTION_ONDUPLICATE时,该值才有意义。##
+     * !!! 无法通过指定 $onDuplicate 来修改存储在 extend 字段里的值,因为没法把已存在的 extend 的值取出来,做一个merge。 !!!
+     * @param array $onDuplicate 如果重复时,需要更新的信息。如果不指定,则使用$tableInfo的值,即认为要全部更新
+     * ##
+     *
+     * @throws ParamsException actionType 参数非法
+     * @return int | boolean
+     */
+    public function add(array $info, $actionType = parent::PARAM_CREATE_ACTION_INSERT, array $onDuplicate = array()) {
+
+        if (!isset($info['create_time']) || !Verify::unsignedInt($info['create_time'])) {
+            $info['create_time'] = time();
+        }
+
+        if (!isset($info['update_time']) || !Verify::unsignedInt($info['update_time'])) {
+            $info['update_time'] = time();
+        }
+
+        $extend = array();
+        foreach ($info as $tmpK => $tmpV) {
+            if (!in_array($tmpK, $this->real_field)) {
+                $extend[$tmpK]  = $tmpV;
+                unset($info[$tmpK]);
+            }
+        }
+
+        # 从0开始。本来打算从1开始的,但考虑到 继续类可能会绕过 add 方法而实现自身的增加记录方法,将 cas_token默认值设为,有助于简化
+        $info['cas_token'] = 0;
+
+
+        $tableInfo = $info;
+        $tableInfo['extend'] = serialize($extend);
+
+        switch ($actionType) {
+        	case parent::PARAM_CREATE_ACTION_INSERT:
+				$id = $this->create($tableInfo);
+        		break;
+        	case parent::PARAM_CREATE_ACTION_INSERT_IGNORE:
+				$id = $this->insertIgnore($tableInfo);
+        		break;
+        	case parent::PARAM_CREATE_ACTION_REPLACE:
+				$id = $this->replace($tableInfo);
+        		break;
+        	case parent::PARAM_CREATE_ACTION_ONDUPLICATE:
+        		if (empty($onDuplicate)) {
+        			$onDuplicate = $tableInfo;
+        			unset($onDuplicate['create_time']);
+        			unset($onDuplicate['cas_token']);
+        		} else {
+        			# 确保不允许修改 extend 字段里存储的值。因为没法把已存在的 extend 的值取出来,做一个merge。
+			        foreach ($onDuplicate as $tmpK => $tmpV) {
+			            if (!in_array($tmpK, $this->real_field)) {
+			                unset($onDuplicate[$tmpK]);
+			            }
+			        }
+        		}
+
+        		$onDuplicate['update_time'] = $info['update_time'];
+        		$onDuplicate['cas_token'] = SqlHelper::wrapperNoQuote("`cas_token` + 1");
+
+        		$id = $this->insertDuplicate($tableInfo, $onDuplicate);
+        		break;
+        	default:
+        		throw new ParamsException("invalid actionType");
+        }
+
+        return $id;
+    }
+
+    /**
+     * 修改
+     * !!!!!!
+     * !!! $cas_token 提供了一种“检查并修改”机制,非常重要的优势, 在这样一个冲突检测机制(乐观锁)下, 我们才能保证高并发下的数据安全。
+     * !!!!!!
+     * 注:$info 或 $condition 里,一定要有一个指定 key 为 $this->primaryKey 对应的值
+     * @param array $info
+     * @param array $condition 条件
+     * @param int $cas_token 如果$cas_token不为null,会执行一个“检查并修改”的操作。因此,仅当 $cas_token 与数据库里的值一致时,才会修改记录。
+     * @return ResultWrapper 成功:修改并影响到了记录;
+     * 	       失败:数据里含失败描述。
+     * 		   另:cas_token检查不通过时,返回'CAS_TOKEN_NOT_MATCH'。这个返回值并不是严格意义上的正确,当$condition指定了条件,并且未匹配时,
+     * 		   也会返回这个值。
+     */
+    public function modify(array $info, array $condition = null, $cas_token = null) {
+        if (!isset($info[$this->primaryKey])) {
+        	if (!isset($condition[$this->primaryKey])) {
+				return ResultWrapper::fail("请指定要修改的记录");
+        	}
+            $info[$this->primaryKey] = $condition[$this->primaryKey];
+        }
+        if (!Verify::unsignedInt($info[$this->primaryKey])) {
+            return ResultWrapper::fail("请指定有效的记录");
+        }
+        if (!isset($condition)) {
+        	$condition = array();
+        }
+
+        $useMasterFlag = $this->db->beginUseMaster();
+        $oldSpecialSale = parent::get($info[$this->primaryKey]);
+        $this->db->restore($useMasterFlag);
+        if (!$oldSpecialSale) {
+            return ResultWrapper::fail("不存在的记录:{$info[$this->primaryKey]}");;
+        }
+
+        # 找出待更新的扩展字段
+        $extend = unserialize($oldSpecialSale['extend']);
+        foreach ($info as $tmpK => $tmpV) {
+            if (!in_array($tmpK, $this->real_field)) {
+                $extend[$tmpK]  = $tmpV;
+                unset($info[$tmpK]);
+            }
+        }
+
+        $info['cas_token'] = SqlHelper::wrapperNoQuote('`cas_token` + 1');
+
+        $tableInfo = $info;
+        $tableInfo['extend'] = serialize($extend);
+        $tableInfo['update_time'] = time();
+
+        $condition[$this->primaryKey] = $info[$this->primaryKey];
+        if (Verify::naturalNumber($cas_token)) {
+        	$condition['cas_token'] = $cas_token;
+        }
+
+        $result = $this->update($tableInfo, $condition);
+        if (!$result) {
+            return ResultWrapper::fail("数据库update操作失败");
+        }
+
+        $affectedRows = $this->db->affectedRows();
+        if (!Verify::unsignedInt($affectedRows)) {
+        	if (Verify::naturalNumber($cas_token)) {
+        		return ResultWrapper::fail("CAS_TOKEN_NOT_MATCH");
+        	} else {
+				return ResultWrapper::fail("NOT_MATCH");
+        	}
+        }
+
+        return ResultWrapper::success();
+    }
+
+    /**
+     * 获取一条记录
+     * @param int $id 记录id
+     * @param int $cas_token 引用传值,默认为 null
+     * @return false | array
+     */
+    public function get($id, & $cas_token = null) {
+        if (!Verify::unsignedInt($id)) {
+            return false;
+        }
+
+        $result = $this->gets(array($id));
+        if (!$result) {
+            return false;
+        }
+
+        $tmpResult = array_pop($result);
+        $cas_token = $tmpResult['cas_token'];
+        return $tmpResult;
+    }
+
+    /**
+     * 获取一批记录
+     * @param array $ids 记录id集
+     * @param array $cas_tokens 引用传值,默认为 null
+     * @return false | array
+     */
+    public function gets(array $ids, array & $cas_tokens = null) {
+        $result = parent::gets($ids);
+        if (!$result) {
+            return $result;
+        }
+
+        foreach ($result as & $tmpV) {
+            $tmpExtend = array();
+            $tmpExtend = unserialize($tmpV['extend']);
+            unset($tmpV['extend']);
+
+            # 将存在扩展字段里的信息提出来
+            if (is_array($tmpExtend)) foreach ($tmpExtend as $tmpKK => $tmpVV) {
+            	if (!in_array($tmpKK, $this->real_field)) {
+                    $tmpV[$tmpKK] = $tmpVV;
+                }
+            }
+
+            $cas_tokens[$tmpV[$this->primaryKey]] = $tmpV['cas_token'];
+        }
+
+        return $result;
+    }
+
+    /**
+     *
+     * 获取所有id集
+     * @param string $order
+     * @param mixed $limit
+     * @return array
+     */
+    public function getsIdsAll($order = null, $limit = null) {
+		$ids = parent::getsAllIds($order, $limit);
+		return $ids;
+    }
+
+	/**
+     *
+     * 获取指定时间$update_time后的最新id集
+     * @param mixed $limit
+     * @return array
+     */
+    public function getsLatestIdsByUpdateTime($update_time, $limit = null) {
+		$order = 'update_time ASC';
+		$condition = array(
+			'update_time'	=> SqlHelper::addCompareOperator('>=', $update_time),
+		);
+
+		return $this->findIdsBy($condition, $limit, $order);
+    }
+}

+ 157 - 0
KIF/Dao/.svn/text-base/SqlHelper.class.php.svn-base

@@ -0,0 +1,157 @@
+<?php
+namespace KIF\Dao;
+
+use KIF\Exception\ParamsException;
+use KIF\Verify;
+
+/*
+ * Sql助手类
+ * @author gaoxiaogang@yoka.com
+ */
+class SqlHelper {
+    /**
+     * 转义数据
+     * @param mixed $data
+     * @param boolean $isQuote 字符串值是否用引号引起来。默认不括起来。
+     * @return mixed
+     */
+    static public function escape($data, $isQuote = false) {
+        if (is_int($data)) {
+            return strval($data);
+        } elseif (is_float($data)) {
+            return strval($data);
+        } elseif (is_string($data)) {
+        	if ($isQuote) {
+        		# 判断值是否需要 quote 起来
+        		if (self::isWrapperNoQuote($data)) {
+        			return addslashes(self::UnwrapperNoQuote($data));
+        		} else {
+        			return "'" . addslashes($data) . "'";
+        		}
+        	} else {
+        		return addslashes($data);
+        	}
+        } elseif (is_bool($data)) {
+            return strval(intval($data));
+        } elseif (empty($data)) {
+            if ($isQuote) {
+                return "''";
+            } else {
+                return '';
+            }
+        } elseif (is_array($data)) {
+            foreach ($data as &$val) {
+                $val = self::escape($val, $isQuote);
+            }
+            return $data;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * 判断给定的值是否是有效的自增主键值
+     * @param mixid $pk
+     * @return boolean
+     */
+    static public function isValidPK($pk) {
+    	# 对开心用户id的处理有大bug,时间上来不及,先通过所有验证
+    	return true;
+
+    	if (!Verify::unsignedInt($pk)) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * 判断给定数组的某个key的值,是否是有效的自增主键值
+     * @param array $arr
+     * @param mixid $key
+     * @return boolean
+     */
+    static public function isValidPKWithArray(array $arr, $key) {
+        if (empty($arr)) {
+            return false;
+        }
+        if (!array_key_exists($key, $arr)) {
+            return false;
+        }
+        return self::isValidPK($arr[$key]);
+    }
+
+    /**
+     *
+     * 对值 $str 做包装,表示该值不需要 引(quote) 起来
+     * @param string $str
+     * @return string
+     */
+    static public function wrapperNoQuote($str) {
+    	if (empty($str)) {
+    		return $str;
+    	}
+    	return "NOQUOTE{$str}NOQUOTE";
+    }
+
+    /**
+     *
+     * 是否对值 $str 做了不需要 引(quote) 起来的包装
+     * @param string $str
+     * @return Boolean
+     */
+    static public function isWrapperNoQuote($str) {
+		if (strpos($str, 'NOQUOTE') !== 0) {
+			return false;
+		}
+
+		if (strrpos($str, 'NOQUOTE') !== (strlen($str) - strlen('NOQUOTE'))) {
+			return false;
+		}
+
+		return true;
+    }
+
+    /**
+     *
+     * 去除不 引(quote) 起来的包装
+     * @param string $str
+     * @return string
+     */
+    static public function UnwrapperNoQuote($str) {
+    	return str_replace('NOQUOTE', '', $str);
+    }
+
+    /**
+     *
+     * 将sql里的比较操作符添加到 $val里
+     * DBAbstract::parseCondition 方法,会调用 SqlHelper::explodeCompareOperator 方法来拆解值与比较操作符
+     * @param string $operator 比较操作符。如: >= 、 < 、!=
+     * @param scalar $val 标题数据
+     * @throws ParamsException '参数val必须是标量scalar类型'
+     * @return string
+     */
+    static public function addCompareOperator($operator, $val) {
+		if (!is_scalar($val)) {
+			throw new ParamsException("参数val必须是标量scalar类型");
+		}
+    	$val = "COMPARE{$operator}COMPARE    {$val}";
+    	return $val;
+    }
+
+    /**
+     *
+     * 拆解 $val 里的 比较操作符 与 数据。
+     * 如果含比较操作符,则认为比较操作符是 =
+     * @param string $val
+     * @return string
+     */
+    static public function explodeCompareOperator($val) {
+    	$pattern = '#^(\'?)COMPARE(.+?)COMPARE\s{4}#';
+    	if (!preg_match($pattern, $val, $match)) {
+			return "  =  {$val}  ";
+    	}
+    	$compareOperator = $match[2];
+    	$val = preg_replace($pattern, '$1', $val);
+    	return "  {$compareOperator}  {$val}  ";
+    }
+}

+ 829 - 0
KIF/Dao/AbstractDao.class.php

@@ -0,0 +1,829 @@
+<?php
+namespace KIF\Dao;
+
+use KIF\Exception\DaoException;
+
+use KIF\Verify;
+
+use KIF\Exception\ParamsException;
+use Exception;
+use KIF\Db\MySQLi;
+use KIF\Dao\SqlHelper;
+use KIF\Core\Config;
+use KIF\Cache\Memcached;
+use KIF\Math\Math;
+
+/**
+ * 数据库操作的抽象类
+ * @author gaoxiaogang@yoka.com
+ *
+ */
+abstract class AbstractDao {
+    /**
+     * 数据表名
+     * 该抽象类针对表的操作(如:delete、create、findBy)都是基于该属性。由继承的子类填充该值。
+     * 好处:1、如果继承的子类只针对一个表,把tableName设为静态表名即可。
+     * 2、如果继承的子类针对一类表操作(如根据某个唯一标志把相同结构的数据hash到多张表中),则针对不同表操作时,动态设置该值
+     * @var string
+     */
+    protected $tableName;
+
+    /**
+     * 表的主键名。默认为id
+     * @var string
+     */
+    protected $primaryKey = 'id';
+    
+    /**
+     * 
+     * 是否使用缓存
+     * @var boolean
+     */
+    private $useCache = false;
+    
+    /**
+     * 
+     * 缓存失效时间 ,默认1小时
+     * @var int
+     */
+    private $cache_expiration = 3600;
+    
+    /**
+     * 
+     * 存放这个数据表的缓存标志。
+     * 目的:清空这个标志,能清空这张表的所有行记录缓存
+     * @var string
+     */
+    private $tableCacheFlag;
+    
+    /**
+     * 
+     * 用于缓存表记录的memcache缓存集群标志
+     * 优先使用config里的 memcached->dao_cache;如不存在,则使用 memcached->default;如仍不存在,则不启用缓存.
+     * @var string
+     */
+    private $memcacheClusterFlag;
+
+    /**
+     *
+     * 插入行为
+     * @var string
+     */
+    const PARAM_CREATE_ACTION_INSERT = 'INSERT INTO';
+
+    /**
+     *
+     * 插入,但重复时忽略
+     * @var string
+     */
+    const PARAM_CREATE_ACTION_INSERT_IGNORE = 'INSERT IGNORE';
+
+    /**
+     *
+     * 插入,但重复时完全replace
+     * @var string
+     */
+    const PARAM_CREATE_ACTION_REPLACE = 'REPLACE INTO';
+
+    /**
+     *
+     * 插入,但重复时自动转为update
+     * @var string
+     */
+    const PARAM_CREATE_ACTION_ONDUPLICATE = 'ON DUPLICATE KEY UPDATE';
+
+    /**
+     * 数据库实例,可被动态切换到主库或从库
+     *
+     * @var KIF\Db\MySQLi
+     */
+    protected $db;
+    
+    /**
+     * 
+     * mysql集群标志
+     * @var string
+     */
+    private $cluster_flag;
+
+    /**
+     * 
+     * @param string $cluster_flag mysql集群标志
+     * 
+     */
+    public function __construct($cluster_flag = 'default') {
+    	$appConfig = Config::getInstance()->current();
+    	$dbConfig = $appConfig['db'];
+    	
+    	if (!$dbConfig || !isset($dbConfig[$cluster_flag]) || !is_string($dbConfig[$cluster_flag])) {
+    		throw new ParamsException("load config error:{$dbConfig}");
+    	}
+    	$this->cluster_flag = $cluster_flag;
+    	
+    	if (isset($appConfig['memcached']['dao_cache'])) {
+    		$this->memcacheClusterFlag = 'dao_cache';
+    	} elseif (isset($appConfig['memcached']['default'])) {
+    		$this->memcacheClusterFlag = 'default';
+    	} else {# 没有缓存配置,不启用缓存
+    		$this->useCache(false);
+    	}
+
+    	$dsn = $dbConfig[$cluster_flag];
+    	$this->db = new MySQLi($dsn);
+    }
+
+    /**
+     *
+     * 获取设置的主键名
+     * @return string
+     */
+    public function getPrimaryKey() {
+    	return $this->primaryKey;
+    }
+
+    /**
+     * 设置表名
+     * 动态设置表名是为了拆表
+     * @param $tableName
+     */
+    public function setTableName($tableName) {
+        $this->tableName = $tableName;
+    }
+    
+    /**
+     * 
+     * 控制是否使用缓存。使用缓存,会将数据表的行记录缓存起来,极大的提高效率和高可用;同时也会在记录删除、更新时,清空对应的缓存。
+     * @param boolean $useCache
+     */
+    public function useCache($useCache = true) {
+    	$this->useCache = (bool) $useCache;
+    }
+
+    /**
+     * 解析条件
+     * @param array|string $condition
+     * @throws Exception parameter errors
+     * @return string
+     */
+    public function parseCondition($condition = null) {
+        if(empty($condition)) return '';
+        
+        // 如果条件语句是字符串 直接拼接
+        if(is_string($condition)){
+        	return "
+            WHERE ". $condition ." ";
+        }
+        // 如果不是字符串 强制传递数组 如果不是数组抛异常
+        if(!is_array($condition)){
+        	throw new Exception('parameter errors');
+        }
+		$condition = $this->quote($condition);
+        $where = array();
+        foreach($condition as $tmpK => $tmpV) {
+        	# 避免条件字段重复,支持二维数组。比如$conditon = array(
+        	# 	array(
+        	#		'publish_time'	=> SqlHelper::addCompareOperator(">=", $s_ts),
+        	#	),
+        	#	array(
+        	#		'publish_time'	=> SqlHelper::addCompareOperator("<", $e_ts),
+        	#	),
+        	# );
+        	# 以前只能这样变相支持:$condition = array(
+        	# 	'publish_time'	=> SqlHelper::addCompareOperator(">=", $s_ts),
+        	#	'`publish_time`'	=> SqlHelper::addCompareOperator("<", $e_ts),
+        	# );
+        	if (Verify::naturalNumber($tmpK)) {
+        		list($key, $val) = each($tmpV);
+        	} else {
+        		$key = $tmpK;
+        		$val = $tmpV;
+        	}
+        	
+        	if (strpos($key, '`') === false // 没用使用 ` 字符,比如 `status`
+        		&& strpos($key, '(') === false) // 也不含有括号(即不是函数),比如 last_insert_id(status)
+        	{
+        		$key = "`{$key}`";
+        	}
+            if(is_scalar($val)) {
+                $where[] = "{$key} " . SqlHelper::explodeCompareOperator($val);
+            } elseif(is_array($val)) {
+                $where[] = "{$key} IN (".join(',', $val).")";
+            } else {
+                throw new Exception('parameter errors');
+            }
+        }
+        return "
+            WHERE ".join(' && ', $where)."
+        ";
+    }
+
+    /**
+     * 根据条件查找主键id列表集
+     * 做这个方法,是出于架构上的考虑。只取出主键id集,然后通过主键id集才去取数据(内存或数据库)
+     * @param array|String $condition 条件
+     * @param string | int $limit 指定分页
+     * @param string $order 指定排序
+     * @return array 如:array(123, 124)。无值时返回空数组
+     */
+    public function findIdsBy($condition = null, $limit = null, $order = null) {
+        $result = $this->findBy($condition, $this->primaryKey, $limit, $this->primaryKey, $order);
+        if (! $result) {
+            return array();
+        }
+
+        return array_keys($result);
+    }
+
+    /**
+     * 查找的底层方法。对查找只提供有限支持,太复杂的查询请手动sql
+     * final 让该方法禁止被继承
+     * 1、根据条件查找,多条件只支持 与(&&),不支持或之类的
+     * 2、支持limit
+     * 3、group by
+     * @param array|string $condition
+     * @param string $returnAssociateKey 如果指定该指,返回的值不再以012为key,而是以其对应的值为key
+     * @param string | int $limit
+     * @param string $selectCols 要获取的列。语法:id, uid ,默认为 *
+     * @param string $order 指定排序
+     * @return array
+     */
+    final public function findBy($condition = null, $returnAssociateKey = null, $limit = null, $selectCols = '*', $order = null) {
+        $where = $this->parseCondition($condition);
+        if (!isset($limit) || !preg_match('#^(?:\d+\s*,\s*)?\d+$#', $limit)) {
+            $strLimit = ' ';
+        } else {
+            $strLimit = " LIMIT $limit ";
+        }
+
+        $strOrder = '';
+        if(!empty($order)) {
+            $strOrder = " ORDER BY {$order} ";
+        }
+
+        if(!isset($selectCols)) {
+            $selectCols = '*';
+        }
+
+        $sql = "SELECT {$selectCols} FROM {$this->tableName}
+            {$where}
+            {$strOrder}
+            {$strLimit}
+            ;
+        ";
+        return $this->db->fetchAll($sql, $returnAssociateKey);
+    }
+
+    public function fetchOne($condition = null, $selectCols = '*', $order = null) {
+        $result = self::findBy($condition, null, 1, $selectCols, $order);
+        if (!$result) {
+        	return false;
+        }
+        return array_pop($result);
+    }
+
+    /**
+     * 获取所有
+     * @param string $order 排序方式。默认以主键倒序;有效的格式:"create_time desc"
+     * @param string $limit 该参数用于支持分页,默认为不使用分页。格式 "offset, length"
+     * @return array
+     */
+    public function getsAll($order = null, $limit = null) {
+        $ids = self::getsAllIds($order, $limit);
+        return $this->gets($ids);
+    }
+
+    /**
+     * 获取所有id列表集
+     * @param string $order 排序方式。默认以主键倒序;有效的格式:"create_time desc"
+     * @param string $limit 该参数用于支持分页,默认为不使用分页。格式 "offset, length"
+     * @return array
+     */
+    public function getsAllIds($order = null, $limit = null) {
+    	if (is_null($order)) {
+            $order = "{$this->primaryKey} desc";
+        }
+        if (!is_string($order)) {
+            throw new Exception('$order 必须是字符串或null');
+        }
+
+        $condition = null;
+        $ids = self::findIdsBy($condition, $limit, $order);
+        return $ids;
+    }
+
+    /**
+     * 获取指定$id
+     * @param int | string $id
+     * @return false | array
+     */
+    public function get($id) {
+        if (Verify::int($id)) {
+            if ($id < 1) {
+                return false;
+            }
+        } elseif (is_string($id)) {
+            if (strlen($id) == 0) {
+                return false;
+            }
+        } else {
+            return false;
+        }
+
+        $result = self::gets(array($id));// 调用该类自身的gets方法,而不是继承者的gets方法!
+        if (!$result) {
+            return false;
+        }
+        return array_pop($result);
+    }
+
+    /**
+     * 批量获取信息
+     * @param array $ids id组成的数组
+     * @return array 无结果时返回空数组
+     */
+    public function gets(array $ids) {
+    	$return = array();
+        if (empty($ids)) {
+            return $return;
+        }
+        $ids = array_unique($ids);
+        
+    	# 从缓存取 
+        $cache_return = self::getsFromCache($ids);
+        $non_cache_ids = array_diff($ids, array_keys($cache_return));
+        if (!$non_cache_ids) {
+        	return $cache_return;
+        }
+        
+        # 从DB取
+        $db_return = self::getsFromDB($non_cache_ids);
+        if ($db_return) {
+        	self::setToCache($db_return);
+        }
+        foreach ($ids as $id) {
+        	if (isset($cache_return[$id])) {
+        		$return[$id] = $cache_return[$id];
+        		continue;
+        	}
+        	
+        	if (isset($db_return[$id])) {
+        		$return[$id] = $db_return[$id];
+        		continue;
+        	}
+        }
+        
+        return $return;
+    }
+    
+    /**
+     * 
+     * 从后端数据库取结果
+     * @param array $ids
+     * @return array 格式:array(
+     * 		(int) id => (mixed) value,
+     * 		... ,
+     * )
+     * 
+     */
+    private function getsFromDB(array $ids) {
+    	$return = array();
+    	$condition = array(
+            $this->primaryKey => $ids,
+        );
+        $result = $this->findBy($condition, $this->primaryKey);
+        if (!$result) {
+            return $return;
+        }
+
+        foreach ($ids as $id) {
+        	$id = (string) $id;// php的数组下标是int整时,如果超出了操作系统平台的最大有符号正整数后,会取不到值。转成字符串型,解决这个bug
+            if (array_key_exists($id, $result)) {
+                $return[$id] = $result[$id];
+            }
+        }
+        return $return;
+    }
+    
+    /**
+     * 
+     * 获取表缓存的标志。后续这个表里每行的缓存,都会关联上这个表缓存标志。
+     * 所以只要清空这个标志,即可使整个表记录的缓存失效。
+     * @Exception DaoException::CACHE_SET_TABLE_FLAG_ERROR
+     * @Exception DaoException::CACHE_SERVICE_UNAVAILABLE
+     * @return String
+     */
+    public function getTableCacheFlag() {
+    	if ($this->tableCacheFlag) {
+    		return $this->tableCacheFlag;
+    	}
+    	
+    	$dbconfig = Config::getInstance()->get('db');
+    	$cacheKey_elements = array(
+    		serialize($dbconfig[$this->cluster_flag]),
+    		$this->tableName,
+    		'table_cache_flag',
+    	);
+    	$key = Math::md5_16(join($cacheKey_elements, '::'));
+    	$objMemcached = new Memcached($this->memcacheClusterFlag);
+    	$tmpGetResult = $objMemcached->get($key);
+    	if ($tmpGetResult) {
+    		$this->tableCacheFlag = $tmpGetResult;
+    		return $this->tableCacheFlag;
+    	}
+    	
+    	if (is_null($tmpGetResult)) {// 缓存不存在,建立
+    		$tmpVal = microtime(true);
+    		if ($objMemcached->set($key, $tmpVal, 0)) {// 永不过期
+    			$this->tableCacheFlag = $tmpVal;
+    			return $this->tableCacheFlag;
+    		}
+    		
+    		# TODO 设置缓存失败的处理
+    		throw new DaoException(DaoException::CACHE_SET_TABLE_FLAG_ERROR);
+    	}
+    	
+    	if (!$tmpGetResult) {
+    		# TODO memcache服务不可用的处理
+    		throw new DaoException(DaoException::CACHE_SERVICE_UNAVAILABLE);
+    	}
+    }
+    
+    /**
+     * 
+     * 获取指定ids集对应的缓存key集
+     * @param array $ids  array(13343, 9939);
+     * @Exception DaoException::CACHE_SET_TABLE_FLAG_ERROR
+     * @Exception DaoException::CACHE_SERVICE_UNAVAILABLE
+     * @return array 格式:array(
+     *  (int) id => (string) cacheKey,
+     *  ... ,
+     * 
+     * )
+     */
+    public function getsCacheKeys(array $ids) {
+    	$tableCacheFlag = $this->getTableCacheFlag();
+    	$cacheKeys = array();
+    	$dbconfig = Config::getInstance()->get('db');
+    	foreach ($ids as $id) {
+    		$cacheKey_elements = array(
+    			$tableCacheFlag,
+    			serialize($dbconfig[$this->cluster_flag]),
+    			$this->tableName,
+    			$id,
+    			'row_cache',// 这是针对数据库记录行的缓存
+    		);
+    		$cacheKeys[$id] = Math::md5_16(join($cacheKey_elements, '::'));
+    	}
+    	
+    	return $cacheKeys;
+    }
+    
+    /**
+     * 
+     * 从缓存里取数据表对应的行记录
+     * @param array $ids
+     * @return array 只返回缓存里有的值,如果缓存里没有任何值,则返回空数组
+     */
+    private function getsFromCache(array $ids) {
+    	$return = array();
+    	if (!$this->useCache) {
+    		return $return;
+    	}
+    	try {
+    		$cacheKeys = self::getsCacheKeys($ids);
+    	} catch (DaoException $e) {
+    		return $return;
+    	}
+    	
+    	$objMemcached = new Memcached($this->memcacheClusterFlag);
+    	$cacheVals = $objMemcached->gets($cacheKeys);
+    	
+    	$cache_return = array_combine(array_keys($cacheKeys), $cacheVals);
+    	foreach ($cacheVals as $tmpK => $tmpV) {
+    		if (is_null($tmpV)) {
+    			continue;
+    		}
+    		
+    		$return[array_search($tmpK, $cacheKeys)] = $tmpV;
+    	}
+    	
+    	return $return;
+    }
+    
+    /**
+     * 
+     * 从缓存里将$id对应的行记录删除
+     * @param int $id
+     * @throws Exception
+     * @return Boolean
+     */
+    private function deleteFromCache($id) {
+    	if (!$this->useCache) {
+    		return false;
+    	}
+    	$objMemcached = new Memcached($this->memcacheClusterFlag);
+    	
+    	if (Verify::unsignedInt($id)) {
+	    	try {
+	    		$cacheKeys = self::getsCacheKeys(array($id));
+	    	} catch (DaoException $e) {
+	    		return false;
+	    	}
+    		return $objMemcached->delete(array_pop($cacheKeys));
+    	}
+    	
+    	if (is_array($id) && !empty($id)) {
+    		try {
+	    		$cacheKeys = self::getsCacheKeys($id);
+	    	} catch (DaoException $e) {
+	    		return false;
+	    	}
+	    	
+    		if (count($cacheKeys) == 1) {
+    			return $objMemcached->delete(array_pop($cacheKeys));
+    		}
+    		return $objMemcached->deletes($cacheKeys);
+    	}
+    	
+    	return false;
+    }
+    
+    /**
+     * 
+     * 清除缓存的这张表的所有记录
+     * @return Boolean
+     */
+    public function deleteAllCaches() {
+    	if (!$this->useCache) {
+    		return false;
+    	}
+    	
+    	try {
+    		$tableCacheFlag = $this->getTableCacheFlag();
+    	} catch (DaoException $e) {
+    		return false;
+    	}
+    	
+    	$objMemcached = new Memcached($this->memcacheClusterFlag);
+    	return $objMemcached->delete($tableCacheFlag);
+    }
+    
+    /**
+     * 
+     * 将gets出来的结果集,存到缓存
+     * @param array $results
+     * @return boolean
+     */
+    private function setToCache(array $results) {
+    	if (!$this->useCache) {
+    		return false;
+    	}
+    	if (!$results) {
+    		return false;
+    	}
+    	try {
+    		$cacheKeys = self::getsCacheKeys(array_keys($results));
+    	} catch (DaoException $e) {
+    		return false;
+    	}
+    	$objMemcached = new Memcached($this->memcacheClusterFlag);
+    	return $objMemcached->sets(array_combine($cacheKeys, $results), $this->cache_expiration);
+    }
+
+    /**
+     * 创建一条记录
+     * @param array $tableInfo 待插入的数据
+     * @param boolean $isAutoIncrement 操作成功时,如果该值为true,返回最后插入的id;否则返回true
+     * @return boolean | int
+     */
+    private function _create(array $tableInfo, $isAutoIncrement = true, $action = self::PARAM_CREATE_ACTION_INSERT) {
+        if(empty($tableInfo)) return false;
+
+        switch($action) {
+            case self::PARAM_CREATE_ACTION_INSERT :
+            case self::PARAM_CREATE_ACTION_INSERT_IGNORE :
+            case self::PARAM_CREATE_ACTION_REPLACE :
+                break;
+            default:
+                throw new Exception('error insert action');
+        }
+
+        $sql = "{$action} {$this->tableName}
+            SET
+        ";
+        $sqlSets = '';
+        $tableInfo = $this->quote($tableInfo);
+        foreach($tableInfo as $key => $val) {
+            if($sqlSets != '') $sqlSets .= ' ,';
+            $sqlSets .= "
+               `{$key}` = {$val}
+            ";
+        }
+        $sql .= $sqlSets;
+
+        if($this->db->query($sql)) {
+            if($isAutoIncrement) {
+                $id = $this->db->insertId();
+                
+                # 清空这条记录的缓存
+                self::deleteFromCache($id);
+                
+                return $id > 0 ? $id : true;
+            } else {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * 创建一条记录,如果重复,则替换
+     * @param array $tableInfo 待插入的数据
+     * @param boolean $isAutoIncrement 操作成功时,如果该值为true,返回最后插入的id;否则返回true
+     * @return boolean | int
+     */
+    public function replace(array $tableInfo, $isAutoIncrement = true) {
+        return $this->_create($tableInfo, $isAutoIncrement, self::PARAM_CREATE_ACTION_REPLACE);
+    }
+
+    /**
+     * 创建一条记录
+     * @param array $tableInfo 待插入的数据
+     * @param boolean $isAutoIncrement 操作成功时,如果该值为true,返回最后插入的id;否则返回true
+     * @return boolean | int
+     */
+    public function create(array $tableInfo, $isAutoIncrement = true) {
+        return $this->_create($tableInfo, $isAutoIncrement, self::PARAM_CREATE_ACTION_INSERT);
+    }
+
+    /**
+     * 创建一条记录,如果重复,则忽略
+     * @param array $tableInfo 待插入的数据
+     * @param boolean $isAutoIncrement 操作成功时,如果该值为true,返回最后插入的id;否则返回true
+     * @return boolean | int PS:$isAutoIncrement = true时:1、如果插入了,返回自动id值;2、如果已存在,返回true
+     */
+    public function insertIgnore(array $tableInfo, $isAutoIncrement = true) {
+        return $this->_create($tableInfo, $isAutoIncrement, self::PARAM_CREATE_ACTION_INSERT_IGNORE);
+    }
+
+    /**
+     *
+     * 插入一条记录,如果重复,自动转为更新语句
+     * @param array $tableInfo
+     * @param array $onDuplicate 如果重复时,需要更新的信息。如果不指定,则使用$tableInfo的值,即认为要全部更新
+     * @return int | Boolean
+     * 		   int:只要存在,无论之前记录是否存在,都会返回记录的id;
+     * 		   true:执行成功,但获取记录id时失败;
+     * 		   false:执行失败
+     */
+    public function insertDuplicate(array $tableInfo, array $onDuplicate = array()) {
+    	if (!$tableInfo) {
+    		return false;
+    	}
+    	$tmpArrKeys = array();
+		foreach ($tableInfo as $tmpKey => $tmpV) {
+			$tmpArrKeys[] = "`{$tmpKey}`";
+		}
+		$sql = "INSERT INTO {$this->tableName} (" . join(', ', $tmpArrKeys). ") VALUES ";
+
+        $tmpArrValues = array();
+        $new_tableInfo = $this->quote($tableInfo);
+        foreach ($new_tableInfo as $tmpKey => $tmpV) {
+			$tmpArrValues[] = $tmpV;
+        }
+        $sql .= " ( " . join(', ', $tmpArrValues) . " ) ";
+
+        $sql .= "
+            ON DUPLICATE KEY UPDATE
+        ";
+        $tmpArrDps = array();
+        if (empty($onDuplicate)) {
+        	$onDuplicate = $tableInfo;
+        }
+
+        $new_onDuplicate = $this->quote($onDuplicate);
+		foreach ($new_onDuplicate as $tmpKey => $tmpV) {
+			$tmpArrDps[] = " `{$tmpKey}` = {$tmpV} ";
+		}
+		$sql .= join(', ', $tmpArrDps);
+
+		if (!$this->db->query($sql)) {
+			return false;
+		}
+
+		$id = $this->db->insertId();
+		
+		# 清空这条记录的缓存
+        self::deleteFromCache($id);
+        
+        return $id > 0 ? $id : true;
+    }
+
+    /**
+     * 根据条件更新指定数据
+     * @param array $tableInfo 待更新的数据(与数据库字段对应的数据)
+     * @param array $condition 条件(与数据库字段对应的数据)
+     * @return boolean
+     */
+    public function update(array $tableInfo, array $condition) {
+        if(empty($tableInfo)) return false;
+
+        $sql = "UPDATE {$this->tableName}
+            SET
+        ";
+        $sqlSets = '';
+        foreach($tableInfo as $key => $val) {
+            if($sqlSets != '') $sqlSets .= ' ,';
+            $sqlSets .= "
+               `{$key}` = {$this->quote($val)}
+            ";
+        }
+        $sql .= $sqlSets;
+
+        $where = $this->parseCondition($condition);
+        $sql .= "
+            {$where}
+            ;
+        ";
+
+        $tmpQueryResult = $this->db->query($sql);
+        
+        if ($tmpQueryResult) {
+        	if (isset($condition[$this->primaryKey])) {
+        		# 清空这条记录的缓存,条件里有可能是一批ids
+        		self::deleteFromCache($condition[$this->primaryKey]);
+        	} else {
+        		$this->deleteAllCaches();
+        	}
+        }
+        
+        return $tmpQueryResult;
+    }
+
+    /**
+     * 根据条件删除数据
+     * @param Array|String $condition 条件
+     * @return boolean
+     */
+    public function delete($condition) {
+        $where = $this->parseCondition($condition);
+
+        $sql = "DELETE FROM {$this->tableName}
+            {$where}
+            ;
+        ";
+           
+        $tmpQueryResult = $this->db->query($sql);
+        
+        if ($tmpQueryResult) {
+        	if (isset($condition[$this->primaryKey])) {
+        		# 清空这条记录的缓存,条件里有可能是一批ids
+        		self::deleteFromCache($condition[$this->primaryKey]);
+        	} else {
+        		$this->deleteAllCaches();
+        	}
+        }
+        
+        return $tmpQueryResult;
+    }
+
+    /**
+     * 转义数据
+     * @param mixed $data
+     */
+    public function quote($data) {
+        return SqlHelper::escape($data, true);
+    }
+
+    /**
+     * 判断给定的值是否是有效的自增主键值
+     * @param mixid $pk
+     * @return boolean
+     */
+    static protected function isValidPK($pk) {
+        return SqlHelper::isValidPK($pk);
+    }
+
+    /**
+     * 判断给定数组的某个key的值,是否是有效的自增主键值
+     * @param array $arr
+     * @param mixid $key
+     * @return boolean
+     */
+    static protected function isValidPKWithArray(array $arr, $key) {
+        return SqlHelper::isValidPKWithArray($arr, $key);
+    }
+
+    /**
+     * 获取指定条件的记录数
+     * @return int
+     */
+    public function totals($condtion = null){
+        $result = $this->findBy($condtion, null, null, "count(*)");
+        if (!$result) {
+            return 0;
+        }
+        return (int) array_pop(array_pop($result));
+    }
+}

+ 278 - 0
KIF/Dao/DBAgileDev.class.php

@@ -0,0 +1,278 @@
+<?php
+namespace KIF\Dao;
+
+use KIF\Verify;
+use KIF\Exception\ParamsException;
+use KIF\Data\ResultWrapper;
+
+/**
+ * 数据库操作的敏捷开发模式抽象类。  敏捷开发(Agile development)
+ * 目的:统一数据库表的设计模式,为 增、删、改 提供便捷
+ * PS: 通过 cas_token 字段,提供冲突检测机制(乐观锁),保证高并发下的数据安全
+ *     这是模仿自 php 的 Memcached 扩展,非常好的功能,故借鉴过来
+ * @author gaoxiaogang@gmail.com
+ *
+ */
+abstract class DBAgileDev extends AbstractDao {
+
+    /**
+     * 除敏捷模式必须要求的字段外,多增加的字段
+     * 该实例变量由继承类填充
+     * @var array
+     */
+    protected $other_field = array();
+
+
+    /**
+     * 数据库里的真实字段
+     * 这个实例变量的值是自动计算出来的
+     * @var array
+     */
+    private $real_field;
+
+    /**
+     * 
+     * Enter description here ...
+     * @param unknown_type $master_flag
+     * @return KIF\Dao\DBAgileDev
+     */
+    public function __construct($master_flag = 'default') {
+    	parent::__construct($master_flag);
+
+    	# 固定字段
+    	$fixed_field = array(
+    	    $this->primaryKey,
+    	    'extend',
+    	    'create_time',
+    	    'update_time',
+    	    'cas_token',// 通过这个字段,提供冲突检测机制(乐观锁),保证高并发下的数据安全
+    	);
+
+    	$this->real_field = array_merge($fixed_field, $this->other_field);
+    }
+
+    /**
+     *
+     * 添加一条记录
+     * @param array $info
+     * @param string $actionType 动态类型,这个参数控制以下行为中的一种: create、replace、insertIgnore
+     *
+     * ## 当$actionType的值为parent::PARAM_CREATE_ACTION_ONDUPLICATE时,该值才有意义。##
+     * !!! 无法通过指定 $onDuplicate 来修改存储在 extend 字段里的值,因为没法把已存在的 extend 的值取出来,做一个merge。 !!!
+     * @param array $onDuplicate 如果重复时,需要更新的信息。如果不指定,则使用$tableInfo的值,即认为要全部更新
+     * ##
+     *
+     * @throws ParamsException actionType 参数非法
+     * @return int | boolean
+     */
+    public function add(array $info, $actionType = parent::PARAM_CREATE_ACTION_INSERT, array $onDuplicate = array()) {
+
+        if (!isset($info['create_time']) || !Verify::unsignedInt($info['create_time'])) {
+            $info['create_time'] = time();
+        }
+
+        if (!isset($info['update_time']) || !Verify::unsignedInt($info['update_time'])) {
+            $info['update_time'] = time();
+        }
+
+        $extend = array();
+        foreach ($info as $tmpK => $tmpV) {
+            if (!in_array($tmpK, $this->real_field)) {
+                $extend[$tmpK]  = $tmpV;
+                unset($info[$tmpK]);
+            }
+        }
+
+        # 从0开始。本来打算从1开始的,但考虑到 继续类可能会绕过 add 方法而实现自身的增加记录方法,将 cas_token默认值设为,有助于简化
+        $info['cas_token'] = 0;
+
+
+        $tableInfo = $info;
+        $tableInfo['extend'] = serialize($extend);
+
+        switch ($actionType) {
+        	case parent::PARAM_CREATE_ACTION_INSERT:
+				$id = $this->create($tableInfo);
+        		break;
+        	case parent::PARAM_CREATE_ACTION_INSERT_IGNORE:
+				$id = $this->insertIgnore($tableInfo);
+        		break;
+        	case parent::PARAM_CREATE_ACTION_REPLACE:
+				$id = $this->replace($tableInfo);
+        		break;
+        	case parent::PARAM_CREATE_ACTION_ONDUPLICATE:
+        		if (empty($onDuplicate)) {
+        			$onDuplicate = $tableInfo;
+        			unset($onDuplicate['create_time']);
+        			unset($onDuplicate['cas_token']);
+        		} else {
+        			# 确保不允许修改 extend 字段里存储的值。因为没法把已存在的 extend 的值取出来,做一个merge。
+			        foreach ($onDuplicate as $tmpK => $tmpV) {
+			            if (!in_array($tmpK, $this->real_field)) {
+			                unset($onDuplicate[$tmpK]);
+			            }
+			        }
+        		}
+
+        		$onDuplicate['update_time'] = $info['update_time'];
+        		$onDuplicate['cas_token'] = SqlHelper::wrapperNoQuote("`cas_token` + 1");
+
+        		$id = $this->insertDuplicate($tableInfo, $onDuplicate);
+        		break;
+        	default:
+        		throw new ParamsException("invalid actionType");
+        }
+
+        return $id;
+    }
+
+    /**
+     * 修改
+     * !!!!!!
+     * !!! $cas_token 提供了一种“检查并修改”机制,非常重要的优势, 在这样一个冲突检测机制(乐观锁)下, 我们才能保证高并发下的数据安全。
+     * !!!!!!
+     * 注:$info 或 $condition 里,一定要有一个指定 key 为 $this->primaryKey 对应的值
+     * @param array $info
+     * @param array $condition 条件
+     * @param int $cas_token 如果$cas_token不为null,会执行一个“检查并修改”的操作。因此,仅当 $cas_token 与数据库里的值一致时,才会修改记录。
+     * @return ResultWrapper 成功:修改并影响到了记录;
+     * 	       失败:数据里含失败描述。
+     * 		   另:cas_token检查不通过时,返回'CAS_TOKEN_NOT_MATCH'。这个返回值并不是严格意义上的正确,当$condition指定了条件,并且未匹配时,
+     * 		   也会返回这个值。
+     */
+    public function modify(array $info, array $condition = null, $cas_token = null) {
+        if (!isset($info[$this->primaryKey])) {
+        	if (!isset($condition[$this->primaryKey])) {
+				return ResultWrapper::fail("请指定要修改的记录");
+        	}
+            $info[$this->primaryKey] = $condition[$this->primaryKey];
+        }
+        if (!Verify::unsignedInt($info[$this->primaryKey])) {
+            return ResultWrapper::fail("请指定有效的记录");
+        }
+        if (!isset($condition)) {
+        	$condition = array();
+        }
+
+        $useMasterFlag = $this->db->beginUseMaster();
+        $oldSpecialSale = parent::get($info[$this->primaryKey]);
+        $this->db->restore($useMasterFlag);
+        if (!$oldSpecialSale) {
+            return ResultWrapper::fail("不存在的记录:{$info[$this->primaryKey]}");;
+        }
+
+        # 找出待更新的扩展字段
+        $extend = unserialize($oldSpecialSale['extend']);
+        foreach ($info as $tmpK => $tmpV) {
+            if (!in_array($tmpK, $this->real_field)) {
+                $extend[$tmpK]  = $tmpV;
+                unset($info[$tmpK]);
+            }
+        }
+
+        $info['cas_token'] = SqlHelper::wrapperNoQuote('`cas_token` + 1');
+
+        $tableInfo = $info;
+        $tableInfo['extend'] = serialize($extend);
+        $tableInfo['update_time'] = time();
+
+        $condition[$this->primaryKey] = $info[$this->primaryKey];
+        if (Verify::naturalNumber($cas_token)) {
+        	$condition['cas_token'] = $cas_token;
+        }
+
+        $result = $this->update($tableInfo, $condition);
+        if (!$result) {
+            return ResultWrapper::fail("数据库update操作失败");
+        }
+
+        $affectedRows = $this->db->affectedRows();
+        if (!Verify::unsignedInt($affectedRows)) {
+        	if (Verify::naturalNumber($cas_token)) {
+        		return ResultWrapper::fail("CAS_TOKEN_NOT_MATCH");
+        	} else {
+				return ResultWrapper::fail("NOT_MATCH");
+        	}
+        }
+
+        return ResultWrapper::success();
+    }
+
+    /**
+     * 获取一条记录
+     * @param int $id 记录id
+     * @param int $cas_token 引用传值,默认为 null
+     * @return false | array
+     */
+    public function get($id, & $cas_token = null) {
+        if (!Verify::unsignedInt($id)) {
+            return false;
+        }
+
+        $result = $this->gets(array($id));
+        if (!$result) {
+            return false;
+        }
+
+        $tmpResult = array_pop($result);
+        $cas_token = $tmpResult['cas_token'];
+        return $tmpResult;
+    }
+
+    /**
+     * 获取一批记录
+     * @param array $ids 记录id集
+     * @param array $cas_tokens 引用传值,默认为 null
+     * @return false | array
+     */
+    public function gets(array $ids, array & $cas_tokens = null) {
+        $result = parent::gets($ids);
+        if (!$result) {
+            return $result;
+        }
+
+        foreach ($result as & $tmpV) {
+            $tmpExtend = array();
+            $tmpExtend = unserialize($tmpV['extend']);
+            unset($tmpV['extend']);
+
+            # 将存在扩展字段里的信息提出来
+            if (is_array($tmpExtend)) foreach ($tmpExtend as $tmpKK => $tmpVV) {
+            	if (!in_array($tmpKK, $this->real_field)) {
+                    $tmpV[$tmpKK] = $tmpVV;
+                }
+            }
+
+            $cas_tokens[$tmpV[$this->primaryKey]] = $tmpV['cas_token'];
+        }
+
+        return $result;
+    }
+
+    /**
+     *
+     * 获取所有id集
+     * @param string $order
+     * @param mixed $limit
+     * @return array
+     */
+    public function getsIdsAll($order = null, $limit = null) {
+		$ids = parent::getsAllIds($order, $limit);
+		return $ids;
+    }
+
+	/**
+     *
+     * 获取指定时间$update_time后的最新id集
+     * @param mixed $limit
+     * @return array
+     */
+    public function getsLatestIdsByUpdateTime($update_time, $limit = null) {
+		$order = 'update_time ASC';
+		$condition = array(
+			'update_time'	=> SqlHelper::addCompareOperator('>=', $update_time),
+		);
+
+		return $this->findIdsBy($condition, $limit, $order);
+    }
+}

+ 157 - 0
KIF/Dao/SqlHelper.class.php

@@ -0,0 +1,157 @@
+<?php
+namespace KIF\Dao;
+
+use KIF\Exception\ParamsException;
+use KIF\Verify;
+
+/*
+ * Sql助手类
+ * @author gaoxiaogang@yoka.com
+ */
+class SqlHelper {
+    /**
+     * 转义数据
+     * @param mixed $data
+     * @param boolean $isQuote 字符串值是否用引号引起来。默认不括起来。
+     * @return mixed
+     */
+    static public function escape($data, $isQuote = false) {
+        if (is_int($data)) {
+            return strval($data);
+        } elseif (is_float($data)) {
+            return strval($data);
+        } elseif (is_string($data)) {
+        	if ($isQuote) {
+        		# 判断值是否需要 quote 起来
+        		if (self::isWrapperNoQuote($data)) {
+        			return addslashes(self::UnwrapperNoQuote($data));
+        		} else {
+        			return "'" . addslashes($data) . "'";
+        		}
+        	} else {
+        		return addslashes($data);
+        	}
+        } elseif (is_bool($data)) {
+            return strval(intval($data));
+        } elseif (empty($data)) {
+            if ($isQuote) {
+                return "''";
+            } else {
+                return '';
+            }
+        } elseif (is_array($data)) {
+            foreach ($data as &$val) {
+                $val = self::escape($val, $isQuote);
+            }
+            return $data;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * 判断给定的值是否是有效的自增主键值
+     * @param mixid $pk
+     * @return boolean
+     */
+    static public function isValidPK($pk) {
+    	# 对开心用户id的处理有大bug,时间上来不及,先通过所有验证
+    	return true;
+
+    	if (!Verify::unsignedInt($pk)) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * 判断给定数组的某个key的值,是否是有效的自增主键值
+     * @param array $arr
+     * @param mixid $key
+     * @return boolean
+     */
+    static public function isValidPKWithArray(array $arr, $key) {
+        if (empty($arr)) {
+            return false;
+        }
+        if (!array_key_exists($key, $arr)) {
+            return false;
+        }
+        return self::isValidPK($arr[$key]);
+    }
+
+    /**
+     *
+     * 对值 $str 做包装,表示该值不需要 引(quote) 起来
+     * @param string $str
+     * @return string
+     */
+    static public function wrapperNoQuote($str) {
+    	if (empty($str)) {
+    		return $str;
+    	}
+    	return "NOQUOTE{$str}NOQUOTE";
+    }
+
+    /**
+     *
+     * 是否对值 $str 做了不需要 引(quote) 起来的包装
+     * @param string $str
+     * @return Boolean
+     */
+    static public function isWrapperNoQuote($str) {
+		if (strpos($str, 'NOQUOTE') !== 0) {
+			return false;
+		}
+
+		if (strrpos($str, 'NOQUOTE') !== (strlen($str) - strlen('NOQUOTE'))) {
+			return false;
+		}
+
+		return true;
+    }
+
+    /**
+     *
+     * 去除不 引(quote) 起来的包装
+     * @param string $str
+     * @return string
+     */
+    static public function UnwrapperNoQuote($str) {
+    	return str_replace('NOQUOTE', '', $str);
+    }
+
+    /**
+     *
+     * 将sql里的比较操作符添加到 $val里
+     * DBAbstract::parseCondition 方法,会调用 SqlHelper::explodeCompareOperator 方法来拆解值与比较操作符
+     * @param string $operator 比较操作符。如: >= 、 < 、!=
+     * @param scalar $val 标题数据
+     * @throws ParamsException '参数val必须是标量scalar类型'
+     * @return string
+     */
+    static public function addCompareOperator($operator, $val) {
+		if (!is_scalar($val)) {
+			throw new ParamsException("参数val必须是标量scalar类型");
+		}
+    	$val = "COMPARE{$operator}COMPARE    {$val}";
+    	return $val;
+    }
+
+    /**
+     *
+     * 拆解 $val 里的 比较操作符 与 数据。
+     * 如果含比较操作符,则认为比较操作符是 =
+     * @param string $val
+     * @return string
+     */
+    static public function explodeCompareOperator($val) {
+    	$pattern = '#^(\'?)COMPARE(.+?)COMPARE\s{4}#';
+    	if (!preg_match($pattern, $val, $match)) {
+			return "  =  {$val}  ";
+    	}
+    	$compareOperator = $match[2];
+    	$val = preg_replace($pattern, '$1', $val);
+    	return "  {$compareOperator}  {$val}  ";
+    }
+}

+ 96 - 0
KIF/Data/.svn/entries

@@ -0,0 +1,96 @@
+10
+
+dir
+924
+svn://182.92.3.30/project/onepage/KIF/Data
+svn://182.92.3.30/project
+
+
+
+2016-01-21T06:27:41.361155Z
+137
+yubin
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+140960d9-11d8-4647-83e1-275f326d1242
+
+ResultWrapper.class.php
+file
+
+
+
+
+2016-02-15T00:10:47.000000Z
+8539fdeaa03304005293c5acd78cbe0c
+2016-01-21T06:27:41.361155Z
+137
+yubin
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+1193
+
+Convert.class.php
+file
+
+
+
+
+2016-02-15T00:10:47.000000Z
+5655d7e04ba728b548ce46a1d96a10c3
+2016-01-21T06:27:41.361155Z
+137
+yubin
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+4978
+

+ 5 - 0
KIF/Data/.svn/prop-base/Convert.class.php.svn-base

@@ -0,0 +1,5 @@
+K 14
+svn:executable
+V 1
+*
+END

+ 5 - 0
KIF/Data/.svn/prop-base/ResultWrapper.class.php.svn-base

@@ -0,0 +1,5 @@
+K 14
+svn:executable
+V 1
+*
+END

+ 219 - 0
KIF/Data/.svn/text-base/Convert.class.php.svn-base

@@ -0,0 +1,219 @@
+<?php
+namespace KIF\Data;
+
+use KIF\Verify;
+use Exception;
+
+/**
+ * 转换数据类,如将 字符表示的ip转换为整数表示的
+ * @author gaoxiaogang@gmail.com
+ *
+ */
+class Convert {
+
+	/**
+	 * 字型型ip转换为长整型
+	 * example:Convert::ip2long("59.151.9.90");
+	 * @param string $ip
+	 * @return int 32位无符号整型
+	 */
+	static public function ip2long($ip) {
+		$array = @explode('.', $ip);
+	    if (count($array) != 4) {
+	        return false;
+	    }
+	    $long = 0;
+	    $strBin = '';
+	    foreach ($array as $k => $v) {
+	        if ($v > 255) {
+	            return false;
+	        }
+	        $long += $v * pow(2, 8 * (3 - $k));
+	    }
+	    return $long;
+	}
+
+	/**
+	 * 将整数值转换成ip
+	 * example:Convert::long2ip(999754074)
+	 * @param int $long
+	 * @return string
+	 */
+	static public function long2ip($long) {
+		if (!Verify::int($long) || $long < 0 || $long >= pow(2, 32)) {
+	        return false;
+	    }
+	    $strBin = base_convert($long, 10, 2);
+	    $strBin = str_repeat('0', 32-strlen($strBin)) . $strBin;
+	    $arrTmpV = str_split($strBin, 8);
+	    foreach ($arrTmpV as & $tmpV) {
+	        $tmpV = base_convert($tmpV, 2, 10);
+	    }
+	    $ip = join('.', $arrTmpV);
+	    return $ip;
+	}
+
+	/**
+	 * utf8编码转gbk
+	 * @param string $u8str
+	 * @throws Exception 'SystemError:convert encoding fail!'
+	 * @return string
+	 */
+	static public function u82gb($u8str) {
+		if (function_exists('mb_convert_encoding')) {
+	        return mb_convert_encoding($u8str, 'gbk', 'utf-8');
+	    } elseif (function_exists('iconv')) {
+	        return iconv('utf-8', 'gbk', $u8str);
+	    } else {
+	    	throw new Exception('SystemError:convert encoding fail!');
+	    }
+	}
+
+	/**
+	 * gbk编码转utf8
+	 * @param string $gbstr
+	 * @throws Exception 'SystemError:convert encoding fail!'
+	 * @return string
+	 */
+	static public function gb2u8($gbstr) {
+		if (function_exists('mb_convert_encoding')) {
+	        return mb_convert_encoding($gbstr, 'utf-8', 'gbk');
+	    } elseif (function_exists('iconv')) {
+	        return iconv('gbk', 'utf-8', $gbstr);
+	    } else {
+	    	throw new Exception('SystemError:convert encoding fail!');
+	    }
+	}
+	
+	/**
+	 * gbk编码数组转utf8
+	 * @param array $gbarr
+	 * @return array
+	 */
+	static public function gb2u8ofarr(array $gbarr) {
+		if (empty($gbarr)) {
+			return $gbarr;
+		}
+		
+		if (!is_array($gbarr)) {
+			$result = self::gb2u8($gbarr);
+		} else {
+			foreach ($gbarr as $tmpkey => $tmpval) {
+				if (is_array($tmpval)) {
+					$tmp_arr = self::gb2u8ofarr($tmpval);
+					$result[$tmpkey] = $tmp_arr;
+				} else {
+					$result[$tmpkey] = self::gb2u8($tmpval);
+				}
+			}
+		}
+		
+		return $result;
+	}
+	
+	/**
+	 * utf8编码数组转gbk
+	 * @param array $u8arr
+	 * @return array
+	 */
+	static public function u82gbofarr(array $u8arr) {
+		if (empty($u8arr)) {
+			return $u8arr;
+		}
+		
+		if (!is_array($u8arr)) {
+			$result = self::u82gb($u8arr);
+		} else {
+			foreach ($u8arr as $tmpkey => $tmpval) {
+				if (is_array($tmpval)) {
+					$tmp_arr = self::u82gbofarr($tmpval);
+					$result[$tmpkey] = $tmp_arr;
+				} else {
+					$result[$tmpkey] = self::u82gb($tmpval);
+				}
+			}
+		}
+		
+		return $result;
+	}
+
+	/**
+	 * 转换为以元为单位的金额表示。即2位小数的浮点数
+	 * example:Convert::toMoney("19.221");
+	 * @param mixed $str
+	 * @param Boolean $isStripSuffixZero 是否去掉后缀的0
+	 * @return float 2位小数的浮点数
+	 */
+	static public function toMoney($str, $isStripSuffixZero = true) {
+		$money = sprintf('%.2f', $str);
+		if ($isStripSuffixZero) {
+			$money = str_replace('.00', '', $money);
+		}
+		return $money;
+	}
+
+	/**
+	 *
+	 * 解密字符串成id
+	 * @param string $str
+	 * @return false | int
+	 */
+	static public function decryptStr2Id($str) {
+		if (!preg_match('#^([a-j]+)[\x6b-\x7a]([a-j]+)$#', $str, $match)) {
+			return false;
+		}
+
+		$id_1 = self::char2num($match[1]);
+		$id_2 = self::char2num($match[2]);
+		$id = $id_2 - $id_1;
+		if (!Verify::unsignedInt($id)) {
+			return false;
+		}
+		return $id;
+	}
+
+	/**
+	 *
+	 * 加密id成字符串,用于保护id不被人发现规律
+	 * @param int $id
+	 * @return false | string
+	 */
+	static public function encryptId2Str($id) {
+		if (!Verify::unsignedInt($id)) {
+			return false;
+		}
+		$id_1 = rand(pow(10, strlen($id)-1), pow(10, strlen($id)));
+        $id_2 = $id_1 + $id;
+        $dash = chr(rand(0x6b, 0x7a));
+		return self::num2char($id_1) . $dash . self::num2char($id_2);
+	}
+
+	/**
+	 *
+	 * 数字转换成对应的字符
+	 * @param int $num
+	 * @return string
+	 */
+	static private function num2char($num) {
+		$str = '';
+        $num = (string) $num;
+        for ($i = 0; $i < strlen($num); $i++) {
+            $str .= chr($num[$i] + 97);
+        }
+        return $str;
+	}
+
+	/**
+	 *
+	 * 将字符转换成对应的数字
+	 * @param string $str
+	 * @return int
+	 */
+	static private function char2num($str) {
+		$num = '';
+        for ($i = 0; $i < strlen($str); $i++) {
+            $num .= (ord($str[$i]) - 97);
+        }
+        return $num;
+	}
+}

+ 70 - 0
KIF/Data/.svn/text-base/ResultWrapper.class.php.svn-base

@@ -0,0 +1,70 @@
+<?php
+namespace KIF\Data;
+
+/**
+ * 内部结果传递类
+ * 目的:标准化方法执行后返回值的表述。
+ * @author gaoxiaogang@gmail.com
+ *
+ */
+class ResultWrapper {
+	/**
+	 * 存放处理状态
+	 * @var Boolean
+	 */
+	private $status;
+
+	/**
+	 * 存放数据
+	 * @var mixed
+	 */
+	private $data;
+
+	/**
+	 * 构造函数私有,这个类不允许从外部实例化
+	 */
+	private function __construct() {
+
+	}
+
+
+	/**
+	 * 表示处理成功
+	 * @param mixed $data
+	 * @return ResultWrapper
+	 */
+	static public function success($data = null) {
+		$objResultWrapper = new self();
+		$objResultWrapper->status = true;
+		$objResultWrapper->data = $data;
+		return $objResultWrapper;
+	}
+
+	/**
+	 * 表示处理失败
+	 * @param mixed $data
+	 * @return ResultWrapper
+	 */
+	static public function fail($data = null) {
+		$objResultWrapper = new self();
+        $objResultWrapper->status = false;
+        $objResultWrapper->data = $data;
+        return $objResultWrapper;
+	}
+
+	/**
+	 * 判断处理是否成功
+	 * @return Boolean
+	 */
+	public function isSuccess() {
+		return $this->status === true;
+	}
+
+	/**
+	 * 获取数据
+	 * @return mixed
+	 */
+	public function getData() {
+		return $this->data;
+	}
+}

+ 219 - 0
KIF/Data/Convert.class.php

@@ -0,0 +1,219 @@
+<?php
+namespace KIF\Data;
+
+use KIF\Verify;
+use Exception;
+
+/**
+ * 转换数据类,如将 字符表示的ip转换为整数表示的
+ * @author gaoxiaogang@gmail.com
+ *
+ */
+class Convert {
+
+	/**
+	 * 字型型ip转换为长整型
+	 * example:Convert::ip2long("59.151.9.90");
+	 * @param string $ip
+	 * @return int 32位无符号整型
+	 */
+	static public function ip2long($ip) {
+		$array = @explode('.', $ip);
+	    if (count($array) != 4) {
+	        return false;
+	    }
+	    $long = 0;
+	    $strBin = '';
+	    foreach ($array as $k => $v) {
+	        if ($v > 255) {
+	            return false;
+	        }
+	        $long += $v * pow(2, 8 * (3 - $k));
+	    }
+	    return $long;
+	}
+
+	/**
+	 * 将整数值转换成ip
+	 * example:Convert::long2ip(999754074)
+	 * @param int $long
+	 * @return string
+	 */
+	static public function long2ip($long) {
+		if (!Verify::int($long) || $long < 0 || $long >= pow(2, 32)) {
+	        return false;
+	    }
+	    $strBin = base_convert($long, 10, 2);
+	    $strBin = str_repeat('0', 32-strlen($strBin)) . $strBin;
+	    $arrTmpV = str_split($strBin, 8);
+	    foreach ($arrTmpV as & $tmpV) {
+	        $tmpV = base_convert($tmpV, 2, 10);
+	    }
+	    $ip = join('.', $arrTmpV);
+	    return $ip;
+	}
+
+	/**
+	 * utf8编码转gbk
+	 * @param string $u8str
+	 * @throws Exception 'SystemError:convert encoding fail!'
+	 * @return string
+	 */
+	static public function u82gb($u8str) {
+		if (function_exists('mb_convert_encoding')) {
+	        return mb_convert_encoding($u8str, 'gbk', 'utf-8');
+	    } elseif (function_exists('iconv')) {
+	        return iconv('utf-8', 'gbk', $u8str);
+	    } else {
+	    	throw new Exception('SystemError:convert encoding fail!');
+	    }
+	}
+
+	/**
+	 * gbk编码转utf8
+	 * @param string $gbstr
+	 * @throws Exception 'SystemError:convert encoding fail!'
+	 * @return string
+	 */
+	static public function gb2u8($gbstr) {
+		if (function_exists('mb_convert_encoding')) {
+	        return mb_convert_encoding($gbstr, 'utf-8', 'gbk');
+	    } elseif (function_exists('iconv')) {
+	        return iconv('gbk', 'utf-8', $gbstr);
+	    } else {
+	    	throw new Exception('SystemError:convert encoding fail!');
+	    }
+	}
+	
+	/**
+	 * gbk编码数组转utf8
+	 * @param array $gbarr
+	 * @return array
+	 */
+	static public function gb2u8ofarr(array $gbarr) {
+		if (empty($gbarr)) {
+			return $gbarr;
+		}
+		
+		if (!is_array($gbarr)) {
+			$result = self::gb2u8($gbarr);
+		} else {
+			foreach ($gbarr as $tmpkey => $tmpval) {
+				if (is_array($tmpval)) {
+					$tmp_arr = self::gb2u8ofarr($tmpval);
+					$result[$tmpkey] = $tmp_arr;
+				} else {
+					$result[$tmpkey] = self::gb2u8($tmpval);
+				}
+			}
+		}
+		
+		return $result;
+	}
+	
+	/**
+	 * utf8编码数组转gbk
+	 * @param array $u8arr
+	 * @return array
+	 */
+	static public function u82gbofarr(array $u8arr) {
+		if (empty($u8arr)) {
+			return $u8arr;
+		}
+		
+		if (!is_array($u8arr)) {
+			$result = self::u82gb($u8arr);
+		} else {
+			foreach ($u8arr as $tmpkey => $tmpval) {
+				if (is_array($tmpval)) {
+					$tmp_arr = self::u82gbofarr($tmpval);
+					$result[$tmpkey] = $tmp_arr;
+				} else {
+					$result[$tmpkey] = self::u82gb($tmpval);
+				}
+			}
+		}
+		
+		return $result;
+	}
+
+	/**
+	 * 转换为以元为单位的金额表示。即2位小数的浮点数
+	 * example:Convert::toMoney("19.221");
+	 * @param mixed $str
+	 * @param Boolean $isStripSuffixZero 是否去掉后缀的0
+	 * @return float 2位小数的浮点数
+	 */
+	static public function toMoney($str, $isStripSuffixZero = true) {
+		$money = sprintf('%.2f', $str);
+		if ($isStripSuffixZero) {
+			$money = str_replace('.00', '', $money);
+		}
+		return $money;
+	}
+
+	/**
+	 *
+	 * 解密字符串成id
+	 * @param string $str
+	 * @return false | int
+	 */
+	static public function decryptStr2Id($str) {
+		if (!preg_match('#^([a-j]+)[\x6b-\x7a]([a-j]+)$#', $str, $match)) {
+			return false;
+		}
+
+		$id_1 = self::char2num($match[1]);
+		$id_2 = self::char2num($match[2]);
+		$id = $id_2 - $id_1;
+		if (!Verify::unsignedInt($id)) {
+			return false;
+		}
+		return $id;
+	}
+
+	/**
+	 *
+	 * 加密id成字符串,用于保护id不被人发现规律
+	 * @param int $id
+	 * @return false | string
+	 */
+	static public function encryptId2Str($id) {
+		if (!Verify::unsignedInt($id)) {
+			return false;
+		}
+		$id_1 = rand(pow(10, strlen($id)-1), pow(10, strlen($id)));
+        $id_2 = $id_1 + $id;
+        $dash = chr(rand(0x6b, 0x7a));
+		return self::num2char($id_1) . $dash . self::num2char($id_2);
+	}
+
+	/**
+	 *
+	 * 数字转换成对应的字符
+	 * @param int $num
+	 * @return string
+	 */
+	static private function num2char($num) {
+		$str = '';
+        $num = (string) $num;
+        for ($i = 0; $i < strlen($num); $i++) {
+            $str .= chr($num[$i] + 97);
+        }
+        return $str;
+	}
+
+	/**
+	 *
+	 * 将字符转换成对应的数字
+	 * @param string $str
+	 * @return int
+	 */
+	static private function char2num($str) {
+		$num = '';
+        for ($i = 0; $i < strlen($str); $i++) {
+            $num .= (ord($str[$i]) - 97);
+        }
+        return $num;
+	}
+}

+ 70 - 0
KIF/Data/ResultWrapper.class.php

@@ -0,0 +1,70 @@
+<?php
+namespace KIF\Data;
+
+/**
+ * 内部结果传递类
+ * 目的:标准化方法执行后返回值的表述。
+ * @author gaoxiaogang@gmail.com
+ *
+ */
+class ResultWrapper {
+	/**
+	 * 存放处理状态
+	 * @var Boolean
+	 */
+	private $status;
+
+	/**
+	 * 存放数据
+	 * @var mixed
+	 */
+	private $data;
+
+	/**
+	 * 构造函数私有,这个类不允许从外部实例化
+	 */
+	private function __construct() {
+
+	}
+
+
+	/**
+	 * 表示处理成功
+	 * @param mixed $data
+	 * @return ResultWrapper
+	 */
+	static public function success($data = null) {
+		$objResultWrapper = new self();
+		$objResultWrapper->status = true;
+		$objResultWrapper->data = $data;
+		return $objResultWrapper;
+	}
+
+	/**
+	 * 表示处理失败
+	 * @param mixed $data
+	 * @return ResultWrapper
+	 */
+	static public function fail($data = null) {
+		$objResultWrapper = new self();
+        $objResultWrapper->status = false;
+        $objResultWrapper->data = $data;
+        return $objResultWrapper;
+	}
+
+	/**
+	 * 判断处理是否成功
+	 * @return Boolean
+	 */
+	public function isSuccess() {
+		return $this->status === true;
+	}
+
+	/**
+	 * 获取数据
+	 * @return mixed
+	 */
+	public function getData() {
+		return $this->data;
+	}
+}

+ 96 - 0
KIF/Db/.svn/entries

@@ -0,0 +1,96 @@
+10
+
+dir
+924
+svn://182.92.3.30/project/onepage/KIF/Db
+svn://182.92.3.30/project
+
+
+
+2016-01-21T06:27:41.361155Z
+137
+yubin
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+140960d9-11d8-4647-83e1-275f326d1242
+
+Transaction.class.php
+file
+
+
+
+
+2016-02-15T00:10:45.000000Z
+9361878fd169ffba8fee8fc4665d4697
+2016-01-21T06:27:41.361155Z
+137
+yubin
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+1955
+
+MySQLi.class.php
+file
+
+
+
+
+2016-02-15T00:10:45.000000Z
+8038d7611e7fd5811f19c01aa9be6d0a
+2016-01-21T06:27:41.361155Z
+137
+yubin
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+44979
+

+ 5 - 0
KIF/Db/.svn/prop-base/MySQLi.class.php.svn-base

@@ -0,0 +1,5 @@
+K 14
+svn:executable
+V 1
+*
+END

+ 5 - 0
KIF/Db/.svn/prop-base/Transaction.class.php.svn-base

@@ -0,0 +1,5 @@
+K 14
+svn:executable
+V 1
+*
+END

+ 1354 - 0
KIF/Db/.svn/text-base/MySQLi.class.php.svn-base

@@ -0,0 +1,1354 @@
+<?php
+namespace KIF\Db;
+
+use KIF\Core\Config;
+
+use MySQLi as MySQLiResource;
+use KIF\Debug\Debug;
+use KIF\Core\Request;
+use KIF\Cookie;
+use Exception;
+
+class MySQLi {
+	/**
+     * 当前的数据库连接对象
+     *
+     * @var MySQLiResource
+     */
+    private $link;
+
+    /**
+     * 当前连接的数据库名
+     * @var string
+     */
+    private $dbname;
+
+        /**
+     * #private
+     * 统计查询次数
+     *
+     * @var Int
+     */
+    static public $queries = 0;
+
+    /**
+     * 通过$this->query 所执行的总时间
+     * 只有 debug::sql打开才会统计
+     *
+     * @var Int
+     */
+    private static $intQueriesTotalTime = 0;
+
+    /**
+     * 保存所有已建立的到服务器的连接。
+     *
+     * @var Array 结构如下:
+     * array(
+     *     '连接唯一标志' => MySQLiResource   //连接唯一标志由 $this->getUniqueFlagOfLink() 获取;MySQLiResource 是到主服务器1的连接。
+     *     , ...
+     * )
+     */
+    static private $links;
+
+    /**
+     * 调试等级
+     * 0 不处理(交予外部程序处理)
+     * 1 显示错误并中断程序
+     * 2 直接中断程序
+     *
+     * @var Int
+     */
+    public $debug_level = 1;
+
+    /**
+     * 最近一次执行的语句
+     *
+     * @var string
+     */
+    private $last_sql;
+
+    /**
+     * 最后一次执行 query 后获取的 mysqli_result 对象
+     *
+     * @var mysqli_result
+     */
+    private $result;
+
+	/**
+     * from Data Source Name (dsn)
+     * for example: mysqli://root:851031@localhost:3306/testDb?charset=utf8
+     *
+     * @var Array ( 
+     * 		'prefix' => '', 
+     * 		'host' => '', 
+     * 		'port' => '', 
+     * 		'user' => '', 
+     * 		'pass' => '',
+     * 		'params'	=> array(),// 通过dsn传递进来的查询字串
+     * )
+     */
+    private $arrDsn;
+
+    /**
+     * 保存当前主服务器连接的信息
+     *是对 self::$masterDBInfos 数据结构某一项的引用
+     * @var array
+     * 结构如下
+     * array(// 当前集群下的主服务器
+     *     'host' => //主机名
+     *     , 'port'  => //端口
+     *     , 'user' => //用户名
+     *     , 'pass' => //密码
+     *     , 'transactionIds' => //用于存储事务id。Array ('taId1' => true, 'taId2' => false, ...);
+     *                                                        //键代表事务标志,同时也是用于保存点的标志。值表示是否为顶层事务(只能有一个顶层事务)
+     *     , 'isRunOnMaster' => Boolean   //$this->query 方法里可检验该值,判断当前查询正连接于主服务器上
+     *     , 'isUseMaster' => Boolean //指示后续查询是否要使用到主库的连接
+     *     , 'arrStatusOfUseMaster' => Array(); //当嵌套修改 isUseMaster 的值时,需要有一个结构保存之前的状态,以便恢复
+     * )
+     */
+    private $masterDBInfo;
+
+    /**
+     * 保存所有用于主服务器连接的信息
+     *
+     * @var Array  结构如下:
+     * array(
+     *     '集群1标志' => array( //
+     *         '主服务器标志' => array(//该标志通过 $this->getUniqueFlagOfLink() 获取:实质是通过 host, port, user, pass 来区别,所以同一集群如果使用不同的用户连接,会有不同的标志
+     *             'host' => //主机名
+     *             , 'port'  => //端口
+     *             , 'user' => //用户名
+     *             , 'pass' => //密码
+     *             , 'transactionIds' => //用于存储事务id。Array ('taId1' => true, 'taId2' => false, ...);
+     *                                                        //键代表事务标志,同时也是用于保存点的标志。值表示是否为顶层事务(只能有一个顶层事务)
+     *             , 'isRunOnMaster' => Boolean   //$this->query 方法里可检验该值,判断$this->link是否正链接到主服务器上
+     *             , 'isUseMaster' => Boolean //指示后续查询是否要使用到主库的连接
+     *             , 'arrStatusOfUseMaster' => Array();
+     *         )
+     *         , ...
+     *     )
+     *     , ...
+     * )
+     */
+    static private $masterDBInfos = array();
+
+
+    /**
+     * 集群信息,即用于候选的从服务器集
+     * array(
+     *     'clusterLevel' => (2 表示正常集群、3 表示低效集群)
+     *     , 'Ignore_Check_Master_Patterns' => array()
+     *
+     *     , '集群1标志' => array( // 该标志通过 $this->getMasterHost() 获取:集群只需要通过主服务器host即可区分
+     *                        array(
+     *                                  'host' =>
+     *                                  , 'port'  =>
+     *                                  , 'user' =>
+     *                                  , 'pass' =>
+     *                        )
+     *                        , ...
+     *                  )
+     *     , ...
+     * )
+     * @var Array
+     */
+    static private $slaveDBInfos;
+
+    /**
+     * 保存已从集群信息中获取的用于连接从服务器的信息
+     * @author Gxg <gaoxiaogang@gmail.com>
+     *
+     * @var Array  结构如下:
+     * array(// 当前集群下的从库信息
+     *         'host' => //主机名
+     *         , 'port'  => //端口
+     *         , 'user' => //用户名
+     *         , 'pass' => //密码
+     * )
+     */
+    private $slaveDBInfo;
+
+    /**
+     * 返回当前集群中从服务器的信息
+     *
+     * @return false | Array 格式:
+     * array(
+     *       'host' => //主机名
+     *       , 'user' => //用户名
+     *       , 'pass' => //密码
+     * )
+     */
+    private function getCurrentSlaveInfo() {
+    	$arrInvalidSlaveInfos = $this->getCurrentInvalidSlaveInfos();
+        if (isset($this->slaveDBInfo)) {
+            # 检验是否有效
+            if (!$arrInvalidSlaveInfos || !in_array($this->slaveDBInfo['host'], $arrInvalidSlaveInfos)) {
+                return $this->slaveDBInfo;
+            }
+        }
+
+        $strUniqueFlagOfCluster = $this->getUniqueFlagOfCurrentCluster();
+        if (!isset(self::$slaveDBInfos[$strUniqueFlagOfCluster])) {
+        	# 不能使用 $this->slaveDBInfo = null,否则引用的 self::$slaveDBInfos 相应的值也会变成 null
+            return false;
+        }
+
+        # 把 self::$slaveDBInfos 中已经无效的从库踢掉
+        if ($arrInvalidSlaveInfos) {
+            foreach (self::$slaveDBInfos[$strUniqueFlagOfCluster] as $key => $arrSlaveInfo) {
+                $strSlaveHost = $arrSlaveInfo['host'];
+                if (in_array($strSlaveHost, $arrInvalidSlaveInfos)) {
+                    unset(self::$slaveDBInfos[$strUniqueFlagOfCluster][$key]);
+                }
+            }
+        }
+
+        if (count(self::$slaveDBInfos[$strUniqueFlagOfCluster]) == 0) {
+        	return false;
+        }
+
+        $intRandPos = array_rand(self::$slaveDBInfos[$strUniqueFlagOfCluster]);
+	    $this->slaveDBInfo = & self::$slaveDBInfos[$strUniqueFlagOfCluster][$intRandPos];
+	    if (!isset($this->slaveDBInfo['user']) || !isset($this->slaveDBInfo['pass'])) {//从服务器没有提供连接帐户,则使用主服务器的帐户
+	        $this->slaveDBInfo['user'] = $this->masterDBInfo['user'];
+	        $this->slaveDBInfo['pass'] = $this->masterDBInfo['pass'];
+	    }
+	    if (!isset($this->slaveDBInfo['port'])) {
+	        $this->slaveDBInfo['port'] = $this->masterDBInfo['port'];
+	    }
+
+	    return $this->slaveDBInfo;
+    }
+
+    /**
+     * 集群中无效的从服务器信息
+     * 寻找从服务器时,会忽略该结构中列出的主机
+     * array(
+     *     '集群1标志' => array('从服务器host', '从服务器host', ...)
+     *     , ...
+     * )
+     *
+     * @var Array
+     */
+    static private $invalidSlaveInfos;
+
+    /**
+     * 增加无效的从服务器信息
+     *
+     * @param String $strHost
+     * @return true
+     */
+    private function addInvalidSlaveInfo($strHost) {
+        $strUniqueFlagOfCluster = $this->getUniqueFlagOfCurrentCluster();
+        if (!isset(self::$invalidSlaveInfos[$strUniqueFlagOfCluster])
+            || !in_array($strHost, self::$invalidSlaveInfos[$strUniqueFlagOfCluster])
+        ) {
+            self::$invalidSlaveInfos[$strUniqueFlagOfCluster][] = $strHost;
+        }
+        return true;
+    }
+
+    /**
+     * 获取当前集群的唯一标志(即主服务器host)
+     *
+     * @return String
+     */
+    private function getUniqueFlagOfCurrentCluster() {
+        //return "{$this->masterDBInfo['host']}_{$this->masterDBInfo['port']}_{$this->masterDBInfo['user']}";
+        return "{$this->masterDBInfo['host']}:{$this->masterDBInfo['port']}";
+    }
+
+    /**
+     * 获取当前集群下无效的从服务器信息
+     *
+     * @return false | Array
+     */
+    private function getCurrentInvalidSlaveInfos() {
+        $strUniqueFlagOfCluster = $this->getUniqueFlagOfCurrentCluster();
+        if (!isset(self::$invalidSlaveInfos[$strUniqueFlagOfCluster])) {
+            return false;
+        }
+
+        return self::$invalidSlaveInfos[$strUniqueFlagOfCluster];
+    }
+
+    /**
+     * 设置客户端连接字符集
+     *
+     * @param String $charset 连接编码。该参数决定了从各种编码的表里取出数据后,以$strCharset指定的编码返回
+     * 		比如:gbk编码存储的表数据,可以通过指定$strCharset为utf8来返回utf8编码的数据
+     * @return Boolean true:成功;false:失败.
+     */
+    public function setCharset($charset = null) {
+    	if (is_null($charset)) {
+    		if (isset($this->arrDsn['params']['charset']) && $this->arrDsn['params']['charset']) {
+    			$charset = $this->arrDsn['params']['charset'];
+    		} else {
+    			$charset = "utf8";
+    		}
+    	}
+        return $boolResult = mysqli_set_charset($this->link, $charset);
+    }
+
+    /**
+     * 取得客户端连接字符集
+     *
+     * @return String
+     */
+    public function getCharset() {
+        return $strResult = mysqli_character_set_name($this->link);
+    }
+
+    /**
+     * 获取当前连接的唯一标志
+     *
+     * @return String
+     */
+    private function getUniqueFlagOfLink() {
+        return "{$this->arrDsn['host']}_{$this->arrDsn['port']}_{$this->arrDsn['user']}_{$this->arrDsn['pass']}";
+    }
+
+    /**
+     * 构造函数
+     *
+     * @param String $dsn For Example: mysqli://root:851031@localhost/testDb
+     */
+    public function __construct($dsn) {
+        $arrUrlInfo = parse_url($dsn);
+        if (!is_array($arrUrlInfo) || !isset($arrUrlInfo['host']) || !isset($arrUrlInfo['user'])
+           || !isset($arrUrlInfo['pass']) || !isset($arrUrlInfo['path']))
+        {
+        	$this->_halt("构造参数不正确:{$dsn}");
+        	return false;
+            //解析出错时的处理
+        }
+        $this->arrDsn['host'] = $arrUrlInfo['host'];
+        $this->arrDsn['user'] = $arrUrlInfo['user'];
+        $this->arrDsn['pass'] = $arrUrlInfo['pass'];
+
+        $dbname = substr($arrUrlInfo['path'], 1);
+        if (empty($dbname)) {
+        	return $this->_halt('请先设置数据库名', '21');
+        }
+        $this->dbname = $dbname;
+
+        isset($arrUrlInfo['scheme']) && $this->arrDsn['prefix'] = $arrUrlInfo['scheme'];
+        if (!isset($arrUrlInfo['port'])) {
+        	$this->arrDsn['port'] = 3306;
+        } else {
+        	$this->arrDsn['port'] = $arrUrlInfo['port'];
+        }
+        
+        # 处理参数
+        if (isset($arrUrlInfo['query'])) {
+        	parse_str($arrUrlInfo['query'], $this->arrDsn['params']);
+        } else {
+        	$this->arrDsn['params'] = array();
+        }
+
+        $strUniqueFlagOfCluster = $this->arrDsn['host'];
+        $strUniqueFlagOfMaster = $this->getUniqueFlagOfLink();
+        if (!isset(self::$masterDBInfos[$strUniqueFlagOfCluster][$strUniqueFlagOfMaster])) {
+            self::$masterDBInfos[$strUniqueFlagOfCluster][$strUniqueFlagOfMaster] = array(
+                'host' => $this->arrDsn['host']
+                , 'port' => $this->arrDsn['port']
+                , 'user' => $this->arrDsn['user']
+                , 'pass' => $this->arrDsn['pass']
+                , 'transactionIds' => null
+                , 'isRunOnMaster' => false
+                , 'isUseMaster' => false
+                , 'arrStatusOfUseMaster' => null
+            );
+        }
+        $this->masterDBInfo = & self::$masterDBInfos[$strUniqueFlagOfCluster][$strUniqueFlagOfMaster];
+
+        if (!isset(self::$slaveDBInfos)) {
+            $this->initSlaveDBInfos();
+        }
+    }
+
+	/**
+     * 初始化 self::$slaveDBInfos 数据结构
+     *
+     * dbslaves 的结构:
+     * array(
+     *     array(
+     *         'master' => (string),//对应主库的host值
+     *         'mixed'  => array(//正常从库
+     *             'mysqld-6.yoka.com',
+     *             '192.168.0.150',
+     *             ... ,
+     *         ),
+     *         'delay_mixed'    => array(//慢速从库,比如用于提供给爬虫、或提供给翻页的比较后面的页面,无需保证特别好的服务
+     *             'mysqld-12.yoka.com',
+     *             array(
+     *                 'host'   => (string),//必须
+     *                 'user'   => (string),//必须
+     *                 'pass'   => (string),//必须
+     *                 'port'   => (int),//非必须
+     *             ),
+     *             ... ,
+     *         ),
+     *     )
+     * );
+     *
+     */
+    private function initSlaveDBInfos() {
+    	try {
+    		$dbslavesConfig = Config::getInstance()->get('dbslaves');
+    	} catch (Exception $e) {
+    		return false;
+    	}
+    	if (!is_array($dbslavesConfig)) {
+    		return false;
+    	}
+    	
+    	$db_cluster_maps = $dbslavesConfig;
+    	if (isset($_SERVER['Cluster_User_Level']) && $_SERVER['Cluster_User_Level'] == 3) {
+    		$cluster_level = 3;
+    		$strType = 'delay_mixed';
+    	} else {
+    		$cluster_level = 2;
+    		$strType = 'mixed';
+    	}
+    	foreach ($db_cluster_maps as $arrClusterInfo) {
+    		if (!isset($arrClusterInfo['master']) || !isset($arrClusterInfo[$strType]) || !is_array($arrClusterInfo[$strType]) || (count($arrClusterInfo[$strType]) == 0)) {
+    			# TODO 记录日志
+    			continue;
+    		}
+
+    		foreach($arrClusterInfo[$strType] as $mixConnectInfo) {
+                if (is_string($mixConnectInfo)) {//类似 mysqld-6.verycd.com 的值
+                    self::$slaveDBInfos[$arrClusterInfo['master']][] = array(
+                        'host'      => $mixConnectInfo
+                        , 'port'    => null
+                        , 'user'    => null
+                        , 'pass'    => null
+                    );
+                } elseif (is_array($mixConnectInfo)) {//如果$_SERVER['DataBase_Cluster_Map'] 提供客启端的密码,请使用以下格式 !
+                    if (isset($mixConnectInfo['host']) && isset($mixConnectInfo['user']) && isset($mixConnectInfo['pass'])) {
+                    	$tmpPort = isset($mixConnectInfo['port']) ? $mixConnectInfo['port'] : 3306;
+                        self::$slaveDBInfos[$arrClusterInfo['master']][] = array(
+                            'host'      => $mixConnectInfo['host']
+                            , 'port'    => $tmpPort
+                            , 'user'    => $mixConnectInfo['user']
+                            , 'pass'    => $mixConnectInfo['pass']
+                        );
+                    } else {
+//                                $this->_halt('该集群:' . $arrClusterInfo['master'] . '在$_SERVER[\'DataBase_Cluster_Map\']里提供的连接信息格式错误');
+                        # TODO 记下错误日志
+                        continue;
+                    }
+                } else {
+                    # TODO 记下错误日志
+//                            $this->_halt('该集群:' . $arrClusterInfo['master'] . '在$_SERVER[\'DataBase_Cluster_Map\']里提供的连接信息格式错误');
+                    continue;
+                }
+            }//end foreach
+    	}//end foreach
+    }
+
+    /**
+     * 获取当前主连接唯一标志
+     *
+     * @return String
+     */
+    private function getUniqueFlagOfCurrentMaster() {
+        return "{$this->masterDBInfo['host']}_{$this->masterDBInfo['port']}_{$this->masterDBInfo['user']}_{$this->masterDBInfo['pass']}";
+    }
+
+    /**
+     * 获取当前集群状态下到主服务器的连接
+     *
+     * @return mysqli | false   已有连接,返回该连接(即mysqli对象);false:没有连接
+     */
+    private function getCurrentMasterLink() {
+        $strUniqueFlagOfCurrentMaster = $this->getUniqueFlagOfCurrentMaster();
+        if (isset(self::$links[$strUniqueFlagOfCurrentMaster])) {
+            if ($this->isLink(self::$links[$strUniqueFlagOfCurrentMaster])) {
+                return self::$links[$strUniqueFlagOfCurrentMaster];
+            } else {
+            	# 曾建立过连接,但中途该连接失效了,应该对此做出处理的。
+            	if (!empty($this->masterDBInfo['transactionIds'])) {
+            		return $this->_halt("到主库的连接已失效,且该主库上存在事务,必须中断!");
+            	}
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * 当前连接是否连到主库
+     */
+    private function isRunOnMaster() {
+        return (boolean) $this->masterDBInfo['isRunOnMaster'];
+    }
+
+    /**
+     * 获取所有事务信息
+     * 只有当前集群中主服务器的连接是可用的,才有事务可言
+     *
+     * @return false | Array()   false:没有到主服务器的连接或者还没开启事务;
+     */
+    private function getTransactions() {
+        if ($this->getCurrentMasterLink()) {
+            if (!empty($this->masterDBInfo['transactionIds'])) {
+                return $this->masterDBInfo['transactionIds'];
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * 当前查询是否正运行在主服务器上并且开启了事务
+     * 该方法在 $this->query 方法里调用才是最有价值的。
+     *
+     * @return Boolean     true:是;false:否
+     */
+    private function isRunOnTransaction() {
+        if ($this->isRunOnMaster() && $this->getTransactions()) {
+            return true;
+        }
+
+        return false;
+    }
+
+    private function isLink($link) {
+        if (!($link instanceof MySQLiResource)) return false;
+        $sinfo = @mysqli_get_host_info($link);
+        return !empty($sinfo);
+    }
+
+    private function isReadSql($sql) {
+        static $r_ops = array('select','show','desc');
+        $sql = strtolower(trim($sql));
+        foreach ($r_ops as $op) {
+           if (strpos($sql,$op)===0) return true;
+        }
+        return false;
+    }
+
+    /**
+     * 连接数据库。这里创建的是真实链接,不会重用已有的链接
+     *
+     * @return false | MySQLiResource false:连接失败
+     */
+     private function connect() {
+        // 连接数据库服务器
+        $objMysqli = mysqli_init();
+
+        $connect_rs = mysqli_real_connect($objMysqli, $this->arrDsn['host'], $this->arrDsn['user'], $this->arrDsn['pass']
+                            , null, $this->arrDsn['port'], null
+                            , MYSQLI_CLIENT_COMPRESS);
+        if (!$connect_rs) {
+            return false;
+        }
+
+        # 设置字符集的代码
+
+        $this->link = $objMysqli;
+        $this->setCharset();
+        return $this->link;
+    }
+
+    /**
+     * 标志当前连接$this->link连接到主库
+     *
+     * @return Boolean
+     */
+    private function beginRunOnMaster() {
+        $this->masterDBInfo['isRunOnMaster'] = true;
+        return true;
+    }
+
+    /**
+     * 标志当前连接$this->link离开主库
+     *
+     * @return Boolean
+     */
+    private function endRunOnMaster() {
+        $this->masterDBInfo['isRunOnMaster'] = false;
+        return true;
+    }
+
+    /**
+     * 该方法只应该由 $this->xconnect()调用
+     *
+     * @return Boolean  false:连接失败
+     */
+    private function connectMaster() {
+        # These codes increase by Gxg <gaoxiaogang@gmail.com>
+        # 保证到当前集群的主服务器的连接唯一
+        $this->arrDsn['host'] = $this->masterDBInfo['host'];
+        $this->arrDsn['port'] = $this->masterDBInfo['port'];
+        $this->arrDsn['user'] = $this->masterDBInfo['user'];
+        $this->arrDsn['pass'] = $this->masterDBInfo['pass'];
+
+        $objCurrentMasterLink = $this->getCurrentMasterLink();
+        if ($objCurrentMasterLink) {
+            $this->link = $objCurrentMasterLink;
+        } else {
+            if (false === $this->connect()) {
+                return false;
+            }
+            $strUniqueFlagOfLink = $this->getUniqueFlagOfLink();
+            self::$links[$strUniqueFlagOfLink] = $this->link;
+        }
+
+        $this->beginRunOnMaster();
+
+        return true;
+    }
+
+    /**
+     * 该方法只应该由 $this->xconnect()调用
+     *
+     * @return Boolean  false:连接失败
+     *
+     */
+    private function connectSlave() {
+    	$arrCurrentSlaveInfo = $this->getCurrentSlaveInfo();
+        if ($arrCurrentSlaveInfo) {
+            $this->arrDsn['host'] = $arrCurrentSlaveInfo['host'];
+            $this->arrDsn['port'] = $arrCurrentSlaveInfo['port'];
+            $this->arrDsn['user'] = $arrCurrentSlaveInfo['user'];
+            $this->arrDsn['pass'] = $arrCurrentSlaveInfo['pass'];
+
+            $strUniqueFlagOfLink = $this->getUniqueFlagOfLink();
+            if (isset(self::$links[$strUniqueFlagOfLink]) && $this->isLink(self::$links[$strUniqueFlagOfLink])) {
+                $this->link = self::$links[$strUniqueFlagOfLink];
+            } else {
+                if(false === $this->connect()) {
+                    return false;
+                }
+                self::$links[$strUniqueFlagOfLink] = $this->link;
+            }
+            return true;
+        } else {//还是连主服务器
+            return $this->connectMaster();
+        }
+    }
+    
+    /**
+     * 
+     * 重新链接。
+     * 目前在 mysql 2006错误,并且没有运行事务时才开启该方法
+     * @return Boolean
+     */
+    private function reConnect() {
+    	if (false === $this->connect()) {
+        	return false;
+        }
+        $strUniqueFlagOfLink = $this->getUniqueFlagOfLink();
+        self::$links[$strUniqueFlagOfLink] = $this->link;
+        return true;
+    }
+
+    /**
+     * 判断查询是否要使用主服务器
+     *
+     * @return Boolean  true:使用主;false:使用从
+     */
+    private function isUseMaster() {
+    	return (boolean) $this->masterDBInfo['isUseMaster'];
+    }
+
+    /**
+     * 保存当前状态,并置为$status
+     *
+     * @param Boolean $status
+     * @return String
+     */
+    private function changeStatusOfUseMaster($status) {
+    	($status === true) || $status = false;
+
+        # 保存当前状态
+        $strMasterStatusId = $this->getUniqueMasterStatusId();
+        $this->masterDBInfo['arrStatusOfUseMaster'][$strMasterStatusId] = $this->masterDBInfo['isUseMaster'];
+        $this->masterDBInfo['isUseMaster'] = $status;
+        return $strMasterStatusId;
+    }
+
+    /**
+     * 开始使用主库
+     *
+     * @return String 返回一串标志,供$this->restore 方法使用,用于恢复上一个状态
+     */
+    public function beginUseMaster() {
+        return $this->changeStatusOfUseMaster(true);
+    }
+
+    /**
+     * 恢复采用 $strMasterStatusId 为句柄保存的上次的状态
+     *
+     * @param String $strMasterStatusId
+     * @return Boolean
+     *
+     */
+    public function restore($strMasterStatusId) {
+        # 恢复指定状态
+        if (isset($this->masterDBInfo['arrStatusOfUseMaster'][$strMasterStatusId])) {
+            $this->masterDBInfo['isUseMaster'] = $this->masterDBInfo['arrStatusOfUseMaster'][$strMasterStatusId];
+            unset($this->masterDBInfo['arrStatusOfUseMaster'][$strMasterStatusId]);
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * 开始使用从库
+     * 尽量不要使用该接口,除非你明白自己真的需要
+     *
+     */
+    public function beginUseSlave() {
+        return $this->changeStatusOfUseMaster(false);
+    }
+
+    /**
+     * 处理连接
+     *
+     * @param String $sql
+     */
+    protected function xconnect($sql) {
+    	$isUseSlave = $this->isReadSql($sql) && !$this->isUseMaster() ? true : false;
+
+        $intConnectErrorNum = 0;//连接出错次数
+        while(true) {
+            if ($isUseSlave) {
+                $isConnect = $this->connectSlave();
+            } else {
+                $isConnect = $this->connectMaster();
+            }
+
+            if (!$isConnect) {//连接失败
+                ++$intConnectErrorNum;
+                $strMasterHost = $this->masterDBInfo['host'];
+                if(4 >= $intConnectErrorNum   //允许四次重试
+                   && $this->arrDsn['host'] != $strMasterHost //错误不是发生在主服务器上
+                ) {
+                    $this->addInvalidSlaveInfo($this->arrDsn['host']);
+//                    $this->addErrorLog(self::PARAM_NO_IMPORTANCE_ERROR_DIR, $intConnectErrorNum);
+                    continue;
+                }
+
+                return $this->_halt('服务器连接失败', '01');
+            }
+
+            # 成功就退出循环
+            break;
+        }
+     }
+
+    /**
+     * 执行一条SQL
+     *
+     * @param String $sql
+     * @return resource result
+     */
+    public function query($sql) {
+    	# 每条语句都添加注释,方便debug。比如慢查询日志里知道问题出在哪个文件。
+	    $sql .= '/* ' . $_SERVER['HTTP_HOST'] . ' in '.$_SERVER['PHP_SELF'] . ' */';
+	    
+        if (Debug::$open && preg_match('#^\s*select\s#i', $sql)) {
+            $explain_query = true;
+        } else {
+            $explain_query = false;
+        }
+
+        if ($explain_query) {
+        	# 便于查看不使用缓存时的情况
+            $sql = preg_replace('#select #i', 'select sql_no_cache ', $sql);
+        }
+
+        $this->last_sql = $sql; // 临时加上
+        $intQueryErrorNum = 0;//查询出错次数
+        $intSelectErrorNum = 0;//选择数据库出错次数
+        while (true) {
+            $this->xconnect($sql);
+
+            $isSelect = mysqli_select_db($this->link, $this->dbname);
+            # 处理选择数据库错误
+            if (!$isSelect) {
+            	if ($this->isRunOnTransaction()) {
+            		return $this->_halt('进入数据库失败:存在事务,直接停机', '02');
+            	}
+            	
+            	++$intSelectErrorNum;
+            	if ($intSelectErrorNum > 4) {
+            		return $this->_halt('进入数据库失败后,重试多次后仍然失败', '02');
+            	}
+            	
+            	// 服务器链接丢失的错误,并且没有运行事务,允许重连
+            	if ($this->errno() == 2006) {
+            		if ($this->reConnect()) {
+            			continue;
+            		}
+            	}
+            	
+            	if ($this->isRunOnMaster()) {// 主库重连错误,停机
+            		return $this->_halt('进入数据库失败', '02');
+            	}
+            		
+            	// 否则将这台重库置为无效,尝试重连
+            	$this->addInvalidSlaveInfo($this->arrDsn['host']);
+                continue;
+            }
+
+            if (!$this->isReadSql($sql)) {
+//                # 如果是写入语句,记录开始时间
+//                $objProcessTimeOfWriteSqlTime = new ProcessTime();
+//                $objProcessTimeOfWriteSqlTime->start();
+
+                # 如果运行于事务中,记录该语句
+                if ($this->isRunOnTransaction()) {
+                    $this->masterDBInfo['arrTransactionSqls'][] = $sql;
+                }
+            }
+
+            $query = mysqli_query($this->link, $sql);
+
+//            # 记录慢写入语句
+//            if (!$this->isReadSql($sql)) {
+//                if (($runTimeOfWriteSql = $objProcessTimeOfWriteSqlTime->getFinalTime()) > 1) {
+//                    $this->addSlowWriteSqlLog($runTimeOfWriteSql);
+//                }
+//            }
+            # 处理查询错误
+            if (!$query) {
+            	if ($this->isRunOnTransaction()) {
+            		return $this->_halt('查询数据库失败:存在事务,直接停机', '21');
+            	}
+            	++$intQueryErrorNum;
+            	if ($intQueryErrorNum > 4) {
+            		return $this->_halt('查询数据库失败后,重试多次后仍然失败', '21');
+            	}
+            	
+            	// 服务器链接丢失的错误,并且没有运行事务,允许重连
+            	if (in_array($this->errno(), array(2006, 2013))) {
+            		if ($this->reConnect()) {
+            			continue;
+            		}
+            	}
+
+                static $arrConnectErrnos = array(
+                    1053      //在操作过程中服务器关闭。
+                    , 1030    //从存储引擎中获得错误
+                    , 126     //表损坏
+                );
+                
+                if ($this->isRunOnMaster()) {// 主库重连错误,停机
+            		return $this->_halt('查询主数据库失败', '21');
+            	}
+            		
+                // 否则将这台重库置为无效,尝试重连
+                if(in_array($this->errno(), $arrConnectErrnos)) {//指定的错误号
+                    $this->addInvalidSlaveInfo($this->arrDsn['host']);//由于连接失效导致的查询出错,将这台服务器标记为无效
+                    continue;
+                }
+
+                return $this->_halt('查询数据库失败,不能处理的错误类型', '21');
+            }
+            break;
+        }
+
+        # 走到这里,说明成功的执行了写入sql
+        if ($this->canSetCookieForMasterDBHasWrite($sql)) {// 如果写入语句成功,就写一个保存时间为$tmp_expiration秒的cookie
+        	$tmp_expiration = 2*60;
+			Cookie::set(KIF_MASTER_DB_HAS_WRITE_COOKIE_KEY, '1', $tmp_expiration);// 设置$tmp_expiration秒的cookie
+        }
+
+        self::$queries++;
+        if ($explain_query) {
+            $begin_microtime = Debug::getTime();
+
+            self::$intQueriesTotalTime += $begin_microtime;
+
+            $explainSql = 'explain ' . $sql;
+            $equery = mysqli_query($this->link, $explainSql);
+            $explain = $this->fetch($equery);
+            $this->freeResult($equery);
+            Debug::db($this->getLinkDesc(), $this->dbname, $explainSql, Debug::getTime() - $begin_microtime, $explain);
+        }
+
+        if (!$this->isReadSql($sql)) {
+        	$begin_microtime = Debug::getTime();
+        	Debug::db($this->getLinkDesc(), $this->dbname, $sql, Debug::getTime() - $begin_microtime, $query);
+        }
+        $this->isRunOnMaster() && $this->endRunOnMaster();
+
+        return $query;
+    }
+
+    /**
+     *
+     * 判断指定的sql执行后,能否设置后续查询转到主库的cookie
+     * @param string $sql
+     * @return boolean
+     */
+    private function canSetCookieForMasterDBHasWrite($sql) {
+    	if (Request::isCLI()) {
+    		return false;
+    	}
+		if ($this->isReadSql($sql)) {
+			return false;
+		}
+
+		# xhprof_logs表是用来记录慢查询的,没必要因为这个表写入了一次就把后续的请求都转到主库。
+		if (preg_match('#(xhprof_logs)#', strtolower($sql))) {
+			return false;
+		}
+
+		return true;
+    }
+
+    public function fetchOne($sql) {
+    	$begin_microtime = Debug::getTime();
+
+        $res = $this->query($sql);
+        if (!$res) {
+        	return false;
+        }
+        $result = $this->fetch($res);
+
+        $this->freeResult($res);
+
+        Debug::db($this->getLinkDesc(), $this->dbname, $this->last_sql, Debug::getTime() - $begin_microtime, $result);
+
+        return $result;
+    }
+
+    /**
+     * 获取连接描述,用于Debug输出
+     */
+    private function getLinkDesc() {
+    	$thread_id = mysqli_thread_id($this->link);
+    	return "mysqli://{$this->arrDsn['user']}:{$this->arrDsn['port']}@{$this->arrDsn['host']} (thread_id: {$thread_id})";
+    }
+
+    /**
+     * 执行一条SQL并返回此查询包含的所有数据(2维数组)
+     *
+     * @param string $sql
+     * @param string $associateKey 如果指定了$associateKey,返回结果以 每条记录的$associateKey字段做数组下标
+     * @return false | array
+     */
+    public function fetchAll($sql, $associateKey = null) {
+    	$begin_microtime = Debug::getTime();
+
+        $res = $this->query($sql);
+        if (!$res) {
+            return false;
+        }
+
+        $result = array();
+        if ($associateKey) {
+            while (true) {
+            	$row = $this->fetch($res);
+            	if (!$row) {
+            		break;
+            	}
+            	if (isset($row[$associateKey])) {
+            		$result[$row[$associateKey]] = $row;
+            	} else {
+            		$result[] = $row;
+            	}
+            }
+        } else {
+            while (true) {
+            	$row = $this->fetch($res);
+            	if (!$row) {
+            		break;
+            	}
+                $result[] = $row;
+            }
+        }
+
+        $this->freeResult($res);
+
+        Debug::db($this->getLinkDesc(), $this->dbname, $this->last_sql, Debug::getTime() - $begin_microtime, $result);
+
+        return $result;
+    }
+
+    /**
+     * 执行SQL语句并返回第一行第一列
+     *
+     * @param string $sql
+     * @return false | scala
+     */
+    public function fetchSclare($sql) {
+    	$begin_microtime = Debug::getTime();
+
+        $result = $this->fetchOne($sql);
+        if (!$result) {
+        	return false;
+        }
+        $result = array_shift($result);
+
+        Debug::db($this->getLinkDesc(), $this->dbname, $this->last_sql, Debug::getTime() - $begin_microtime, $result);
+
+        return $result;
+    }
+
+    /**
+     * 返回上一步 INSERT 查询中产生的 AUTO_INCREMENT 的 ID 号;
+     * 或者 返回 update 语句中 last_insert_id()函数中表达式的值。
+     *
+     * !!请记住,一定紧接在insert 或 update 语句后执行该方法,否则$this->link可能已经指向别的服务器了
+     *
+     * @return int | NULL >0:成功取到;0:没取到;NULL:$this->link无效
+     */
+    public function insertId() {
+        return mysqli_insert_id($this->link);
+    }
+
+    /**
+     * $this->insertId() 的别名
+     * !!请记住,一定紧接在insert 或 update 语句后执行该方法,否则$this->link可能已经指向别的服务器了
+     * @return int | NULL >0:成功取到;0:没取到;NULL:$this->link无效
+     */
+    public function getLastInsertId() {
+    	return $this->insertId();
+    }
+
+    /**
+     * 返回最近一次 INSERT,UPDATE 或 DELETE 查询所影响的记录行数。
+     * @return int | null 返回值 >= 0:成功;等于 -1:最后一条查询错误;null:$this->link无效
+     **/
+    public function affectedRows()
+    {
+        return mysqli_affected_rows($this->link);
+    }
+
+    public function fetch($query, $resulttype = MYSQLI_ASSOC) {
+        return mysqli_fetch_array($query, $resulttype);
+    }
+
+    protected function freeResult($query) {
+        return mysqli_free_result($query);
+    }
+
+    /**
+     * 生成唯一的字符串作为事务的唯一id
+     *
+     * @return String
+     */
+    static private function getUniqueTransactionId() {
+        return self::getUniqueId('TAId');
+    }
+
+    /**
+     * 生成唯一的字符串作为保存当前主服务器状态的唯一id
+     *
+     * @return String
+     */
+    static private function getUniqueMasterStatusId() {
+        return self::getUniqueId('MSId');
+    }
+
+    /**
+     * 生成唯一id
+     *
+     * @param String $prefix
+     * @return String
+     */
+    static private function getUniqueId($prefix = '') {
+        if(!is_string($prefix)) {
+            $prefix = '';
+        }
+        return uniqid($prefix . '_'.rand());
+    }
+
+    /**
+     * 返回上一个错误文本,如果没有出错则返回 ''(空字符串)。
+     * 如果没有指定连接资源号,则使用上一个成功打开的连接从数据库服务器提取错误信息。
+     *
+     * @return String
+     */
+    public function error() {
+        return @mysqli_error($this->link);
+    }
+
+    /**
+     * 返回上一个错误号
+     *
+     * @return int | NULL
+     */
+    public function errno() {
+        return @mysqli_errno($this->link);
+    }
+
+    /**
+     * 返回上一个连接错误
+     *
+     * @return String
+     */
+    public function connect_error() {
+        return @mysqli_connect_error();
+    }
+
+    /**
+     * 返回上一个连接的错误号
+     *
+     * @return int
+     */
+    public function connect_errno() {
+        return @mysqli_connect_errno();
+    }
+
+    /**
+     * 根据 $this->debug_level 处理一些异常情况
+     * 1 直接输出错误信息并中断程序
+     * 2 直接中断程序
+     * 其他情况不处理错误,返回flase,修改错误代号和本函数所提供的错误信息,最后的是MySQL服务器提供的信息
+     *
+     * @param String $msg
+     * @param String $errorcode
+     * @return Array
+     */
+    function _halt($msg, $errorcode = '00') {
+        switch ($this->debug_level) {
+            case 1:
+                ob_clean();
+                header("HTTP/1.0 500 Server Error");
+                header("Expires: ".gmdate("D, d M Y H:i:s", time())." GMT");
+                header("Last-Modified: ".gmdate("D, d M Y H:i:s", time())." GMT");
+                header("Cache-Control: private");
+//                $out = file_get_contents(ROOT_PATH . '/mysql.html');
+				$out = '$the_error';
+                $out = str_replace('$the_error', $msg.'<hr />'.$this->error().' No.'.$this->errno()."<!-- {$this->last_sql} -->", $out);
+
+//                $this->addErrorLog(null, null, $msg);
+
+                echo $out;
+                exit;
+                break;
+            case 2:
+                ob_clean();
+                header("HTTP/1.0 500 Server Error");
+                header("Expires: ".gmdate("D, d M Y H:i:s", time())." GMT");
+                header("Last-Modified: ".gmdate("D, d M Y H:i:s", time())." GMT");
+                header("Cache-Control: private");
+                echo $this->connect_error(), "<br />";
+                echo $this->connect_errno(), "<br />";
+                echo "{$msg}<br />";
+                exit('MySQL.');
+                break;
+            default:
+                $this->errorcode = array($errorcode, $msg.':'.$this->last_sql, $this->errno().": ".$this->error());
+                return false;
+                break;
+        }
+    }
+
+    ########### TRANSACTION ##########
+    /**
+     * 开启事务
+     *
+     * @return false | string false:失败;string:成功返回事务标志
+     */
+    public function startTransaction() {
+        $strTransactionId = self::getUniqueTransactionId();
+
+        if ($this->getTransactions()) {//已存在事务
+            if ($this->setSavePoint($strTransactionId)) {
+            	$this->masterDBInfo['transactionIds'][$strTransactionId] = false;
+                return $strTransactionId;
+            }
+        } else {
+	        # 初次开启事务
+	        if (true === $this->query('START TRANSACTION;')) {//如果没有建立到当前主服务器的连接,该操作会隐式的建立
+	            $this->masterDBInfo['transactionIds'][$strTransactionId] = true;
+	            return $strTransactionId;
+	        }
+        }
+
+        # 开启事务失败。返回一个独特的字符串,该字符串是不可能出现在 事务id数组中的
+        return false;
+    }
+
+    /**
+     * 回滚父事务
+     *
+     * @param String $strTransactionId
+     * @return Boolean
+     */
+    private function _rollbackRootTransaction($strTransactionId) {
+        if ($this->isRootTransaction($strTransactionId)) {//父事务
+            $this->masterDBInfo['transactionIds'] = null;
+            $this->masterDBInfo['arrTransactionSqls'] = array();
+            return $this->query('ROLLBACK;');
+        }
+        return false;
+    }
+
+    /**
+     * 回滚子事务
+     *
+     * @param String $strTransactionId
+     * @return Boolean
+     */
+    private function _rollbackSubTransaction($strTransactionId) {
+        if($this->isSubTransaction($strTransactionId)) {//子事务
+            $boolStatusTmp = $this->rollbackToSavePoint($strTransactionId);
+            $this->releaseSavePoint($strTransactionId);
+            unset($this->masterDBInfo['transactionIds'][$strTransactionId]);
+            return $boolStatusTmp;
+        }
+        return false;
+    }
+
+    /**
+     * 撤消指定事务
+     *
+     * @param String $strTransactionId
+     * @return Bollean true:成功;false:失败
+     */
+    public function rollback($strTransactionId) {
+        if ($this->isRootTransaction($strTransactionId)) {//父事务
+            return $this->_rollbackRootTransaction($strTransactionId);
+        } elseif ($this->isSubTransaction($strTransactionId)) {//子事务
+            return $this->_rollbackSubTransaction($strTransactionId);
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * 提交父事务
+     *
+     * @param String $strTransactionId
+     * @return Boolean
+     */
+    private function _commitRootTransaction($strTransactionId) {
+        if ($this->isRootTransaction($strTransactionId)) {//父事务
+            $this->masterDBInfo['transactionIds'] = null;
+            $this->masterDBInfo['arrTransactionSqls'] = array();
+            return $this->query('COMMIT;');
+        }
+        return false;
+    }
+
+    /**
+     * 提交子事务
+     *
+     * @param String $strTransactionId
+     * @return Boolean
+     */
+    private function _commitSubTransaction($strTransactionId) {
+        if ($this->isSubTransaction($strTransactionId)) {//子事务
+            $this->releaseSavePoint($strTransactionId);
+            unset($this->masterDBInfo['transactionIds'][$strTransactionId]);
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * 提交指定事务
+     *
+     * @param String $strTransactionId
+     * @return Boolean true:成功;false:失败
+     */
+    public function commit($strTransactionId) {
+        if ($this->isRootTransaction($strTransactionId)) {//父事务
+            return $this->_commitRootTransaction($strTransactionId);
+        } elseif ($this->isSubTransaction($strTransactionId)) {//子事务
+            return $this->_commitSubTransaction($strTransactionId);
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * 设置子事务的保存点,用于支持子事务的回滚
+     *
+     * @param String $SPId 应该被传递的值是事务的唯一id,即调用self::getUniqueTransactionId()生成的
+     * @return Boolean  true:成功;false:失败
+     */
+    private function setSavePoint($SPId) {
+        if (true === $this->query("SAVEPOINT {$SPId}")) {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * 获取指定事务的类型(父事务 还是 子事务)
+     * 只有当前集群中主服务器的连接是可用的,才有事务可言
+     *
+     * @param String $strTransactionId
+     * @return Boolean | null true:父事务;false:子事务;null:无效
+     */
+    private function getTransactionTypeById($strTransactionId) {
+        if ($this->getCurrentMasterLink()) {
+            if (isset($this->masterDBInfo['transactionIds'][$strTransactionId])) {
+                return $this->masterDBInfo['transactionIds'][$strTransactionId];
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * 是否父事务
+     *
+     * @param String $strTransactionId
+     * @return Boolean      true:是;false:否
+     */
+    private function isRootTransaction($strTransactionId) {
+        if (true === $this->getTransactionTypeById($strTransactionId)) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * 是否子事务
+     *
+     * @param String $strTransactionId
+     * @return Boolean      true:是;false:否
+     */
+    private function isSubTransaction($strTransactionId) {
+        if (false === $this->getTransactionTypeById($strTransactionId)) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * 回滚到指定事务点
+     *
+     * @param String $SPId
+     * @return Boolean  true:成功;false:失败
+     */
+    private function rollbackToSavePoint($SPId) {
+        # 只对子事务的回滚点操作
+        if ($this->isSubTransaction($SPId)) {
+            if (true === $this->query("ROLLBACK TO SAVEPOINT {$SPId}")) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 释放事务保存点
+     *
+     * @param String $SPId
+     * @return Boolean  true:成功;false:失败
+     */
+    private function releaseSavePoint($SPId) {
+        # 只对子事务的回滚点操作
+        if ($this->isSubTransaction($SPId)) {
+            if (true === $this->query("RELEASE SAVEPOINT {$SPId}")) {
+                return true;
+            }
+        }
+        return false;
+    }
+}

+ 80 - 0
KIF/Db/.svn/text-base/Transaction.class.php.svn-base

@@ -0,0 +1,80 @@
+<?php
+namespace KIF\Db;
+
+use KIF\Exception\ParamsException;
+use KIF\Db\MySQLi;
+use KIF\Core\Config;
+
+/**
+ * 数据库事务类
+ * 主要是包装 MySQLite 的事务方法。供前端类、前端php响应页面调用。
+ * @author gaoxiaogang@gmail.com
+ *
+ */
+class Transaction {
+
+    /**
+     * 数据库实例
+     *
+     * @var KIF\Db\MySQLi
+     */
+    protected $db;
+
+    public function __construct($cluster_flag = 'default') {
+    	$appConfig = Config::getInstance()->current();
+    	$dbConfig = $appConfig['db'];
+    	
+    	if (!$dbConfig || !isset($dbConfig[$cluster_flag]) || !is_string($dbConfig[$cluster_flag])) {
+    		throw new ParamsException("load config error:{$dbConfig}");
+    	}
+   
+        $this->db = new MySQLi($dbConfig[$cluster_flag]);
+    }
+
+    /**
+     * 开始事务
+     * @return false | string false:失败;string:成功返回事务标志
+     */
+    public function start() {
+    	return $this->db->startTransaction();
+    }
+
+    /**
+     * 提交事务
+     * @param string $strTransactionId
+     * @return Boolean
+     */
+    public function commit($strTransactionId) {
+    	return $this->db->commit($strTransactionId);
+    }
+
+    /**
+     * 回滚事务
+     * @param string $strTransactionId
+     * @return Boolean
+     */
+    public function rollback($strTransactionId) {
+        return $this->db->rollback($strTransactionId);
+    }
+
+    /**
+     *
+     * 开始使用主库。后续的所有读查询,都会被强制到主库
+     *
+     * @return String 返回一串标志,供$this->restore 方法使用,用于恢复上一个状态
+     */
+    public function beginUseMaster() {
+    	return $this->db->beginUseMaster();
+    }
+
+    /**
+     * 恢复采用 $strMasterStatusId 为句柄保存的上次的状态
+     *
+     * @param String $strMasterStatusId
+     * @return Boolean
+     *
+     */
+    public function restore($strMasterStatusId) {
+    	return $this->db->restore($strMasterStatusId);
+    }
+}

+ 1354 - 0
KIF/Db/MySQLi.class.php

@@ -0,0 +1,1354 @@
+<?php
+namespace KIF\Db;
+
+use KIF\Core\Config;
+
+use MySQLi as MySQLiResource;
+use KIF\Debug\Debug;
+use KIF\Core\Request;
+use KIF\Cookie;
+use Exception;
+
+class MySQLi {
+	/**
+     * 当前的数据库连接对象
+     *
+     * @var MySQLiResource
+     */
+    private $link;
+
+    /**
+     * 当前连接的数据库名
+     * @var string
+     */
+    private $dbname;
+
+        /**
+     * #private
+     * 统计查询次数
+     *
+     * @var Int
+     */
+    static public $queries = 0;
+
+    /**
+     * 通过$this->query 所执行的总时间
+     * 只有 debug::sql打开才会统计
+     *
+     * @var Int
+     */
+    private static $intQueriesTotalTime = 0;
+
+    /**
+     * 保存所有已建立的到服务器的连接。
+     *
+     * @var Array 结构如下:
+     * array(
+     *     '连接唯一标志' => MySQLiResource   //连接唯一标志由 $this->getUniqueFlagOfLink() 获取;MySQLiResource 是到主服务器1的连接。
+     *     , ...
+     * )
+     */
+    static private $links;
+
+    /**
+     * 调试等级
+     * 0 不处理(交予外部程序处理)
+     * 1 显示错误并中断程序
+     * 2 直接中断程序
+     *
+     * @var Int
+     */
+    public $debug_level = 1;
+
+    /**
+     * 最近一次执行的语句
+     *
+     * @var string
+     */
+    private $last_sql;
+
+    /**
+     * 最后一次执行 query 后获取的 mysqli_result 对象
+     *
+     * @var mysqli_result
+     */
+    private $result;
+
+	/**
+     * from Data Source Name (dsn)
+     * for example: mysqli://root:851031@localhost:3306/testDb?charset=utf8
+     *
+     * @var Array ( 
+     * 		'prefix' => '', 
+     * 		'host' => '', 
+     * 		'port' => '', 
+     * 		'user' => '', 
+     * 		'pass' => '',
+     * 		'params'	=> array(),// 通过dsn传递进来的查询字串
+     * )
+     */
+    private $arrDsn;
+
+    /**
+     * 保存当前主服务器连接的信息
+     *是对 self::$masterDBInfos 数据结构某一项的引用
+     * @var array
+     * 结构如下
+     * array(// 当前集群下的主服务器
+     *     'host' => //主机名
+     *     , 'port'  => //端口
+     *     , 'user' => //用户名
+     *     , 'pass' => //密码
+     *     , 'transactionIds' => //用于存储事务id。Array ('taId1' => true, 'taId2' => false, ...);
+     *                                                        //键代表事务标志,同时也是用于保存点的标志。值表示是否为顶层事务(只能有一个顶层事务)
+     *     , 'isRunOnMaster' => Boolean   //$this->query 方法里可检验该值,判断当前查询正连接于主服务器上
+     *     , 'isUseMaster' => Boolean //指示后续查询是否要使用到主库的连接
+     *     , 'arrStatusOfUseMaster' => Array(); //当嵌套修改 isUseMaster 的值时,需要有一个结构保存之前的状态,以便恢复
+     * )
+     */
+    private $masterDBInfo;
+
+    /**
+     * 保存所有用于主服务器连接的信息
+     *
+     * @var Array  结构如下:
+     * array(
+     *     '集群1标志' => array( //
+     *         '主服务器标志' => array(//该标志通过 $this->getUniqueFlagOfLink() 获取:实质是通过 host, port, user, pass 来区别,所以同一集群如果使用不同的用户连接,会有不同的标志
+     *             'host' => //主机名
+     *             , 'port'  => //端口
+     *             , 'user' => //用户名
+     *             , 'pass' => //密码
+     *             , 'transactionIds' => //用于存储事务id。Array ('taId1' => true, 'taId2' => false, ...);
+     *                                                        //键代表事务标志,同时也是用于保存点的标志。值表示是否为顶层事务(只能有一个顶层事务)
+     *             , 'isRunOnMaster' => Boolean   //$this->query 方法里可检验该值,判断$this->link是否正链接到主服务器上
+     *             , 'isUseMaster' => Boolean //指示后续查询是否要使用到主库的连接
+     *             , 'arrStatusOfUseMaster' => Array();
+     *         )
+     *         , ...
+     *     )
+     *     , ...
+     * )
+     */
+    static private $masterDBInfos = array();
+
+
+    /**
+     * 集群信息,即用于候选的从服务器集
+     * array(
+     *     'clusterLevel' => (2 表示正常集群、3 表示低效集群)
+     *     , 'Ignore_Check_Master_Patterns' => array()
+     *
+     *     , '集群1标志' => array( // 该标志通过 $this->getMasterHost() 获取:集群只需要通过主服务器host即可区分
+     *                        array(
+     *                                  'host' =>
+     *                                  , 'port'  =>
+     *                                  , 'user' =>
+     *                                  , 'pass' =>
+     *                        )
+     *                        , ...
+     *                  )
+     *     , ...
+     * )
+     * @var Array
+     */
+    static private $slaveDBInfos;
+
+    /**
+     * 保存已从集群信息中获取的用于连接从服务器的信息
+     * @author Gxg <gaoxiaogang@gmail.com>
+     *
+     * @var Array  结构如下:
+     * array(// 当前集群下的从库信息
+     *         'host' => //主机名
+     *         , 'port'  => //端口
+     *         , 'user' => //用户名
+     *         , 'pass' => //密码
+     * )
+     */
+    private $slaveDBInfo;
+
+    /**
+     * 返回当前集群中从服务器的信息
+     *
+     * @return false | Array 格式:
+     * array(
+     *       'host' => //主机名
+     *       , 'user' => //用户名
+     *       , 'pass' => //密码
+     * )
+     */
+    private function getCurrentSlaveInfo() {
+    	$arrInvalidSlaveInfos = $this->getCurrentInvalidSlaveInfos();
+        if (isset($this->slaveDBInfo)) {
+            # 检验是否有效
+            if (!$arrInvalidSlaveInfos || !in_array($this->slaveDBInfo['host'], $arrInvalidSlaveInfos)) {
+                return $this->slaveDBInfo;
+            }
+        }
+
+        $strUniqueFlagOfCluster = $this->getUniqueFlagOfCurrentCluster();
+        if (!isset(self::$slaveDBInfos[$strUniqueFlagOfCluster])) {
+        	# 不能使用 $this->slaveDBInfo = null,否则引用的 self::$slaveDBInfos 相应的值也会变成 null
+            return false;
+        }
+
+        # 把 self::$slaveDBInfos 中已经无效的从库踢掉
+        if ($arrInvalidSlaveInfos) {
+            foreach (self::$slaveDBInfos[$strUniqueFlagOfCluster] as $key => $arrSlaveInfo) {
+                $strSlaveHost = $arrSlaveInfo['host'];
+                if (in_array($strSlaveHost, $arrInvalidSlaveInfos)) {
+                    unset(self::$slaveDBInfos[$strUniqueFlagOfCluster][$key]);
+                }
+            }
+        }
+
+        if (count(self::$slaveDBInfos[$strUniqueFlagOfCluster]) == 0) {
+        	return false;
+        }
+
+        $intRandPos = array_rand(self::$slaveDBInfos[$strUniqueFlagOfCluster]);
+	    $this->slaveDBInfo = & self::$slaveDBInfos[$strUniqueFlagOfCluster][$intRandPos];
+	    if (!isset($this->slaveDBInfo['user']) || !isset($this->slaveDBInfo['pass'])) {//从服务器没有提供连接帐户,则使用主服务器的帐户
+	        $this->slaveDBInfo['user'] = $this->masterDBInfo['user'];
+	        $this->slaveDBInfo['pass'] = $this->masterDBInfo['pass'];
+	    }
+	    if (!isset($this->slaveDBInfo['port'])) {
+	        $this->slaveDBInfo['port'] = $this->masterDBInfo['port'];
+	    }
+
+	    return $this->slaveDBInfo;
+    }
+
+    /**
+     * 集群中无效的从服务器信息
+     * 寻找从服务器时,会忽略该结构中列出的主机
+     * array(
+     *     '集群1标志' => array('从服务器host', '从服务器host', ...)
+     *     , ...
+     * )
+     *
+     * @var Array
+     */
+    static private $invalidSlaveInfos;
+
+    /**
+     * 增加无效的从服务器信息
+     *
+     * @param String $strHost
+     * @return true
+     */
+    private function addInvalidSlaveInfo($strHost) {
+        $strUniqueFlagOfCluster = $this->getUniqueFlagOfCurrentCluster();
+        if (!isset(self::$invalidSlaveInfos[$strUniqueFlagOfCluster])
+            || !in_array($strHost, self::$invalidSlaveInfos[$strUniqueFlagOfCluster])
+        ) {
+            self::$invalidSlaveInfos[$strUniqueFlagOfCluster][] = $strHost;
+        }
+        return true;
+    }
+
+    /**
+     * 获取当前集群的唯一标志(即主服务器host)
+     *
+     * @return String
+     */
+    private function getUniqueFlagOfCurrentCluster() {
+        //return "{$this->masterDBInfo['host']}_{$this->masterDBInfo['port']}_{$this->masterDBInfo['user']}";
+        return "{$this->masterDBInfo['host']}:{$this->masterDBInfo['port']}";
+    }
+
+    /**
+     * 获取当前集群下无效的从服务器信息
+     *
+     * @return false | Array
+     */
+    private function getCurrentInvalidSlaveInfos() {
+        $strUniqueFlagOfCluster = $this->getUniqueFlagOfCurrentCluster();
+        if (!isset(self::$invalidSlaveInfos[$strUniqueFlagOfCluster])) {
+            return false;
+        }
+
+        return self::$invalidSlaveInfos[$strUniqueFlagOfCluster];
+    }
+
+    /**
+     * 设置客户端连接字符集
+     *
+     * @param String $charset 连接编码。该参数决定了从各种编码的表里取出数据后,以$strCharset指定的编码返回
+     * 		比如:gbk编码存储的表数据,可以通过指定$strCharset为utf8来返回utf8编码的数据
+     * @return Boolean true:成功;false:失败.
+     */
+    public function setCharset($charset = null) {
+    	if (is_null($charset)) {
+    		if (isset($this->arrDsn['params']['charset']) && $this->arrDsn['params']['charset']) {
+    			$charset = $this->arrDsn['params']['charset'];
+    		} else {
+    			$charset = "utf8";
+    		}
+    	}
+        return $boolResult = mysqli_set_charset($this->link, $charset);
+    }
+
+    /**
+     * 取得客户端连接字符集
+     *
+     * @return String
+     */
+    public function getCharset() {
+        return $strResult = mysqli_character_set_name($this->link);
+    }
+
+    /**
+     * 获取当前连接的唯一标志
+     *
+     * @return String
+     */
+    private function getUniqueFlagOfLink() {
+        return "{$this->arrDsn['host']}_{$this->arrDsn['port']}_{$this->arrDsn['user']}_{$this->arrDsn['pass']}";
+    }
+
+    /**
+     * 构造函数
+     *
+     * @param String $dsn For Example: mysqli://root:851031@localhost/testDb
+     */
+    public function __construct($dsn) {
+        $arrUrlInfo = parse_url($dsn);
+        if (!is_array($arrUrlInfo) || !isset($arrUrlInfo['host']) || !isset($arrUrlInfo['user'])
+           || !isset($arrUrlInfo['pass']) || !isset($arrUrlInfo['path']))
+        {
+        	$this->_halt("构造参数不正确:{$dsn}");
+        	return false;
+            //解析出错时的处理
+        }
+        $this->arrDsn['host'] = $arrUrlInfo['host'];
+        $this->arrDsn['user'] = $arrUrlInfo['user'];
+        $this->arrDsn['pass'] = $arrUrlInfo['pass'];
+
+        $dbname = substr($arrUrlInfo['path'], 1);
+        if (empty($dbname)) {
+        	return $this->_halt('请先设置数据库名', '21');
+        }
+        $this->dbname = $dbname;
+
+        isset($arrUrlInfo['scheme']) && $this->arrDsn['prefix'] = $arrUrlInfo['scheme'];
+        if (!isset($arrUrlInfo['port'])) {
+        	$this->arrDsn['port'] = 3306;
+        } else {
+        	$this->arrDsn['port'] = $arrUrlInfo['port'];
+        }
+        
+        # 处理参数
+        if (isset($arrUrlInfo['query'])) {
+        	parse_str($arrUrlInfo['query'], $this->arrDsn['params']);
+        } else {
+        	$this->arrDsn['params'] = array();
+        }
+
+        $strUniqueFlagOfCluster = $this->arrDsn['host'];
+        $strUniqueFlagOfMaster = $this->getUniqueFlagOfLink();
+        if (!isset(self::$masterDBInfos[$strUniqueFlagOfCluster][$strUniqueFlagOfMaster])) {
+            self::$masterDBInfos[$strUniqueFlagOfCluster][$strUniqueFlagOfMaster] = array(
+                'host' => $this->arrDsn['host']
+                , 'port' => $this->arrDsn['port']
+                , 'user' => $this->arrDsn['user']
+                , 'pass' => $this->arrDsn['pass']
+                , 'transactionIds' => null
+                , 'isRunOnMaster' => false
+                , 'isUseMaster' => false
+                , 'arrStatusOfUseMaster' => null
+            );
+        }
+        $this->masterDBInfo = & self::$masterDBInfos[$strUniqueFlagOfCluster][$strUniqueFlagOfMaster];
+
+        if (!isset(self::$slaveDBInfos)) {
+            $this->initSlaveDBInfos();
+        }
+    }
+
+	/**
+     * 初始化 self::$slaveDBInfos 数据结构
+     *
+     * dbslaves 的结构:
+     * array(
+     *     array(
+     *         'master' => (string),//对应主库的host值
+     *         'mixed'  => array(//正常从库
+     *             'mysqld-6.yoka.com',
+     *             '192.168.0.150',
+     *             ... ,
+     *         ),
+     *         'delay_mixed'    => array(//慢速从库,比如用于提供给爬虫、或提供给翻页的比较后面的页面,无需保证特别好的服务
+     *             'mysqld-12.yoka.com',
+     *             array(
+     *                 'host'   => (string),//必须
+     *                 'user'   => (string),//必须
+     *                 'pass'   => (string),//必须
+     *                 'port'   => (int),//非必须
+     *             ),
+     *             ... ,
+     *         ),
+     *     )
+     * );
+     *
+     */
+    private function initSlaveDBInfos() {
+    	try {
+    		$dbslavesConfig = Config::getInstance()->get('dbslaves');
+    	} catch (Exception $e) {
+    		return false;
+    	}
+    	if (!is_array($dbslavesConfig)) {
+    		return false;
+    	}
+    	
+    	$db_cluster_maps = $dbslavesConfig;
+    	if (isset($_SERVER['Cluster_User_Level']) && $_SERVER['Cluster_User_Level'] == 3) {
+    		$cluster_level = 3;
+    		$strType = 'delay_mixed';
+    	} else {
+    		$cluster_level = 2;
+    		$strType = 'mixed';
+    	}
+    	foreach ($db_cluster_maps as $arrClusterInfo) {
+    		if (!isset($arrClusterInfo['master']) || !isset($arrClusterInfo[$strType]) || !is_array($arrClusterInfo[$strType]) || (count($arrClusterInfo[$strType]) == 0)) {
+    			# TODO 记录日志
+    			continue;
+    		}
+
+    		foreach($arrClusterInfo[$strType] as $mixConnectInfo) {
+                if (is_string($mixConnectInfo)) {//类似 mysqld-6.verycd.com 的值
+                    self::$slaveDBInfos[$arrClusterInfo['master']][] = array(
+                        'host'      => $mixConnectInfo
+                        , 'port'    => null
+                        , 'user'    => null
+                        , 'pass'    => null
+                    );
+                } elseif (is_array($mixConnectInfo)) {//如果$_SERVER['DataBase_Cluster_Map'] 提供客启端的密码,请使用以下格式 !
+                    if (isset($mixConnectInfo['host']) && isset($mixConnectInfo['user']) && isset($mixConnectInfo['pass'])) {
+                    	$tmpPort = isset($mixConnectInfo['port']) ? $mixConnectInfo['port'] : 3306;
+                        self::$slaveDBInfos[$arrClusterInfo['master']][] = array(
+                            'host'      => $mixConnectInfo['host']
+                            , 'port'    => $tmpPort
+                            , 'user'    => $mixConnectInfo['user']
+                            , 'pass'    => $mixConnectInfo['pass']
+                        );
+                    } else {
+//                                $this->_halt('该集群:' . $arrClusterInfo['master'] . '在$_SERVER[\'DataBase_Cluster_Map\']里提供的连接信息格式错误');
+                        # TODO 记下错误日志
+                        continue;
+                    }
+                } else {
+                    # TODO 记下错误日志
+//                            $this->_halt('该集群:' . $arrClusterInfo['master'] . '在$_SERVER[\'DataBase_Cluster_Map\']里提供的连接信息格式错误');
+                    continue;
+                }
+            }//end foreach
+    	}//end foreach
+    }
+
+    /**
+     * 获取当前主连接唯一标志
+     *
+     * @return String
+     */
+    private function getUniqueFlagOfCurrentMaster() {
+        return "{$this->masterDBInfo['host']}_{$this->masterDBInfo['port']}_{$this->masterDBInfo['user']}_{$this->masterDBInfo['pass']}";
+    }
+
+    /**
+     * 获取当前集群状态下到主服务器的连接
+     *
+     * @return mysqli | false   已有连接,返回该连接(即mysqli对象);false:没有连接
+     */
+    private function getCurrentMasterLink() {
+        $strUniqueFlagOfCurrentMaster = $this->getUniqueFlagOfCurrentMaster();
+        if (isset(self::$links[$strUniqueFlagOfCurrentMaster])) {
+            if ($this->isLink(self::$links[$strUniqueFlagOfCurrentMaster])) {
+                return self::$links[$strUniqueFlagOfCurrentMaster];
+            } else {
+            	# 曾建立过连接,但中途该连接失效了,应该对此做出处理的。
+            	if (!empty($this->masterDBInfo['transactionIds'])) {
+            		return $this->_halt("到主库的连接已失效,且该主库上存在事务,必须中断!");
+            	}
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * 当前连接是否连到主库
+     */
+    private function isRunOnMaster() {
+        return (boolean) $this->masterDBInfo['isRunOnMaster'];
+    }
+
+    /**
+     * 获取所有事务信息
+     * 只有当前集群中主服务器的连接是可用的,才有事务可言
+     *
+     * @return false | Array()   false:没有到主服务器的连接或者还没开启事务;
+     */
+    private function getTransactions() {
+        if ($this->getCurrentMasterLink()) {
+            if (!empty($this->masterDBInfo['transactionIds'])) {
+                return $this->masterDBInfo['transactionIds'];
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * 当前查询是否正运行在主服务器上并且开启了事务
+     * 该方法在 $this->query 方法里调用才是最有价值的。
+     *
+     * @return Boolean     true:是;false:否
+     */
+    private function isRunOnTransaction() {
+        if ($this->isRunOnMaster() && $this->getTransactions()) {
+            return true;
+        }
+
+        return false;
+    }
+
+    private function isLink($link) {
+        if (!($link instanceof MySQLiResource)) return false;
+        $sinfo = @mysqli_get_host_info($link);
+        return !empty($sinfo);
+    }
+
+    private function isReadSql($sql) {
+        static $r_ops = array('select','show','desc');
+        $sql = strtolower(trim($sql));
+        foreach ($r_ops as $op) {
+           if (strpos($sql,$op)===0) return true;
+        }
+        return false;
+    }
+
+    /**
+     * 连接数据库。这里创建的是真实链接,不会重用已有的链接
+     *
+     * @return false | MySQLiResource false:连接失败
+     */
+     private function connect() {
+        // 连接数据库服务器
+        $objMysqli = mysqli_init();
+
+        $connect_rs = mysqli_real_connect($objMysqli, $this->arrDsn['host'], $this->arrDsn['user'], $this->arrDsn['pass']
+                            , null, $this->arrDsn['port'], null
+                            , MYSQLI_CLIENT_COMPRESS);
+        if (!$connect_rs) {
+            return false;
+        }
+
+        # 设置字符集的代码
+
+        $this->link = $objMysqli;
+        $this->setCharset();
+        return $this->link;
+    }
+
+    /**
+     * 标志当前连接$this->link连接到主库
+     *
+     * @return Boolean
+     */
+    private function beginRunOnMaster() {
+        $this->masterDBInfo['isRunOnMaster'] = true;
+        return true;
+    }
+
+    /**
+     * 标志当前连接$this->link离开主库
+     *
+     * @return Boolean
+     */
+    private function endRunOnMaster() {
+        $this->masterDBInfo['isRunOnMaster'] = false;
+        return true;
+    }
+
+    /**
+     * 该方法只应该由 $this->xconnect()调用
+     *
+     * @return Boolean  false:连接失败
+     */
+    private function connectMaster() {
+        # These codes increase by Gxg <gaoxiaogang@gmail.com>
+        # 保证到当前集群的主服务器的连接唯一
+        $this->arrDsn['host'] = $this->masterDBInfo['host'];
+        $this->arrDsn['port'] = $this->masterDBInfo['port'];
+        $this->arrDsn['user'] = $this->masterDBInfo['user'];
+        $this->arrDsn['pass'] = $this->masterDBInfo['pass'];
+
+        $objCurrentMasterLink = $this->getCurrentMasterLink();
+        if ($objCurrentMasterLink) {
+            $this->link = $objCurrentMasterLink;
+        } else {
+            if (false === $this->connect()) {
+                return false;
+            }
+            $strUniqueFlagOfLink = $this->getUniqueFlagOfLink();
+            self::$links[$strUniqueFlagOfLink] = $this->link;
+        }
+
+        $this->beginRunOnMaster();
+
+        return true;
+    }
+
+    /**
+     * 该方法只应该由 $this->xconnect()调用
+     *
+     * @return Boolean  false:连接失败
+     *
+     */
+    private function connectSlave() {
+    	$arrCurrentSlaveInfo = $this->getCurrentSlaveInfo();
+        if ($arrCurrentSlaveInfo) {
+            $this->arrDsn['host'] = $arrCurrentSlaveInfo['host'];
+            $this->arrDsn['port'] = $arrCurrentSlaveInfo['port'];
+            $this->arrDsn['user'] = $arrCurrentSlaveInfo['user'];
+            $this->arrDsn['pass'] = $arrCurrentSlaveInfo['pass'];
+
+            $strUniqueFlagOfLink = $this->getUniqueFlagOfLink();
+            if (isset(self::$links[$strUniqueFlagOfLink]) && $this->isLink(self::$links[$strUniqueFlagOfLink])) {
+                $this->link = self::$links[$strUniqueFlagOfLink];
+            } else {
+                if(false === $this->connect()) {
+                    return false;
+                }
+                self::$links[$strUniqueFlagOfLink] = $this->link;
+            }
+            return true;
+        } else {//还是连主服务器
+            return $this->connectMaster();
+        }
+    }
+    
+    /**
+     * 
+     * 重新链接。
+     * 目前在 mysql 2006错误,并且没有运行事务时才开启该方法
+     * @return Boolean
+     */
+    private function reConnect() {
+    	if (false === $this->connect()) {
+        	return false;
+        }
+        $strUniqueFlagOfLink = $this->getUniqueFlagOfLink();
+        self::$links[$strUniqueFlagOfLink] = $this->link;
+        return true;
+    }
+
+    /**
+     * 判断查询是否要使用主服务器
+     *
+     * @return Boolean  true:使用主;false:使用从
+     */
+    private function isUseMaster() {
+    	return (boolean) $this->masterDBInfo['isUseMaster'];
+    }
+
+    /**
+     * 保存当前状态,并置为$status
+     *
+     * @param Boolean $status
+     * @return String
+     */
+    private function changeStatusOfUseMaster($status) {
+    	($status === true) || $status = false;
+
+        # 保存当前状态
+        $strMasterStatusId = $this->getUniqueMasterStatusId();
+        $this->masterDBInfo['arrStatusOfUseMaster'][$strMasterStatusId] = $this->masterDBInfo['isUseMaster'];
+        $this->masterDBInfo['isUseMaster'] = $status;
+        return $strMasterStatusId;
+    }
+
+    /**
+     * 开始使用主库
+     *
+     * @return String 返回一串标志,供$this->restore 方法使用,用于恢复上一个状态
+     */
+    public function beginUseMaster() {
+        return $this->changeStatusOfUseMaster(true);
+    }
+
+    /**
+     * 恢复采用 $strMasterStatusId 为句柄保存的上次的状态
+     *
+     * @param String $strMasterStatusId
+     * @return Boolean
+     *
+     */
+    public function restore($strMasterStatusId) {
+        # 恢复指定状态
+        if (isset($this->masterDBInfo['arrStatusOfUseMaster'][$strMasterStatusId])) {
+            $this->masterDBInfo['isUseMaster'] = $this->masterDBInfo['arrStatusOfUseMaster'][$strMasterStatusId];
+            unset($this->masterDBInfo['arrStatusOfUseMaster'][$strMasterStatusId]);
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * 开始使用从库
+     * 尽量不要使用该接口,除非你明白自己真的需要
+     *
+     */
+    public function beginUseSlave() {
+        return $this->changeStatusOfUseMaster(false);
+    }
+
+    /**
+     * 处理连接
+     *
+     * @param String $sql
+     */
+    protected function xconnect($sql) {
+    	$isUseSlave = $this->isReadSql($sql) && !$this->isUseMaster() ? true : false;
+
+        $intConnectErrorNum = 0;//连接出错次数
+        while(true) {
+            if ($isUseSlave) {
+                $isConnect = $this->connectSlave();
+            } else {
+                $isConnect = $this->connectMaster();
+            }
+
+            if (!$isConnect) {//连接失败
+                ++$intConnectErrorNum;
+                $strMasterHost = $this->masterDBInfo['host'];
+                if(4 >= $intConnectErrorNum   //允许四次重试
+                   && $this->arrDsn['host'] != $strMasterHost //错误不是发生在主服务器上
+                ) {
+                    $this->addInvalidSlaveInfo($this->arrDsn['host']);
+//                    $this->addErrorLog(self::PARAM_NO_IMPORTANCE_ERROR_DIR, $intConnectErrorNum);
+                    continue;
+                }
+
+                return $this->_halt('服务器连接失败', '01');
+            }
+
+            # 成功就退出循环
+            break;
+        }
+     }
+
+    /**
+     * 执行一条SQL
+     *
+     * @param String $sql
+     * @return resource result
+     */
+    public function query($sql) {
+    	# 每条语句都添加注释,方便debug。比如慢查询日志里知道问题出在哪个文件。
+	    $sql .= '/* ' . $_SERVER['HTTP_HOST'] . ' in '.$_SERVER['PHP_SELF'] . ' */';
+	    
+        if (Debug::$open && preg_match('#^\s*select\s#i', $sql)) {
+            $explain_query = true;
+        } else {
+            $explain_query = false;
+        }
+
+        if ($explain_query) {
+        	# 便于查看不使用缓存时的情况
+            $sql = preg_replace('#select #i', 'select sql_no_cache ', $sql);
+        }
+
+        $this->last_sql = $sql; // 临时加上
+        $intQueryErrorNum = 0;//查询出错次数
+        $intSelectErrorNum = 0;//选择数据库出错次数
+        while (true) {
+            $this->xconnect($sql);
+
+            $isSelect = mysqli_select_db($this->link, $this->dbname);
+            # 处理选择数据库错误
+            if (!$isSelect) {
+            	if ($this->isRunOnTransaction()) {
+            		return $this->_halt('进入数据库失败:存在事务,直接停机', '02');
+            	}
+            	
+            	++$intSelectErrorNum;
+            	if ($intSelectErrorNum > 4) {
+            		return $this->_halt('进入数据库失败后,重试多次后仍然失败', '02');
+            	}
+            	
+            	// 服务器链接丢失的错误,并且没有运行事务,允许重连
+            	if ($this->errno() == 2006) {
+            		if ($this->reConnect()) {
+            			continue;
+            		}
+            	}
+            	
+            	if ($this->isRunOnMaster()) {// 主库重连错误,停机
+            		return $this->_halt('进入数据库失败', '02');
+            	}
+            		
+            	// 否则将这台重库置为无效,尝试重连
+            	$this->addInvalidSlaveInfo($this->arrDsn['host']);
+                continue;
+            }
+
+            if (!$this->isReadSql($sql)) {
+//                # 如果是写入语句,记录开始时间
+//                $objProcessTimeOfWriteSqlTime = new ProcessTime();
+//                $objProcessTimeOfWriteSqlTime->start();
+
+                # 如果运行于事务中,记录该语句
+                if ($this->isRunOnTransaction()) {
+                    $this->masterDBInfo['arrTransactionSqls'][] = $sql;
+                }
+            }
+
+            $query = mysqli_query($this->link, $sql);
+
+//            # 记录慢写入语句
+//            if (!$this->isReadSql($sql)) {
+//                if (($runTimeOfWriteSql = $objProcessTimeOfWriteSqlTime->getFinalTime()) > 1) {
+//                    $this->addSlowWriteSqlLog($runTimeOfWriteSql);
+//                }
+//            }
+            # 处理查询错误
+            if (!$query) {
+            	if ($this->isRunOnTransaction()) {
+            		return $this->_halt('查询数据库失败:存在事务,直接停机', '21');
+            	}
+            	++$intQueryErrorNum;
+            	if ($intQueryErrorNum > 4) {
+            		return $this->_halt('查询数据库失败后,重试多次后仍然失败', '21');
+            	}
+            	
+            	// 服务器链接丢失的错误,并且没有运行事务,允许重连
+            	if (in_array($this->errno(), array(2006, 2013))) {
+            		if ($this->reConnect()) {
+            			continue;
+            		}
+            	}
+
+                static $arrConnectErrnos = array(
+                    1053      //在操作过程中服务器关闭。
+                    , 1030    //从存储引擎中获得错误
+                    , 126     //表损坏
+                );
+                
+                if ($this->isRunOnMaster()) {// 主库重连错误,停机
+            		return $this->_halt('查询主数据库失败', '21');
+            	}
+            		
+                // 否则将这台重库置为无效,尝试重连
+                if(in_array($this->errno(), $arrConnectErrnos)) {//指定的错误号
+                    $this->addInvalidSlaveInfo($this->arrDsn['host']);//由于连接失效导致的查询出错,将这台服务器标记为无效
+                    continue;
+                }
+
+                return $this->_halt('查询数据库失败,不能处理的错误类型', '21');
+            }
+            break;
+        }
+
+        # 走到这里,说明成功的执行了写入sql
+        if ($this->canSetCookieForMasterDBHasWrite($sql)) {// 如果写入语句成功,就写一个保存时间为$tmp_expiration秒的cookie
+        	$tmp_expiration = 2*60;
+			Cookie::set(KIF_MASTER_DB_HAS_WRITE_COOKIE_KEY, '1', $tmp_expiration);// 设置$tmp_expiration秒的cookie
+        }
+
+        self::$queries++;
+        if ($explain_query) {
+            $begin_microtime = Debug::getTime();
+
+            self::$intQueriesTotalTime += $begin_microtime;
+
+            $explainSql = 'explain ' . $sql;
+            $equery = mysqli_query($this->link, $explainSql);
+            $explain = $this->fetch($equery);
+            $this->freeResult($equery);
+            Debug::db($this->getLinkDesc(), $this->dbname, $explainSql, Debug::getTime() - $begin_microtime, $explain);
+        }
+
+        if (!$this->isReadSql($sql)) {
+        	$begin_microtime = Debug::getTime();
+        	Debug::db($this->getLinkDesc(), $this->dbname, $sql, Debug::getTime() - $begin_microtime, $query);
+        }
+        $this->isRunOnMaster() && $this->endRunOnMaster();
+
+        return $query;
+    }
+
+    /**
+     *
+     * 判断指定的sql执行后,能否设置后续查询转到主库的cookie
+     * @param string $sql
+     * @return boolean
+     */
+    private function canSetCookieForMasterDBHasWrite($sql) {
+    	if (Request::isCLI()) {
+    		return false;
+    	}
+		if ($this->isReadSql($sql)) {
+			return false;
+		}
+
+		# xhprof_logs表是用来记录慢查询的,没必要因为这个表写入了一次就把后续的请求都转到主库。
+		if (preg_match('#(xhprof_logs)#', strtolower($sql))) {
+			return false;
+		}
+
+		return true;
+    }
+
+    public function fetchOne($sql) {
+    	$begin_microtime = Debug::getTime();
+
+        $res = $this->query($sql);
+        if (!$res) {
+        	return false;
+        }
+        $result = $this->fetch($res);
+
+        $this->freeResult($res);
+
+        Debug::db($this->getLinkDesc(), $this->dbname, $this->last_sql, Debug::getTime() - $begin_microtime, $result);
+
+        return $result;
+    }
+
+    /**
+     * 获取连接描述,用于Debug输出
+     */
+    private function getLinkDesc() {
+    	$thread_id = mysqli_thread_id($this->link);
+    	return "mysqli://{$this->arrDsn['user']}:{$this->arrDsn['port']}@{$this->arrDsn['host']} (thread_id: {$thread_id})";
+    }
+
+    /**
+     * 执行一条SQL并返回此查询包含的所有数据(2维数组)
+     *
+     * @param string $sql
+     * @param string $associateKey 如果指定了$associateKey,返回结果以 每条记录的$associateKey字段做数组下标
+     * @return false | array
+     */
+    public function fetchAll($sql, $associateKey = null) {
+    	$begin_microtime = Debug::getTime();
+
+        $res = $this->query($sql);
+        if (!$res) {
+            return false;
+        }
+
+        $result = array();
+        if ($associateKey) {
+            while (true) {
+            	$row = $this->fetch($res);
+            	if (!$row) {
+            		break;
+            	}
+            	if (isset($row[$associateKey])) {
+            		$result[$row[$associateKey]] = $row;
+            	} else {
+            		$result[] = $row;
+            	}
+            }
+        } else {
+            while (true) {
+            	$row = $this->fetch($res);
+            	if (!$row) {
+            		break;
+            	}
+                $result[] = $row;
+            }
+        }
+
+        $this->freeResult($res);
+
+        Debug::db($this->getLinkDesc(), $this->dbname, $this->last_sql, Debug::getTime() - $begin_microtime, $result);
+
+        return $result;
+    }
+
+    /**
+     * 执行SQL语句并返回第一行第一列
+     *
+     * @param string $sql
+     * @return false | scala
+     */
+    public function fetchSclare($sql) {
+    	$begin_microtime = Debug::getTime();
+
+        $result = $this->fetchOne($sql);
+        if (!$result) {
+        	return false;
+        }
+        $result = array_shift($result);
+
+        Debug::db($this->getLinkDesc(), $this->dbname, $this->last_sql, Debug::getTime() - $begin_microtime, $result);
+
+        return $result;
+    }
+
+    /**
+     * 返回上一步 INSERT 查询中产生的 AUTO_INCREMENT 的 ID 号;
+     * 或者 返回 update 语句中 last_insert_id()函数中表达式的值。
+     *
+     * !!请记住,一定紧接在insert 或 update 语句后执行该方法,否则$this->link可能已经指向别的服务器了
+     *
+     * @return int | NULL >0:成功取到;0:没取到;NULL$this->link无效
+     */
+    public function insertId() {
+        return mysqli_insert_id($this->link);
+    }
+
+    /**
+     * $this->insertId() 的别名
+     * !!请记住,一定紧接在insert 或 update 语句后执行该方法,否则$this->link可能已经指向别的服务器了
+     * @return int | NULL >0:成功取到;0:没取到;NULL$this->link无效
+     */
+    public function getLastInsertId() {
+    	return $this->insertId();
+    }
+
+    /**
+     * 返回最近一次 INSERT,UPDATE 或 DELETE 查询所影响的记录行数。
+     * @return int | null 返回值 >= 0:成功;等于 -1:最后一条查询错误;null$this->link无效
+     **/
+    public function affectedRows()
+    {
+        return mysqli_affected_rows($this->link);
+    }
+
+    public function fetch($query, $resulttype = MYSQLI_ASSOC) {
+        return mysqli_fetch_array($query, $resulttype);
+    }
+
+    protected function freeResult($query) {
+        return mysqli_free_result($query);
+    }
+
+    /**
+     * 生成唯一的字符串作为事务的唯一id
+     *
+     * @return String
+     */
+    static private function getUniqueTransactionId() {
+        return self::getUniqueId('TAId');
+    }
+
+    /**
+     * 生成唯一的字符串作为保存当前主服务器状态的唯一id
+     *
+     * @return String
+     */
+    static private function getUniqueMasterStatusId() {
+        return self::getUniqueId('MSId');
+    }
+
+    /**
+     * 生成唯一id
+     *
+     * @param String $prefix
+     * @return String
+     */
+    static private function getUniqueId($prefix = '') {
+        if(!is_string($prefix)) {
+            $prefix = '';
+        }
+        return uniqid($prefix . '_'.rand());
+    }
+
+    /**
+     * 返回上一个错误文本,如果没有出错则返回 ''(空字符串)。
+     * 如果没有指定连接资源号,则使用上一个成功打开的连接从数据库服务器提取错误信息。
+     *
+     * @return String
+     */
+    public function error() {
+        return @mysqli_error($this->link);
+    }
+
+    /**
+     * 返回上一个错误号
+     *
+     * @return int | NULL
+     */
+    public function errno() {
+        return @mysqli_errno($this->link);
+    }
+
+    /**
+     * 返回上一个连接错误
+     *
+     * @return String
+     */
+    public function connect_error() {
+        return @mysqli_connect_error();
+    }
+
+    /**
+     * 返回上一个连接的错误号
+     *
+     * @return int
+     */
+    public function connect_errno() {
+        return @mysqli_connect_errno();
+    }
+
+    /**
+     * 根据 $this->debug_level 处理一些异常情况
+     * 1 直接输出错误信息并中断程序
+     * 2 直接中断程序
+     * 其他情况不处理错误,返回flase,修改错误代号和本函数所提供的错误信息,最后的是MySQL服务器提供的信息
+     *
+     * @param String $msg
+     * @param String $errorcode
+     * @return Array
+     */
+    function _halt($msg, $errorcode = '00') {
+        switch ($this->debug_level) {
+            case 1:
+                ob_clean();
+                header("HTTP/1.0 500 Server Error");
+                header("Expires: ".gmdate("D, d M Y H:i:s", time())." GMT");
+                header("Last-Modified: ".gmdate("D, d M Y H:i:s", time())." GMT");
+                header("Cache-Control: private");
+//                $out = file_get_contents(ROOT_PATH . '/mysql.html');
+				$out = '$the_error';
+                $out = str_replace('$the_error', $msg.'<hr />'.$this->error().' No.'.$this->errno()."<!-- {$this->last_sql} -->", $out);
+
+//                $this->addErrorLog(null, null, $msg);
+
+                echo $out;
+                exit;
+                break;
+            case 2:
+                ob_clean();
+                header("HTTP/1.0 500 Server Error");
+                header("Expires: ".gmdate("D, d M Y H:i:s", time())." GMT");
+                header("Last-Modified: ".gmdate("D, d M Y H:i:s", time())." GMT");
+                header("Cache-Control: private");
+                echo $this->connect_error(), "<br />";
+                echo $this->connect_errno(), "<br />";
+                echo "{$msg}<br />";
+                exit('MySQL.');
+                break;
+            default:
+                $this->errorcode = array($errorcode, $msg.':'.$this->last_sql, $this->errno().": ".$this->error());
+                return false;
+                break;
+        }
+    }
+
+    ########### TRANSACTION ##########
+    /**
+     * 开启事务
+     *
+     * @return false | string false:失败;string:成功返回事务标志
+     */
+    public function startTransaction() {
+        $strTransactionId = self::getUniqueTransactionId();
+
+        if ($this->getTransactions()) {//已存在事务
+            if ($this->setSavePoint($strTransactionId)) {
+            	$this->masterDBInfo['transactionIds'][$strTransactionId] = false;
+                return $strTransactionId;
+            }
+        } else {
+	        # 初次开启事务
+	        if (true === $this->query('START TRANSACTION;')) {//如果没有建立到当前主服务器的连接,该操作会隐式的建立
+	            $this->masterDBInfo['transactionIds'][$strTransactionId] = true;
+	            return $strTransactionId;
+	        }
+        }
+
+        # 开启事务失败。返回一个独特的字符串,该字符串是不可能出现在 事务id数组中的
+        return false;
+    }
+
+    /**
+     * 回滚父事务
+     *
+     * @param String $strTransactionId
+     * @return Boolean
+     */
+    private function _rollbackRootTransaction($strTransactionId) {
+        if ($this->isRootTransaction($strTransactionId)) {//父事务
+            $this->masterDBInfo['transactionIds'] = null;
+            $this->masterDBInfo['arrTransactionSqls'] = array();
+            return $this->query('ROLLBACK;');
+        }
+        return false;
+    }
+
+    /**
+     * 回滚子事务
+     *
+     * @param String $strTransactionId
+     * @return Boolean
+     */
+    private function _rollbackSubTransaction($strTransactionId) {
+        if($this->isSubTransaction($strTransactionId)) {//子事务
+            $boolStatusTmp = $this->rollbackToSavePoint($strTransactionId);
+            $this->releaseSavePoint($strTransactionId);
+            unset($this->masterDBInfo['transactionIds'][$strTransactionId]);
+            return $boolStatusTmp;
+        }
+        return false;
+    }
+
+    /**
+     * 撤消指定事务
+     *
+     * @param String $strTransactionId
+     * @return Bollean true:成功;false:失败
+     */
+    public function rollback($strTransactionId) {
+        if ($this->isRootTransaction($strTransactionId)) {//父事务
+            return $this->_rollbackRootTransaction($strTransactionId);
+        } elseif ($this->isSubTransaction($strTransactionId)) {//子事务
+            return $this->_rollbackSubTransaction($strTransactionId);
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * 提交父事务
+     *
+     * @param String $strTransactionId
+     * @return Boolean
+     */
+    private function _commitRootTransaction($strTransactionId) {
+        if ($this->isRootTransaction($strTransactionId)) {//父事务
+            $this->masterDBInfo['transactionIds'] = null;
+            $this->masterDBInfo['arrTransactionSqls'] = array();
+            return $this->query('COMMIT;');
+        }
+        return false;
+    }
+
+    /**
+     * 提交子事务
+     *
+     * @param String $strTransactionId
+     * @return Boolean
+     */
+    private function _commitSubTransaction($strTransactionId) {
+        if ($this->isSubTransaction($strTransactionId)) {//子事务
+            $this->releaseSavePoint($strTransactionId);
+            unset($this->masterDBInfo['transactionIds'][$strTransactionId]);
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * 提交指定事务
+     *
+     * @param String $strTransactionId
+     * @return Boolean true:成功;false:失败
+     */
+    public function commit($strTransactionId) {
+        if ($this->isRootTransaction($strTransactionId)) {//父事务
+            return $this->_commitRootTransaction($strTransactionId);
+        } elseif ($this->isSubTransaction($strTransactionId)) {//子事务
+            return $this->_commitSubTransaction($strTransactionId);
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * 设置子事务的保存点,用于支持子事务的回滚
+     *
+     * @param String $SPId 应该被传递的值是事务的唯一id,即调用self::getUniqueTransactionId()生成的
+     * @return Boolean  true:成功;false:失败
+     */
+    private function setSavePoint($SPId) {
+        if (true === $this->query("SAVEPOINT {$SPId}")) {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * 获取指定事务的类型(父事务 还是 子事务)
+     * 只有当前集群中主服务器的连接是可用的,才有事务可言
+     *
+     * @param String $strTransactionId
+     * @return Boolean | null true:父事务;false:子事务;null:无效
+     */
+    private function getTransactionTypeById($strTransactionId) {
+        if ($this->getCurrentMasterLink()) {
+            if (isset($this->masterDBInfo['transactionIds'][$strTransactionId])) {
+                return $this->masterDBInfo['transactionIds'][$strTransactionId];
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * 是否父事务
+     *
+     * @param String $strTransactionId
+     * @return Boolean      true:是;false:否
+     */
+    private function isRootTransaction($strTransactionId) {
+        if (true === $this->getTransactionTypeById($strTransactionId)) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * 是否子事务
+     *
+     * @param String $strTransactionId
+     * @return Boolean      true:是;false:否
+     */
+    private function isSubTransaction($strTransactionId) {
+        if (false === $this->getTransactionTypeById($strTransactionId)) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * 回滚到指定事务点
+     *
+     * @param String $SPId
+     * @return Boolean  true:成功;false:失败
+     */
+    private function rollbackToSavePoint($SPId) {
+        # 只对子事务的回滚点操作
+        if ($this->isSubTransaction($SPId)) {
+            if (true === $this->query("ROLLBACK TO SAVEPOINT {$SPId}")) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 释放事务保存点
+     *
+     * @param String $SPId
+     * @return Boolean  true:成功;false:失败
+     */
+    private function releaseSavePoint($SPId) {
+        # 只对子事务的回滚点操作
+        if ($this->isSubTransaction($SPId)) {
+            if (true === $this->query("RELEASE SAVEPOINT {$SPId}")) {
+                return true;
+            }
+        }
+        return false;
+    }
+}

+ 80 - 0
KIF/Db/Transaction.class.php

@@ -0,0 +1,80 @@
+<?php
+namespace KIF\Db;
+
+use KIF\Exception\ParamsException;
+use KIF\Db\MySQLi;
+use KIF\Core\Config;
+
+/**
+ * 数据库事务类
+ * 主要是包装 MySQLite 的事务方法。供前端类、前端php响应页面调用。
+ * @author gaoxiaogang@gmail.com
+ *
+ */
+class Transaction {
+
+    /**
+     * 数据库实例
+     *
+     * @var KIF\Db\MySQLi
+     */
+    protected $db;
+
+    public function __construct($cluster_flag = 'default') {
+    	$appConfig = Config::getInstance()->current();
+    	$dbConfig = $appConfig['db'];
+    	
+    	if (!$dbConfig || !isset($dbConfig[$cluster_flag]) || !is_string($dbConfig[$cluster_flag])) {
+    		throw new ParamsException("load config error:{$dbConfig}");
+    	}
+   
+        $this->db = new MySQLi($dbConfig[$cluster_flag]);
+    }
+
+    /**
+     * 开始事务
+     * @return false | string false:失败;string:成功返回事务标志
+     */
+    public function start() {
+    	return $this->db->startTransaction();
+    }
+
+    /**
+     * 提交事务
+     * @param string $strTransactionId
+     * @return Boolean
+     */
+    public function commit($strTransactionId) {
+    	return $this->db->commit($strTransactionId);
+    }
+
+    /**
+     * 回滚事务
+     * @param string $strTransactionId
+     * @return Boolean
+     */
+    public function rollback($strTransactionId) {
+        return $this->db->rollback($strTransactionId);
+    }
+
+    /**
+     *
+     * 开始使用主库。后续的所有读查询,都会被强制到主库
+     *
+     * @return String 返回一串标志,供$this->restore 方法使用,用于恢复上一个状态
+     */
+    public function beginUseMaster() {
+    	return $this->db->beginUseMaster();
+    }
+
+    /**
+     * 恢复采用 $strMasterStatusId 为句柄保存的上次的状态
+     *
+     * @param String $strMasterStatusId
+     * @return Boolean
+     *
+     */
+    public function restore($strMasterStatusId) {
+    	return $this->db->restore($strMasterStatusId);
+    }
+}

+ 96 - 0
KIF/Debug/.svn/entries

@@ -0,0 +1,96 @@
+10
+
+dir
+924
+svn://182.92.3.30/project/onepage/KIF/Debug
+svn://182.92.3.30/project
+
+
+
+2016-01-21T06:27:41.361155Z
+137
+yubin
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+140960d9-11d8-4647-83e1-275f326d1242
+
+FirePHP.class.php
+file
+
+
+
+
+2016-02-15T00:10:45.000000Z
+b57d29c96b61cfe882f66ebddcf3ef03
+2016-01-21T06:27:41.361155Z
+137
+yubin
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+63783
+
+Debug.class.php
+file
+
+
+
+
+2016-02-15T00:10:45.000000Z
+8705473019919b26ddd8eca6d83c7ab4
+2016-01-21T06:27:41.361155Z
+137
+yubin
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+9935
+

+ 5 - 0
KIF/Debug/.svn/prop-base/Debug.class.php.svn-base

@@ -0,0 +1,5 @@
+K 14
+svn:executable
+V 1
+*
+END

+ 5 - 0
KIF/Debug/.svn/prop-base/FirePHP.class.php.svn-base

@@ -0,0 +1,5 @@
+K 14
+svn:executable
+V 1
+*
+END

部分文件因文件數量過多而無法顯示